@eventcatalog/core 2.4.0 → 2.5.1
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/CHANGELOG.md +19 -0
- package/README.md +8 -1
- package/bin/dist/eventcatalog.cjs +5 -4
- package/bin/dist/eventcatalog.js +5 -4
- package/package.json +8 -6
- package/scripts/catalog-to-astro-content-directory.js +32 -1
- package/scripts/default-files-for-collections/flows.md +11 -0
- package/scripts/default-files-for-collections/pages.md +1 -0
- package/scripts/watcher.js +1 -2
- package/src/components/DocsNavigation.astro +5 -1
- package/src/components/MDX/Flow/Flow.astro +63 -0
- package/src/components/MDX/NodeGraph/NodeGraph.astro +12 -0
- package/src/components/MDX/NodeGraph/NodeGraph.tsx +75 -33
- package/src/components/MDX/NodeGraph/Nodes/ExternalSystem.tsx +79 -0
- package/src/components/MDX/NodeGraph/Nodes/Service.tsx +0 -12
- package/src/components/MDX/NodeGraph/Nodes/Step.tsx +69 -0
- package/src/components/MDX/NodeGraph/Nodes/User.tsx +79 -0
- package/src/components/MDX/components.tsx +2 -0
- package/src/components/SideBars/DomainSideBar.astro +1 -1
- package/src/components/Tables/columns/FlowTableColumns.tsx +82 -0
- package/src/components/Tables/columns/index.tsx +3 -0
- package/src/content/config.ts +65 -5
- package/src/layouts/DiscoverLayout.astro +11 -1
- package/src/layouts/VisualiserLayout.astro +33 -22
- package/src/pages/discover/[type]/index.astro +3 -0
- package/src/pages/docs/[type]/[id]/[version]/index.astro +12 -3
- package/src/pages/docs/teams/[id]/index.astro +14 -0
- package/src/pages/docs/users/[id]/index.astro +23 -4
- package/src/pages/visualiser/[type]/[id]/[version]/index.astro +9 -2
- package/src/types/index.ts +1 -1
- package/src/utils/collections/util.ts +22 -0
- package/src/utils/commands/node-graph.ts +13 -2
- package/src/utils/config/catalog.ts +1 -0
- package/src/utils/domains/domains.ts +3 -5
- package/src/utils/domains/node-graph.ts +28 -9
- package/src/utils/events/node-graph.ts +13 -2
- package/src/utils/flows/flows.ts +59 -0
- package/src/utils/flows/node-graph.ts +153 -0
- package/src/utils/services/node-graph.ts +16 -5
- package/src/utils/services/services.ts +3 -26
- package/src/utils/teams.ts +7 -1
- package/src/utils/users.ts +8 -0
- package/src/utils/versions/versions.ts +26 -0
- package/tailwind.config.mjs +1 -0
|
@@ -29,11 +29,18 @@ const { render, ...props } = Astro.props;
|
|
|
29
29
|
|
|
30
30
|
const { Content } = await render();
|
|
31
31
|
|
|
32
|
+
const domains = props.data.ownedDomains as CollectionEntry<'domains'>[];
|
|
32
33
|
const services = props.data.ownedServices as CollectionEntry<'services'>[];
|
|
33
34
|
const events = props.data.ownedEvents as CollectionEntry<'events'>[];
|
|
34
35
|
const commands = props.data.ownedCommands as CollectionEntry<'commands'>[];
|
|
35
36
|
const teams = props.data.associatedTeams as CollectionEntry<'teams'>[];
|
|
36
37
|
|
|
38
|
+
const ownedDomainsList = domains.map((p) => ({
|
|
39
|
+
label: `${p.data.name}`,
|
|
40
|
+
href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
|
|
41
|
+
tag: `v${p.data.version}`,
|
|
42
|
+
}));
|
|
43
|
+
|
|
37
44
|
const ownedServicesList = services.map((p) => ({
|
|
38
45
|
label: `${p.data.name} (service)`,
|
|
39
46
|
href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
|
|
@@ -41,7 +48,8 @@ const ownedServicesList = services.map((p) => ({
|
|
|
41
48
|
}));
|
|
42
49
|
|
|
43
50
|
const ownedMessageList = [...events, ...commands].map((p) => ({
|
|
44
|
-
label: `${p.data.name}
|
|
51
|
+
label: `${p.data.name}`,
|
|
52
|
+
badge: p.collection,
|
|
45
53
|
color: p.collection === 'events' ? 'orange' : 'blue',
|
|
46
54
|
tag: `v${p.data.version}`,
|
|
47
55
|
href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
|
|
@@ -91,7 +99,11 @@ const associatedTeams = teams.map((o) => ({
|
|
|
91
99
|
<div class="border-b border-gray-200">
|
|
92
100
|
<div class="mx-auto max-w-7xl px-6 lg:px-8">
|
|
93
101
|
<div class="mx-auto max-w-2xl lg:max-w-none">
|
|
94
|
-
<dl class="hidden lg:grid grid-cols-1 gap-0.5 overflow-hidden rounded-2xl text-center sm:grid-cols-
|
|
102
|
+
<dl class="hidden lg:grid grid-cols-1 gap-0.5 overflow-hidden rounded-2xl text-center sm:grid-cols-4 lg:grid-cols-4">
|
|
103
|
+
<div class="flex flex-col p-8">
|
|
104
|
+
<dt class="text-sm font-semibold leading-6 text-gray-600"># owned domains</dt>
|
|
105
|
+
<dd class="order-first text-3xl font-semibold tracking-tight text-gray-900">{ownedDomainsList.length}</dd>
|
|
106
|
+
</div>
|
|
95
107
|
<div class="flex flex-col p-8">
|
|
96
108
|
<dt class="text-sm font-semibold leading-6 text-gray-600"># owned services</dt>
|
|
97
109
|
<dd class="order-first text-3xl font-semibold tracking-tight text-gray-900">{ownedServicesList.length}</dd>
|
|
@@ -118,18 +130,25 @@ const associatedTeams = teams.map((o) => ({
|
|
|
118
130
|
</main>
|
|
119
131
|
<aside class="sticky top-20 h-[calc(100vh-theme(spacing.16))] w-56 overflow-y-auto">
|
|
120
132
|
<div class="divide-y-2 divide-gray-100">
|
|
133
|
+
<PillList
|
|
134
|
+
color="pink"
|
|
135
|
+
title={`Owned domains (${ownedDomainsList.length})`}
|
|
136
|
+
pills={ownedDomainsList}
|
|
137
|
+
emptyMessage={`${props.data.name} does not own any domains.`}
|
|
138
|
+
client:load
|
|
139
|
+
/>
|
|
121
140
|
<PillList
|
|
122
141
|
color="pink"
|
|
123
142
|
title={`Owned services (${ownedServicesList.length})`}
|
|
124
143
|
pills={ownedServicesList}
|
|
125
|
-
emptyMessage={`${props.data.name} does not own any services
|
|
144
|
+
emptyMessage={`${props.data.name} does not own any services.`}
|
|
126
145
|
client:load
|
|
127
146
|
/>
|
|
128
147
|
<PillList
|
|
129
148
|
color="orange"
|
|
130
149
|
title={`Owned messages (${ownedMessageList.length})`}
|
|
131
150
|
pills={ownedMessageList}
|
|
132
|
-
emptyMessage={`${props.data.name} does not own any messages
|
|
151
|
+
emptyMessage={`${props.data.name} does not own any messages.`}
|
|
133
152
|
client:load
|
|
134
153
|
/>
|
|
135
154
|
<OwnersList
|
|
@@ -5,12 +5,19 @@ import type { CollectionTypes } from '@types';
|
|
|
5
5
|
import { getCommands } from '@utils/commands';
|
|
6
6
|
import { getDomains } from '@utils/domains/domains';
|
|
7
7
|
import { getEvents } from '@utils/events';
|
|
8
|
+
import { getFlows } from '@utils/flows/flows';
|
|
8
9
|
import { getServices } from '@utils/services/services';
|
|
9
10
|
import { buildUrl } from '@utils/url-builder';
|
|
10
11
|
import type { CollectionEntry } from 'astro:content';
|
|
11
12
|
|
|
12
13
|
export async function getStaticPaths() {
|
|
13
|
-
const [services, events, commands, domains] = await Promise.all([
|
|
14
|
+
const [services, events, commands, domains, flows] = await Promise.all([
|
|
15
|
+
getServices(),
|
|
16
|
+
getEvents(),
|
|
17
|
+
getCommands(),
|
|
18
|
+
getDomains(),
|
|
19
|
+
getFlows(),
|
|
20
|
+
]);
|
|
14
21
|
|
|
15
22
|
const buildPages = (collection: CollectionEntry<CollectionTypes>[]) => {
|
|
16
23
|
return collection.map((item) => ({
|
|
@@ -26,7 +33,7 @@ export async function getStaticPaths() {
|
|
|
26
33
|
}));
|
|
27
34
|
};
|
|
28
35
|
|
|
29
|
-
return [...buildPages(services), ...buildPages(events), ...buildPages(commands), ...buildPages(domains)];
|
|
36
|
+
return [...buildPages(services), ...buildPages(events), ...buildPages(commands), ...buildPages(domains), ...buildPages(flows)];
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
const props = Astro.props;
|
package/src/types/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type CollectionTypes = 'commands' | 'events' | 'domains' | 'services';
|
|
1
|
+
export type CollectionTypes = 'commands' | 'events' | 'domains' | 'services' | 'flows';
|
|
2
2
|
export type CollectionMessageTypes = 'commands' | 'events';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CollectionTypes } from '@types';
|
|
2
2
|
import type { CollectionEntry } from 'astro:content';
|
|
3
|
+
import { satisfies, validRange } from 'semver';
|
|
3
4
|
|
|
4
5
|
export const getVersions = (data: CollectionEntry<CollectionTypes>[]) => {
|
|
5
6
|
const allVersions = data.map((item) => item.data.version).sort();
|
|
@@ -20,3 +21,24 @@ export const getVersionForCollectionItem = (
|
|
|
20
21
|
const latestVersion = versions[0];
|
|
21
22
|
return { versions, latestVersion };
|
|
22
23
|
};
|
|
24
|
+
|
|
25
|
+
export const getItemsFromCollectionByIdAndSemverOrLatest = <T extends { data: { id: string; version: string } }>(
|
|
26
|
+
collection: T[],
|
|
27
|
+
id: string,
|
|
28
|
+
version?: string
|
|
29
|
+
): T[] => {
|
|
30
|
+
const semverRange = validRange(version);
|
|
31
|
+
const filteredCollection = collection.filter((c) => c.data.id == id);
|
|
32
|
+
|
|
33
|
+
if (semverRange) {
|
|
34
|
+
return filteredCollection.filter((c) => satisfies(c.data.version, semverRange));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Order by version
|
|
38
|
+
const sorted = filteredCollection.sort((a, b) => {
|
|
39
|
+
return a.data.version.localeCompare(b.data.version);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// latest version
|
|
43
|
+
return sorted.length > 0 ? [sorted[sorted.length - 1]] : [];
|
|
44
|
+
};
|
|
@@ -2,6 +2,7 @@ import { getCommands } from '@utils/commands';
|
|
|
2
2
|
import { calculatedNodes, createDagreGraph, generateIdForNode, generatedIdForEdge } from '@utils/node-graph-utils/utils';
|
|
3
3
|
import { type CollectionEntry } from 'astro:content';
|
|
4
4
|
import dagre from 'dagre';
|
|
5
|
+
import { MarkerType } from 'reactflow';
|
|
5
6
|
|
|
6
7
|
type DagreGraph = any;
|
|
7
8
|
|
|
@@ -50,7 +51,12 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
|
|
|
50
51
|
label: 'invokes',
|
|
51
52
|
animated: false,
|
|
52
53
|
markerEnd: {
|
|
53
|
-
type:
|
|
54
|
+
type: MarkerType.ArrowClosed,
|
|
55
|
+
width: 40,
|
|
56
|
+
height: 40,
|
|
57
|
+
},
|
|
58
|
+
style: {
|
|
59
|
+
strokeWidth: 1,
|
|
54
60
|
},
|
|
55
61
|
});
|
|
56
62
|
});
|
|
@@ -84,7 +90,12 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
|
|
|
84
90
|
label: 'accepts',
|
|
85
91
|
animated: false,
|
|
86
92
|
markerEnd: {
|
|
87
|
-
type:
|
|
93
|
+
type: MarkerType.ArrowClosed,
|
|
94
|
+
width: 40,
|
|
95
|
+
height: 40,
|
|
96
|
+
},
|
|
97
|
+
style: {
|
|
98
|
+
strokeWidth: 1,
|
|
88
99
|
},
|
|
89
100
|
});
|
|
90
101
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getItemsFromCollectionByIdAndSemverOrLatest, getVersionForCollectionItem } from '@utils/collections/util';
|
|
2
2
|
import { getCollection } from 'astro:content';
|
|
3
3
|
import type { CollectionEntry } from 'astro:content';
|
|
4
4
|
import path from 'path';
|
|
@@ -28,10 +28,8 @@ export const getDomains = async ({ getAllVersions = true }: Props = {}): Promise
|
|
|
28
28
|
const servicesInDomain = domain.data.services || [];
|
|
29
29
|
|
|
30
30
|
const services = servicesInDomain
|
|
31
|
-
.map((_service) =>
|
|
32
|
-
|
|
33
|
-
})
|
|
34
|
-
.filter((e) => e !== undefined);
|
|
31
|
+
.map((_service) => getItemsFromCollectionByIdAndSemverOrLatest(servicesCollection, _service.id, _service.version))
|
|
32
|
+
.flat();
|
|
35
33
|
|
|
36
34
|
return {
|
|
37
35
|
...domain,
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
// import { getColor } from '@utils/colors';
|
|
2
1
|
import { getCollection } from 'astro:content';
|
|
3
2
|
import dagre from 'dagre';
|
|
4
3
|
import { getNodesAndEdges as getServicesNodeAndEdges } from '../services/node-graph';
|
|
4
|
+
import merge from 'lodash.merge';
|
|
5
|
+
import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
|
|
5
6
|
|
|
6
7
|
type DagreGraph = any;
|
|
7
8
|
|
|
8
9
|
// Creates a new dagre graph
|
|
9
10
|
export const getDagreGraph = () => {
|
|
10
11
|
const graph = new dagre.graphlib.Graph({ compound: true });
|
|
11
|
-
graph.setGraph({ rankdir: 'LR', ranksep: 200, nodesep:
|
|
12
|
+
graph.setGraph({ rankdir: 'LR', ranksep: 200, nodesep: 100 });
|
|
12
13
|
graph.setDefaultEdgeLabel(() => ({}));
|
|
13
14
|
return graph;
|
|
14
15
|
};
|
|
@@ -22,8 +23,8 @@ interface Props {
|
|
|
22
23
|
|
|
23
24
|
export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simple' }: Props) => {
|
|
24
25
|
const flow = defaultFlow || getDagreGraph();
|
|
25
|
-
let nodes =
|
|
26
|
-
edges =
|
|
26
|
+
let nodes = new Map(),
|
|
27
|
+
edges = new Map();
|
|
27
28
|
|
|
28
29
|
const domains = await getCollection('domains');
|
|
29
30
|
|
|
@@ -39,9 +40,16 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
|
|
|
39
40
|
|
|
40
41
|
const rawServices = domain?.data.services || [];
|
|
41
42
|
|
|
43
|
+
const servicesCollection = await getCollection('services');
|
|
44
|
+
|
|
45
|
+
const domainServicesWithVersion = rawServices
|
|
46
|
+
.map((service) => getItemsFromCollectionByIdAndSemverOrLatest(servicesCollection, service.id, service.version))
|
|
47
|
+
.flat()
|
|
48
|
+
.map((svc) => ({ id: svc.data.id, version: svc.data.version }));
|
|
49
|
+
|
|
42
50
|
// Get all the nodes for everyhing
|
|
43
51
|
|
|
44
|
-
for (const service of
|
|
52
|
+
for (const service of domainServicesWithVersion) {
|
|
45
53
|
const { nodes: serviceNodes, edges: serviceEdges } = await getServicesNodeAndEdges({
|
|
46
54
|
id: service.id,
|
|
47
55
|
version: service.version,
|
|
@@ -49,12 +57,23 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
|
|
|
49
57
|
mode,
|
|
50
58
|
renderAllEdges: true,
|
|
51
59
|
});
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
serviceNodes.forEach((n) => {
|
|
61
|
+
/**
|
|
62
|
+
* A message could be sent by one service and received by another service on the same domain.
|
|
63
|
+
* So, we need deep merge the message to keep the `showSource` and `showTarget` as true.
|
|
64
|
+
*
|
|
65
|
+
* Let's see an example:
|
|
66
|
+
* Take an `OrderPlaced` event sent by the `OrderService` `{ showSource: true }` and
|
|
67
|
+
* received by `PaymentService` `{ showTarget: true }`.
|
|
68
|
+
*/
|
|
69
|
+
nodes.set(n.id, nodes.has(n.id) ? merge(nodes.get(n.id), n) : n);
|
|
70
|
+
});
|
|
71
|
+
// @ts-ignore
|
|
72
|
+
serviceEdges.forEach((e) => edges.set(e.id, e));
|
|
54
73
|
}
|
|
55
74
|
|
|
56
75
|
return {
|
|
57
|
-
nodes: nodes,
|
|
58
|
-
edges: edges,
|
|
76
|
+
nodes: [...nodes.values()],
|
|
77
|
+
edges: [...edges.values()],
|
|
59
78
|
};
|
|
60
79
|
};
|
|
@@ -3,6 +3,7 @@ import { getEvents } from '@utils/events';
|
|
|
3
3
|
import type { CollectionEntry } from 'astro:content';
|
|
4
4
|
import dagre from 'dagre';
|
|
5
5
|
import { calculatedNodes, createDagreGraph, generatedIdForEdge, generateIdForNode } from '../node-graph-utils/utils';
|
|
6
|
+
import { MarkerType } from 'reactflow';
|
|
6
7
|
|
|
7
8
|
type DagreGraph = any;
|
|
8
9
|
|
|
@@ -51,7 +52,12 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
|
|
|
51
52
|
label: 'publishes event',
|
|
52
53
|
animated: false,
|
|
53
54
|
markerEnd: {
|
|
54
|
-
type:
|
|
55
|
+
type: MarkerType.ArrowClosed,
|
|
56
|
+
width: 40,
|
|
57
|
+
height: 40,
|
|
58
|
+
},
|
|
59
|
+
style: {
|
|
60
|
+
strokeWidth: 1,
|
|
55
61
|
},
|
|
56
62
|
});
|
|
57
63
|
});
|
|
@@ -85,7 +91,12 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
|
|
|
85
91
|
label: 'subscribed by',
|
|
86
92
|
animated: false,
|
|
87
93
|
markerEnd: {
|
|
88
|
-
type:
|
|
94
|
+
type: MarkerType.ArrowClosed,
|
|
95
|
+
width: 40,
|
|
96
|
+
height: 40,
|
|
97
|
+
},
|
|
98
|
+
style: {
|
|
99
|
+
strokeWidth: 1,
|
|
89
100
|
},
|
|
90
101
|
});
|
|
91
102
|
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { getVersionForCollectionItem, getVersions } from '@utils/collections/util';
|
|
2
|
+
import { getVersionFromCollection } from '@utils/versions/versions';
|
|
3
|
+
import { getCollection } from 'astro:content';
|
|
4
|
+
import type { CollectionEntry } from 'astro:content';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
|
|
8
|
+
|
|
9
|
+
export type Flow = CollectionEntry<'flows'>;
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
getAllVersions?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const getFlows = async ({ getAllVersions = true }: Props = {}): Promise<Flow[]> => {
|
|
16
|
+
// Get flows that are not versioned
|
|
17
|
+
const flows = await getCollection('flows', (flow) => {
|
|
18
|
+
return (getAllVersions || !flow.slug.includes('versioned')) && flow.data.hidden !== true;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const events = await getCollection('events');
|
|
22
|
+
const commands = await getCollection('commands');
|
|
23
|
+
|
|
24
|
+
const allMessages = [...events, ...commands];
|
|
25
|
+
|
|
26
|
+
// @ts-ignore // TODO: Fix this type
|
|
27
|
+
return flows.map((flow) => {
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
const { latestVersion, versions } = getVersionForCollectionItem(flow, flows);
|
|
30
|
+
const steps = flow.data.steps || [];
|
|
31
|
+
|
|
32
|
+
const hydrateSteps = steps.map((step) => {
|
|
33
|
+
if (!step.message) return { ...flow, data: { ...flow.data, type: 'node' } };
|
|
34
|
+
const message = getVersionFromCollection(allMessages, step.message.id, step.message.version);
|
|
35
|
+
return {
|
|
36
|
+
...step,
|
|
37
|
+
type: 'message',
|
|
38
|
+
message: message,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
...flow,
|
|
44
|
+
data: {
|
|
45
|
+
...flow.data,
|
|
46
|
+
steps: hydrateSteps,
|
|
47
|
+
versions,
|
|
48
|
+
latestVersion,
|
|
49
|
+
},
|
|
50
|
+
catalog: {
|
|
51
|
+
path: path.join(flow.collection, flow.id.replace('/index.mdx', '')),
|
|
52
|
+
absoluteFilePath: path.join(PROJECT_DIR, flow.collection, flow.id.replace('/index.mdx', '/index.md')),
|
|
53
|
+
filePath: path.join(process.cwd(), 'src', 'catalog-files', flow.collection, flow.id.replace('/index.mdx', '')),
|
|
54
|
+
publicPath: path.join('/generated', flow.collection, flow.id.replace('/index.mdx', '')),
|
|
55
|
+
type: 'flow',
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { getCollection, type CollectionEntry } from 'astro:content';
|
|
2
|
+
import dagre from 'dagre';
|
|
3
|
+
import { createDagreGraph, calculatedNodes } from '@utils/node-graph-utils/utils';
|
|
4
|
+
import { MarkerType } from 'reactflow';
|
|
5
|
+
import type { Node as NodeType } from 'reactflow';
|
|
6
|
+
import { getVersionFromCollection } from '@utils/versions/versions';
|
|
7
|
+
|
|
8
|
+
type DagreGraph = any;
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
id: string;
|
|
12
|
+
version: string;
|
|
13
|
+
defaultFlow?: DagreGraph;
|
|
14
|
+
mode?: 'simple' | 'full';
|
|
15
|
+
renderAllEdges?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const getServiceNode = (step: any, services: CollectionEntry<'services'>[]) => {
|
|
19
|
+
const service = services.find(
|
|
20
|
+
(service) => service.data.id === step.service.id && service.data.version === step.service.version
|
|
21
|
+
);
|
|
22
|
+
return {
|
|
23
|
+
...step,
|
|
24
|
+
type: service ? service.collection : 'step',
|
|
25
|
+
service,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getMessageNode = (step: any, messages: CollectionEntry<'events' | 'commands'>[]) => {
|
|
30
|
+
const messagesForVersion = getVersionFromCollection(messages, step.message.id, step.message.version);
|
|
31
|
+
const message = messagesForVersion[0];
|
|
32
|
+
return {
|
|
33
|
+
...step,
|
|
34
|
+
type: message ? message.collection : 'step',
|
|
35
|
+
message,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simple', renderAllEdges = false }: Props) => {
|
|
40
|
+
const graph = defaultFlow || createDagreGraph({ ranksep: 360, nodesep: 200 });
|
|
41
|
+
const nodes = [] as any,
|
|
42
|
+
edges = [] as any;
|
|
43
|
+
|
|
44
|
+
const flows = await getCollection('flows');
|
|
45
|
+
const flow = flows.find((flow) => flow.data.id === id && flow.data.version === version);
|
|
46
|
+
|
|
47
|
+
// Nothing found...
|
|
48
|
+
if (!flow) {
|
|
49
|
+
return {
|
|
50
|
+
nodes: [],
|
|
51
|
+
edges: [],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const events = await getCollection('events');
|
|
56
|
+
const commands = await getCollection('commands');
|
|
57
|
+
const services = await getCollection('services');
|
|
58
|
+
|
|
59
|
+
const messages = [...events, ...commands];
|
|
60
|
+
|
|
61
|
+
const steps = flow?.data.steps || [];
|
|
62
|
+
|
|
63
|
+
// Hydrate the steps with information they may need.
|
|
64
|
+
const hydratedSteps = steps.map((step: any) => {
|
|
65
|
+
if (step.service) return getServiceNode(step, services);
|
|
66
|
+
if (step.message) return getMessageNode(step, messages);
|
|
67
|
+
if (step.actor) return { ...step, type: 'actor', actor: step.actor };
|
|
68
|
+
if (step.externalSystem) return { ...step, type: 'externalSystem', externalSystem: step.externalSystem };
|
|
69
|
+
return { ...step, type: 'step' };
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Create nodes
|
|
73
|
+
hydratedSteps.forEach((step, index: number) => {
|
|
74
|
+
const node = {
|
|
75
|
+
id: `step-${step.id}`,
|
|
76
|
+
sourcePosition: 'right',
|
|
77
|
+
targetPosition: 'left',
|
|
78
|
+
data: {
|
|
79
|
+
mode,
|
|
80
|
+
step: step,
|
|
81
|
+
showTarget: true,
|
|
82
|
+
showSource: true,
|
|
83
|
+
},
|
|
84
|
+
position: { x: 250, y: index * 150 },
|
|
85
|
+
type: step.type,
|
|
86
|
+
} as NodeType;
|
|
87
|
+
|
|
88
|
+
if (step.service) node.data.service = step.service;
|
|
89
|
+
if (step.message) node.data.message = step.message;
|
|
90
|
+
if (step.actor) node.data.actor = step.actor;
|
|
91
|
+
if (step.externalSystem) node.data.externalSystem = step.externalSystem;
|
|
92
|
+
|
|
93
|
+
nodes.push(node);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Create Edges
|
|
97
|
+
hydratedSteps.forEach((step, index: number) => {
|
|
98
|
+
let paths = step.next_steps || [];
|
|
99
|
+
|
|
100
|
+
if (step.next_step) {
|
|
101
|
+
// If its a string or number
|
|
102
|
+
if (!step.next_step?.id) {
|
|
103
|
+
paths = [{ id: step.next_step }];
|
|
104
|
+
} else {
|
|
105
|
+
paths = [step.next_step];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
paths = paths.map((path: any) => {
|
|
110
|
+
if (typeof path === 'string') {
|
|
111
|
+
return { id: path };
|
|
112
|
+
}
|
|
113
|
+
return path;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
paths.forEach((path: any) => {
|
|
117
|
+
edges.push({
|
|
118
|
+
id: `step-${step.id}-step-${path.id}`,
|
|
119
|
+
source: `step-${step.id}`,
|
|
120
|
+
target: `step-${path.id}`,
|
|
121
|
+
type: 'smoothstep',
|
|
122
|
+
label: path.label,
|
|
123
|
+
animated: true,
|
|
124
|
+
markerEnd: {
|
|
125
|
+
type: MarkerType.ArrowClosed,
|
|
126
|
+
width: 20,
|
|
127
|
+
height: 20,
|
|
128
|
+
color: '#acacac',
|
|
129
|
+
},
|
|
130
|
+
style: {
|
|
131
|
+
strokeWidth: 2,
|
|
132
|
+
stroke: '#acacac',
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
nodes.forEach((node: any) => {
|
|
139
|
+
graph.setNode(node.id, { width: 150, height: 100 });
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
edges.forEach((edge: any) => {
|
|
143
|
+
graph.setEdge(edge.source, edge.target);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Render the diagram in memory getting hte X and Y
|
|
147
|
+
dagre.layout(graph);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
nodes: calculatedNodes(graph, nodes),
|
|
151
|
+
edges: edges,
|
|
152
|
+
};
|
|
153
|
+
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// import { getColor } from '@utils/colors';
|
|
2
2
|
import { getCollection, type CollectionEntry } from 'astro:content';
|
|
3
3
|
import dagre from 'dagre';
|
|
4
|
-
import { getVersion } from './services';
|
|
5
4
|
import { createDagreGraph, generateIdForNode, generatedIdForEdge, calculatedNodes } from '@utils/node-graph-utils/utils';
|
|
5
|
+
import { MarkerType } from 'reactflow';
|
|
6
|
+
import { getVersionFromCollection } from '@utils/versions/versions';
|
|
6
7
|
|
|
7
8
|
type DagreGraph = any;
|
|
8
9
|
|
|
@@ -40,12 +41,12 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
|
|
|
40
41
|
const messages = [...events, ...commands];
|
|
41
42
|
|
|
42
43
|
const receivesHydrated = receivesRaw
|
|
43
|
-
.map((message) =>
|
|
44
|
+
.map((message) => getVersionFromCollection(messages, message.id, message.version))
|
|
44
45
|
.flat()
|
|
45
46
|
.filter((e) => e !== undefined);
|
|
46
47
|
|
|
47
48
|
const sendsHydrated = sendsRaw
|
|
48
|
-
.map((message) =>
|
|
49
|
+
.map((message) => getVersionFromCollection(messages, message.id, message.version))
|
|
49
50
|
.flat()
|
|
50
51
|
.filter((e) => e !== undefined);
|
|
51
52
|
|
|
@@ -73,7 +74,12 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
|
|
|
73
74
|
label: receive?.collection === 'events' ? 'receives event' : 'accepts',
|
|
74
75
|
animated: false,
|
|
75
76
|
markerEnd: {
|
|
76
|
-
type:
|
|
77
|
+
type: MarkerType.ArrowClosed,
|
|
78
|
+
width: 40,
|
|
79
|
+
height: 40,
|
|
80
|
+
},
|
|
81
|
+
style: {
|
|
82
|
+
strokeWidth: 1,
|
|
77
83
|
},
|
|
78
84
|
});
|
|
79
85
|
});
|
|
@@ -107,7 +113,12 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
|
|
|
107
113
|
label: send?.collection === 'events' ? 'publishes event' : 'invokes command',
|
|
108
114
|
animated: false,
|
|
109
115
|
markerEnd: {
|
|
110
|
-
type:
|
|
116
|
+
type: MarkerType.ArrowClosed,
|
|
117
|
+
width: 40,
|
|
118
|
+
height: 40,
|
|
119
|
+
},
|
|
120
|
+
style: {
|
|
121
|
+
strokeWidth: 1,
|
|
111
122
|
},
|
|
112
123
|
});
|
|
113
124
|
});
|
|
@@ -1,36 +1,13 @@
|
|
|
1
1
|
import { getVersionForCollectionItem } from '@utils/collections/util';
|
|
2
|
+
import { getVersionFromCollection } from '@utils/versions/versions';
|
|
2
3
|
import { getCollection } from 'astro:content';
|
|
3
4
|
import type { CollectionEntry } from 'astro:content';
|
|
4
5
|
import path from 'path';
|
|
5
|
-
import { satisfies, validRange } from 'semver';
|
|
6
6
|
|
|
7
7
|
const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
|
|
8
8
|
|
|
9
9
|
export type Service = CollectionEntry<'services'>;
|
|
10
10
|
|
|
11
|
-
export const getVersion = (
|
|
12
|
-
collection: CollectionEntry<'events' | 'commands'>[],
|
|
13
|
-
id: string,
|
|
14
|
-
version?: string
|
|
15
|
-
): CollectionEntry<'events' | 'commands'>[] => {
|
|
16
|
-
const data = collection;
|
|
17
|
-
const semverRange = validRange(version);
|
|
18
|
-
|
|
19
|
-
if (semverRange) {
|
|
20
|
-
return data.filter((msg) => msg.data.id == id).filter((msg) => satisfies(msg.data.version, semverRange));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const filteredEvents = data.filter((event) => event.data.id === id);
|
|
24
|
-
|
|
25
|
-
// Order by version
|
|
26
|
-
const sorted = filteredEvents.sort((a, b) => {
|
|
27
|
-
return a.data.version.localeCompare(b.data.version);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// latest version
|
|
31
|
-
return [sorted[sorted.length - 1]];
|
|
32
|
-
};
|
|
33
|
-
|
|
34
11
|
interface Props {
|
|
35
12
|
getAllVersions?: boolean;
|
|
36
13
|
}
|
|
@@ -53,12 +30,12 @@ export const getServices = async ({ getAllVersions = true }: Props = {}): Promis
|
|
|
53
30
|
const receivesMessages = service.data.receives || [];
|
|
54
31
|
|
|
55
32
|
const sends = sendsMessages
|
|
56
|
-
.map((message) =>
|
|
33
|
+
.map((message) => getVersionFromCollection(allMessages, message.id, message.version))
|
|
57
34
|
.flat()
|
|
58
35
|
.filter((e) => e !== undefined);
|
|
59
36
|
|
|
60
37
|
const receives = receivesMessages
|
|
61
|
-
.map((message) =>
|
|
38
|
+
.map((message) => getVersionFromCollection(allMessages, message.id, message.version))
|
|
62
39
|
.flat()
|
|
63
40
|
.filter((e) => e !== undefined);
|
|
64
41
|
|
package/src/utils/teams.ts
CHANGED
|
@@ -9,7 +9,8 @@ export const getTeams = async (): Promise<Team[]> => {
|
|
|
9
9
|
const teams = await getCollection('teams', (team) => {
|
|
10
10
|
return team.data.hidden !== true;
|
|
11
11
|
});
|
|
12
|
-
|
|
12
|
+
// What do they own?
|
|
13
|
+
const domains = await getCollection('domains');
|
|
13
14
|
// What do they own?
|
|
14
15
|
const services = await getCollection('services');
|
|
15
16
|
// What do they own?
|
|
@@ -17,6 +18,10 @@ export const getTeams = async (): Promise<Team[]> => {
|
|
|
17
18
|
const commands = await getCollection('commands');
|
|
18
19
|
|
|
19
20
|
return teams.map((team) => {
|
|
21
|
+
const ownedDomains = domains.filter((domain) => {
|
|
22
|
+
return domain.data.owners?.find((owner) => owner.slug === team.data.id);
|
|
23
|
+
});
|
|
24
|
+
|
|
20
25
|
const ownedServices = services.filter((service) => {
|
|
21
26
|
return service.data.owners?.find((owner) => owner.slug === team.data.id);
|
|
22
27
|
});
|
|
@@ -33,6 +38,7 @@ export const getTeams = async (): Promise<Team[]> => {
|
|
|
33
38
|
...team,
|
|
34
39
|
data: {
|
|
35
40
|
...team.data,
|
|
41
|
+
ownedDomains,
|
|
36
42
|
ownedServices,
|
|
37
43
|
ownedCommands,
|
|
38
44
|
ownedEvents,
|