@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +7 -0
  3. package/bin/dist/eventcatalog.cjs +5 -4
  4. package/bin/dist/eventcatalog.js +5 -4
  5. package/package.json +8 -6
  6. package/scripts/catalog-to-astro-content-directory.js +32 -1
  7. package/scripts/default-files-for-collections/flows.md +11 -0
  8. package/scripts/default-files-for-collections/pages.md +1 -0
  9. package/scripts/watcher.js +1 -2
  10. package/src/components/DocsNavigation.astro +5 -1
  11. package/src/components/MDX/NodeGraph/NodeGraph.astro +12 -0
  12. package/src/components/MDX/NodeGraph/NodeGraph.tsx +16 -4
  13. package/src/components/MDX/NodeGraph/Nodes/ExternalSystem.tsx +79 -0
  14. package/src/components/MDX/NodeGraph/Nodes/Service.tsx +0 -12
  15. package/src/components/MDX/NodeGraph/Nodes/Step.tsx +69 -0
  16. package/src/components/MDX/NodeGraph/Nodes/User.tsx +79 -0
  17. package/src/components/SideBars/DomainSideBar.astro +1 -1
  18. package/src/components/Tables/columns/FlowTableColumns.tsx +82 -0
  19. package/src/components/Tables/columns/index.tsx +3 -0
  20. package/src/content/config.ts +65 -5
  21. package/src/layouts/DiscoverLayout.astro +11 -1
  22. package/src/layouts/VisualiserLayout.astro +33 -22
  23. package/src/pages/discover/[type]/index.astro +3 -0
  24. package/src/pages/docs/[type]/[id]/[version]/index.astro +10 -2
  25. package/src/pages/docs/teams/[id]/index.astro +14 -0
  26. package/src/pages/docs/users/[id]/index.astro +23 -4
  27. package/src/pages/visualiser/[type]/[id]/[version]/index.astro +9 -2
  28. package/src/types/index.ts +1 -1
  29. package/src/utils/collections/util.ts +22 -0
  30. package/src/utils/commands/node-graph.ts +13 -2
  31. package/src/utils/config/catalog.ts +1 -0
  32. package/src/utils/domains/domains.ts +3 -5
  33. package/src/utils/domains/node-graph.ts +28 -9
  34. package/src/utils/events/node-graph.ts +13 -2
  35. package/src/utils/flows/flows.ts +59 -0
  36. package/src/utils/flows/node-graph.ts +153 -0
  37. package/src/utils/services/node-graph.ts +13 -2
  38. package/src/utils/teams.ts +7 -1
  39. package/src/utils/users.ts +8 -0
  40. 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: 200 });
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 = [] as any,
26
- edges = [] as any;
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 rawServices) {
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
- nodes = [...nodes, ...serviceNodes];
53
- edges = [...edges, ...serviceEdges];
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: 'arrow',
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: 'arrow',
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: 'arrow',
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: 'arrow',
116
+ type: MarkerType.ArrowClosed,
117
+ width: 40,
118
+ height: 40,
119
+ },
120
+ style: {
121
+ strokeWidth: 1,
111
122
  },
112
123
  });
113
124
  });
@@ -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,
@@ -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,
@@ -7,6 +7,7 @@ export default {
7
7
  safelist: [
8
8
  {pattern: /border-.*-(200|400|500)/},
9
9
  {pattern: /bg-.*-(100|200|400|500)/},
10
+ {pattern: /from-.*-(100|200|400|500|700)/},
10
11
  {pattern: /text-.*-(400|500|800)/},
11
12
  'border-blue-200',
12
13
  'border-green-300',