core-api: run bindRoutes in the app impl and inject provider
This commit is contained in:
committed by
Patrik Oldsberg
parent
ad5eab923c
commit
5d52e20a44
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { createExternalRouteRef, createRouteRef } from '../routing/RouteRef';
|
||||
import { generateBoundRoutes } from './App';
|
||||
|
||||
describe('generateBoundRoutes', () => {
|
||||
it('runs happy path', () => {
|
||||
const external = { myRoute: createExternalRouteRef() };
|
||||
const ref = createRouteRef({ path: '', title: '' });
|
||||
const result = generateBoundRoutes(({ bind }) => {
|
||||
bind(external, { myRoute: ref });
|
||||
});
|
||||
|
||||
expect(result.get(external.myRoute)).toBe(ref);
|
||||
});
|
||||
|
||||
it('throws on unknown keys', () => {
|
||||
const external = { myRoute: createExternalRouteRef() };
|
||||
const ref = createRouteRef({ path: '', title: '' });
|
||||
expect(() =>
|
||||
generateBoundRoutes(({ bind }) => {
|
||||
bind(external, { someOtherRoute: ref } as any);
|
||||
}),
|
||||
).toThrow('Key someOtherRoute is not an existing external route');
|
||||
});
|
||||
});
|
||||
@@ -15,46 +15,82 @@
|
||||
*/
|
||||
import React, {
|
||||
ComponentType,
|
||||
PropsWithChildren,
|
||||
ReactElement,
|
||||
useMemo,
|
||||
useState,
|
||||
ReactElement,
|
||||
PropsWithChildren,
|
||||
} from 'react';
|
||||
import { Route, Routes, Navigate } from 'react-router-dom';
|
||||
import { AppContextProvider } from './AppContext';
|
||||
import {
|
||||
BackstageApp,
|
||||
AppComponents,
|
||||
AppConfigLoader,
|
||||
SignInResult,
|
||||
SignInPageProps,
|
||||
} from './types';
|
||||
import { BackstagePlugin } from '../plugin';
|
||||
import {
|
||||
featureFlagsApiRef,
|
||||
AppThemeApi,
|
||||
ConfigApi,
|
||||
identityApiRef,
|
||||
} from '../apis/definitions';
|
||||
import { AppThemeProvider } from './AppThemeProvider';
|
||||
|
||||
import { IconComponent, SystemIcons, SystemIconKey } from '../icons';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import { useAsync } from 'react-use';
|
||||
import {
|
||||
AnyApiFactory,
|
||||
ApiHolder,
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
AppTheme,
|
||||
AppThemeSelector,
|
||||
appThemeApiRef,
|
||||
AppThemeSelector,
|
||||
configApiRef,
|
||||
ConfigReader,
|
||||
useApi,
|
||||
AnyApiFactory,
|
||||
ApiHolder,
|
||||
LocalStorageFeatureFlags,
|
||||
useApi,
|
||||
} from '../apis';
|
||||
import { useAsync } from 'react-use';
|
||||
import {
|
||||
AppThemeApi,
|
||||
ConfigApi,
|
||||
featureFlagsApiRef,
|
||||
identityApiRef,
|
||||
} from '../apis/definitions';
|
||||
import { ApiFactoryRegistry, ApiResolver } from '../apis/system';
|
||||
import {
|
||||
childDiscoverer,
|
||||
routeElementDiscoverer,
|
||||
traverseElementTree,
|
||||
} from '../extensions/traversal';
|
||||
import { IconComponent, SystemIconKey, SystemIcons } from '../icons';
|
||||
import { BackstagePlugin } from '../plugin';
|
||||
import { RouteRef } from '../routing';
|
||||
import {
|
||||
routeObjectCollector,
|
||||
routeParentCollector,
|
||||
routePathCollector,
|
||||
} from '../routing/collectors';
|
||||
import { RoutingProvider } from '../routing/hooks';
|
||||
import { ExternalRouteRef } from '../routing/RouteRef';
|
||||
import { AppContextProvider } from './AppContext';
|
||||
import { AppIdentity } from './AppIdentity';
|
||||
import { ApiResolver, ApiFactoryRegistry } from '../apis/system';
|
||||
import { AppThemeProvider } from './AppThemeProvider';
|
||||
import {
|
||||
AppComponents,
|
||||
AppConfigLoader,
|
||||
AppOptions,
|
||||
BackstageApp,
|
||||
BindRouteFunc,
|
||||
SignInPageProps,
|
||||
SignInResult,
|
||||
} from './types';
|
||||
|
||||
export function generateBoundRoutes(
|
||||
bindRoutes: AppOptions['bindRoutes'],
|
||||
): Map<ExternalRouteRef, RouteRef> {
|
||||
const result = new Map<ExternalRouteRef, RouteRef>();
|
||||
|
||||
if (bindRoutes) {
|
||||
const bind: BindRouteFunc = (externalRoutes, targetRoutes) => {
|
||||
for (const [key, value] of Object.entries(targetRoutes)) {
|
||||
const externalRoute = externalRoutes[key];
|
||||
if (!externalRoute) {
|
||||
throw new Error(`Key ${key} is not an existing external route`);
|
||||
}
|
||||
|
||||
result.set(externalRoute, value);
|
||||
}
|
||||
};
|
||||
bindRoutes({ bind });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
type FullAppOptions = {
|
||||
apis: Iterable<AnyApiFactory>;
|
||||
@@ -64,6 +100,7 @@ type FullAppOptions = {
|
||||
themes: AppTheme[];
|
||||
configLoader?: AppConfigLoader;
|
||||
defaultApis: Iterable<AnyApiFactory>;
|
||||
bindRoutes?: AppOptions['bindRoutes'];
|
||||
};
|
||||
|
||||
function useConfigLoader(
|
||||
@@ -112,6 +149,7 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
private readonly themes: AppTheme[];
|
||||
private readonly configLoader?: AppConfigLoader;
|
||||
private readonly defaultApis: Iterable<AnyApiFactory>;
|
||||
private readonly bindRoutes: AppOptions['bindRoutes'];
|
||||
|
||||
private readonly identityApi = new AppIdentity();
|
||||
|
||||
@@ -123,6 +161,7 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
this.themes = options.themes;
|
||||
this.configLoader = options.configLoader;
|
||||
this.defaultApis = options.defaultApis;
|
||||
this.bindRoutes = options.bindRoutes;
|
||||
}
|
||||
|
||||
getPlugins(): BackstagePlugin<any, any>[] {
|
||||
@@ -202,6 +241,17 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
[],
|
||||
);
|
||||
|
||||
// Probably want to memo this?
|
||||
const { routePaths, routeParents, routeObjects } = traverseElementTree({
|
||||
root: children,
|
||||
discoverers: [childDiscoverer, routeElementDiscoverer],
|
||||
collectors: {
|
||||
routePaths: routePathCollector,
|
||||
routeParents: routeParentCollector,
|
||||
routeObjects: routeObjectCollector,
|
||||
},
|
||||
});
|
||||
|
||||
const loadedConfig = useConfigLoader(
|
||||
this.configLoader,
|
||||
this.components,
|
||||
@@ -218,7 +268,16 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
return (
|
||||
<ApiProvider apis={this.getApiHolder()}>
|
||||
<AppContextProvider app={this}>
|
||||
<AppThemeProvider>{children}</AppThemeProvider>
|
||||
<AppThemeProvider>
|
||||
<RoutingProvider
|
||||
routePaths={routePaths}
|
||||
routeParents={routeParents}
|
||||
routeObjects={routeObjects}
|
||||
routeBindings={generateBoundRoutes(this.bindRoutes)}
|
||||
>
|
||||
{children}
|
||||
</RoutingProvider>
|
||||
</AppThemeProvider>
|
||||
</AppContextProvider>
|
||||
</ApiProvider>
|
||||
);
|
||||
|
||||
@@ -79,7 +79,7 @@ export type AppComponents = {
|
||||
*/
|
||||
export type AppConfigLoader = () => Promise<AppConfig[]>;
|
||||
|
||||
type BindRouteFunc = <T extends AnyExternalRoutes>(
|
||||
export type BindRouteFunc = <T extends AnyExternalRoutes>(
|
||||
externalRoutes: T,
|
||||
targetRoutes: { [key in keyof T]: RouteRef<any> },
|
||||
) => void;
|
||||
|
||||
@@ -142,6 +142,7 @@ export function createApp(options?: AppOptions) {
|
||||
themes,
|
||||
configLoader,
|
||||
defaultApis,
|
||||
bindRoutes: options?.bindRoutes,
|
||||
});
|
||||
|
||||
app.verify();
|
||||
|
||||
@@ -80,6 +80,7 @@ export function wrapInTestApp(
|
||||
},
|
||||
],
|
||||
defaultApis: mockApis,
|
||||
bindRoutes: () => {},
|
||||
});
|
||||
|
||||
let Wrapper: ComponentType;
|
||||
|
||||
Reference in New Issue
Block a user