diff --git a/.changeset/swift-badgers-hide.md b/.changeset/swift-badgers-hide.md new file mode 100644 index 0000000000..217253370e --- /dev/null +++ b/.changeset/swift-badgers-hide.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Internal refactor to rename the app graph to app tree diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts index c6b025e8b3..c44c10180a 100644 --- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts +++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts @@ -28,7 +28,7 @@ import { createRouteRef, } from '@backstage/frontend-plugin-api'; import { MockConfigApi } from '@backstage/test-utils'; -import { createAppGraph } from '../graph'; +import { createAppTree } from '../tree'; import { Core } from '../extensions/Core'; import { CoreRoutes } from '../extensions/CoreRoutes'; import { CoreNav } from '../extensions/CoreNav'; @@ -77,13 +77,13 @@ function routeInfoFromExtensions(extensions: Extension[]) { id: 'test', extensions, }); - const graph = createAppGraph({ + const tree = createAppTree({ config: new MockConfigApi({}), builtinExtensions: [Core, CoreRoutes, CoreNav, CoreLayout], features: [plugin], }); - return extractRouteInfoFromAppNode(graph.root); + return extractRouteInfoFromAppNode(tree.root); } function sortedEntries(map: Map): [RouteRef, T][] { diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts index 71991ee44e..5700bf4eae 100644 --- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts +++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts @@ -18,7 +18,7 @@ import { RouteRef, coreExtensionData } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toLegacyPlugin } from '../wiring/createApp'; import { BackstageRouteObject } from './types'; -import { AppNode } from '../graph'; +import { AppNode } from '../tree'; // 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. diff --git a/packages/frontend-app-api/src/graph/createAppGraph.test.ts b/packages/frontend-app-api/src/tree/createAppTree.test.ts similarity index 91% rename from packages/frontend-app-api/src/graph/createAppGraph.test.ts rename to packages/frontend-app-api/src/tree/createAppTree.test.ts index 1a046b210b..667e3a6fc3 100644 --- a/packages/frontend-app-api/src/graph/createAppGraph.test.ts +++ b/packages/frontend-app-api/src/tree/createAppTree.test.ts @@ -20,7 +20,7 @@ import { createPlugin, } from '@backstage/frontend-plugin-api'; import { MockConfigApi } from '@backstage/test-utils'; -import { createAppGraph } from './createAppGraph'; +import { createAppTree } from './createAppTree'; const extBase = { id: 'test', @@ -29,7 +29,7 @@ const extBase = { factory: () => ({}), }; -describe('createAppGraph', () => { +describe('createAppTree', () => { it('throws an error when a core extension is parametrized', () => { const config = new MockConfigApi({ app: { @@ -47,7 +47,7 @@ describe('createAppGraph', () => { }), ]; expect(() => - createAppGraph({ features, config, builtinExtensions: [] }), + createAppTree({ features, config, builtinExtensions: [] }), ).toThrow("Configuration of the 'core' extension is forbidden"); }); @@ -68,7 +68,7 @@ describe('createAppGraph', () => { }), ]; expect(() => - createAppGraph({ features, config, builtinExtensions: [] }), + createAppTree({ features, config, builtinExtensions: [] }), ).toThrow( "It is forbidden to override the following extension(s): 'core', which is done by the following plugin(s): 'plugin'", ); @@ -94,7 +94,7 @@ describe('createAppGraph', () => { const features = [PluginA, PluginB]; expect(() => - createAppGraph({ features, config, builtinExtensions: [] }), + createAppTree({ features, config, builtinExtensions: [] }), ).toThrow( "The following extensions are duplicated: The extension 'A' was provided 2 time(s) by the plugin 'A' and 1 time(s) by the plugin 'B', The extension 'B' was provided 2 time(s) by the plugin 'B'", ); @@ -102,7 +102,7 @@ describe('createAppGraph', () => { it('throws an error when duplicated extension overrides are detected', () => { expect(() => - createAppGraph({ + createAppTree({ features: [ createExtensionOverrides({ extensions: [ diff --git a/packages/frontend-app-api/src/graph/createAppGraph.ts b/packages/frontend-app-api/src/tree/createAppTree.ts similarity index 81% rename from packages/frontend-app-api/src/graph/createAppGraph.ts rename to packages/frontend-app-api/src/tree/createAppTree.ts index ace8595f53..2c36d56bf7 100644 --- a/packages/frontend-app-api/src/graph/createAppGraph.ts +++ b/packages/frontend-app-api/src/tree/createAppTree.ts @@ -20,22 +20,22 @@ import { ExtensionOverrides, } from '@backstage/frontend-plugin-api'; import { readAppExtensionsConfig } from './readAppExtensionsConfig'; -import { resolveAppGraph } from './resolveAppGraph'; +import { resolveAppTree } from './resolveAppTree'; import { resolveAppNodeSpecs } from './resolveAppNodeSpecs'; -import { AppGraph } from './types'; +import { AppTree } from './types'; import { Config } from '@backstage/config'; import { instantiateAppNodeTree } from './instantiateAppNodeTree'; /** @internal */ -export interface CreateAppGraphOptions { +export interface CreateAppTreeOptions { features: (BackstagePlugin | ExtensionOverrides)[]; builtinExtensions: Extension[]; config: Config; } /** @internal */ -export function createAppGraph(options: CreateAppGraphOptions): AppGraph { - const appGraph = resolveAppGraph( +export function createAppTree(options: CreateAppTreeOptions): AppTree { + const tree = resolveAppTree( 'core', resolveAppNodeSpecs({ features: options.features, @@ -44,6 +44,6 @@ export function createAppGraph(options: CreateAppGraphOptions): AppGraph { forbidden: new Set(['core']), }), ); - instantiateAppNodeTree(appGraph.root); - return appGraph; + instantiateAppNodeTree(tree.root); + return tree; } diff --git a/packages/frontend-app-api/src/graph/index.ts b/packages/frontend-app-api/src/tree/index.ts similarity index 93% rename from packages/frontend-app-api/src/graph/index.ts rename to packages/frontend-app-api/src/tree/index.ts index 4a3b54dffb..c864e86a7a 100644 --- a/packages/frontend-app-api/src/graph/index.ts +++ b/packages/frontend-app-api/src/tree/index.ts @@ -20,4 +20,4 @@ export type { AppNodeInstance, AppNodeSpec, } from './types'; -export { createAppGraph } from './createAppGraph'; +export { createAppTree } from './createAppTree'; diff --git a/packages/frontend-app-api/src/graph/instantiateAppNodeTree.test.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts similarity index 92% rename from packages/frontend-app-api/src/graph/instantiateAppNodeTree.test.ts rename to packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts index 9be3482b5d..2f4a112166 100644 --- a/packages/frontend-app-api/src/graph/instantiateAppNodeTree.test.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts @@ -26,7 +26,7 @@ import { instantiateAppNodeTree, } from './instantiateAppNodeTree'; import { AppNodeInstance, AppNodeSpec } from './types'; -import { resolveAppGraph } from './resolveAppGraph'; +import { resolveAppTree } from './resolveAppTree'; const testDataRef = createExtensionDataRef('test'); const otherDataRef = createExtensionDataRef('other'); @@ -79,30 +79,30 @@ function makeInstanceWithId( describe('instantiateAppNodeTree', () => { it('should instantiate a single node', () => { - const graph = resolveAppGraph('root-node', [ + const tree = resolveAppTree('root-node', [ { ...makeSpec(simpleExtension), id: 'root-node' }, ]); - expect(graph.root.instance).not.toBeDefined(); - instantiateAppNodeTree(graph.root); - expect(graph.root.instance).toBeDefined(); - expect(graph.root.instance?.getData(testDataRef)).toBe('test'); + expect(tree.root.instance).not.toBeDefined(); + instantiateAppNodeTree(tree.root); + expect(tree.root.instance).toBeDefined(); + expect(tree.root.instance?.getData(testDataRef)).toBe('test'); // Multiple calls should have no effect - instantiateAppNodeTree(graph.root); - expect(graph.root.instance).toBeDefined(); + instantiateAppNodeTree(tree.root); + expect(tree.root.instance).toBeDefined(); }); it('should not instantiate disabled nodes', () => { - const graph = resolveAppGraph('root-node', [ + const tree = resolveAppTree('root-node', [ { ...makeSpec(simpleExtension), id: 'root-node', disabled: true }, ]); - expect(graph.root.instance).not.toBeDefined(); - instantiateAppNodeTree(graph.root); - expect(graph.root.instance).not.toBeDefined(); + expect(tree.root.instance).not.toBeDefined(); + instantiateAppNodeTree(tree.root); + expect(tree.root.instance).not.toBeDefined(); }); it('should instantiate a node with attachments', () => { - const graph = resolveAppGraph('root-node', [ + const tree = resolveAppTree('root-node', [ { ...makeSpec( createExtension({ @@ -127,26 +127,26 @@ describe('instantiateAppNodeTree', () => { }, ]); - const childNode = graph.nodes.get('child-node'); + const childNode = tree.nodes.get('child-node'); expect(childNode).toBeDefined(); - expect(graph.root.instance).not.toBeDefined(); + expect(tree.root.instance).not.toBeDefined(); expect(childNode?.instance).not.toBeDefined(); - instantiateAppNodeTree(graph.root); - expect(graph.root.instance).toBeDefined(); + instantiateAppNodeTree(tree.root); + expect(tree.root.instance).toBeDefined(); expect(childNode?.instance).toBeDefined(); - expect(graph.root.instance?.getData(inputMirrorDataRef)).toEqual({ + expect(tree.root.instance?.getData(inputMirrorDataRef)).toEqual({ test: [{ test: 'test' }], }); // Multiple calls should have no effect - instantiateAppNodeTree(graph.root); - expect(graph.root.instance).toBeDefined(); + instantiateAppNodeTree(tree.root); + expect(tree.root.instance).toBeDefined(); expect(childNode?.instance).toBeDefined(); }); it('should not instantiate disabled attachments', () => { - const graph = resolveAppGraph('root-node', [ + const tree = resolveAppTree('root-node', [ { ...makeSpec( createExtension({ @@ -172,15 +172,15 @@ describe('instantiateAppNodeTree', () => { }, ]); - const childNode = graph.nodes.get('child-node'); + const childNode = tree.nodes.get('child-node'); expect(childNode).toBeDefined(); - expect(graph.root.instance).not.toBeDefined(); + expect(tree.root.instance).not.toBeDefined(); expect(childNode?.instance).not.toBeDefined(); - instantiateAppNodeTree(graph.root); - expect(graph.root.instance).toBeDefined(); + instantiateAppNodeTree(tree.root); + expect(tree.root.instance).toBeDefined(); expect(childNode?.instance).not.toBeDefined(); - expect(graph.root.instance?.getData(inputMirrorDataRef)).toEqual({ + expect(tree.root.instance?.getData(inputMirrorDataRef)).toEqual({ test: [], }); }); diff --git a/packages/frontend-app-api/src/graph/instantiateAppNodeTree.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts similarity index 99% rename from packages/frontend-app-api/src/graph/instantiateAppNodeTree.ts rename to packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts index bf6028554b..b6e4179867 100644 --- a/packages/frontend-app-api/src/graph/instantiateAppNodeTree.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts @@ -151,7 +151,7 @@ export function createAppNodeInstance(options: { } /** - * Starting at the provided node, instantiate all reachable nodes in the graph that have not been disabled. + * Starting at the provided node, instantiate all reachable nodes in the tree that have not been disabled. * @internal */ export function instantiateAppNodeTree(rootNode: AppNode): void { diff --git a/packages/frontend-app-api/src/graph/readAppExtensionsConfig.test.ts b/packages/frontend-app-api/src/tree/readAppExtensionsConfig.test.ts similarity index 100% rename from packages/frontend-app-api/src/graph/readAppExtensionsConfig.test.ts rename to packages/frontend-app-api/src/tree/readAppExtensionsConfig.test.ts diff --git a/packages/frontend-app-api/src/graph/readAppExtensionsConfig.ts b/packages/frontend-app-api/src/tree/readAppExtensionsConfig.ts similarity index 100% rename from packages/frontend-app-api/src/graph/readAppExtensionsConfig.ts rename to packages/frontend-app-api/src/tree/readAppExtensionsConfig.ts diff --git a/packages/frontend-app-api/src/graph/resolveAppNodeSpecs.test.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts similarity index 100% rename from packages/frontend-app-api/src/graph/resolveAppNodeSpecs.test.ts rename to packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts diff --git a/packages/frontend-app-api/src/graph/resolveAppNodeSpecs.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts similarity index 100% rename from packages/frontend-app-api/src/graph/resolveAppNodeSpecs.ts rename to packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts diff --git a/packages/frontend-app-api/src/graph/resolveAppGraph.test.ts b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts similarity index 73% rename from packages/frontend-app-api/src/graph/resolveAppGraph.test.ts rename to packages/frontend-app-api/src/tree/resolveAppTree.test.ts index 2b7257446e..d84064b26a 100644 --- a/packages/frontend-app-api/src/graph/resolveAppGraph.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts @@ -15,7 +15,7 @@ */ import { createExtension } from '@backstage/frontend-plugin-api'; -import { resolveAppGraph } from './resolveAppGraph'; +import { resolveAppTree } from './resolveAppTree'; const extBaseConfig = { id: 'test', @@ -32,25 +32,25 @@ const baseSpec = { disabled: false, }; -describe('buildAppGraph', () => { - it('should fail to create an empty graph', () => { - expect(() => resolveAppGraph('core', [])).toThrow( - "No root node with id 'core' found in app graph", +describe('buildAppTree', () => { + it('should fail to create an empty tree', () => { + expect(() => resolveAppTree('core', [])).toThrow( + "No root node with id 'core' found in app tree", ); }); - it('should create a graph with only one node', () => { - const graph = resolveAppGraph('core', [{ ...baseSpec, id: 'core' }]); - expect(graph.root).toEqual({ + it('should create a tree with only one node', () => { + const tree = resolveAppTree('core', [{ ...baseSpec, id: 'core' }]); + expect(tree.root).toEqual({ spec: { ...baseSpec, id: 'core' }, edges: { attachments: new Map() }, }); - expect(Array.from(graph.orphans)).toEqual([]); - expect(Array.from(graph.nodes.keys())).toEqual(['core']); + expect(Array.from(tree.orphans)).toEqual([]); + expect(Array.from(tree.nodes.keys())).toEqual(['core']); }); - it('should create a graph', () => { - const graph = resolveAppGraph('b', [ + it('should create a tree', () => { + const tree = resolveAppTree('b', [ { ...baseSpec, id: 'a' }, { ...baseSpec, id: 'b' }, { ...baseSpec, id: 'c' }, @@ -60,7 +60,7 @@ describe('buildAppGraph', () => { { ...baseSpec, attachTo: { id: 'd', input: 'x' }, id: 'dx1' }, ]); - expect(Array.from(graph.nodes.keys())).toEqual([ + expect(Array.from(tree.nodes.keys())).toEqual([ 'a', 'b', 'c', @@ -70,7 +70,7 @@ describe('buildAppGraph', () => { 'dx1', ]); - expect(JSON.parse(JSON.stringify(graph.root))).toMatchInlineSnapshot(` + expect(JSON.parse(JSON.stringify(tree.root))).toMatchInlineSnapshot(` { "attachments": { "x": [ @@ -90,7 +90,7 @@ describe('buildAppGraph', () => { "id": "b", } `); - expect(String(graph.root)).toMatchInlineSnapshot(` + expect(String(tree.root)).toMatchInlineSnapshot(` " x [ @@ -102,7 +102,7 @@ describe('buildAppGraph', () => { " `); - const orphans = Array.from(graph.orphans).map(String); + const orphans = Array.from(tree.orphans).map(String); expect(orphans).toMatchInlineSnapshot(` [ "", @@ -112,8 +112,8 @@ describe('buildAppGraph', () => { `); }); - it('should create a graph out of order', () => { - const graph = resolveAppGraph('b', [ + it('should create a tree out of order', () => { + const tree = resolveAppTree('b', [ { ...baseSpec, attachTo: { id: 'b', input: 'x' }, id: 'bx2' }, { ...baseSpec, id: 'a' }, { ...baseSpec, attachTo: { id: 'b', input: 'y' }, id: 'by1' }, @@ -123,7 +123,7 @@ describe('buildAppGraph', () => { { ...baseSpec, attachTo: { id: 'd', input: 'x' }, id: 'dx1' }, ]); - expect(Array.from(graph.nodes.keys())).toEqual([ + expect(Array.from(tree.nodes.keys())).toEqual([ 'bx2', 'a', 'by1', @@ -133,7 +133,7 @@ describe('buildAppGraph', () => { 'dx1', ]); - expect(String(graph.root)).toMatchInlineSnapshot(` + expect(String(tree.root)).toMatchInlineSnapshot(` " x [ @@ -145,7 +145,7 @@ describe('buildAppGraph', () => { " `); - const orphans = Array.from(graph.orphans).map(String); + const orphans = Array.from(tree.orphans).map(String); expect(orphans).toMatchInlineSnapshot(` [ "", @@ -157,7 +157,7 @@ describe('buildAppGraph', () => { it('throws an error when duplicated extensions are detected', () => { expect(() => - resolveAppGraph('core', [ + resolveAppTree('core', [ { ...baseSpec, id: 'a' }, { ...baseSpec, id: 'a' }, ]), diff --git a/packages/frontend-app-api/src/graph/resolveAppGraph.ts b/packages/frontend-app-api/src/tree/resolveAppTree.ts similarity index 92% rename from packages/frontend-app-api/src/graph/resolveAppGraph.ts rename to packages/frontend-app-api/src/tree/resolveAppTree.ts index 354b4c53ee..9fda8e0f6a 100644 --- a/packages/frontend-app-api/src/graph/resolveAppGraph.ts +++ b/packages/frontend-app-api/src/tree/resolveAppTree.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AppGraph, AppNode, AppNodeInstance, AppNodeSpec } from './types'; +import { AppTree, AppNode, AppNodeInstance, AppNodeSpec } from './types'; function indent(str: string) { return str.replace(/^/gm, ' '); @@ -83,17 +83,17 @@ class SerializableAppNode implements AppNode { } /** - * Build the app graph by iterating through all node specs and constructing the app + * Build the app tree by iterating through all node specs and constructing the app * tree with all attachments in the same order as they appear in the input specs array. * @internal */ -export function resolveAppGraph( +export function resolveAppTree( rootNodeId: string, specs: AppNodeSpec[], -): AppGraph { +): AppTree { const nodes = new Map(); - // A node with the provided rootNodeId must be found in the graph, and it must not be attached to anything + // A node with the provided rootNodeId must be found in the tree, and it must not be attached to anything let rootNode: AppNode | undefined = undefined; // While iterating through the inputs specs we keep track of all nodes that were created @@ -141,7 +141,7 @@ export function resolveAppGraph( } if (!rootNode) { - throw new Error(`No root node with id '${rootNodeId}' found in app graph`); + throw new Error(`No root node with id '${rootNodeId}' found in app tree`); } return { diff --git a/packages/frontend-app-api/src/graph/types.ts b/packages/frontend-app-api/src/tree/types.ts similarity index 90% rename from packages/frontend-app-api/src/graph/types.ts rename to packages/frontend-app-api/src/tree/types.ts index 9267f13158..3246dfe5c2 100644 --- a/packages/frontend-app-api/src/graph/types.ts +++ b/packages/frontend-app-api/src/tree/types.ts @@ -25,13 +25,13 @@ NOTE: These types are marked as @internal for now, but the intention is for this */ /** - * The specification for this node in the app graph. + * The specification for this node in the app tree. * * @internal * @remarks * * The specifications for a collection of app nodes is all the information needed - * to build the graph and instantiate the nodes. + * to build the tree and instantiate the nodes. */ export interface AppNodeSpec { readonly id: string; @@ -57,7 +57,7 @@ export interface AppNodeEdges { } /** - * The instance of this node in the app graph. + * The instance of this node in the app tree. * * @internal * @remarks @@ -80,18 +80,18 @@ export interface AppNodeInstance { export interface AppNode { /** The specification for how this node should be instantiated */ readonly spec: AppNodeSpec; - /** The edges from this node to other nodes in the app graph */ + /** The edges from this node to other nodes in the app tree */ readonly edges: AppNodeEdges; /** The instance of this node, if it was instantiated */ readonly instance?: AppNodeInstance; } /** - * The app graph containing all nodes of the app. + * The app tree containing all nodes of the app. * * @internal */ -export interface AppGraph { +export interface AppTree { /** The root node of the app */ root: AppNode; /** A map of all nodes in the app by ID, including orphaned or disabled nodes */ diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index 4f7817314a..d726773fd3 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -87,7 +87,7 @@ import { AppRouteBinder } from '../routing'; import { RoutingProvider } from '../routing/RoutingProvider'; import { resolveRouteBindings } from '../routing/resolveRouteBindings'; import { collectRouteIds } from '../routing/collectRouteIds'; -import { AppNode, createAppGraph } from '../graph'; +import { AppNode, createAppTree } from '../tree'; const builtinExtensions = [ Core, @@ -117,7 +117,7 @@ export function createExtensionTree(options: { config: Config; }): ExtensionTree { const features = getAvailableFeatures(options.config); - const graph = createAppGraph({ + const tree = createAppTree({ features, builtinExtensions, config: options.config, @@ -136,14 +136,14 @@ export function createExtensionTree(options: { return { getExtension(id: string): ExtensionTreeNode | undefined { - return convertNode(graph.nodes.get(id)); + return convertNode(tree.nodes.get(id)); }, getExtensionAttachments( id: string, inputName: string, ): ExtensionTreeNode[] { return ( - graph.nodes + tree.nodes .get(id) ?.edges.attachments.get(inputName) ?.map(convertNode) @@ -247,7 +247,7 @@ export function createApp(options: { ...(options.features ?? []), ]); - const appGraph = createAppGraph({ + const tree = createAppTree({ features: allFeatures, builtinExtensions, config, @@ -262,11 +262,11 @@ export function createApp(options: { const routeIds = collectRouteIds(allFeatures); const App = () => ( - + {/* TODO: set base path using the logic from AppRouter */} - {appGraph.root.instance!.getData( - coreExtensionData.reactElement, - )} + {tree.root.instance!.getData(coreExtensionData.reactElement)}