From f0e3c10f4096095062fb1fb5c30471aa5f472db0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 Sep 2023 15:32:22 +0200 Subject: [PATCH] frontend-app-api: add legacy app context and default APIs Co-authored-by: Camila Belo Co-authored-by: Philipp Hugenroth Signed-off-by: Patrik Oldsberg --- packages/frontend-app-api/src/createApp.tsx | 90 ++++++++++++++++++--- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/packages/frontend-app-api/src/createApp.tsx b/packages/frontend-app-api/src/createApp.tsx index 588ebc7f5e..19ca7b367f 100644 --- a/packages/frontend-app-api/src/createApp.tsx +++ b/packages/frontend-app-api/src/createApp.tsx @@ -33,11 +33,16 @@ import { } from './wiring/parameters'; import { RoutingProvider } from './routing/RoutingContext'; import { + AnyApiFactory, ApiHolder, + AppComponents, + AppContext, appThemeApiRef, ConfigApi, configApiRef, + IconComponent, RouteRef, + BackstagePlugin as LegacyBackstagePlugin, } from '@backstage/core-plugin-api'; import { getAvailablePlugins } from './wiring/discovery'; import { @@ -51,11 +56,18 @@ import { // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { AppThemeProvider } from '../../core-app-api/src/app/AppThemeProvider'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { AppContextProvider } from '../../core-app-api/src/app/AppContext'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports import { defaultConfigLoaderSync } from '../../core-app-api/src/app/defaultConfigLoader'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { overrideBaseUrlConfigs } from '../../core-app-api/src/app/overrideBaseUrlConfigs'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { themes } from '../../app-defaults/src/defaults/themes'; +import { + apis as defaultApis, + components as defaultComponents, + icons as defaultIcons, + themes as defaultThemes, +} from '../../app-defaults/src/defaults'; /** @public */ export function createApp(options: { @@ -70,11 +82,12 @@ export function createApp(options: { const builtinExtensions = [CoreRouter, Core]; const discoveredPlugins = getAvailablePlugins(); + const allPlugins = [...discoveredPlugins, ...options.plugins]; // pull in default extension instance from discovered packages // apply config to adjust default extension instances and add more const extensionParams = mergeExtensionParameters({ - sources: [...options.plugins, ...discoveredPlugins], + sources: allPlugins, builtinExtensions, parameters: readAppExtensionParameters(appConfig), }); @@ -150,6 +163,8 @@ export function createApp(options: { const apiHolder = createApiHolder(coreInstance, appConfig); + const appContext = createLegacyAppContext(allPlugins); + return { createRoot() { const rootComponents = rootInstances @@ -162,19 +177,65 @@ export function createApp(options: { .filter(Boolean); return ( - - - {rootComponents.map((Component, i) => ( - - ))} - - + + + + {rootComponents.map((Component, i) => ( + + ))} + + + ); }, }; } +function toLegacyPlugin(plugin: BackstagePlugin): LegacyBackstagePlugin { + const errorMsg = 'Not implemented in legacy plugin compatibility layer'; + const notImplemented = () => { + throw new Error(errorMsg); + }; + return { + getId(): string { + return plugin.id; + }, + get routes(): never { + throw new Error(errorMsg); + }, + get externalRoutes(): never { + throw new Error(errorMsg); + }, + getApis: notImplemented, + getFeatureFlags: notImplemented, + provide: notImplemented, + __experimentalReconfigure: notImplemented, + }; +} + +function createLegacyAppContext(plugins: BackstagePlugin[]): AppContext { + return { + getPlugins(): LegacyBackstagePlugin[] { + return plugins.map(toLegacyPlugin); + }, + + getSystemIcon(key: string): IconComponent | undefined { + return key in defaultIcons + ? defaultIcons[key as keyof typeof defaultIcons] + : undefined; + }, + + getSystemIcons(): Record { + return defaultIcons; + }, + + getComponents(): AppComponents { + return defaultComponents; + }, + }; +} + function createApiHolder( coreExtension: ExtensionInstance, configApi: ConfigApi, @@ -200,7 +261,7 @@ function createApiHolder( api: appThemeApiRef, deps: {}, // TODO: add extension for registering themes - factory: () => AppThemeSelector.createWithStorage(themes), + factory: () => AppThemeSelector.createWithStorage(defaultThemes), }); factoryRegistry.register('static', { @@ -209,6 +270,15 @@ function createApiHolder( factory: () => configApi, }); + // TODO: ship these as default extensions instead + for (const factory of defaultApis as AnyApiFactory[]) { + if (!factoryRegistry.register('app', factory)) { + throw new Error( + `Duplicate or forbidden API factory for ${factory.api} in app`, + ); + } + } + ApiResolver.validateFactories(factoryRegistry, factoryRegistry.getAllApis()); return new ApiResolver(factoryRegistry);