From d1a1cad0e713bca807f81dc49b873a04403b3dca Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 4 Sep 2023 16:31:37 +0200 Subject: [PATCH] frontend-plugin-api: swtich extension factory to bind all outputs at once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johan Haals Co-authored-by: Fredrik Adelöw Co-authored-by: Philipp Hugenroth Signed-off-by: Patrik Oldsberg --- .../src/createExtensionInstance.ts | 14 +++++-- .../src/extensions/CoreRouter.tsx | 12 +++--- .../src/extensions/createApiExtension.ts | 4 +- .../src/extensions/createPageExtension.tsx | 23 ++++++----- .../src/wiring/createExtension.test.ts | 19 +++++---- .../src/wiring/createExtension.ts | 40 +++++++++++++------ .../src/wiring/createPlugin.test.ts | 19 ++++----- 7 files changed, 81 insertions(+), 50 deletions(-) diff --git a/packages/frontend-app-api/src/createExtensionInstance.ts b/packages/frontend-app-api/src/createExtensionInstance.ts index a7c49d48cb..d767f21d58 100644 --- a/packages/frontend-app-api/src/createExtensionInstance.ts +++ b/packages/frontend-app-api/src/createExtensionInstance.ts @@ -54,9 +54,17 @@ export function createExtensionInstance(options: { extension.factory({ source, config: parsedConfig, - bind: mapValues(extension.output, ref => { - return (value: unknown) => extensionData.set(ref.id, value); - }), + bind: namedOutputs => { + for (const [name, output] of Object.entries(namedOutputs)) { + const ref = extension.output[name]; + if (!ref) { + throw new Error( + `Extension instance '${extension.id}' tried to bind unknown output '${name}'`, + ); + } + extensionData.set(ref.id, output); + } + }, inputs: mapValues( extension.inputs, ({ extensionData: pointData }, inputName) => { diff --git a/packages/frontend-app-api/src/extensions/CoreRouter.tsx b/packages/frontend-app-api/src/extensions/CoreRouter.tsx index f2a760d25e..05f84aba6d 100644 --- a/packages/frontend-app-api/src/extensions/CoreRouter.tsx +++ b/packages/frontend-app-api/src/extensions/CoreRouter.tsx @@ -47,10 +47,12 @@ export const CoreRouter = createExtension({ return element; }; - bind.component(() => ( - - - - )); + bind({ + component: () => ( + + + + ), + }); }, }); diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts index d59a7ab2c3..057addc4a8 100644 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts @@ -63,9 +63,9 @@ export function createApiExtension< }, factory({ bind, config, inputs }) { if (typeof factory === 'function') { - bind.api(factory({ config, inputs })); + bind({ api: factory({ config, inputs }) }); } else { - bind.api(factory); + bind({ api: factory }); } }, }); diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx index e136f1bc2a..7f16a81e24 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx @@ -82,17 +82,18 @@ export function createPageExtension< .component({ config, inputs }) .then(element => ({ default: () => element })), ); - bind.path(config.path); - bind.component(() => ( - - - - - - )); - if (options.routeRef) { - bind.routeRef!(options.routeRef); - } + + bind({ + path: config.path, + component: () => ( + + + + + + ), + routeRef: options.routeRef, + }); }, }); } diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 69ff83dfe9..14b1fd2d50 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -25,21 +25,26 @@ describe('createExtension', () => { id: 'test', at: 'root', output: { - foo: coreExtensionData.title, - foo2: stringData, + foo: stringData, }, factory({ bind }) { - // Make it work well with required and optional output - // HOH - High Order Helper bind({ foo: 'bar', }); + bind({ + // @ts-expect-error + foo: 3, + }); + bind({ + // @ts-expect-error + bar: 'bar', + }); // @ts-expect-error - bind.foo(3); + bind({}); // @ts-expect-error - bind.foo(); + bind(); // @ts-expect-error - bind.bar('bar'); + bind('bar'); }, }); expect(extension.id).toBe('test'); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index e7f673cd2f..1ef0d3c272 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -19,9 +19,9 @@ import { BackstagePlugin } from './createPlugin'; import { AnyExtensionDataMap, Extension } from './types'; /** @public */ -export type ExtensionDataBind = { - [K in keyof TData]: (value: TData[K]['T']) => void; -}; +export type ExtensionDataBind = (outputs: { + [K in keyof TOutput]: TOutput[K]['T']; +}) => void; /** @public */ export type ExtensionDataValue = { @@ -30,23 +30,23 @@ export type ExtensionDataValue = { /** @public */ export interface CreateExtensionOptions< - TData extends AnyExtensionDataMap, - TPoint extends Record, + TOutput extends AnyExtensionDataMap, + TInputs extends Record, TConfig, > { id: string; at: string; disabled?: boolean; - inputs?: TPoint; - output: TData; + inputs?: TInputs; + output: TOutput; configSchema?: PortableSchema; factory(options: { source?: BackstagePlugin; - bind: ExtensionDataBind; + bind: ExtensionDataBind; config: TConfig; inputs: { - [pointName in keyof TPoint]: ExtensionDataValue< - TPoint[pointName]['extensionData'] + [pointName in keyof TInputs]: ExtensionDataValue< + TInputs[pointName]['extensionData'] >[]; }; }): void; @@ -54,14 +54,28 @@ export interface CreateExtensionOptions< /** @public */ export function createExtension< - TData extends AnyExtensionDataMap, - TPoint extends Record, + TOutput extends AnyExtensionDataMap, + TInputs extends Record, TConfig = never, ->(options: CreateExtensionOptions): Extension { +>( + options: CreateExtensionOptions, +): Extension { return { ...options, disabled: options.disabled ?? false, $$type: 'extension', inputs: options.inputs ?? {}, + factory({ bind, config, inputs }) { + // TODO: Simplify this, but TS wouldn't infer the input type for some reason + return options.factory({ + bind, + config, + inputs: inputs as { + [pointName in keyof TInputs]: ExtensionDataValue< + TInputs[pointName]['extensionData'] + >[]; + }, + }); + }, }; } diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts index 7ebcb62ceb..724dad5143 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts @@ -33,7 +33,7 @@ const TechRadarPage = createExtension({ name: nameExtensionDataRef, }, factory({ bind }) { - bind.name('TechRadar'); + bind({ name: 'TechRadar' }); }, }); @@ -47,7 +47,7 @@ const CatalogPage = createExtension({ z.object({ name: z.string().default('Catalog') }), ), factory({ bind, config }) { - bind.name(config.name); + bind({ name: config.name }); }, }); @@ -61,7 +61,7 @@ const TechDocsAddon = createExtension({ z.object({ name: z.string().default('TechDocsAddon') }), ), factory({ bind, config }) { - bind.name(config.name); + bind({ name: config.name }); }, }); @@ -79,7 +79,7 @@ const TechDocsPage = createExtension({ name: nameExtensionDataRef, }, factory({ bind, inputs }) { - bind.name(`TechDocs-${inputs.addons.map(n => n.name).join('-')}`); + bind({ name: `TechDocs-${inputs.addons.map(n => n.name).join('-')}` }); }, }); @@ -97,11 +97,12 @@ const outputExtension = createExtension({ component: coreExtensionData.reactComponent, }, factory({ bind, inputs }) { - bind.component(() => - React.createElement('span', {}, [ - `Names: ${inputs.names.map(n => n.name).join(', ')}`, - ]), - ); + bind({ + component: () => + React.createElement('span', {}, [ + `Names: ${inputs.names.map(n => n.name).join(', ')}`, + ]), + }); }, });