core-compat-api: add forwards compat for app context
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -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"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user