ensure that things stringify nicely overall
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -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
|
||||
@@ -33,6 +33,7 @@ function makeExt(
|
||||
id,
|
||||
attachTo: { id: attachId, input: 'default' },
|
||||
disabled: status === 'disabled',
|
||||
toString: expect.any(Function),
|
||||
} as Extension<unknown>;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<T extends {} = {}>(options: {
|
||||
id: string;
|
||||
}): ComponentRef<T>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createExtension<
|
||||
TOutput extends AnyExtensionDataMap,
|
||||
@@ -735,6 +742,8 @@ export interface Extension<TConfig> {
|
||||
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<TConfig> {
|
||||
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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1
-16
@@ -19,22 +19,7 @@ import {
|
||||
CoreNotFoundErrorPageProps,
|
||||
CoreProgressProps,
|
||||
} from '../types';
|
||||
|
||||
/** @public */
|
||||
export type ComponentRef<T extends {} = {}> = {
|
||||
id: string;
|
||||
T: T;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export function createComponentRef<T extends {} = {}>(options: {
|
||||
id: string;
|
||||
}): ComponentRef<T> {
|
||||
const { id } = options;
|
||||
return {
|
||||
id,
|
||||
} as ComponentRef<T>;
|
||||
}
|
||||
import { createComponentRef } from './createComponentRef';
|
||||
|
||||
const coreProgressComponentRef = createComponentRef<CoreProgressProps>({
|
||||
id: 'core.components.progress',
|
||||
@@ -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}');
|
||||
});
|
||||
});
|
||||
@@ -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<T extends {} = {}> = {
|
||||
id: string;
|
||||
T: T;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export function createComponentRef<T extends {} = {}>(options: {
|
||||
id: string;
|
||||
}): ComponentRef<T> {
|
||||
const { id } = options;
|
||||
return {
|
||||
id,
|
||||
toString: () => `componentRef{id=${id}}`,
|
||||
} as ComponentRef<T>;
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,6 +41,7 @@ describe('createNavLogoExtension', () => {
|
||||
logos: expect.anything(),
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,3 +15,5 @@
|
||||
*/
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
(global as unknown as { CSSOM: any }).CSSOM = { parse() {} };
|
||||
|
||||
@@ -287,5 +287,8 @@ describe('createExtension', () => {
|
||||
},
|
||||
});
|
||||
expect(extension.namespace).toBe('test');
|
||||
expect(String(extension)).toBe(
|
||||
'extensionDefinition{namespace=test,attachTo=root@default}',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,6 +107,7 @@ export interface ExtensionDefinition<TConfig> {
|
||||
readonly attachTo: { id: string; input: string };
|
||||
readonly disabled: boolean;
|
||||
readonly configSchema?: PortableSchema<TConfig>;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -120,6 +121,7 @@ export interface InternalExtensionDefinition<TConfig>
|
||||
config: TConfig;
|
||||
inputs: ResolvedExtensionInputs<any>;
|
||||
}): ExtensionDataValues<any>;
|
||||
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<TConfig>;
|
||||
}
|
||||
|
||||
@@ -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}');
|
||||
});
|
||||
});
|
||||
@@ -23,6 +23,7 @@ export type ExtensionDataRef<
|
||||
T: TData;
|
||||
config: TConfig;
|
||||
$$type: '@backstage/ExtensionDataRef';
|
||||
toString(): string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
@@ -43,7 +44,14 @@ export function createExtensionDataRef<TData>(
|
||||
$$type: '@backstage/ExtensionDataRef',
|
||||
config: {},
|
||||
optional() {
|
||||
return { ...this, config: { ...this.config, optional: true } };
|
||||
return {
|
||||
...this,
|
||||
config: { ...this.config, optional: true },
|
||||
};
|
||||
},
|
||||
} as ConfigurableExtensionDataRef<TData>;
|
||||
toString() {
|
||||
const optional = Boolean(this.config.optional);
|
||||
return `extensionDataRef{id=${id},optional=${optional}}`;
|
||||
},
|
||||
} as ConfigurableExtensionDataRef<TData, { optional?: true }>;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -82,6 +82,9 @@ export function createPlugin<
|
||||
externalRoutes: options.externalRoutes ?? ({} as ExternalRoutes),
|
||||
featureFlags: options.featureFlags ?? [],
|
||||
extensions,
|
||||
toString() {
|
||||
return `plugin{id=${options.id}}`;
|
||||
},
|
||||
} as InternalBackstagePlugin<Routes, ExternalRoutes>;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ describe('resolveExtensionDefinition', () => {
|
||||
...definition,
|
||||
} as ExtensionDefinition<unknown>);
|
||||
expect(resolved.id).toBe(expected);
|
||||
expect(String(resolved)).toBe(`extension{id=${expected}}`);
|
||||
});
|
||||
|
||||
it('should fail to resolve extension ID without namespace', () => {
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface Extension<TConfig> {
|
||||
readonly attachTo: { id: string; input: string };
|
||||
readonly disabled: boolean;
|
||||
readonly configSchema?: PortableSchema<TConfig>;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -81,10 +82,15 @@ export function resolveExtensionDefinition<TConfig>(
|
||||
);
|
||||
}
|
||||
|
||||
const id = kind ? `${kind}:${namePart}` : namePart;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
$$type: '@backstage/Extension',
|
||||
version: 'v1',
|
||||
id: kind ? `${kind}:${namePart}` : namePart,
|
||||
} as InternalExtension<TConfig>;
|
||||
id,
|
||||
toString() {
|
||||
return `extension{id=${id}}`;
|
||||
},
|
||||
} as Extension<TConfig>;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user