@eventcatalog/core 4.10.0 → 4.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-BDFTJVGN.js → chunk-C73V4K3S.js} +1 -1
- package/dist/{chunk-BQXEWQ5J.js → chunk-E5JJSOMA.js} +1 -1
- package/dist/{chunk-5XURY2JV.js → chunk-MGK66RUH.js} +1 -1
- package/dist/{chunk-IC6XSXJO.js → chunk-NOD5BM45.js} +1 -1
- package/dist/{chunk-HFAQXAG3.js → chunk-SB2TMVVX.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/astro.config.mjs +6 -0
- package/eventcatalog/src/components/CatalogGraph/CatalogForceGraph.tsx +260 -175
- package/eventcatalog/src/components/MDX/ArchitectureGraph/ArchitectureGraph.astro +62 -0
- package/eventcatalog/src/components/MDX/ArchitectureGraph/ArchitectureGraphPortal.tsx +22 -0
- package/eventcatalog/src/components/MDX/ArchitectureGraph/AstroArchitectureGraph.tsx +42 -0
- package/eventcatalog/src/components/MDX/components.tsx +13 -0
- package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +46 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +41 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/_index.data.ts +1 -1
- package/eventcatalog/src/pages/index.astro +34 -2
- package/eventcatalog/src/pages/visualiser/graph/index.astro +2 -23
- package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +1 -1
- package/eventcatalog/src/stores/sidebar-store/builders/service.ts +1 -1
- package/eventcatalog/src/stores/sidebar-store/state.ts +1 -1
- package/eventcatalog/src/utils/node-graphs/catalog-force-graph.ts +41 -0
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import CatalogForceGraph from '@components/CatalogGraph/CatalogForceGraph';
|
|
4
|
+
import type { CatalogGraphWireLink, CatalogGraphWireNode } from '@utils/node-graphs/catalog-force-graph';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
/** DOM id of the placeholder div ArchitectureGraphPortal rendered into the MDX content */
|
|
8
|
+
portalId: string;
|
|
9
|
+
nodes: CatalogGraphWireNode[];
|
|
10
|
+
links: CatalogGraphWireLink[];
|
|
11
|
+
linkLabels: string[];
|
|
12
|
+
initialLens?: string;
|
|
13
|
+
initialFocus?: string;
|
|
14
|
+
initialFocusDepth?: number;
|
|
15
|
+
showSearch?: boolean;
|
|
16
|
+
showLegend?: boolean;
|
|
17
|
+
showLensPicker?: boolean;
|
|
18
|
+
/** Base URL of the standalone /visualiser/graph page — the graph renders an
|
|
19
|
+
* "open full screen" link carrying its live view state (focus, depth, lens) */
|
|
20
|
+
href?: string;
|
|
21
|
+
hrefLabel?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The island mounts outside the MDX content, finds the placeholder div the MDX
|
|
25
|
+
// component map emitted, and portals the graph into it — same pattern as the
|
|
26
|
+
// visualiser's NodeGraph.
|
|
27
|
+
const AstroArchitectureGraph = ({ portalId, href, hrefLabel, ...graphProps }: Props) => {
|
|
28
|
+
const [container, setContainer] = useState<HTMLElement | null>(null);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
setContainer(document.getElementById(portalId));
|
|
32
|
+
}, [portalId]);
|
|
33
|
+
|
|
34
|
+
if (!container) return null;
|
|
35
|
+
|
|
36
|
+
return createPortal(
|
|
37
|
+
<CatalogForceGraph {...graphProps} syncUrl={false} zoomOnScroll={false} openInGraphUrl={href} openInGraphLabel={hrefLabel} />,
|
|
38
|
+
container
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default AstroArchitectureGraph;
|
|
@@ -35,6 +35,7 @@ import MermaidFileLoader from '@components/MDX/MermaidFileLoader/MermaidFileLoad
|
|
|
35
35
|
import LikeC4View from '@components/MDX/LikeC4View/LikeC4View.astro';
|
|
36
36
|
// Portals: required for server/client components
|
|
37
37
|
import NodeGraphPortal from '@components/MDX/NodeGraph/NodeGraphPortal';
|
|
38
|
+
import ArchitectureGraphPortal from '@components/MDX/ArchitectureGraph/ArchitectureGraphPortal';
|
|
38
39
|
import ContextDiagramPortal from '@components/MDX/ContextDiagram/ContextDiagramPortal';
|
|
39
40
|
import SystemContextMapPortal from '@components/MDX/SystemContextMap/SystemContextMapPortal';
|
|
40
41
|
import SchemaViewerPortal from '@components/MDX/SchemaViewer/SchemaViewerPortal';
|
|
@@ -50,6 +51,10 @@ const getEntityMapCollection = (props: any, mdxProp: any) => {
|
|
|
50
51
|
};
|
|
51
52
|
|
|
52
53
|
const components = (props: any) => {
|
|
54
|
+
// Occurrence counter so multiple <ArchitectureGraph/> tags on one page each get
|
|
55
|
+
// their own portal — MDX renders in document order, matching the body scan order
|
|
56
|
+
// the host pages use to render the islands.
|
|
57
|
+
let architectureGraphOccurrence = 0;
|
|
53
58
|
return {
|
|
54
59
|
Attachments: (mdxProp: any) => jsx(Attachments, { ...props, ...mdxProp }),
|
|
55
60
|
CustomProperties: (mdxProp: any) => jsx(CustomProperties, { ...props, ...mdxProp }),
|
|
@@ -69,6 +74,14 @@ const components = (props: any) => {
|
|
|
69
74
|
ADRTable: (mdxProp: any) => jsx(ADRTable, { ...props, ...mdxProp }),
|
|
70
75
|
EntityPropertiesTable: (mdxProp: any) => jsx(EntityPropertiesTable, { ...props, ...mdxProp }),
|
|
71
76
|
NodeGraph: (mdxProp: any) => jsx(NodeGraphPortal, { ...props.data, ...mdxProp, props, mdxProp }),
|
|
77
|
+
ArchitectureGraph: (mdxProp: any) =>
|
|
78
|
+
jsx(ArchitectureGraphPortal, {
|
|
79
|
+
...props.data,
|
|
80
|
+
...mdxProp,
|
|
81
|
+
occurrenceIndex: architectureGraphOccurrence++,
|
|
82
|
+
props,
|
|
83
|
+
mdxProp,
|
|
84
|
+
}),
|
|
72
85
|
ComponentDiagram: (mdxProp: any) => jsx(NodeGraphPortal, { ...props.data, ...mdxProp, props, mdxProp }),
|
|
73
86
|
ContextDiagram: (mdxProp: any) => jsx(ContextDiagramPortal, { ...props.data, ...mdxProp, props, mdxProp }),
|
|
74
87
|
SystemContextMap: (mdxProp: any) => jsx(SystemContextMapPortal, { ...mdxProp }),
|
|
@@ -14,9 +14,17 @@ import { getMDXComponentsByName, parseMdxBooleanProp } from '@utils/markdown';
|
|
|
14
14
|
import { getAdjacentPages, getNavigationItems } from '@enterprise/custom-documentation/utils/custom-docs';
|
|
15
15
|
import CustomDocsNav from '@enterprise/custom-documentation/components/CustomDocsNav/CustomDocsNav.astro';
|
|
16
16
|
import NodeGraph from '@components/MDX/NodeGraph/NodeGraph.astro';
|
|
17
|
+
import ArchitectureGraph from '@components/MDX/ArchitectureGraph/ArchitectureGraph.astro';
|
|
18
|
+
import { architectureGraphPortalId } from '@components/MDX/ArchitectureGraph/ArchitectureGraphPortal';
|
|
17
19
|
import CopyAsMarkdown from '@components/CopyAsMarkdown';
|
|
18
20
|
|
|
19
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
isVisualiserEnabled,
|
|
23
|
+
isMarkdownDownloadEnabled,
|
|
24
|
+
isRSSEnabled,
|
|
25
|
+
isEventCatalogChatEnabled,
|
|
26
|
+
isArchitectureGraphEnabled,
|
|
27
|
+
} from '@utils/feature';
|
|
20
28
|
|
|
21
29
|
const props = Astro.props;
|
|
22
30
|
const doc = props.data;
|
|
@@ -48,6 +56,18 @@ const resolvedNodeGraphs = await Promise.all(
|
|
|
48
56
|
})
|
|
49
57
|
);
|
|
50
58
|
|
|
59
|
+
// Architecture graphs embedded via <ArchitectureGraph />. Custom docs have no
|
|
60
|
+
// resource of their own, so with no `id` + `type` the whole catalog is rendered.
|
|
61
|
+
const architectureGraphs = (getMDXComponentsByName(props.body, 'ArchitectureGraph') || []).map((graph: any) => ({
|
|
62
|
+
id: graph.id,
|
|
63
|
+
collection: graph.type ? resourceToCollectionMap[graph.type as keyof typeof resourceToCollectionMap] : undefined,
|
|
64
|
+
depth: graph.depth ? Number(graph.depth) : undefined,
|
|
65
|
+
lens: graph.lens,
|
|
66
|
+
search: parseMdxBooleanProp(graph.search, true),
|
|
67
|
+
legend: parseMdxBooleanProp(graph.legend, true),
|
|
68
|
+
lensPicker: parseMdxBooleanProp(graph.lensPicker, false),
|
|
69
|
+
}));
|
|
70
|
+
|
|
51
71
|
const { prev, next } = await getAdjacentPages(currentSlug);
|
|
52
72
|
|
|
53
73
|
const ownersRaw = doc?.owners || [];
|
|
@@ -171,6 +191,31 @@ const editUrl =
|
|
|
171
191
|
})
|
|
172
192
|
}
|
|
173
193
|
|
|
194
|
+
{
|
|
195
|
+
architectureGraphs.length > 0 &&
|
|
196
|
+
architectureGraphs.map((graph: any, occurrenceIndex: number) => (
|
|
197
|
+
<ArchitectureGraph
|
|
198
|
+
id={graph.id}
|
|
199
|
+
collection={graph.collection}
|
|
200
|
+
depth={graph.depth}
|
|
201
|
+
lens={graph.lens}
|
|
202
|
+
showSearch={graph.search}
|
|
203
|
+
showLegend={graph.legend}
|
|
204
|
+
showLensPicker={graph.lensPicker}
|
|
205
|
+
portalId={architectureGraphPortalId(graph.id ?? (doc as any).id, occurrenceIndex)}
|
|
206
|
+
href={
|
|
207
|
+
isArchitectureGraphEnabled()
|
|
208
|
+
? {
|
|
209
|
+
label: 'Open full screen',
|
|
210
|
+
// Base URL only — the graph appends its live view state (focus, depth, lens)
|
|
211
|
+
url: buildUrl('/visualiser/graph'),
|
|
212
|
+
}
|
|
213
|
+
: undefined
|
|
214
|
+
}
|
|
215
|
+
/>
|
|
216
|
+
))
|
|
217
|
+
}
|
|
218
|
+
|
|
174
219
|
<!-- Previous / Next Navigation -->
|
|
175
220
|
<div class="py-8 border-t border-[rgb(var(--ec-page-border))] mt-8">
|
|
176
221
|
<div class="flex flex-col sm:flex-row justify-between w-full gap-4">
|
|
@@ -10,6 +10,8 @@ import Footer from '@layouts/Footer.astro';
|
|
|
10
10
|
|
|
11
11
|
import components from '@components/MDX/components';
|
|
12
12
|
import NodeGraph from '@components/MDX/NodeGraph/NodeGraph.astro';
|
|
13
|
+
import ArchitectureGraph from '@components/MDX/ArchitectureGraph/ArchitectureGraph.astro';
|
|
14
|
+
import { architectureGraphPortalId } from '@components/MDX/ArchitectureGraph/ArchitectureGraphPortal';
|
|
13
15
|
import SchemaViewer from '@components/MDX/SchemaViewer/SchemaViewerRoot.astro';
|
|
14
16
|
import Admonition from '@components/MDX/Admonition';
|
|
15
17
|
import VersionList from '@components/Lists/VersionList.astro';
|
|
@@ -65,6 +67,7 @@ import {
|
|
|
65
67
|
isVisualiserEnabled,
|
|
66
68
|
isRSSEnabled,
|
|
67
69
|
isEventCatalogMCPEnabled,
|
|
70
|
+
isArchitectureGraphEnabled,
|
|
68
71
|
} from '@utils/feature';
|
|
69
72
|
import { getMDXComponentsByName, parseMdxBooleanProp } from '@utils/markdown';
|
|
70
73
|
|
|
@@ -361,6 +364,20 @@ const contextDiagrams = contextDiagramSupported
|
|
|
361
364
|
})
|
|
362
365
|
: [];
|
|
363
366
|
|
|
367
|
+
// Architecture graphs embedded via <ArchitectureGraph />. Renders the D3
|
|
368
|
+
// catalog-wide force graph focused on the current resource's neighbourhood;
|
|
369
|
+
// `id` + `type` focus another resource instead. Each renders into its own
|
|
370
|
+
// portal, so it never collides with the page's NodeGraph.
|
|
371
|
+
const architectureGraphs = (getMDXComponentsByName(props.body || '', 'ArchitectureGraph') || []).map((graph: any) => ({
|
|
372
|
+
id: graph.id ?? props.data.id,
|
|
373
|
+
collection: graph.type ? resourceToCollectionMap[graph.type as keyof typeof resourceToCollectionMap] : props.collection,
|
|
374
|
+
depth: graph.depth ? Number(graph.depth) : undefined,
|
|
375
|
+
lens: graph.lens,
|
|
376
|
+
search: parseMdxBooleanProp(graph.search, true),
|
|
377
|
+
legend: parseMdxBooleanProp(graph.legend, true),
|
|
378
|
+
lensPicker: parseMdxBooleanProp(graph.lensPicker, false),
|
|
379
|
+
}));
|
|
380
|
+
|
|
364
381
|
// Get props for the node graph (when no id is passed, we assume its the current page)
|
|
365
382
|
const hasCurrentFlowEmbed =
|
|
366
383
|
props.collection === 'flows' &&
|
|
@@ -653,6 +670,30 @@ if (!isAdrPage && !hasCurrentFlowEmbed && !hasCurrentPageNodeGraph) {
|
|
|
653
670
|
/>
|
|
654
671
|
))
|
|
655
672
|
}
|
|
673
|
+
{
|
|
674
|
+
architectureGraphs.length > 0 &&
|
|
675
|
+
architectureGraphs.map((graph: any, occurrenceIndex: number) => (
|
|
676
|
+
<ArchitectureGraph
|
|
677
|
+
id={graph.id}
|
|
678
|
+
collection={graph.collection}
|
|
679
|
+
depth={graph.depth}
|
|
680
|
+
lens={graph.lens}
|
|
681
|
+
showSearch={graph.search}
|
|
682
|
+
showLegend={graph.legend}
|
|
683
|
+
showLensPicker={graph.lensPicker}
|
|
684
|
+
portalId={architectureGraphPortalId(graph.id, occurrenceIndex)}
|
|
685
|
+
href={
|
|
686
|
+
isArchitectureGraphEnabled()
|
|
687
|
+
? {
|
|
688
|
+
label: 'Open full screen',
|
|
689
|
+
// Base URL only — the graph appends its live view state (focus, depth, lens)
|
|
690
|
+
url: buildUrl('/visualiser/graph'),
|
|
691
|
+
}
|
|
692
|
+
: undefined
|
|
693
|
+
}
|
|
694
|
+
/>
|
|
695
|
+
))
|
|
696
|
+
}
|
|
656
697
|
</div>
|
|
657
698
|
<Footer />
|
|
658
699
|
<!-- Add edit this page button with icon and text-->
|
|
@@ -13,7 +13,7 @@ export const resolveUbiquitousLanguageTermEditUrl = ({
|
|
|
13
13
|
collection,
|
|
14
14
|
configEditUrl,
|
|
15
15
|
}: {
|
|
16
|
-
term: Pick<DictionaryTerm, 'editUrl'>;
|
|
16
|
+
term: Pick<DictionaryTerm, 'id' | 'editUrl'>;
|
|
17
17
|
collection: Pick<UbiquitousLanguage, 'filePath'> & { data: Pick<UbiquitousLanguage['data'], 'editUrl'> };
|
|
18
18
|
configEditUrl?: string;
|
|
19
19
|
}) =>
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
|
|
3
|
-
import { getMDXComponentsByName } from '@utils/markdown';
|
|
3
|
+
import { getMDXComponentsByName, parseMdxBooleanProp } from '@utils/markdown';
|
|
4
|
+
import { architectureGraphPortalId } from '@components/MDX/ArchitectureGraph/ArchitectureGraphPortal';
|
|
4
5
|
import { resourceToCollectionMap } from '@utils/collections/util';
|
|
5
6
|
import { buildUrl } from '@utils/url-builder';
|
|
6
|
-
import { isCustomLandingPageEnabled, isVisualiserEnabled } from '@utils/feature';
|
|
7
|
+
import { isCustomLandingPageEnabled, isVisualiserEnabled, isArchitectureGraphEnabled } from '@utils/feature';
|
|
7
8
|
import DefaultAstroLandingPage from './_index.astro';
|
|
8
9
|
import config from '@config';
|
|
9
10
|
|
|
10
11
|
let nodeGraphs: any[] = [];
|
|
11
12
|
let systemContextMaps: any[] = [];
|
|
13
|
+
let architectureGraphs: any[] = [];
|
|
12
14
|
let CustomContent = null;
|
|
13
15
|
let NodeGraph = null;
|
|
16
|
+
let ArchitectureGraph = null;
|
|
14
17
|
let customHomepageComponents: Record<string, any> = {};
|
|
15
18
|
|
|
16
19
|
import path from 'path';
|
|
@@ -37,11 +40,19 @@ if (existsSync(pathToUserDefinedLandingPage) && isCustomLandingPageEnabled()) {
|
|
|
37
40
|
// The homepage can also embed the global System Context Map via <SystemContextMap />.
|
|
38
41
|
systemContextMaps = getMDXComponentsByName(rawContent, 'SystemContextMap') || [];
|
|
39
42
|
|
|
43
|
+
// …and the catalog-wide Architecture Graph via <ArchitectureGraph />. With no
|
|
44
|
+
// `id` + `type` the whole catalog is rendered.
|
|
45
|
+
architectureGraphs = getMDXComponentsByName(rawContent, 'ArchitectureGraph') || [];
|
|
46
|
+
|
|
40
47
|
// Avoid pulling visualizer CSS into the default homepage bundle.
|
|
41
48
|
// Only load the NodeGraph component when the custom homepage actually uses a graph.
|
|
42
49
|
if (nodeGraphs.length > 0 || systemContextMaps.length > 0) {
|
|
43
50
|
NodeGraph = (await import('@components/MDX/NodeGraph/NodeGraph.astro')).default;
|
|
44
51
|
}
|
|
52
|
+
// Only build the catalog-wide graph data when the homepage actually embeds it.
|
|
53
|
+
if (architectureGraphs.length > 0) {
|
|
54
|
+
ArchitectureGraph = (await import('@components/MDX/ArchitectureGraph/ArchitectureGraph.astro')).default;
|
|
55
|
+
}
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
if (config.landingPage) {
|
|
@@ -104,6 +115,27 @@ if (config.landingPage) {
|
|
|
104
115
|
}
|
|
105
116
|
/>
|
|
106
117
|
))}
|
|
118
|
+
{ArchitectureGraph &&
|
|
119
|
+
architectureGraphs.map((graph: any, occurrenceIndex: number) => (
|
|
120
|
+
<ArchitectureGraph
|
|
121
|
+
id={graph.id}
|
|
122
|
+
collection={graph.type ? resourceToCollectionMap[graph.type as keyof typeof resourceToCollectionMap] : undefined}
|
|
123
|
+
depth={graph.depth ? Number(graph.depth) : undefined}
|
|
124
|
+
lens={graph.lens}
|
|
125
|
+
showSearch={parseMdxBooleanProp(graph.search, true)}
|
|
126
|
+
showLegend={parseMdxBooleanProp(graph.legend, true)}
|
|
127
|
+
showLensPicker={parseMdxBooleanProp(graph.lensPicker, false)}
|
|
128
|
+
portalId={architectureGraphPortalId(graph.id, occurrenceIndex)}
|
|
129
|
+
href={
|
|
130
|
+
isArchitectureGraphEnabled()
|
|
131
|
+
? {
|
|
132
|
+
label: 'Open full screen',
|
|
133
|
+
url: buildUrl('/visualiser/graph'),
|
|
134
|
+
}
|
|
135
|
+
: undefined
|
|
136
|
+
}
|
|
137
|
+
/>
|
|
138
|
+
))}
|
|
107
139
|
</main>
|
|
108
140
|
</VerticalSideBarLayout>
|
|
109
141
|
) : (
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import VisualiserLayout from '@layouts/VisualiserLayout.astro';
|
|
3
3
|
import CatalogForceGraph from '@components/CatalogGraph/CatalogForceGraph';
|
|
4
|
-
import
|
|
5
|
-
import { getCatalogForceGraph } from '@utils/node-graphs/catalog-force-graph';
|
|
4
|
+
import { getCatalogForceGraph, toCatalogForceGraphWire } from '@utils/node-graphs/catalog-force-graph';
|
|
6
5
|
import { isArchitectureGraphEnabled } from '@utils/feature';
|
|
7
6
|
|
|
8
7
|
// Bail before building the graph — on very large catalogs the whole point of
|
|
@@ -11,29 +10,9 @@ if (!isArchitectureGraphEnabled()) {
|
|
|
11
10
|
return new Response(null, { status: 404 });
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
const { nodes, links } = await getCatalogForceGraph();
|
|
15
|
-
|
|
16
13
|
// Compact wire format: index-based links and client-derived URLs keep the
|
|
17
14
|
// island props Astro serialises into the page small for large catalogs.
|
|
18
|
-
const
|
|
19
|
-
const linkLabels = [...new Set(links.map((link) => link.label))];
|
|
20
|
-
const labelIndexByLabel = new Map(linkLabels.map((label, index) => [label, index]));
|
|
21
|
-
|
|
22
|
-
const wireNodes = nodes.map(({ id, label, collection, version }) => ({
|
|
23
|
-
// Node ids are `${collection}/${resourceId}` — send only the resource id, the client rebuilds the key
|
|
24
|
-
id: id.slice(collection.length + 1),
|
|
25
|
-
label,
|
|
26
|
-
collection,
|
|
27
|
-
version,
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
|
-
const wireLinks = links.map(
|
|
31
|
-
(link): CatalogGraphWireLink => [
|
|
32
|
-
nodeIndexById.get(link.source)!,
|
|
33
|
-
nodeIndexById.get(link.target)!,
|
|
34
|
-
labelIndexByLabel.get(link.label)!,
|
|
35
|
-
]
|
|
36
|
-
);
|
|
15
|
+
const { nodes: wireNodes, links: wireLinks, linkLabels } = toCatalogForceGraphWire(await getCatalogForceGraph());
|
|
37
16
|
---
|
|
38
17
|
|
|
39
18
|
<VisualiserLayout title="Visualiser | Architecture Graph" description="Force-directed graph of every resource in the catalog">
|
|
@@ -195,7 +195,7 @@ export const buildServiceSections = (
|
|
|
195
195
|
leftIcon: '/icons/graphql-black.svg',
|
|
196
196
|
href: buildUrl(`${docsBasePath}/graphql/${specification.filenameWithoutExtension}`),
|
|
197
197
|
})),
|
|
198
|
-
],
|
|
198
|
+
] as ChildRef[],
|
|
199
199
|
}
|
|
200
200
|
: null,
|
|
201
201
|
'resource-groups': renderResourceGroups
|
|
@@ -35,6 +35,47 @@ export interface CatalogGraphLink {
|
|
|
35
35
|
label: string;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** Compact wire format — index-based links keep the payload Astro serialises into the island small. */
|
|
39
|
+
export interface CatalogGraphWireNode {
|
|
40
|
+
/** Resource id, e.g. `InventoryService` — the client rebuilds the `${collection}/${id}` key */
|
|
41
|
+
id: string;
|
|
42
|
+
label: string;
|
|
43
|
+
collection: string;
|
|
44
|
+
version?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** [sourceIndex, targetIndex, labelIndex] into the nodes / linkLabels arrays */
|
|
48
|
+
export type CatalogGraphWireLink = [number, number, number];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Compacts the built graph for serialisation into a client island: node ids are
|
|
52
|
+
* stripped of their `${collection}/` prefix, link labels are deduped into one
|
|
53
|
+
* array, and each link becomes an index tuple. URLs are dropped server-side and
|
|
54
|
+
* rebuilt client-side.
|
|
55
|
+
*/
|
|
56
|
+
export const toCatalogForceGraphWire = ({ nodes, links }: { nodes: CatalogGraphNode[]; links: CatalogGraphLink[] }) => {
|
|
57
|
+
const nodeIndexById = new Map(nodes.map((node, index) => [node.id, index]));
|
|
58
|
+
const linkLabels = [...new Set(links.map((link) => link.label))];
|
|
59
|
+
const labelIndexByLabel = new Map(linkLabels.map((label, index) => [label, index]));
|
|
60
|
+
|
|
61
|
+
const wireNodes: CatalogGraphWireNode[] = nodes.map(({ id, label, collection, version }) => ({
|
|
62
|
+
id: id.slice(collection.length + 1),
|
|
63
|
+
label,
|
|
64
|
+
collection,
|
|
65
|
+
version,
|
|
66
|
+
}));
|
|
67
|
+
|
|
68
|
+
const wireLinks = links.map(
|
|
69
|
+
(link): CatalogGraphWireLink => [
|
|
70
|
+
nodeIndexById.get(link.source)!,
|
|
71
|
+
nodeIndexById.get(link.target)!,
|
|
72
|
+
labelIndexByLabel.get(link.label)!,
|
|
73
|
+
]
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return { nodes: wireNodes, links: wireLinks, linkLabels };
|
|
77
|
+
};
|
|
78
|
+
|
|
38
79
|
type AnyEntry = {
|
|
39
80
|
collection?: string;
|
|
40
81
|
data: { id: string; name?: string; version?: string; latestVersion?: string; visualiser?: boolean };
|