diff --git a/.changeset/nice-apricots-love.md b/.changeset/nice-apricots-love.md new file mode 100644 index 0000000000..527b644d5f --- /dev/null +++ b/.changeset/nice-apricots-love.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +Prevent duplicate feature flag components from rendering in the settings when using components diff --git a/packages/core-app-api/src/app/AppManager.test.tsx b/packages/core-app-api/src/app/AppManager.test.tsx index 17d33693e5..f81f382650 100644 --- a/packages/core-app-api/src/app/AppManager.test.tsx +++ b/packages/core-app-api/src/app/AppManager.test.tsx @@ -38,6 +38,7 @@ import { } from '@backstage/core-plugin-api'; import { AppManager } from './AppManager'; import { AppComponents, AppIcons } from './types'; +import { FeatureFlagged } from '../routing/FeatureFlagged'; describe('Integration Test', () => { const noOpAnalyticsApi = createApiFactory( @@ -434,6 +435,58 @@ describe('Integration Test', () => { expect(screen.getByText('Flags: foo')).toBeInTheDocument(); }); + it('should prevent duplicate feature flags from being rendered', async () => { + const p1 = createPlugin({ + id: 'p1', + featureFlags: [{ name: 'show-p1-feature' }], + }); + const p2 = createPlugin({ + id: 'p2', + featureFlags: [{ name: 'show-p2-feature' }], + }); + + const app = new AppManager({ + apis: [], + defaultApis: [], + themes, + icons, + plugins: [p1, p2], + components, + configLoader: async () => [], + }); + + const Provider = app.getProvider(); + const Router = app.getRouter(); + + function FeatureFlags() { + const featureFlags = useApi(featureFlagsApiRef); + return ( +
{`Flags: ${featureFlags + .getRegisteredFlags() + .map(f => f.name) + .join(',')}`}
+ ); + } + + await renderWithEffects( + + + +
My feature behind a flag
+
+ +
My feature behind a flag
+
+ +
+
, + ); + + expect( + screen.getByText('Flags: show-p1-feature,show-p2-feature'), + ).toBeInTheDocument(); + }); + it('should track route changes via analytics api', async () => { const mockAnalyticsApi = new MockAnalyticsApi(); const apis = [createApiFactory(analyticsApiRef, mockAnalyticsApi)]; diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index a6e15fe40c..b7eafb0100 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -322,8 +322,13 @@ export class AppManager implements BackstageApp { // Go through the featureFlags returned from the traversal and // register those now the configApi has been loaded + const registeredFlags = featureFlagsApi.getRegisteredFlags(); + const flagNames = new Set(registeredFlags.map(f => f.name)); for (const name of featureFlags) { - featureFlagsApi.registerFlag({ name, pluginId: '' }); + // Prevents adding duplicate feature flags + if (!flagNames.has(name)) { + featureFlagsApi.registerFlag({ name, pluginId: '' }); + } } } }