feat: implementing plugin overrides

Co-authored-by: Patrik Oldsberg <poldsberg@gmail.com>
Co-authored-by: Fredrik Adelöw <freben@users.noreply.github.com>
Co-authored-by: Camila Belo <camilaibs@users.noreply.github.com>
Co-authored-by: Johan Haals <johan@haals.se>
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2024-08-12 17:15:30 +02:00
parent e2eeb1dde1
commit d0ab4d3356
4 changed files with 81 additions and 67 deletions
@@ -64,68 +64,3 @@ export const PageBlueprint = createExtensionBlueprint({
}
},
});
const homePage = PageBlueprint.make({
params: {
defaultPath: '/home',
loader: async () => <h1>Home</h1>,
},
});
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 () => <h1>Home</h1>,
// },
// })
// ]);
// // 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',
// // });
// },
// })
// ]
// })
@@ -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();
});
});
});
@@ -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 */