refactor: return node in resolved extension inputs

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2023-12-04 23:20:12 +01:00
parent 6bad140c57
commit cb4197aadb
5 changed files with 36 additions and 27 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/frontend-plugin-api': patch
'@backstage/frontend-app-api': patch
---
Forward ` node`` instead of `extensionId` to resolved extension inputs.
@@ -26,7 +26,7 @@ import {
createAppNodeInstance,
instantiateAppNodeTree,
} from './instantiateAppNodeTree';
import { AppNodeInstance, AppNodeSpec } from '@backstage/frontend-plugin-api';
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';
@@ -85,11 +85,12 @@ function makeNode<TConfig>(
function makeInstanceWithId<TConfig>(
extension: Extension<TConfig>,
config?: TConfig,
): { id: string; instance: AppNodeInstance } {
): AppNode {
const node = makeNode(extension, { config });
return {
id: extension.id,
...node,
instance: createAppNodeInstance({
node: makeNode(extension, { config }),
node,
attachments: new Map(),
}),
};
@@ -152,8 +153,10 @@ describe('instantiateAppNodeTree', () => {
instantiateAppNodeTree(tree.root);
expect(tree.root.instance).toBeDefined();
expect(childNode?.instance).toBeDefined();
expect(tree.root.instance?.getData(inputMirrorDataRef)).toEqual({
test: [{ extensionId: 'child-node', output: { test: 'test' } }],
expect(tree.root.instance?.getData(inputMirrorDataRef)).toMatchObject({
test: [
{ node: { spec: { id: 'child-node' } }, output: { test: 'test' } },
],
});
// Multiple calls should have no effect
@@ -295,18 +298,21 @@ describe('createAppNodeInstance', () => {
});
expect(Array.from(instance.getDataRefs())).toEqual([inputMirrorDataRef]);
expect(instance.getData(inputMirrorDataRef)).toEqual({
expect(instance.getData(inputMirrorDataRef)).toMatchObject({
optionalSingletonPresent: {
extensionId: 'core/test',
node: { spec: { id: 'core/test' } },
output: { test: 'optionalSingletonPresent' },
},
singleton: {
extensionId: 'core/test',
node: { spec: { id: 'core/test' } },
output: { test: 'singleton', other: 2 },
},
many: [
{ extensionId: 'core/test', output: { test: 'many1' } },
{ extensionId: 'core/test', output: { test: 'many2', other: 3 } },
{ node: { spec: { id: 'core/test' } }, output: { test: 'many1' } },
{
node: { spec: { id: 'core/test' } },
output: { test: 'many2', other: 3 },
},
],
});
});
@@ -29,14 +29,14 @@ type Mutable<T> = {
function resolveInputData(
dataMap: AnyExtensionDataMap,
attachment: { id: string; instance: AppNodeInstance },
attachment: AppNode,
inputName: string,
) {
return mapValues(dataMap, ref => {
const value = attachment.instance.getData(ref);
const value = attachment.instance?.getData(ref);
if (value === undefined && !ref.config.optional) {
throw new Error(
`input '${inputName}' did not receive required extension data '${ref.id}' from extension '${attachment.id}'`,
`input '${inputName}' did not receive required extension data '${ref.id}' from extension '${attachment.spec.id}'`,
);
}
return value;
@@ -45,7 +45,7 @@ function resolveInputData(
function resolveInputs(
inputMap: AnyExtensionInputMap,
attachments: ReadonlyMap<string, { id: string; instance: AppNodeInstance }[]>,
attachments: ReadonlyMap<string, AppNode[]>,
): ResolvedExtensionInputs<AnyExtensionInputMap> {
const undeclaredAttachments = Array.from(attachments.entries()).filter(
([inputName]) => inputMap[inputName] === undefined,
@@ -59,7 +59,7 @@ function resolveInputs(
.map(
([k, exts]) =>
`'${k}' from extension${exts.length > 1 ? 's' : ''} '${exts
.map(e => e.id)
.map(e => e.spec.id)
.join("', '")}'`,
)
.join(' and ')}`,
@@ -71,7 +71,7 @@ function resolveInputs(
if (input.config.singleton) {
if (attachedNodes.length > 1) {
const attachedNodeIds = attachedNodes.map(e => e.id);
const attachedNodeIds = attachedNodes.map(e => e.spec.id);
throw Error(
`expected ${
input.config.optional ? 'at most' : 'exactly'
@@ -86,7 +86,7 @@ function resolveInputs(
throw Error(`input '${inputName}' is required but was not received`);
}
return {
extensionId: attachedNodes[0].id,
node: attachedNodes[0],
output: resolveInputData(
input.extensionData,
attachedNodes[0],
@@ -96,7 +96,7 @@ function resolveInputs(
}
return attachedNodes.map(attachment => ({
extensionId: attachment.id,
node: attachment,
output: resolveInputData(input.extensionData, attachment, inputName),
}));
}) as ResolvedExtensionInputs<AnyExtensionInputMap>;
@@ -105,7 +105,7 @@ function resolveInputs(
/** @internal */
export function createAppNodeInstance(options: {
node: AppNode;
attachments: ReadonlyMap<string, { id: string; instance: AppNodeInstance }[]>;
attachments: ReadonlyMap<string, AppNode[]>;
}): AppNodeInstance {
const { node, attachments } = options;
const { id, extension, config } = node.spec;
@@ -172,10 +172,7 @@ export function instantiateAppNodeTree(rootNode: AppNode): void {
return undefined;
}
const instantiatedAttachments = new Map<
string,
{ id: string; instance: AppNodeInstance }[]
>();
const instantiatedAttachments = new Map<string, AppNode[]>();
for (const [input, children] of node.edges.attachments) {
const instantiatedChildren = children.flatMap(child => {
@@ -183,7 +180,7 @@ export function instantiateAppNodeTree(rootNode: AppNode): void {
if (!childInstance) {
return [];
}
return [{ id: child.spec.id, instance: childInstance }];
return [child];
});
if (instantiatedChildren.length > 0) {
instantiatedAttachments.set(input, instantiatedChildren);
+1 -1
View File
@@ -888,7 +888,7 @@ export { ProfileInfoApi };
// @public
export type ResolvedExtensionInput<TExtensionData extends AnyExtensionDataMap> =
{
extensionId: string;
node: AppNode;
output: ExtensionDataValues<TExtensionData>;
};
@@ -57,7 +57,7 @@ export type ExtensionDataValues<TExtensionData extends AnyExtensionDataMap> = {
*/
export type ResolvedExtensionInput<TExtensionData extends AnyExtensionDataMap> =
{
extensionId: string;
node: AppNode;
output: ExtensionDataValues<TExtensionData>;
};