frontend-*-api: introduce ExtensionBoundary and pass source to extension factories

Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Co-authored-by: Johan Haals <johan.haals@gmail.com>
Co-authored-by: Camila Belo <camilaibs@gmail.com>
Co-authored-by: Philipp Hugenroth <philipph@spotify.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-09-04 14:20:24 +02:00
parent f4a9884c62
commit d81c13f21a
10 changed files with 154 additions and 46 deletions
+6 -7
View File
@@ -41,13 +41,11 @@ export function createApp(options: { plugins: BackstagePlugin[] }): {
// pull in default extension instance from discovered packages
// apply config to adjust default extension instances and add more
const extensionParams = mergeExtensionParameters(
[
...options.plugins.flatMap(plugin => plugin.extensions),
...builtinExtensions,
],
readAppExtensionParameters(appConfig),
);
const extensionParams = mergeExtensionParameters({
sources: options.plugins,
builtinExtensions,
parameters: readAppExtensionParameters(appConfig),
});
// TODO: validate the config of all extension instances
// We do it at this point to ensure that merging (if any) of config has already happened
@@ -96,6 +94,7 @@ export function createApp(options: { plugins: BackstagePlugin[] }): {
return createExtensionInstance({
extension: instanceParams.extension,
source: instanceParams.source,
config: instanceParams.config,
attachments,
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { Extension } from '@backstage/frontend-plugin-api';
import { BackstagePlugin, Extension } from '@backstage/frontend-plugin-api';
import mapValues from 'lodash/mapValues';
/** @internal */
@@ -28,9 +28,10 @@ export interface ExtensionInstance {
export function createExtensionInstance(options: {
extension: Extension<unknown>;
config: unknown;
source?: BackstagePlugin;
attachments: Record<string, ExtensionInstance[]>;
}): ExtensionInstance {
const { extension, config, attachments } = options;
const { extension, config, source, attachments } = options;
const extensionData = new Map<string, unknown>();
let parsedConfig: unknown;
@@ -44,6 +45,7 @@ export function createExtensionInstance(options: {
try {
extension.factory({
source,
config: parsedConfig,
bind: mapValues(extension.output, ref => {
return (value: unknown) => extensionData.set(ref.id, value);
@@ -15,7 +15,7 @@
*/
import { ConfigReader } from '@backstage/config';
import { Extension } from '@backstage/frontend-plugin-api';
import { createPlugin, Extension } from '@backstage/frontend-plugin-api';
import { JsonValue } from '@backstage/types';
import {
expandShorthandExtensionParameters,
@@ -33,15 +33,25 @@ function makeExt(id: string, status: 'disabled' | 'enabled' = 'enabled') {
describe('mergeExtensionParameters', () => {
it('should filter out disabled extension instances', () => {
expect(mergeExtensionParameters([makeExt('a', 'disabled')], [])).toEqual(
[],
);
expect(
mergeExtensionParameters({
sources: [],
builtinExtensions: [makeExt('a', 'disabled')],
parameters: [],
}),
).toEqual([]);
});
it('should pass through extension instances', () => {
const a = makeExt('a');
const b = makeExt('b');
expect(mergeExtensionParameters([a, b], [])).toEqual([
expect(
mergeExtensionParameters({
sources: [],
builtinExtensions: [a, b],
parameters: [],
}),
).toEqual([
{ extension: a, at: 'root' },
{ extension: b, at: 'root' },
]);
@@ -50,18 +60,20 @@ describe('mergeExtensionParameters', () => {
it('should override attachment points', () => {
const a = makeExt('a');
const b = makeExt('b');
const pluginA = createPlugin({ id: 'test', extensions: [a] });
expect(
mergeExtensionParameters(
[a, b],
[
mergeExtensionParameters({
sources: [pluginA],
builtinExtensions: [b],
parameters: [
{
id: 'b',
at: 'derp',
},
],
),
}),
).toEqual([
{ extension: a, at: 'root' },
{ extension: a, at: 'root', source: pluginA },
{ extension: b, at: 'derp' },
]);
});
@@ -69,10 +81,12 @@ describe('mergeExtensionParameters', () => {
it('should fully override configuration and duplicate', () => {
const a = makeExt('a');
const b = makeExt('b');
const plugin = createPlugin({ id: 'test', extensions: [a, b] });
expect(
mergeExtensionParameters(
[a, b],
[
mergeExtensionParameters({
sources: [plugin],
builtinExtensions: [],
parameters: [
{
id: 'a',
config: { foo: { bar: 1 } },
@@ -86,10 +100,10 @@ describe('mergeExtensionParameters', () => {
config: { foo: { qux: 3 } },
},
],
),
}),
).toEqual([
{ extension: a, at: 'root', config: { foo: { bar: 1 } } },
{ extension: b, at: 'root', config: { foo: { qux: 3 } } },
{ extension: a, at: 'root', source: plugin, config: { foo: { bar: 1 } } },
{ extension: b, at: 'root', source: plugin, config: { foo: { qux: 3 } } },
]);
});
@@ -97,9 +111,10 @@ describe('mergeExtensionParameters', () => {
const a = makeExt('a', 'disabled');
const b = makeExt('b', 'disabled');
expect(
mergeExtensionParameters(
[a, b],
[
mergeExtensionParameters({
sources: [createPlugin({ id: 'empty', extensions: [] })],
builtinExtensions: [a, b],
parameters: [
{
id: 'b',
disabled: false,
@@ -109,7 +124,7 @@ describe('mergeExtensionParameters', () => {
disabled: false,
},
],
),
}),
).toEqual([
{ extension: b, at: 'root' },
{ extension: a, at: 'root' },
@@ -15,7 +15,7 @@
*/
import { Config } from '@backstage/config';
import { Extension } from '@backstage/frontend-plugin-api';
import { BackstagePlugin, Extension } from '@backstage/frontend-plugin-api';
import { JsonValue } from '@backstage/types';
export interface ExtensionParameters {
@@ -183,23 +183,41 @@ export function expandShorthandExtensionParameters(
export interface ExtensionInstanceParameters {
extension: Extension<unknown>;
source?: BackstagePlugin;
at: string;
config?: unknown;
}
/** @internal */
export function mergeExtensionParameters(
base: Extension<unknown>[],
parameters: Array<ExtensionParameters>,
): ExtensionInstanceParameters[] {
const overrides = base.map(extension => ({
extension,
params: {
at: extension.at,
disabled: extension.disabled,
config: undefined as unknown,
},
}));
export function mergeExtensionParameters(options: {
sources: BackstagePlugin[];
builtinExtensions: Extension<unknown>[];
parameters: Array<ExtensionParameters>;
}): ExtensionInstanceParameters[] {
const { sources, builtinExtensions, parameters } = options;
const overrides = [
...sources.flatMap(plugin =>
plugin.extensions.map(extension => ({
extension,
params: {
source: plugin,
at: extension.at,
disabled: extension.disabled,
config: undefined as unknown,
},
})),
),
...builtinExtensions.map(extension => ({
extension,
params: {
source: undefined,
at: extension.at,
disabled: extension.disabled,
config: undefined as unknown,
},
})),
];
for (const overrideParam of parameters) {
const existingIndex = overrides.findIndex(
@@ -234,6 +252,7 @@ export function mergeExtensionParameters(
.map(param => ({
extension: param.extension,
at: param.params.at,
source: param.params.source,
config: param.params.config,
}));
}