From fcf1b80beda4edbcffc1b906b50d69e47f9eb219 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 18 Oct 2023 13:26:56 +0200 Subject: [PATCH] frontend-app-api: migrate createApp to use createAppGraph Signed-off-by: Patrik Oldsberg --- .../frontend-app-api/src/wiring/createApp.tsx | 150 +++++------------- 1 file changed, 44 insertions(+), 106 deletions(-) diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index 485882c8eb..1dbfb3912b 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -28,11 +28,6 @@ import { Core } from '../extensions/Core'; import { CoreRoutes } from '../extensions/CoreRoutes'; import { CoreLayout } from '../extensions/CoreLayout'; import { CoreNav } from '../extensions/CoreNav'; -import { - createExtensionInstance, - ExtensionInstance, -} from './graph/createExtensionInstance'; -import { resolveAppNodeSpecs } from './graph/resolveAppNodeSpecs'; import { AnyApiFactory, ApiHolder, @@ -82,7 +77,7 @@ import { import { BrowserRouter, Route } from 'react-router-dom'; import { SidebarItem } from '@backstage/core-components'; import { DarkTheme, LightTheme } from '../extensions/themes'; -import { extractRouteInfoFromInstanceTree } from '../routing/extractRouteInfoFromInstanceTree'; +import { extractRouteInfoFromAppNode } from '../routing/extractRouteInfoFromAppNode'; import { getOrCreateGlobalSingleton } from '@backstage/version-bridge'; import { appLanguageApiRef, @@ -92,8 +87,16 @@ import { AppRouteBinder } from '../routing'; import { RoutingProvider } from '../routing/RoutingProvider'; import { resolveRouteBindings } from '../routing/resolveRouteBindings'; import { collectRouteIds } from '../routing/collectRouteIds'; -import { readAppExtensionsConfig } from './graph/readAppExtensionsConfig'; -import { AppNodeSpec } from './graph'; +import { AppNode, createAppGraph } from './graph'; + +const builtinExtensions = [ + Core, + CoreRoutes, + CoreNav, + CoreLayout, + LightTheme, + DarkTheme, +]; /** @public */ export interface ExtensionTreeNode { @@ -114,20 +117,38 @@ export function createExtensionTree(options: { config: Config; }): ExtensionTree { const features = getAvailableFeatures(options.config); - const { instances } = createInstances({ + const graph = createAppGraph({ features, + builtinExtensions, config: options.config, }); + function convertNode(node?: AppNode): ExtensionTreeNode | undefined { + return ( + node && { + id: node.spec.id, + getData(ref: ExtensionDataRef): T | undefined { + return node.instance?.getData(ref); + }, + } + ); + } + return { getExtension(id: string): ExtensionTreeNode | undefined { - return instances.get(id); + return convertNode(graph.nodes.get(id)); }, getExtensionAttachments( id: string, inputName: string, ): ExtensionTreeNode[] { - return instances.get(id)?.attachments.get(inputName) ?? []; + return ( + graph.nodes + .get(id) + ?.edges.attachments.get(inputName) + ?.map(convertNode) + .filter((node): node is ExtensionTreeNode => Boolean(node)) ?? [] + ); }, getRootRoutes(): JSX.Element[] { return this.getExtensionAttachments('core.routes', 'routes').map(node => { @@ -177,89 +198,6 @@ export function createExtensionTree(options: { }; } -/** - * @internal - */ -export function createInstances(options: { - features: (BackstagePlugin | ExtensionOverrides)[]; - config: Config; -}) { - const builtinExtensions = [ - Core, - CoreRoutes, - CoreNav, - CoreLayout, - LightTheme, - DarkTheme, - ]; - - // pull in default extension instance from discovered packages - // apply config to adjust default extension instances and add more - const appNodeSpecs = resolveAppNodeSpecs({ - features: options.features, - builtinExtensions, - parameters: readAppExtensionsConfig(options.config), - }); - - // 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 - - // Create attachment map so that we can look attachments up during instance creation - const attachmentMap = new Map>(); - for (const instanceParams of appNodeSpecs) { - const extensionId = instanceParams.attachTo.id; - const pointId = instanceParams.attachTo.input; - let pointMap = attachmentMap.get(extensionId); - if (!pointMap) { - pointMap = new Map(); - attachmentMap.set(extensionId, pointMap); - } - - let instances = pointMap.get(pointId); - if (!instances) { - instances = []; - pointMap.set(pointId, instances); - } - - instances.push(instanceParams); - } - - const instances = new Map(); - - function createInstance(instanceParams: AppNodeSpec): ExtensionInstance { - const extensionId = instanceParams.extension.id; - const existingInstance = instances.get(extensionId); - if (existingInstance) { - return existingInstance; - } - - const attachments = new Map( - Array.from(attachmentMap.get(extensionId)?.entries() ?? []).map( - ([inputName, attachmentConfigs]) => { - return [inputName, attachmentConfigs.map(createInstance)]; - }, - ), - ); - - const newInstance = createExtensionInstance({ - extension: instanceParams.extension, - source: instanceParams.source, - config: instanceParams.config, - attachments, - }); - - instances.set(extensionId, newInstance); - - return newInstance; - } - - const coreInstance = createInstance( - appNodeSpecs.find(p => p.extension.id === 'core')!, - ); - - return { coreInstance, instances }; -} - function deduplicateFeatures( allFeatures: (BackstagePlugin | ExtensionOverrides)[], ): (BackstagePlugin | ExtensionOverrides)[] { @@ -309,8 +247,9 @@ export function createApp(options: { ...(options.features ?? []), ]); - const { coreInstance } = createInstances({ + const appGraph = createAppGraph({ features: allFeatures, + builtinExtensions, config, }); @@ -323,11 +262,11 @@ export function createApp(options: { const routeIds = collectRouteIds(allFeatures); const App = () => ( - + {/* TODO: set base path using the logic from AppRouter */} - {coreInstance.getData(coreExtensionData.reactElement)} + {appGraph.root.instance!.getData( + coreExtensionData.reactElement, + )} @@ -417,22 +358,19 @@ function createLegacyAppContext(plugins: BackstagePlugin[]): AppContext { }; } -function createApiHolder( - coreExtension: ExtensionInstance, - configApi: ConfigApi, -): ApiHolder { +function createApiHolder(core: AppNode, configApi: ConfigApi): ApiHolder { const factoryRegistry = new ApiFactoryRegistry(); const pluginApis = - coreExtension.attachments + core.edges.attachments .get('apis') - ?.map(e => e.getData(coreExtensionData.apiFactory)) + ?.map(e => e.instance?.getData(coreExtensionData.apiFactory)) .filter((x): x is AnyApiFactory => !!x) ?? []; const themeExtensions = - coreExtension.attachments + core.edges.attachments .get('themes') - ?.map(e => e.getData(coreExtensionData.theme)) + ?.map(e => e.instance?.getData(coreExtensionData.theme)) .filter((x): x is AppTheme => !!x) ?? []; for (const factory of [...defaultApis, ...pluginApis]) {