diff --git a/.changeset/light-beers-cheer.md b/.changeset/light-beers-cheer.md new file mode 100644 index 0000000000..7d732edae8 --- /dev/null +++ b/.changeset/light-beers-cheer.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': patch +--- + +Added `compatWrapper`, which can be used to wrap any React element to provide bi-directional interoperability between the `@backstage/core-*-api` and `@backstage/frontend-*-api` APIs. diff --git a/.changeset/polite-crabs-build.md b/.changeset/polite-crabs-build.md new file mode 100644 index 0000000000..5975ac3c4e --- /dev/null +++ b/.changeset/polite-crabs-build.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-user-settings': patch +'@backstage/plugin-tech-radar': patch +'@backstage/plugin-graphiql': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-search': patch +'@backstage/plugin-home': patch +--- + +Wrap `/alpha` export extension elements in backwards compatibility wrapper. diff --git a/.changeset/shiny-books-search.md b/.changeset/shiny-books-search.md new file mode 100644 index 0000000000..1d743565b9 --- /dev/null +++ b/.changeset/shiny-books-search.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': minor +--- + +The app no longer provides the `AppContext` from `@backstage/core-plugin-api`. Components that require this context to be available should use the `compatWrapper` helper from `@backstage/core-compat-api`. diff --git a/packages/core-compat-api/api-report.md b/packages/core-compat-api/api-report.md index 93a2fa8cf1..b7a544f673 100644 --- a/packages/core-compat-api/api-report.md +++ b/packages/core-compat-api/api-report.md @@ -13,6 +13,7 @@ import { ExtensionOverrides } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { ExternalRouteRef as ExternalRouteRef_2 } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; +import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { SubRouteRef } from '@backstage/core-plugin-api'; @@ -28,6 +29,9 @@ export function collectLegacyRoutes( flatRoutesElement: JSX.Element, ): BackstagePlugin[]; +// @public +export function compatWrapper(element: ReactNode): React_2.JSX.Element; + // @public (undocumented) export function convertLegacyApp( rootElement: React_2.JSX.Element, diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 531b09722e..f713b49cf6 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -24,10 +24,12 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", "@backstage/plugin-puppetdb": "workspace:^", "@backstage/plugin-stackstorm": "workspace:^", "@oriflame/backstage-plugin-score-card": "^0.7.0", - "@testing-library/jest-dom": "^6.0.0" + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^14.0.0" }, "files": [ "dist" @@ -40,6 +42,7 @@ "@backstage/core-app-api": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/version-bridge": "workspace:^", "@types/react": "^16.13.1 || ^17.0.0" } } diff --git a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx new file mode 100644 index 0000000000..099993a392 --- /dev/null +++ b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx @@ -0,0 +1,129 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useMemo } from 'react'; +import { ReactNode } from 'react'; +// 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 { + components as defaultComponents, + icons as defaultIcons, +} from '../../../app-defaults/src/defaults'; +import { + BackstagePlugin as NewBackstagePlugin, + appTreeApiRef, + useApi, +} from '@backstage/frontend-plugin-api'; +import { + AppComponents, + IconComponent, + BackstagePlugin as LegacyBackstagePlugin, +} from '@backstage/core-plugin-api'; +import { getOrCreateGlobalSingleton } from '@backstage/version-bridge'; + +// Make sure that we only convert each new plugin instance to its legacy equivalent once +const legacyPluginStore = getOrCreateGlobalSingleton( + 'legacy-plugin-compatibility-store', + () => new WeakMap(), +); + +function toLegacyPlugin(plugin: NewBackstagePlugin): LegacyBackstagePlugin { + let legacy = legacyPluginStore.get(plugin); + if (legacy) { + return legacy; + } + + const errorMsg = 'Not implemented in legacy plugin compatibility layer'; + const notImplemented = () => { + throw new Error(errorMsg); + }; + + legacy = { + getId(): string { + return plugin.id; + }, + get routes() { + return {}; + }, + get externalRoutes() { + return {}; + }, + getApis: notImplemented, + getFeatureFlags: notImplemented, + provide: notImplemented, + }; + + legacyPluginStore.set(plugin, legacy); + return legacy; +} + +// Recreates the old AppContext APIs using the various new APIs that replaced it +function LegacyAppContextProvider(props: { children: ReactNode }) { + const appTreeApi = useApi(appTreeApiRef); + + const appContext = useMemo(() => { + const { tree } = appTreeApi.getTree(); + + let gatheredPlugins: LegacyBackstagePlugin[] | undefined = undefined; + + return { + getPlugins(): LegacyBackstagePlugin[] { + if (gatheredPlugins) { + return gatheredPlugins; + } + + const pluginSet = new Set(); + for (const node of tree.nodes.values()) { + const plugin = node.spec.source; + if (plugin) { + pluginSet.add(toLegacyPlugin(plugin)); + } + } + gatheredPlugins = Array.from(pluginSet); + + return gatheredPlugins; + }, + + // TODO: Grab these from new API once it exists + getSystemIcon(key: string): IconComponent | undefined { + return key in defaultIcons + ? defaultIcons[key as keyof typeof defaultIcons] + : undefined; + }, + + // TODO: Grab these from new API once it exists + getSystemIcons(): Record { + return defaultIcons; + }, + + // TODO: Grab these from new API once it exists + getComponents(): AppComponents { + return defaultComponents; + }, + }; + }, [appTreeApi]); + + return ( + + {props.children} + + ); +} + +export function BackwardsCompatProvider(props: { children: ReactNode }) { + return {props.children}; +} diff --git a/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx new file mode 100644 index 0000000000..5544862aee --- /dev/null +++ b/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx @@ -0,0 +1,23 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { ReactNode } from 'react'; + +export function ForwardsCompatProvider(props: { children: ReactNode }) { + // TODO(Rugvip): Implement + return <>{props.children}; +} diff --git a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx new file mode 100644 index 0000000000..b2ad7f3d0c --- /dev/null +++ b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { + coreExtensionData, + createExtension, +} from '@backstage/frontend-plugin-api'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { screen } from '@testing-library/react'; +import { compatWrapper } from './compatWrapper'; +import { useApp } from '@backstage/core-plugin-api'; + +describe('BackwardsCompatProvider', () => { + it('should convert the app context', () => { + // TODO(Rugvip): Replace with the new renderInTestApp once it's available, and have some plugins + createExtensionTester( + createExtension({ + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + element: coreExtensionData.reactElement, + }, + factory() { + function Component() { + const app = useApp(); + return ( +
+ plugins: + {app + .getPlugins() + .map(p => p.getId()) + .join(', ')} + {'\n'} + components: {Object.keys(app.getComponents()).join(', ')} + {'\n'} + icons: {Object.keys(app.getSystemIcons()).join(', ')} +
+ ); + } + + return { + element: compatWrapper(), + }; + }, + }), + ).render(); + + expect(screen.getByTestId('ctx').textContent).toMatchInlineSnapshot(` + "plugins: + components: Progress, Router, NotFoundErrorPage, BootErrorPage, ErrorBoundaryFallback + icons: brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, user, warning" + `); + }); +}); diff --git a/packages/core-compat-api/src/compatWrapper/compatWrapper.tsx b/packages/core-compat-api/src/compatWrapper/compatWrapper.tsx new file mode 100644 index 0000000000..cd7378a384 --- /dev/null +++ b/packages/core-compat-api/src/compatWrapper/compatWrapper.tsx @@ -0,0 +1,42 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { useVersionedContext } from '@backstage/version-bridge'; +import { ReactNode } from 'react'; +import { BackwardsCompatProvider } from './BackwardsCompatProvider'; +import { ForwardsCompatProvider } from './ForwardsCompatProvider'; + +function BidirectionalCompatProvider(props: { children: ReactNode }) { + const isInNewApp = !useVersionedContext<{ 1: unknown }>('app-context'); + + if (isInNewApp) { + return ; + } + + return ; +} + +/** + * Wraps a React element in a bidirectional compatibility provider, allow APIs + * from `@backstage/core-plugin-api` to be used in an app from `@backstage/frontend-app-api`, + * and APIs from `@backstage/frontend-plugin-api` to be used in an app from `@backstage/core-app-api`. + * + * @public + */ +export function compatWrapper(element: ReactNode) { + return {element}; +} diff --git a/packages/core-compat-api/src/compatWrapper/index.ts b/packages/core-compat-api/src/compatWrapper/index.ts new file mode 100644 index 0000000000..42a8ff6dd4 --- /dev/null +++ b/packages/core-compat-api/src/compatWrapper/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { compatWrapper } from './compatWrapper'; diff --git a/packages/core-compat-api/src/index.ts b/packages/core-compat-api/src/index.ts index d31513e559..add15c53fe 100644 --- a/packages/core-compat-api/src/index.ts +++ b/packages/core-compat-api/src/index.ts @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +export * from './compatWrapper'; + export { collectLegacyRoutes } from './collectLegacyRoutes'; export { collectLegacyComponents } from './collectLegacyComponents'; export { convertLegacyApp } from './convertLegacyApp'; diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index f58dfb3159..74812a5acd 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -35,13 +35,10 @@ import { CoreNav } from '../extensions/CoreNav'; import { AnyApiFactory, ApiHolder, - AppComponents, - AppContext, appThemeApiRef, ConfigApi, configApiRef, IconComponent, - BackstagePlugin as LegacyBackstagePlugin, featureFlagsApiRef, attachComponentData, identityApiRef, @@ -61,8 +58,6 @@ import { AppThemeProvider } from '../../../core-app-api/src/app/AppThemeProvider // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; // 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 { LocalStorageFeatureFlags } from '../../../core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { defaultConfigLoaderSync } from '../../../core-app-api/src/app/defaultConfigLoader'; @@ -75,11 +70,7 @@ import { I18nextTranslationApi } from '../../../core-app-api/src/apis/implementa // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - apis as defaultApis, - components as defaultComponents, - icons as defaultIcons, -} from '../../../app-defaults/src/defaults'; +import { apis as defaultApis } from '../../../app-defaults/src/defaults'; import { Route } from 'react-router-dom'; import { SidebarItem } from '@backstage/core-components'; import { DarkTheme, LightTheme } from '../extensions/themes'; @@ -100,7 +91,6 @@ import { DefaultNotFoundErrorPageComponent, } from '../extensions/components'; import { AppNode } from '@backstage/frontend-plugin-api'; -import { toLegacyPlugin } from '../routing/toLegacyPlugin'; import { InternalAppContext } from './InternalAppContext'; import { CoreRouter } from '../extensions/CoreRouter'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports @@ -315,12 +305,6 @@ export function createSpecializedApp(options?: { config, }); - const appContext = createLegacyAppContext( - features.filter( - (f): f is BackstagePlugin => f.$$type === '@backstage/BackstagePlugin', - ), - ); - const appIdentityProxy = new AppIdentityProxy(); const apiHolder = createApiHolder(tree, config, appIdentityProxy); @@ -353,17 +337,15 @@ export function createSpecializedApp(options?: { const App = () => ( - - - - - {rootEl} - - - - + + + + {rootEl} + + + ); @@ -374,28 +356,6 @@ export function createSpecializedApp(options?: { }; } -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( tree: AppTree, configApi: ConfigApi, diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx index 7ecbac5346..858e4fa91b 100644 --- a/plugins/catalog-import/src/alpha.tsx +++ b/plugins/catalog-import/src/alpha.tsx @@ -20,7 +20,10 @@ import { discoveryApiRef, identityApiRef, } from '@backstage/core-plugin-api'; -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; import { createApiExtension, createPageExtension, @@ -40,7 +43,10 @@ import { catalogApiRef } from '@backstage/plugin-catalog-react'; const CatalogImportPageExtension = createPageExtension({ defaultPath: '/catalog-import', routeRef: convertLegacyRouteRef(rootRouteRef), - loader: () => import('./components/ImportPage').then(m => ), + loader: () => + import('./components/ImportPage').then(m => + compatWrapper(), + ), }); const CatalogImportService = createApiExtension({ diff --git a/plugins/catalog/src/alpha/entityCards.tsx b/plugins/catalog/src/alpha/entityCards.tsx index b6a804a576..371c62afbe 100644 --- a/plugins/catalog/src/alpha/entityCards.tsx +++ b/plugins/catalog/src/alpha/entityCards.tsx @@ -16,79 +16,80 @@ import React from 'react'; import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { compatWrapper } from '@backstage/core-compat-api'; export const EntityAboutCard = createEntityCardExtension({ name: 'about', loader: async () => - import('../components/AboutCard').then(m => ( - - )), + import('../components/AboutCard').then(m => + compatWrapper(), + ), }); export const EntityLinksCard = createEntityCardExtension({ name: 'links', filter: 'has:links', loader: async () => - import('../components/EntityLinksCard').then(m => { - return ; - }), + import('../components/EntityLinksCard').then(m => + compatWrapper(), + ), }); export const EntityLabelsCard = createEntityCardExtension({ name: 'labels', filter: 'has:labels', loader: async () => - import('../components/EntityLabelsCard').then(m => ( - - )), + import('../components/EntityLabelsCard').then(m => + compatWrapper(), + ), }); export const EntityDependsOnComponentsCard = createEntityCardExtension({ name: 'depends-on-components', loader: async () => - import('../components/DependsOnComponentsCard').then(m => ( - - )), + import('../components/DependsOnComponentsCard').then(m => + compatWrapper(), + ), }); export const EntityDependsOnResourcesCard = createEntityCardExtension({ name: 'depends-on-resources', loader: async () => - import('../components/DependsOnResourcesCard').then(m => ( - - )), + import('../components/DependsOnResourcesCard').then(m => + compatWrapper(), + ), }); export const EntityHasComponentsCard = createEntityCardExtension({ name: 'has-components', loader: async () => - import('../components/HasComponentsCard').then(m => ( - - )), + import('../components/HasComponentsCard').then(m => + compatWrapper(), + ), }); export const EntityHasResourcesCard = createEntityCardExtension({ name: 'has-resources', loader: async () => - import('../components/HasResourcesCard').then(m => ( - - )), + import('../components/HasResourcesCard').then(m => + compatWrapper(), + ), }); export const EntityHasSubcomponentsCard = createEntityCardExtension({ name: 'has-subcomponents', loader: async () => - import('../components/HasSubcomponentsCard').then(m => ( - - )), + import('../components/HasSubcomponentsCard').then(m => + compatWrapper(), + ), }); export const EntityHasSystemsCard = createEntityCardExtension({ name: 'has-systems', loader: async () => - import('../components/HasSystemsCard').then(m => ( - - )), + import('../components/HasSystemsCard').then(m => + compatWrapper(), + ), }); export default [ diff --git a/plugins/catalog/src/alpha/pages.tsx b/plugins/catalog/src/alpha/pages.tsx index ee1da49147..c80e17ba4e 100644 --- a/plugins/catalog/src/alpha/pages.tsx +++ b/plugins/catalog/src/alpha/pages.tsx @@ -15,7 +15,10 @@ */ import React from 'react'; -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; import { createPageExtension, coreExtensionData, @@ -40,7 +43,7 @@ export const CatalogIndexPage = createPageExtension({ loader: async ({ inputs }) => { const { BaseCatalogPage } = await import('../components/CatalogPage'); const filters = inputs.filters.map(filter => filter.output.element); - return {filters}} />; + return compatWrapper({filters}} />); }, }); @@ -75,7 +78,7 @@ export const CatalogEntityPage = createPageExtension({ ); }; - return ; + return compatWrapper(); }, }); diff --git a/plugins/graphiql/src/alpha.tsx b/plugins/graphiql/src/alpha.tsx index f2c23f14e4..2fbd9235ac 100644 --- a/plugins/graphiql/src/alpha.tsx +++ b/plugins/graphiql/src/alpha.tsx @@ -34,13 +34,17 @@ import { } from '@backstage/plugin-graphiql'; import { createApiFactory, IconComponent } from '@backstage/core-plugin-api'; import { graphiQLRouteRef } from './route-refs'; -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; /** @alpha */ export const GraphiqlPage = createPageExtension({ defaultPath: '/graphiql', routeRef: convertLegacyRouteRef(graphiQLRouteRef), - loader: () => import('./components').then(m => ), + loader: () => + import('./components').then(m => compatWrapper()), }); /** @alpha */ diff --git a/plugins/home/package.json b/plugins/home/package.json index 57a5189fc9..038037e303 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -51,6 +51,7 @@ "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/core-app-api": "workspace:^", + "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index f16881de95..631f4293be 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -24,6 +24,7 @@ import { createPlugin, createRouteRef, } from '@backstage/frontend-plugin-api'; +import { compatWrapper } from '@backstage/core-compat-api'; const rootRouteRef = createRouteRef(); @@ -49,12 +50,14 @@ const HomepageCompositionRootExtension = createPageExtension({ ), }, loader: ({ inputs }) => - import('./components/').then(m => ( - - )), + import('./components/').then(m => + compatWrapper( + , + ), + ), }); /** diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index d18f93391c..7899fb2ae1 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -67,7 +67,10 @@ import { rootRouteRef } from './plugin'; import { SearchClient } from './apis'; import { SearchType } from './components/SearchType'; import { UrlUpdater } from './components/SearchPage/SearchPage'; -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; /** @alpha */ export const SearchApi = createApiExtension({ @@ -225,11 +228,11 @@ export const SearchPage = createPageExtension({ ); }; - return ( + return compatWrapper( - + , ); }, }); diff --git a/plugins/tech-radar/src/alpha.tsx b/plugins/tech-radar/src/alpha.tsx index 2d15e92537..dfed6d3af0 100644 --- a/plugins/tech-radar/src/alpha.tsx +++ b/plugins/tech-radar/src/alpha.tsx @@ -24,7 +24,10 @@ import { import React from 'react'; import { techRadarApiRef } from './api'; import { SampleTechRadarApi } from './sample'; -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; import { rootRouteRef } from './plugin'; /** @alpha */ @@ -44,7 +47,9 @@ export const TechRadarPage = createPageExtension({ }), ), loader: ({ config }) => - import('./components').then(m => ), + import('./components').then(m => + compatWrapper(), + ), }); /** @alpha */ diff --git a/plugins/techdocs/src/alpha.tsx b/plugins/techdocs/src/alpha.tsx index 82bda828e2..26891b23f0 100644 --- a/plugins/techdocs/src/alpha.tsx +++ b/plugins/techdocs/src/alpha.tsx @@ -31,7 +31,10 @@ import { fetchApiRef, identityApiRef, } from '@backstage/core-plugin-api'; -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; import { techdocsApiRef, techdocsStorageApiRef, @@ -100,7 +103,8 @@ export const TechDocsSearchResultListItemExtension = const { TechDocsSearchResultListItem } = await import( './search/components/TechDocsSearchResultListItem' ); - return props => ; + return props => + compatWrapper(); }, }); @@ -113,9 +117,9 @@ const TechDocsIndexPage = createPageExtension({ defaultPath: '/docs', routeRef: convertLegacyRouteRef(rootRouteRef), loader: () => - import('./home/components/TechDocsIndexPage').then(m => ( - - )), + import('./home/components/TechDocsIndexPage').then(m => + compatWrapper(), + ), }); /** @@ -128,9 +132,9 @@ const TechDocsReaderPage = createPageExtension({ defaultPath: '/docs/:namespace/:kind/:name', routeRef: convertLegacyRouteRef(rootDocsRouteRef), loader: () => - import('./reader/components/TechDocsReaderPage').then(m => ( - - )), + import('./reader/components/TechDocsReaderPage').then(m => + compatWrapper(), + ), }); /** @@ -141,7 +145,8 @@ const TechDocsReaderPage = createPageExtension({ const TechDocsEntityContent = createEntityContentExtension({ defaultPath: 'docs', defaultTitle: 'TechDocs', - loader: () => import('./Router').then(m => ), + loader: () => + import('./Router').then(m => compatWrapper()), }); /** @alpha */ diff --git a/plugins/user-settings/src/alpha.tsx b/plugins/user-settings/src/alpha.tsx index cda8301e82..9ee2e4a7c5 100644 --- a/plugins/user-settings/src/alpha.tsx +++ b/plugins/user-settings/src/alpha.tsx @@ -19,7 +19,10 @@ import { createPageExtension, createPlugin, } from '@backstage/frontend-plugin-api'; -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { + convertLegacyRouteRef, + compatWrapper, +} from '@backstage/core-compat-api'; import { settingsRouteRef } from './plugin'; import React from 'react'; @@ -38,11 +41,13 @@ const UserSettingsPage = createPageExtension({ ), }, loader: ({ inputs }) => - import('./components/SettingsPage').then(m => ( - - )), + import('./components/SettingsPage').then(m => + compatWrapper( + , + ), + ), }); /** diff --git a/yarn.lock b/yarn.lock index 2fd28cd48c..f38145a560 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3855,10 +3855,13 @@ __metadata: "@backstage/core-app-api": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/frontend-test-utils": "workspace:^" "@backstage/plugin-puppetdb": "workspace:^" "@backstage/plugin-stackstorm": "workspace:^" + "@backstage/version-bridge": "workspace:^" "@oriflame/backstage-plugin-score-card": ^0.7.0 "@testing-library/jest-dom": ^6.0.0 + "@testing-library/react": ^14.0.0 "@types/react": ^16.13.1 || ^17.0.0 peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 @@ -7380,6 +7383,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/core-app-api": "workspace:^" + "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^"