Merge pull request #22229 from backstage/rugvip/warn

frontend-app-api: switch undeclared inputs to be a warning instead of error
This commit is contained in:
Patrik Oldsberg
2024-01-13 15:20:11 +01:00
committed by GitHub
3 changed files with 41 additions and 24 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-app-api': minor
---
Attaching extensions to an input that does not exist is now a warning rather than an error.
@@ -30,6 +30,7 @@ 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';
import { withLogCollector } from '@backstage/test-utils';
const testDataRef = createExtensionDataRef<string>('test');
const otherDataRef = createExtensionDataRef<number>('other');
@@ -433,8 +434,8 @@ describe('createAppNodeInstance', () => {
);
});
it('should refuse to create an instance with undeclared inputs', () => {
expect(() =>
it('should warn when creating an instance with undeclared inputs', () => {
const { warn } = withLogCollector(['warn'], () =>
createAppNodeInstance({
attachments: new Map([
[
@@ -458,7 +459,7 @@ describe('createAppNodeInstance', () => {
resolveExtensionDefinition(
createExtension({
namespace: 'app',
name: 'test',
name: 'parent',
attachTo: { id: 'ignored', input: 'ignored' },
inputs: {
declared: createExtensionInput({
@@ -471,13 +472,15 @@ describe('createAppNodeInstance', () => {
),
),
}),
).toThrow(
"Failed to instantiate extension 'app/test', received undeclared input 'undeclared' from extension 'app/test'",
);
expect(warn).toEqual([
"The extension 'app/test' is attached to the input 'undeclared' of the extension 'app/parent', but it has no such input (candidates are 'declared')",
]);
});
it('should refuse to create an instance with multiple undeclared inputs', () => {
expect(() =>
const { warn } = withLogCollector(['warn'], () =>
createAppNodeInstance({
attachments: new Map([
[
@@ -496,7 +499,7 @@ describe('createAppNodeInstance', () => {
resolveExtensionDefinition(
createExtension({
namespace: 'app',
name: 'test',
name: 'parent',
attachTo: { id: 'ignored', input: 'ignored' },
output: {},
factory: () => ({}),
@@ -504,9 +507,12 @@ describe('createAppNodeInstance', () => {
),
),
}),
).toThrow(
"Failed to instantiate extension 'app/test', received undeclared inputs 'undeclared1' from extension 'app/test' and 'undeclared2' from extensions 'app/test', 'app/test'",
);
expect(warn).toEqual([
"The extension 'app/test' is attached to the input 'undeclared1' of the extension 'app/parent', but it has no inputs",
"The extensions 'app/test', 'app/test' are attached to the input 'undeclared2' of the extension 'app/parent', but it has no inputs",
]);
});
it('should refuse to create an instance with multiple inputs for required singleton', () => {
@@ -46,26 +46,32 @@ function resolveInputData(
}
function resolveInputs(
id: string,
inputMap: AnyExtensionInputMap,
attachments: ReadonlyMap<string, AppNode[]>,
): ResolvedExtensionInputs<AnyExtensionInputMap> {
const undeclaredAttachments = Array.from(attachments.entries()).filter(
([inputName]) => inputMap[inputName] === undefined,
);
// TODO: Make this a warning rather than an error
if (undeclaredAttachments.length > 0) {
throw new Error(
`received undeclared input${
undeclaredAttachments.length > 1 ? 's' : ''
} ${undeclaredAttachments
.map(
([k, exts]) =>
`'${k}' from extension${exts.length > 1 ? 's' : ''} '${exts
.map(e => e.spec.id)
.join("', '")}'`,
)
.join(' and ')}`,
);
if (process.env.NODE_ENV !== 'production') {
const inputNames = Object.keys(inputMap);
for (const [name, nodes] of undeclaredAttachments) {
const pl = nodes.length > 1;
// eslint-disable-next-line no-console
console.warn(
[
`The extension${pl ? 's' : ''} '${nodes
.map(n => n.spec.id)
.join("', '")}' ${pl ? 'are' : 'is'}`,
`attached to the input '${name}' of the extension '${id}', but it`,
inputNames.length === 0
? 'has no inputs'
: `has no such input (candidates are '${inputNames.join("', '")}')`,
].join(' '),
);
}
}
return mapValues(inputMap, (input, inputName) => {
@@ -129,7 +135,7 @@ export function createAppNodeInstance(options: {
const namedOutputs = internalExtension.factory({
node,
config: parsedConfig,
inputs: resolveInputs(internalExtension.inputs, attachments),
inputs: resolveInputs(id, internalExtension.inputs, attachments),
});
for (const [name, output] of Object.entries(namedOutputs)) {