diff --git a/.changeset/thin-experts-sing.md b/.changeset/thin-experts-sing.md new file mode 100644 index 0000000000..55aa8eac90 --- /dev/null +++ b/.changeset/thin-experts-sing.md @@ -0,0 +1,10 @@ +--- +'@backstage/core-compat-api': patch +--- + +The `convertLegacyApp` has received the following changes: + +- `null` routes will now be ignored. +- Converted routes no longer need to belong to a plugin, falling back to a `converted-orphan-routes` plugin instead. +- The generate layout override extension is now properly attached to the `app/root` extension. +- Converted root elements are now automatically wrapped with `compatWrapper`. diff --git a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx index a28bda6dea..ce0b055c00 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx @@ -48,8 +48,10 @@ describe('collectLegacyRoutes', () => { } /> } /> + } /> } /> } /> + } /> , ); @@ -96,6 +98,23 @@ describe('collectLegacyRoutes', () => { }, ], }, + { + id: 'converted-orphan-routes', + extensions: [ + { + id: 'page:converted-orphan-routes', + attachTo: { id: 'app/routes', input: 'routes' }, + disabled: false, + defaultConfig: {}, + }, + { + id: 'page:converted-orphan-routes/2', + attachTo: { id: 'app/routes', input: 'routes' }, + disabled: false, + defaultConfig: {}, + }, + ], + }, { id: 'puppetDb', extensions: [ @@ -378,8 +397,6 @@ describe('collectLegacyRoutes', () => { } /> , ), - ).toThrow( - /Route with path undefined has en element that can not be converted/, - ); + ).toThrow(/Route element inside FlatRoutes had no path prop value given/); }); }); diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx index f3d94dc4b9..b0a55f9ddd 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx @@ -18,6 +18,7 @@ import { AnyRouteRefParams, BackstagePlugin as LegacyBackstagePlugin, RouteRef, + createPlugin, getComponentData, } from '@backstage/core-plugin-api'; import { @@ -167,6 +168,9 @@ export function collectLegacyRoutes( return () => String(currentIndex++); })(); + // Placeholder plugin for any routes that don't belong to a plugin + const orphanRoutesPlugin = createPlugin({ id: 'converted-orphan-routes' }); + const getPluginExtensions = (plugin: LegacyBackstagePlugin) => { let extensions = pluginExtensions.get(plugin); if (!extensions) { @@ -179,6 +183,9 @@ export function collectLegacyRoutes( React.Children.forEach( flatRoutesElement.props.children, (route: ReactNode) => { + if (route === null) { + return; + } // TODO(freben): Handle feature flag and permissions framework wrapper elements if (!React.isValidElement(route)) { throw new Error( @@ -192,20 +199,13 @@ export function collectLegacyRoutes( } const routeElement = route.props.element; const path: string | undefined = route.props.path; - const plugin = getComponentData( - routeElement, - 'core.plugin', - ); + const plugin = + getComponentData(routeElement, 'core.plugin') ?? + orphanRoutesPlugin; const routeRef = getComponentData( routeElement, 'core.mountPoint', ); - if (!plugin) { - throw new Error( - // TODO(vinzscam): add See for more info - `Route with path ${path} has en element that can not be converted as it does not belong to a plugin. Make sure that the top-level React element of the element prop is an extension from a Backstage plugin, or remove the Route completely.`, - ); - } if (path === undefined) { throw new Error( `Route element inside FlatRoutes had no path prop value given`, diff --git a/packages/core-compat-api/src/convertLegacyApp.test.tsx b/packages/core-compat-api/src/convertLegacyApp.test.tsx index 224ab46221..7153fcb4be 100644 --- a/packages/core-compat-api/src/convertLegacyApp.test.tsx +++ b/packages/core-compat-api/src/convertLegacyApp.test.tsx @@ -114,7 +114,7 @@ describe('convertLegacyApp', () => { extensions: [ { id: 'app/layout', - attachTo: { id: 'app', input: 'root' }, + attachTo: { id: 'app/root', input: 'children' }, disabled: false, }, { diff --git a/packages/core-compat-api/src/convertLegacyApp.ts b/packages/core-compat-api/src/convertLegacyApp.ts index 3f35c78408..f098d090ed 100644 --- a/packages/core-compat-api/src/convertLegacyApp.ts +++ b/packages/core-compat-api/src/convertLegacyApp.ts @@ -32,6 +32,7 @@ import { } from '@backstage/frontend-plugin-api'; import { getComponentData } from '@backstage/core-plugin-api'; import { collectLegacyRoutes } from './collectLegacyRoutes'; +import { compatWrapper } from './compatWrapper'; function selectChildren( rootNode: ReactNode, @@ -106,7 +107,7 @@ export function convertLegacyApp( const CoreLayoutOverride = createExtension({ name: 'layout', - attachTo: { id: 'app', input: 'root' }, + attachTo: { id: 'app/root', input: 'children' }, inputs: { content: createExtensionInput([coreExtensionData.reactElement], { singleton: true, @@ -117,10 +118,12 @@ export function convertLegacyApp( // Clone the root element, this replaces the FlatRoutes declared in the app with out content input return [ coreExtensionData.reactElement( - React.cloneElement( - rootEl, - undefined, - inputs.content.get(coreExtensionData.reactElement), + compatWrapper( + React.cloneElement( + rootEl, + undefined, + inputs.content.get(coreExtensionData.reactElement), + ), ), ), ];