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: '' });
+ }
}
}
}