From e8867e17f1fb751cb47b85d273fb93a2ba4e3bc3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 6 Aug 2024 17:23:26 +0200 Subject: [PATCH] frontend-plugin-api: extension data container runtime + tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Signed-off-by: Patrik Oldsberg --- .../wiring/createExtensionBlueprint.test.tsx | 99 +++++++++++++++---- .../src/wiring/createExtensionBlueprint.ts | 37 ++++++- 2 files changed, 113 insertions(+), 23 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index 70d874d9ad..66c3939a96 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -21,10 +21,21 @@ import { createExtensionTester } from '@backstage/frontend-test-utils'; import { createExtensionDataRef } from './createExtensionDataRef'; import { createExtensionInput } from './createExtensionInput'; import { RouteRef } from '../routing'; -import { toInternalExtensionDefinition } from './createExtension'; +import { + ExtensionDefinition, + toInternalExtensionDefinition, +} from './createExtension'; function unused(..._any: any[]) {} +function factoryOutput(ext: ExtensionDefinition) { + const int = toInternalExtensionDefinition(ext); + if (int.version !== 'v2') { + throw new Error('Expected v2 extension'); + } + return Array.from(int.factory({} as any)); +} + describe('createExtensionBlueprint', () => { it('should allow creation of extension blueprints', () => { const TestExtensionBlueprint = createExtensionBlueprint({ @@ -476,6 +487,8 @@ describe('createExtensionBlueprint', () => { ); expect(ext.output).toEqual([testDataRef2]); + + expect(factoryOutput(ext)).toEqual([testDataRef2('foobar')]); }); it('should allow returning of the parent data container', () => { @@ -491,23 +504,28 @@ describe('createExtensionBlueprint', () => { }, }); - blueprint.make({ - output: [testDataRef1, testDataRef2], - *factory(origFactory) { - yield* origFactory({}); - yield testDataRef2('bar'); - }, - }); + expect( + factoryOutput( + blueprint.make({ + output: [testDataRef1, testDataRef2], + *factory(origFactory) { + yield* origFactory({}); + yield testDataRef2('bar'); + }, + }), + ), + ).toEqual([testDataRef1('foo'), testDataRef2('bar')]); - blueprint.make({ - output: [testDataRef1, testDataRef2], - factory(origFactory) { - return [...origFactory({}), testDataRef2('bar')]; - }, - }); - - expect(true).toBe(true); - // todo: test that the data is actually available + expect( + factoryOutput( + blueprint.make({ + output: [testDataRef1, testDataRef2], + factory(origFactory) { + return [...origFactory({}), testDataRef2('bar')]; + }, + }), + ), + ).toEqual([testDataRef1('foo'), testDataRef2('bar')]); }); it('should not allow returning parent output if outputs are overridden', () => { @@ -523,13 +541,58 @@ describe('createExtensionBlueprint', () => { }, }); + // @ts-expect-error blueprint.make({ output: [testDataRef2.optional()], *factory() { + yield testDataRef1('foo'); yield testDataRef2('bar'); }, }); - expect(true).toBe(true); + expect( + factoryOutput( + blueprint.make({ + output: [testDataRef2.optional()], + *factory() { + yield testDataRef2('bar'); + }, + }), + ), + ).toEqual([testDataRef2('bar')]); + }); + + it('should not rely on optional outputs when forwarding from parent', () => { + const testDataRef1 = createExtensionDataRef().with({ id: 'test1' }); + const testDataRef2 = createExtensionDataRef().with({ id: 'test2' }); + + const blueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef1, testDataRef2.optional()], + factory() { + return [testDataRef1('foo')]; + }, + }); + + // @ts-expect-error + blueprint.make({ + output: [testDataRef1, testDataRef2], + *factory(origFactory) { + yield* origFactory({}); + }, + }); + + expect( + factoryOutput( + blueprint.make({ + output: [testDataRef1, testDataRef2], + *factory(origFactory) { + yield* origFactory({}); + yield testDataRef2('bar'); + }, + }), + ), + ).toEqual([testDataRef1('foo'), testDataRef2('bar')]); }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 603ffda621..2b80dac81e 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -28,6 +28,7 @@ import { z } from 'zod'; import { ExtensionInput } from './createExtensionInput'; import { AnyExtensionDataRef, + ExtensionDataRef, ExtensionDataValue, } from './createExtensionDataRef'; @@ -169,6 +170,30 @@ export interface ExtensionBlueprint< >; } +/** @internal */ +function createDataContainer( + values: Iterable< + UData extends ExtensionDataRef + ? ExtensionDataValue + : never + >, +): ExtensionDataContainer { + const container = new Map>(); + + for (const output of values) { + container.set(output.id, output); + } + + return { + get(ref) { + return container.get(ref.id)?.value; + }, + [Symbol.iterator]() { + return container.values(); + }, + } as ExtensionDataContainer; +} + /** * @internal */ @@ -312,11 +337,13 @@ class ExtensionBlueprintImpl< inputs?: Expand>; }, ): ExtensionDataContainer => { - return this.options.factory(innerParams, { - node, - config: innerContext?.config ?? config, - inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden - }); + return createDataContainer( + this.options.factory(innerParams, { + node, + config: innerContext?.config ?? config, + inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden + }), + ); }, { node,