diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index a76f7878dd..4a143a50c7 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -44,6 +44,7 @@ "@backstage/frontend-app-api": "workspace:^", "@backstage/frontend-test-utils": "workspace:^", "@backstage/plugin-catalog": "workspace:^", + "@backstage/test-utils": "workspace:^", "@oriflame/backstage-plugin-score-card": "^0.8.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^15.0.0" diff --git a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx index 288f3f900b..4f9ba3a836 100644 --- a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx +++ b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx @@ -50,7 +50,9 @@ const legacyPluginStore = getOrCreateGlobalSingleton( () => new WeakMap(), ); -function toLegacyPlugin(plugin: NewBackstagePlugin): LegacyBackstagePlugin { +export function toLegacyPlugin( + plugin: NewBackstagePlugin, +): LegacyBackstagePlugin { let legacy = legacyPluginStore.get(plugin); if (legacy) { return legacy; diff --git a/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx index 5544862aee..182b774cca 100644 --- a/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx +++ b/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx @@ -14,10 +14,106 @@ * limitations under the License. */ -import React from 'react'; +import { + ApiHolder, + ApiRef, + AppContext, + useApp, +} from '@backstage/core-plugin-api'; +import { + ComponentRef, + ComponentsApi, + CoreErrorBoundaryFallbackProps, + CoreNotFoundErrorPageProps, + CoreProgressProps, + IconComponent, + IconsApi, + componentsApiRef, + coreComponentRefs, + iconsApiRef, +} from '@backstage/frontend-plugin-api'; +import React, { ComponentType, useMemo } from 'react'; import { ReactNode } from 'react'; +import { toLegacyPlugin } from './BackwardsCompatProvider'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { ApiProvider } from '../../../core-app-api/src/apis/system/ApiProvider'; + +class CompatComponentsApi implements ComponentsApi { + readonly #Progress: ComponentType; + readonly #NotFoundErrorPage: ComponentType; + readonly #ErrorBoundaryFallback: ComponentType; + + constructor(app: AppContext) { + const components = app.getComponents(); + const ErrorBoundaryFallback = (props: CoreErrorBoundaryFallbackProps) => ( + + ); + this.#Progress = components.Progress; + this.#NotFoundErrorPage = components.NotFoundErrorPage; + this.#ErrorBoundaryFallback = ErrorBoundaryFallback; + } + + getComponent(ref: ComponentRef): ComponentType { + switch (ref.id) { + case coreComponentRefs.progress.id: + return this.#Progress as ComponentType; + case coreComponentRefs.notFoundErrorPage.id: + return this.#NotFoundErrorPage as ComponentType; + case coreComponentRefs.errorBoundaryFallback.id: + return this.#ErrorBoundaryFallback as ComponentType; + default: + throw new Error( + `No backwards compatible component is available for ref '${ref.id}'`, + ); + } + } +} + +class CompatIconsApi implements IconsApi { + readonly #app: AppContext; + + constructor(app: AppContext) { + this.#app = app; + } + + getIcon(key: string): IconComponent | undefined { + return this.#app.getSystemIcon(key); + } + + listIconKeys(): string[] { + return Object.keys(this.#app.getSystemIcons()); + } +} + +class AppFallbackApis implements ApiHolder { + readonly #componentsApi: ComponentsApi; + readonly #iconsApi: IconsApi; + + constructor(app: AppContext) { + this.#componentsApi = new CompatComponentsApi(app); + this.#iconsApi = new CompatIconsApi(app); + } + + get(ref: ApiRef): T | undefined { + if (ref.id === componentsApiRef.id) { + return this.#componentsApi as T; + } else if (ref.id === iconsApiRef.id) { + return this.#iconsApi as T; + } + return undefined; + } +} + +function NewAppApisProvider(props: { children: ReactNode }) { + const app = useApp(); + const appFallbackApis = useMemo(() => new AppFallbackApis(app), [app]); + + return {props.children}; +} export function ForwardsCompatProvider(props: { children: ReactNode }) { - // TODO(Rugvip): Implement - return <>{props.children}; + 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 index 74b9f209eb..b17ca2d508 100644 --- a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx +++ b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx @@ -16,21 +16,27 @@ import React from 'react'; import { + componentsApiRef, + coreComponentRefs, coreExtensionData, createExtension, + iconsApiRef, + useRouteRef as useNewRouteRef, + useApi, } from '@backstage/frontend-plugin-api'; import { createExtensionTester, - renderInTestApp, + renderInTestApp as renderInNewTestApp, } from '@backstage/frontend-test-utils'; import { screen } from '@testing-library/react'; import { compatWrapper } from './compatWrapper'; import { - createRouteRef, useApp, - useRouteRef, + useRouteRef as useOldRouteRef, + createRouteRef as createOldRouteRef, } from '@backstage/core-plugin-api'; import { convertLegacyRouteRef } from '../convertLegacyRouteRef'; +import { renderInTestApp as renderInOldTestApp } from '@backstage/test-utils'; describe('BackwardsCompatProvider', () => { it('should convert the app context', () => { @@ -74,17 +80,46 @@ describe('BackwardsCompatProvider', () => { }); it('should convert the routing context', () => { - const routeRef = createRouteRef({ id: 'test' }); + const routeRef = createOldRouteRef({ id: 'test' }); function Component() { - const link = useRouteRef(routeRef); + const link = useOldRouteRef(routeRef); return
link: {link()}
; } - renderInTestApp(compatWrapper(), { + renderInNewTestApp(compatWrapper(), { mountedRoutes: { '/test': convertLegacyRouteRef(routeRef) }, }); expect(screen.getByText('link: /test')).toBeInTheDocument(); }); }); + +describe('ForwardsCompatProvider', () => { + it('should convert the app context', async () => { + function Component() { + const components = useApi(componentsApiRef); + const icons = useApi(iconsApiRef); + return ( +
+ components:{' '} + {Object.entries(coreComponentRefs) + .map( + ([name, ref]) => + `${name}=${Boolean(components.getComponent(ref))}`, + ) + .join(', ')} + {'\n'} + icons: {icons.listIconKeys().join(', ')} +
+ ); + } + + await renderInOldTestApp(compatWrapper()); + + expect(screen.getByTestId('ctx').textContent).toMatchInlineSnapshot(` + "components: progress=true, notFoundErrorPage=true, errorBoundaryFallback=true + icons: kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, user, warning" + `); + }); +}); diff --git a/yarn.lock b/yarn.lock index c1dc608e87..e26ceb0aa6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4171,6 +4171,7 @@ __metadata: "@backstage/frontend-plugin-api": "workspace:^" "@backstage/frontend-test-utils": "workspace:^" "@backstage/plugin-catalog": "workspace:^" + "@backstage/test-utils": "workspace:^" "@backstage/version-bridge": "workspace:^" "@oriflame/backstage-plugin-score-card": ^0.8.0 "@testing-library/jest-dom": ^6.0.0