diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index c688f92004..432fb7f89e 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { createExtensionTester } from '@backstage/frontend-test-utils'; import { coreExtensionData } from './coreExtensionData'; import { createExtension } from './createExtension'; import { createExtensionDataRef } from './createExtensionDataRef'; @@ -671,6 +672,42 @@ describe('createExtension', () => { return [stringDataRef(`foo-${foo}-override`)]; }, }); + + expect(true).toBe(true); + }); + + it('should work functionally with overrides', () => { + const testExtension = createExtension({ + kind: 'thing', + namespace: 'test', + attachTo: { id: 'root', input: 'default' }, + output: [stringDataRef], + config: { + schema: { + foo: z => z.string().default('boom'), + }, + }, + factory({ config }) { + return [stringDataRef(config.foo)]; + }, + }); + + const overriden = testExtension.override({ + config: { + schema: { + bar: z => z.string().optional(), + }, + }, + factory(originalFactory, { config }) { + const response = originalFactory(); + + const foo: string = response.get(stringDataRef); + + return [stringDataRef(`foo-${foo}-override-${config.bar}`)]; + }, + }); + + const tester = createExtensionTester(overriden).render(); }); }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 87a4342df7..200d3d1133 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { over } from 'lodash'; import { AppNode } from '../apis'; import { PortableSchema, createSchemaFromZod } from '../schema'; import { Expand } from '../types'; @@ -547,8 +548,89 @@ export function createExtension< parts.push(`attachTo=${options.attachTo.id}@${options.attachTo.input}`); return `ExtensionDefinition{${parts.join(',')}}`; }, - override() { - // todo(blam): implement. + override< + TConfigSchemaOverrides extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + UOutputOverrides extends AnyExtensionDataRef, + UFactoryOverrideOutput extends ExtensionDataValue, + >( + overrideOptions: OverrideExtensionOptions< + UOutput, + TInputs, + TConfig, + TConfigInput, + TConfigSchema, + TConfigSchemaOverrides, + UOutputOverrides, + UFactoryOverrideOutput + >, + ) { + const overrideNewConfigSchema = overrideOptions.config?.schema; + if (overrideNewConfigSchema && overrideOptions.configSchema) { + throw new Error(`Cannot provide both configSchema and config.schema`); + } + + const mergedConfigSchema = { + ...options.config?.schema, + ...overrideOptions.config?.schema, + }; + + const overrideConfigSchema = mergedConfigSchema + ? createSchemaFromZod(innerZ => + innerZ.object( + Object.fromEntries( + Object.entries(mergedConfigSchema).map(([k, v]) => [ + k, + v(innerZ), + ]), + ), + ), + ) + : overrideOptions.configSchema; + + const buildDataContainer = (outputs): ExtensionDataContainer => { + const dataMap = new Map(); + if (Symbol.iterator in outputs) { + for (const output of outputs) { + dataMap.set(output.id, output.value); + } + } + + return { + get(ref) { + return dataMap.get(ref.id); + }, + } as ExtensionDataContainer; + }; + + return createExtension({ + attachTo: options.attachTo, + output: overrideOptions.output ?? options.output, + configSchema: overrideConfigSchema, + factory: ({ node, config, inputs }) => { + if (overrideOptions.factory) { + return overrideOptions.factory( + innerCtx => { + const originalFactoryResponse = options.factory({ + node, + config: innerCtx?.config ?? config, + inputs: innerCtx?.inputs ?? inputs, + }); + + return buildDataContainer(originalFactoryResponse); + }, + { node, config, inputs }, + ); + } + return options.factory(originalFactory, context); + }, + disabled: overrideOptions.disabled ?? options.disabled, + inputs: overrideOptions.inputs ?? options.inputs, + kind: overrideOptions.kind ?? options.kind, + name: overrideOptions.name ?? options.name, + namespace: overrideOptions.namespace ?? options.namespace, + }); }, } as InternalExtensionDefinition< TConfig & diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts index aa00247222..555cd82d4c 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts @@ -75,6 +75,7 @@ describe('createExtensionOverrides', () => { "id": "a", "inputs": {}, "output": {}, + "override": [Function], "toString": [Function], "version": "v1", }, @@ -90,6 +91,7 @@ describe('createExtensionOverrides', () => { "id": "b", "inputs": {}, "output": {}, + "override": [Function], "toString": [Function], "version": "v1", }, @@ -105,6 +107,7 @@ describe('createExtensionOverrides', () => { "id": "k:c/n", "inputs": {}, "output": {}, + "override": [Function], "toString": [Function], "version": "v1", },