frontend-app-api: switch undeclared inputs to be a warning instead of error

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-01-12 17:11:34 +01:00
parent b8e9eb3f73
commit 074dfe37b0
3 changed files with 35 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 'app/parent', but the extension 'app/parent' noes not declare a 'undeclared' input",
]);
});
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 'app/parent', but the extension 'app/parent' noes not declare a 'undeclared1' input",
"The extensions 'app/test', 'app/test' are attached to the input 'undeclared2' of 'app/parent', but the extension 'app/parent' noes not declare a 'undeclared2' input",
]);
});
it('should refuse to create an instance with multiple inputs for required singleton', () => {
@@ -46,26 +46,26 @@ 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') {
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 '${id}', but the extension '${id}' noes not declare a '${name}' input`,
);
}
}
return mapValues(inputMap, (input, inputName) => {
@@ -129,7 +129,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)) {