diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx index 89ac938505..b224ea491e 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx @@ -64,68 +64,3 @@ export const PageBlueprint = createExtensionBlueprint({ } }, }); - -const homePage = PageBlueprint.make({ - params: { - defaultPath: '/home', - loader: async () =>

Home

, - }, -}); - -const homePlugin = createPlugin({ - id: 'asd', - extensions: [homePage], -}); - -// overrides-package locally - -/* - -# TODO -- [ ] Decorate getExtension(...) extension definitions with a namespace -- [ ] Implement plugin.override([...]) - -*/ - -// import plugin from '@backstage/plugin-home'; - -// export const exp1 = plugin.override([ -// plugin.getExtension('page:home').override({ -// *factory(originalFactory) { -// yield* originalFactory(); -// yield coreExtensionData.routePath('/backstage'); -// // return [...originalFactory(), coreExtensionData.routePath('/backstage')]; -// // return originalFactory({ -// // defaultPath: '/backstage', -// // }); -// }, -// }), -// PageBlueprint.make({ -// name: 'home', -// params: { -// defaultPath: '/home', -// loader: async () =>

Home

, -// }, -// }) -// ]); - -// // kind:namespace/name -// // entity-card:github/issues -// // entity-card:github/pull-requests -// // page:catalog - -// export const exp2 = createExtensionOverrides({ -// extensions: [ -// homePlugin.getExtension('page:home').override({ -// // namespace: 'home', -// *factory(originalFactory) { -// yield* originalFactory(); -// yield coreExtensionData.routePath('/backstage'); -// // return [...originalFactory(), coreExtensionData.routePath('/backstage')]; -// // return originalFactory({ -// // defaultPath: '/backstage', -// // }); -// }, -// }) -// ] -// }) diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts index 76cc030be3..f1a0817c80 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts @@ -148,7 +148,28 @@ describe('createPlugin', () => { }); expect(plugin).toBeDefined(); - expect(plugin.getExtension('test/1')).toBe(Extension1); + expect(plugin.getExtension('test/1')).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "test/output", + "input": "names", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": undefined, + "name": "1", + "namespace": "test", + "output": [ + [Function], + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); // @ts-expect-error expect(plugin.getExtension('nonexistent')).toBeUndefined(); @@ -250,4 +271,42 @@ describe('createPlugin', () => { }), ).toThrow("Plugin 'test' provided duplicate extensions: test/2, test/3"); }); + + describe('overrides', () => { + it('should allow overriding extensions that have a matching ID, while keeping old extensions that do not have overlapping IDs', async () => { + const plugin = createPlugin({ + id: 'test', + extensions: [Extension1, Extension2, outputExtension], + }); + + expect(plugin.getExtension('test/1')).toMatchObject({ + namespace: 'test', + }); + + await renderWithEffects( + createTestAppRoot({ + features: [ + plugin.withOverrides({ + extensions: [ + plugin.getExtension('test/1').override({ + factory() { + return [nameExtensionDataRef('overridden')]; + }, + }), + ], + }), + ], + config: { + app: { + extensions: [{ 'app/root': false }], + }, + }, + }), + ); + + await expect( + screen.findByText('Names: extension-2, overridden'), + ).resolves.toBeInTheDocument(); + }); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.ts index 1d2a12ffe2..2f7a813c90 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.ts @@ -78,7 +78,7 @@ export function createPlugin< for (const def of options.extensions ?? []) { const ext = resolveExtensionDefinition(def, { namespace: options.id }); extensions.push(ext); - extensionDefinitionsById.set(ext.id, def); + extensionDefinitionsById.set(ext.id, { ...def, namespace: options.id }); } if (extensions.length !== extensionDefinitionsById.size) { @@ -110,6 +110,23 @@ export function createPlugin< toString() { return `Plugin{id=${options.id}}`; }, + withOverrides(overrides) { + const overrideExtensionIds = new Set( + overrides.extensions.map( + e => resolveExtensionDefinition(e, { namespace: options.id }).id, + ), + ); + const nonOverridenExtensions = (options.extensions ?? []).filter( + e => + !overrideExtensionIds.has( + resolveExtensionDefinition(e, { namespace: options.id }).id, + ), + ); + return createPlugin({ + ...options, + extensions: [...nonOverridenExtensions, ...overrides.extensions], + }); + }, } as InternalBackstagePlugin; } diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts index b1afd4e7ea..4a7d972b37 100644 --- a/packages/frontend-plugin-api/src/wiring/types.ts +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -51,6 +51,9 @@ export interface BackstagePlugin< readonly routes: TRoutes; readonly externalRoutes: TExternalRoutes; getExtension(id: TId): TExtensionMap[TId]; + withOverrides(options: { + extensions: Array>; + }): BackstagePlugin; } /** @public */