Implemented a new 'getNodesByRoutePath' method in the AppTreeApi
Signed-off-by: Musaab Elfaqih <musaabe@spotify.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': patch
|
||||
'@backstage/frontend-app-api': patch
|
||||
---
|
||||
|
||||
Added 'getNodesByRoutePath' method to the AppTreeApi.
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
RouteResolutionApi,
|
||||
createApiFactory,
|
||||
routeResolutionApiRef,
|
||||
AppNode,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
AnyApiFactory,
|
||||
@@ -68,7 +69,8 @@ import { ApiRegistry } from '../../../core-app-api/src/apis/system/ApiRegistry';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';
|
||||
import { BackstageRouteObject } from '../routing/types';
|
||||
import { FrontendFeature } from './types';
|
||||
import { FrontendFeature, RouteInfo } from './types';
|
||||
import { matchRoutes } from 'react-router-dom';
|
||||
|
||||
function deduplicateFeatures(
|
||||
allFeatures: FrontendFeature[],
|
||||
@@ -95,21 +97,47 @@ function deduplicateFeatures(
|
||||
|
||||
// Helps delay callers from reaching out to the API before the app tree has been materialized
|
||||
class AppTreeApiProxy implements AppTreeApi {
|
||||
#safeToUse: boolean = false;
|
||||
#routeInfo?: RouteInfo;
|
||||
|
||||
constructor(private readonly tree: AppTree) {}
|
||||
constructor(
|
||||
private readonly tree: AppTree,
|
||||
private readonly appBasePath: string,
|
||||
) {}
|
||||
|
||||
getTree() {
|
||||
if (!this.#safeToUse) {
|
||||
private checkIfInitialized() {
|
||||
if (!this.#routeInfo) {
|
||||
throw new Error(
|
||||
`You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getTree() {
|
||||
this.checkIfInitialized();
|
||||
|
||||
return { tree: this.tree };
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.#safeToUse = true;
|
||||
getNodesByRoutePath(sourcePath: string): { nodes: AppNode[] } {
|
||||
this.checkIfInitialized();
|
||||
|
||||
let path = sourcePath;
|
||||
if (sourcePath.startsWith(this.appBasePath)) {
|
||||
path = sourcePath.slice(this.appBasePath.length);
|
||||
}
|
||||
|
||||
const matchedRoutes = matchRoutes(this.#routeInfo!.routeObjects, path);
|
||||
|
||||
const matchedAppNodes =
|
||||
matchedRoutes
|
||||
?.filter(routeObj => !!routeObj.route.appNode)
|
||||
.map(routeObj => routeObj.route.appNode!) || [];
|
||||
|
||||
return { nodes: matchedAppNodes };
|
||||
}
|
||||
|
||||
initialize(routeInfo: RouteInfo) {
|
||||
this.#routeInfo = routeInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,12 +147,11 @@ class RouteResolutionApiProxy implements RouteResolutionApi {
|
||||
#routeObjects: BackstageRouteObject[] | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly tree: AppTree,
|
||||
private readonly routeBindings: Map<
|
||||
ExternalRouteRef,
|
||||
RouteRef | SubRouteRef
|
||||
>,
|
||||
private readonly basePath: string,
|
||||
private readonly appBasePath: string,
|
||||
) {}
|
||||
|
||||
resolve<TParams extends AnyRouteRefParams>(
|
||||
@@ -143,15 +170,13 @@ class RouteResolutionApiProxy implements RouteResolutionApi {
|
||||
return this.#delegate.resolve(anyRouteRef, options);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
const routeInfo = extractRouteInfoFromAppNode(this.tree.root);
|
||||
|
||||
initialize(routeInfo: RouteInfo) {
|
||||
this.#delegate = new RouteResolver(
|
||||
routeInfo.routePaths,
|
||||
routeInfo.routeParents,
|
||||
routeInfo.routeObjects,
|
||||
this.routeBindings,
|
||||
this.basePath,
|
||||
this.appBasePath,
|
||||
);
|
||||
this.#routeObjects = routeInfo.routeObjects;
|
||||
|
||||
@@ -190,15 +215,15 @@ export function createSpecializedApp(options?: {
|
||||
);
|
||||
|
||||
const factories = createApiFactories({ tree });
|
||||
const appTreeApi = new AppTreeApiProxy(tree);
|
||||
const appBasePath = getBasePath(config);
|
||||
const appTreeApi = new AppTreeApiProxy(tree, appBasePath);
|
||||
const routeResolutionApi = new RouteResolutionApiProxy(
|
||||
tree,
|
||||
resolveRouteBindings(
|
||||
options?.bindRoutes,
|
||||
config,
|
||||
collectRouteIds(features),
|
||||
),
|
||||
getBasePath(config),
|
||||
appBasePath,
|
||||
);
|
||||
|
||||
const appIdentityProxy = new AppIdentityProxy();
|
||||
@@ -237,8 +262,10 @@ export function createSpecializedApp(options?: {
|
||||
// Now instantiate the entire tree, which will skip anything that's already been instantiated
|
||||
instantiateAppNodeTree(tree.root, apiHolder);
|
||||
|
||||
routeResolutionApi.initialize();
|
||||
appTreeApi.initialize();
|
||||
const routeInfo = extractRouteInfoFromAppNode(tree.root);
|
||||
|
||||
routeResolutionApi.initialize(routeInfo);
|
||||
appTreeApi.initialize(routeInfo);
|
||||
|
||||
const rootEl = tree.root.instance!.getData(coreExtensionData.reactElement);
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { RouteRef } from '@backstage/frontend-plugin-api';
|
||||
import { FrontendModule, FrontendPlugin } from '@backstage/frontend-plugin-api';
|
||||
import { BackstageRouteObject } from '../routing/types';
|
||||
|
||||
/** @public */
|
||||
export type FrontendFeature =
|
||||
@@ -22,3 +24,10 @@ export type FrontendFeature =
|
||||
// TODO(blam): This is just forwards backwards compatibility, remove after v1.31.0
|
||||
| { $$type: '@backstage/ExtensionOverrides' }
|
||||
| { $$type: '@backstage/BackstagePlugin' };
|
||||
|
||||
/** @internal */
|
||||
export type RouteInfo = {
|
||||
routePaths: Map<RouteRef, string>;
|
||||
routeParents: Map<RouteRef, RouteRef | undefined>;
|
||||
routeObjects: BackstageRouteObject[];
|
||||
};
|
||||
|
||||
@@ -300,6 +300,9 @@ export interface AppTree {
|
||||
|
||||
// @public
|
||||
export interface AppTreeApi {
|
||||
getNodesByRoutePath(sourcePath: string): {
|
||||
nodes: AppNode[];
|
||||
};
|
||||
getTree(): {
|
||||
tree: AppTree;
|
||||
};
|
||||
|
||||
@@ -104,6 +104,11 @@ export interface AppTreeApi {
|
||||
* Get the {@link AppTree} for the app.
|
||||
*/
|
||||
getTree(): { tree: AppTree };
|
||||
|
||||
/**
|
||||
* Get all nodes in the app that are mounted at a given route path.
|
||||
*/
|
||||
getNodesByRoutePath(sourcePath: string): { nodes: AppNode[] };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user