frontend-app-api: updates to use new routing API

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-10-12 14:30:32 +02:00
parent f4efd3d30a
commit 2c09f44cce
5 changed files with 88 additions and 24 deletions
@@ -20,8 +20,8 @@ import {
coreExtensionData,
createExtensionInput,
NavTarget,
useRouteRef,
} from '@backstage/frontend-plugin-api';
import { useRouteRef } from '@backstage/core-plugin-api';
import { makeStyles } from '@material-ui/core';
import {
Sidebar,
@@ -66,7 +66,7 @@ const SidebarLogo = () => {
const SidebarNavItem = (props: NavTarget) => {
const { icon: Icon, title, routeRef } = props;
const to = useRouteRef(routeRef)({});
const to = useRouteRef(routeRef)();
// TODO: Support opening modal, for example, the search one
return <SidebarItem to={to} icon={Icon} text={title} />;
};
@@ -15,28 +15,27 @@
*/
import React from 'react';
import {
BackstagePlugin,
RouteRef,
createRouteRef,
} from '@backstage/core-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { extractRouteInfoFromInstanceTree } from './extractRouteInfoFromInstanceTree';
import {
AnyRouteParams,
Extension,
RouteRef,
coreExtensionData,
createExtension,
createExtensionInput,
createPlugin,
createRouteRef,
} from '@backstage/frontend-plugin-api';
import { createInstances } from '../wiring/createApp';
import { MockConfigApi } from '@backstage/test-utils';
const ref1 = createRouteRef({ id: 'page1' });
const ref2 = createRouteRef({ id: 'page2' });
const ref3 = createRouteRef({ id: 'page3' });
const ref4 = createRouteRef({ id: 'page4' });
const ref5 = createRouteRef({ id: 'page5' });
const refOrder = [ref1, ref2, ref3, ref4, ref5];
const ref1 = createRouteRef();
const ref2 = createRouteRef();
const ref3 = createRouteRef();
const ref4 = createRouteRef();
const ref5 = createRouteRef();
const refOrder: RouteRef<AnyRouteParams>[] = [ref1, ref2, ref3, ref4, ref5];
function createTestExtension(options: {
id: string;
@@ -14,12 +14,24 @@
* limitations under the License.
*/
import { RouteRef } from '@backstage/core-plugin-api';
import { coreExtensionData } from '@backstage/frontend-plugin-api';
import { RouteRef, coreExtensionData } from '@backstage/frontend-plugin-api';
import { ExtensionInstance } from '../wiring/createExtensionInstance';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { BackstageRouteObject } from '../../../core-app-api/src/routing/types';
import { toLegacyPlugin } from '../wiring/createApp';
import { BackstagePlugin as LegacyBackstagePlugin } from '@backstage/core-plugin-api';
/**
* A duplicate of the react-router RouteObject, but with routeRef added
* @internal
*/
export interface BackstageRouteObject {
caseSensitive: boolean;
children?: BackstageRouteObject[];
element: React.ReactNode;
path: string;
routeRefs: Set<RouteRef>;
plugins: Set<LegacyBackstagePlugin>;
}
// We always add a child that matches all subroutes but without any route refs. This makes
// sure that we're always able to match each route no matter how deep the navigation goes.
@@ -19,13 +19,13 @@ import {
createExtensionOverrides,
createPageExtension,
createPlugin,
createRouteRef,
createThemeExtension,
} from '@backstage/frontend-plugin-api';
import { createApp, createInstances } from './createApp';
import { screen } from '@testing-library/react';
import { MockConfigApi, renderWithEffects } from '@backstage/test-utils';
import React from 'react';
import { createRouteRef } from '@backstage/core-plugin-api';
const extBaseConfig = {
id: 'test',
@@ -83,14 +83,14 @@ describe('createInstances', () => {
const ExtensionA = createPageExtension({
id: 'A',
defaultPath: '/',
routeRef: createRouteRef({ id: 'A.route' }),
routeRef: createRouteRef(),
loader: async () => <div>Extension A</div>,
});
const ExtensionB = createPageExtension({
id: 'B',
defaultPath: '/',
routeRef: createRouteRef({ id: 'B.route' }),
routeRef: createRouteRef(),
loader: async () => <div>Extension B</div>,
});
@@ -21,6 +21,10 @@ import {
coreExtensionData,
ExtensionDataRef,
ExtensionOverrides,
ExternalRouteRef,
RouteRef,
SubRouteRef,
useRouteRef,
} from '@backstage/frontend-plugin-api';
import { Core } from '../extensions/Core';
import { CoreRoutes } from '../extensions/CoreRoutes';
@@ -44,11 +48,9 @@ import {
ConfigApi,
configApiRef,
IconComponent,
RouteRef,
BackstagePlugin as LegacyBackstagePlugin,
featureFlagsApiRef,
attachComponentData,
useRouteRef,
identityApiRef,
AppTheme,
} from '@backstage/core-plugin-api';
@@ -57,7 +59,6 @@ import {
ApiFactoryRegistry,
ApiProvider,
ApiResolver,
AppRouteBinder,
AppThemeSelector,
} from '@backstage/core-app-api';
@@ -98,6 +99,57 @@ import {
translationApiRef,
} from '@backstage/core-plugin-api/alpha';
/**
* Extracts a union of the keys in a map whose value extends the given type
*
* @ignore
*/
type KeysWithType<Obj extends { [key in string]: any }, Type> = {
[key in keyof Obj]: Obj[key] extends Type ? key : never;
}[keyof Obj];
/**
* Takes a map Map required values and makes all keys matching Keys optional
*
* @ignore
*/
type PartialKeys<
Map extends { [name in string]: any },
Keys extends keyof Map,
> = Partial<Pick<Map, Keys>> & Required<Omit<Map, Keys>>;
/**
* Creates a map of target routes with matching parameters based on a map of external routes.
*
* @ignore
*/
type TargetRouteMap<
ExternalRoutes extends { [name: string]: ExternalRouteRef },
> = {
[name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef<
infer Params,
any
>
? RouteRef<Params> | SubRouteRef<Params>
: never;
};
/**
* A function that can bind from external routes of a given plugin, to concrete
* routes of other plugins. See {@link createApp}.
*
* @public
*/
export type AppRouteBinder = <
TExternalRoutes extends { [name: string]: ExternalRouteRef },
>(
externalRoutes: TExternalRoutes,
targetRoutes: PartialKeys<
TargetRouteMap<TExternalRoutes>,
KeysWithType<TExternalRoutes, ExternalRouteRef<any, true>>
>,
) => void;
/** @public */
export interface ExtensionTreeNode {
id: string;
@@ -312,8 +364,9 @@ export function createApp(options: {
<AppContextProvider appContext={appContext}>
<AppThemeProvider>
<RoutingProvider
{...extractRouteInfoFromInstanceTree(coreInstance)}
routeBindings={resolveRouteBindings(options.bindRoutes)}
// TODO(Rugvip): Move over routing app API to new system to avoid these casts
{...(extractRouteInfoFromInstanceTree(coreInstance) as any)}
routeBindings={resolveRouteBindings(options.bindRoutes as any)}
>
{/* TODO: set base path using the logic from AppRouter */}
<BrowserRouter>