core-compat-api: add forwards compat for app context

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-08-03 12:42:46 +02:00
parent 67e76f2bb9
commit 08528670dc
5 changed files with 145 additions and 10 deletions
+1
View File
@@ -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"
@@ -50,7 +50,9 @@ const legacyPluginStore = getOrCreateGlobalSingleton(
() => new WeakMap<NewBackstagePlugin, LegacyBackstagePlugin>(),
);
function toLegacyPlugin(plugin: NewBackstagePlugin): LegacyBackstagePlugin {
export function toLegacyPlugin(
plugin: NewBackstagePlugin,
): LegacyBackstagePlugin {
let legacy = legacyPluginStore.get(plugin);
if (legacy) {
return legacy;
@@ -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<CoreProgressProps>;
readonly #NotFoundErrorPage: ComponentType<CoreNotFoundErrorPageProps>;
readonly #ErrorBoundaryFallback: ComponentType<CoreErrorBoundaryFallbackProps>;
constructor(app: AppContext) {
const components = app.getComponents();
const ErrorBoundaryFallback = (props: CoreErrorBoundaryFallbackProps) => (
<components.ErrorBoundaryFallback
{...props}
plugin={props.plugin && toLegacyPlugin(props.plugin)}
/>
);
this.#Progress = components.Progress;
this.#NotFoundErrorPage = components.NotFoundErrorPage;
this.#ErrorBoundaryFallback = ErrorBoundaryFallback;
}
getComponent<T extends {}>(ref: ComponentRef<T>): ComponentType<T> {
switch (ref.id) {
case coreComponentRefs.progress.id:
return this.#Progress as ComponentType<any>;
case coreComponentRefs.notFoundErrorPage.id:
return this.#NotFoundErrorPage as ComponentType<any>;
case coreComponentRefs.errorBoundaryFallback.id:
return this.#ErrorBoundaryFallback as ComponentType<any>;
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<T>(ref: ApiRef<any>): 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 <ApiProvider apis={appFallbackApis}>{props.children}</ApiProvider>;
}
export function ForwardsCompatProvider(props: { children: ReactNode }) {
// TODO(Rugvip): Implement
return <>{props.children}</>;
return <NewAppApisProvider>{props.children}</NewAppApisProvider>;
}
@@ -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 <div>link: {link()}</div>;
}
renderInTestApp(compatWrapper(<Component />), {
renderInNewTestApp(compatWrapper(<Component />), {
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 (
<div data-testid="ctx">
components:{' '}
{Object.entries(coreComponentRefs)
.map(
([name, ref]) =>
`${name}=${Boolean(components.getComponent(ref))}`,
)
.join(', ')}
{'\n'}
icons: {icons.listIconKeys().join(', ')}
</div>
);
}
await renderInOldTestApp(compatWrapper(<Component />));
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"
`);
});
});
+1
View File
@@ -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