diff --git a/packages/app/src/components/DynamicRoot/DynamicRoot.tsx b/packages/app/src/components/DynamicRoot/DynamicRoot.tsx index 55b3751221..127e0c7dd7 100644 --- a/packages/app/src/components/DynamicRoot/DynamicRoot.tsx +++ b/packages/app/src/components/DynamicRoot/DynamicRoot.tsx @@ -3,7 +3,11 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { createApp } from '@backstage/app-defaults'; import { BackstageApp } from '@backstage/core-app-api'; -import { AnyApiFactory, BackstagePlugin } from '@backstage/core-plugin-api'; +import { + AnyApiFactory, + AppComponents, + BackstagePlugin, +} from '@backstage/core-plugin-api'; import { useThemes } from '@redhat-developer/red-hat-developer-hub-theme'; import { AppsConfig } from '@scalprum/core'; @@ -61,7 +65,9 @@ export const DynamicRoot = ({ React.ComponentType | undefined >(undefined); // registry of remote components loaded at bootstrap - const [components, setComponents] = useState(); + const [componentRegistry, setComponentRegistry] = useState< + ComponentRegistry | undefined + >(); const { initialized, pluginStore, api: scalprumApi } = useScalprum(); const themes = useThemes(); @@ -72,6 +78,7 @@ export const DynamicRoot = ({ pluginModules, apiFactories, appIcons, + components, dynamicRoutes, menuItems, entityTabs, @@ -86,6 +93,10 @@ export const DynamicRoot = ({ scope, module, })), + ...components.map(({ scope, module }) => ({ + scope, + module, + })), ...routeBindingTargets.map(({ scope, module }) => ({ scope, module, @@ -172,6 +183,23 @@ export const DynamicRoot = ({ ), ); + const appComponents = components.reduce>( + (componentMap, { scope, module, importName, name }) => { + if (typeof allPlugins[scope]?.[module]?.[importName] !== 'undefined') { + componentMap[name] = allPlugins[scope]?.[module]?.[ + importName + ] as React.ComponentType; + } else { + // eslint-disable-next-line no-console + console.warn( + `Plugin ${scope} is not configured properly: ${module}.${importName} not found, ignoring AppComponent: ${name}`, + ); + } + return componentMap; + }, + {}, + ); + let icons = Object.fromEntries( appIcons.reduce<[string, React.ComponentType<{}>][]>( (acc, { scope, module, importName, name }) => { @@ -408,7 +436,10 @@ export const DynamicRoot = ({ ...remoteBackstagePlugins, ], themes: [...filteredStaticThemes, ...dynamicThemeProviders], - components: defaultAppComponents, + components: { + ...defaultAppComponents, + ...appComponents, + } as Partial, }); } @@ -424,7 +455,7 @@ export const DynamicRoot = ({ scaffolderFieldExtensionComponents; // make the dynamic UI configuration available to DynamicRootContext consumers - setComponents({ + setComponentRegistry({ AppProvider: app.current.getProvider(), AppRouter: app.current.getRouter(), dynamicRoutes: dynamicRoutesComponents, @@ -449,17 +480,17 @@ export const DynamicRoot = ({ ]); useEffect(() => { - if (initialized && !components) { + if (initialized && !componentRegistry) { initializeRemoteModules(); } - }, [initialized, components, initializeRemoteModules]); + }, [initialized, componentRegistry, initializeRemoteModules]); - if (!initialized || !components) { + if (!initialized || !componentRegistry) { return ; } return ( - + {ChildComponent ? : } ); diff --git a/packages/app/src/utils/dynamicUI/extractDynamicConfig.test.ts b/packages/app/src/utils/dynamicUI/extractDynamicConfig.test.ts index e24757b854..a6bd5922cf 100644 --- a/packages/app/src/utils/dynamicUI/extractDynamicConfig.test.ts +++ b/packages/app/src/utils/dynamicUI/extractDynamicConfig.test.ts @@ -148,6 +148,7 @@ describe('extractDynamicConfig', () => { const config = extractDynamicConfig(source as DynamicPluginConfig); expect(config).toEqual({ pluginModules: [], + components: [], routeBindings: [], dynamicRoutes: [], entityTabs: [], @@ -162,6 +163,20 @@ describe('extractDynamicConfig', () => { }); it.each([ + [ + 'a component', + { components: [{ name: 'foo', importName: 'blah' }] }, + { + components: [ + { + importName: 'blah', + module: 'PluginRoot', + name: 'foo', + scope: 'janus-idp.plugin-foo', + }, + ], + }, + ], [ 'a dynamicRoute', { dynamicRoutes: [{ path: '/foo' }] }, @@ -506,6 +521,7 @@ describe('extractDynamicConfig', () => { scope: 'janus-idp.plugin-foo', }, ], + components: [], routeBindings: [], routeBindingTargets: [], dynamicRoutes: [], diff --git a/packages/app/src/utils/dynamicUI/extractDynamicConfig.ts b/packages/app/src/utils/dynamicUI/extractDynamicConfig.ts index f89f8b22b9..dd430dbecd 100644 --- a/packages/app/src/utils/dynamicUI/extractDynamicConfig.ts +++ b/packages/app/src/utils/dynamicUI/extractDynamicConfig.ts @@ -1,5 +1,5 @@ import { Entity } from '@backstage/catalog-model'; -import { ApiHolder } from '@backstage/core-plugin-api'; +import { ApiHolder, AppComponents } from '@backstage/core-plugin-api'; import { isKind } from '@backstage/plugin-catalog'; import { hasAnnotation, isType } from '../../components/catalog/utils'; @@ -116,8 +116,17 @@ type ThemeEntry = { importName: string; }; +type ComponentEntry = { + scope: string; + module: string; + id: string; + importName: string; + name: keyof AppComponents; +}; + type CustomProperties = { pluginModule?: string; + components: ComponentEntry[]; dynamicRoutes?: (DynamicModuleEntry & { importName?: string; module?: string; @@ -149,6 +158,7 @@ type DynamicConfig = { pluginModules: PluginModule[]; apiFactories: ApiFactory[]; appIcons: AppIcon[]; + components: ComponentEntry[]; dynamicRoutes: DynamicRoute[]; menuItems: MenuItem[]; entityTabs: EntityTabEntry[]; @@ -171,6 +181,7 @@ function extractDynamicConfig( pluginModules: [], apiFactories: [], appIcons: [], + components: [], dynamicRoutes: [], menuItems: [], entityTabs: [], @@ -190,6 +201,20 @@ function extractDynamicConfig( }, [], ); + config.components = Object.entries(frontend).reduce( + (pluginSet, [scope, customProperties]) => { + pluginSet.push( + ...(customProperties.components ?? []).map(component => ({ + ...component, + module: component.module ?? 'PluginRoot', + importName: component.importName ?? 'default', + scope, + })), + ); + return pluginSet; + }, + [], + ); config.dynamicRoutes = Object.entries(frontend).reduce( (pluginSet, [scope, customProperties]) => { pluginSet.push( diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 812796b107..31f0aa6414 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -91,7 +91,11 @@ backend.add(rbacDynamicPluginsProvider); backend.add(import('@backstage/plugin-auth-backend')); backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); -backend.add(import('./modules/authProvidersModule')); +if (process.env.ENABLE_AUTH_PROVIDER_MODULE_OVERRIDE !== 'true') { + backend.add(import('./modules/authProvidersModule')); +} else { + staticLogger.info(`Default authentication provider module disabled`); +} backend.add(import('@internal/plugin-dynamic-plugins-info-backend')); backend.add(import('@internal/plugin-scalprum-backend'));