frontend-app-api: extension instance string and JSON serialization
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-app-api': patch
|
||||
---
|
||||
|
||||
Implement `toString()` and `toJSON()` for extension instances.
|
||||
@@ -25,6 +25,7 @@ import { screen } from '@testing-library/react';
|
||||
import { MockConfigApi, renderWithEffects } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { createRouteRef } from '@backstage/core-plugin-api';
|
||||
import { createExtensionInstance } from './createExtensionInstance';
|
||||
|
||||
describe('createInstances', () => {
|
||||
it('throws an error when a root extension is parametrized', () => {
|
||||
@@ -132,4 +133,112 @@ describe('createApp', () => {
|
||||
|
||||
await expect(screen.findByText('Derp')).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should log an app', () => {
|
||||
const { rootInstances } = createInstances({
|
||||
config: new MockConfigApi({}),
|
||||
plugins: [],
|
||||
});
|
||||
const root = createExtensionInstance({
|
||||
extension: createExtension({
|
||||
id: 'root',
|
||||
attachTo: { id: '', input: '' },
|
||||
output: {},
|
||||
factory() {},
|
||||
}),
|
||||
config: undefined,
|
||||
attachments: new Map([['children', rootInstances]]),
|
||||
});
|
||||
|
||||
expect(String(root)).toMatchInlineSnapshot(`
|
||||
"<root>
|
||||
children [
|
||||
<core>
|
||||
themes [
|
||||
<themes.light out=[core.theme] />
|
||||
<themes.dark out=[core.theme] />
|
||||
]
|
||||
</core>
|
||||
<core.layout out=[core.reactElement]>
|
||||
content [
|
||||
<core.routes out=[core.reactElement] />
|
||||
]
|
||||
nav [
|
||||
<core.nav out=[core.reactElement] />
|
||||
]
|
||||
</core.layout>
|
||||
]
|
||||
</root>"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should serialize an app as JSON', () => {
|
||||
const { rootInstances } = createInstances({
|
||||
config: new MockConfigApi({}),
|
||||
plugins: [],
|
||||
});
|
||||
const root = createExtensionInstance({
|
||||
extension: createExtension({
|
||||
id: 'root',
|
||||
attachTo: { id: '', input: '' },
|
||||
output: {},
|
||||
factory() {},
|
||||
}),
|
||||
config: undefined,
|
||||
attachments: new Map([['children', rootInstances]]),
|
||||
});
|
||||
|
||||
expect(JSON.parse(JSON.stringify(root))).toMatchInlineSnapshot(`
|
||||
{
|
||||
"attachments": {
|
||||
"children": [
|
||||
{
|
||||
"attachments": {
|
||||
"themes": [
|
||||
{
|
||||
"id": "themes.light",
|
||||
"output": [
|
||||
"core.theme",
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "themes.dark",
|
||||
"output": [
|
||||
"core.theme",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
"id": "core",
|
||||
},
|
||||
{
|
||||
"attachments": {
|
||||
"content": [
|
||||
{
|
||||
"id": "core.routes",
|
||||
"output": [
|
||||
"core.reactElement",
|
||||
],
|
||||
},
|
||||
],
|
||||
"nav": [
|
||||
{
|
||||
"id": "core.nav",
|
||||
"output": [
|
||||
"core.reactElement",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
"id": "core.layout",
|
||||
"output": [
|
||||
"core.reactElement",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
"id": "root",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -90,6 +90,68 @@ function resolveInputs(
|
||||
});
|
||||
}
|
||||
|
||||
function indent(str: string) {
|
||||
return str.replace(/^/gm, ' ');
|
||||
}
|
||||
|
||||
class ExtensionInstanceImpl implements ExtensionInstance {
|
||||
readonly $$type = '@backstage/ExtensionInstance';
|
||||
|
||||
readonly id: string;
|
||||
readonly #extensionData: Map<string, unknown>;
|
||||
readonly attachments: Map<string, ExtensionInstance[]>;
|
||||
readonly source?: BackstagePlugin;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
extensionData: Map<string, unknown>,
|
||||
attachments: Map<string, ExtensionInstance[]>,
|
||||
source: BackstagePlugin | undefined,
|
||||
) {
|
||||
this.id = id;
|
||||
this.#extensionData = extensionData;
|
||||
this.attachments = attachments;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
getData<T>(ref: ExtensionDataRef<T>): T | undefined {
|
||||
return this.#extensionData.get(ref.id) as T | undefined;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
output:
|
||||
this.#extensionData.size > 0
|
||||
? [...this.#extensionData.keys()]
|
||||
: undefined,
|
||||
attachments:
|
||||
this.attachments.size > 0
|
||||
? Object.fromEntries(this.attachments)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
toString() {
|
||||
const out =
|
||||
this.#extensionData.size > 0
|
||||
? ` out=[${[...this.#extensionData.keys()].join(', ')}]`
|
||||
: '';
|
||||
|
||||
if (this.attachments.size === 0) {
|
||||
return `<${this.id}${out} />`;
|
||||
}
|
||||
|
||||
return [
|
||||
`<${this.id}${out}>`,
|
||||
...[...this.attachments.entries()].map(([k, v]) =>
|
||||
indent([`${k} [`, ...v.map(e => indent(e.toString())), `]`].join('\n')),
|
||||
),
|
||||
`</${this.id}>`,
|
||||
].join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function createExtensionInstance(options: {
|
||||
extension: Extension<unknown>;
|
||||
@@ -137,13 +199,10 @@ export function createExtensionInstance(options: {
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
$$type: '@backstage/ExtensionInstance',
|
||||
id: options.extension.id,
|
||||
getData<T>(ref: ExtensionDataRef<T>): T | undefined {
|
||||
return extensionData.get(ref.id) as T | undefined;
|
||||
},
|
||||
source,
|
||||
return new ExtensionInstanceImpl(
|
||||
options.extension.id,
|
||||
extensionData,
|
||||
attachments,
|
||||
};
|
||||
source,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user