frontend-plugin-api: return output from extension factories instead
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': minor
|
||||
---
|
||||
|
||||
Extensions now return their output from the factory function rather than calling `bind(...)`.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/frontend-app-api': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
'@backstage/plugin-graphiql': patch
|
||||
'@backstage/plugin-search-react': patch
|
||||
---
|
||||
|
||||
Internal updates to match changes in the experimental `@backstage/frontend-plugin-api`.
|
||||
@@ -79,8 +79,8 @@ const homePageExtension = createExtension({
|
||||
children: coreExtensionData.reactElement,
|
||||
title: titleExtensionDataRef,
|
||||
},
|
||||
factory({ bind }) {
|
||||
bind({ children: homePage, title: 'just a title' });
|
||||
factory() {
|
||||
return { children: homePage, title: 'just a title' };
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -113,18 +113,18 @@ export function convertLegacyApp(
|
||||
output: {
|
||||
element: coreExtensionData.reactElement,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
factory({ inputs }) {
|
||||
// Clone the root element, this replaces the FlatRoutes declared in the app with out content input
|
||||
bind({
|
||||
return {
|
||||
element: React.cloneElement(rootEl, undefined, inputs.content.element),
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
const CoreNavOverride = createExtension({
|
||||
id: 'core.nav',
|
||||
attachTo: { id: 'core.layout', input: 'nav' },
|
||||
output: {},
|
||||
factory() {},
|
||||
factory: () => ({}),
|
||||
disabled: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -40,9 +40,9 @@ export const Core = createExtension({
|
||||
output: {
|
||||
root: coreExtensionData.reactElement,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
bind({
|
||||
factory({ inputs }) {
|
||||
return {
|
||||
root: inputs.root.element,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -42,14 +42,14 @@ export const CoreLayout = createExtension({
|
||||
output: {
|
||||
element: coreExtensionData.reactElement,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
bind({
|
||||
factory({ inputs }) {
|
||||
return {
|
||||
element: (
|
||||
<SidebarPage>
|
||||
{inputs.nav.element}
|
||||
{inputs.content.element}
|
||||
</SidebarPage>
|
||||
),
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -82,8 +82,8 @@ export const CoreNav = createExtension({
|
||||
output: {
|
||||
element: coreExtensionData.reactElement,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
bind({
|
||||
factory({ inputs }) {
|
||||
return {
|
||||
element: (
|
||||
<Sidebar>
|
||||
<SidebarLogo />
|
||||
@@ -93,6 +93,6 @@ export const CoreNav = createExtension({
|
||||
))}
|
||||
</Sidebar>
|
||||
),
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ export const CoreRoutes = createExtension({
|
||||
output: {
|
||||
element: coreExtensionData.reactElement,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
factory({ inputs }) {
|
||||
const Routes = () => {
|
||||
const element = useRoutes(
|
||||
inputs.routes.map(route => ({
|
||||
@@ -46,8 +46,8 @@ export const CoreRoutes = createExtension({
|
||||
|
||||
return element;
|
||||
};
|
||||
bind({
|
||||
return {
|
||||
element: <Routes />,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ const extBase = {
|
||||
id: 'test',
|
||||
attachTo: { id: 'core', input: 'root' },
|
||||
output: {},
|
||||
factory() {},
|
||||
factory: () => ({}),
|
||||
};
|
||||
|
||||
describe('createAppGraph', () => {
|
||||
@@ -62,7 +62,7 @@ describe('createAppGraph', () => {
|
||||
attachTo: { id: 'core.routes', input: 'route' },
|
||||
inputs: {},
|
||||
output: {},
|
||||
factory() {},
|
||||
factory: () => ({}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -45,8 +45,8 @@ const simpleExtension = createExtension({
|
||||
other: z.number().optional(),
|
||||
}),
|
||||
),
|
||||
factory({ bind, config }) {
|
||||
bind({ test: config.output, other: config.other });
|
||||
factory({ config }) {
|
||||
return { test: config.output, other: config.other };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -114,8 +114,8 @@ describe('instantiateAppNodeTree', () => {
|
||||
output: {
|
||||
inputMirror: inputMirrorDataRef,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
bind({ inputMirror: inputs });
|
||||
factory({ inputs }) {
|
||||
return { inputMirror: inputs };
|
||||
},
|
||||
}),
|
||||
),
|
||||
@@ -158,8 +158,8 @@ describe('instantiateAppNodeTree', () => {
|
||||
output: {
|
||||
inputMirror: inputMirrorDataRef,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
bind({ inputMirror: inputs });
|
||||
factory({ inputs }) {
|
||||
return { inputMirror: inputs };
|
||||
},
|
||||
}),
|
||||
),
|
||||
@@ -264,8 +264,8 @@ describe('createAppNodeInstance', () => {
|
||||
output: {
|
||||
inputMirror: inputMirrorDataRef,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
bind({ inputMirror: inputs });
|
||||
factory({ inputs }) {
|
||||
return { inputMirror: inputs };
|
||||
},
|
||||
}),
|
||||
),
|
||||
@@ -326,8 +326,8 @@ describe('createAppNodeInstance', () => {
|
||||
test1: testDataRef,
|
||||
test2: testDataRef,
|
||||
},
|
||||
factory({ bind }) {
|
||||
bind({ test1: 'test', test2: 'test2' });
|
||||
factory({}) {
|
||||
return { test1: 'test', test2: 'test2' };
|
||||
},
|
||||
}),
|
||||
),
|
||||
@@ -348,8 +348,8 @@ describe('createAppNodeInstance', () => {
|
||||
output: {
|
||||
test: testDataRef,
|
||||
},
|
||||
factory({ bind }) {
|
||||
bind({ nonexistent: 'test' } as any);
|
||||
factory({}) {
|
||||
return { nonexistent: 'test' } as any;
|
||||
},
|
||||
}),
|
||||
),
|
||||
@@ -376,7 +376,7 @@ describe('createAppNodeInstance', () => {
|
||||
),
|
||||
},
|
||||
output: {},
|
||||
factory() {},
|
||||
factory: () => ({}),
|
||||
}),
|
||||
),
|
||||
attachments: new Map(),
|
||||
@@ -417,7 +417,7 @@ describe('createAppNodeInstance', () => {
|
||||
}),
|
||||
},
|
||||
output: {},
|
||||
factory() {},
|
||||
factory: () => ({}),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
@@ -447,7 +447,7 @@ describe('createAppNodeInstance', () => {
|
||||
id: 'core.test',
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
output: {},
|
||||
factory() {},
|
||||
factory: () => ({}),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
@@ -481,7 +481,7 @@ describe('createAppNodeInstance', () => {
|
||||
),
|
||||
},
|
||||
output: {},
|
||||
factory() {},
|
||||
factory: () => ({}),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
@@ -515,7 +515,7 @@ describe('createAppNodeInstance', () => {
|
||||
),
|
||||
},
|
||||
output: {},
|
||||
factory() {},
|
||||
factory: () => ({}),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
@@ -543,7 +543,7 @@ describe('createAppNodeInstance', () => {
|
||||
),
|
||||
},
|
||||
output: {},
|
||||
factory() {},
|
||||
factory: () => ({}),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
|
||||
@@ -113,26 +113,25 @@ export function createAppNodeInstance(options: {
|
||||
}
|
||||
|
||||
try {
|
||||
extension.factory({
|
||||
const namedOutputs = extension.factory({
|
||||
source,
|
||||
config: parsedConfig,
|
||||
bind: namedOutputs => {
|
||||
for (const [name, output] of Object.entries(namedOutputs)) {
|
||||
const ref = extension.output[name];
|
||||
if (!ref) {
|
||||
throw new Error(`unknown output provided via '${name}'`);
|
||||
}
|
||||
if (extensionData.has(ref.id)) {
|
||||
throw new Error(
|
||||
`duplicate extension data '${ref.id}' received via output '${name}'`,
|
||||
);
|
||||
}
|
||||
extensionData.set(ref.id, output);
|
||||
extensionDataRefs.add(ref);
|
||||
}
|
||||
},
|
||||
inputs: resolveInputs(extension.inputs, attachments),
|
||||
});
|
||||
|
||||
for (const [name, output] of Object.entries(namedOutputs)) {
|
||||
const ref = extension.output[name];
|
||||
if (!ref) {
|
||||
throw new Error(`unknown output provided via '${name}'`);
|
||||
}
|
||||
if (extensionData.has(ref.id)) {
|
||||
throw new Error(
|
||||
`duplicate extension data '${ref.id}' received via output '${name}'`,
|
||||
);
|
||||
}
|
||||
extensionData.set(ref.id, output);
|
||||
extensionDataRefs.add(ref);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Failed to instantiate extension '${id}'${
|
||||
|
||||
@@ -21,7 +21,7 @@ const extBaseConfig = {
|
||||
id: 'test',
|
||||
attachTo: { id: 'nonexistent', input: 'nonexistent' },
|
||||
output: {},
|
||||
factory() {},
|
||||
factory: () => ({}),
|
||||
};
|
||||
|
||||
const extension = createExtension(extBaseConfig);
|
||||
|
||||
@@ -62,12 +62,12 @@ function createTestExtension(options: {
|
||||
element: coreExtensionData.reactElement,
|
||||
}),
|
||||
},
|
||||
factory({ bind }) {
|
||||
bind({
|
||||
factory() {
|
||||
return {
|
||||
path: options.path,
|
||||
routeRef: options.routeRef,
|
||||
element: React.createElement('div'),
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -170,10 +170,9 @@ export interface CreateExtensionOptions<
|
||||
// (undocumented)
|
||||
factory(options: {
|
||||
source?: BackstagePlugin;
|
||||
bind(values: Expand<ExtensionDataValues<TOutput>>): void;
|
||||
config: TConfig;
|
||||
inputs: Expand<ExtensionInputValues<TInputs>>;
|
||||
}): void;
|
||||
}): Expand<ExtensionDataValues<TOutput>>;
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
@@ -313,13 +312,12 @@ export interface Extension<TConfig> {
|
||||
// (undocumented)
|
||||
factory(options: {
|
||||
source?: BackstagePlugin;
|
||||
bind(values: ExtensionInputValues<any>): void;
|
||||
config: TConfig;
|
||||
inputs: Record<
|
||||
string,
|
||||
undefined | Record<string, unknown> | Array<Record<string, unknown>>
|
||||
>;
|
||||
}): void;
|
||||
}): ExtensionDataValues<any>;
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
|
||||
@@ -66,8 +66,8 @@ const wrapInBoundaryExtension = (element: JSX.Element) => {
|
||||
path: coreExtensionData.routePath,
|
||||
routeRef: coreExtensionData.routeRef.optional(),
|
||||
},
|
||||
factory({ bind, source }) {
|
||||
bind({
|
||||
factory({ source }) {
|
||||
return {
|
||||
routeRef,
|
||||
path: '/',
|
||||
element: (
|
||||
@@ -75,7 +75,7 @@ const wrapInBoundaryExtension = (element: JSX.Element) => {
|
||||
{element}
|
||||
</ExtensionBoundary>
|
||||
),
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -58,12 +58,11 @@ export function createApiExtension<
|
||||
output: {
|
||||
api: coreExtensionData.apiFactory,
|
||||
},
|
||||
factory({ bind, config, inputs }) {
|
||||
factory({ config, inputs }) {
|
||||
if (typeof factory === 'function') {
|
||||
bind({ api: factory({ config, inputs }) });
|
||||
} else {
|
||||
bind({ api: factory });
|
||||
return { api: factory({ config, inputs }) };
|
||||
}
|
||||
return { api: factory };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,14 +41,12 @@ export function createNavItemExtension(options: {
|
||||
output: {
|
||||
navTarget: coreExtensionData.navTarget,
|
||||
},
|
||||
factory: ({ bind, config }) => {
|
||||
bind({
|
||||
navTarget: {
|
||||
title: config.title,
|
||||
icon,
|
||||
routeRef,
|
||||
},
|
||||
});
|
||||
},
|
||||
factory: ({ config }) => ({
|
||||
navTarget: {
|
||||
title: config.title,
|
||||
icon,
|
||||
routeRef,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import { useAnalytics } from '@backstage/core-plugin-api';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { PortableSchema } from '../schema';
|
||||
import {
|
||||
ExtensionInputValues,
|
||||
coreExtensionData,
|
||||
createExtensionInput,
|
||||
createPlugin,
|
||||
@@ -127,16 +126,14 @@ describe('createPageExtension', () => {
|
||||
loader: async () => <div>Component</div>,
|
||||
});
|
||||
|
||||
extension.factory({
|
||||
bind: (values: ExtensionInputValues<any>) =>
|
||||
renderWithEffects(
|
||||
wrapInTestApp(values.element as unknown as JSX.Element),
|
||||
),
|
||||
const output = extension.factory({
|
||||
source: createPlugin({ id: 'plugin ' }),
|
||||
config: { path: '/' },
|
||||
inputs: {},
|
||||
});
|
||||
|
||||
renderWithEffects(wrapInTestApp(output.element as unknown as JSX.Element));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(captureEvent).toHaveBeenCalledWith(
|
||||
'_ROUTABLE-EXTENSION-RENDERED',
|
||||
|
||||
@@ -75,14 +75,14 @@ export function createPageExtension<
|
||||
path: coreExtensionData.routePath,
|
||||
routeRef: coreExtensionData.routeRef.optional(),
|
||||
},
|
||||
factory({ bind, config, inputs, source }) {
|
||||
factory({ config, inputs, source }) {
|
||||
const ExtensionComponent = lazy(() =>
|
||||
options
|
||||
.loader({ config, inputs })
|
||||
.then(element => ({ default: () => element })),
|
||||
);
|
||||
|
||||
bind({
|
||||
return {
|
||||
path: config.path,
|
||||
routeRef: options.routeRef,
|
||||
element: (
|
||||
@@ -90,7 +90,7 @@ export function createPageExtension<
|
||||
<ExtensionComponent />
|
||||
</ExtensionBoundary>
|
||||
),
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ export function createThemeExtension(theme: AppTheme) {
|
||||
output: {
|
||||
theme: coreExtensionData.theme,
|
||||
},
|
||||
factory({ bind }) {
|
||||
bind({ theme });
|
||||
},
|
||||
factory: () => ({ theme }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,71 +24,211 @@ function unused(..._any: any[]) {}
|
||||
|
||||
describe('createExtension', () => {
|
||||
it('should create an extension with a simple output', () => {
|
||||
const extension = createExtension({
|
||||
const baseConfig = {
|
||||
id: 'test',
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
output: {
|
||||
foo: stringData,
|
||||
},
|
||||
factory({ bind }) {
|
||||
bind({
|
||||
};
|
||||
const extension = createExtension({
|
||||
...baseConfig,
|
||||
factory() {
|
||||
return {
|
||||
foo: 'bar',
|
||||
});
|
||||
bind({
|
||||
// @ts-expect-error
|
||||
foo: 3,
|
||||
});
|
||||
bind({
|
||||
// @ts-expect-error
|
||||
bar: 'bar',
|
||||
});
|
||||
// @ts-expect-error
|
||||
bind({});
|
||||
// @ts-expect-error
|
||||
bind();
|
||||
// @ts-expect-error
|
||||
bind('bar');
|
||||
};
|
||||
},
|
||||
});
|
||||
expect(extension.id).toBe('test');
|
||||
|
||||
// When declared as an error function without a block the TypeScript errors
|
||||
// are a more specific and will point at the property that is problematic.
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
factory: () => ({
|
||||
// @ts-expect-error
|
||||
foo: 3,
|
||||
}),
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
factory: () =>
|
||||
// @ts-expect-error
|
||||
({
|
||||
bar: 'bar',
|
||||
}),
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
factory: () =>
|
||||
// @ts-expect-error
|
||||
({}),
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
factory: () =>
|
||||
// @ts-expect-error
|
||||
undefined,
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
factory: () =>
|
||||
// @ts-expect-error
|
||||
'bar',
|
||||
});
|
||||
|
||||
// When declared as a function with a block the TypeScript error will instead
|
||||
// be tied to the factory function declaration itself, but the error messages
|
||||
// is still helpful and points to part of the return type that is problematic.
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
// @ts-expect-error
|
||||
factory() {
|
||||
return {
|
||||
foo: 3,
|
||||
};
|
||||
},
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
// @ts-expect-error
|
||||
factory() {
|
||||
return {
|
||||
bar: 'bar',
|
||||
};
|
||||
},
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
// @ts-expect-error
|
||||
factory() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
// @ts-expect-error
|
||||
factory() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
// @ts-expect-error
|
||||
factory() {
|
||||
return 'bar';
|
||||
},
|
||||
});
|
||||
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
// @ts-expect-error
|
||||
factory: () => {
|
||||
return {
|
||||
foo: 3,
|
||||
};
|
||||
},
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
// @ts-expect-error
|
||||
factory: () => {
|
||||
return {
|
||||
bar: 'bar',
|
||||
};
|
||||
},
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
// @ts-expect-error
|
||||
factory: () => {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
// @ts-expect-error
|
||||
factory: () => {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
// @ts-expect-error
|
||||
factory: () => {
|
||||
return 'bar';
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should create an extension with a some optional output', () => {
|
||||
const extension = createExtension({
|
||||
const baseConfig = {
|
||||
id: 'test',
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
output: {
|
||||
foo: stringData,
|
||||
bar: stringData.optional(),
|
||||
},
|
||||
factory({ bind }) {
|
||||
bind({
|
||||
foo: 'bar',
|
||||
});
|
||||
bind({
|
||||
foo: 'bar',
|
||||
bar: 'baz',
|
||||
});
|
||||
bind({
|
||||
// @ts-expect-error
|
||||
foo: 3,
|
||||
});
|
||||
bind({
|
||||
foo: 'bar',
|
||||
// @ts-expect-error
|
||||
bar: 3,
|
||||
});
|
||||
// @ts-expect-error
|
||||
bind({ bar: 'bar' });
|
||||
// @ts-expect-error
|
||||
bind({});
|
||||
// @ts-expect-error
|
||||
bind();
|
||||
// @ts-expect-error
|
||||
bind('bar');
|
||||
},
|
||||
};
|
||||
const extension = createExtension({
|
||||
...baseConfig,
|
||||
factory: () => ({
|
||||
foo: 'bar',
|
||||
}),
|
||||
});
|
||||
expect(extension.id).toBe('test');
|
||||
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
factory: () => ({
|
||||
foo: 'bar',
|
||||
bar: 'baz',
|
||||
}),
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
factory: () => ({
|
||||
// @ts-expect-error
|
||||
foo: 3,
|
||||
}),
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
factory: () => ({
|
||||
foo: 'bar',
|
||||
// @ts-expect-error
|
||||
bar: 3,
|
||||
}),
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
factory: () =>
|
||||
// @ts-expect-error
|
||||
({ bar: 'bar' }),
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
factory: () =>
|
||||
// @ts-expect-error
|
||||
({}),
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
factory: () =>
|
||||
// @ts-expect-error
|
||||
undefined,
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
// @ts-expect-error
|
||||
factory: () => {},
|
||||
});
|
||||
createExtension({
|
||||
...baseConfig,
|
||||
factory: () =>
|
||||
// @ts-expect-error
|
||||
'bar',
|
||||
});
|
||||
});
|
||||
|
||||
it('should create an extension with input', () => {
|
||||
@@ -110,7 +250,7 @@ describe('createExtension', () => {
|
||||
output: {
|
||||
foo: stringData,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
factory({ inputs }) {
|
||||
const a1: string = inputs.mixed?.[0].required;
|
||||
// @ts-expect-error
|
||||
const a2: number = inputs.mixed?.[0].required;
|
||||
@@ -141,9 +281,9 @@ describe('createExtension', () => {
|
||||
const d4: number | undefined = inputs.onlyOptional?.[0].optional;
|
||||
unused(d1, d2, d3, d4);
|
||||
|
||||
bind({
|
||||
return {
|
||||
foo: 'bar',
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
expect(extension.id).toBe('test');
|
||||
|
||||
@@ -81,10 +81,9 @@ export interface CreateExtensionOptions<
|
||||
configSchema?: PortableSchema<TConfig>;
|
||||
factory(options: {
|
||||
source?: BackstagePlugin;
|
||||
bind(values: Expand<ExtensionDataValues<TOutput>>): void;
|
||||
config: TConfig;
|
||||
inputs: Expand<ExtensionInputValues<TInputs>>;
|
||||
}): void;
|
||||
}): Expand<ExtensionDataValues<TOutput>>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
@@ -98,13 +97,12 @@ export interface Extension<TConfig> {
|
||||
configSchema?: PortableSchema<TConfig>;
|
||||
factory(options: {
|
||||
source?: BackstagePlugin;
|
||||
bind(values: ExtensionInputValues<any>): void;
|
||||
config: TConfig;
|
||||
inputs: Record<
|
||||
string,
|
||||
undefined | Record<string, unknown> | Array<Record<string, unknown>>
|
||||
>;
|
||||
}): void;
|
||||
}): ExtensionDataValues<any>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
@@ -120,12 +118,11 @@ export function createExtension<
|
||||
disabled: options.disabled ?? false,
|
||||
$$type: '@backstage/Extension',
|
||||
inputs: options.inputs ?? {},
|
||||
factory({ bind, config, inputs }) {
|
||||
factory({ inputs, ...rest }) {
|
||||
// TODO: Simplify this, but TS wouldn't infer the input type for some reason
|
||||
return options.factory({
|
||||
bind,
|
||||
config,
|
||||
inputs: inputs as Expand<ExtensionInputValues<TInputs>>,
|
||||
...rest,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('createExtensionOverrides', () => {
|
||||
id: 'a',
|
||||
attachTo: { id: 'core', input: 'apis' },
|
||||
output: {},
|
||||
factory() {},
|
||||
factory: () => ({}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
@@ -72,7 +72,7 @@ describe('createExtensionOverrides', () => {
|
||||
id: 'a',
|
||||
attachTo: { id: 'core', input: 'apis' },
|
||||
output: {},
|
||||
factory() {},
|
||||
factory: () => ({}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -34,8 +34,8 @@ const TechRadarPage = createExtension({
|
||||
output: {
|
||||
name: nameExtensionDataRef,
|
||||
},
|
||||
factory({ bind }) {
|
||||
bind({ name: 'TechRadar' });
|
||||
factory() {
|
||||
return { name: 'TechRadar' };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -48,8 +48,8 @@ const CatalogPage = createExtension({
|
||||
configSchema: createSchemaFromZod(z =>
|
||||
z.object({ name: z.string().default('Catalog') }),
|
||||
),
|
||||
factory({ bind, config }) {
|
||||
bind({ name: config.name });
|
||||
factory({ config }) {
|
||||
return { name: config.name };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -62,8 +62,8 @@ const TechDocsAddon = createExtension({
|
||||
configSchema: createSchemaFromZod(z =>
|
||||
z.object({ name: z.string().default('TechDocsAddon') }),
|
||||
),
|
||||
factory({ bind, config }) {
|
||||
bind({ name: config.name });
|
||||
factory({ config }) {
|
||||
return { name: config.name };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -78,8 +78,8 @@ const TechDocsPage = createExtension({
|
||||
output: {
|
||||
name: nameExtensionDataRef,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
bind({ name: `TechDocs-${inputs.addons.map(n => n.name).join('-')}` });
|
||||
factory({ inputs }) {
|
||||
return { name: `TechDocs-${inputs.addons.map(n => n.name).join('-')}` };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -94,12 +94,12 @@ const outputExtension = createExtension({
|
||||
output: {
|
||||
element: coreExtensionData.reactElement,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
bind({
|
||||
factory({ inputs }) {
|
||||
return {
|
||||
element: React.createElement('span', {}, [
|
||||
`Names: ${inputs.names.map(n => n.name).join(', ')}`,
|
||||
]),
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -113,21 +113,21 @@ export function createEntityCardExtension<
|
||||
.optional(),
|
||||
}),
|
||||
),
|
||||
factory({ bind, config, inputs, source }) {
|
||||
factory({ config, inputs, source }) {
|
||||
const ExtensionComponent = lazy(() =>
|
||||
options
|
||||
.loader({ inputs })
|
||||
.then(element => ({ default: () => element })),
|
||||
);
|
||||
|
||||
bind({
|
||||
return {
|
||||
element: (
|
||||
<ExtensionBoundary id={id} source={source}>
|
||||
<ExtensionComponent />
|
||||
</ExtensionBoundary>
|
||||
),
|
||||
filter: buildFilter(config, options.filter),
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -179,14 +179,14 @@ export function createEntityContentExtension<
|
||||
.optional(),
|
||||
}),
|
||||
),
|
||||
factory({ bind, config, inputs, source }) {
|
||||
factory({ config, inputs, source }) {
|
||||
const ExtensionComponent = lazy(() =>
|
||||
options
|
||||
.loader({ inputs })
|
||||
.then(element => ({ default: () => element })),
|
||||
);
|
||||
|
||||
bind({
|
||||
return {
|
||||
path: config.path,
|
||||
title: config.title,
|
||||
routeRef: options.routeRef,
|
||||
@@ -196,7 +196,7 @@ export function createEntityContentExtension<
|
||||
</ExtensionBoundary>
|
||||
),
|
||||
filter: buildFilter(config, options.filter),
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,20 +43,20 @@ export function createCatalogFilterExtension<
|
||||
output: {
|
||||
element: coreExtensionData.reactElement,
|
||||
},
|
||||
factory({ bind, config, source }) {
|
||||
factory({ config, source }) {
|
||||
const ExtensionComponent = lazy(() =>
|
||||
options
|
||||
.loader({ config })
|
||||
.then(element => ({ default: () => element })),
|
||||
);
|
||||
|
||||
bind({
|
||||
return {
|
||||
element: (
|
||||
<ExtensionBoundary id={id} source={source}>
|
||||
<ExtensionComponent />
|
||||
</ExtensionBoundary>
|
||||
),
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -88,10 +88,10 @@ export function createEndpointExtension<TConfig extends {}>(options: {
|
||||
output: {
|
||||
endpoint: endpointDataRef,
|
||||
},
|
||||
factory({ bind, config }) {
|
||||
bind({
|
||||
factory({ config }) {
|
||||
return {
|
||||
endpoint: options.factory({ config }).endpoint,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -107,14 +107,14 @@ export function createSearchResultListItemExtension<
|
||||
output: {
|
||||
item: searchResultItemExtensionData,
|
||||
},
|
||||
factory({ bind, config, source }) {
|
||||
factory({ config, source }) {
|
||||
const ExtensionComponent = lazy(() =>
|
||||
options
|
||||
.component({ config })
|
||||
.then(component => ({ default: component })),
|
||||
) as unknown as SearchResultItemExtensionComponent;
|
||||
|
||||
bind({
|
||||
return {
|
||||
item: {
|
||||
predicate: options.predicate,
|
||||
component: props => (
|
||||
@@ -129,7 +129,7 @@ export function createSearchResultListItemExtension<
|
||||
</ExtensionBoundary>
|
||||
),
|
||||
},
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user