From af5796f5c38a6b4de7a4bffaf5ecc97f8d9a4513 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:48:25 +0200 Subject: [PATCH 01/16] frontend-plugin-api: remove support for v1 extensions Signed-off-by: Patrik Oldsberg --- .../src/wiring/createExtension.test.ts | 219 +++++++---------- .../src/wiring/createExtension.ts | 225 +++++------------- .../src/wiring/createExtensionInput.test.ts | 42 +--- .../src/wiring/createExtensionInput.ts | 78 ++---- .../wiring/createExtensionOverrides.test.ts | 16 +- .../src/wiring/createFrontendPlugin.test.ts | 77 +++--- .../frontend-plugin-api/src/wiring/index.ts | 5 - .../src/wiring/resolveExtensionDefinition.ts | 27 ++- 8 files changed, 236 insertions(+), 453 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 5902c5c3e7..5853f28004 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -21,6 +21,9 @@ import { createExtensionInput } from './createExtensionInput'; const stringDataRef = createExtensionDataRef().with({ id: 'string' }); const numberDataRef = createExtensionDataRef().with({ id: 'number' }); +const booleanDataRef = createExtensionDataRef().with({ + id: 'boolean', +}); function unused(..._any: any[]) {} @@ -29,42 +32,35 @@ describe('createExtension', () => { const baseConfig = { namespace: 'test', attachTo: { id: 'root', input: 'default' }, - output: { - foo: stringDataRef, - }, + output: [stringDataRef], }; const extension = createExtension({ ...baseConfig, factory() { - return { - foo: 'bar', - }; + return [stringDataRef('bar')]; }, }); - expect(extension).toMatchObject({ version: 'v1', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2', namespace: 'test' }); - // When declared as an error function without a block the TypeScript errors - // are a more specific and will often point at the property that is problematic. + // Member arrow function declaration + createExtension({ + ...baseConfig, + factory: () => [ + stringDataRef( + // @ts-expect-error + 3, + ), + ], + }); // @ts-expect-error createExtension({ ...baseConfig, - factory: () => ({ - foo: 3, - }), + factory: () => [numberDataRef(3)], }); + // @ts-expect-error createExtension({ ...baseConfig, - factory: () => - // @ts-expect-error - ({ - bar: 'bar', - }), - }); - createExtension({ - ...baseConfig, - factory: () => - // @ts-expect-error - ({}), + factory: () => [], }); createExtension({ ...baseConfig, @@ -79,39 +75,37 @@ describe('createExtension', () => { 'bar', }); - // When declared as a function with a block the TypeScript error will instead - // be tied to the factory function declaration itself, but the error messages - // is still helpful and points to part of the return type that is problematic. + // Method declaration createExtension({ ...baseConfig, - // @ts-expect-error factory() { - return { - foo: 3, - }; + return [ + stringDataRef( + // @ts-expect-error + 3, + ), + ]; + }, + }); + // @ts-expect-error + createExtension({ + ...baseConfig, + factory() { + return [numberDataRef(3)]; + }, + }); + // @ts-expect-error + createExtension({ + ...baseConfig, + factory() { + return []; }, }); createExtension({ ...baseConfig, // @ts-expect-error factory() { - return { - bar: 'bar', - }; - }, - }); - createExtension({ - ...baseConfig, - // @ts-expect-error - factory() { - return {}; - }, - }); - createExtension({ - ...baseConfig, - // @ts-expect-error - factory() { - return {}; + return; }, }); createExtension({ @@ -122,36 +116,37 @@ describe('createExtension', () => { }, }); + // Member function declaration createExtension({ ...baseConfig, - // @ts-expect-error factory: () => { - return { - foo: 3, - }; + return [ + stringDataRef( + // @ts-expect-error + 3, + ), + ]; + }, + }); + // @ts-expect-error + createExtension({ + ...baseConfig, + factory: () => { + return [numberDataRef(3)]; + }, + }); + // @ts-expect-error + createExtension({ + ...baseConfig, + factory: () => { + return []; }, }); createExtension({ ...baseConfig, // @ts-expect-error factory: () => { - return { - bar: 'bar', - }; - }, - }); - createExtension({ - ...baseConfig, - // @ts-expect-error - factory: () => { - return {}; - }, - }); - createExtension({ - ...baseConfig, - // @ts-expect-error - factory: () => { - return {}; + return; }, }); createExtension({ @@ -167,52 +162,27 @@ describe('createExtension', () => { const baseConfig = { namespace: 'test', attachTo: { id: 'root', input: 'default' }, - output: { - foo: stringDataRef, - bar: stringDataRef.optional(), - }, + output: [stringDataRef, numberDataRef.optional()], }; const extension = createExtension({ ...baseConfig, - factory: () => ({ - foo: 'bar', - }), + factory: () => [stringDataRef('bar')], }); - expect(extension).toMatchObject({ version: 'v1', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2', namespace: 'test' }); createExtension({ ...baseConfig, - factory: () => ({ - foo: 'bar', - bar: 'baz', - }), + factory: () => [stringDataRef('bar'), numberDataRef(3)], }); // @ts-expect-error createExtension({ ...baseConfig, - factory: () => ({ - foo: 3, - }), + factory: () => [numberDataRef(3)], }); // @ts-expect-error createExtension({ ...baseConfig, - factory: () => ({ - foo: 'bar', - bar: 3, - }), - }); - createExtension({ - ...baseConfig, - factory: () => - // @ts-expect-error - ({ bar: 'bar' }), - }); - createExtension({ - ...baseConfig, - factory: () => - // @ts-expect-error - ({}), + factory: () => [], }); createExtension({ ...baseConfig, @@ -238,57 +208,48 @@ describe('createExtension', () => { namespace: 'test', attachTo: { id: 'root', input: 'default' }, inputs: { - mixed: createExtensionInput({ - required: stringDataRef, - optional: stringDataRef.optional(), - }), - onlyRequired: createExtensionInput({ - required: stringDataRef, - }), - onlyOptional: createExtensionInput({ - optional: stringDataRef.optional(), - }), - }, - output: { - foo: stringDataRef, + mixed: createExtensionInput([stringDataRef, numberDataRef.optional()]), + onlyRequired: createExtensionInput([stringDataRef]), + onlyOptional: createExtensionInput([stringDataRef.optional()]), }, + output: [stringDataRef], factory({ inputs }) { - const a1: string = inputs.mixed?.[0].output.required; + const a1: string = inputs.mixed?.[0].get(stringDataRef); // @ts-expect-error - const a2: number = inputs.mixed?.[0].output.required; + const a2: number = inputs.mixed?.[0].get(stringDataRef); // @ts-expect-error - const a3: any = inputs.mixed?.[0].output.nonExistent; + const a3: any = inputs.mixed?.[0].get(booleanDataRef); unused(a1, a2, a3); - const b1: string | undefined = inputs.mixed?.[0].output.optional; + const b1: number | undefined = inputs.mixed?.[0].get(numberDataRef); // @ts-expect-error - const b2: string = inputs.mixed?.[0].output.optional; + const b2: string = inputs.mixed?.[0].get(numberDataRef); // @ts-expect-error - const b3: number = inputs.mixed?.[0].output.optional; + const b3: number = inputs.mixed?.[0].get(numberDataRef); // @ts-expect-error - const b4: number | undefined = inputs.mixed?.[0].output.optional; + const b4: string | undefined = inputs.mixed?.[0].get(numberDataRef); unused(b1, b2, b3, b4); - const c1: string = inputs.onlyRequired?.[0].output.required; + const c1: string = inputs.onlyRequired?.[0].get(stringDataRef); // @ts-expect-error - const c2: number = inputs.onlyRequired?.[0].output.required; + const c2: number = inputs.onlyRequired?.[0].get(stringDataRef); unused(c1, c2); - const d1: string | undefined = inputs.onlyOptional?.[0].output.optional; + const d1: string | undefined = + inputs.onlyOptional?.[0].get(stringDataRef); // @ts-expect-error - const d2: string = inputs.onlyOptional?.[0].output.optional; + const d2: string = inputs.onlyOptional?.[0].get(stringDataRef); // @ts-expect-error - const d3: number = inputs.onlyOptional?.[0].output.optional; + const d3: number = inputs.onlyOptional?.[0].get(stringDataRef); // @ts-expect-error - const d4: number | undefined = inputs.onlyOptional?.[0].output.optional; + const d4: number | undefined = + inputs.onlyOptional?.[0].get(stringDataRef); unused(d1, d2, d3, d4); - return { - foo: 'bar', - }; + return [stringDataRef('bar')]; }, }); - expect(extension).toMatchObject({ version: 'v1', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2', namespace: 'test' }); expect(String(extension)).toBe( 'ExtensionDefinition{namespace=test,attachTo=root@default}', ); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 66b908f236..0c89ff1567 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -15,7 +15,7 @@ */ import { AppNode } from '../apis'; -import { PortableSchema, createSchemaFromZod } from '../schema'; +import { PortableSchema } from '../schema'; import { Expand } from '../types'; import { ResolveInputValueOverrides, @@ -29,46 +29,9 @@ import { AnyExtensionDataRef, ExtensionDataValue, } from './createExtensionDataRef'; -import { ExtensionInput, LegacyExtensionInput } from './createExtensionInput'; +import { ExtensionInput } from './createExtensionInput'; import { z } from 'zod'; - -/** - * @public - * @deprecated Extension data maps will be removed. - */ -export type AnyExtensionDataMap = { - [name in string]: AnyExtensionDataRef; -}; - -/** - * @public - * @deprecated This type will be removed. - */ -export type AnyExtensionInputMap = { - [inputName in string]: LegacyExtensionInput< - AnyExtensionDataMap, - { optional: boolean; singleton: boolean } - >; -}; - -/** - * Converts an extension data map into the matching concrete data values type. - * @public - * @deprecated Extension data maps will be removed. - */ -export type ExtensionDataValues = { - [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends { - optional: true; - } - ? never - : DataName]: TExtensionData[DataName]['T']; -} & { - [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends { - optional: true; - } - ? DataName - : never]?: TExtensionData[DataName]['T']; -}; +import { createSchemaFromZod } from '../schema/createSchemaFromZod'; /** * Convert a single extension input into a matching resolved input. @@ -80,11 +43,6 @@ export type ResolvedExtensionInput< ? { node: AppNode; } & ExtensionDataContainer - : TExtensionInput['extensionData'] extends AnyExtensionDataMap - ? { - node: AppNode; - output: ExtensionDataValues; - } : never; /** @@ -93,7 +51,7 @@ export type ResolvedExtensionInput< */ export type ResolvedExtensionInputs< TInputs extends { - [name in string]: ExtensionInput | LegacyExtensionInput; + [name in string]: ExtensionInput; }, > = { [InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton'] @@ -103,31 +61,6 @@ export type ResolvedExtensionInputs< : Expand | undefined>; }; -/** - * @public - * @deprecated This way of structuring the options is deprecated, this type will be removed in the future - */ -export interface LegacyCreateExtensionOptions< - TOutput extends AnyExtensionDataMap, - TInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, -> { - kind?: string; - namespace?: string; - name?: string; - attachTo: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - output: TOutput; - configSchema?: PortableSchema; - factory(context: { - node: AppNode; - config: TConfig; - inputs: Expand>; - }): Expand>; -} - type ToIntersection = (U extends any ? (k: U) => void : never) extends ( k: infer I, ) => void @@ -326,13 +259,27 @@ export type InternalExtensionDefinition< ( | { readonly version: 'v1'; - readonly inputs: AnyExtensionInputMap; - readonly output: AnyExtensionDataMap; + readonly inputs: { + [inputName in string]: { + $$type: '@backstage/ExtensionInput'; + extensionData: { + [name in string]: AnyExtensionDataRef; + }; + config: { optional: boolean; singleton: boolean }; + }; + }; + readonly output: { + [name in string]: AnyExtensionDataRef; + }; factory(context: { node: AppNode; config: TConfig; - inputs: ResolvedExtensionInputs; - }): ExtensionDataValues; + inputs: { + [inputName in string]: unknown; + }; + }): { + [inputName in string]: unknown; + }; } | { readonly version: 'v2'; @@ -419,23 +366,6 @@ export function createExtension< name: string | undefined extends TName ? undefined : TName; } >; -/** - * @public - * @deprecated - use the array format of `output` instead, see TODO-doc-link - */ -export function createExtension< - TOutput extends AnyExtensionDataMap, - TInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, ->( - options: LegacyCreateExtensionOptions< - TOutput, - TInputs, - TConfig, - TConfigInput - >, -): ExtensionDefinition; export function createExtension< const TKind extends string | undefined, const TNamespace extends string | undefined, @@ -447,43 +377,31 @@ export function createExtension< { optional: boolean; singleton: boolean } >; }, - TLegacyInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, >( - options: - | CreateExtensionOptions< - TKind, - TNamespace, - TName, - UOutput, - TInputs, - TConfigSchema, - UFactoryOutput - > - | LegacyCreateExtensionOptions< - AnyExtensionDataMap, - TLegacyInputs, - TConfig, - TConfigInput - >, + options: CreateExtensionOptions< + TKind, + TNamespace, + TName, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput + >, ): ExtensionDefinition< - TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer>; - }), - TConfigInput & - (string extends keyof TConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - >), + string extends keyof TConfigSchema + ? {} + : { + [key in keyof TConfigSchema]: z.infer>; + }, + string extends keyof TConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >, UOutput, TInputs, { @@ -492,29 +410,20 @@ export function createExtension< name: TName; } > { - if ('configSchema' in options && 'config' in options) { - throw new Error(`Cannot provide both configSchema and config.schema`); - } - let configSchema: PortableSchema | undefined; - if ('configSchema' in options) { - configSchema = options.configSchema; - } - if ('config' in options) { - const newConfigSchema = options.config?.schema; - configSchema = - newConfigSchema && - createSchemaFromZod(innerZ => - innerZ.object( - Object.fromEntries( - Object.entries(newConfigSchema).map(([k, v]) => [k, v(innerZ)]), - ), + const schemaDeclaration = options.config?.schema; + const configSchema = + schemaDeclaration && + createSchemaFromZod(innerZ => + innerZ.object( + Object.fromEntries( + Object.entries(schemaDeclaration).map(([k, v]) => [k, v(innerZ)]), ), - ); - } + ), + ); return { $$type: '@backstage/ExtensionDefinition', - version: Symbol.iterator in options.output ? 'v2' : 'v1', + version: 'v2', kind: options.kind, namespace: options.namespace, name: options.name, @@ -687,22 +596,18 @@ export function createExtension< } as CreateExtensionOptions); }, } as InternalExtensionDefinition< - TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }), - TConfigInput & - (string extends keyof TConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - >), + string extends keyof TConfigSchema + ? {} + : { + [key in keyof TConfigSchema]: z.infer>; + }, + string extends keyof TConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >, UOutput, TInputs, { diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts index e401adc675..084758b793 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts @@ -15,11 +15,7 @@ */ import { createExtensionDataRef } from './createExtensionDataRef'; -import { - ExtensionInput, - LegacyExtensionInput, - createExtensionInput, -} from './createExtensionInput'; +import { ExtensionInput, createExtensionInput } from './createExtensionInput'; const stringDataRef = createExtensionDataRef().with({ id: 'str' }); const numberDataRef = createExtensionDataRef().with({ id: 'num' }); @@ -130,40 +126,4 @@ describe('createExtensionInput', () => { createExtensionInput([stringDataRef, stringDataRef], { singleton: true }), ).toThrow("ExtensionInput may not have duplicate data refs: 'str'"); }); - - describe('old api', () => { - it('should create a regular input', () => { - const input = createExtensionInput({ - str: stringDataRef, - num: numberDataRef, - }); - expect(input).toEqual({ - $$type: '@backstage/ExtensionInput', - extensionData: { str: stringDataRef, num: numberDataRef }, - config: { singleton: false, optional: false }, - }); - - const x1: LegacyExtensionInput< - { str: typeof stringDataRef; num: typeof numberDataRef }, - { singleton: false; optional: false } - > = input; - // @ts-expect-error - const x2: LegacyExtensionInput< - { str: typeof numberDataRef; num: typeof stringDataRef }, // switched - { singleton: false; optional: false } - > = input; - // @ts-expect-error - const x3: LegacyExtensionInput< - { str: typeof stringDataRef; num: typeof numberDataRef }, - { singleton: true; optional: false } - > = input; - // @ts-expect-error - const x4: LegacyExtensionInput< - { str: typeof stringDataRef; num: typeof numberDataRef }, - { singleton: false; optional: true } - > = input; - - unused(x1, x2, x3, x4); - }); - }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts b/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts index b7efb13e6e..3651eb3ee5 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { AnyExtensionDataMap } from './createExtension'; import { ExtensionDataRef } from './createExtensionDataRef'; /** @public */ @@ -27,36 +26,6 @@ export interface ExtensionInput< config: TConfig; } -/** - * @public - * @deprecated This type will be removed. Use `ExtensionInput` instead. - */ -export interface LegacyExtensionInput< - TExtensionDataMap extends AnyExtensionDataMap, - TConfig extends { singleton: boolean; optional: boolean }, -> { - $$type: '@backstage/ExtensionInput'; - extensionData: TExtensionDataMap; - config: TConfig; -} - -/** - * @public - * @deprecated Use the following form instead: `createExtensionInput([dataRef1, dataRef2])` - */ -export function createExtensionInput< - TExtensionDataMap extends AnyExtensionDataMap, - TConfig extends { singleton?: boolean; optional?: boolean }, ->( - extensionData: TExtensionDataMap, - config?: TConfig, -): LegacyExtensionInput< - TExtensionDataMap, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } ->; /** @public */ export function createExtensionInput< UExtensionData extends ExtensionDataRef, @@ -73,26 +42,17 @@ export function createExtensionInput< >; export function createExtensionInput< TExtensionData extends ExtensionDataRef, - TExtensionDataMap extends AnyExtensionDataMap, TConfig extends { singleton?: boolean; optional?: boolean }, >( - extensionData: Array | TExtensionDataMap, + extensionData: Array, config?: TConfig, -): - | LegacyExtensionInput< - TExtensionDataMap, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } - > - | ExtensionInput< - TExtensionData, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } - > { +): ExtensionInput< + TExtensionData, + { + singleton: TConfig['singleton'] extends true ? true : false; + optional: TConfig['optional'] extends true ? true : false; + } +> { if (process.env.NODE_ENV !== 'production') { if (Array.isArray(extensionData)) { const seen = new Set(); @@ -124,19 +84,11 @@ export function createExtensionInput< ? true : false, }, - } as - | LegacyExtensionInput< - TExtensionDataMap, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } - > - | ExtensionInput< - TExtensionData, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } - >; + } as ExtensionInput< + TExtensionData, + { + singleton: TConfig['singleton'] extends true ? true : false; + optional: TConfig['optional'] extends true ? true : false; + } + >; } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts index aa00247222..ef95f82e25 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts @@ -40,22 +40,22 @@ describe('createExtensionOverrides', () => { createExtension({ name: 'a', attachTo: { id: 'app', input: 'apis' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), createExtension({ namespace: 'b', attachTo: { id: 'app', input: 'apis' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), createExtension({ kind: 'k', namespace: 'c', name: 'n', attachTo: { id: 'app', input: 'apis' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), ], }), @@ -122,8 +122,8 @@ describe('createExtensionOverrides', () => { createExtension({ namespace: 'a', attachTo: { id: 'app', input: 'apis' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), ], }); diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts index 71508b0c15..3615ba29e5 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts @@ -17,7 +17,6 @@ import React from 'react'; import { createApp } from '@backstage/frontend-app-api'; import { screen } from '@testing-library/react'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; import { createFrontendPlugin } from './createFrontendPlugin'; import { JsonObject } from '@backstage/types'; import { createExtension } from './createExtension'; @@ -43,14 +42,14 @@ const Extension1 = createExtension({ const Extension2 = createExtension({ name: '2', attachTo: { id: 'test/output', input: 'names' }, - output: { - name: nameExtensionDataRef, + output: [nameExtensionDataRef], + config: { + schema: { + name: z => z.string().default('extension-2'), + }, }, - configSchema: createSchemaFromZod(z => - z.object({ name: z.string().default('extension-2') }), - ), factory({ config }) { - return { name: config.name }; + return [nameExtensionDataRef(config.name)]; }, }); @@ -58,45 +57,45 @@ const Extension3 = createExtension({ name: '3', attachTo: { id: 'test/output', input: 'names' }, inputs: { - addons: createExtensionInput({ - name: nameExtensionDataRef, - }), - }, - output: { - name: nameExtensionDataRef, + addons: createExtensionInput([nameExtensionDataRef]), }, + output: [nameExtensionDataRef], factory({ inputs }) { - return { - name: `extension-3:${inputs.addons.map(n => n.output.name).join('-')}`, - }; + return [ + nameExtensionDataRef( + `extension-3:${inputs.addons + .map(n => n.get(nameExtensionDataRef)) + .join('-')}`, + ), + ]; }, }); const Child = createExtension({ name: 'child', attachTo: { id: 'test/3', input: 'addons' }, - output: { - name: nameExtensionDataRef, + output: [nameExtensionDataRef], + config: { + schema: { + name: z => z.string().default('child'), + }, }, - configSchema: createSchemaFromZod(z => - z.object({ name: z.string().default('child') }), - ), factory({ config }) { - return { name: config.name }; + return [nameExtensionDataRef(config.name)]; }, }); const Child2 = createExtension({ name: 'child2', attachTo: { id: 'test/3', input: 'addons' }, - output: { - name: nameExtensionDataRef, + output: [nameExtensionDataRef], + config: { + schema: { + name: z => z.string().default('child2'), + }, }, - configSchema: createSchemaFromZod(z => - z.object({ name: z.string().default('child2') }), - ), factory({ config }) { - return { name: config.name }; + return [nameExtensionDataRef(config.name)]; }, }); @@ -104,19 +103,19 @@ const outputExtension = createExtension({ name: 'output', attachTo: { id: 'app', input: 'root' }, inputs: { - names: createExtensionInput({ - name: nameExtensionDataRef, - }), - }, - output: { - element: coreExtensionData.reactElement, + names: createExtensionInput([nameExtensionDataRef]), }, + output: [coreExtensionData.reactElement], factory({ inputs }) { - return { - element: React.createElement('span', {}, [ - `Names: ${inputs.names.map(n => n.output.name).join(', ')}`, - ]), - }; + return [ + coreExtensionData.reactElement( + React.createElement('span', {}, [ + `Names: ${inputs.names + .map(n => n.get(nameExtensionDataRef)) + .join(', ')}`, + ]), + ), + ]; }, }); diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index 0b64490742..3862c0e178 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -19,17 +19,12 @@ export { createExtension, type ExtensionDefinition, type CreateExtensionOptions, - type ExtensionDataValues, type ResolvedExtensionInput, type ResolvedExtensionInputs, - type LegacyCreateExtensionOptions, - type AnyExtensionInputMap, - type AnyExtensionDataMap, } from './createExtension'; export { createExtensionInput, type ExtensionInput, - type LegacyExtensionInput, } from './createExtensionInput'; export { type ExtensionDataContainer } from './createExtensionDataContainer'; export { diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index 9cd4a76882..3cbc28f8ff 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -16,9 +16,6 @@ import { AppNode } from '../apis'; import { - AnyExtensionDataMap, - AnyExtensionInputMap, - ExtensionDataValues, ExtensionDefinition, ResolvedExtensionInputs, toInternalExtensionDefinition, @@ -47,13 +44,27 @@ export type InternalExtension = Extension< ( | { readonly version: 'v1'; - readonly inputs: AnyExtensionInputMap; - readonly output: AnyExtensionDataMap; - factory(options: { + readonly inputs: { + [inputName in string]: { + $$type: '@backstage/ExtensionInput'; + extensionData: { + [name in string]: AnyExtensionDataRef; + }; + config: { optional: boolean; singleton: boolean }; + }; + }; + readonly output: { + [name in string]: AnyExtensionDataRef; + }; + factory(context: { node: AppNode; config: TConfig; - inputs: ResolvedExtensionInputs; - }): ExtensionDataValues; + inputs: { + [inputName in string]: unknown; + }; + }): { + [inputName in string]: unknown; + }; } | { readonly version: 'v2'; From 9727678a7bed794d046d5da62598dadd7d8f6a1c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:48:50 +0200 Subject: [PATCH 02/16] frontend-plugin-api: stop exporting createSchemaFromZod Signed-off-by: Patrik Oldsberg --- packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts | 3 +-- packages/frontend-plugin-api/src/schema/index.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts index 9e63515ae4..85f9d5c44a 100644 --- a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts +++ b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts @@ -20,8 +20,7 @@ import zodToJsonSchema from 'zod-to-json-schema'; import { PortableSchema } from './types'; /** - * @public - * @deprecated Use the `config.schema` option of `createExtension` instead, or use `createExtensionBlueprint`. + * @internal */ export function createSchemaFromZod( schemaCreator: (zImpl: typeof z) => ZodSchema, diff --git a/packages/frontend-plugin-api/src/schema/index.ts b/packages/frontend-plugin-api/src/schema/index.ts index 7f21c4e07d..a8f92a37f4 100644 --- a/packages/frontend-plugin-api/src/schema/index.ts +++ b/packages/frontend-plugin-api/src/schema/index.ts @@ -14,5 +14,4 @@ * limitations under the License. */ -export { createSchemaFromZod } from './createSchemaFromZod'; export { type PortableSchema } from './types'; From 613e199390a2dea9b32c221523f7fc1b76dc6322 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:49:35 +0200 Subject: [PATCH 03/16] frontend-plugin-api: remove deprecations from createComponentExtension Signed-off-by: Patrik Oldsberg --- .../extensions/createComponentExtension.tsx | 57 ++++++------------- 1 file changed, 16 insertions(+), 41 deletions(-) diff --git a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx index b83b2586ba..9561636d22 100644 --- a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx @@ -15,41 +15,20 @@ */ import { lazy, ComponentType } from 'react'; -import { - AnyExtensionInputMap, - ResolvedExtensionInputs, - createExtension, - createExtensionDataRef, -} from '../wiring'; -import { Expand } from '../types'; -import { PortableSchema } from '../schema'; +import { createExtension, createExtensionDataRef } from '../wiring'; import { ComponentRef } from '../components'; /** @public */ -export function createComponentExtension< - TProps extends {}, - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { +export function createComponentExtension(options: { ref: ComponentRef; name?: string; disabled?: boolean; - /** @deprecated these will be removed in the future */ - inputs?: TInputs; - /** @deprecated these will be removed in the future */ - configSchema?: PortableSchema; loader: | { - lazy: (values: { - config: TConfig; - inputs: Expand>; - }) => Promise>; + lazy: () => Promise>; } | { - sync: (values: { - config: TConfig; - inputs: Expand>; - }) => ComponentType; + sync: () => ComponentType; }; }) { return createExtension({ @@ -57,34 +36,30 @@ export function createComponentExtension< namespace: options.ref.id, name: options.name, attachTo: { id: 'app', input: 'components' }, - inputs: options.inputs, disabled: options.disabled, - configSchema: options.configSchema, - output: { - component: createComponentExtension.componentDataRef, - }, - factory({ config, inputs }) { + output: [createComponentExtension.componentDataRef], + factory() { if ('sync' in options.loader) { - return { - component: { + return [ + createComponentExtension.componentDataRef({ ref: options.ref, - impl: options.loader.sync({ config, inputs }) as ComponentType, - }, - }; + impl: options.loader.sync() as ComponentType, + }), + ]; } const lazyLoader = options.loader.lazy; const ExtensionComponent = lazy(() => - lazyLoader({ config, inputs }).then(Component => ({ + lazyLoader().then(Component => ({ default: Component, })), ) as unknown as ComponentType; - return { - component: { + return [ + createComponentExtension.componentDataRef({ ref: options.ref, impl: ExtensionComponent, - }, - }; + }), + ]; }, }); } From 154573ab641d5f535b1d5944413ad217bf427809 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:50:04 +0200 Subject: [PATCH 04/16] frontend-test-utils: update extension tester to drop v1 support Signed-off-by: Patrik Oldsberg --- .../src/app/createExtensionTester.test.tsx | 22 ++++--- .../src/app/createExtensionTester.tsx | 63 ++++++++----------- 2 files changed, 39 insertions(+), 46 deletions(-) diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index 7467f61c4e..9528562198 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -18,10 +18,10 @@ import React, { useCallback } from 'react'; import { Link } from 'react-router-dom'; import { fireEvent, screen, waitFor } from '@testing-library/react'; import { + ApiBlueprint, analyticsApiRef, configApiRef, coreExtensionData, - createApiExtension, createApiFactory, createExtension, createExtensionDataRef, @@ -64,8 +64,8 @@ describe('createExtensionTester', () => { it("should fail to render an extension that doesn't output a react element", async () => { const extension = createExtension({ ...defaultDefinition, - output: { path: coreExtensionData.routePath }, - factory: () => ({ path: '/foo' }), + output: [coreExtensionData.routePath], + factory: () => [coreExtensionData.routePath('/foo')], }); const tester = createExtensionTester(extension); expect(() => tester.render()).toThrowErrorMatchingInlineSnapshot( @@ -122,7 +122,7 @@ describe('createExtensionTester', () => { const appTitle = configApi.getOptionalString('app.title'); return (
-

{appTitle ?? 'Backstafe app'}

+

{appTitle ?? 'Backstage app'}

{config.title ?? 'Index page'}

See details
@@ -186,12 +186,14 @@ describe('createExtensionTester', () => { // Mocking the analytics api implementation const analyticsApiMock = new MockAnalyticsApi(); - const analyticsApiOverride = createApiExtension({ - factory: createApiFactory({ - api: analyticsApiRef, - deps: {}, - factory: () => analyticsApiMock, - }), + const analyticsApiOverride = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: analyticsApiRef, + deps: {}, + factory: () => analyticsApiMock, + }), + }, }); const indexPageExtension = createExtension({ diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index 1f901699b1..63de46a76e 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -26,13 +26,13 @@ import { ExtensionDataRef, ExtensionDefinition, IconComponent, + NavItemBlueprint, RouteRef, + RouterBlueprint, coreExtensionData, createExtension, createExtensionInput, createExtensionOverrides, - createNavItemExtension, - createRouterExtension, useRouteRef, } from '@backstage/frontend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; @@ -74,30 +74,29 @@ const TestAppNavExtension = createExtension({ name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, inputs: { - items: createExtensionInput({ - target: createNavItemExtension.targetDataRef, - }), - }, - output: { - element: coreExtensionData.reactElement, + items: createExtensionInput([NavItemBlueprint.dataRefs.target]), }, + output: [coreExtensionData.reactElement], factory({ inputs }) { - return { - element: ( + return [ + coreExtensionData.reactElement( + , ), - }; + ]; }, }); @@ -253,18 +252,7 @@ export class ExtensionTester { let subjectOverride; // attaching to app/routes to render as index route if (subjectInternal.version === 'v1') { - subjectOverride = createExtension({ - ...subjectInternal, - attachTo: { id: 'app/routes', input: 'routes' }, - output: { - ...subjectInternal.output, - path: coreExtensionData.routePath, - }, - factory: params => ({ - ...subjectInternal.factory(params as any), - path: '/', - }), - }); + throw new Error('The extension tester does not support v1 extensions'); } else if (subjectInternal.version === 'v2') { subjectOverride = createExtension({ ...subjectInternal, @@ -282,6 +270,7 @@ export class ExtensionTester { return [...parentOutput, coreExtensionData.routePath('/')]; }, }); + (subjectOverride as any).configSchema = subjectInternal.configSchema; } else { throw new Error('Unsupported extension version'); } @@ -293,11 +282,13 @@ export class ExtensionTester { subjectOverride, ...this.#extensions.slice(1).map(extension => extension.definition), TestAppNavExtension, - createRouterExtension({ + RouterBlueprint.make({ namespace: 'test', - Component: ({ children }) => ( - {children} - ), + params: { + Component: ({ children }) => ( + {children} + ), + }, }), ], }), From ac6a64d7088509e3a76b9686971ad14a1adacc0c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:51:45 +0200 Subject: [PATCH 05/16] app-next: remove usage of v1 extension Signed-off-by: Patrik Oldsberg --- packages/app-next/src/index-public-experimental.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/app-next/src/index-public-experimental.tsx b/packages/app-next/src/index-public-experimental.tsx index 56bc698ffd..27b2af7d91 100644 --- a/packages/app-next/src/index-public-experimental.tsx +++ b/packages/app-next/src/index-public-experimental.tsx @@ -29,12 +29,8 @@ const authRedirectExtension = createExtension({ namespace: 'app', name: 'layout', attachTo: { id: 'app/root', input: 'children' }, - output: { - element: coreExtensionData.reactElement, - }, - factory: () => ({ - element: , - }), + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement()], }); const app = createApp({ From 90508e3a0661b226da7d906d99a789921339f937 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:54:08 +0200 Subject: [PATCH 06/16] frontend-app-api: update AppNav to use dataRefs from blueprint Signed-off-by: Patrik Oldsberg --- .../frontend-app-api/src/extensions/AppNav.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/frontend-app-api/src/extensions/AppNav.tsx b/packages/frontend-app-api/src/extensions/AppNav.tsx index 49c95e84f2..b80a537ef6 100644 --- a/packages/frontend-app-api/src/extensions/AppNav.tsx +++ b/packages/frontend-app-api/src/extensions/AppNav.tsx @@ -20,8 +20,8 @@ import { coreExtensionData, createExtensionInput, useRouteRef, - createNavItemExtension, - createNavLogoExtension, + NavItemBlueprint, + NavLogoBlueprint, } from '@backstage/frontend-plugin-api'; import { makeStyles } from '@material-ui/core/styles'; import { @@ -53,7 +53,7 @@ const useSidebarLogoStyles = makeStyles({ }); const SidebarLogo = ( - props: (typeof createNavLogoExtension.logoElementsDataRef)['T'], + props: (typeof NavLogoBlueprint.dataRefs.logoElements)['T'], ) => { const classes = useSidebarLogoStyles(); const { isOpen } = useSidebarOpenState(); @@ -70,7 +70,7 @@ const SidebarLogo = ( }; const SidebarNavItem = ( - props: (typeof createNavItemExtension.targetDataRef)['T'], + props: (typeof NavItemBlueprint.dataRefs.target)['T'], ) => { const { icon: Icon, title, routeRef } = props; const link = useRouteRef(routeRef); @@ -86,8 +86,8 @@ export const AppNav = createExtension({ name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, inputs: { - items: createExtensionInput([createNavItemExtension.targetDataRef]), - logos: createExtensionInput([createNavLogoExtension.logoElementsDataRef], { + items: createExtensionInput([NavItemBlueprint.dataRefs.target]), + logos: createExtensionInput([NavLogoBlueprint.dataRefs.logoElements], { singleton: true, optional: true, }), @@ -97,12 +97,12 @@ export const AppNav = createExtension({ coreExtensionData.reactElement( {inputs.items.map((item, index) => ( ))} From f010b2cafceebe05a4ad4d8077c9ab3a724fd1de Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:57:04 +0200 Subject: [PATCH 07/16] frontend-app-api: update instantiateAppNodeTree to inline support for v1 extensions Signed-off-by: Patrik Oldsberg --- .../src/tree/instantiateAppNodeTree.test.ts | 459 +++++++++--------- .../src/tree/instantiateAppNodeTree.ts | 33 +- 2 files changed, 262 insertions(+), 230 deletions(-) diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts index aded5d7826..b8acb6643d 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts @@ -15,14 +15,15 @@ */ import { + AnyExtensionDataRef, AppNode, Extension, ExtensionInput, + PortableSchema, ResolvedExtensionInput, createExtension, createExtensionDataRef, createExtensionInput, - createSchemaFromZod, } from '@backstage/frontend-plugin-api'; import { createAppNodeInstance, @@ -31,7 +32,12 @@ import { import { AppNodeSpec } from '@backstage/frontend-plugin-api'; import { resolveAppTree } from './resolveAppTree'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; +import { + InternalExtension, + resolveExtensionDefinition, +} from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { createSchemaFromZod } from '../../../frontend-plugin-api/src/schema/createSchemaFromZod'; import { withLogCollector } from '@backstage/test-utils'; const testDataRef = createExtensionDataRef().with({ id: 'test' }); @@ -80,28 +86,63 @@ function makeInstanceWithId( }; } +function createV1ExtensionInput( + extensionData: Record, + options: { singleton?: boolean; optional?: boolean } = {}, +) { + return { + $$type: '@backstage/ExtensionInput' as const, + extensionData, + config: { + singleton: options.singleton ?? false, + optional: options.optional ?? false, + }, + }; +} + +function createV1Extension(opts: { + namespace: string; + name?: string; + attachTo?: { id: string; input: string }; + inputs?: Record>; + output: Record; + configSchema?: PortableSchema; + factory: (ctx: { inputs: any; config: any }) => any; +}): Extension { + const ext: InternalExtension = { + $$type: '@backstage/Extension', + version: 'v1', + id: opts.name ? `${opts.namespace}/${opts.name}` : opts.namespace, + disabled: false, + attachTo: opts.attachTo ?? { id: 'ignored', input: 'ignored' }, + inputs: opts.inputs ?? {}, + output: opts.output, + configSchema: opts.configSchema, + factory: opts.factory, + }; + return ext; +} + describe('instantiateAppNodeTree', () => { describe('v1', () => { - const simpleExtension = resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - test: testDataRef, - other: otherDataRef.optional(), - }, - configSchema: createSchemaFromZod(z => - z.object({ - output: z.string().default('test'), - other: z.number().optional(), - }), - ), - factory({ config }) { - return { test: config.output, other: config.other }; - }, - }), - ); + const simpleExtension = createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + test: testDataRef, + other: otherDataRef.optional(), + }, + configSchema: createSchemaFromZod(z => + z.object({ + output: z.string().default('test'), + other: z.number().optional(), + }), + ), + factory({ config }) { + return { test: config.output, other: config.other }; + }, + }); it('should instantiate a single node', () => { const tree = resolveAppTree('root-node', [ @@ -129,21 +170,19 @@ describe('instantiateAppNodeTree', () => { it('should instantiate a node with attachments', () => { const tree = resolveAppTree('root-node', [ makeSpec( - resolveExtensionDefinition( - createExtension({ - namespace: 'root-node', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - test: createExtensionInput({ test: testDataRef }), - }, - output: { - inputMirror: inputMirrorDataRef, - }, - factory({ inputs }) { - return { inputMirror: inputs }; - }, - }), - ), + createV1Extension({ + namespace: 'root-node', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + test: createV1ExtensionInput({ test: testDataRef }), + }, + output: { + inputMirror: inputMirrorDataRef, + }, + factory({ inputs }) { + return { inputMirror: inputs }; + }, + }), ), makeSpec(simpleExtension, { id: 'child-node', @@ -175,21 +214,19 @@ describe('instantiateAppNodeTree', () => { const tree = resolveAppTree('root-node', [ { ...makeSpec( - resolveExtensionDefinition( - createExtension({ - namespace: 'root-node', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - test: createExtensionInput({ test: testDataRef }), - }, - output: { - inputMirror: inputMirrorDataRef, - }, - factory({ inputs }) { - return { inputMirror: inputs }; - }, - }), - ), + createV1Extension({ + namespace: 'root-node', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + test: createV1ExtensionInput({ test: testDataRef }), + }, + output: { + inputMirror: inputMirrorDataRef, + }, + factory({ inputs }) { + return { inputMirror: inputs }; + }, + }), ), }, { @@ -262,46 +299,44 @@ describe('instantiateAppNodeTree', () => { const instance = createAppNodeInstance({ attachments, node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - optionalSingletonPresent: createExtensionInput( - { - test: testDataRef, - other: otherDataRef.optional(), - }, - { singleton: true, optional: true }, - ), - optionalSingletonMissing: createExtensionInput( - { - test: testDataRef, - other: otherDataRef.optional(), - }, - { singleton: true, optional: true }, - ), - singleton: createExtensionInput( - { - test: testDataRef, - other: otherDataRef.optional(), - }, - { singleton: true }, - ), - many: createExtensionInput({ + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + optionalSingletonPresent: createV1ExtensionInput( + { test: testDataRef, other: otherDataRef.optional(), - }), - }, - output: { - inputMirror: inputMirrorDataRef, - }, - factory({ inputs }) { - return { inputMirror: inputs }; - }, - }), - ), + }, + { singleton: true, optional: true }, + ), + optionalSingletonMissing: createV1ExtensionInput( + { + test: testDataRef, + other: otherDataRef.optional(), + }, + { singleton: true, optional: true }, + ), + singleton: createV1ExtensionInput( + { + test: testDataRef, + other: otherDataRef.optional(), + }, + { singleton: true }, + ), + many: createV1ExtensionInput({ + test: testDataRef, + other: otherDataRef.optional(), + }), + }, + output: { + inputMirror: inputMirrorDataRef, + }, + factory({ inputs }) { + return { inputMirror: inputs }; + }, + }), ), }); @@ -344,19 +379,17 @@ describe('instantiateAppNodeTree', () => { expect(() => createAppNodeInstance({ node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: {}, - factory() { - const error = new Error('NOPE'); - error.name = 'NopeError'; - throw error; - }, - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: {}, + factory() { + const error = new Error('NOPE'); + error.name = 'NopeError'; + throw error; + }, + }), ), attachments: new Map(), }), @@ -369,20 +402,18 @@ describe('instantiateAppNodeTree', () => { expect(() => createAppNodeInstance({ node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - test1: testDataRef, - test2: testDataRef, - }, - factory({}) { - return { test1: 'test', test2: 'test2' }; - }, - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + test1: testDataRef, + test2: testDataRef, + }, + factory({}) { + return { test1: 'test', test2: 'test2' }; + }, + }), ), attachments: new Map(), }), @@ -395,19 +426,17 @@ describe('instantiateAppNodeTree', () => { expect(() => createAppNodeInstance({ node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - test: testDataRef, - }, - factory({}) { - return { nonexistent: 'test' } as any; - }, - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + test: testDataRef, + }, + factory({}) { + return { nonexistent: 'test' } as any; + }, + }), ), attachments: new Map(), }), @@ -420,23 +449,21 @@ describe('instantiateAppNodeTree', () => { expect(() => createAppNodeInstance({ node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - test: testDataRef, - }, - { singleton: true }, - ), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createV1ExtensionInput( + { + test: testDataRef, + }, + { singleton: true }, + ), + }, + output: {}, + factory: () => ({}), + }), ), attachments: new Map(), }), @@ -467,20 +494,18 @@ describe('instantiateAppNodeTree', () => { ], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'parent', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - declared: createExtensionInput({ - test: testDataRef, - }), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'parent', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + declared: createV1ExtensionInput({ + test: testDataRef, + }), + }, + output: {}, + factory: () => ({}), + }), ), }), ); @@ -507,15 +532,13 @@ describe('instantiateAppNodeTree', () => { ], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'parent', - attachTo: { id: 'ignored', input: 'ignored' }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'parent', + attachTo: { id: 'ignored', input: 'ignored' }, + output: {}, + factory: () => ({}), + }), ), }), ); @@ -539,23 +562,21 @@ describe('instantiateAppNodeTree', () => { ], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - test: testDataRef, - }, - { singleton: true }, - ), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createV1ExtensionInput( + { + test: testDataRef, + }, + { singleton: true }, + ), + }, + output: {}, + factory: () => ({}), + }), ), }), ).toThrow( @@ -576,23 +597,21 @@ describe('instantiateAppNodeTree', () => { ], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - test: testDataRef, - }, - { singleton: true, optional: true }, - ), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createV1ExtensionInput( + { + test: testDataRef, + }, + { singleton: true, optional: true }, + ), + }, + output: {}, + factory: () => ({}), + }), ), }), ).toThrow( @@ -607,23 +626,21 @@ describe('instantiateAppNodeTree', () => { ['singleton', [makeInstanceWithId(simpleExtension, undefined)]], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - other: otherDataRef, - }, - { singleton: true }, - ), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createV1ExtensionInput( + { + other: otherDataRef, + }, + { singleton: true }, + ), + }, + output: {}, + factory: () => ({}), + }), ), }), ).toThrowErrorMatchingInlineSnapshot( diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts index c70e351105..fd8d324652 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts @@ -15,9 +15,7 @@ */ import { - AnyExtensionDataMap, AnyExtensionDataRef, - AnyExtensionInputMap, ExtensionDataContainer, ExtensionDataRef, ExtensionInput, @@ -32,8 +30,10 @@ type Mutable = { -readonly [P in keyof T]: T[P]; }; -function resolveInputDataMap( - dataMap: AnyExtensionDataMap, +function resolveV1InputDataMap( + dataMap: { + [name in string]: AnyExtensionDataRef; + }, attachment: AppNode, inputName: string, ) { @@ -134,9 +134,17 @@ function reportUndeclaredAttachments( } function resolveV1Inputs( - inputMap: AnyExtensionInputMap, + inputMap: { + [inputName in string]: { + $$type: '@backstage/ExtensionInput'; + extensionData: { + [name in string]: AnyExtensionDataRef; + }; + config: { optional: boolean; singleton: boolean }; + }; + }, attachments: ReadonlyMap, -): ResolvedExtensionInputs { +) { return mapValues(inputMap, (input, inputName) => { const attachedNodes = attachments.get(inputName) ?? []; @@ -158,7 +166,7 @@ function resolveV1Inputs( } return { node: attachedNodes[0], - output: resolveInputDataMap( + output: resolveV1InputDataMap( input.extensionData, attachedNodes[0], inputName, @@ -168,9 +176,16 @@ function resolveV1Inputs( return attachedNodes.map(attachment => ({ node: attachment, - output: resolveInputDataMap(input.extensionData, attachment, inputName), + output: resolveV1InputDataMap(input.extensionData, attachment, inputName), })); - }) as ResolvedExtensionInputs; + }) as { + [inputName in string]: { + node: AppNode; + output: { + [name in string]: unknown; + }; + }; + }; } function resolveV2Inputs( From 7fb8102c0cf1bfb6688534ea8a2c66c2a3b29d4e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:58:09 +0200 Subject: [PATCH 08/16] frontend-plugin-api: remove deprecated extension creators Signed-off-by: Patrik Oldsberg --- .../src/extensions/createApiExtension.test.ts | 92 ---------- .../src/extensions/createApiExtension.ts | 82 --------- .../createAppRootElementExtension.test.tsx | 109 ----------- .../createAppRootElementExtension.ts | 72 -------- .../createAppRootWrapperExtension.test.tsx | 121 ------------- .../createAppRootWrapperExtension.tsx | 88 --------- .../src/extensions/createNavItemExtension.tsx | 69 ------- .../createNavLogoExtension.test.tsx | 48 ----- .../src/extensions/createNavLogoExtension.tsx | 61 ------- .../extensions/createPageExtension.test.tsx | 145 --------------- .../src/extensions/createPageExtension.tsx | 98 ---------- .../extensions/createRouterExtension.test.tsx | 169 ------------------ .../src/extensions/createRouterExtension.tsx | 88 --------- .../createSignInPageExtension.test.tsx | 47 ----- .../extensions/createSignInPageExtension.tsx | 88 --------- .../src/extensions/createThemeExtension.ts | 44 ----- .../createTranslationExtension.test.ts | 137 -------------- .../extensions/createTranslationExtension.ts | 47 ----- .../src/extensions/index.ts | 10 -- 19 files changed, 1615 deletions(-) delete mode 100644 packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts delete mode 100644 packages/frontend-plugin-api/src/extensions/createApiExtension.ts delete mode 100644 packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts delete mode 100644 packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createPageExtension.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createThemeExtension.ts delete mode 100644 packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts delete mode 100644 packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts deleted file mode 100644 index 8f8a0cd9ac..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createApiExtension } from './createApiExtension'; -import { createApiFactory, createApiRef } from '@backstage/core-plugin-api'; - -describe('createApiExtension', () => { - it('fills in the expected values for an existing factory', () => { - const api = createApiRef<{ foo: string }>({ id: 'test' }); - const factory = createApiFactory({ - api, - deps: {}, - factory: () => ({ foo: 'bar' }), - }); - - expect( - createApiExtension({ - factory, - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'api', - namespace: 'test', - attachTo: { id: 'app', input: 'apis' }, - disabled: false, - configSchema: undefined, - inputs: {}, - output: { - api: expect.objectContaining({ - $$type: '@backstage/ExtensionDataRef', - id: 'core.api.factory', - config: {}, - }), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - }); - - it('fills in the expected values for a ref and custom factory', () => { - const api = createApiRef<{ foo: string }>({ id: 'test' }); - const factory = jest.fn(() => ({ foo: 'bar' })); - - const extension = createApiExtension({ - api, - inputs: {}, - factory({ config: _config, inputs: _inputs }) { - return createApiFactory({ - api, - deps: {}, - factory, - }); - }, - }); - // boo - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'api', - namespace: 'test', - attachTo: { id: 'app', input: 'apis' }, - disabled: false, - configSchema: undefined, - inputs: {}, - output: { - api: expect.objectContaining({ - $$type: '@backstage/ExtensionDataRef', - id: 'core.api.factory', - config: {}, - }), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts deleted file mode 100644 index bb854d202f..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AnyApiFactory, AnyApiRef } from '@backstage/core-plugin-api'; -import { PortableSchema } from '../schema'; -import { ResolvedExtensionInputs, createExtension } from '../wiring'; -import { AnyExtensionInputMap } from '../wiring/createExtension'; -import { Expand } from '../types'; -import { ApiBlueprint } from '../blueprints/ApiBlueprint'; - -/** - * @public - * @deprecated Use {@link ApiBlueprint} instead. - */ -export function createApiExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->( - options: ( - | { - api: AnyApiRef; - factory: (options: { - config: TConfig; - inputs: Expand>; - }) => AnyApiFactory; - } - | { - factory: AnyApiFactory; - } - ) & { - configSchema?: PortableSchema; - inputs?: TInputs; - }, -) { - const { factory, configSchema, inputs: extensionInputs } = options; - - const apiRef = - 'api' in options ? options.api : (factory as { api: AnyApiRef }).api; - - return createExtension({ - kind: 'api', - // Since ApiRef IDs use a global namespace we use the namespace here in order to override - // potential plugin IDs and always end up with the format `api:` - namespace: apiRef.id, - attachTo: { id: 'app', input: 'apis' }, - inputs: extensionInputs, - configSchema, - output: { - api: ApiBlueprint.dataRefs.factory, - }, - factory({ config, inputs }) { - if (typeof factory === 'function') { - return { api: factory({ config, inputs }) }; - } - return { api: factory }; - }, - }); -} - -/** - * @public - * @deprecated Use {@link ApiBlueprint} instead. - */ -export namespace createApiExtension { - /** - * @deprecated Use {@link ApiBlueprint} instead. - */ - export const factoryDataRef = ApiBlueprint.dataRefs.factory; -} diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx deleted file mode 100644 index 6e80f6fe13..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { screen } from '@testing-library/react'; -import React from 'react'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { coreExtensionData } from '../wiring/coreExtensionData'; -import { createExtension } from '../wiring/createExtension'; -import { createExtensionInput } from '../wiring/createExtensionInput'; -import { createAppRootElementExtension } from './createAppRootElementExtension'; - -describe('createAppRootElementExtension', () => { - it('works with simple options and just an element', async () => { - const extension = createAppRootElementExtension({ - element:
Hello
, - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-root-element', - attachTo: { id: 'app/root', input: 'elements' }, - disabled: false, - inputs: {}, - output: { - element: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - - createExtensionTester(extension).render(); - - await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); - }); - - it('works with complex options and a callback', async () => { - const schema = createSchemaFromZod(z => z.object({ name: z.string() })); - - const extension = createAppRootElementExtension({ - namespace: 'ns', - name: 'test', - configSchema: schema, - attachTo: { id: 'other', input: 'slot' }, - disabled: true, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - element: ({ inputs, config }) => ( -
- Hello, {config.name}, {inputs.children.length} -
- ), - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-root-element', - namespace: 'ns', - name: 'test', - attachTo: { id: 'other', input: 'slot' }, - configSchema: schema, - disabled: true, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - output: { - element: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - - createExtensionTester(extension, { config: { name: 'Robin' } }) - .add( - createExtension({ - attachTo: { id: 'app-root-element:ns/test', input: 'children' }, - output: { element: coreExtensionData.reactElement }, - factory: () => ({ element:
}), - }), - ) - .render(); - - await expect( - screen.findByText('Hello, Robin, 1'), - ).resolves.toBeInTheDocument(); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts deleted file mode 100644 index c8f7bdc366..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { JSX } from 'react'; -import { PortableSchema } from '../schema/types'; -import { Expand } from '../types'; -import { coreExtensionData } from '../wiring/coreExtensionData'; -import { - AnyExtensionInputMap, - ExtensionDefinition, - ResolvedExtensionInputs, - createExtension, -} from '../wiring/createExtension'; - -/** - * Creates an extension that renders a React element at the app root, outside of - * the app layout. This is useful for example for shared popups and similar. - * - * @public - * @deprecated Use {@link AppRootElementBlueprint} instead. - */ -export function createAppRootElementExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - element: - | JSX.Element - | ((options: { - inputs: Expand>; - config: TConfig; - }) => JSX.Element); -}): ExtensionDefinition { - return createExtension({ - kind: 'app-root-element', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { id: 'app/root', input: 'elements' }, - configSchema: options.configSchema, - disabled: options.disabled, - inputs: options.inputs, - output: { - element: coreExtensionData.reactElement, - }, - factory({ inputs, config }) { - return { - element: - typeof options.element === 'function' - ? options.element({ inputs, config }) - : options.element, - }; - }, - }); -} diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx deleted file mode 100644 index 993c4fdee3..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { screen } from '@testing-library/react'; -import React from 'react'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { coreExtensionData } from '../wiring/coreExtensionData'; -import { createExtension } from '../wiring/createExtension'; -import { createExtensionInput } from '../wiring/createExtensionInput'; -import { createAppRootWrapperExtension } from './createAppRootWrapperExtension'; -import { createPageExtension } from './createPageExtension'; - -describe('createAppRootWrapperExtension', () => { - it('works with simple options and no props', async () => { - const extension = createAppRootWrapperExtension({ - Component: () =>
Hello
, - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-root-wrapper', - attachTo: { id: 'app/root', input: 'wrappers' }, - disabled: false, - inputs: {}, - output: { - component: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - - createExtensionTester( - createPageExtension({ - defaultPath: '/', - loader: async () =>
, - }), - ) - .add(extension) - .render(); - - await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); - }); - - it('works with complex options and props', async () => { - const schema = createSchemaFromZod(z => z.object({ name: z.string() })); - - const extension = createAppRootWrapperExtension({ - namespace: 'ns', - name: 'test', - configSchema: schema, - disabled: true, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - Component: ({ inputs, config, children }) => ( -
- {children} -
- ), - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-root-wrapper', - namespace: 'ns', - name: 'test', - attachTo: { id: 'app/root', input: 'wrappers' }, - configSchema: schema, - disabled: true, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - output: { - component: expect.anything(), - }, - factory: expect.any(Function), - override: expect.any(Function), - toString: expect.any(Function), - }); - - createExtensionTester( - createPageExtension({ - defaultPath: '/', - loader: async () =>
Hello
, - }), - ) - .add(extension, { config: { name: 'Robin' } }) - .add( - createExtension({ - attachTo: { id: 'app-root-wrapper:ns/test', input: 'children' }, - output: { element: coreExtensionData.reactElement }, - factory: () => ({ element:
}), - }), - ) - .render(); - - await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); - await expect(screen.findByTestId('Robin-1')).resolves.toBeInTheDocument(); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx deleted file mode 100644 index f055618ffd..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { ComponentType, PropsWithChildren } from 'react'; -import { PortableSchema } from '../schema/types'; -import { - AnyExtensionInputMap, - ExtensionDefinition, - ResolvedExtensionInputs, - createExtension, -} from '../wiring/createExtension'; -import { Expand } from '../types'; -import { AppRootWrapperBlueprint } from '../blueprints/AppRootWrapperBlueprint'; - -/** - * Creates an extension that renders a React wrapper at the app root, enclosing - * the app layout. This is useful for example for adding global React contexts - * and similar. - * - * @public - * @deprecated Use {@link AppRootWrapperBlueprint} instead. - */ -export function createAppRootWrapperExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - Component: ComponentType< - PropsWithChildren<{ - inputs: Expand>; - config: TConfig; - }> - >; -}): ExtensionDefinition { - return createExtension({ - kind: 'app-root-wrapper', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { id: 'app/root', input: 'wrappers' }, - configSchema: options.configSchema, - disabled: options.disabled, - inputs: options.inputs, - output: { - component: AppRootWrapperBlueprint.dataRefs.component, - }, - factory({ inputs, config }) { - const Component = (props: PropsWithChildren<{}>) => { - return ( - - {props.children} - - ); - }; - return { - component: Component, - }; - }, - }); -} - -/** - * @public - * @deprecated Use {@link AppRootWrapperBlueprint} instead. - */ -export namespace createAppRootWrapperExtension { - /** - * @deprecated Use {@link AppRootWrapperBlueprint} instead. - */ - export const componentDataRef = AppRootWrapperBlueprint.dataRefs.component; -} diff --git a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx deleted file mode 100644 index f6da531776..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { IconComponent } from '@backstage/core-plugin-api'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { createExtension } from '../wiring'; -import { RouteRef } from '../routing'; -import { NavItemBlueprint } from '../blueprints/NavItemBlueprint'; - -/** - * Helper for creating extensions for a nav item. - * - * @public - * @deprecated Use {@link NavItemBlueprint} instead. - */ -export function createNavItemExtension(options: { - namespace?: string; - name?: string; - routeRef: RouteRef; - title: string; - icon: IconComponent; -}) { - const { routeRef, title, icon, namespace, name } = options; - return createExtension({ - namespace, - name, - kind: 'nav-item', - attachTo: { id: 'app/nav', input: 'items' }, - configSchema: createSchemaFromZod(z => - z.object({ - title: z.string().default(title), - }), - ), - output: { - navTarget: NavItemBlueprint.dataRefs.target, - }, - factory: ({ config }) => ({ - navTarget: { - title: config.title, - icon, - routeRef, - }, - }), - }); -} - -/** - * @public - * @deprecated Use {@link NavItemBlueprint} instead. - */ -export namespace createNavItemExtension { - /** - * @deprecated Use {@link NavItemBlueprint} instead. - */ - export const targetDataRef = NavItemBlueprint.dataRefs.target; -} diff --git a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx deleted file mode 100644 index 17187a339d..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { createNavLogoExtension } from './createNavLogoExtension'; - -jest.mock('@backstage/core-plugin-api', () => ({ - ...jest.requireActual('@backstage/core-plugin-api'), -})); - -describe('createNavLogoExtension', () => { - it('creates the extension properly', () => { - expect( - createNavLogoExtension({ - name: 'test', - logoFull:
Logo Full
, - logoIcon:
Logo Icon
, - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'nav-logo', - name: 'test', - attachTo: { id: 'app/nav', input: 'logos' }, - disabled: false, - inputs: {}, - output: { - logos: expect.anything(), - }, - factory: expect.any(Function), - override: expect.any(Function), - toString: expect.any(Function), - }); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx deleted file mode 100644 index 449bddb342..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createExtension } from '../wiring'; -import { NavLogoBlueprint } from '../blueprints/NavLogoBlueprint'; - -/** - * Helper for creating extensions for a nav logos. - * - * @public - * @deprecated Use {@link NavLogoBlueprint} instead. - */ -export function createNavLogoExtension(options: { - name?: string; - namespace?: string; - logoIcon: JSX.Element; - logoFull: JSX.Element; -}) { - const { logoIcon, logoFull } = options; - return createExtension({ - kind: 'nav-logo', - name: options?.name, - namespace: options?.namespace, - attachTo: { id: 'app/nav', input: 'logos' }, - output: { - logos: NavLogoBlueprint.dataRefs.logoElements, - }, - factory: () => { - return { - logos: { - logoIcon, - logoFull, - }, - }; - }, - }); -} - -/** - * @public - * @deprecated Use {@link NavLogoBlueprint} instead. - */ -export namespace createNavLogoExtension { - /** - * @deprecated Use {@link NavLogoBlueprint} instead. - */ - export const logoElementsDataRef = NavLogoBlueprint.dataRefs.logoElements; -} diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx deleted file mode 100644 index e5dd4027c0..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { useAnalytics } from '@backstage/core-plugin-api'; -import { waitFor } from '@testing-library/react'; -import { PortableSchema } from '../schema'; -import { coreExtensionData, createExtensionInput } from '../wiring'; -import { createPageExtension } from './createPageExtension'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; - -jest.mock('@backstage/core-plugin-api', () => ({ - ...jest.requireActual('@backstage/core-plugin-api'), - useAnalytics: jest.fn(), -})); - -describe('createPageExtension', () => { - it('creates the extension properly', () => { - const configSchema: PortableSchema<{ path: string }> = { - parse: jest.fn(), - schema: {} as any, - }; - - expect( - createPageExtension({ - name: 'test', - configSchema, - loader: async () =>
, - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - name: 'test', - kind: 'page', - attachTo: { id: 'app/routes', input: 'routes' }, - configSchema: expect.anything(), - disabled: false, - inputs: {}, - output: { - element: expect.anything(), - path: expect.anything(), - routeRef: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - - expect( - createPageExtension({ - name: 'test', - attachTo: { id: 'other', input: 'place' }, - disabled: true, - configSchema, - inputs: { - first: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - loader: async () =>
, - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - name: 'test', - kind: 'page', - attachTo: { id: 'other', input: 'place' }, - configSchema: expect.anything(), - override: expect.any(Function), - disabled: true, - inputs: { - first: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - output: { - element: expect.anything(), - path: expect.anything(), - routeRef: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - }); - - expect( - createPageExtension({ - name: 'test', - defaultPath: '/here', - loader: async () =>
, - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - name: 'test', - kind: 'page', - attachTo: { id: 'app/routes', input: 'routes' }, - configSchema: expect.anything(), - disabled: false, - inputs: {}, - output: { - element: expect.anything(), - path: expect.anything(), - routeRef: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - }); - - it('capture page view event in analytics', async () => { - const captureEvent = jest.fn(); - - (useAnalytics as jest.Mock).mockReturnValue({ - captureEvent, - }); - - createExtensionTester( - createPageExtension({ - defaultPath: '/', - loader: async () =>
Component
, - }), - ).render(); - - await waitFor(() => - expect(captureEvent).toHaveBeenCalledWith( - '_ROUTABLE-EXTENSION-RENDERED', - '', - ), - ); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx deleted file mode 100644 index bef642e465..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { lazy } from 'react'; -import { ExtensionBoundary } from '../components'; -import { createSchemaFromZod, PortableSchema } from '../schema'; -import { - coreExtensionData, - createExtension, - ResolvedExtensionInputs, - AnyExtensionInputMap, -} from '../wiring'; -import { RouteRef } from '../routing'; -import { Expand } from '../types'; -import { ExtensionDefinition } from '../wiring/createExtension'; - -/** - * Helper for creating extensions for a routable React page component. - * - * @public - * @deprecated Use {@link PageBlueprint} instead. - */ -export function createPageExtension< - TConfig extends { path: string }, - TInputs extends AnyExtensionInputMap, ->( - options: ( - | { - defaultPath: string; - } - | { - configSchema: PortableSchema; - } - ) & { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - routeRef?: RouteRef; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise; - }, -): ExtensionDefinition { - const configSchema = - 'configSchema' in options - ? options.configSchema - : (createSchemaFromZod(z => - z.object({ path: z.string().default(options.defaultPath) }), - ) as PortableSchema); - - return createExtension({ - kind: 'page', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { id: 'app/routes', input: 'routes' }, - configSchema, - inputs: options.inputs, - disabled: options.disabled, - output: { - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath, - routeRef: coreExtensionData.routeRef.optional(), - }, - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ config, inputs }) - .then(element => ({ default: () => element })), - ); - - return { - path: config.path, - routeRef: options.routeRef, - element: ( - - - - ), - }; - }, - }); -} diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx deleted file mode 100644 index 2c16076fcf..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createSpecializedApp } from '@backstage/frontend-app-api'; -import { render, screen } from '@testing-library/react'; -import React from 'react'; -import { MockConfigApi } from '@backstage/test-utils'; -import { MemoryRouter } from 'react-router-dom'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { coreExtensionData } from '../wiring/coreExtensionData'; -import { createExtension } from '../wiring/createExtension'; -import { createExtensionInput } from '../wiring/createExtensionInput'; -import { createExtensionOverrides } from '../wiring/createExtensionOverrides'; -import { createPageExtension } from './createPageExtension'; -import { createRouterExtension } from './createRouterExtension'; - -describe('createRouterExtension', () => { - it('works with simple options and no props', async () => { - const extension = createRouterExtension({ - namespace: 'test', - Component: ({ children }) => ( - -
{children}
-
- ), - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-router-component', - namespace: 'test', - attachTo: { id: 'app/root', input: 'router' }, - disabled: false, - inputs: {}, - output: { - component: expect.anything(), - }, - factory: expect.any(Function), - override: expect.any(Function), - toString: expect.any(Function), - }); - - const app = createSpecializedApp({ - features: [ - createExtensionOverrides({ - extensions: [ - extension, - createPageExtension({ - namespace: 'test', - defaultPath: '/', - loader: async () =>
, - }), - ], - }), - ], - }); - - render(app.createRoot()); - - await expect( - screen.findByTestId('test-contents'), - ).resolves.toBeInTheDocument(); - await expect( - screen.findByTestId('test-router'), - ).resolves.toBeInTheDocument(); - }); - - it('works with complex options and props', async () => { - const schema = createSchemaFromZod(z => z.object({ name: z.string() })); - - const extension = createRouterExtension({ - namespace: 'test', - name: 'test', - configSchema: schema, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - Component: ({ inputs, config, children }) => ( - -
- {children} -
-
- ), - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-router-component', - namespace: 'test', - name: 'test', - attachTo: { id: 'app/root', input: 'router' }, - configSchema: schema, - disabled: false, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - output: { - component: expect.anything(), - }, - factory: expect.any(Function), - override: expect.any(Function), - toString: expect.any(Function), - }); - - const app = createSpecializedApp({ - features: [ - createExtensionOverrides({ - extensions: [ - extension, - createExtension({ - namespace: 'test', - attachTo: { - id: 'app-router-component:test/test', - input: 'children', - }, - output: { element: coreExtensionData.reactElement }, // doesn't matter - factory: () => ({ element:
}), - }), - createPageExtension({ - namespace: 'test', - defaultPath: '/', - loader: async () =>
, - }), - ], - }), - ], - config: new MockConfigApi({ - app: { - extensions: [ - { - 'app-router-component:test/test': { config: { name: 'Robin' } }, - }, - ], - }, - }), - }); - - render(app.createRoot()); - - await expect( - screen.findByTestId('test-contents'), - ).resolves.toBeInTheDocument(); - await expect( - screen.findByTestId('test-router-Robin-1'), - ).resolves.toBeInTheDocument(); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx deleted file mode 100644 index 258940c694..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { ComponentType, PropsWithChildren } from 'react'; -import { PortableSchema } from '../schema/types'; -import { - AnyExtensionInputMap, - ExtensionDefinition, - ResolvedExtensionInputs, - createExtension, -} from '../wiring/createExtension'; -import { Expand } from '../types'; -import { RouterBlueprint } from '../blueprints/RouterBlueprint'; - -/** - * Creates an extension that replaces the router implementation at the app root. - * This is useful to be able to for example replace the BrowserRouter with a - * MemoryRouter in tests, or to add additional props to a BrowserRouter. - * - * @public - * @deprecated Use {@link RouterBlueprint} instead. - */ -export function createRouterExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - Component: ComponentType< - PropsWithChildren<{ - inputs: Expand>; - config: TConfig; - }> - >; -}): ExtensionDefinition { - return createExtension({ - kind: 'app-router-component', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { id: 'app/root', input: 'router' }, - configSchema: options.configSchema, - disabled: options.disabled, - inputs: options.inputs, - output: { - component: RouterBlueprint.dataRefs.component, - }, - factory({ inputs, config }) { - const Component = (props: PropsWithChildren<{}>) => { - return ( - - {props.children} - - ); - }; - return { - component: Component, - }; - }, - }); -} - -/** - * @public - * @deprecated Use {@link RouterBlueprint} instead. - */ -export namespace createRouterExtension { - /** - * @deprecated Use {@link RouterBlueprint} instead. - */ - export const componentDataRef = RouterBlueprint.dataRefs.component; -} diff --git a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx deleted file mode 100644 index d8835ae788..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { screen } from '@testing-library/react'; -import { createSignInPageExtension } from './createSignInPageExtension'; -import { coreExtensionData, createExtension } from '../wiring'; - -describe('createSignInPageExtension', () => { - it('renders a sign-in page', async () => { - const SignInPage = createSignInPageExtension({ - name: 'test', - loader: async () => () =>
, - }); - - createExtensionTester( - createExtension({ - name: 'dummy', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - element: coreExtensionData.reactElement, - }, - factory: () => ({ element:
}), - }), - ) - .add(SignInPage) - .render(); - - await expect( - screen.findByTestId('sign-in-page'), - ).resolves.toBeInTheDocument(); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx deleted file mode 100644 index 9c663b72f5..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { ComponentType, lazy } from 'react'; -import { ExtensionBoundary } from '../components'; -import { PortableSchema } from '../schema'; -import { - createExtension, - ResolvedExtensionInputs, - AnyExtensionInputMap, - ExtensionDefinition, -} from '../wiring'; -import { Expand } from '../types'; -import { SignInPageProps } from '@backstage/core-plugin-api'; -import { SignInPageBlueprint } from '../blueprints'; - -/** - * - * @public - * @deprecated Use {@link SignInPageBlueprint} instead. - */ -export function createSignInPageExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise>; -}): ExtensionDefinition { - return createExtension({ - kind: 'sign-in-page', - namespace: options?.namespace, - name: options?.name, - attachTo: options.attachTo ?? { id: 'app/root', input: 'signInPage' }, - configSchema: options.configSchema, - inputs: options.inputs, - disabled: options.disabled, - output: { - component: createSignInPageExtension.componentDataRef, - }, - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ config, inputs }) - .then(component => ({ default: component })), - ); - - return { - component: props => ( - - - - ), - }; - }, - }); -} - -/** - * @public - * @deprecated Use {@link SignInPageBlueprint} instead. - */ -export namespace createSignInPageExtension { - /** - * @deprecated Use {@link SignInPageBlueprint} instead. - */ - export const componentDataRef = SignInPageBlueprint.dataRefs.component; -} diff --git a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts b/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts deleted file mode 100644 index 2662ebb39c..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ThemeBlueprint } from '../blueprints/ThemeBlueprint'; -import { createExtension } from '../wiring'; -import { AppTheme } from '@backstage/core-plugin-api'; - -/** - * @public - * @deprecated Use {@link ThemeBlueprint} instead. - */ -export function createThemeExtension(theme: AppTheme) { - return createExtension({ - kind: 'theme', - namespace: 'app', - name: theme.id, - attachTo: { id: 'app', input: 'themes' }, - output: { - theme: ThemeBlueprint.dataRefs.theme, - }, - factory: () => ({ theme }), - }); -} - -/** - * @public - * @deprecated Use {@link ThemeBlueprint} instead. - */ -export namespace createThemeExtension { - export const themeDataRef = ThemeBlueprint.dataRefs.theme; -} diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts deleted file mode 100644 index 019c565cbd..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createTranslationRef, - createTranslationMessages, - createTranslationResource, -} from '@backstage/core-plugin-api/alpha'; -import { createTranslationExtension } from './createTranslationExtension'; - -const translationRef = createTranslationRef({ - id: 'test', - messages: { - a: 'a', - b: 'b', - }, -}); - -describe('createTranslationExtension', () => { - it('creates a translation message extension', () => { - const messages = createTranslationMessages({ - ref: translationRef, - messages: { - a: 'A', - }, - }); - const extension = createTranslationExtension({ resource: messages }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'translation', - namespace: 'test', - attachTo: { id: 'app', input: 'translations' }, - disabled: false, - override: expect.any(Function), - inputs: {}, - output: { - resource: createTranslationExtension.translationDataRef, - }, - factory: expect.any(Function), - toString: expect.any(Function), - }); - - expect((extension as any).factory({} as any)).toEqual({ - resource: messages, - }); - }); - - it('creates a translation resource extension', () => { - const resource = createTranslationResource({ - ref: translationRef, - translations: { - sv: () => - Promise.resolve({ - default: createTranslationMessages({ - ref: translationRef, - messages: { - a: 'Ä', - b: 'Ö', - }, - }), - }), - }, - }); - const extension = createTranslationExtension({ resource }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'translation', - namespace: 'test', - attachTo: { id: 'app', input: 'translations' }, - disabled: false, - override: expect.any(Function), - inputs: {}, - output: { - resource: createTranslationExtension.translationDataRef, - }, - factory: expect.any(Function), - toString: expect.any(Function), - }); - - expect((extension as any).factory({} as any)).toEqual({ resource }); - }); - - it('creates a translation resource extension with a name', () => { - expect( - createTranslationExtension({ - name: 'sv', - resource: createTranslationResource({ - ref: translationRef, - translations: { - sv: () => - Promise.resolve({ - default: createTranslationMessages({ - ref: translationRef, - messages: { - a: 'Ä', - b: 'Ö', - }, - }), - }), - }, - }), - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'translation', - namespace: 'test', - override: expect.any(Function), - name: 'sv', - attachTo: { id: 'app', input: 'translations' }, - disabled: false, - inputs: {}, - output: { - resource: createTranslationExtension.translationDataRef, - }, - factory: expect.any(Function), - toString: expect.any(Function), - }); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts deleted file mode 100644 index 8a094cd593..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { TranslationBlueprint } from '../blueprints/TranslationBlueprint'; -import { TranslationMessages, TranslationResource } from '../translation'; -import { createExtension } from '../wiring'; - -/** - * @public - * @deprecated Use {@link TranslationBlueprint} instead. - */ -export function createTranslationExtension(options: { - name?: string; - resource: TranslationResource | TranslationMessages; -}) { - return createExtension({ - kind: 'translation', - namespace: options.resource.id, - name: options.name, - attachTo: { id: 'app', input: 'translations' }, - output: { - resource: TranslationBlueprint.dataRefs.translation, - }, - factory: () => ({ resource: options.resource }), - }); -} - -/** - * @public - * @deprecated Use {@link TranslationBlueprint} instead. - */ -export namespace createTranslationExtension { - export const translationDataRef = TranslationBlueprint.dataRefs.translation; -} diff --git a/packages/frontend-plugin-api/src/extensions/index.ts b/packages/frontend-plugin-api/src/extensions/index.ts index 562cb728cb..f75c7474ac 100644 --- a/packages/frontend-plugin-api/src/extensions/index.ts +++ b/packages/frontend-plugin-api/src/extensions/index.ts @@ -14,14 +14,4 @@ * limitations under the License. */ -export { createApiExtension } from './createApiExtension'; -export { createAppRootElementExtension } from './createAppRootElementExtension'; -export { createAppRootWrapperExtension } from './createAppRootWrapperExtension'; -export { createRouterExtension } from './createRouterExtension'; -export { createPageExtension } from './createPageExtension'; -export { createNavItemExtension } from './createNavItemExtension'; -export { createNavLogoExtension } from './createNavLogoExtension'; -export { createSignInPageExtension } from './createSignInPageExtension'; -export { createThemeExtension } from './createThemeExtension'; export { createComponentExtension } from './createComponentExtension'; -export { createTranslationExtension } from './createTranslationExtension'; From 44ea4b9571f8248fb00a7da95c7060d805b5ac22 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 15:02:21 +0200 Subject: [PATCH 09/16] frontend-test-utils: update to reference dataRefs via blueprint Signed-off-by: Patrik Oldsberg --- packages/frontend-test-utils/src/app/renderInTestApp.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index b37a7c2a8d..8bbe2d058c 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -29,8 +29,8 @@ import { useRouteRef, createExtensionInput, IconComponent, - createNavItemExtension, RouterBlueprint, + NavItemBlueprint, } from '@backstage/frontend-plugin-api'; /** @@ -86,7 +86,7 @@ export const TestAppNavExtension = createExtension({ name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, inputs: { - items: createExtensionInput([createNavItemExtension.targetDataRef]), + items: createExtensionInput([NavItemBlueprint.dataRefs.target]), }, output: [coreExtensionData.reactElement], factory({ inputs }) { @@ -96,7 +96,7 @@ export const TestAppNavExtension = createExtension({
    {inputs.items.map((item, index) => { const { icon, title, routeRef } = item.get( - createNavItemExtension.targetDataRef, + NavItemBlueprint.dataRefs.target, ); return ( From bf92da98bc14a6cb963e49ec60db41f6810dad16 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 15:11:35 +0200 Subject: [PATCH 10/16] catalog-react: remove deprecated extension creators Signed-off-by: Patrik Oldsberg --- .../alpha/blueprints/EntityCardBlueprint.ts | 23 +- .../blueprints/EntityContentBlueprint.ts | 30 +-- .../src/alpha/blueprints/extensionData.tsx | 34 +++ .../catalog-react/src/alpha/extensions.tsx | 213 ------------------ plugins/catalog-react/src/alpha/index.ts | 4 +- 5 files changed, 67 insertions(+), 237 deletions(-) create mode 100644 plugins/catalog-react/src/alpha/blueprints/extensionData.tsx delete mode 100644 plugins/catalog-react/src/alpha/extensions.tsx diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts index d13fd2f2c0..82ff2df44a 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts +++ b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts @@ -19,7 +19,10 @@ import { coreExtensionData, createExtensionBlueprint, } from '@backstage/frontend-plugin-api'; -import { catalogExtensionData } from '../extensions'; +import { + entityFilterFunctionDataRef, + entityFilterExpressionDataRef, +} from './extensionData'; /** * @alpha @@ -30,12 +33,12 @@ export const EntityCardBlueprint = createExtensionBlueprint({ attachTo: { id: 'entity-content:catalog/overview', input: 'cards' }, output: [ coreExtensionData.reactElement, - catalogExtensionData.entityFilterFunction.optional(), - catalogExtensionData.entityFilterExpression.optional(), + entityFilterFunctionDataRef.optional(), + entityFilterExpressionDataRef.optional(), ], dataRefs: { - filterFunction: catalogExtensionData.entityFilterFunction, - filterExpression: catalogExtensionData.entityFilterExpression, + filterFunction: entityFilterFunctionDataRef, + filterExpression: entityFilterExpressionDataRef, }, config: { schema: { @@ -49,19 +52,19 @@ export const EntityCardBlueprint = createExtensionBlueprint({ }: { loader: () => Promise; filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; + | typeof entityFilterFunctionDataRef.T + | typeof entityFilterExpressionDataRef.T; }, { node, config }, ) { yield coreExtensionData.reactElement(ExtensionBoundary.lazy(node, loader)); if (config.filter) { - yield catalogExtensionData.entityFilterExpression(config.filter); + yield entityFilterExpressionDataRef(config.filter); } else if (typeof filter === 'string') { - yield catalogExtensionData.entityFilterExpression(filter); + yield entityFilterExpressionDataRef(filter); } else if (typeof filter === 'function') { - yield catalogExtensionData.entityFilterFunction(filter); + yield entityFilterFunctionDataRef(filter); } }, }); diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts index 36bca99ecc..54cd476a90 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts @@ -20,7 +20,11 @@ import { ExtensionBoundary, RouteRef, } from '@backstage/frontend-plugin-api'; -import { catalogExtensionData } from '../extensions'; +import { + entityContentTitleDataRef, + entityFilterFunctionDataRef, + entityFilterExpressionDataRef, +} from './extensionData'; /** * @alpha @@ -32,15 +36,15 @@ export const EntityContentBlueprint = createExtensionBlueprint({ output: [ coreExtensionData.reactElement, coreExtensionData.routePath, - catalogExtensionData.entityContentTitle, + entityContentTitleDataRef, coreExtensionData.routeRef.optional(), - catalogExtensionData.entityFilterFunction.optional(), - catalogExtensionData.entityFilterExpression.optional(), + entityFilterFunctionDataRef.optional(), + entityFilterExpressionDataRef.optional(), ], dataRefs: { - title: catalogExtensionData.entityContentTitle, - filterFunction: catalogExtensionData.entityFilterFunction, - filterExpression: catalogExtensionData.entityFilterExpression, + title: entityContentTitleDataRef, + filterFunction: entityFilterFunctionDataRef, + filterExpression: entityFilterExpressionDataRef, }, config: { schema: { @@ -62,8 +66,8 @@ export const EntityContentBlueprint = createExtensionBlueprint({ defaultTitle: string; routeRef?: RouteRef; filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; + | typeof entityFilterFunctionDataRef.T + | typeof entityFilterExpressionDataRef.T; }, { node, config }, ) { @@ -74,18 +78,18 @@ export const EntityContentBlueprint = createExtensionBlueprint({ yield coreExtensionData.routePath(path); - yield catalogExtensionData.entityContentTitle(title); + yield entityContentTitleDataRef(title); if (routeRef) { yield coreExtensionData.routeRef(routeRef); } if (config.filter) { - yield catalogExtensionData.entityFilterExpression(config.filter); + yield entityFilterExpressionDataRef(config.filter); } else if (typeof filter === 'string') { - yield catalogExtensionData.entityFilterExpression(filter); + yield entityFilterExpressionDataRef(filter); } else if (typeof filter === 'function') { - yield catalogExtensionData.entityFilterFunction(filter); + yield entityFilterFunctionDataRef(filter); } }, }); diff --git a/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx b/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx new file mode 100644 index 0000000000..09aac809cb --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx @@ -0,0 +1,34 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; +import { createExtensionDataRef } from '@backstage/frontend-plugin-api'; + +/** @internal */ +export const entityContentTitleDataRef = createExtensionDataRef().with({ + id: 'catalog.entity-content-title', +}); + +/** @internal */ +export const entityFilterFunctionDataRef = createExtensionDataRef< + (entity: Entity) => boolean +>().with({ id: 'catalog.entity-filter-function' }); + +/** @internal */ +export const entityFilterExpressionDataRef = + createExtensionDataRef().with({ + id: 'catalog.entity-filter-expression', + }); diff --git a/plugins/catalog-react/src/alpha/extensions.tsx b/plugins/catalog-react/src/alpha/extensions.tsx deleted file mode 100644 index e527edc355..0000000000 --- a/plugins/catalog-react/src/alpha/extensions.tsx +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - AnyExtensionInputMap, - ExtensionBoundary, - PortableSchema, - ResolvedExtensionInputs, - RouteRef, - coreExtensionData, - createExtension, - createExtensionDataRef, - createSchemaFromZod, -} from '@backstage/frontend-plugin-api'; -import React, { lazy } from 'react'; -import { Entity } from '@backstage/catalog-model'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { Expand } from '../../../../packages/frontend-plugin-api/src/types'; - -export { useEntityPermission } from '../hooks/useEntityPermission'; -export { isOwnerOf } from '../utils'; -export * from '../translation'; - -/** - * @alpha - * @deprecated use `dataRefs` property on either {@link EntityCardBlueprint} or {@link EntityContentBlueprint} instead - */ -export const catalogExtensionData = { - entityContentTitle: createExtensionDataRef().with({ - id: 'catalog.entity-content-title', - }), - entityFilterFunction: createExtensionDataRef< - (entity: Entity) => boolean - >().with({ id: 'catalog.entity-filter-function' }), - entityFilterExpression: createExtensionDataRef().with({ - id: 'catalog.entity-filter-expression', - }), -}; - -// TODO: Figure out how to merge with provided config schema -/** - * @alpha - * @deprecated use {@link EntityCardBlueprint} instead - */ -export function createEntityCardExtension< - TConfig extends { filter?: string }, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - configSchema?: PortableSchema; - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise; -}) { - const configSchema = - 'configSchema' in options - ? options.configSchema - : (createSchemaFromZod(z => - z.object({ - filter: z.string().optional(), - }), - ) as PortableSchema); - return createExtension({ - kind: 'entity-card', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { - id: 'entity-content:catalog/overview', - input: 'cards', - }, - disabled: options.disabled, - output: { - element: coreExtensionData.reactElement, - filterFunction: catalogExtensionData.entityFilterFunction.optional(), - filterExpression: catalogExtensionData.entityFilterExpression.optional(), - }, - inputs: options.inputs, - configSchema, - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ inputs, config }) - .then(element => ({ default: () => element })), - ); - - return { - element: ( - - - - ), - ...mergeFilters({ config, options }), - }; - }, - }); -} - -/** - * @alpha - * @deprecated use {@link EntityContentBlueprint} instead - */ -export function createEntityContentExtension< - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - routeRef?: RouteRef; - defaultPath: string; - defaultTitle: string; - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - loader: (options: { - inputs: Expand>; - }) => Promise; -}) { - return createExtension({ - kind: 'entity-content', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { - id: 'page:catalog/entity', - input: 'contents', - }, - disabled: options.disabled, - output: { - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath, - routeRef: coreExtensionData.routeRef.optional(), - title: catalogExtensionData.entityContentTitle, - filterFunction: catalogExtensionData.entityFilterFunction.optional(), - filterExpression: catalogExtensionData.entityFilterExpression.optional(), - }, - inputs: options.inputs, - configSchema: createSchemaFromZod(z => - z.object({ - path: z.string().default(options.defaultPath), - title: z.string().default(options.defaultTitle), - filter: z.string().optional(), - }), - ), - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ inputs }) - .then(element => ({ default: () => element })), - ); - - return { - path: config.path, - title: config.title, - routeRef: options.routeRef, - element: ( - - - - ), - ...mergeFilters({ config, options }), - }; - }, - }); -} - -/** - * Decides what filter outputs to produce, given some options and config - */ -function mergeFilters(inputs: { - options: { - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - }; - config: { - filter?: string; - }; -}): { - filterFunction?: typeof catalogExtensionData.entityFilterFunction.T; - filterExpression?: typeof catalogExtensionData.entityFilterExpression.T; -} { - const { options, config } = inputs; - if (config.filter) { - return { filterExpression: config.filter }; - } else if (typeof options.filter === 'string') { - return { filterExpression: options.filter }; - } else if (typeof options.filter === 'function') { - return { filterFunction: options.filter }; - } - return {}; -} diff --git a/plugins/catalog-react/src/alpha/index.ts b/plugins/catalog-react/src/alpha/index.ts index 3930fbde87..a46fec0615 100644 --- a/plugins/catalog-react/src/alpha/index.ts +++ b/plugins/catalog-react/src/alpha/index.ts @@ -15,5 +15,7 @@ */ export * from './blueprints'; -export * from './extensions'; export * from './converters'; +export { catalogReactTranslationRef } from '../translation'; +export { isOwnerOf } from '../utils/isOwnerOf'; +export { useEntityPermission } from '../hooks/useEntityPermission'; From 71bf340bee44b06d31543c7a8c825f457cf1ac41 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 15:12:18 +0200 Subject: [PATCH 11/16] catalog: remove deprecated extension creator Signed-off-by: Patrik Oldsberg --- .../alpha/createCatalogFilterExtension.tsx | 66 ------------------- plugins/catalog/src/alpha/index.ts | 1 - 2 files changed, 67 deletions(-) delete mode 100644 plugins/catalog/src/alpha/createCatalogFilterExtension.tsx diff --git a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx b/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx deleted file mode 100644 index 85d911ef3b..0000000000 --- a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { lazy } from 'react'; -import { - AnyExtensionInputMap, - ExtensionBoundary, - PortableSchema, - coreExtensionData, - createExtension, -} from '@backstage/frontend-plugin-api'; - -/** - * @alpha - * @deprecated Use {@link CatalogFilterBlueprint} instead - */ -export function createCatalogFilterExtension< - TInputs extends AnyExtensionInputMap, - TConfig, ->(options: { - namespace?: string; - name?: string; - inputs?: TInputs; - configSchema?: PortableSchema; - loader: (options: { config: TConfig }) => Promise; -}) { - return createExtension({ - kind: 'catalog-filter', - namespace: options.namespace, - name: options.name, - attachTo: { id: 'page:catalog', input: 'filters' }, - inputs: options.inputs ?? {}, - configSchema: options.configSchema, - output: { - element: coreExtensionData.reactElement, - }, - factory({ config, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ config }) - .then(element => ({ default: () => element })), - ); - - return { - element: ( - - - - ), - }; - }, - }); -} diff --git a/plugins/catalog/src/alpha/index.ts b/plugins/catalog/src/alpha/index.ts index f004c4f19e..3357024fd9 100644 --- a/plugins/catalog/src/alpha/index.ts +++ b/plugins/catalog/src/alpha/index.ts @@ -15,7 +15,6 @@ */ export { default } from './plugin'; -export { createCatalogFilterExtension } from './createCatalogFilterExtension'; export * from './blueprints'; export * from './translation'; From d0501bb8c267fdbc871b0deeb197df6141a6794e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 15:13:38 +0200 Subject: [PATCH 12/16] search-react: removed deprecated extension creators Signed-off-by: Patrik Oldsberg --- .../src/alpha/extensions.test.tsx | 171 ------------------ plugins/search-react/src/alpha/extensions.tsx | 133 -------------- plugins/search-react/src/alpha/index.ts | 1 - 3 files changed, 305 deletions(-) delete mode 100644 plugins/search-react/src/alpha/extensions.test.tsx delete mode 100644 plugins/search-react/src/alpha/extensions.tsx diff --git a/plugins/search-react/src/alpha/extensions.test.tsx b/plugins/search-react/src/alpha/extensions.test.tsx deleted file mode 100644 index 5194fadd1c..0000000000 --- a/plugins/search-react/src/alpha/extensions.test.tsx +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createExtensionInput, - createPageExtension, - createSchemaFromZod, -} from '@backstage/frontend-plugin-api'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { SearchResult } from '@backstage/plugin-search-common'; -import { screen } from '@testing-library/react'; -import React from 'react'; -import { createSearchResultListItemExtension } from './extensions'; -import { BaseSearchResultListItemProps } from './blueprints'; - -describe('createSearchResultListItemExtension', () => { - it('Should use the correct result component', async () => { - type TechDocsSearchResultListItemProps = BaseSearchResultListItemProps<{ - lineClamp: number; - }>; - const TechDocsSearchResultItemComponent = ( - props: TechDocsSearchResultListItemProps, - ) => ( -
    - TechDocs - Rank: {props.rank} - Line clamp: {props.lineClamp} -
    - ); - - const TechDocsSearchResultItemExtension = - createSearchResultListItemExtension({ - namespace: 'techdocs', - configSchema: createSchemaFromZod(z => - z - .object({ - noTrack: z.boolean().default(true), - lineClamp: z.number().default(5), - }) - .default({}), - ), - predicate: result => result.type === 'techdocs', - component: - async ({ config }) => - props => - , - }); - - const ExploreSearchResultItemComponent = ( - props: BaseSearchResultListItemProps, - ) =>
    Explore - Rank: {props.rank}
    ; - - const ExploreSearchResultItemExtension = - createSearchResultListItemExtension({ - namespace: 'explore', - predicate: result => result.type === 'explore', - component: async () => ExploreSearchResultItemComponent, - }); - - const SearchPageExtension = createPageExtension({ - namespace: 'search', - defaultPath: '/', - inputs: { - items: createExtensionInput({ - item: createSearchResultListItemExtension.itemDataRef, - }), - }, - loader: async ({ inputs }) => { - const results = [ - { - type: 'techdocs', - rank: 1, - document: { - title: 'Title1', - text: 'Text1', - location: '/location1', - }, - }, - { - type: 'explore', - rank: 2, - document: { - title: 'Title2', - text: 'Text2', - location: '/location2', - }, - }, - { - type: 'other', - rank: 3, - document: { - title: 'Title3', - text: 'Text3', - location: '/location3', - }, - }, - ]; - - const DefaultResultItem = (props: BaseSearchResultListItemProps) => ( -
    Default - Rank: {props.rank}
    - ); - - const getResultItemComponent = (result: SearchResult) => { - const value = inputs.items.find(item => - item?.output.item.predicate?.(result), - ); - return value?.output.item.component ?? DefaultResultItem; - }; - - const Component = () => { - return ( -
    -

    Search Page

    -
      - {results.map((result, index) => { - const SearchResultListItem = getResultItemComponent(result); - return ( - - ); - })} -
    -
    - ); - }; - - return ; - }, - }); - - createExtensionTester(SearchPageExtension) - .add(TechDocsSearchResultItemExtension, { - // TODO(Rugvip): We need to make the config input type available for use here - config: { - lineClamp: 3, - } as any, - }) - .add(ExploreSearchResultItemExtension) - .render(); - - expect(await screen.findByText(/Search Page/)).toBeInTheDocument(); - - expect( - await screen.findByText(/TechDocs - Rank: 1 - Line clamp: 3/, { - exact: false, - }), - ).toBeInTheDocument(); - - expect( - await screen.findByText(/Explore - Rank: 2/, { exact: false }), - ).toBeInTheDocument(); - - expect( - await screen.findByText(/Default - Rank: 3/, { exact: false }), - ).toBeInTheDocument(); - }); -}); diff --git a/plugins/search-react/src/alpha/extensions.tsx b/plugins/search-react/src/alpha/extensions.tsx deleted file mode 100644 index db09510a1e..0000000000 --- a/plugins/search-react/src/alpha/extensions.tsx +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { lazy } from 'react'; -import { - ExtensionBoundary, - PortableSchema, - createExtension, - createSchemaFromZod, -} from '@backstage/frontend-plugin-api'; -import { - SearchResultItemExtensionComponent, - SearchResultItemExtensionPredicate, -} from './blueprints'; -import { SearchResultListItemExtension } from '../extensions'; -import { searchResultListItemDataRef } from './blueprints/types'; - -/** - * @alpha - * @deprecated Use {@link SearchResultListItemBlueprint} instead - */ -export type SearchResultItemExtensionOptions< - TConfig extends { noTrack?: boolean }, -> = { - /** - * The extension namespace. - */ - namespace?: string; - /** - * The extension name. - */ - name?: string; - /** - * The extension attachment point (e.g., search modal or page). - */ - attachTo?: { id: string; input: string }; - /** - * Optional extension config schema. - */ - configSchema?: PortableSchema; - /** - * The extension component. - */ - component: (options: { - config: TConfig; - }) => Promise; - /** - * When an extension defines a predicate, it returns true if the result should be rendered by that extension. - * Defaults to a predicate that returns true, which means it renders all sorts of results. - */ - predicate?: SearchResultItemExtensionPredicate; -}; - -/** - * Creates items for the search result list. - * - * @alpha - * @deprecated Use {@link SearchResultListItemBlueprint} instead - */ -export function createSearchResultListItemExtension< - TConfig extends { noTrack?: boolean }, ->(options: SearchResultItemExtensionOptions) { - const configSchema = - 'configSchema' in options - ? options.configSchema - : (createSchemaFromZod(z => - z.object({ - noTrack: z.boolean().default(false), - }), - ) as PortableSchema); - - return createExtension({ - kind: 'search-result-list-item', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { - id: 'page:search', - input: 'items', - }, - configSchema, - output: { - item: createSearchResultListItemExtension.itemDataRef, - }, - factory({ config, node }) { - const ExtensionComponent = lazy(() => - options - .component({ config }) - .then(component => ({ default: component })), - ) as unknown as SearchResultItemExtensionComponent; - - return { - item: { - predicate: options.predicate, - component: props => ( - - - - - - ), - }, - }; - }, - }); -} - -/** - * @alpha - * @deprecated Use {@link SearchResultListItemBlueprint} instead - */ -export namespace createSearchResultListItemExtension { - /** - * @deprecated Use {@link SearchResultListItemBlueprint#dataRefs.item} instead - */ - export const itemDataRef = searchResultListItemDataRef; -} diff --git a/plugins/search-react/src/alpha/index.ts b/plugins/search-react/src/alpha/index.ts index 04026d629d..239ffa89dd 100644 --- a/plugins/search-react/src/alpha/index.ts +++ b/plugins/search-react/src/alpha/index.ts @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './extensions'; export * from './blueprints'; From 28311eb02fce0793dded14179b11bd31e3c35fc0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 15:15:29 +0200 Subject: [PATCH 13/16] update API reports for v1 extension removal Signed-off-by: Patrik Oldsberg --- packages/frontend-plugin-api/api-report.md | 496 ++------------------- plugins/catalog-react/api-report-alpha.md | 98 ---- plugins/catalog/api-report-alpha.md | 24 - plugins/search-react/api-report-alpha.md | 53 --- 4 files changed, 30 insertions(+), 641 deletions(-) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index a6ca3f3aff..384c14fb17 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -89,8 +89,6 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { vmwareCloudAuthApiRef } from '@backstage/core-plugin-api'; import { withApis } from '@backstage/core-plugin-api'; import { z } from 'zod'; -import { ZodSchema } from 'zod'; -import { ZodTypeDef } from 'zod'; export { AlertApi }; @@ -147,11 +145,6 @@ export { AnyApiFactory }; export { AnyApiRef }; -// @public @deprecated (undocumented) -export type AnyExtensionDataMap = { - [name in string]: AnyExtensionDataRef; -}; - // @public (undocumented) export type AnyExtensionDataRef = ExtensionDataRef< unknown, @@ -161,17 +154,6 @@ export type AnyExtensionDataRef = ExtensionDataRef< } >; -// @public @deprecated (undocumented) -export type AnyExtensionInputMap = { - [inputName in string]: LegacyExtensionInput< - AnyExtensionDataMap, - { - optional: boolean; - singleton: boolean; - } - >; -}; - // @public (undocumented) export type AnyExternalRoutes = { [name in string]: ExternalRouteRef; @@ -456,141 +438,50 @@ export type CoreNotFoundErrorPageProps = { // @public (undocumented) export type CoreProgressProps = {}; -// @public @deprecated (undocumented) -export function createApiExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->( - options: ( - | { - api: AnyApiRef; - factory: (options: { - config: TConfig; - inputs: Expand>; - }) => AnyApiFactory; - } - | { - factory: AnyApiFactory; - } - ) & { - configSchema?: PortableSchema; - inputs?: TInputs; - }, -): ExtensionDefinition< - TConfig, - TConfig, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @public @deprecated (undocumented) -export namespace createApiExtension { - const // @deprecated (undocumented) - factoryDataRef: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; -} - export { createApiFactory }; export { createApiRef }; -// @public @deprecated -export function createAppRootElementExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - element: - | JSX_2.Element - | ((options: { - inputs: Expand>; - config: TConfig; - }) => JSX_2.Element); -}): ExtensionDefinition; - -// @public @deprecated -export function createAppRootWrapperExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - Component: ComponentType< - PropsWithChildren<{ - inputs: Expand>; - config: TConfig; - }> - >; -}): ExtensionDefinition; - -// @public @deprecated (undocumented) -export namespace createAppRootWrapperExtension { - const // @deprecated (undocumented) - componentDataRef: ConfigurableExtensionDataRef< - React_2.ComponentType<{ - children?: React_2.ReactNode; - }>, - 'app.root.wrapper', - {} - >; -} - // @public (undocumented) -export function createComponentExtension< - TProps extends {}, - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { +export function createComponentExtension(options: { ref: ComponentRef; name?: string; disabled?: boolean; - inputs?: TInputs; - configSchema?: PortableSchema; loader: | { - lazy: (values: { - config: TConfig; - inputs: Expand>; - }) => Promise>; + lazy: () => Promise>; } | { - sync: (values: { - config: TConfig; - inputs: Expand>; - }) => ComponentType; + sync: () => ComponentType; }; }): ExtensionDefinition< - TConfig, - TConfig, - never, - never, { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; + [x: string]: any; + }, + { + [x: string]: any; + }, + ConfigurableExtensionDataRef< + { + ref: ComponentRef; + impl: ComponentType; + }, + 'core.component.component', + {} + >, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + { + kind: 'component'; + namespace: string; + name: string; } >; @@ -659,21 +550,6 @@ export function createExtension< } >; -// @public @deprecated (undocumented) -export function createExtension< - TOutput extends AnyExtensionDataMap, - TInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, ->( - options: LegacyCreateExtensionOptions< - TOutput, - TInputs, - TConfig, - TConfigInput - >, -): ExtensionDefinition; - // @public export function createExtensionBlueprint< TParams, @@ -795,24 +671,6 @@ export function createExtensionDataRef(): { }): ConfigurableExtensionDataRef; }; -// @public @deprecated (undocumented) -export function createExtensionInput< - TExtensionDataMap extends AnyExtensionDataMap, - TConfig extends { - singleton?: boolean; - optional?: boolean; - }, ->( - extensionData: TExtensionDataMap, - config?: TConfig, -): LegacyExtensionInput< - TExtensionDataMap, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } ->; - // @public (undocumented) export function createExtensionInput< UExtensionData extends ExtensionDataRef< @@ -926,105 +784,6 @@ export function createFrontendPlugin< } >; -// @public @deprecated -export function createNavItemExtension(options: { - namespace?: string; - name?: string; - routeRef: RouteRef; - title: string; - icon: IconComponent_2; -}): ExtensionDefinition< - { - title: string; - }, - { - title?: string | undefined; - }, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @public @deprecated (undocumented) -export namespace createNavItemExtension { - const // @deprecated (undocumented) - targetDataRef: ConfigurableExtensionDataRef< - { - title: string; - icon: IconComponent_2; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; -} - -// @public @deprecated -export function createNavLogoExtension(options: { - name?: string; - namespace?: string; - logoIcon: JSX.Element; - logoFull: JSX.Element; -}): ExtensionDefinition< - unknown, - unknown, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @public @deprecated (undocumented) -export namespace createNavLogoExtension { - const // @deprecated (undocumented) - logoElementsDataRef: ConfigurableExtensionDataRef< - { - logoIcon?: JSX.Element | undefined; - logoFull?: JSX.Element | undefined; - }, - 'core.nav-logo.logo-elements', - {} - >; -} - -// @public @deprecated -export function createPageExtension< - TConfig extends { - path: string; - }, - TInputs extends AnyExtensionInputMap, ->( - options: ( - | { - defaultPath: string; - } - | { - configSchema: PortableSchema; - } - ) & { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - disabled?: boolean; - inputs?: TInputs; - routeRef?: RouteRef; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise; - }, -): ExtensionDefinition; - // @public @deprecated (undocumented) export const createPlugin: typeof createFrontendPlugin; @@ -1048,75 +807,6 @@ export function createRouteRef< } >; -// @public @deprecated -export function createRouterExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - Component: ComponentType< - PropsWithChildren<{ - inputs: Expand>; - config: TConfig; - }> - >; -}): ExtensionDefinition; - -// @public @deprecated (undocumented) -export namespace createRouterExtension { - const // @deprecated (undocumented) - componentDataRef: ConfigurableExtensionDataRef< - React_2.ComponentType<{ - children?: React_2.ReactNode; - }>, - 'app.router.wrapper', - {} - >; -} - -// @public @deprecated (undocumented) -export function createSchemaFromZod( - schemaCreator: (zImpl: typeof z) => ZodSchema, -): PortableSchema; - -// @public @deprecated (undocumented) -export function createSignInPageExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise>; -}): ExtensionDefinition; - -// @public @deprecated (undocumented) -export namespace createSignInPageExtension { - const // @deprecated (undocumented) - componentDataRef: ConfigurableExtensionDataRef< - React_2.ComponentType, - 'core.sign-in-page.component', - {} - >; -} - // @public export function createSubRouteRef< Path extends string, @@ -1126,62 +816,6 @@ export function createSubRouteRef< parent: RouteRef; }): MakeSubRouteRef, ParentParams>; -// @public @deprecated (undocumented) -export function createThemeExtension(theme: AppTheme): ExtensionDefinition< - unknown, - unknown, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @public @deprecated (undocumented) -export namespace createThemeExtension { - const // (undocumented) - themeDataRef: ConfigurableExtensionDataRef< - AppTheme, - 'core.theme.theme', - {} - >; -} - -// @public @deprecated (undocumented) -export function createTranslationExtension(options: { - name?: string; - resource: TranslationResource | TranslationMessages; -}): ExtensionDefinition< - unknown, - unknown, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @public @deprecated (undocumented) -export namespace createTranslationExtension { - const // (undocumented) - translationDataRef: ConfigurableExtensionDataRef< - | TranslationResource - | TranslationMessages< - string, - { - [x: string]: string; - }, - boolean - >, - 'core.translation.translation', - {} - >; -} - export { createTranslationMessages }; export { createTranslationRef }; @@ -1429,21 +1063,6 @@ export type ExtensionDataValue = { readonly value: TData; }; -// @public @deprecated -export type ExtensionDataValues = { - [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends { - optional: true; - } - ? never - : DataName]: TExtensionData[DataName]['T']; -} & { - [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends { - optional: true; - } - ? DataName - : never]?: TExtensionData[DataName]['T']; -}; - // @public (undocumented) export interface ExtensionDefinition< TConfig, @@ -1696,56 +1315,6 @@ export { IdentityApi }; export { identityApiRef }; -// @public @deprecated (undocumented) -export interface LegacyCreateExtensionOptions< - TOutput extends AnyExtensionDataMap, - TInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, -> { - // (undocumented) - attachTo: { - id: string; - input: string; - }; - // (undocumented) - configSchema?: PortableSchema; - // (undocumented) - disabled?: boolean; - // (undocumented) - factory(context: { - node: AppNode; - config: TConfig; - inputs: Expand>; - }): Expand>; - // (undocumented) - inputs?: TInputs; - // (undocumented) - kind?: string; - // (undocumented) - name?: string; - // (undocumented) - namespace?: string; - // (undocumented) - output: TOutput; -} - -// @public @deprecated (undocumented) -export interface LegacyExtensionInput< - TExtensionDataMap extends AnyExtensionDataMap, - TConfig extends { - singleton: boolean; - optional: boolean; - }, -> { - // (undocumented) - $$type: '@backstage/ExtensionInput'; - // (undocumented) - config: TConfig; - // (undocumented) - extensionData: TExtensionDataMap; -} - export { microsoftAuthApiRef }; // @public @@ -1906,17 +1475,12 @@ export type ResolvedExtensionInput< ? { node: AppNode; } & ExtensionDataContainer - : TExtensionInput['extensionData'] extends AnyExtensionDataMap - ? { - node: AppNode; - output: ExtensionDataValues; - } : never; // @public export type ResolvedExtensionInputs< TInputs extends { - [name in string]: ExtensionInput | LegacyExtensionInput; + [name in string]: ExtensionInput; }, > = { [InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton'] diff --git a/plugins/catalog-react/api-report-alpha.md b/plugins/catalog-react/api-report-alpha.md index e5af309ba1..b22d76cd15 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -5,7 +5,6 @@ ```ts /// -import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; @@ -13,31 +12,10 @@ import { Entity } from '@backstage/catalog-model'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; -import { PortableSchema } from '@backstage/frontend-plugin-api'; -import { ResolvedExtensionInputs } from '@backstage/frontend-plugin-api'; import { ResourcePermission } from '@backstage/plugin-permission-common'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; -// @alpha @deprecated (undocumented) -export const catalogExtensionData: { - entityContentTitle: ConfigurableExtensionDataRef< - string, - 'catalog.entity-content-title', - {} - >; - entityFilterFunction: ConfigurableExtensionDataRef< - (entity: Entity) => boolean, - 'catalog.entity-filter-function', - {} - >; - entityFilterExpression: ConfigurableExtensionDataRef< - string, - 'catalog.entity-filter-expression', - {} - >; -}; - // @alpha (undocumented) export const catalogReactTranslationRef: TranslationRef< 'catalog-react', @@ -120,82 +98,6 @@ export function convertLegacyEntityContentExtension( }, ): ExtensionDefinition; -// @alpha @deprecated (undocumented) -export function createEntityCardExtension< - TConfig extends { - filter?: string; - }, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - disabled?: boolean; - inputs?: TInputs; - configSchema?: PortableSchema; - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise; -}): ExtensionDefinition< - TConfig, - TConfig, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @alpha @deprecated (undocumented) -export function createEntityContentExtension< - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - disabled?: boolean; - inputs?: TInputs; - routeRef?: RouteRef; - defaultPath: string; - defaultTitle: string; - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - loader: (options: { - inputs: Expand>; - }) => Promise; -}): ExtensionDefinition< - { - title: string; - path: string; - filter?: string | undefined; - }, - { - filter?: string | undefined; - title?: string | undefined; - path?: string | undefined; - }, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - // @alpha export const EntityCardBlueprint: ExtensionBlueprint< { diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 806fad5754..42f4355921 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -7,7 +7,6 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; @@ -18,7 +17,6 @@ import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; -import { PortableSchema } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; @@ -126,28 +124,6 @@ export const catalogTranslationRef: TranslationRef< } >; -// @alpha @deprecated (undocumented) -export function createCatalogFilterExtension< - TInputs extends AnyExtensionInputMap, - TConfig, ->(options: { - namespace?: string; - name?: string; - inputs?: TInputs; - configSchema?: PortableSchema; - loader: (options: { config: TConfig }) => Promise; -}): ExtensionDefinition< - TConfig, - TConfig, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - // @alpha (undocumented) const _default: BackstagePlugin< { diff --git a/plugins/search-react/api-report-alpha.md b/plugins/search-react/api-report-alpha.md index bce620c378..57bf03114f 100644 --- a/plugins/search-react/api-report-alpha.md +++ b/plugins/search-react/api-report-alpha.md @@ -7,9 +7,7 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; -import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ListItemProps } from '@material-ui/core/ListItem'; -import { PortableSchema } from '@backstage/frontend-plugin-api'; import { SearchDocument } from '@backstage/plugin-search-common'; import { SearchResult } from '@backstage/plugin-search-common'; @@ -19,38 +17,6 @@ export type BaseSearchResultListItemProps = T & { result?: SearchDocument; } & Omit; -// @alpha @deprecated -export function createSearchResultListItemExtension< - TConfig extends { - noTrack?: boolean; - }, ->( - options: SearchResultItemExtensionOptions, -): ExtensionDefinition< - TConfig, - TConfig, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @alpha @deprecated (undocumented) -export namespace createSearchResultListItemExtension { - const // @deprecated (undocumented) - itemDataRef: ConfigurableExtensionDataRef< - { - predicate?: SearchResultItemExtensionPredicate | undefined; - component: SearchResultItemExtensionComponent; - }, - 'search.search-result-list-item.item', - {} - >; -} - // @alpha (undocumented) export type SearchResultItemExtensionComponent = < P extends BaseSearchResultListItemProps, @@ -58,25 +24,6 @@ export type SearchResultItemExtensionComponent = < props: P, ) => JSX.Element | null; -// @alpha @deprecated (undocumented) -export type SearchResultItemExtensionOptions< - TConfig extends { - noTrack?: boolean; - }, -> = { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - component: (options: { - config: TConfig; - }) => Promise; - predicate?: SearchResultItemExtensionPredicate; -}; - // @alpha (undocumented) export type SearchResultItemExtensionPredicate = ( result: SearchResult, From 54460616ff81885d9333c1a4de5f46fcf8a5e401 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 15:27:17 +0200 Subject: [PATCH 14/16] changesets: add changesets for v1 extension removal Signed-off-by: Patrik Oldsberg --- .changeset/brown-boxes-arrive.md | 5 +++++ .changeset/empty-coats-sparkle.md | 5 +++++ .changeset/serious-cheetahs-help.md | 7 +++++++ .changeset/sharp-mayflies-beg.md | 5 +++++ 4 files changed, 22 insertions(+) create mode 100644 .changeset/brown-boxes-arrive.md create mode 100644 .changeset/empty-coats-sparkle.md create mode 100644 .changeset/serious-cheetahs-help.md create mode 100644 .changeset/sharp-mayflies-beg.md diff --git a/.changeset/brown-boxes-arrive.md b/.changeset/brown-boxes-arrive.md new file mode 100644 index 0000000000..edefc0dcaf --- /dev/null +++ b/.changeset/brown-boxes-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Removed support for "v1" extensions. This means that it is no longer possible to declare inputs and outputs as objects when using `createExtension`. In addition, all extension creators except for `createComponentExtension` have been removed, use the equivalent blueprint instead. See the [1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations/#130) for more information on this change. diff --git a/.changeset/empty-coats-sparkle.md b/.changeset/empty-coats-sparkle.md new file mode 100644 index 0000000000..7e3b791c6b --- /dev/null +++ b/.changeset/empty-coats-sparkle.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': minor +--- + +Removed support for testing "v1" extensions, where outputs are defined as an object rather than an array. diff --git a/.changeset/serious-cheetahs-help.md b/.changeset/serious-cheetahs-help.md new file mode 100644 index 0000000000..fa9debcf8c --- /dev/null +++ b/.changeset/serious-cheetahs-help.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-search-react': patch +'@backstage/plugin-catalog': patch +--- + +The `/alpha` export no longer export extension creators for the new frontend system, existing usage should be switched to use the equivalent extension blueprint instead. diff --git a/.changeset/sharp-mayflies-beg.md b/.changeset/sharp-mayflies-beg.md new file mode 100644 index 0000000000..c78335c187 --- /dev/null +++ b/.changeset/sharp-mayflies-beg.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Internal refactor following removal of v1 extension support. The app implementation itself still supports v1 extensions at runtime. From 9bd5088b4a35011f70026a78e76823ef3a995726 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 20 Aug 2024 12:14:12 +0200 Subject: [PATCH 15/16] changesets: link to 1.30 migration docs Signed-off-by: Patrik Oldsberg --- .changeset/serious-cheetahs-help.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/serious-cheetahs-help.md b/.changeset/serious-cheetahs-help.md index fa9debcf8c..88bad696c5 100644 --- a/.changeset/serious-cheetahs-help.md +++ b/.changeset/serious-cheetahs-help.md @@ -4,4 +4,4 @@ '@backstage/plugin-catalog': patch --- -The `/alpha` export no longer export extension creators for the new frontend system, existing usage should be switched to use the equivalent extension blueprint instead. +The `/alpha` export no longer export extension creators for the new frontend system, existing usage should be switched to use the equivalent extension blueprint instead. For more information see the [new frontend system 1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#130). From acf0e68263fd2367d9ab56787551fb5adfc09c6e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 20 Aug 2024 12:15:03 +0200 Subject: [PATCH 16/16] frontend-plugin-api: update test snapshots for createExtensionOverrides Signed-off-by: Patrik Oldsberg --- .../src/wiring/createExtensionOverrides.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts index ef95f82e25..2219f963ed 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts @@ -74,9 +74,9 @@ describe('createExtensionOverrides', () => { "factory": [Function], "id": "a", "inputs": {}, - "output": {}, + "output": [], "toString": [Function], - "version": "v1", + "version": "v2", }, { "$$type": "@backstage/Extension", @@ -89,9 +89,9 @@ describe('createExtensionOverrides', () => { "factory": [Function], "id": "b", "inputs": {}, - "output": {}, + "output": [], "toString": [Function], - "version": "v1", + "version": "v2", }, { "$$type": "@backstage/Extension", @@ -104,9 +104,9 @@ describe('createExtensionOverrides', () => { "factory": [Function], "id": "k:c/n", "inputs": {}, - "output": {}, + "output": [], "toString": [Function], - "version": "v1", + "version": "v2", }, ], "featureFlags": [],