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:
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user