core-app-api: immediately register discovered feature flags

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2022-12-01 16:07:02 +01:00
parent f382f2d324
commit b4b5b02315
3 changed files with 60 additions and 9 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-app-api': patch
---
Tweak feature flag registration so that it happens immediately before the first rendering of the app, rather than just after.
@@ -34,6 +34,7 @@ import {
createSubRouteRef,
createRoutableExtension,
analyticsApiRef,
useApi,
} from '@backstage/core-plugin-api';
import { AppManager } from './AppManager';
import { AppComponents, AppIcons } from './types';
@@ -395,6 +396,44 @@ describe('Integration Test', () => {
});
});
it('feature flags should be available immediately', async () => {
const app = new AppManager({
apis: [
createApiFactory({
api: featureFlagsApiRef,
deps: { configApi: configApiRef },
factory() {
return new LocalStorageFeatureFlags();
},
}),
],
defaultApis: [],
themes,
icons,
plugins: [createPlugin({ id: 'test', featureFlags: [{ name: 'foo' }] })],
components,
configLoader: async () => [],
});
const Provider = app.getProvider();
const Router = app.getRouter();
const FeatureFlags = () => {
const featureFlags = useApi(featureFlagsApiRef).getRegisteredFlags();
return <div>Flags: {featureFlags.map(f => f.name).join(',')}</div>;
};
await renderWithEffects(
<Provider>
<Router>
<FeatureFlags />
</Router>
</Provider>,
);
expect(screen.getByText('Flags: foo')).toBeInTheDocument();
});
it('should track route changes via analytics api', async () => {
const mockAnalyticsApi = new MockAnalyticsApi();
const apis = [createApiFactory(analyticsApiRef, mockAnalyticsApi)];
+16 -9
View File
@@ -21,8 +21,8 @@ import React, {
PropsWithChildren,
ReactElement,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { Route, Routes } from 'react-router-dom';
@@ -230,6 +230,7 @@ export class AppManager implements BackstageApp {
let routesHaveBeenValidated = false;
const Provider = ({ children }: PropsWithChildren<{}>) => {
const needsFeatureFlagRegistrationRef = useRef(true);
const appThemeApi = useMemo(
() => AppThemeSelector.createWithStorage(this.themes),
[],
@@ -284,10 +285,21 @@ export class AppManager implements BackstageApp {
this.configApi = api;
}
useEffect(() => {
if (hasConfigApi) {
const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!;
if ('node' in loadedConfig) {
// Loading or error
return loadedConfig.node;
}
// We can't register feature flags just after the element traversal, because the
// config API isn't available yet and implementations frequently depend on it.
// Instead we make it happen immediately, to make sure all flags are available
// for the first render.
if (hasConfigApi && needsFeatureFlagRegistrationRef.current) {
needsFeatureFlagRegistrationRef.current = false;
const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!;
if (featureFlagsApi) {
for (const plugin of this.plugins.values()) {
if ('getFeatureFlags' in plugin) {
for (const flag of plugin.getFeatureFlags()) {
@@ -314,11 +326,6 @@ export class AppManager implements BackstageApp {
featureFlagsApi.registerFlag({ name, pluginId: '' });
}
}
}, [hasConfigApi, loadedConfig, featureFlags]);
if ('node' in loadedConfig) {
// Loading or error
return loadedConfig.node;
}
const { ThemeProvider = AppThemeProvider } = this.components;