From bca8e8b393af7112a126f42dd4b8183d9f12fc45 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Wed, 4 Jan 2023 15:41:46 +0200 Subject: [PATCH] feat: allow specifying app level feature flags application feature flags can be defined in the application creation. see docs/plugins/feature-flags.md for reference. closes #15553 Signed-off-by: Heikki Hellgren --- .changeset/moody-eggs-smash.md | 7 ++++++ .changeset/rare-cooks-peel.md | 5 ++++ docs/plugins/feature-flags.md | 25 ++++++++++++++++--- packages/app-defaults/src/createApp.tsx | 1 + packages/app/src/App.tsx | 4 +++ packages/core-app-api/api-report.md | 1 + packages/core-app-api/src/app/AppManager.tsx | 10 ++++++++ packages/core-app-api/src/app/types.ts | 6 +++++ packages/core-plugin-api/api-report.md | 1 + .../src/apis/definitions/FeatureFlagsApi.ts | 1 + .../FeatureFlags/FeatureFlagsItem.tsx | 14 ++++++++--- 11 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 .changeset/moody-eggs-smash.md create mode 100644 .changeset/rare-cooks-peel.md diff --git a/.changeset/moody-eggs-smash.md b/.changeset/moody-eggs-smash.md new file mode 100644 index 0000000000..0d3d1c83cd --- /dev/null +++ b/.changeset/moody-eggs-smash.md @@ -0,0 +1,7 @@ +--- +'@backstage/app-defaults': minor +'@backstage/core-plugin-api': minor +'@backstage/core-app-api': minor +--- + +Allow defining application level feature flags. See [Feature Flags documentation](/docs/plugins/feature-flags.md) for reference. diff --git a/.changeset/rare-cooks-peel.md b/.changeset/rare-cooks-peel.md new file mode 100644 index 0000000000..95b878ed86 --- /dev/null +++ b/.changeset/rare-cooks-peel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-user-settings': patch +--- + +Feature flags now accept a description property. diff --git a/docs/plugins/feature-flags.md b/docs/plugins/feature-flags.md index a26eb49a9b..d9841598bb 100644 --- a/docs/plugins/feature-flags.md +++ b/docs/plugins/feature-flags.md @@ -1,16 +1,18 @@ --- id: feature-flags title: Feature Flags -description: Details the process of defining setting and reading a plugin feature flag. +description: Details the process of defining setting and reading a feature flag. --- -Backstage offers the ability to define feature flags inside a plugin. This allows you to restrict parts of your plugin to those individual users who have toggled the feature flag to on. +Backstage offers the ability to define feature flags inside a plugin or during application creation. This allows you to restrict parts of your plugin to those individual users who have toggled the feature flag to on. -This page describes the process of defining setting and reading a plugin feature flag. If you are looking for using feature flags with software templates that can be found under [Writing Templates](https://backstage.io/docs/features/software-templates/writing-templates#remove-sections-or-fields-based-on-feature-flags). +This page describes the process of defining setting and reading a feature flag. If you are looking for using feature flags with software templates that can be found under [Writing Templates](https://backstage.io/docs/features/software-templates/writing-templates#remove-sections-or-fields-based-on-feature-flags). ## Defining a Feature Flag -Before using a feature flag we must first define it. This is done when we create the plugin by passing the name of the feature flag into the `featureFlags` array. +### In a plugin + +Defining feature flag in a plugin is done by passing the name of the feature flag into the `featureFlags` array: ```ts /* src/plugin.ts */ @@ -26,6 +28,21 @@ export const examplePlugin = createPlugin({ }); ``` +### In the application + +Defining feature flag in the application is done by adding feature flags in`featureFlags` array in +`createApp()` function call: + +```ts +const app = createApp({ + // ... + featureFlags: [ + { name: 'tech-radar', description: 'Enables the tech radar plugin' }, + ], + // ... +}); +``` + ## Enabling Feature Flags Feature flags are defaulted to off and can be updated by individual users in the backstage interface. diff --git a/packages/app-defaults/src/createApp.tsx b/packages/app-defaults/src/createApp.tsx index 9d0c01c633..6cac337d22 100644 --- a/packages/app-defaults/src/createApp.tsx +++ b/packages/app-defaults/src/createApp.tsx @@ -51,6 +51,7 @@ export function createApp( ...options?.icons, }, plugins: (options?.plugins as BackstagePlugin[]) ?? [], + featureFlags: options?.featureFlags ?? [], themes: options?.themes ?? themes, }); } diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 6bb8405972..f223924294 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -115,6 +115,10 @@ const app = createApp({ // Custom icon example alert: AlarmIcon, }, + // Example of application level feature flag + // featureFlags: [ + // { name: 'tech-radar', description: 'Enables the tech radar plugin' }, + // ], components: { SignInPage: props => { return ( diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 4d867f0b37..133ac22e5b 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -208,6 +208,7 @@ export type AppOptions = { >; } >; + featureFlags?: (FeatureFlag & Omit)[]; components: AppComponents; themes: (Partial & Omit)[]; configLoader?: AppConfigLoader; diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index 5a752ad5df..d5f8210185 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -40,6 +40,7 @@ import { featureFlagsApiRef, identityApiRef, BackstagePlugin, + FeatureFlag, } from '@backstage/core-plugin-api'; import { ApiFactoryRegistry, ApiResolver } from '../apis/system'; import { @@ -211,6 +212,8 @@ export class AppManager implements BackstageApp { private readonly apis: Iterable; private readonly icons: NonNullable; private readonly plugins: Set; + private readonly featureFlags: (FeatureFlag & + Omit)[]; private readonly components: AppComponents; private readonly themes: AppTheme[]; private readonly configLoader?: AppConfigLoader; @@ -224,6 +227,7 @@ export class AppManager implements BackstageApp { this.apis = options.apis ?? []; this.icons = options.icons; this.plugins = new Set((options.plugins as CompatiblePlugin[]) ?? []); + this.featureFlags = options.featureFlags ?? []; this.components = options.components; this.themes = options.themes as AppTheme[]; this.configLoader = options.configLoader ?? defaultConfigLoader; @@ -341,6 +345,12 @@ export class AppManager implements BackstageApp { const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!; if (featureFlagsApi) { + for (const flag of this.featureFlags) { + featureFlagsApi.registerFlag({ + ...flag, + pluginId: '', + }); + } for (const plugin of this.plugins.values()) { if ('getFeatureFlags' in plugin) { for (const flag of plugin.getFeatureFlags()) { diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 82c05ce268..75b8fb2a34 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -24,6 +24,7 @@ import { SubRouteRef, ExternalRouteRef, IdentityApi, + FeatureFlag, } from '@backstage/core-plugin-api'; import { AppConfig } from '@backstage/config'; @@ -213,6 +214,11 @@ export type AppOptions = { } >; + /** + * Application level feature flags. + */ + featureFlags?: (FeatureFlag & Omit)[]; + /** * Supply components to the app to override the default ones. */ diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index dff4f7e5a6..dc7aa9eb5f 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -425,6 +425,7 @@ export type ExternalRouteRef< export type FeatureFlag = { name: string; pluginId: string; + description?: string; }; // @public diff --git a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts index 946bd1d37a..66260afa32 100644 --- a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts @@ -24,6 +24,7 @@ import { ApiRef, createApiRef } from '../system'; export type FeatureFlag = { name: string; pluginId: string; + description?: string; }; /** diff --git a/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx b/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx index e97b33cd92..a5f0d480cb 100644 --- a/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx +++ b/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx @@ -30,6 +30,15 @@ type Props = { toggleHandler: Function; }; +const getSecondaryText = (flag: FeatureFlag) => { + if (flag.description) { + return flag.description; + } + return flag.pluginId + ? `Registered in ${flag.pluginId} plugin` + : 'Registered in the application'; +}; + export const FlagItem = ({ flag, enabled, toggleHandler }: Props) => ( toggleHandler(flag.name)}> @@ -37,9 +46,6 @@ export const FlagItem = ({ flag, enabled, toggleHandler }: Props) => ( - + );