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 <heikki.hellgren@op.fi>
This commit is contained in:
Heikki Hellgren
2023-01-04 15:41:46 +02:00
parent dcf3b33189
commit bca8e8b393
11 changed files with 67 additions and 8 deletions
+7
View File
@@ -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.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-user-settings': patch
---
Feature flags now accept a description property.
+21 -4
View File
@@ -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.
+1
View File
@@ -51,6 +51,7 @@ export function createApp(
...options?.icons,
},
plugins: (options?.plugins as BackstagePlugin[]) ?? [],
featureFlags: options?.featureFlags ?? [],
themes: options?.themes ?? themes,
});
}
+4
View File
@@ -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 (
+1
View File
@@ -208,6 +208,7 @@ export type AppOptions = {
>;
}
>;
featureFlags?: (FeatureFlag & Omit<FeatureFlag, 'pluginId'>)[];
components: AppComponents;
themes: (Partial<AppTheme> & Omit<AppTheme, 'theme'>)[];
configLoader?: AppConfigLoader;
@@ -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<AnyApiFactory>;
private readonly icons: NonNullable<AppOptions['icons']>;
private readonly plugins: Set<CompatiblePlugin>;
private readonly featureFlags: (FeatureFlag &
Omit<FeatureFlag, 'pluginId'>)[];
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()) {
+6
View File
@@ -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<FeatureFlag, 'pluginId'>)[];
/**
* Supply components to the app to override the default ones.
*/
+1
View File
@@ -425,6 +425,7 @@ export type ExternalRouteRef<
export type FeatureFlag = {
name: string;
pluginId: string;
description?: string;
};
// @public
@@ -24,6 +24,7 @@ import { ApiRef, createApiRef } from '../system';
export type FeatureFlag = {
name: string;
pluginId: string;
description?: string;
};
/**
@@ -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) => (
<ListItem divider button onClick={() => toggleHandler(flag.name)}>
<ListItemIcon>
@@ -37,9 +46,6 @@ export const FlagItem = ({ flag, enabled, toggleHandler }: Props) => (
<Switch color="primary" checked={enabled} name={flag.name} />
</Tooltip>
</ListItemIcon>
<ListItemText
primary={flag.name}
secondary={`Registered in ${flag.pluginId} plugin`}
/>
<ListItemText primary={flag.name} secondary={getSecondaryText(flag)} />
</ListItem>
);