@eventcatalog/core 2.4.0 → 2.5.0
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 +13 -0
- package/README.md +7 -0
- 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/NodeGraph/NodeGraph.astro +12 -0
- package/src/components/MDX/NodeGraph/NodeGraph.tsx +16 -4
- 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/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 +10 -2
- 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 +13 -2
- package/src/utils/teams.ts +7 -1
- package/src/utils/users.ts +8 -0
- package/tailwind.config.mjs +1 -0
|
@@ -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 { getVersion } from '@utils/services/services';
|
|
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 = getVersion(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 { getVersion } from '../services/services';
|
|
4
|
+
import { createDagreGraph, calculatedNodes } from '@utils/node-graph-utils/utils';
|
|
5
|
+
import { MarkerType } from 'reactflow';
|
|
6
|
+
import type { Node as NodeType } from 'reactflow';
|
|
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 = getVersion(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
|
+
};
|
|
@@ -3,6 +3,7 @@ import { getCollection, type CollectionEntry } from 'astro:content';
|
|
|
3
3
|
import dagre from 'dagre';
|
|
4
4
|
import { getVersion } from './services';
|
|
5
5
|
import { createDagreGraph, generateIdForNode, generatedIdForEdge, calculatedNodes } from '@utils/node-graph-utils/utils';
|
|
6
|
+
import { MarkerType } from 'reactflow';
|
|
6
7
|
|
|
7
8
|
type DagreGraph = any;
|
|
8
9
|
|
|
@@ -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
|
});
|
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,
|
package/src/utils/users.ts
CHANGED
|
@@ -10,6 +10,9 @@ export const getUsers = async (): Promise<User[]> => {
|
|
|
10
10
|
return user.data.hidden !== true;
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
// What do they own?
|
|
14
|
+
const domains = await getCollection('domains');
|
|
15
|
+
|
|
13
16
|
// What do they own?
|
|
14
17
|
const services = await getCollection('services');
|
|
15
18
|
|
|
@@ -24,6 +27,10 @@ export const getUsers = async (): Promise<User[]> => {
|
|
|
24
27
|
});
|
|
25
28
|
|
|
26
29
|
return users.map((user) => {
|
|
30
|
+
const ownedDomains = domains.filter((domain) => {
|
|
31
|
+
return domain.data.owners?.find((owner) => owner.slug === user.data.id);
|
|
32
|
+
});
|
|
33
|
+
|
|
27
34
|
const ownedServices = services.filter((service) => {
|
|
28
35
|
return service.data.owners?.find((owner) => owner.slug === user.data.id);
|
|
29
36
|
});
|
|
@@ -44,6 +51,7 @@ export const getUsers = async (): Promise<User[]> => {
|
|
|
44
51
|
...user,
|
|
45
52
|
data: {
|
|
46
53
|
...user.data,
|
|
54
|
+
ownedDomains,
|
|
47
55
|
ownedServices,
|
|
48
56
|
ownedEvents,
|
|
49
57
|
ownedCommands,
|
package/tailwind.config.mjs
CHANGED