Merge pull request #22176 from backstage/freben/refstr

ensure that things stringify nicely overall
This commit is contained in:
Fredrik Adelöw
2024-01-11 08:57:32 +01:00
committed by GitHub
22 changed files with 165 additions and 25 deletions
+5
View File
@@ -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>;
}
@@ -428,6 +428,11 @@ export namespace createComponentExtension {
>;
}
// @public (undocumented)
export function createComponentRef<T extends {} = {}>(options: {
id: string;
}): ComponentRef<T>;
// @public (undocumented)
export function createExtension<
TOutput extends AnyExtensionDataMap,
@@ -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;
@@ -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,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.
*/
/** @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() {
return `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),
});
});
});
@@ -287,5 +287,8 @@ describe('createExtension', () => {
},
});
expect(extension.namespace).toBe('test');
expect(String(extension)).toBe(
'ExtensionDefinition{namespace=test,attachTo=root@default}',
);
});
});
@@ -166,5 +166,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}');
});
});
@@ -43,7 +43,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', () => {
@@ -81,10 +81,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>;
}