Merge pull request #25981 from backstage/mob/nfs/plugin-overrides

NFS: Support `plugin.withOverrides` to override extensions from existing plugins
This commit is contained in:
Ben Lambert
2024-08-13 09:37:09 +02:00
committed by GitHub
5 changed files with 131 additions and 2 deletions
@@ -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,68 @@ describe('createPlugin', () => {
}),
).toThrow("Plugin 'test' provided duplicate extensions: test/2, test/3");
});
describe('overrides', () => {
it('should return a plugin instance with the correct namespace', () => {
const plugin = createPlugin({
id: 'test',
extensions: [Extension1, Extension2],
});
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",
}
`);
});
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],
});
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();
});
});
});
@@ -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<TRoutes, TExternalRoutes>;
}
@@ -51,6 +51,9 @@ export interface BackstagePlugin<
readonly routes: TRoutes;
readonly externalRoutes: TExternalRoutes;
getExtension<TId extends keyof TExtensionMap>(id: TId): TExtensionMap[TId];
withOverrides(options: {
extensions: Array<ExtensionDefinition<any, any>>;
}): BackstagePlugin<TRoutes, TExternalRoutes, TExtensionMap>;
}
/** @public */