diff --git a/.changeset/slow-olives-confess.md b/.changeset/slow-olives-confess.md
new file mode 100644
index 0000000000..68b2539157
--- /dev/null
+++ b/.changeset/slow-olives-confess.md
@@ -0,0 +1,7 @@
+---
+'@backstage/core-plugin-api': patch
+---
+
+Deprecated `register` option of `createPlugin` and the `outputs` methods of the plugin instance.
+
+Introduces the `featureFlags` property to define your feature flags instead.
diff --git a/.changeset/young-hats-shop.md b/.changeset/young-hats-shop.md
new file mode 100644
index 0000000000..4db46cf9d9
--- /dev/null
+++ b/.changeset/young-hats-shop.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-cost-insights': patch
+---
+
+Supply featureFlags using featureFlag config option.
diff --git a/packages/core-app-api/src/app/AppManager.test.tsx b/packages/core-app-api/src/app/AppManager.test.tsx
index 07401660e8..20872b26f0 100644
--- a/packages/core-app-api/src/app/AppManager.test.tsx
+++ b/packages/core-app-api/src/app/AppManager.test.tsx
@@ -349,6 +349,78 @@ describe('Integration Test', () => {
});
});
+ it('getFeatureFlags should return feature flags', async () => {
+ const storageFlags = new LocalStorageFeatureFlags();
+ jest.spyOn(storageFlags, 'registerFlag');
+
+ const apis = [
+ noOpAnalyticsApi,
+ createApiFactory({
+ api: featureFlagsApiRef,
+ deps: { configApi: configApiRef },
+ factory() {
+ return storageFlags;
+ },
+ }),
+ ];
+
+ const app = new AppManager({
+ apis,
+ defaultApis: [],
+ themes: [
+ {
+ id: 'light',
+ title: 'Light Theme',
+ variant: 'light',
+ Provider: ({ children }) => <>{children}>,
+ },
+ ],
+ icons,
+ plugins: [
+ createPlugin({
+ id: 'test',
+ featureFlags: [
+ {
+ name: 'foo',
+ },
+ ],
+ register: p => p.featureFlags.register('name'),
+ }),
+ ],
+ components,
+ configLoader: async () => [],
+ bindRoutes: ({ bind }) => {
+ bind(plugin1.externalRoutes, {
+ extRouteRef1: plugin1RouteRef,
+ extRouteRef2: plugin2RouteRef,
+ });
+ },
+ });
+
+ const Provider = app.getProvider();
+ const Router = app.getRouter();
+
+ await renderWithEffects(
+
+
+
+ } />
+ } />
+
+
+ ,
+ );
+
+ expect(storageFlags.registerFlag).toHaveBeenCalledWith({
+ name: 'name',
+ pluginId: 'test',
+ });
+ expect(storageFlags.registerFlag).toHaveBeenCalledWith({
+ name: 'foo',
+ pluginId: 'test',
+ });
+ });
+
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 950f57bb7d..5903a8c874 100644
--- a/packages/core-app-api/src/app/AppManager.tsx
+++ b/packages/core-app-api/src/app/AppManager.tsx
@@ -272,17 +272,26 @@ export class AppManager implements BackstageApp {
const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!;
for (const plugin of this.plugins.values()) {
- for (const output of plugin.output()) {
- switch (output.type) {
- case 'feature-flag': {
- featureFlagsApi.registerFlag({
- name: output.name,
- pluginId: plugin.getId(),
- });
- break;
+ if ('getFeatureFlags' in plugin) {
+ for (const flag of plugin.getFeatureFlags()) {
+ featureFlagsApi.registerFlag({
+ name: flag.name,
+ pluginId: plugin.getId(),
+ });
+ }
+ } else {
+ for (const output of plugin.output()) {
+ switch (output.type) {
+ case 'feature-flag': {
+ featureFlagsApi.registerFlag({
+ name: output.name,
+ pluginId: plugin.getId(),
+ });
+ break;
+ }
+ default:
+ break;
}
- default:
- break;
}
}
}
diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md
index 27f3480930..560071e1fa 100644
--- a/packages/core-plugin-api/api-report.md
+++ b/packages/core-plugin-api/api-report.md
@@ -258,6 +258,7 @@ export type BackstagePlugin<
getId(): string;
output(): PluginOutput[];
getApis(): Iterable;
+ getFeatureFlags(): Iterable;
provide(extension: Extension): T;
routes: Routes;
externalRoutes: ExternalRoutes;
@@ -463,7 +464,7 @@ export type FeatureFlag = {
pluginId: string;
};
-// @public
+// @public @deprecated
export type FeatureFlagOutput = {
type: 'feature-flag';
name: string;
@@ -682,14 +683,20 @@ export type PluginConfig<
register?(hooks: PluginHooks): void;
routes?: Routes;
externalRoutes?: ExternalRoutes;
+ featureFlags?: PluginFeatureFlagConfig[];
};
// @public
+export type PluginFeatureFlagConfig = {
+ name: string;
+};
+
+// @public @deprecated
export type PluginHooks = {
featureFlags: FeatureFlagsHooks;
};
-// @public
+// @public @deprecated
export type PluginOutput = FeatureFlagOutput;
// @public
diff --git a/packages/core-plugin-api/src/plugin/Plugin.test.tsx b/packages/core-plugin-api/src/plugin/Plugin.test.tsx
new file mode 100644
index 0000000000..d3758543a1
--- /dev/null
+++ b/packages/core-plugin-api/src/plugin/Plugin.test.tsx
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createPlugin } from './Plugin';
+
+describe('Plugin Feature Flag', () => {
+ it('should be able to register and receive feature flags', () => {
+ expect(
+ createPlugin({
+ id: 'test',
+ featureFlags: [{ name: 'test' }],
+ }).getFeatureFlags(),
+ ).toEqual([{ name: 'test' }]);
+
+ expect(
+ createPlugin({
+ id: 'test',
+ register({ featureFlags }) {
+ featureFlags.register('blob');
+ },
+ }).getFeatureFlags(),
+ ).toEqual([{ name: 'blob' }]);
+
+ expect(
+ createPlugin({
+ id: 'test',
+ register({ featureFlags }) {
+ featureFlags.register('blob');
+ },
+ featureFlags: [{ name: 'test' }],
+ }).getFeatureFlags(),
+ ).toEqual([{ name: 'test' }, { name: 'blob' }]);
+
+ expect(
+ createPlugin({
+ id: 'test',
+ }).getFeatureFlags(),
+ ).toEqual([]);
+
+ /* deprecated tests */
+
+ expect(
+ createPlugin({
+ id: 'test',
+ featureFlags: [{ name: 'test' }],
+ }).output(),
+ ).toEqual([{ name: 'test', type: 'feature-flag' }]);
+
+ expect(
+ createPlugin({
+ id: 'test',
+ register({ featureFlags }) {
+ featureFlags.register('blob');
+ },
+ }).output(),
+ ).toEqual([{ name: 'blob', type: 'feature-flag' }]);
+ expect(
+ createPlugin({
+ id: 'test',
+ register({ featureFlags }) {
+ featureFlags.register('blob');
+ },
+ featureFlags: [{ name: 'test' }],
+ }).output(),
+ ).toEqual([
+ { name: 'test', type: 'feature-flag' },
+ { name: 'blob', type: 'feature-flag' },
+ ]);
+
+ expect(
+ createPlugin({
+ id: 'test',
+ }).output(),
+ ).toEqual([]);
+ });
+});
diff --git a/packages/core-plugin-api/src/plugin/Plugin.tsx b/packages/core-plugin-api/src/plugin/Plugin.tsx
index 52ca0e76e3..0fb7574cd7 100644
--- a/packages/core-plugin-api/src/plugin/Plugin.tsx
+++ b/packages/core-plugin-api/src/plugin/Plugin.tsx
@@ -21,6 +21,7 @@ import {
Extension,
AnyRoutes,
AnyExternalRoutes,
+ PluginFeatureFlagConfig,
} from './types';
import { AnyApiFactory } from '../apis';
@@ -44,6 +45,14 @@ export class PluginImpl<
return this.config.apis ?? [];
}
+ getFeatureFlags(): Iterable {
+ const registeredFlags = this.output()
+ .filter(({ type }) => type === 'feature-flag')
+ .map(({ name }) => ({ name }));
+
+ return registeredFlags;
+ }
+
get routes(): Routes {
return this.config.routes ?? ({} as Routes);
}
@@ -56,11 +65,18 @@ export class PluginImpl<
if (this.storedOutput) {
return this.storedOutput;
}
- if (!this.config.register) {
- return [];
+ const outputs = new Array();
+ this.storedOutput = outputs;
+
+ if (this.config.featureFlags) {
+ for (const flag of this.config.featureFlags) {
+ outputs.push({ type: 'feature-flag', name: flag.name });
+ }
}
- const outputs = new Array();
+ if (!this.config.register) {
+ return outputs;
+ }
this.config.register({
featureFlags: {
@@ -70,7 +86,6 @@ export class PluginImpl<
},
});
- this.storedOutput = outputs;
return this.storedOutput;
}
diff --git a/packages/core-plugin-api/src/plugin/index.ts b/packages/core-plugin-api/src/plugin/index.ts
index d3272607ef..4f7609fd9c 100644
--- a/packages/core-plugin-api/src/plugin/index.ts
+++ b/packages/core-plugin-api/src/plugin/index.ts
@@ -25,4 +25,5 @@ export type {
PluginConfig,
PluginHooks,
PluginOutput,
+ PluginFeatureFlagConfig,
} from './types';
diff --git a/packages/core-plugin-api/src/plugin/types.ts b/packages/core-plugin-api/src/plugin/types.ts
index aeb7037c51..0f8d3404d8 100644
--- a/packages/core-plugin-api/src/plugin/types.ts
+++ b/packages/core-plugin-api/src/plugin/types.ts
@@ -19,7 +19,7 @@ import { AnyApiFactory } from '../apis/system';
/**
* Replace with using {@link RouteRef}s.
- *
+ * @deprecated will be removed
* @public
*/
export type FeatureFlagOutput = {
@@ -31,6 +31,7 @@ export type FeatureFlagOutput = {
* {@link FeatureFlagOutput} type.
*
* @public
+ * @deprecated Use {@link BackstagePlugin.getFeatureFlags} instead.
*/
export type PluginOutput = FeatureFlagOutput;
@@ -71,13 +72,30 @@ export type BackstagePlugin<
ExternalRoutes extends AnyExternalRoutes = {},
> = {
getId(): string;
+ /**
+ * @deprecated use getFeatureFlags instead.
+ * */
output(): PluginOutput[];
getApis(): Iterable;
+ /**
+ * Returns all registered feature flags for this plugin.
+ */
+ getFeatureFlags(): Iterable;
provide(extension: Extension): T;
routes: Routes;
externalRoutes: ExternalRoutes;
};
+/**
+ * Plugin feature flag configuration.
+ *
+ * @public
+ */
+export type PluginFeatureFlagConfig = {
+ /** Feature flag name */
+ name: string;
+};
+
/**
* Plugin descriptor type.
*
@@ -89,14 +107,17 @@ export type PluginConfig<
> = {
id: string;
apis?: Iterable;
+ /** @deprecated use featureFlags property instead for defining feature flags */
register?(hooks: PluginHooks): void;
routes?: Routes;
externalRoutes?: ExternalRoutes;
+ featureFlags?: PluginFeatureFlagConfig[];
};
/**
* Holds hooks registered by the plugin.
*
+ * @deprecated - feature flags are now registered in plugin config under featureFlags
* @public
*/
export type PluginHooks = {
diff --git a/plugins/cost-insights/src/plugin.ts b/plugins/cost-insights/src/plugin.ts
index 1714c5fbd5..a422098e08 100644
--- a/plugins/cost-insights/src/plugin.ts
+++ b/plugins/cost-insights/src/plugin.ts
@@ -34,9 +34,7 @@ export const unlabeledDataflowAlertRef = createRouteRef({
export const costInsightsPlugin = createPlugin({
id: 'cost-insights',
- register({ featureFlags }) {
- featureFlags.register('cost-insights-currencies');
- },
+ featureFlags: [{ name: 'cost-insights-currencies' }],
routes: {
root: rootRouteRef,
growthAlerts: projectGrowthAlertRef,