diff --git a/.changeset/ninety-brooms-fry.md b/.changeset/ninety-brooms-fry.md new file mode 100644 index 0000000000..1d7f48ba81 --- /dev/null +++ b/.changeset/ninety-brooms-fry.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Reverse the order of API factory initialization in order to make sure that overrides from modules take priority diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx index e98014f273..72594cefd1 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx @@ -25,6 +25,7 @@ import { createExternalRouteRef, createExtensionInput, useRouteRef, + analyticsApiRef, } from '@backstage/frontend-plugin-api'; import { screen, render } from '@testing-library/react'; import { createSpecializedApp } from './createSpecializedApp'; @@ -167,6 +168,75 @@ describe('createSpecializedApp', () => { expect(screen.getByText('flags:test=a,test=b')).toBeInTheDocument(); }); + it('should intitialize the APIs in the correct order to allow for overrides', () => { + const mockAnalyticsApi = jest.fn(() => ({ captureEvent: jest.fn() })); + + const app = createSpecializedApp({ + features: [ + createFrontendPlugin({ + id: 'first', + extensions: [ + ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: analyticsApiRef, + deps: {}, + factory: () => { + throw new Error('BROKEN'); + }, + }), + }, + }), + ], + }), + createFrontendPlugin({ + id: 'test', + featureFlags: [{ name: 'a' }, { name: 'b' }], + extensions: [ + createExtension({ + attachTo: { id: 'root', input: 'app' }, + output: [coreExtensionData.reactElement], + factory: ({ apis }) => { + const Component = () => { + const analytics = apis.get(analyticsApiRef); + return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions +
+ analytics!.captureEvent({ + action: 'asd', + subject: 'asd', + context: { extensionId: 'asd', pluginId: 'asd' }, + }) + } + > + Click me +
+ ); + }; + + return [coreExtensionData.reactElement()]; + }, + }), + ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: analyticsApiRef, + deps: {}, + factory: mockAnalyticsApi, + }), + }, + }), + ], + }), + ], + }); + + render(app.createRoot()); + + expect(mockAnalyticsApi).toHaveBeenCalled(); + }); + it('should make the app structure available through the AppTreeApi', async () => { let appTreeApi: AppTreeApi | undefined = undefined; diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx index 612befc597..f79ed4510a 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx @@ -305,7 +305,7 @@ function createApiHolder(options: { }): ApiHolder { const factoryRegistry = new ApiFactoryRegistry(); - for (const factory of options.factories) { + for (const factory of options.factories.slice().reverse()) { factoryRegistry.register('default', factory); }