refactor(frontend-app-api): remove duplicated plugin extensions

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2023-09-21 15:24:10 +02:00
parent 7899201594
commit 7041414b06
4 changed files with 27 additions and 32 deletions
+1 -1
View File
@@ -2,4 +2,4 @@
'@backstage/frontend-app-api': patch
---
Prevents root extension configuration and cyclic dependency between extensions when creating extension instances.
Prevents root extension override and duplicated plugin extensions.
@@ -72,8 +72,8 @@ describe('createInstances', () => {
);
});
describe('throws an error when immediate cyclical dependencies are found', () => {
it('in an immediate level (e.g., A -> A)', () => {
describe('should deduplicated plugins', () => {
it('in an immediate dependency level (e.g., A -> A)', () => {
const config = new MockConfigApi({});
const addonExtensionData =
@@ -114,8 +114,9 @@ describe('createInstances', () => {
}),
];
expect(() => createInstances({ config, plugins })).toThrow(
/There is a cyclical dependency with the extension "A": (.*) → A → A/,
// It should not create an infinite loop
expect(() => createInstances({ config, plugins })).not.toThrow(
'Maximum call stack size exceeded',
);
});
@@ -182,8 +183,9 @@ describe('createInstances', () => {
}),
];
expect(() => createInstances({ config, plugins })).toThrow(
/There is a cyclical dependency with the extension "A": (.*) → A → B → A/,
// It should not create an infinite loop
expect(() => createInstances({ config, plugins })).not.toThrow(
'Maximum call stack size exceeded',
);
});
@@ -288,8 +290,9 @@ describe('createInstances', () => {
}),
];
expect(() => createInstances({ config, plugins })).toThrow(
/There is a cyclical dependency with the extension "B": (.*) → A → B → C → D → B/,
// It should not create an infinite loop
expect(() => createInstances({ config, plugins })).not.toThrow(
'Maximum call stack size exceeded',
);
});
});
@@ -208,7 +208,6 @@ export function createInstances(options: {
function createInstance(
instanceParams: ExtensionInstanceParameters,
extensionAncestorIds: string[] = [],
): ExtensionInstance {
const extensionId = instanceParams.extension.id;
const existingInstance = instances.get(extensionId);
@@ -216,28 +215,10 @@ export function createInstances(options: {
return existingInstance;
}
// Prevent cyclical dependencies
if (extensionAncestorIds.includes(extensionId)) {
const extensionDependencyGraph = extensionAncestorIds
.concat(extensionId)
.join(' → ');
throw new Error(
`There is a cyclical dependency with the extension "${extensionId}": ${extensionDependencyGraph}`,
);
}
const attachments = new Map(
Array.from(attachmentMap.get(extensionId)?.entries() ?? []).map(
([inputName, attachmentConfigs]) => {
return [
inputName,
attachmentConfigs.map(attachmentConfig =>
createInstance(
attachmentConfig,
extensionAncestorIds.concat(extensionId),
),
),
];
return [inputName, attachmentConfigs.map(createInstance)];
},
),
);
@@ -196,9 +196,20 @@ export function mergeExtensionParameters(options: {
}): ExtensionInstanceParameters[] {
const { sources, builtinExtensions, parameters } = options;
const pluginExtensions = sources.flatMap(source =>
source.extensions.map(extension => ({ ...extension, source })),
);
const pluginExtensionIds = new Set<string>();
const pluginExtensions = sources.flatMap(source => {
return source.extensions
.filter(extension => {
// Filter out duplicated extensions
const id = extension.id;
if (!pluginExtensionIds.has(id)) {
pluginExtensionIds.add(id);
return true;
}
return false;
})
.map(extension => ({ ...extension, source }));
});
// Prevent root override
if (pluginExtensions.some(({ id }) => id === 'root')) {