make the loader into a proper interface
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -24,9 +24,12 @@ export function createApp(options?: {
|
||||
};
|
||||
|
||||
// @public
|
||||
export type CreateAppFeatureLoader = (options: {
|
||||
config: ConfigApi;
|
||||
}) => Promise<FrontendFeature[]>;
|
||||
export interface CreateAppFeatureLoader {
|
||||
getLoaderName(): string;
|
||||
load(options: { config: ConfigApi }): Promise<{
|
||||
features: FrontendFeature[];
|
||||
}>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type CreateAppRouteBinder = <
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
createThemeExtension,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { createApp } from './createApp';
|
||||
import { CreateAppFeatureLoader, createApp } from './createApp';
|
||||
import { MockConfigApi, renderWithEffects } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
@@ -100,23 +100,32 @@ describe('createApp', () => {
|
||||
});
|
||||
|
||||
it('should support feature loaders', async () => {
|
||||
const loader: CreateAppFeatureLoader = {
|
||||
getLoaderName() {
|
||||
return 'test-loader';
|
||||
},
|
||||
async load({ config }) {
|
||||
return {
|
||||
features: [
|
||||
createPlugin({
|
||||
id: 'test',
|
||||
extensions: [
|
||||
createPageExtension({
|
||||
defaultPath: '/',
|
||||
loader: async () => <div>{config.getString('key')}</div>,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const app = createApp({
|
||||
configLoader: async () => ({
|
||||
config: new MockConfigApi({ key: 'config-value' }),
|
||||
}),
|
||||
features: [
|
||||
async ({ config }) => [
|
||||
createPlugin({
|
||||
id: 'test',
|
||||
extensions: [
|
||||
createPageExtension({
|
||||
defaultPath: '/',
|
||||
loader: async () => <div>{config.getString('key')}</div>,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
],
|
||||
features: [loader],
|
||||
});
|
||||
|
||||
await renderWithEffects(app.createRoot());
|
||||
@@ -127,21 +136,26 @@ describe('createApp', () => {
|
||||
});
|
||||
|
||||
it('should propagate errors thrown by feature loaders', async () => {
|
||||
const loader: CreateAppFeatureLoader = {
|
||||
getLoaderName() {
|
||||
return 'test-loader';
|
||||
},
|
||||
async load() {
|
||||
throw new TypeError('boom');
|
||||
},
|
||||
};
|
||||
|
||||
const app = createApp({
|
||||
configLoader: async () => ({
|
||||
config: new MockConfigApi({}),
|
||||
}),
|
||||
features: [
|
||||
async () => {
|
||||
throw new TypeError('boom');
|
||||
},
|
||||
],
|
||||
features: [loader],
|
||||
});
|
||||
|
||||
await expect(
|
||||
renderWithEffects(app.createRoot()),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Failed to read frontend features from loader, TypeError: boom"`,
|
||||
`"Failed to read frontend features from loader 'test-loader', TypeError: boom"`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -244,9 +244,19 @@ function deduplicateFeatures(
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CreateAppFeatureLoader = (options: {
|
||||
config: ConfigApi;
|
||||
}) => Promise<FrontendFeature[]>;
|
||||
export interface CreateAppFeatureLoader {
|
||||
/**
|
||||
* Returns name of this loader. suitable for showing to users.
|
||||
*/
|
||||
getLoaderName(): string;
|
||||
|
||||
/**
|
||||
* Loads a number of features dynamically.
|
||||
*/
|
||||
load(options: { config: ConfigApi }): Promise<{
|
||||
features: FrontendFeature[];
|
||||
}>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function createApp(options?: {
|
||||
@@ -266,20 +276,20 @@ export function createApp(options?: {
|
||||
const discoveredFeatures = getAvailableFeatures(config);
|
||||
|
||||
const providedFeatures: FrontendFeature[] = [];
|
||||
for (const feature of options?.features ?? []) {
|
||||
if (typeof feature === 'function') {
|
||||
for (const entry of options?.features ?? []) {
|
||||
if ('load' in entry) {
|
||||
try {
|
||||
const loadedFeatures = await feature({ config });
|
||||
providedFeatures.push(...loadedFeatures);
|
||||
const result = await entry.load({ config });
|
||||
providedFeatures.push(...result.features);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Failed to read frontend features from loader, ${stringifyError(
|
||||
`Failed to read frontend features from loader '${entry.getLoaderName()}', ${stringifyError(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
providedFeatures.push(feature);
|
||||
providedFeatures.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user