frontend-plugin-api: support feature-level predicates
This lets plugin and module instances apply a shared condition to all of their extensions, while preserving extension-level conditions by combining them with logical AND during app spec resolution. Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> Made-with: Cursor
This commit is contained in:
@@ -255,12 +255,12 @@ export interface AppNodeSpec {
|
||||
// (undocumented)
|
||||
readonly disabled: boolean;
|
||||
// (undocumented)
|
||||
readonly enabled?: FilterPredicate;
|
||||
// (undocumented)
|
||||
readonly extension: Extension<unknown, unknown>;
|
||||
// (undocumented)
|
||||
readonly id: string;
|
||||
// (undocumented)
|
||||
readonly if?: FilterPredicate;
|
||||
// (undocumented)
|
||||
readonly plugin: FrontendPlugin;
|
||||
}
|
||||
|
||||
@@ -580,7 +580,7 @@ export type CreateExtensionBlueprintOptions<
|
||||
attachTo: ExtensionDefinitionAttachTo<UParentInputs> &
|
||||
VerifyExtensionAttachTo<UOutput, UParentInputs>;
|
||||
disabled?: boolean;
|
||||
enabled?: FilterPredicate;
|
||||
if?: FilterPredicate;
|
||||
inputs?: TInputs;
|
||||
output: Array<UOutput>;
|
||||
config?: {
|
||||
@@ -667,7 +667,7 @@ export type CreateExtensionOptions<
|
||||
attachTo: ExtensionDefinitionAttachTo<UParentInputs> &
|
||||
VerifyExtensionAttachTo<UOutput, UParentInputs>;
|
||||
disabled?: boolean;
|
||||
enabled?: FilterPredicate;
|
||||
if?: FilterPredicate;
|
||||
inputs?: TInputs;
|
||||
output: Array<UOutput>;
|
||||
config?: {
|
||||
@@ -756,6 +756,8 @@ export interface CreateFrontendModuleOptions<
|
||||
// (undocumented)
|
||||
featureFlags?: FeatureFlagConfig[];
|
||||
// (undocumented)
|
||||
if?: FilterPredicate;
|
||||
// (undocumented)
|
||||
pluginId: TPluginId;
|
||||
}
|
||||
|
||||
@@ -770,45 +772,13 @@ export function createFrontendPlugin<
|
||||
[name in string]: ExternalRouteRef;
|
||||
} = {},
|
||||
>(
|
||||
options: CreateFrontendPluginOptions<
|
||||
TId,
|
||||
TRoutes,
|
||||
TExternalRoutes,
|
||||
TExtensions
|
||||
>,
|
||||
options: PluginOptions<TId, TRoutes, TExternalRoutes, TExtensions>,
|
||||
): OverridableFrontendPlugin<
|
||||
TRoutes,
|
||||
TExternalRoutes,
|
||||
MakeSortedExtensionsMap<TExtensions[number], TId>
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface CreateFrontendPluginOptions<
|
||||
TId extends string,
|
||||
TRoutes extends {
|
||||
[name in string]: RouteRef | SubRouteRef;
|
||||
},
|
||||
TExternalRoutes extends {
|
||||
[name in string]: ExternalRouteRef;
|
||||
},
|
||||
TExtensions extends readonly ExtensionDefinition[],
|
||||
> {
|
||||
// (undocumented)
|
||||
extensions?: TExtensions;
|
||||
// (undocumented)
|
||||
externalRoutes?: TExternalRoutes;
|
||||
// (undocumented)
|
||||
featureFlags?: FeatureFlagConfig[];
|
||||
icon?: IconElement;
|
||||
// (undocumented)
|
||||
info?: FrontendPluginInfoOptions;
|
||||
// (undocumented)
|
||||
pluginId: TId;
|
||||
// (undocumented)
|
||||
routes?: TRoutes;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function createRouteRef<
|
||||
TParams extends
|
||||
@@ -1031,7 +1001,7 @@ export interface ExtensionBlueprint<
|
||||
attachTo?: ExtensionDefinitionAttachTo<UParentInputs> &
|
||||
VerifyExtensionAttachTo<NonNullable<T['output']>, UParentInputs>;
|
||||
disabled?: boolean;
|
||||
enabled?: FilterPredicate;
|
||||
if?: FilterPredicate;
|
||||
params: TParamsInput extends ExtensionBlueprintDefineParams
|
||||
? TParamsInput
|
||||
: T['params'] extends ExtensionBlueprintDefineParams
|
||||
@@ -1067,7 +1037,7 @@ export interface ExtensionBlueprint<
|
||||
UParentInputs
|
||||
>;
|
||||
disabled?: boolean;
|
||||
enabled?: FilterPredicate;
|
||||
if?: FilterPredicate;
|
||||
inputs?: TExtraInputs & {
|
||||
[KName in keyof T['inputs']]?: `Error: Input '${KName &
|
||||
string}' is already defined in parent definition`;
|
||||
@@ -1709,7 +1679,7 @@ export interface OverridableExtensionDefinition<
|
||||
UParentInputs
|
||||
>;
|
||||
disabled?: boolean;
|
||||
enabled?: FilterPredicate;
|
||||
if?: FilterPredicate;
|
||||
inputs?: TExtraInputs & {
|
||||
[KName in keyof T['inputs']]?: `Error: Input '${KName &
|
||||
string}' is already defined in parent definition`;
|
||||
@@ -1815,6 +1785,7 @@ export interface OverridableFrontendPlugin<
|
||||
// (undocumented)
|
||||
withOverrides(options: {
|
||||
extensions?: Array<ExtensionDefinition>;
|
||||
if?: FilterPredicate;
|
||||
title?: string;
|
||||
icon?: IconElement;
|
||||
info?: FrontendPluginInfoOptions;
|
||||
@@ -1971,8 +1942,8 @@ export const pluginHeaderActionsApiRef: ApiRef_2<
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export type PluginOptions<
|
||||
// @public (undocumented)
|
||||
export interface PluginOptions<
|
||||
TId extends string,
|
||||
TRoutes extends {
|
||||
[name in string]: RouteRef | SubRouteRef;
|
||||
@@ -1981,7 +1952,24 @@ export type PluginOptions<
|
||||
[name in string]: ExternalRouteRef;
|
||||
},
|
||||
TExtensions extends readonly ExtensionDefinition[],
|
||||
> = CreateFrontendPluginOptions<TId, TRoutes, TExternalRoutes, TExtensions>;
|
||||
> {
|
||||
// (undocumented)
|
||||
extensions?: TExtensions;
|
||||
// (undocumented)
|
||||
externalRoutes?: TExternalRoutes;
|
||||
// (undocumented)
|
||||
featureFlags?: FeatureFlagConfig[];
|
||||
icon?: IconElement;
|
||||
// (undocumented)
|
||||
if?: FilterPredicate;
|
||||
// (undocumented)
|
||||
info?: FrontendPluginInfoOptions;
|
||||
// (undocumented)
|
||||
pluginId: TId;
|
||||
// (undocumented)
|
||||
routes?: TRoutes;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type PluginWrapperApi = {
|
||||
@@ -2064,6 +2052,19 @@ export const Progress: {
|
||||
// @public (undocumented)
|
||||
export type ProgressProps = {};
|
||||
|
||||
// @public
|
||||
export type ResolvedExtensionInputs<
|
||||
TInputs extends {
|
||||
[name in string]: ExtensionInput;
|
||||
},
|
||||
> = {
|
||||
[InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton']
|
||||
? Array<Expand<ResolvedExtensionInput<TInputs[InputName]>>>
|
||||
: false extends TInputs[InputName]['config']['optional']
|
||||
? Expand<ResolvedExtensionInput<TInputs[InputName]>>
|
||||
: Expand<ResolvedExtensionInput<TInputs[InputName]> | undefined>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type RouteFunc<TParams extends AnyRouteRefParams> = (
|
||||
...input: TParams extends undefined ? readonly [] : readonly [params: TParams]
|
||||
|
||||
@@ -46,6 +46,7 @@ describe('createFrontendModule', () => {
|
||||
"disabled": false,
|
||||
"factory": [Function],
|
||||
"id": "route:test/test",
|
||||
"if": undefined,
|
||||
"inputs": {},
|
||||
"output": [],
|
||||
"toString": [Function],
|
||||
@@ -53,6 +54,7 @@ describe('createFrontendModule', () => {
|
||||
},
|
||||
],
|
||||
"featureFlags": [],
|
||||
"if": undefined,
|
||||
"pluginId": "test",
|
||||
"toString": [Function],
|
||||
"version": "v1",
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
resolveExtensionDefinition,
|
||||
} from './resolveExtensionDefinition';
|
||||
import { FeatureFlagConfig } from './types';
|
||||
import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
|
||||
/** @public */
|
||||
export interface CreateFrontendModuleOptions<
|
||||
@@ -30,6 +31,7 @@ export interface CreateFrontendModuleOptions<
|
||||
pluginId: TPluginId;
|
||||
extensions?: TExtensions;
|
||||
featureFlags?: FeatureFlagConfig[];
|
||||
if?: FilterPredicate;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
@@ -43,6 +45,7 @@ export interface InternalFrontendModule extends FrontendModule {
|
||||
readonly version: 'v1';
|
||||
readonly extensions: Extension<unknown>[];
|
||||
readonly featureFlags: FeatureFlagConfig[];
|
||||
readonly if?: FilterPredicate;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,6 +129,7 @@ export function createFrontendModule<
|
||||
version: 'v1',
|
||||
pluginId,
|
||||
featureFlags: options.featureFlags ?? [],
|
||||
if: options.if,
|
||||
extensions,
|
||||
toString() {
|
||||
return `Module{pluginId=${pluginId}}`;
|
||||
|
||||
@@ -171,6 +171,7 @@ describe('createFrontendPlugin', () => {
|
||||
"configSchema": undefined,
|
||||
"disabled": false,
|
||||
"factory": [Function],
|
||||
"if": undefined,
|
||||
"inputs": {},
|
||||
"kind": undefined,
|
||||
"name": "1",
|
||||
@@ -359,6 +360,7 @@ describe('createFrontendPlugin', () => {
|
||||
"configSchema": undefined,
|
||||
"disabled": false,
|
||||
"factory": [Function],
|
||||
"if": undefined,
|
||||
"inputs": {},
|
||||
"kind": undefined,
|
||||
"name": "1",
|
||||
|
||||
@@ -32,6 +32,7 @@ import { JsonObject } from '@backstage/types';
|
||||
import { IconElement } from '../icons/types';
|
||||
import { RouteRef, SubRouteRef, ExternalRouteRef } from '../routing';
|
||||
import { ID_PATTERN } from './constants';
|
||||
import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
|
||||
/**
|
||||
* Information about the plugin.
|
||||
@@ -195,6 +196,7 @@ export interface CreateFrontendPluginOptions<
|
||||
externalRoutes?: TExternalRoutes;
|
||||
extensions?: TExtensions;
|
||||
featureFlags?: FeatureFlagConfig[];
|
||||
if?: FilterPredicate;
|
||||
info?: FrontendPluginInfoOptions;
|
||||
}
|
||||
|
||||
@@ -304,6 +306,7 @@ export function createFrontendPlugin<
|
||||
routes: options.routes ?? ({} as TRoutes),
|
||||
externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes),
|
||||
featureFlags: options.featureFlags ?? [],
|
||||
if: options.if,
|
||||
extensions: extensions,
|
||||
infoOptions: options.info,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user