Merge pull request #26204 from backstage/rugvip/type-params

frontend-plugin-api: consolidate type parameters for extension definitions and blueprints
This commit is contained in:
Patrik Oldsberg
2024-08-27 10:58:34 +02:00
committed by GitHub
60 changed files with 2496 additions and 2417 deletions
+21
View File
@@ -0,0 +1,21 @@
---
'@backstage/frontend-test-utils': patch
'@backstage/frontend-app-api': patch
'@backstage/core-compat-api': patch
'@backstage/plugin-catalog-import': patch
'@backstage/plugin-catalog-graph': patch
'@backstage/plugin-catalog-react': patch
'@backstage/plugin-user-settings': patch
'@backstage/plugin-search-react': patch
'@backstage/plugin-kubernetes': patch
'@backstage/plugin-scaffolder': patch
'@backstage/plugin-api-docs': patch
'@backstage/plugin-devtools': patch
'@backstage/plugin-techdocs': patch
'@backstage/plugin-catalog': patch
'@backstage/plugin-search': patch
'@backstage/plugin-home': patch
'@backstage/plugin-org': patch
---
Updated exports to use the new type parameters for extensions and extension blueprints.
+31
View File
@@ -0,0 +1,31 @@
---
'@backstage/frontend-plugin-api': minor
---
**BREAKING**: Updated the type parameters for `ExtensionDefinition` and `ExtensionBlueprint` to only have a single object parameter. The base type parameter is exported as `ExtensionDefinitionParameters` and `ExtensionBlueprintParameters` respectively. This is shipped as an immediate breaking change as we expect usage of these types to be rare, and it does not affect the runtime behavior of the API.
This is a breaking change as it changes the type parameters. Existing usage can generally be updated as follows:
- `ExtensionDefinition<any>` -> `ExtensionDefinition`
- `ExtensionDefinition<any, any>` -> `ExtensionDefinition`
- `ExtensionDefinition<TConfig>` -> `ExtensionDefinition<{ config: TConfig }>`
- `ExtensionDefinition<TConfig, TConfigInput>` -> `ExtensionDefinition<{ config: TConfig, configInput: TConfigInput }>`
If you need to infer the parameter you can use `ExtensionDefinitionParameters`, for example:
```ts
import {
ExtensionDefinition,
ExtensionDefinitionParameters,
} from '@backstage/frontend-plugin-api';
function myUtility<T extends ExtensionDefinitionParameters>(
ext: ExtensionDefinition<T>,
): T['config'] {
// ...
}
```
The same patterns apply to `ExtensionBlueprint`.
This change is made to improve the readability of API references and ability to evolve the type parameters in the future.
+24 -25
View File
@@ -15,33 +15,32 @@ const examplePlugin: BackstagePlugin<
{},
{},
{
'page:example': ExtensionDefinition<
{
'page:example': ExtensionDefinition<{
kind: 'page';
namespace: undefined;
name: undefined;
config: {
path: string | undefined;
},
{
};
configInput: {
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>,
{},
{
kind: 'page';
namespace: undefined;
name: undefined;
}
>;
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {};
}>;
}
>;
export default examplePlugin;
+2 -2
View File
@@ -37,13 +37,13 @@ export function convertLegacyPageExtension(
name?: string;
defaultPath?: string;
},
): ExtensionDefinition<any>;
): ExtensionDefinition;
// @public (undocumented)
export function convertLegacyPlugin(
legacyPlugin: BackstagePlugin,
options: {
extensions: ExtensionDefinition<any, any>[];
extensions: ExtensionDefinition[];
},
): BackstagePlugin_2;
@@ -106,7 +106,7 @@ function visitRouteChildren(options: {
parentExtensionId: string;
context: {
pluginId: string;
extensions: ExtensionDefinition<any, any>[];
extensions: ExtensionDefinition[];
getUniqueName: () => string;
discoverPlugin: (plugin: LegacyBackstagePlugin) => void;
};
@@ -159,7 +159,7 @@ export function collectLegacyRoutes(
): BackstagePlugin[] {
const pluginExtensions = new Map<
LegacyBackstagePlugin,
ExtensionDefinition<any, any>[]
ExtensionDefinition[]
>();
const getUniqueName = (() => {
@@ -48,12 +48,11 @@ describe('convertLegacyPageExtension', () => {
);
const converted = convertLegacyPageExtension(LegacyExtension);
expect(converted.kind).toBe('page');
expect(converted.namespace).toBe(undefined);
expect(converted.name).toBe('example');
const tester = createExtensionTester(converted);
expect(tester.query(converted).node.spec.id).toBe('page:example');
await renderInTestApp(tester.reactElement(), {
mountedRoutes: {
'/': convertLegacyRouteRef(routeRef),
@@ -79,12 +78,11 @@ describe('convertLegacyPageExtension', () => {
name: 'other',
defaultPath: '/other',
});
expect(converted.kind).toBe('page');
expect(converted.namespace).toBe(undefined);
expect(converted.name).toBe('other');
const tester = createExtensionTester(converted);
expect(tester.query(converted).node.spec.id).toBe('page:other');
await renderInTestApp(tester.reactElement(), {
mountedRoutes: {
'/': convertLegacyRouteRef(routeRef),
@@ -35,7 +35,7 @@ export function convertLegacyPageExtension(
name?: string;
defaultPath?: string;
},
): ExtensionDefinition<any> {
): ExtensionDefinition {
const element = <LegacyExtension />;
const extName = getComponentData<string>(element, 'core.extensionName');
@@ -26,7 +26,7 @@ import { convertLegacyRouteRefs } from './convertLegacyRouteRef';
/** @public */
export function convertLegacyPlugin(
legacyPlugin: LegacyBackstagePlugin,
options: { extensions: ExtensionDefinition<any, any>[] },
options: { extensions: ExtensionDefinition[] },
): NewBackstagePlugin {
const apiExtensions = Array.from(legacyPlugin.getApis()).map(factory =>
ApiBlueprint.make({ namespace: factory.api.id, params: { factory } }),
@@ -76,7 +76,7 @@ function createTestExtension(options: {
});
}
function routeInfoFromExtensions(extensions: ExtensionDefinition<any, any>[]) {
function routeInfoFromExtensions(extensions: ExtensionDefinition[]) {
const plugin = createFrontendPlugin({
id: 'test',
extensions,
@@ -44,12 +44,13 @@ function makeExtDef(
) {
return {
$$type: '@backstage/ExtensionDefinition',
T: undefined as any,
version: 'v1',
name,
attachTo: { id: attachId, input: 'default' },
disabled: status === 'disabled',
override: () => ({} as ExtensionDefinition<unknown>),
} as ExtensionDefinition<unknown>;
override: () => ({} as ExtensionDefinition),
} as ExtensionDefinition;
}
describe('resolveAppNodeSpecs', () => {
@@ -22,6 +22,7 @@ import {
AppTreeApi,
appTreeApiRef,
coreExtensionData,
ExtensionDefinition,
FrontendFeature,
RouteRef,
ExternalRouteRef,
@@ -112,28 +113,30 @@ const DefaultApis = defaultApis.map(factory =>
ApiBlueprint.make({ namespace: factory.api.id, params: { factory } }),
);
export const builtinExtensions = [
Root,
App,
AppRoot,
AppRoutes,
AppNav,
AppLayout,
DefaultProgressComponent,
DefaultErrorBoundaryComponent,
DefaultNotFoundErrorPageComponent,
LightTheme,
DarkTheme,
oauthRequestDialogAppRootElement,
alertDisplayAppRootElement,
AppThemeApi,
AppLanguageApi,
IconsApi,
TranslationsApi,
ComponentsApi,
FeatureFlagsApi,
...DefaultApis,
].map(def => resolveExtensionDefinition(def));
export const builtinExtensions = (
[
Root,
App,
AppRoot,
AppRoutes,
AppNav,
AppLayout,
DefaultProgressComponent,
DefaultErrorBoundaryComponent,
DefaultNotFoundErrorPageComponent,
LightTheme,
DarkTheme,
oauthRequestDialogAppRootElement,
alertDisplayAppRootElement,
AppThemeApi,
AppLanguageApi,
IconsApi,
TranslationsApi,
ComponentsApi,
FeatureFlagsApi,
...DefaultApis,
] as ExtensionDefinition[]
).map(def => resolveExtensionDefinition(def));
function deduplicateFeatures(
allFeatures: FrontendFeature[],
File diff suppressed because it is too large Load Diff
@@ -37,6 +37,7 @@ describe('ApiBlueprint', () => {
expect(extension).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "root",
"input": "apis",
@@ -86,6 +87,7 @@ describe('ApiBlueprint', () => {
expect(extension).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "root",
"input": "apis",
@@ -27,6 +27,7 @@ describe('AppRootElementBlueprint', () => {
expect(extension).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "app/root",
"input": "elements",
@@ -37,6 +37,7 @@ describe('AppRootWrapperBlueprint', () => {
expect(extension).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "app/root",
"input": "wrappers",
@@ -33,6 +33,7 @@ describe('NavItemBlueprint', () => {
expect(extension).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "app/nav",
"input": "items",
@@ -30,6 +30,7 @@ describe('NavLogoBlueprint', () => {
expect(extension).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "app/nav",
"input": "logos",
@@ -43,6 +43,7 @@ describe('PageBlueprint', () => {
expect(myPage).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "app/routes",
"input": "routes",
@@ -38,6 +38,7 @@ describe('RouterBlueprint', () => {
expect(extension).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "app/root",
"input": "router",
@@ -31,6 +31,7 @@ describe('SignInPageBlueprint', () => {
).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "app/root",
"input": "signInPage",
@@ -31,6 +31,7 @@ describe('ThemeBlueprint', () => {
.toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "api:app-theme",
"input": "themes",
@@ -46,6 +46,7 @@ describe('TranslationBlueprint', () => {
).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "api:translations",
"input": "translations",
@@ -18,6 +18,7 @@ import { createExtensionTester } from '@backstage/frontend-test-utils';
import { createExtension } from './createExtension';
import { createExtensionDataRef } from './createExtensionDataRef';
import { createExtensionInput } from './createExtensionInput';
import { PortableSchema } from '../schema';
const stringDataRef = createExtensionDataRef<string>().with({ id: 'string' });
const numberDataRef = createExtensionDataRef<number>().with({ id: 'number' });
@@ -289,7 +290,12 @@ describe('createExtension', () => {
);
expect(
extension.configSchema?.parse({
(
(extension as any).configSchema as PortableSchema<
(typeof extension.T)['config'],
(typeof extension.T)['configInput']
>
)?.parse({
foo: 'x',
bar: 'y',
baz: 'z',
@@ -302,7 +308,12 @@ describe('createExtension', () => {
baz: 'z',
});
expect(
extension.configSchema?.parse({
(
(extension as any).configSchema as PortableSchema<
(typeof extension.T)['config'],
(typeof extension.T)['configInput']
>
)?.parse({
foo: 'x',
}),
).toEqual({
@@ -142,33 +142,27 @@ export type CreateExtensionOptions<
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>;
/** @public */
export interface ExtensionDefinition<
TConfig,
TConfigInput = TConfig,
UOutput extends AnyExtensionDataRef = AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
export type ExtensionDefinitionParameters = {
kind?: string;
namespace?: string;
name?: string;
configInput?: { [K in string]: any };
config?: { [K in string]: any };
output?: AnyExtensionDataRef;
inputs?: {
[KName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
} = {},
TIdParts extends {
kind?: string;
namespace?: string;
name?: string;
} = {
kind?: string;
namespace?: string;
name?: string;
},
> {
};
};
/** @public */
export type ExtensionDefinition<
T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters,
> = {
$$type: '@backstage/ExtensionDefinition';
readonly kind?: TIdParts['kind'];
readonly namespace?: TIdParts['namespace'];
readonly name?: TIdParts['name'];
readonly attachTo: { id: string; input: string };
readonly disabled: boolean;
readonly configSchema?: PortableSchema<TConfig, TConfigInput>;
readonly T: T;
override<
TExtensionConfigSchema extends {
@@ -187,78 +181,71 @@ export interface ExtensionDefinition<
attachTo?: { id: string; input: string };
disabled?: boolean;
inputs?: TExtraInputs & {
[KName in keyof TInputs]?: `Error: Input '${KName &
[KName in keyof T['inputs']]?: `Error: Input '${KName &
string}' is already defined in parent definition`;
};
output?: Array<UNewOutput>;
config?: {
schema: TExtensionConfigSchema & {
[KName in keyof TConfig]?: `Error: Config key '${KName &
[KName in keyof T['config']]?: `Error: Config key '${KName &
string}' is already defined in parent schema`;
};
};
factory(
originalFactory: (context?: {
config?: TConfig;
inputs?: ResolveInputValueOverrides<TInputs>;
}) => ExtensionDataContainer<UOutput>,
config?: T['config'];
inputs?: ResolveInputValueOverrides<NonNullable<T['inputs']>>;
}) => ExtensionDataContainer<NonNullable<T['output']>>,
context: {
node: AppNode;
apis: ApiHolder;
config: TConfig & {
config: T['config'] & {
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
};
inputs: Expand<ResolvedExtensionInputs<TInputs & TExtraInputs>>;
inputs: Expand<ResolvedExtensionInputs<T['inputs'] & TExtraInputs>>;
},
): Iterable<UFactoryOutput>;
} & VerifyExtensionFactoryOutput<
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
AnyExtensionDataRef extends UNewOutput
? NonNullable<T['output']>
: UNewOutput,
UFactoryOutput
>,
): ExtensionDefinition<
{
): ExtensionDefinition<{
kind: T['kind'];
namespace: T['namespace'];
name: T['name'];
output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput;
inputs: T['inputs'] & TExtraInputs;
config: T['config'] & {
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
} & TConfig,
z.input<
z.ZodObject<{
[key in keyof TExtensionConfigSchema]: ReturnType<
TExtensionConfigSchema[key]
>;
}>
> &
TConfigInput,
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
TInputs & TExtraInputs,
TIdParts
>;
}
};
configInput: T['configInput'] &
z.input<
z.ZodObject<{
[key in keyof TExtensionConfigSchema]: ReturnType<
TExtensionConfigSchema[key]
>;
}>
>;
}>;
};
/** @internal */
export type InternalExtensionDefinition<
TConfig,
TConfigInput = TConfig,
UOutput extends AnyExtensionDataRef = AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
} = {},
TIdParts extends {
kind?: string;
namespace?: string;
name?: string;
} = {
kind?: string;
namespace?: string;
name?: string;
},
> = ExtensionDefinition<TConfig, TConfigInput, UOutput, TInputs, TIdParts> &
(
T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters,
> = ExtensionDefinition<T> & {
readonly kind?: string;
readonly namespace?: string;
readonly name?: string;
readonly attachTo: { id: string; input: string };
readonly disabled: boolean;
readonly configSchema?: PortableSchema<T['config'], T['configInput']>;
} & (
| {
readonly version: 'v1';
readonly inputs: {
@@ -276,7 +263,7 @@ export type InternalExtensionDefinition<
factory(context: {
node: AppNode;
apis: ApiHolder;
config: TConfig;
config: object;
inputs: {
[inputName in string]: unknown;
};
@@ -296,7 +283,7 @@ export type InternalExtensionDefinition<
factory(context: {
node: AppNode;
apis: ApiHolder;
config: TConfig;
config: object;
inputs: ResolvedExtensionInputs<{
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
@@ -308,13 +295,10 @@ export type InternalExtensionDefinition<
);
/** @internal */
export function toInternalExtensionDefinition<TConfig, TConfigInput>(
overrides: ExtensionDefinition<TConfig, TConfigInput>,
): InternalExtensionDefinition<TConfig, TConfigInput> {
const internal = overrides as InternalExtensionDefinition<
TConfig,
TConfigInput
>;
export function toInternalExtensionDefinition<
T extends ExtensionDefinitionParameters,
>(overrides: ExtensionDefinition<T>): InternalExtensionDefinition<T> {
const internal = overrides as InternalExtensionDefinition<T>;
if (internal.$$type !== '@backstage/ExtensionDefinition') {
throw new Error(
`Invalid extension definition instance, bad type '${internal.$$type}'`,
@@ -353,23 +337,45 @@ export function createExtension<
TConfigSchema,
UFactoryOutput
>,
): ExtensionDefinition<
{
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
},
z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>,
UOutput,
TInputs,
{
): ExtensionDefinition<{
config: string extends keyof TConfigSchema
? {}
: {
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
};
configInput: string extends keyof TConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>;
output: UOutput;
inputs: TInputs;
kind: string | undefined extends TKind ? undefined : TKind;
namespace: string | undefined extends TNamespace ? undefined : TNamespace;
name: string | undefined extends TName ? undefined : TName;
}> {
type T = {
config: string extends keyof TConfigSchema
? {}
: {
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
};
configInput: string extends keyof TConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>;
output: UOutput;
inputs: TInputs;
kind: string | undefined extends TKind ? undefined : TKind;
namespace: string | undefined extends TNamespace ? undefined : TNamespace;
name: string | undefined extends TName ? undefined : TName;
}
> {
};
const schemaDeclaration = options.config?.schema;
const configSchema =
schemaDeclaration &&
@@ -384,6 +390,7 @@ export function createExtension<
return {
$$type: '@backstage/ExtensionDefinition',
version: 'v2',
T: undefined as unknown as T,
kind: options.kind,
namespace: options.namespace,
name: options.name,
@@ -486,21 +493,5 @@ export function createExtension<
},
}) as ExtensionDefinition<any>;
},
} as InternalExtensionDefinition<
{
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
},
z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>,
UOutput,
TInputs,
{
kind: string | undefined extends TKind ? undefined : TKind;
namespace: string | undefined extends TNamespace ? undefined : TNamespace;
name: string | undefined extends TName ? undefined : TName;
}
>;
} as InternalExtensionDefinition<T>;
}
@@ -35,10 +35,7 @@ import { createExtensionDataContainer } from './createExtensionDataContainer';
function unused(..._any: any[]) {}
function factoryOutput(
ext: ExtensionDefinition<any, any>,
inputs: unknown = undefined,
) {
function factoryOutput(ext: ExtensionDefinition, inputs: unknown = undefined) {
const int = toInternalExtensionDefinition(ext);
if (int.version !== 'v2') {
throw new Error('Expected v2 extension');
@@ -81,28 +81,31 @@ export type CreateExtensionBlueprintOptions<
dataRefs?: TDataRefs;
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>;
/** @public */
export type ExtensionBlueprintParameters = {
kind: string;
namespace?: string;
name?: string;
params?: object;
configInput?: { [K in string]: any };
config?: { [K in string]: any };
output?: AnyExtensionDataRef;
inputs?: {
[KName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
};
dataRefs?: { [name in string]: AnyExtensionDataRef };
};
/**
* @public
*/
export interface ExtensionBlueprint<
TIdParts extends {
kind: string;
namespace?: string;
name?: string;
},
TParams,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
},
TConfig extends { [key in string]: unknown },
TConfigInput extends { [key in string]: unknown },
TDataRefs extends { [name in string]: AnyExtensionDataRef },
T extends ExtensionBlueprintParameters = ExtensionBlueprintParameters,
> {
dataRefs: TDataRefs;
dataRefs: T['dataRefs'];
make<
TNewNamespace extends string | undefined,
@@ -112,20 +115,18 @@ export interface ExtensionBlueprint<
name?: TNewName;
attachTo?: { id: string; input: string };
disabled?: boolean;
params: TParams;
}): ExtensionDefinition<
TConfig,
TConfigInput,
UOutput,
TInputs,
{
kind: TIdParts['kind'];
namespace: string | undefined extends TNewNamespace
? TIdParts['namespace']
: TNewNamespace;
name: string | undefined extends TNewName ? TIdParts['name'] : TNewName;
}
>;
params: T['params'];
}): ExtensionDefinition<{
kind: T['kind'];
namespace: string | undefined extends TNewNamespace
? T['namespace']
: TNewNamespace;
name: string | undefined extends TNewName ? T['name'] : TNewName;
config: T['config'];
configInput: T['configInput'];
output: T['output'];
inputs: T['inputs'];
}>;
/**
* Creates a new extension from the blueprint.
@@ -153,63 +154,68 @@ export interface ExtensionBlueprint<
attachTo?: { id: string; input: string };
disabled?: boolean;
inputs?: TExtraInputs & {
[KName in keyof TInputs]?: `Error: Input '${KName &
[KName in keyof T['inputs']]?: `Error: Input '${KName &
string}' is already defined in parent definition`;
};
output?: Array<UNewOutput>;
config?: {
schema: TExtensionConfigSchema & {
[KName in keyof TConfig]?: `Error: Config key '${KName &
[KName in keyof T['config']]?: `Error: Config key '${KName &
string}' is already defined in parent schema`;
};
};
factory(
originalFactory: (
params: TParams,
params: T['params'],
context?: {
config?: TConfig;
inputs?: ResolveInputValueOverrides<TInputs>;
config?: T['config'];
inputs?: ResolveInputValueOverrides<NonNullable<T['inputs']>>;
},
) => ExtensionDataContainer<UOutput>,
) => ExtensionDataContainer<NonNullable<T['output']>>,
context: {
node: AppNode;
apis: ApiHolder;
config: TConfig & {
config: T['config'] & {
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
};
inputs: Expand<ResolvedExtensionInputs<TInputs & TExtraInputs>>;
inputs: Expand<ResolvedExtensionInputs<T['inputs'] & TExtraInputs>>;
},
): Iterable<UFactoryOutput> &
VerifyExtensionFactoryOutput<
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
AnyExtensionDataRef extends UNewOutput
? NonNullable<T['output']>
: UNewOutput,
UFactoryOutput
>;
}): ExtensionDefinition<
{
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
} & TConfig,
z.input<
z.ZodObject<{
[key in keyof TExtensionConfigSchema]: ReturnType<
TExtensionConfigSchema[key]
>;
}>
> &
TConfigInput,
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
TInputs & TExtraInputs,
{
kind: TIdParts['kind'];
namespace: string | undefined extends TNewNamespace
? TIdParts['namespace']
: TNewNamespace;
name: string | undefined extends TNewName ? TIdParts['name'] : TNewName;
}
>;
}): ExtensionDefinition<{
config: (string extends keyof TExtensionConfigSchema
? {}
: {
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
}) &
T['config'];
configInput: (string extends keyof TExtensionConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TExtensionConfigSchema]: ReturnType<
TExtensionConfigSchema[key]
>;
}>
>) &
T['configInput'];
output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput;
inputs: T['inputs'] & TExtraInputs;
kind: T['kind'];
namespace: string | undefined extends TNewNamespace
? T['namespace']
: TNewNamespace;
name: string | undefined extends TNewName ? T['name'] : TNewName;
}>;
}
/**
@@ -219,7 +225,7 @@ export interface ExtensionBlueprint<
* @public
*/
export function createExtensionBlueprint<
TParams,
TParams extends object,
UOutput extends AnyExtensionDataRef,
TInputs extends {
[inputName in string]: ExtensionInput<
@@ -245,27 +251,25 @@ export function createExtensionBlueprint<
UFactoryOutput,
TDataRefs
>,
): ExtensionBlueprint<
{
kind: TKind;
namespace: TNamespace;
name: TName;
},
TParams,
UOutput,
string extends keyof TInputs ? {} : TInputs,
string extends keyof TConfigSchema
): ExtensionBlueprint<{
kind: TKind;
namespace: TNamespace;
name: TName;
params: TParams;
output: UOutput;
inputs: string extends keyof TInputs ? {} : TInputs;
config: string extends keyof TConfigSchema
? {}
: { [key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>> },
string extends keyof TConfigSchema
: { [key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>> };
configInput: string extends keyof TConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>,
TDataRefs
> {
>;
dataRefs: TDataRefs;
}> {
return {
dataRefs: options.dataRefs,
make(args) {
@@ -282,7 +286,7 @@ export function createExtensionBlueprint<
options.factory(args.params, ctx) as Iterable<
ExtensionDataValue<any, any>
>,
});
}) as ExtensionDefinition;
},
makeWithOverrides(args) {
return createExtension({
@@ -327,29 +331,27 @@ export function createExtensionBlueprint<
},
) as Iterable<ExtensionDataValue<any, any>>;
},
}) as ExtensionDefinition<any>;
}) as ExtensionDefinition;
},
} as ExtensionBlueprint<
{
kind: TKind;
namespace: TNamespace;
name: TName;
},
TParams,
UOutput,
string extends keyof TInputs ? {} : TInputs,
string extends keyof TConfigSchema
} as ExtensionBlueprint<{
kind: TKind;
namespace: TNamespace;
name: TName;
params: TParams;
output: UOutput;
inputs: string extends keyof TInputs ? {} : TInputs;
config: string extends keyof TConfigSchema
? {}
: {
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
},
string extends keyof TConfigSchema
};
configInput: string extends keyof TConfigSchema
? {}
: z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>,
TDataRefs
>;
>;
dataRefs: TDataRefs;
}>;
}
@@ -65,6 +65,7 @@ describe('createExtensionOverrides', () => {
"extensions": [
{
"$$type": "@backstage/Extension",
"T": undefined,
"attachTo": {
"id": "app",
"input": "apis",
@@ -80,6 +81,7 @@ describe('createExtensionOverrides', () => {
},
{
"$$type": "@backstage/Extension",
"T": undefined,
"attachTo": {
"id": "app",
"input": "apis",
@@ -95,6 +97,7 @@ describe('createExtensionOverrides', () => {
},
{
"$$type": "@backstage/Extension",
"T": undefined,
"attachTo": {
"id": "app",
"input": "apis",
@@ -23,7 +23,7 @@ import { ExtensionOverrides, FeatureFlagConfig } from './types';
/** @public */
export interface ExtensionOverridesOptions {
extensions: ExtensionDefinition<any, any>[];
extensions: ExtensionDefinition[];
featureFlags?: FeatureFlagConfig[];
}
@@ -150,6 +150,7 @@ describe('createFrontendPlugin', () => {
expect(plugin.getExtension('test/1')).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "test/output",
"input": "names",
@@ -281,6 +282,7 @@ describe('createFrontendPlugin', () => {
expect(plugin.getExtension('test/1')).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "test/output",
"input": "names",
@@ -14,7 +14,11 @@
* limitations under the License.
*/
import { ExtensionDefinition } from './createExtension';
import {
ExtensionDefinition,
InternalExtensionDefinition,
toInternalExtensionDefinition,
} from './createExtension';
import {
Extension,
ResolveExtensionId,
@@ -32,7 +36,7 @@ export interface PluginOptions<
TId extends string,
TRoutes extends AnyRoutes,
TExternalRoutes extends AnyExternalRoutes,
TExtensions extends readonly ExtensionDefinition<any, any>[],
TExtensions extends readonly ExtensionDefinition[],
> {
id: TId;
routes?: TRoutes;
@@ -56,7 +60,7 @@ export function createFrontendPlugin<
TId extends string,
TRoutes extends AnyRoutes = {},
TExternalRoutes extends AnyExternalRoutes = {},
TExtensions extends readonly ExtensionDefinition<any, any>[] = [],
TExtensions extends readonly ExtensionDefinition[] = [],
>(
options: PluginOptions<TId, TRoutes, TExternalRoutes, TExtensions>,
): BackstagePlugin<
@@ -69,16 +73,20 @@ export function createFrontendPlugin<
>]: KExtension;
}
> {
const extensions = new Array<Extension<unknown>>();
const extensions = new Array<Extension<any>>();
const extensionDefinitionsById = new Map<
string,
ExtensionDefinition<unknown>
InternalExtensionDefinition
>();
for (const def of options.extensions ?? []) {
const internal = toInternalExtensionDefinition(def);
const ext = resolveExtensionDefinition(def, { namespace: options.id });
extensions.push(ext);
extensionDefinitionsById.set(ext.id, { ...def, namespace: options.id });
extensionDefinitionsById.set(ext.id, {
...internal,
namespace: options.id,
});
}
if (extensions.length !== extensionDefinitionsById.size) {
@@ -18,6 +18,7 @@ export { coreExtensionData } from './coreExtensionData';
export {
createExtension,
type ExtensionDefinition,
type ExtensionDefinitionParameters,
type CreateExtensionOptions,
type ResolvedExtensionInput,
type ResolvedExtensionInputs,
@@ -56,6 +57,7 @@ export {
export {
type CreateExtensionBlueprintOptions,
type ExtensionBlueprint,
type ExtensionBlueprintParameters,
createExtensionBlueprint,
} from './createExtensionBlueprint';
export { type ResolveInputValueOverrides } from './resolveInputOverrides';
@@ -23,10 +23,11 @@ import {
describe('resolveExtensionDefinition', () => {
const baseDef = {
$$type: '@backstage/ExtensionDefinition',
T: undefined as any,
version: 'v2',
attachTo: { id: '', input: '' },
disabled: false,
override: () => ({} as ExtensionDefinition<unknown>),
override: () => ({} as ExtensionDefinition),
};
it.each([
@@ -39,7 +40,7 @@ describe('resolveExtensionDefinition', () => {
const resolved = resolveExtensionDefinition({
...baseDef,
...definition,
} as ExtensionDefinition<unknown>);
} as ExtensionDefinition);
expect(resolved.id).toBe(expected);
expect(String(resolved)).toBe(`Extension{id=${expected}}`);
});
@@ -49,12 +50,12 @@ describe('resolveExtensionDefinition', () => {
resolveExtensionDefinition({
...baseDef,
kind: 'k',
} as ExtensionDefinition<unknown>),
} as ExtensionDefinition),
).toThrow(
'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=k namespace=undefined name=undefined',
);
expect(() =>
resolveExtensionDefinition(baseDef as ExtensionDefinition<unknown>),
resolveExtensionDefinition(baseDef as ExtensionDefinition),
).toThrow(
'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=undefined namespace=undefined name=undefined',
);
@@ -64,10 +65,11 @@ describe('resolveExtensionDefinition', () => {
describe('old resolveExtensionDefinition', () => {
const baseDef = {
$$type: '@backstage/ExtensionDefinition',
T: undefined as any,
version: 'v1',
attachTo: { id: '', input: '' },
disabled: false,
override: () => ({} as ExtensionDefinition<unknown>),
override: () => ({} as ExtensionDefinition),
};
it.each([
@@ -80,7 +82,7 @@ describe('old resolveExtensionDefinition', () => {
const resolved = resolveExtensionDefinition({
...baseDef,
...definition,
} as ExtensionDefinition<unknown>);
} as ExtensionDefinition);
expect(resolved.id).toBe(expected);
expect(String(resolved)).toBe(`Extension{id=${expected}}`);
});
@@ -90,12 +92,12 @@ describe('old resolveExtensionDefinition', () => {
resolveExtensionDefinition({
...baseDef,
kind: 'k',
} as ExtensionDefinition<unknown>),
} as ExtensionDefinition),
).toThrow(
'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=k namespace=undefined name=undefined',
);
expect(() =>
resolveExtensionDefinition(baseDef as ExtensionDefinition<unknown>),
resolveExtensionDefinition(baseDef as ExtensionDefinition),
).toThrow(
'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=undefined namespace=undefined name=undefined',
);
@@ -108,13 +110,12 @@ describe('ResolveExtensionId', () => {
TKind extends string | undefined,
TNamespace extends string | undefined,
TName extends string | undefined,
> = ExtensionDefinition<
any,
any,
any,
any,
{ kind: TKind; namespace: TNamespace; name: TName }
>;
> = ExtensionDefinition<{
kind: TKind;
namespace: TNamespace;
name: TName;
output: any;
}>;
const id1: 'k:ns' = {} as ResolveExtensionId<
NamedExtension<'k', 'ns', undefined>,
@@ -17,6 +17,7 @@
import { ApiHolder, AppNode } from '../apis';
import {
ExtensionDefinition,
ExtensionDefinitionParameters,
ResolvedExtensionInputs,
toInternalExtensionDefinition,
} from './createExtension';
@@ -109,19 +110,13 @@ export function toInternalExtension<TConfig, TConfigInput>(
/** @ignore */
export type ResolveExtensionId<
TExtension extends ExtensionDefinition<any>,
TExtension extends ExtensionDefinition,
TDefaultNamespace extends string | undefined,
> = TExtension extends ExtensionDefinition<
any,
any,
any,
any,
{
kind: infer IKind extends string | undefined;
namespace: infer INamespace extends string | undefined;
name: infer IName extends string | undefined;
}
>
> = TExtension extends ExtensionDefinition<{
kind: infer IKind extends string | undefined;
namespace: infer INamespace extends string | undefined;
name: infer IName extends string | undefined;
}>
? [string | undefined] extends [IKind | INamespace | IName]
? never
: (
@@ -140,10 +135,12 @@ export type ResolveExtensionId<
: never;
/** @internal */
export function resolveExtensionDefinition<TConfig, TConfigInput>(
definition: ExtensionDefinition<TConfig, TConfigInput>,
export function resolveExtensionDefinition<
T extends ExtensionDefinitionParameters,
>(
definition: ExtensionDefinition<T>,
context?: { namespace?: string },
): Extension<TConfig, TConfigInput> {
): Extension<T['config'], T['configInput']> {
const internalDefinition = toInternalExtensionDefinition(definition);
const {
name,
@@ -172,5 +169,5 @@ export function resolveExtensionDefinition<TConfig, TConfigInput>(
toString() {
return `Extension{id=${id}}`;
},
} as InternalExtension<TConfig, TConfigInput>;
} as InternalExtension<T['config'], T['configInput']> & Object;
}
@@ -35,7 +35,7 @@ export type AnyExternalRoutes = { [name in string]: ExternalRouteRef };
/** @public */
export type ExtensionMap<
TExtensionMap extends { [id in string]: ExtensionDefinition<any, any> },
TExtensionMap extends { [id in string]: ExtensionDefinition },
> = {
get<TId extends keyof TExtensionMap>(id: TId): TExtensionMap[TId];
};
@@ -44,7 +44,7 @@ export type ExtensionMap<
export interface BackstagePlugin<
TRoutes extends AnyRoutes = AnyRoutes,
TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes,
TExtensionMap extends { [id in string]: ExtensionDefinition<any, any> } = {},
TExtensionMap extends { [id in string]: ExtensionDefinition } = {},
> {
readonly $$type: '@backstage/BackstagePlugin';
readonly id: string;
@@ -52,7 +52,7 @@ export interface BackstagePlugin<
readonly externalRoutes: TExternalRoutes;
getExtension<TId extends keyof TExtensionMap>(id: TId): TExtensionMap[TId];
withOverrides(options: {
extensions: Array<ExtensionDefinition<any, any>>;
extensions: Array<ExtensionDefinition>;
}): BackstagePlugin<TRoutes, TExternalRoutes, TExtensionMap>;
}
+11 -14
View File
@@ -13,6 +13,7 @@ import { AppNodeInstance } from '@backstage/frontend-plugin-api';
import { ErrorWithContext } from '@backstage/test-utils';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
import { ExtensionDefinitionParameters } from '@backstage/frontend-plugin-api';
import { JsonObject } from '@backstage/types';
import { MockConfigApi } from '@backstage/test-utils';
import { MockErrorApi } from '@backstage/test-utils';
@@ -31,16 +32,12 @@ import { TestApiRegistry } from '@backstage/test-utils';
import { withLogCollector } from '@backstage/test-utils';
// @public (undocumented)
export function createExtensionTester<
TConfig,
TConfigInput,
UOutput extends AnyExtensionDataRef,
>(
subject: ExtensionDefinition<TConfig, TConfigInput, UOutput>,
export function createExtensionTester<T extends ExtensionDefinitionParameters>(
subject: ExtensionDefinition<T>,
options?: {
config?: TConfigInput;
config?: T['configInput'];
},
): ExtensionTester<UOutput>;
): ExtensionTester<NonNullable<T['output']>>;
export { ErrorWithContext };
@@ -64,10 +61,10 @@ export class ExtensionQuery<UOutput extends AnyExtensionDataRef> {
// @public (undocumented)
export class ExtensionTester<UOutput extends AnyExtensionDataRef> {
// (undocumented)
add<TConfig, TConfigInput>(
extension: ExtensionDefinition<TConfig, TConfigInput>,
add<T extends ExtensionDefinitionParameters>(
extension: ExtensionDefinition<T>,
options?: {
config?: TConfigInput;
config?: T['configInput'];
},
): ExtensionTester<UOutput>;
// (undocumented)
@@ -79,9 +76,9 @@ export class ExtensionTester<UOutput extends AnyExtensionDataRef> {
: IData
: never;
// (undocumented)
query<UQueryExtensionOutput extends AnyExtensionDataRef>(
extension: ExtensionDefinition<any, any, UQueryExtensionOutput>,
): ExtensionQuery<UQueryExtensionOutput>;
query<T extends ExtensionDefinitionParameters>(
extension: ExtensionDefinition<T>,
): ExtensionQuery<NonNullable<T['output']>>;
// (undocumented)
reactElement(): JSX.Element;
}
@@ -21,6 +21,7 @@ import {
Extension,
ExtensionDataRef,
ExtensionDefinition,
ExtensionDefinitionParameters,
coreExtensionData,
} from '@backstage/frontend-plugin-api';
import { Config, ConfigReader } from '@backstage/config';
@@ -28,6 +29,8 @@ import { JsonArray, JsonObject, JsonValue } from '@backstage/types';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { toInternalExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/createExtension';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { resolveAppTree } from '../../../frontend-app-api/src/tree/resolveAppTree';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { resolveAppNodeSpecs } from '../../../frontend-app-api/src/tree/resolveAppNodeSpecs';
@@ -75,12 +78,12 @@ export class ExtensionQuery<UOutput extends AnyExtensionDataRef> {
/** @public */
export class ExtensionTester<UOutput extends AnyExtensionDataRef> {
/** @internal */
static forSubject<TConfig, TConfigInput, UOutput extends AnyExtensionDataRef>(
subject: ExtensionDefinition<TConfig, TConfigInput>,
options?: { config?: TConfigInput },
): ExtensionTester<UOutput> {
static forSubject<T extends ExtensionDefinitionParameters>(
subject: ExtensionDefinition<T>,
options?: { config?: T['configInput'] },
): ExtensionTester<NonNullable<T['output']>> {
const tester = new ExtensionTester();
tester.add(subject, options as TConfigInput & {});
tester.add(subject, options as T['configInput'] & {});
return tester;
}
@@ -89,13 +92,13 @@ export class ExtensionTester<UOutput extends AnyExtensionDataRef> {
readonly #extensions = new Array<{
id: string;
extension: Extension<any>;
definition: ExtensionDefinition<any>;
definition: ExtensionDefinition;
config?: JsonValue;
}>();
add<TConfig, TConfigInput>(
extension: ExtensionDefinition<TConfig, TConfigInput>,
options?: { config?: TConfigInput },
add<T extends ExtensionDefinitionParameters>(
extension: ExtensionDefinition<T>,
options?: { config?: T['configInput'] },
): ExtensionTester<UOutput> {
if (this.#tree) {
throw new Error(
@@ -103,7 +106,7 @@ export class ExtensionTester<UOutput extends AnyExtensionDataRef> {
);
}
const { name, namespace } = extension;
const { name, namespace } = toInternalExtensionDefinition(extension);
const definition = {
...extension,
@@ -135,12 +138,18 @@ export class ExtensionTester<UOutput extends AnyExtensionDataRef> {
return new ExtensionQuery(tree.root).get(ref);
}
query<UQueryExtensionOutput extends AnyExtensionDataRef>(
extension: ExtensionDefinition<any, any, UQueryExtensionOutput>,
): ExtensionQuery<UQueryExtensionOutput> {
query<T extends ExtensionDefinitionParameters>(
extension: ExtensionDefinition<T>,
): ExtensionQuery<NonNullable<T['output']>> {
const tree = this.#resolveTree();
const actualId = resolveExtensionDefinition(extension).id;
// Same fallback logic as in .add
const { name, namespace } = toInternalExtensionDefinition(extension);
const definition = {
...extension,
name: !namespace && !name ? 'test' : name,
};
const actualId = resolveExtensionDefinition(definition).id;
const node = tree.nodes.get(actualId);
@@ -232,13 +241,9 @@ export class ExtensionTester<UOutput extends AnyExtensionDataRef> {
}
/** @public */
export function createExtensionTester<
TConfig,
TConfigInput,
UOutput extends AnyExtensionDataRef,
>(
subject: ExtensionDefinition<TConfig, TConfigInput, UOutput>,
options?: { config?: TConfigInput },
): ExtensionTester<UOutput> {
export function createExtensionTester<T extends ExtensionDefinitionParameters>(
subject: ExtensionDefinition<T>,
options?: { config?: T['configInput'] },
): ExtensionTester<NonNullable<T['output']>> {
return ExtensionTester.forSubject(subject, options);
}
@@ -123,7 +123,7 @@ export function renderInTestApp(
element: JSX.Element,
options?: TestAppOptions,
): RenderResult {
const extensions: Array<ExtensionDefinition<any, any>> = [
const extensions: Array<ExtensionDefinition> = [
createExtension({
namespace: 'test',
attachTo: { id: 'app/routes', input: 'routes' },
+314 -315
View File
@@ -25,10 +25,13 @@ const _default: BackstagePlugin<
registerApi: ExternalRouteRef<undefined>;
},
{
'nav-item:api-docs': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<
'nav-item:api-docs': ExtensionDefinition<{
kind: 'nav-item';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
{
title: string;
icon: IconComponent;
@@ -36,50 +39,48 @@ const _default: BackstagePlugin<
},
'core.nav-item.target',
{}
>,
{},
{
kind: 'nav-item';
namespace: undefined;
name: undefined;
}
>;
'api:api-docs/config': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>,
{},
{
kind: 'api';
namespace: undefined;
name: 'config';
}
>;
'page:api-docs': ExtensionDefinition<
{
>;
inputs: {};
}>;
'api:api-docs/config': ExtensionDefinition<{
kind: 'api';
namespace: undefined;
name: 'config';
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
>;
inputs: {};
}>;
'page:api-docs': ExtensionDefinition<{
config: {
initiallySelectedFilter: 'all' | 'owned' | 'starred' | undefined;
} & {
path: string | undefined;
},
{
};
configInput: {
initiallySelectedFilter?: 'all' | 'owned' | 'starred' | undefined;
} & {
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>,
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {
[x: string]: ExtensionInput<
AnyExtensionDataRef,
{
@@ -87,303 +88,301 @@ const _default: BackstagePlugin<
singleton: boolean;
}
>;
},
{
kind: 'page';
namespace: undefined;
name: undefined;
}
>;
'entity-card:api-docs/has-apis': ExtensionDefinition<
{
};
kind: 'page';
namespace: undefined;
name: undefined;
}>;
'entity-card:api-docs/has-apis': ExtensionDefinition<{
kind: 'entity-card';
namespace: undefined;
name: 'has-apis';
config: {
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-card';
namespace: undefined;
name: 'has-apis';
}
>;
'entity-card:api-docs/definition': ExtensionDefinition<
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
'entity-card:api-docs/definition': ExtensionDefinition<{
kind: 'entity-card';
namespace: undefined;
name: 'definition';
config: {
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-card';
namespace: undefined;
name: 'definition';
}
>;
'entity-card:api-docs/consumed-apis': ExtensionDefinition<
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
'entity-card:api-docs/consumed-apis': ExtensionDefinition<{
kind: 'entity-card';
namespace: undefined;
name: 'consumed-apis';
config: {
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-card';
namespace: undefined;
name: 'consumed-apis';
}
>;
'entity-card:api-docs/provided-apis': ExtensionDefinition<
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
'entity-card:api-docs/provided-apis': ExtensionDefinition<{
kind: 'entity-card';
namespace: undefined;
name: 'provided-apis';
config: {
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-card';
namespace: undefined;
name: 'provided-apis';
}
>;
'entity-card:api-docs/consuming-components': ExtensionDefinition<
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
'entity-card:api-docs/consuming-components': ExtensionDefinition<{
kind: 'entity-card';
namespace: undefined;
name: 'consuming-components';
config: {
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-card';
namespace: undefined;
name: 'consuming-components';
}
>;
'entity-card:api-docs/providing-components': ExtensionDefinition<
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
'entity-card:api-docs/providing-components': ExtensionDefinition<{
kind: 'entity-card';
namespace: undefined;
name: 'providing-components';
config: {
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-card';
namespace: undefined;
name: 'providing-components';
}
>;
'entity-content:api-docs/definition': ExtensionDefinition<
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
'entity-content:api-docs/definition': ExtensionDefinition<{
kind: 'entity-content';
namespace: undefined;
name: 'definition';
config: {
path: string | undefined;
title: string | undefined;
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
title?: string | undefined;
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<string, 'catalog.entity-content-title', {}>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-content';
namespace: undefined;
name: 'definition';
}
>;
'entity-content:api-docs/apis': ExtensionDefinition<
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-content-title',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
'entity-content:api-docs/apis': ExtensionDefinition<{
kind: 'entity-content';
namespace: undefined;
name: 'apis';
config: {
path: string | undefined;
title: string | undefined;
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
title?: string | undefined;
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<string, 'catalog.entity-content-title', {}>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-content';
namespace: undefined;
name: 'apis';
}
>;
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-content-title',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
}
>;
export default _default;
+34 -37
View File
@@ -16,37 +16,39 @@ const visualizerPlugin: BackstagePlugin<
{},
{},
{
'page:app-visualizer': ExtensionDefinition<
{
'page:app-visualizer': ExtensionDefinition<{
kind: 'page';
namespace: undefined;
name: undefined;
config: {
path: string | undefined;
},
{
};
configInput: {
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>,
{},
{
kind: 'page';
namespace: undefined;
name: undefined;
}
>;
'nav-item:app-visualizer': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {};
}>;
'nav-item:app-visualizer': ExtensionDefinition<{
kind: 'nav-item';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
{
title: string;
icon: IconComponent;
@@ -54,14 +56,9 @@ const visualizerPlugin: BackstagePlugin<
},
'core.nav-item.target',
{}
>,
{},
{
kind: 'nav-item';
namespace: undefined;
name: undefined;
}
>;
>;
inputs: {};
}>;
}
>;
export default visualizerPlugin;
+56 -58
View File
@@ -28,8 +28,8 @@ const _default: BackstagePlugin<
}>;
},
{
'entity-card:catalog-graph/relations': ExtensionDefinition<
{
'entity-card:catalog-graph/relations': ExtensionDefinition<{
config: {
kinds: string[] | undefined;
relations: string[] | undefined;
maxDepth: number | undefined;
@@ -43,8 +43,8 @@ const _default: BackstagePlugin<
height: number | undefined;
} & {
filter: string | undefined;
},
{
};
configInput: {
height?: number | undefined;
curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined;
direction?: Direction | undefined;
@@ -58,27 +58,28 @@ const _default: BackstagePlugin<
relationPairs?: [string, string][] | undefined;
} & {
filter?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {
[x: string]: ExtensionInput<
AnyExtensionDataRef,
{
@@ -86,15 +87,13 @@ const _default: BackstagePlugin<
singleton: boolean;
}
>;
},
{
kind: 'entity-card';
namespace: undefined;
name: 'relations';
}
>;
'page:catalog-graph': ExtensionDefinition<
{
};
kind: 'entity-card';
namespace: undefined;
name: 'relations';
}>;
'page:catalog-graph': ExtensionDefinition<{
config: {
selectedKinds: string[] | undefined;
selectedRelations: string[] | undefined;
rootEntityRefs: string[] | undefined;
@@ -110,8 +109,8 @@ const _default: BackstagePlugin<
zoom: 'disabled' | 'enabled' | 'enable-on-click' | undefined;
} & {
path: string | undefined;
},
{
};
configInput: {
curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined;
direction?: Direction | undefined;
zoom?: 'disabled' | 'enabled' | 'enable-on-click' | undefined;
@@ -127,21 +126,22 @@ const _default: BackstagePlugin<
showFilters?: boolean | undefined;
} & {
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>,
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {
[x: string]: ExtensionInput<
AnyExtensionDataRef,
{
@@ -149,13 +149,11 @@ const _default: BackstagePlugin<
singleton: boolean;
}
>;
},
{
kind: 'page';
namespace: undefined;
name: undefined;
}
>;
};
kind: 'page';
namespace: undefined;
name: undefined;
}>;
}
>;
export default _default;
+37 -36
View File
@@ -18,44 +18,45 @@ const _default: BackstagePlugin<
},
{},
{
'api:catalog-import': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>,
{},
{
kind: 'api';
namespace: undefined;
name: undefined;
}
>;
'page:catalog-import': ExtensionDefinition<
{
'api:catalog-import': ExtensionDefinition<{
kind: 'api';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
>;
inputs: {};
}>;
'page:catalog-import': ExtensionDefinition<{
kind: 'page';
namespace: undefined;
name: undefined;
config: {
path: string | undefined;
},
{
};
configInput: {
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>,
{},
{
kind: 'page';
namespace: undefined;
name: undefined;
}
>;
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {};
}>;
}
>;
export default _default;
+71 -73
View File
@@ -83,7 +83,7 @@ export function convertLegacyEntityCardExtension(
| typeof EntityCardBlueprint.dataRefs.filterFunction.T
| typeof EntityCardBlueprint.dataRefs.filterExpression.T;
},
): ExtensionDefinition<any>;
): ExtensionDefinition;
// @alpha (undocumented)
export function convertLegacyEntityContentExtension(
@@ -96,42 +96,41 @@ export function convertLegacyEntityContentExtension(
defaultPath?: string;
defaultTitle?: string;
},
): ExtensionDefinition<any>;
): ExtensionDefinition;
// @alpha
export const EntityCardBlueprint: ExtensionBlueprint<
{
kind: 'entity-card';
namespace: undefined;
name: undefined;
},
{
export const EntityCardBlueprint: ExtensionBlueprint<{
kind: 'entity-card';
namespace: undefined;
name: undefined;
params: {
loader: () => Promise<JSX.Element>;
filter?: string | ((entity: Entity) => boolean) | undefined;
},
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
};
output:
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
config: {
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
},
{
};
dataRefs: {
filterFunction: ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
@@ -142,59 +141,58 @@ export const EntityCardBlueprint: ExtensionBlueprint<
'catalog.entity-filter-expression',
{}
>;
}
>;
};
}>;
// @alpha
export const EntityContentBlueprint: ExtensionBlueprint<
{
kind: 'entity-content';
namespace: undefined;
name: undefined;
},
{
export const EntityContentBlueprint: ExtensionBlueprint<{
kind: 'entity-content';
namespace: undefined;
name: undefined;
params: {
loader: () => Promise<JSX.Element>;
defaultPath: string;
defaultTitle: string;
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
filter?: string | ((entity: Entity) => boolean) | undefined;
},
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<string, 'catalog.entity-content-title', {}>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
};
output:
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<string, 'catalog.entity-content-title', {}>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
config: {
path: string | undefined;
title: string | undefined;
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
title?: string | undefined;
path?: string | undefined;
},
{
};
dataRefs: {
title: ConfigurableExtensionDataRef<
string,
'catalog.entity-content-title',
@@ -210,8 +208,8 @@ export const EntityContentBlueprint: ExtensionBlueprint<
'catalog.entity-filter-expression',
{}
>;
}
>;
};
}>;
// @alpha
export function isOwnerOf(owner: Entity, entity: Entity): boolean;
@@ -40,6 +40,7 @@ describe('EntityCardBlueprint', () => {
expect(extension).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "entity-content:catalog/overview",
"input": "cards",
@@ -42,6 +42,7 @@ describe('EntityContentBlueprint', () => {
expect(extension).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "page:catalog/entity",
"input": "contents",
@@ -48,12 +48,11 @@ describe('convertLegacyEntityCardExtension', () => {
);
const converted = convertLegacyEntityCardExtension(LegacyExtension);
expect(converted.kind).toBe('entity-card');
expect(converted.namespace).toBe(undefined);
expect(converted.name).toBe('example');
const tester = createExtensionTester(converted);
expect(tester.query(converted).node.spec.id).toBe('entity-card:example');
await renderInTestApp(tester.reactElement(), {
mountedRoutes: {
'/': convertLegacyRouteRef(routeRef),
@@ -83,12 +82,11 @@ describe('convertLegacyEntityCardExtension', () => {
name: 'other',
filter: 'my-filter',
});
expect(converted.kind).toBe('entity-card');
expect(converted.namespace).toBe(undefined);
expect(converted.name).toBe('other');
const tester = createExtensionTester(converted);
expect(tester.query(converted).node.spec.id).toBe('entity-card:other');
await renderInTestApp(tester.reactElement(), {
mountedRoutes: {
'/': convertLegacyRouteRef(routeRef),
@@ -106,7 +104,7 @@ describe('convertLegacyEntityCardExtension', () => {
});
it('should support various naming patterns for entity card extensions', async () => {
const withName = (name: string) => {
const getDiscoveredId = (name: string) => {
const converted = convertLegacyEntityCardExtension(
legacyPlugin.provide(
createRoutableExtension({
@@ -116,14 +114,14 @@ describe('convertLegacyEntityCardExtension', () => {
}),
),
);
return converted.name;
return createExtensionTester(converted).query(converted).node.spec.id;
};
expect(withName('EntityTestCard')).toBe(undefined);
expect(withName('EntityTestTrimCard')).toBe('trim');
expect(withName('EntityTeStTrimCard')).toBe('trim');
expect(withName('EntityExampleCard')).toBe('example');
expect(withName('EntityExAmpleCard')).toBe('ex-ample');
expect(withName('ExampleCard')).toBe('example-card');
expect(getDiscoveredId('EntityTestCard')).toBe('entity-card:test'); // falls back to test namespace
expect(getDiscoveredId('EntityTestTrimCard')).toBe('entity-card:trim');
expect(getDiscoveredId('EntityTeStTrimCard')).toBe('entity-card:trim');
expect(getDiscoveredId('EntityExampleCard')).toBe('entity-card:example');
expect(getDiscoveredId('EntityExAmpleCard')).toBe('entity-card:ex-ample');
expect(getDiscoveredId('ExampleCard')).toBe('entity-card:example-card');
});
});
@@ -30,7 +30,7 @@ export function convertLegacyEntityCardExtension(
| typeof EntityCardBlueprint.dataRefs.filterFunction.T
| typeof EntityCardBlueprint.dataRefs.filterExpression.T;
},
): ExtensionDefinition<any> {
): ExtensionDefinition {
const element = <LegacyExtension />;
const extName = getComponentData<string>(element, 'core.extensionName');
@@ -49,12 +49,11 @@ describe('convertLegacyEntityContentExtension', () => {
);
const converted = convertLegacyEntityContentExtension(LegacyExtension);
expect(converted.kind).toBe('entity-content');
expect(converted.namespace).toBe(undefined);
expect(converted.name).toBe('example');
const tester = createExtensionTester(converted);
expect(tester.query(converted).node.spec.id).toBe('entity-content:example');
await renderInTestApp(tester.reactElement(), {
mountedRoutes: {
'/': convertLegacyRouteRef(routeRef),
@@ -88,12 +87,11 @@ describe('convertLegacyEntityContentExtension', () => {
defaultTitle: 'Other',
filter: 'my-filter',
});
expect(converted.kind).toBe('entity-content');
expect(converted.namespace).toBe(undefined);
expect(converted.name).toBe('other');
const tester = createExtensionTester(converted);
expect(tester.query(converted).node.spec.id).toBe('entity-content:other');
await renderInTestApp(tester.reactElement(), {
mountedRoutes: {
'/': convertLegacyRouteRef(routeRef),
@@ -123,14 +121,14 @@ describe('convertLegacyEntityContentExtension', () => {
}),
),
);
return converted.name;
return createExtensionTester(converted).query(converted).node.spec.id;
};
expect(withName('EntityTestContent')).toBe(undefined);
expect(withName('EntityTestTrimContent')).toBe('trim');
expect(withName('EntityTeStTrimContent')).toBe('trim');
expect(withName('EntityExampleContent')).toBe('example');
expect(withName('EntityExAmpleContent')).toBe('ex-ample');
expect(withName('ExampleContent')).toBe('example-content');
expect(withName('EntityTestContent')).toBe('entity-content:test'); // falls back to test namespace
expect(withName('EntityTestTrimContent')).toBe('entity-content:trim');
expect(withName('EntityTeStTrimContent')).toBe('entity-content:trim');
expect(withName('EntityExampleContent')).toBe('entity-content:example');
expect(withName('EntityExAmpleContent')).toBe('entity-content:ex-ample');
expect(withName('ExampleContent')).toBe('entity-content:example-content');
});
});
@@ -40,7 +40,7 @@ export function convertLegacyEntityContentExtension(
defaultPath?: string;
defaultTitle?: string;
},
): ExtensionDefinition<any> {
): ExtensionDefinition {
const element = <LegacyExtension />;
const extName = getComponentData<string>(element, 'core.extensionName');
File diff suppressed because it is too large Load Diff
@@ -36,6 +36,7 @@ describe('CatalogFilterBlueprint', () => {
expect(extension).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "page:catalog",
"input": "filters",
+47 -48
View File
@@ -19,48 +19,52 @@ const _default: BackstagePlugin<
},
{},
{
'api:devtools': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>,
{},
{
kind: 'api';
namespace: undefined;
name: undefined;
}
>;
'page:devtools': ExtensionDefinition<
{
'api:devtools': ExtensionDefinition<{
kind: 'api';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
>;
inputs: {};
}>;
'page:devtools': ExtensionDefinition<{
kind: 'page';
namespace: undefined;
name: undefined;
config: {
path: string | undefined;
},
{
};
configInput: {
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>,
{},
{
kind: 'page';
namespace: undefined;
name: undefined;
}
>;
'nav-item:devtools': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {};
}>;
'nav-item:devtools': ExtensionDefinition<{
kind: 'nav-item';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
{
title: string;
icon: IconComponent;
@@ -68,14 +72,9 @@ const _default: BackstagePlugin<
},
'core.nav-item.target',
{}
>,
{},
{
kind: 'nav-item';
namespace: undefined;
name: undefined;
}
>;
>;
inputs: {};
}>;
}
>;
export default _default;
+25 -30
View File
@@ -16,31 +16,28 @@ const _default: BackstagePlugin<
{},
{},
{
'page:home': ExtensionDefinition<
{
[x: string]: any;
} & {
'page:home': ExtensionDefinition<{
config: {
path: string | undefined;
},
{
[x: string]: any;
} & {
};
configInput: {
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>,
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {
props: ExtensionInput<
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
@@ -61,13 +58,11 @@ const _default: BackstagePlugin<
optional: true;
}
>;
},
{
kind: 'page';
namespace: undefined;
name: undefined;
}
>;
};
kind: 'page';
namespace: undefined;
name: undefined;
}>;
}
>;
export default _default;
+111 -90
View File
@@ -21,104 +21,125 @@ const _default: BackstagePlugin<
},
{},
{
'page:kubernetes': ExtensionDefinition<
{
'api:kubernetes': ExtensionDefinition<{
kind: 'api';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
>;
inputs: {};
}>;
'page:kubernetes': ExtensionDefinition<{
kind: 'page';
namespace: undefined;
name: undefined;
config: {
path: string | undefined;
},
{
};
configInput: {
path?: string | undefined;
},
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>,
{},
{
kind: 'page';
namespace: undefined;
name: undefined;
}
>;
'entity-content:kubernetes/kubernetes': ExtensionDefinition<
{
};
output:
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {};
}>;
'entity-content:kubernetes/kubernetes': ExtensionDefinition<{
kind: 'entity-content';
namespace: undefined;
name: 'kubernetes';
config: {
path: string | undefined;
title: string | undefined;
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
title?: string | undefined;
path?: string | undefined;
},
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<string, 'catalog.entity-content-title', {}>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-content';
namespace: undefined;
name: 'kubernetes';
}
>;
'api:kubernetes/proxy': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>,
{},
{
kind: 'api';
namespace: undefined;
name: 'proxy';
}
>;
'api:kubernetes/auth-providers': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>,
{},
{
kind: 'api';
namespace: undefined;
name: 'auth-providers';
}
>;
'api:kubernetes/cluster-link-formatter': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>,
{},
{
kind: 'api';
namespace: undefined;
name: 'cluster-link-formatter';
}
>;
};
output:
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-content-title',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
'api:kubernetes/proxy': ExtensionDefinition<{
kind: 'api';
namespace: undefined;
name: 'proxy';
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
>;
inputs: {};
}>;
'api:kubernetes/auth-providers': ExtensionDefinition<{
kind: 'api';
namespace: undefined;
name: 'auth-providers';
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
>;
inputs: {};
}>;
'api:kubernetes/cluster-link-formatter': ExtensionDefinition<{
kind: 'api';
namespace: undefined;
name: 'cluster-link-formatter';
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
>;
inputs: {};
}>;
}
>;
export default _default;
+120 -124
View File
@@ -17,138 +17,134 @@ const _default: BackstagePlugin<
catalogIndex: ExternalRouteRef<undefined>;
},
{
'entity-card:org/group-profile': ExtensionDefinition<
{
'entity-card:org/group-profile': ExtensionDefinition<{
kind: 'entity-card';
namespace: undefined;
name: 'group-profile';
config: {
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-card';
namespace: undefined;
name: 'group-profile';
}
>;
'entity-card:org/members-list': ExtensionDefinition<
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
'entity-card:org/members-list': ExtensionDefinition<{
kind: 'entity-card';
namespace: undefined;
name: 'members-list';
config: {
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-card';
namespace: undefined;
name: 'members-list';
}
>;
'entity-card:org/ownership': ExtensionDefinition<
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
'entity-card:org/ownership': ExtensionDefinition<{
kind: 'entity-card';
namespace: undefined;
name: 'ownership';
config: {
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-card';
namespace: undefined;
name: 'ownership';
}
>;
'entity-card:org/user-profile': ExtensionDefinition<
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
'entity-card:org/user-profile': ExtensionDefinition<{
kind: 'entity-card';
namespace: undefined;
name: 'user-profile';
config: {
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-card';
namespace: undefined;
name: 'user-profile';
}
>;
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
}
>;
export default _default;
+47 -48
View File
@@ -44,48 +44,52 @@ const _default: BackstagePlugin<
}>;
},
{
'api:scaffolder': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>,
{},
{
kind: 'api';
namespace: undefined;
name: undefined;
}
>;
'page:scaffolder': ExtensionDefinition<
{
'api:scaffolder': ExtensionDefinition<{
kind: 'api';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
>;
inputs: {};
}>;
'page:scaffolder': ExtensionDefinition<{
kind: 'page';
namespace: undefined;
name: undefined;
config: {
path: string | undefined;
},
{
};
configInput: {
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>,
{},
{
kind: 'page';
namespace: undefined;
name: undefined;
}
>;
'nav-item:scaffolder': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {};
}>;
'nav-item:scaffolder': ExtensionDefinition<{
kind: 'nav-item';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
{
title: string;
icon: IconComponent;
@@ -93,14 +97,9 @@ const _default: BackstagePlugin<
},
'core.nav-item.target',
{}
>,
{},
{
kind: 'nav-item';
namespace: undefined;
name: undefined;
}
>;
>;
inputs: {};
}>;
}
>;
export default _default;
+15 -17
View File
@@ -30,29 +30,27 @@ export type SearchResultItemExtensionPredicate = (
) => boolean;
// @alpha
export const SearchResultListItemBlueprint: ExtensionBlueprint<
{
kind: 'search-result-list-item';
namespace: undefined;
name: undefined;
},
SearchResultListItemBlueprintParams,
ConfigurableExtensionDataRef<
export const SearchResultListItemBlueprint: ExtensionBlueprint<{
kind: 'search-result-list-item';
namespace: undefined;
name: undefined;
params: SearchResultListItemBlueprintParams;
output: ConfigurableExtensionDataRef<
{
predicate?: SearchResultItemExtensionPredicate | undefined;
component: SearchResultItemExtensionComponent;
},
'search.search-result-list-item.item',
{}
>,
{},
{
>;
inputs: {};
config: {
noTrack: boolean;
},
{
};
configInput: {
noTrack?: boolean | undefined;
},
{
};
dataRefs: {
item: ConfigurableExtensionDataRef<
{
predicate?: SearchResultItemExtensionPredicate | undefined;
@@ -61,8 +59,8 @@ export const SearchResultListItemBlueprint: ExtensionBlueprint<
'search.search-result-list-item.item',
{}
>;
}
>;
};
}>;
// @alpha (undocumented)
export interface SearchResultListItemBlueprintParams {
@@ -40,6 +40,7 @@ describe('SearchResultListItemBlueprint', () => {
expect(extension).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "page:search",
"input": "items",
+88 -94
View File
@@ -22,21 +22,26 @@ const _default: BackstagePlugin<
},
{},
{
'api:search': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>,
{},
{
kind: 'api';
namespace: undefined;
name: undefined;
}
>;
'nav-item:search': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<
'api:search': ExtensionDefinition<{
kind: 'api';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
>;
inputs: {};
}>;
'nav-item:search': ExtensionDefinition<{
kind: 'nav-item';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
{
title: string;
icon: IconComponent;
@@ -44,39 +49,35 @@ const _default: BackstagePlugin<
},
'core.nav-item.target',
{}
>,
{},
{
kind: 'nav-item';
namespace: undefined;
name: undefined;
}
>;
'page:search': ExtensionDefinition<
{
>;
inputs: {};
}>;
'page:search': ExtensionDefinition<{
config: {
noTrack: boolean;
} & {
path: string | undefined;
},
{
};
configInput: {
noTrack?: boolean | undefined;
} & {
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>,
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {
items: ExtensionInput<
ConfigurableExtensionDataRef<
{
@@ -91,35 +92,34 @@ const _default: BackstagePlugin<
optional: false;
}
>;
},
{
kind: 'page';
namespace: undefined;
name: undefined;
}
>;
};
kind: 'page';
namespace: undefined;
name: undefined;
}>;
}
>;
export default _default;
// @alpha (undocumented)
export const searchApi: ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>,
{},
{
kind: 'api';
namespace: undefined;
name: undefined;
}
>;
export const searchApi: ExtensionDefinition<{
kind: 'api';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
inputs: {};
}>;
// @alpha (undocumented)
export const searchNavItem: ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<
export const searchNavItem: ExtensionDefinition<{
kind: 'nav-item';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
{
title: string;
icon: IconComponent;
@@ -127,37 +127,33 @@ export const searchNavItem: ExtensionDefinition<
},
'core.nav-item.target',
{}
>,
{},
{
kind: 'nav-item';
namespace: undefined;
name: undefined;
}
>;
>;
inputs: {};
}>;
// @alpha (undocumented)
export const searchPage: ExtensionDefinition<
{
export const searchPage: ExtensionDefinition<{
config: {
noTrack: boolean;
} & {
path: string | undefined;
},
{
};
configInput: {
noTrack?: boolean | undefined;
} & {
path?: string | undefined;
},
| ConfigurableExtensionDataRef<React_2.JSX.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>,
{
};
output:
| ConfigurableExtensionDataRef<React_2.JSX.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {
items: ExtensionInput<
ConfigurableExtensionDataRef<
{
@@ -172,13 +168,11 @@ export const searchPage: ExtensionDefinition<
optional: false;
}
>;
},
{
kind: 'page';
namespace: undefined;
name: undefined;
}
>;
};
kind: 'page';
namespace: undefined;
name: undefined;
}>;
// (No @packageDocumentation comment for this package)
```
+155 -118
View File
@@ -30,10 +30,52 @@ const _default: BackstagePlugin<
},
{},
{
'nav-item:techdocs': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<
'api:techdocs': ExtensionDefinition<{
kind: 'api';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
>;
inputs: {};
}>;
'page:techdocs': ExtensionDefinition<{
kind: 'page';
namespace: undefined;
name: undefined;
config: {
path: string | undefined;
};
configInput: {
path?: string | undefined;
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {};
}>;
'nav-item:techdocs': ExtensionDefinition<{
kind: 'nav-item';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
{
title: string;
icon: IconComponent;
@@ -41,51 +83,48 @@ const _default: BackstagePlugin<
},
'core.nav-item.target',
{}
>,
{},
{
kind: 'nav-item';
namespace: undefined;
name: undefined;
}
>;
'api:techdocs/storage': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>,
{},
{
kind: 'api';
namespace: undefined;
name: 'storage';
}
>;
'search-result-list-item:techdocs': ExtensionDefinition<
{
>;
inputs: {};
}>;
'api:techdocs/storage': ExtensionDefinition<{
kind: 'api';
namespace: undefined;
name: 'storage';
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
>;
inputs: {};
}>;
'search-result-list-item:techdocs': ExtensionDefinition<{
config: {
title: string | undefined;
lineClamp: number;
asLink: boolean;
asListItem: boolean;
} & {
noTrack: boolean;
},
{
};
configInput: {
title?: string | undefined;
lineClamp?: number | undefined;
asListItem?: boolean | undefined;
asLink?: boolean | undefined;
} & {
noTrack?: boolean | undefined;
},
ConfigurableExtensionDataRef<
};
output: ConfigurableExtensionDataRef<
{
predicate?: SearchResultItemExtensionPredicate | undefined;
component: SearchResultItemExtensionComponent;
},
'search.search-result-list-item.item',
{}
>,
{
>;
inputs: {
[x: string]: ExtensionInput<
AnyExtensionDataRef,
{
@@ -93,117 +132,117 @@ const _default: BackstagePlugin<
singleton: boolean;
}
>;
},
{
kind: 'search-result-list-item';
namespace: undefined;
name: undefined;
}
>;
'page:techdocs/reader': ExtensionDefinition<
{
};
kind: 'search-result-list-item';
namespace: undefined;
name: undefined;
}>;
'page:techdocs/reader': ExtensionDefinition<{
kind: 'page';
namespace: undefined;
name: 'reader';
config: {
path: string | undefined;
},
{
};
configInput: {
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>,
{},
{
kind: 'page';
namespace: undefined;
name: 'reader';
}
>;
'entity-content:techdocs': ExtensionDefinition<
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {};
}>;
'entity-content:techdocs': ExtensionDefinition<{
kind: 'entity-content';
namespace: undefined;
name: undefined;
config: {
path: string | undefined;
title: string | undefined;
filter: string | undefined;
},
{
};
configInput: {
filter?: string | undefined;
title?: string | undefined;
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<string, 'catalog.entity-content-title', {}>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>,
{},
{
kind: 'entity-content';
namespace: undefined;
name: undefined;
}
>;
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-content-title',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>;
inputs: {};
}>;
}
>;
export default _default;
// @alpha (undocumented)
export const techDocsSearchResultListItemExtension: ExtensionDefinition<
{
export const techDocsSearchResultListItemExtension: ExtensionDefinition<{
config: {
title: string | undefined;
lineClamp: number;
asLink: boolean;
asListItem: boolean;
} & {
noTrack: boolean;
},
{
};
configInput: {
title?: string | undefined;
lineClamp?: number | undefined;
asListItem?: boolean | undefined;
asLink?: boolean | undefined;
} & {
noTrack?: boolean | undefined;
},
ConfigurableExtensionDataRef<
};
output: ConfigurableExtensionDataRef<
{
predicate?: SearchResultItemExtensionPredicate | undefined;
component: SearchResultItemExtensionComponent;
},
'search.search-result-list-item.item',
{}
>,
{
>;
inputs: {
[x: string]: ExtensionInput<
AnyExtensionDataRef,
{
@@ -211,13 +250,11 @@ export const techDocsSearchResultListItemExtension: ExtensionDefinition<
singleton: boolean;
}
>;
},
{
kind: 'search-result-list-item';
namespace: undefined;
name: undefined;
}
>;
};
kind: 'search-result-list-item';
namespace: undefined;
name: undefined;
}>;
// (No @packageDocumentation comment for this package)
```
+45 -54
View File
@@ -20,10 +20,13 @@ const _default: BackstagePlugin<
},
{},
{
'nav-item:user-settings': ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<
'nav-item:user-settings': ExtensionDefinition<{
kind: 'nav-item';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
{
title: string;
icon: IconComponent;
@@ -31,39 +34,31 @@ const _default: BackstagePlugin<
},
'core.nav-item.target',
{}
>,
{},
{
kind: 'nav-item';
namespace: undefined;
name: undefined;
}
>;
'page:user-settings': ExtensionDefinition<
{
[x: string]: any;
} & {
>;
inputs: {};
}>;
'page:user-settings': ExtensionDefinition<{
config: {
path: string | undefined;
},
{
[x: string]: any;
} & {
};
configInput: {
path?: string | undefined;
},
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>,
{
};
output:
| ConfigurableExtensionDataRef<
React_2.JSX.Element,
'core.reactElement',
{}
>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>;
inputs: {
providerSettings: ExtensionInput<
ConfigurableExtensionDataRef<
React_2.JSX.Element,
@@ -75,22 +70,23 @@ const _default: BackstagePlugin<
optional: true;
}
>;
},
{
kind: 'page';
namespace: undefined;
name: undefined;
}
>;
};
kind: 'page';
namespace: undefined;
name: undefined;
}>;
}
>;
export default _default;
// @alpha (undocumented)
export const settingsNavItem: ExtensionDefinition<
{},
{},
ConfigurableExtensionDataRef<
export const settingsNavItem: ExtensionDefinition<{
kind: 'nav-item';
namespace: undefined;
name: undefined;
config: {};
configInput: {};
output: ConfigurableExtensionDataRef<
{
title: string;
icon: IconComponent;
@@ -98,14 +94,9 @@ export const settingsNavItem: ExtensionDefinition<
},
'core.nav-item.target',
{}
>,
{},
{
kind: 'nav-item';
namespace: undefined;
name: undefined;
}
>;
>;
inputs: {};
}>;
// @alpha (undocumented)
export const userSettingsTranslationRef: TranslationRef<