From b2d370ee7ef4776b77e3a07a3f7dcc93187b7ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 10 Jan 2024 14:06:38 +0100 Subject: [PATCH] ensure that things stringify nicely overall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/calm-grapes-remember.md | 5 +++ .../src/tree/resolveAppNodeSpecs.test.ts | 1 + packages/frontend-plugin-api/api-report.md | 14 ++++++++ .../src/components/ExtensionBoundary.tsx | 2 +- ...{ComponentRef.tsx => coreComponentRefs.ts} | 17 +--------- .../components/createComponentRef.test.tsx | 25 +++++++++++++++ .../src/components/createComponentRef.tsx | 32 +++++++++++++++++++ .../src/components/index.ts | 4 +-- .../src/extensions/createApiExtension.test.ts | 2 ++ .../createNavLogoExtension.test.tsx | 1 + .../extensions/createPageExtension.test.tsx | 3 ++ .../createTranslationExtension.test.ts | 3 ++ .../frontend-plugin-api/src/setupTests.ts | 2 ++ .../src/wiring/createExtension.test.ts | 3 ++ .../src/wiring/createExtension.ts | 16 ++++++++++ .../src/wiring/createExtensionDataRef.test.ts | 28 ++++++++++++++++ .../src/wiring/createExtensionDataRef.ts | 12 +++++-- .../wiring/createExtensionOverrides.test.ts | 5 +++ .../src/wiring/createExtensionOverrides.ts | 13 ++++++-- .../src/wiring/createPlugin.test.ts | 1 + .../src/wiring/createPlugin.ts | 3 ++ .../wiring/resolveExtensionDefinition.test.ts | 1 + .../src/wiring/resolveExtensionDefinition.ts | 10 ++++-- .../frontend-plugin-api/src/wiring/types.ts | 2 ++ 24 files changed, 180 insertions(+), 25 deletions(-) create mode 100644 .changeset/calm-grapes-remember.md rename packages/frontend-plugin-api/src/components/{ComponentRef.tsx => coreComponentRefs.ts} (82%) create mode 100644 packages/frontend-plugin-api/src/components/createComponentRef.test.tsx create mode 100644 packages/frontend-plugin-api/src/components/createComponentRef.tsx create mode 100644 packages/frontend-plugin-api/src/wiring/createExtensionDataRef.test.ts diff --git a/.changeset/calm-grapes-remember.md b/.changeset/calm-grapes-remember.md new file mode 100644 index 0000000000..f21a074c74 --- /dev/null +++ b/.changeset/calm-grapes-remember.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Exposed `createComponentRef`, and ensured that produced refs and feature bits have a `toString` for easier debugging diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts index 8dfa7aeea9..82f274b960 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts @@ -33,6 +33,7 @@ function makeExt( id, attachTo: { id: attachId, input: 'default' }, disabled: status === 'disabled', + toString: expect.any(Function), } as Extension; } diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 46599bd6c4..7b0c182003 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -280,6 +280,8 @@ export interface BackstagePlugin< readonly id: string; // (undocumented) readonly routes: Routes; + // (undocumented) + toString(): string; } export { BackstageUserIdentity }; @@ -428,6 +430,11 @@ export namespace createComponentExtension { >; } +// @public (undocumented) +export function createComponentRef(options: { + id: string; +}): ComponentRef; + // @public (undocumented) export function createExtension< TOutput extends AnyExtensionDataMap, @@ -735,6 +742,8 @@ export interface Extension { readonly disabled: boolean; // (undocumented) readonly id: string; + // (undocumented) + toString(): string; } // @public (undocumented) @@ -763,6 +772,7 @@ export type ExtensionDataRef< T: TData; config: TConfig; $$type: '@backstage/ExtensionDataRef'; + toString(): string; }; // @public @@ -799,6 +809,8 @@ export interface ExtensionDefinition { readonly name?: string; // (undocumented) readonly namespace?: string; + // (undocumented) + toString(): string; } // @public (undocumented) @@ -821,6 +833,8 @@ export interface ExtensionInput< export interface ExtensionOverrides { // (undocumented) readonly $$type: '@backstage/ExtensionOverrides'; + // (undocumented) + toString(): string; } // @public (undocumented) diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx index d9bd41f2a9..9a8eb5b750 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx @@ -25,7 +25,7 @@ import { ErrorBoundary } from './ErrorBoundary'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker'; import { AppNode, useComponentRef } from '../apis'; -import { coreComponentRefs } from './ComponentRef'; +import { coreComponentRefs } from './coreComponentRefs'; type RouteTrackerProps = PropsWithChildren<{ disableTracking?: boolean; diff --git a/packages/frontend-plugin-api/src/components/ComponentRef.tsx b/packages/frontend-plugin-api/src/components/coreComponentRefs.ts similarity index 82% rename from packages/frontend-plugin-api/src/components/ComponentRef.tsx rename to packages/frontend-plugin-api/src/components/coreComponentRefs.ts index 50df9552ec..64412b8710 100644 --- a/packages/frontend-plugin-api/src/components/ComponentRef.tsx +++ b/packages/frontend-plugin-api/src/components/coreComponentRefs.ts @@ -19,22 +19,7 @@ import { CoreNotFoundErrorPageProps, CoreProgressProps, } from '../types'; - -/** @public */ -export type ComponentRef = { - id: string; - T: T; -}; - -/** @public */ -export function createComponentRef(options: { - id: string; -}): ComponentRef { - const { id } = options; - return { - id, - } as ComponentRef; -} +import { createComponentRef } from './createComponentRef'; const coreProgressComponentRef = createComponentRef({ id: 'core.components.progress', diff --git a/packages/frontend-plugin-api/src/components/createComponentRef.test.tsx b/packages/frontend-plugin-api/src/components/createComponentRef.test.tsx new file mode 100644 index 0000000000..e66c026c5c --- /dev/null +++ b/packages/frontend-plugin-api/src/components/createComponentRef.test.tsx @@ -0,0 +1,25 @@ +/* + * 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 { createComponentRef } from './createComponentRef'; + +describe('createComponentRef', () => { + it('can be created and read', () => { + const ref = createComponentRef({ id: 'foo' }); + expect(ref.id).toBe('foo'); + expect(String(ref)).toBe('componentRef{id=foo}'); + }); +}); diff --git a/packages/frontend-plugin-api/src/components/createComponentRef.tsx b/packages/frontend-plugin-api/src/components/createComponentRef.tsx new file mode 100644 index 0000000000..c868c12eb3 --- /dev/null +++ b/packages/frontend-plugin-api/src/components/createComponentRef.tsx @@ -0,0 +1,32 @@ +/* + * 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. + */ + +/** @public */ +export type ComponentRef = { + id: string; + T: T; +}; + +/** @public */ +export function createComponentRef(options: { + id: string; +}): ComponentRef { + const { id } = options; + return { + id, + toString: () => `componentRef{id=${id}}`, + } as ComponentRef; +} diff --git a/packages/frontend-plugin-api/src/components/index.ts b/packages/frontend-plugin-api/src/components/index.ts index 893918059b..5066144000 100644 --- a/packages/frontend-plugin-api/src/components/index.ts +++ b/packages/frontend-plugin-api/src/components/index.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -export { coreComponentRefs, type ComponentRef } from './ComponentRef'; - export { ExtensionBoundary, type ExtensionBoundaryProps, } from './ExtensionBoundary'; +export { coreComponentRefs } from './coreComponentRefs'; +export { createComponentRef, type ComponentRef } from './createComponentRef'; diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts index 0636d0d79d..c4d0239094 100644 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts @@ -47,6 +47,7 @@ describe('createApiExtension', () => { }), }, factory: expect.any(Function), + toString: expect.any(Function), }); }); @@ -83,6 +84,7 @@ describe('createApiExtension', () => { }), }, factory: expect.any(Function), + toString: expect.any(Function), }); }); }); diff --git a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx index 90e1448448..8c54743f1d 100644 --- a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx @@ -41,6 +41,7 @@ describe('createNavLogoExtension', () => { logos: expect.anything(), }, factory: expect.any(Function), + toString: expect.any(Function), }); }); }); diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx index 2de72296ac..374400fce8 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx @@ -55,6 +55,7 @@ describe('createPageExtension', () => { routeRef: expect.anything(), }, factory: expect.any(Function), + toString: expect.any(Function), }); expect( @@ -89,6 +90,7 @@ describe('createPageExtension', () => { routeRef: expect.anything(), }, factory: expect.any(Function), + toString: expect.any(Function), }); expect( @@ -112,6 +114,7 @@ describe('createPageExtension', () => { routeRef: expect.anything(), }, factory: expect.any(Function), + toString: expect.any(Function), }); }); diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts index 3712da7185..975901b8e5 100644 --- a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts +++ b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts @@ -51,6 +51,7 @@ describe('createTranslationExtension', () => { resource: createTranslationExtension.translationDataRef, }, factory: expect.any(Function), + toString: expect.any(Function), }); expect((extension as any).factory({} as any)).toEqual({ @@ -88,6 +89,7 @@ describe('createTranslationExtension', () => { resource: createTranslationExtension.translationDataRef, }, factory: expect.any(Function), + toString: expect.any(Function), }); expect((extension as any).factory({} as any)).toEqual({ resource }); @@ -126,6 +128,7 @@ describe('createTranslationExtension', () => { resource: createTranslationExtension.translationDataRef, }, factory: expect.any(Function), + toString: expect.any(Function), }); }); }); diff --git a/packages/frontend-plugin-api/src/setupTests.ts b/packages/frontend-plugin-api/src/setupTests.ts index c30f1d15cb..8c1503912f 100644 --- a/packages/frontend-plugin-api/src/setupTests.ts +++ b/packages/frontend-plugin-api/src/setupTests.ts @@ -15,3 +15,5 @@ */ import '@testing-library/jest-dom'; + +(global as unknown as { CSSOM: any }).CSSOM = { parse() {} }; diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 22db95082a..f607fe3eb1 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -287,5 +287,8 @@ describe('createExtension', () => { }, }); expect(extension.namespace).toBe('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 0d6806fe49..85aa587275 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -107,6 +107,7 @@ export interface ExtensionDefinition { readonly attachTo: { id: string; input: string }; readonly disabled: boolean; readonly configSchema?: PortableSchema; + toString(): string; } /** @internal */ @@ -120,6 +121,7 @@ export interface InternalExtensionDefinition config: TConfig; inputs: ResolvedExtensionInputs; }): ExtensionDataValues; + toString(): string; } /** @internal */ @@ -166,5 +168,19 @@ export function createExtension< ...rest, }); }, + toString() { + const parts: string[] = []; + if (options.kind) { + parts.push(`kind=${options.kind}`); + } + if (options.namespace) { + parts.push(`namespace=${options.namespace}`); + } + if (options.name) { + parts.push(`name=${options.name}`); + } + parts.push(`attachTo=${options.attachTo.id}@${options.attachTo.input}`); + return `extensionDefinition{${parts.join(',')}}`; + }, } as InternalExtensionDefinition; } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.test.ts new file mode 100644 index 0000000000..d83a134b15 --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.test.ts @@ -0,0 +1,28 @@ +/* + * 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 { createExtensionDataRef } from './createExtensionDataRef'; + +describe('createExtensionDataRef', () => { + it('can be created and read', () => { + const ref = createExtensionDataRef('foo'); + expect(ref.id).toBe('foo'); + expect(String(ref)).toBe('extensionDataRef{id=foo,optional=false}'); + const refOptional = ref.optional(); + expect(refOptional.id).toBe('foo'); + expect(String(refOptional)).toBe('extensionDataRef{id=foo,optional=true}'); + }); +}); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts index d0d96e85f9..fc08b54e16 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts @@ -23,6 +23,7 @@ export type ExtensionDataRef< T: TData; config: TConfig; $$type: '@backstage/ExtensionDataRef'; + toString(): string; }; /** @public */ @@ -43,7 +44,14 @@ export function createExtensionDataRef( $$type: '@backstage/ExtensionDataRef', config: {}, optional() { - return { ...this, config: { ...this.config, optional: true } }; + return { + ...this, + config: { ...this.config, optional: true }, + }; }, - } as ConfigurableExtensionDataRef; + toString() { + const optional = Boolean(this.config.optional); + return `extensionDataRef{id=${id},optional=${optional}}`; + }, + } as ConfigurableExtensionDataRef; } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts index de2d2f9a92..aa00247222 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts @@ -27,6 +27,7 @@ describe('createExtensionOverrides', () => { "$$type": "@backstage/ExtensionOverrides", "extensions": [], "featureFlags": [], + "toString": [Function], "version": "v1", } `); @@ -74,6 +75,7 @@ describe('createExtensionOverrides', () => { "id": "a", "inputs": {}, "output": {}, + "toString": [Function], "version": "v1", }, { @@ -88,6 +90,7 @@ describe('createExtensionOverrides', () => { "id": "b", "inputs": {}, "output": {}, + "toString": [Function], "version": "v1", }, { @@ -102,10 +105,12 @@ describe('createExtensionOverrides', () => { "id": "k:c/n", "inputs": {}, "output": {}, + "toString": [Function], "version": "v1", }, ], "featureFlags": [], + "toString": [Function], "version": "v1", } `); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts index 3c14d285b3..6664dff9c7 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts @@ -38,11 +38,20 @@ export interface InternalExtensionOverrides extends ExtensionOverrides { export function createExtensionOverrides( options: ExtensionOverridesOptions, ): ExtensionOverrides { + const extensions = options.extensions.map(def => + resolveExtensionDefinition(def), + ); + const featureFlags = options.featureFlags ?? []; return { $$type: '@backstage/ExtensionOverrides', version: 'v1', - extensions: options.extensions.map(def => resolveExtensionDefinition(def)), - featureFlags: options.featureFlags ?? [], + extensions, + featureFlags, + toString() { + const ex = extensions.map(String).join(','); + const ff = featureFlags.map(f => f.name).join(','); + return `extensionOverrides{extensions=[${ex}],featureFlags=[${ff}]}`; + }, } as InternalExtensionOverrides; } diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts index d2f78122d3..93f1c78d9f 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts @@ -138,6 +138,7 @@ describe('createPlugin', () => { const plugin = createPlugin({ id: 'test' }); expect(plugin).toBeDefined(); + expect(String(plugin)).toBe('plugin{id=test}'); }); it('should create a plugin with extension instances', async () => { diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.ts index 67907e188e..fccd904276 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.ts @@ -82,6 +82,9 @@ export function createPlugin< externalRoutes: options.externalRoutes ?? ({} as ExternalRoutes), featureFlags: options.featureFlags ?? [], extensions, + toString() { + return `plugin{id=${options.id}}`; + }, } as InternalBackstagePlugin; } diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts index 9db9539ebb..e4dce35151 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts @@ -37,6 +37,7 @@ describe('resolveExtensionDefinition', () => { ...definition, } as ExtensionDefinition); expect(resolved.id).toBe(expected); + expect(String(resolved)).toBe(`extension{id=${expected}}`); }); it('should fail to resolve extension ID without namespace', () => { diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index d702120447..3827eb4d1c 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -32,6 +32,7 @@ export interface Extension { readonly attachTo: { id: string; input: string }; readonly disabled: boolean; readonly configSchema?: PortableSchema; + toString(): string; } /** @internal */ @@ -81,10 +82,15 @@ export function resolveExtensionDefinition( ); } + const id = kind ? `${kind}:${namePart}` : namePart; + return { ...rest, $$type: '@backstage/Extension', version: 'v1', - id: kind ? `${kind}:${namePart}` : namePart, - } as InternalExtension; + id, + toString() { + return `extension{id=${id}}`; + }, + } as Extension; } diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts index c2a6b09ba7..e50b335421 100644 --- a/packages/frontend-plugin-api/src/wiring/types.ts +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -41,11 +41,13 @@ export interface BackstagePlugin< readonly id: string; readonly routes: Routes; readonly externalRoutes: ExternalRoutes; + toString(): string; } /** @public */ export interface ExtensionOverrides { readonly $$type: '@backstage/ExtensionOverrides'; + toString(): string; } /** @public */