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:
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-user-settings': patch
|
||||
---
|
||||
|
||||
Feature flags now accept a description property.
|
||||
@@ -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.
|
||||
|
||||
@@ -51,6 +51,7 @@ export function createApp(
|
||||
...options?.icons,
|
||||
},
|
||||
plugins: (options?.plugins as BackstagePlugin[]) ?? [],
|
||||
featureFlags: options?.featureFlags ?? [],
|
||||
themes: options?.themes ?? themes,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user