core-app-api: moved out and delay the route binding logic
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -35,31 +35,9 @@ import {
|
||||
createRoutableExtension,
|
||||
analyticsApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { generateBoundRoutes, AppManager } from './AppManager';
|
||||
import { AppManager } from './AppManager';
|
||||
import { AppComponents, AppIcons } from './types';
|
||||
|
||||
describe('generateBoundRoutes', () => {
|
||||
it('runs happy path', () => {
|
||||
const external = { myRoute: createExternalRouteRef({ id: '1' }) };
|
||||
const ref = createRouteRef({ id: 'ref-1' });
|
||||
const result = generateBoundRoutes(({ bind }) => {
|
||||
bind(external, { myRoute: ref });
|
||||
});
|
||||
|
||||
expect(result.get(external.myRoute)).toBe(ref);
|
||||
});
|
||||
|
||||
it('throws on unknown keys', () => {
|
||||
const external = { myRoute: createExternalRouteRef({ id: '2' }) };
|
||||
const ref = createRouteRef({ id: 'ref-2' });
|
||||
expect(() =>
|
||||
generateBoundRoutes(({ bind }) => {
|
||||
bind(external, { someOtherRoute: ref } as any);
|
||||
}),
|
||||
).toThrow('Key someOtherRoute is not an existing external route');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Integration Test', () => {
|
||||
const noOpAnalyticsApi = createApiFactory(
|
||||
analyticsApiRef,
|
||||
|
||||
@@ -45,9 +45,6 @@ import {
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
BackstagePlugin,
|
||||
RouteRef,
|
||||
SubRouteRef,
|
||||
ExternalRouteRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { ApiFactoryRegistry, ApiResolver } from '../apis/system';
|
||||
import {
|
||||
@@ -75,13 +72,13 @@ import {
|
||||
AppConfigLoader,
|
||||
AppContext,
|
||||
AppOptions,
|
||||
AppRouteBinder,
|
||||
BackstageApp,
|
||||
SignInPageProps,
|
||||
} from './types';
|
||||
import { AppThemeProvider } from './AppThemeProvider';
|
||||
import { defaultConfigLoader } from './defaultConfigLoader';
|
||||
import { ApiRegistry } from '../apis/system/ApiRegistry';
|
||||
import { resolveRouteBindings } from './resolveRouteBindings';
|
||||
|
||||
type CompatiblePlugin =
|
||||
| BackstagePlugin<any, any>
|
||||
@@ -89,35 +86,6 @@ type CompatiblePlugin =
|
||||
output(): Array<{ type: 'feature-flag'; name: string }>;
|
||||
});
|
||||
|
||||
export function generateBoundRoutes(bindRoutes: AppOptions['bindRoutes']) {
|
||||
const result = new Map<ExternalRouteRef, RouteRef | SubRouteRef>();
|
||||
|
||||
if (bindRoutes) {
|
||||
const bind: AppRouteBinder = (
|
||||
externalRoutes,
|
||||
targetRoutes: { [name: string]: RouteRef | SubRouteRef },
|
||||
) => {
|
||||
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`);
|
||||
}
|
||||
if (!value && !externalRoute.optional) {
|
||||
throw new Error(
|
||||
`External route ${key} is required but was undefined`,
|
||||
);
|
||||
}
|
||||
if (value) {
|
||||
result.set(externalRoute, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
bindRoutes({ bind });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the app base path from the configured app baseUrl.
|
||||
*
|
||||
@@ -196,7 +164,7 @@ export class AppManager implements BackstageApp {
|
||||
private readonly themes: AppTheme[];
|
||||
private readonly configLoader?: AppConfigLoader;
|
||||
private readonly defaultApis: Iterable<AnyApiFactory>;
|
||||
private readonly routeBindings: Map<ExternalRouteRef, RouteRef | SubRouteRef>;
|
||||
private readonly bindRoutes: AppOptions['bindRoutes'];
|
||||
|
||||
private readonly appIdentityProxy = new AppIdentityProxy();
|
||||
private readonly apiFactoryRegistry: ApiFactoryRegistry;
|
||||
@@ -209,7 +177,7 @@ export class AppManager implements BackstageApp {
|
||||
this.themes = options.themes as AppTheme[];
|
||||
this.configLoader = options.configLoader ?? defaultConfigLoader;
|
||||
this.defaultApis = options.defaultApis ?? [];
|
||||
this.routeBindings = generateBoundRoutes(options.bindRoutes);
|
||||
this.bindRoutes = options.bindRoutes;
|
||||
this.apiFactoryRegistry = new ApiFactoryRegistry();
|
||||
}
|
||||
|
||||
@@ -237,37 +205,45 @@ export class AppManager implements BackstageApp {
|
||||
[],
|
||||
);
|
||||
|
||||
const { routePaths, routeParents, routeObjects, featureFlags } =
|
||||
useMemo(() => {
|
||||
const result = traverseElementTree({
|
||||
root: children,
|
||||
discoverers: [childDiscoverer, routeElementDiscoverer],
|
||||
collectors: {
|
||||
routePaths: routePathCollector,
|
||||
routeParents: routeParentCollector,
|
||||
routeObjects: routeObjectCollector,
|
||||
collectedPlugins: pluginCollector,
|
||||
featureFlags: featureFlagCollector,
|
||||
},
|
||||
});
|
||||
const {
|
||||
routePaths,
|
||||
routeParents,
|
||||
routeObjects,
|
||||
featureFlags,
|
||||
routeBindings,
|
||||
} = useMemo(() => {
|
||||
const result = traverseElementTree({
|
||||
root: children,
|
||||
discoverers: [childDiscoverer, routeElementDiscoverer],
|
||||
collectors: {
|
||||
routePaths: routePathCollector,
|
||||
routeParents: routeParentCollector,
|
||||
routeObjects: routeObjectCollector,
|
||||
collectedPlugins: pluginCollector,
|
||||
featureFlags: featureFlagCollector,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO(Rugvip): Restructure the public API so that we can get an immediate view of
|
||||
// the app, rather than having to wait for the provider to render.
|
||||
// For now we need to push the additional plugins we find during
|
||||
// collection and then make sure we initialize things afterwards.
|
||||
result.collectedPlugins.forEach(plugin => this.plugins.add(plugin));
|
||||
this.verifyPlugins(this.plugins);
|
||||
// TODO(Rugvip): Restructure the public API so that we can get an immediate view of
|
||||
// the app, rather than having to wait for the provider to render.
|
||||
// For now we need to push the additional plugins we find during
|
||||
// collection and then make sure we initialize things afterwards.
|
||||
result.collectedPlugins.forEach(plugin => this.plugins.add(plugin));
|
||||
this.verifyPlugins(this.plugins);
|
||||
|
||||
// Initialize APIs once all plugins are available
|
||||
this.getApiHolder();
|
||||
return result;
|
||||
}, [children]);
|
||||
// Initialize APIs once all plugins are available
|
||||
this.getApiHolder();
|
||||
return {
|
||||
...result,
|
||||
routeBindings: resolveRouteBindings(this.bindRoutes),
|
||||
};
|
||||
}, [children]);
|
||||
|
||||
if (!routesHaveBeenValidated) {
|
||||
routesHaveBeenValidated = true;
|
||||
validateRouteParameters(routePaths, routeParents);
|
||||
validateRouteBindings(
|
||||
this.routeBindings,
|
||||
routeBindings,
|
||||
this.plugins as Iterable<BackstagePlugin<any, any>>,
|
||||
);
|
||||
}
|
||||
@@ -331,7 +307,7 @@ export class AppManager implements BackstageApp {
|
||||
routePaths={routePaths}
|
||||
routeParents={routeParents}
|
||||
routeObjects={routeObjects}
|
||||
routeBindings={this.routeBindings}
|
||||
routeBindings={routeBindings}
|
||||
basePath={getBasePath(loadedConfig.api)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* 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 '@backstage/core-plugin-api';
|
||||
import { resolveRouteBindings } from './resolveRouteBindings';
|
||||
|
||||
describe('resolveRouteBindings', () => {
|
||||
it('runs happy path', () => {
|
||||
const external = { myRoute: createExternalRouteRef({ id: '1' }) };
|
||||
const ref = createRouteRef({ id: 'ref-1' });
|
||||
const result = resolveRouteBindings(({ bind }) => {
|
||||
bind(external, { myRoute: ref });
|
||||
});
|
||||
|
||||
expect(result.get(external.myRoute)).toBe(ref);
|
||||
});
|
||||
|
||||
it('throws on unknown keys', () => {
|
||||
const external = { myRoute: createExternalRouteRef({ id: '2' }) };
|
||||
const ref = createRouteRef({ id: 'ref-2' });
|
||||
expect(() =>
|
||||
resolveRouteBindings(({ bind }) => {
|
||||
bind(external, { someOtherRoute: ref } as any);
|
||||
}),
|
||||
).toThrow('Key someOtherRoute is not an existing external route');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
RouteRef,
|
||||
SubRouteRef,
|
||||
ExternalRouteRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { AppOptions, AppRouteBinder } from './types';
|
||||
|
||||
export function resolveRouteBindings(bindRoutes: AppOptions['bindRoutes']) {
|
||||
const result = new Map<ExternalRouteRef, RouteRef | SubRouteRef>();
|
||||
|
||||
if (bindRoutes) {
|
||||
const bind: AppRouteBinder = (
|
||||
externalRoutes,
|
||||
targetRoutes: { [name: string]: RouteRef | SubRouteRef },
|
||||
) => {
|
||||
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`);
|
||||
}
|
||||
if (!value && !externalRoute.optional) {
|
||||
throw new Error(
|
||||
`External route ${key} is required but was undefined`,
|
||||
);
|
||||
}
|
||||
if (value) {
|
||||
result.set(externalRoute, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
bindRoutes({ bind });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user