From 544968da085cca68110f42af2675ce414ab9d762 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 17 Oct 2023 18:01:45 +0200 Subject: [PATCH] frontend-app-api: make app node serializable Signed-off-by: Patrik Oldsberg --- .../src/wiring/graph/buildAppGraph.test.ts | 57 ++++++++++++++++ .../src/wiring/graph/buildAppGraph.ts | 68 ++++++++++++++++--- .../src/wiring/graph/types.ts | 3 +- 3 files changed, 119 insertions(+), 9 deletions(-) diff --git a/packages/frontend-app-api/src/wiring/graph/buildAppGraph.test.ts b/packages/frontend-app-api/src/wiring/graph/buildAppGraph.test.ts index 1d5d8f415f..21a5009a95 100644 --- a/packages/frontend-app-api/src/wiring/graph/buildAppGraph.test.ts +++ b/packages/frontend-app-api/src/wiring/graph/buildAppGraph.test.ts @@ -47,6 +47,49 @@ describe('buildAppGraph', () => { ]); expect([...graph.rootNodes.keys()]).toEqual(['a', 'b', 'c']); expect([...graph.orphanNodes.keys()]).toEqual(['dx1']); + + expect(JSON.parse(JSON.stringify([...graph.rootNodes.values()]))) + .toMatchInlineSnapshot(` + [ + { + "id": "a", + }, + { + "attachments": { + "x": [ + { + "id": "bx1", + }, + { + "id": "bx2", + }, + ], + "y": [ + { + "id": "by1", + }, + ], + }, + "id": "b", + }, + { + "id": "c", + }, + ] + `); + expect(String(graph.rootNodes.get('a'))).toMatchInlineSnapshot(`""`); + expect(String(graph.rootNodes.get('b'))).toMatchInlineSnapshot(` + " + x [ + + + ] + y [ + + ] + " + `); + expect(String(graph.rootNodes.get('c'))).toMatchInlineSnapshot(`""`); }); it('should create a graph out of order', () => { @@ -61,6 +104,20 @@ describe('buildAppGraph', () => { ]); expect([...graph.rootNodes.keys()]).toEqual(['a', 'b', 'c']); expect([...graph.orphanNodes.keys()]).toEqual(['dx1']); + + expect(String(graph.rootNodes.get('a'))).toMatchInlineSnapshot(`""`); + expect(String(graph.rootNodes.get('b'))).toMatchInlineSnapshot(` + " + x [ + + + ] + y [ + + ] + " + `); + expect(String(graph.rootNodes.get('c'))).toMatchInlineSnapshot(`""`); }); it('throws an error when duplicated extensions are detected', () => { diff --git a/packages/frontend-app-api/src/wiring/graph/buildAppGraph.ts b/packages/frontend-app-api/src/wiring/graph/buildAppGraph.ts index 9cfb430bcb..79977f6993 100644 --- a/packages/frontend-app-api/src/wiring/graph/buildAppGraph.ts +++ b/packages/frontend-app-api/src/wiring/graph/buildAppGraph.ts @@ -14,14 +14,71 @@ * limitations under the License. */ -import { AppNode, AppNodeEdges, AppNodeSpec, Mutable } from './types'; +import { + AppGraph, + AppNode, + AppNodeEdges, + AppNodeInstance, + AppNodeSpec, + Mutable, +} from './types'; + +function indent(str: string) { + return str.replace(/^/gm, ' '); +} + +/** @internal */ +class SerializableAppNode implements AppNode { + public readonly spec: AppNodeSpec; + public readonly edges: AppNodeEdges = { attachments: new Map() }; + public readonly instance?: AppNodeInstance; + + constructor(spec: AppNodeSpec) { + this.spec = spec; + } + + toJSON() { + const dataRefs = this.instance && [...this.instance.getDataRefs()]; + return { + id: this.spec.id, + output: + dataRefs && dataRefs.length > 0 + ? dataRefs.map(ref => ref.id) + : undefined, + attachments: + this.edges.attachments.size > 0 + ? Object.fromEntries(this.edges.attachments) + : undefined, + }; + } + + toString() { + const dataRefs = this.instance && [...this.instance.getDataRefs()]; + const out = + dataRefs && dataRefs.length > 0 + ? ` out=[${[...dataRefs.keys()].join(', ')}]` + : ''; + + if (this.edges.attachments.size === 0) { + return `<${this.spec.id}${out} />`; + } + + return [ + `<${this.spec.id}${out}>`, + ...[...this.edges.attachments.entries()].map(([k, v]) => + indent([`${k} [`, ...v.map(e => indent(e.toString())), `]`].join('\n')), + ), + ``, + ].join('\n'); + } +} /** * Build the app graph 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 buildAppGraph(specs: AppNodeSpec[]) { +export function buildAppGraph(specs: AppNodeSpec[]): AppGraph { const nodes = new Map(); const rootNodes = new Map(); // While iterating through the inputs specs we keep track of all nodes that were created @@ -39,12 +96,7 @@ export function buildAppGraph(specs: AppNodeSpec[]) { throw new Error(`Unexpected duplicate extension id '${spec.id}'`); } - const node: AppNode = { - spec, - edges: { - attachments: new Map(), - }, - }; + const node = new SerializableAppNode(spec); nodes.set(spec.id, node); if (spec.attachTo) { diff --git a/packages/frontend-app-api/src/wiring/graph/types.ts b/packages/frontend-app-api/src/wiring/graph/types.ts index 88fc363d6f..29c0028d66 100644 --- a/packages/frontend-app-api/src/wiring/graph/types.ts +++ b/packages/frontend-app-api/src/wiring/graph/types.ts @@ -52,7 +52,7 @@ export interface AppNodeEdges { * @public */ export interface AppNodeInstance { - getDataRefs(): ExtensionDataRef[]; + getDataRefs(): Iterable>; getData(ref: ExtensionDataRef): T | unknown; } @@ -71,4 +71,5 @@ export interface AppNode { export interface AppGraph { rootNodes: Map; + orphanNodes: Map; }