refactor: return node in resolved extension inputs
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user