@eventcatalog/core 2.64.3 → 2.65.0-beta.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 (35) hide show
  1. package/README.md +2 -1
  2. package/dist/analytics/analytics.cjs +1 -1
  3. package/dist/analytics/analytics.js +2 -2
  4. package/dist/analytics/log-build.cjs +1 -1
  5. package/dist/analytics/log-build.js +3 -3
  6. package/dist/{chunk-CXZKUSOU.js → chunk-2ZXSFW7J.js} +1 -1
  7. package/dist/chunk-622JYJWG.js +109 -0
  8. package/dist/{chunk-C7L3FLQL.js → chunk-6MJGAOPK.js} +1 -1
  9. package/dist/chunk-BH3JMNAV.js +12 -0
  10. package/dist/{chunk-WAWMXWSY.js → chunk-GGFP7ZBX.js} +1 -1
  11. package/dist/constants.cjs +1 -1
  12. package/dist/constants.js +1 -1
  13. package/dist/eventcatalog.cjs +139 -24
  14. package/dist/eventcatalog.js +9 -3
  15. package/dist/migrations/index.cjs +150 -0
  16. package/dist/migrations/index.d.cts +3 -0
  17. package/dist/migrations/index.d.ts +3 -0
  18. package/dist/migrations/index.js +7 -0
  19. package/dist/migrations/message-channels-to-service-channels.cjs +139 -0
  20. package/dist/migrations/message-channels-to-service-channels.d.cts +6 -0
  21. package/dist/migrations/message-channels-to-service-channels.d.ts +6 -0
  22. package/dist/migrations/message-channels-to-service-channels.js +6 -0
  23. package/eventcatalog/src/components/MDX/NodeGraph/Edges/AnimatedMessageEdge.tsx +42 -28
  24. package/eventcatalog/src/components/MDX/SchemaViewer/SchemaViewerRoot.astro +1 -0
  25. package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +41 -35
  26. package/eventcatalog/src/content.config.ts +31 -3
  27. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +0 -4
  28. package/eventcatalog/src/hooks/eventcatalog-visualizer.ts +35 -15
  29. package/eventcatalog/src/utils/channels.ts +73 -1
  30. package/eventcatalog/src/utils/collections/util.ts +7 -0
  31. package/eventcatalog/src/utils/node-graphs/channel-node-graph.ts +75 -0
  32. package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +856 -61
  33. package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +46 -70
  34. package/eventcatalog/src/utils/node-graphs/utils/utils.ts +26 -80
  35. package/package.json +2 -2
@@ -8,15 +8,24 @@ import {
8
8
  createEdge,
9
9
  generatedIdForEdge,
10
10
  generateIdForNode,
11
- getChannelNodesAndEdges,
11
+ getColorFromString,
12
12
  getEdgeLabelForMessageAsSource,
13
13
  getEdgeLabelForServiceAsTarget,
14
14
  } from './utils/utils';
15
- import { MarkerType } from '@xyflow/react';
16
- import { findMatchingNodes } from '@utils/collections/util';
15
+ import { MarkerType, type Node, type Edge } from '@xyflow/react';
16
+ import {
17
+ findMatchingNodes,
18
+ getItemsFromCollectionByIdAndSemverOrLatest,
19
+ getLatestVersionInCollectionById,
20
+ } from '@utils/collections/util';
17
21
  import type { CollectionMessageTypes } from '@types';
18
22
  import { getCommands } from '@utils/commands';
19
23
  import { getQueries } from '@utils/queries';
24
+ import { createNode } from './utils/utils';
25
+ import { getConsumersOfMessage, getProducersOfMessage } from '@utils/collections/services';
26
+ import { getNodesAndEdgesForChannelChain } from './channel-node-graph';
27
+ import { getChannelChain, isChannelsConnected } from '@utils/channels';
28
+ import { getChannels } from '@utils/channels';
20
29
 
21
30
  type DagreGraph = any;
22
31
 
@@ -27,6 +36,7 @@ interface Props {
27
36
  mode?: 'simple' | 'full';
28
37
  channelRenderMode?: 'flat' | 'single';
29
38
  collection?: CollectionEntry<CollectionMessageTypes>[];
39
+ channels?: CollectionEntry<'channels'>[];
30
40
  }
31
41
 
32
42
  const getNodesAndEdges = async ({
@@ -36,6 +46,7 @@ const getNodesAndEdges = async ({
36
46
  mode = 'simple',
37
47
  channelRenderMode = 'flat',
38
48
  collection = [],
49
+ channels = [],
39
50
  }: Props) => {
40
51
  const flow = defaultFlow || createDagreGraph({ ranksep: 300, nodesep: 50 });
41
52
  const nodes = [] as any,
@@ -53,14 +64,29 @@ const getNodesAndEdges = async ({
53
64
  };
54
65
  }
55
66
 
67
+ // We always render the message itself
68
+ nodes.push({
69
+ id: generateIdForNode(message),
70
+ sourcePosition: 'right',
71
+ targetPosition: 'left',
72
+ data: {
73
+ mode,
74
+ message: {
75
+ ...message.data,
76
+ },
77
+ },
78
+ position: { x: 0, y: 0 },
79
+ type: message.collection,
80
+ });
81
+
56
82
  const producers = (message.data.producers as CollectionEntry<'services'>[]) || [];
57
83
  const consumers = (message.data.consumers as CollectionEntry<'services'>[]) || [];
58
- const channels = (message.data.messageChannels as CollectionEntry<'channels'>[]) || [];
59
84
 
60
85
  // Track nodes that are both sent and received
61
86
  const bothSentAndReceived = findMatchingNodes(producers, consumers);
62
87
 
63
- producers.forEach((producer) => {
88
+ for (const producer of producers) {
89
+ // Create the producer node
64
90
  nodes.push({
65
91
  id: generateIdForNode(producer),
66
92
  type: producer?.collection,
@@ -70,58 +96,94 @@ const getNodesAndEdges = async ({
70
96
  position: { x: 250, y: 0 },
71
97
  });
72
98
 
73
- // If the event has channels, we need to render them, otherwise connect the producer to the event
74
- if (message.data.channels) {
75
- const { nodes: channelNodes, edges: channelEdges } = getChannelNodesAndEdges({
76
- channels,
77
- channelsToRender: message.data.channels,
78
- source: producer,
79
- target: message,
80
- sourceToChannelLabel: getEdgeLabelForServiceAsTarget(message),
81
- channelToTargetLabel: getEdgeLabelForServiceAsTarget(message),
82
- mode,
83
- currentNodes: nodes,
84
- channelRenderMode,
85
- });
86
- nodes.push(...channelNodes);
87
- edges.push(...channelEdges);
88
- } else {
99
+ // Is the producer sending this message to a channel?
100
+ const producerConfigurationForMessage = producer.data.sends?.find((send) => send.id === message.data.id);
101
+ const producerChannelConfiguration = producerConfigurationForMessage?.to ?? [];
102
+
103
+ const producerHasChannels = producerChannelConfiguration?.length > 0;
104
+
105
+ const rootSourceAndTarget = {
106
+ source: { id: generateIdForNode(producer), collection: producer.collection },
107
+ target: { id: generateIdForNode(message), collection: message.collection },
108
+ };
109
+
110
+ // If the producer does not have any channels defined, then we just connect the producer to the event directly
111
+ if (!producerHasChannels) {
89
112
  edges.push({
90
113
  id: generatedIdForEdge(producer, message),
91
114
  source: generateIdForNode(producer),
92
115
  target: generateIdForNode(message),
93
116
  label: getEdgeLabelForServiceAsTarget(message),
94
- data: { message },
117
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
95
118
  animated: false,
96
119
  markerEnd: {
97
120
  type: MarkerType.ArrowClosed,
98
121
  width: 40,
99
122
  height: 40,
100
123
  },
101
- style: {
102
- strokeWidth: 1,
103
- },
104
124
  });
125
+ continue;
105
126
  }
106
- });
107
127
 
108
- // The message itself
109
- nodes.push({
110
- id: generateIdForNode(message),
111
- sourcePosition: 'right',
112
- targetPosition: 'left',
113
- data: {
114
- mode,
115
- message: {
116
- ...message.data,
117
- },
118
- },
119
- position: { x: 0, y: 0 },
120
- type: message.collection,
121
- });
128
+ // If the producer has channels defined, we need to render them
129
+ for (const producerChannel of producerChannelConfiguration) {
130
+ const channel = getItemsFromCollectionByIdAndSemverOrLatest(
131
+ channels,
132
+ producerChannel.id,
133
+ producerChannel.version
134
+ )[0] as CollectionEntry<'channels'>;
135
+
136
+ // If we cannot find the channel in EventCatalog, we just connect the producer to the event directly
137
+ if (!channel) {
138
+ edges.push(
139
+ createEdge({
140
+ id: generatedIdForEdge(producer, message),
141
+ source: generateIdForNode(producer),
142
+ target: generateIdForNode(message),
143
+ label: getEdgeLabelForMessageAsSource(message),
144
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
145
+ })
146
+ );
147
+ continue;
148
+ }
149
+
150
+ // We render the channel node
151
+ nodes.push(
152
+ createNode({
153
+ id: generateIdForNode(channel),
154
+ type: channel.collection,
155
+ data: { mode, channel: { ...channel.data } },
156
+ position: { x: 0, y: 0 },
157
+ })
158
+ );
159
+
160
+ // Connect the producer to the message
161
+ edges.push(
162
+ createEdge({
163
+ id: generatedIdForEdge(producer, message),
164
+ source: generateIdForNode(producer),
165
+ target: generateIdForNode(message),
166
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
167
+ label: getEdgeLabelForServiceAsTarget(message),
168
+ })
169
+ );
170
+
171
+ // Connect the message to the channel
172
+ edges.push(
173
+ createEdge({
174
+ id: generatedIdForEdge(message, channel),
175
+ source: generateIdForNode(message),
176
+ target: generateIdForNode(channel),
177
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
178
+ label: 'routes to',
179
+ })
180
+ );
181
+ }
182
+ }
122
183
 
123
184
  // The messages the service sends
124
- consumers.forEach((consumer) => {
185
+ for (const consumer of consumers) {
186
+ // Render the consumer node
125
187
  nodes.push({
126
188
  id: generateIdForNode(consumer),
127
189
  sourcePosition: 'right',
@@ -131,32 +193,152 @@ const getNodesAndEdges = async ({
131
193
  type: consumer?.collection,
132
194
  });
133
195
 
134
- if (message.data.channels) {
135
- const { nodes: channelNodes, edges: channelEdges } = getChannelNodesAndEdges({
136
- channels,
137
- channelsToRender: channels.map((channel) => ({ id: channel.data.id, version: channel.data.version })),
138
- source: message,
139
- target: consumer,
140
- channelToTargetLabel: getEdgeLabelForMessageAsSource(message),
141
- mode,
142
- currentNodes: nodes,
143
- channelRenderMode,
144
- });
196
+ // Is the consumer receiving this message from a channel?
197
+ const consumerConfigurationForMessage = consumer.data.receives?.find((receive) => receive.id === message.data.id);
198
+ const consumerChannelConfiguration = consumerConfigurationForMessage?.from ?? [];
145
199
 
146
- nodes.push(...channelNodes);
147
- edges.push(...channelEdges);
148
- } else {
200
+ const consumerHasChannels = consumerChannelConfiguration.length > 0;
201
+
202
+ const rootSourceAndTarget = {
203
+ source: { id: generateIdForNode(message), collection: message.collection },
204
+ target: { id: generateIdForNode(consumer), collection: consumer.collection },
205
+ };
206
+
207
+ // If the consumer does not have any channels defined, connect the consumer to the event directly
208
+ if (!consumerHasChannels) {
149
209
  edges.push(
150
210
  createEdge({
151
211
  id: generatedIdForEdge(message, consumer),
152
212
  source: generateIdForNode(message),
153
213
  target: generateIdForNode(consumer),
154
214
  label: getEdgeLabelForMessageAsSource(message),
155
- data: { message },
215
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
156
216
  })
157
217
  );
158
218
  }
159
- });
219
+
220
+ // If the consumer has channels defined, we try and render them
221
+ for (const consumerChannel of consumerChannelConfiguration) {
222
+ const channel = getItemsFromCollectionByIdAndSemverOrLatest(
223
+ channels,
224
+ consumerChannel.id,
225
+ consumerChannel.version
226
+ )[0] as CollectionEntry<'channels'>;
227
+
228
+ // If we cannot find the channel in EventCatalog, we connect the message directly to the consumer
229
+ if (!channel) {
230
+ edges.push(
231
+ createEdge({
232
+ id: generatedIdForEdge(message, consumer),
233
+ source: generateIdForNode(message),
234
+ target: generateIdForNode(consumer),
235
+ label: getEdgeLabelForMessageAsSource(message),
236
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
237
+ })
238
+ );
239
+ continue;
240
+ }
241
+
242
+ // Can any of the consumer channels be linked to any of the producer channels?
243
+ const producerChannels = producers
244
+ .map((producer) => producer.data.sends?.find((send) => send.id === message.data.id)?.to ?? [])
245
+ .flat();
246
+ const consumerChannels = consumer.data.receives?.find((receive) => receive.id === message.data.id)?.from ?? [];
247
+
248
+ for (const producerChannel of producerChannels) {
249
+ const producerChannelValue = getItemsFromCollectionByIdAndSemverOrLatest(
250
+ channels,
251
+ producerChannel.id,
252
+ producerChannel.version
253
+ )[0] as CollectionEntry<'channels'>;
254
+
255
+ for (const consumerChannel of consumerChannels) {
256
+ const consumerChannelValue = getItemsFromCollectionByIdAndSemverOrLatest(
257
+ channels,
258
+ consumerChannel.id,
259
+ consumerChannel.version
260
+ )[0] as CollectionEntry<'channels'>;
261
+ const channelChainToRender = getChannelChain(producerChannelValue, consumerChannelValue, channels);
262
+
263
+ // If there is a chain between them we need to render them al
264
+ if (channelChainToRender.length > 0) {
265
+ const { nodes: channelNodes, edges: channelEdges } = getNodesAndEdgesForChannelChain({
266
+ source: message,
267
+ target: consumer,
268
+ channelChain: channelChainToRender,
269
+ mode,
270
+ });
271
+
272
+ nodes.push(...channelNodes);
273
+ edges.push(...channelEdges);
274
+ } else {
275
+ // There is no chain found, we need to render the channel between message and the consumers
276
+ nodes.push(
277
+ createNode({
278
+ id: generateIdForNode(channel),
279
+ type: channel.collection,
280
+ data: { mode, channel: { ...channel.data } },
281
+ position: { x: 0, y: 0 },
282
+ })
283
+ );
284
+ edges.push(
285
+ createEdge({
286
+ id: generatedIdForEdge(message, channel),
287
+ source: generateIdForNode(message),
288
+ target: generateIdForNode(channel),
289
+ label: 'routes to',
290
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
291
+ })
292
+ );
293
+ edges.push(
294
+ createEdge({
295
+ id: generatedIdForEdge(channel, consumer),
296
+ source: generateIdForNode(channel),
297
+ target: generateIdForNode(consumer),
298
+ label: getEdgeLabelForMessageAsSource(message),
299
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
300
+ })
301
+ );
302
+ }
303
+ }
304
+ }
305
+
306
+ // If producer does not have a any channels defined, we need to connect the message to the consumer directly
307
+ if (producerChannels.length === 0 && channel) {
308
+ // Create the channel node
309
+ nodes.push(
310
+ createNode({
311
+ id: generateIdForNode(channel),
312
+ type: channel.collection,
313
+ data: { mode, channel: { ...channel.data } },
314
+ position: { x: 0, y: 0 },
315
+ })
316
+ );
317
+
318
+ // Connect the message to the channel
319
+ edges.push(
320
+ createEdge({
321
+ id: generatedIdForEdge(message, channel),
322
+ source: generateIdForNode(message),
323
+ target: generateIdForNode(channel),
324
+ label: 'routes to',
325
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
326
+ })
327
+ );
328
+
329
+ // Connect the channel to the consumer
330
+ edges.push(
331
+ createEdge({
332
+ id: generatedIdForEdge(channel, consumer),
333
+ source: generateIdForNode(channel),
334
+ target: generateIdForNode(consumer),
335
+ label: getEdgeLabelForMessageAsSource(message),
336
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
337
+ })
338
+ );
339
+ }
340
+ }
341
+ }
160
342
 
161
343
  // Handle messages that are both sent and received
162
344
  bothSentAndReceived.forEach((_message) => {
@@ -167,7 +349,7 @@ const getNodesAndEdges = async ({
167
349
  source: generateIdForNode(message),
168
350
  target: generateIdForNode(_message),
169
351
  label: 'publishes and subscribes',
170
- data: { message },
352
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget: { source: message, target: _message } },
171
353
  })
172
354
  );
173
355
  }
@@ -198,7 +380,8 @@ export const getNodesAndEdgesForQueries = async ({
198
380
  channelRenderMode = 'flat',
199
381
  }: Props) => {
200
382
  const queries = await getQueries();
201
- return getNodesAndEdges({ id, version, defaultFlow, mode, channelRenderMode, collection: queries });
383
+ const channels = await getChannels();
384
+ return getNodesAndEdges({ id, version, defaultFlow, mode, channelRenderMode, collection: queries, channels });
202
385
  };
203
386
 
204
387
  export const getNodesAndEdgesForCommands = async ({
@@ -209,7 +392,8 @@ export const getNodesAndEdgesForCommands = async ({
209
392
  channelRenderMode = 'flat',
210
393
  }: Props) => {
211
394
  const commands = await getCommands();
212
- return getNodesAndEdges({ id, version, defaultFlow, mode, channelRenderMode, collection: commands });
395
+ const channels = await getChannels();
396
+ return getNodesAndEdges({ id, version, defaultFlow, mode, channelRenderMode, collection: commands, channels });
213
397
  };
214
398
 
215
399
  export const getNodesAndEdgesForEvents = async ({
@@ -220,5 +404,616 @@ export const getNodesAndEdgesForEvents = async ({
220
404
  channelRenderMode = 'flat',
221
405
  }: Props) => {
222
406
  const events = await getEvents();
223
- return getNodesAndEdges({ id, version, defaultFlow, mode, channelRenderMode, collection: events });
407
+ const channels = await getChannels();
408
+ return getNodesAndEdges({ id, version, defaultFlow, mode, channelRenderMode, collection: events, channels });
409
+ };
410
+
411
+ export const getNodesAndEdgesForConsumedMessage = ({
412
+ message,
413
+ targetChannels = [],
414
+ services,
415
+ channels,
416
+ currentNodes = [],
417
+ target,
418
+ mode = 'simple',
419
+ }: {
420
+ message: CollectionEntry<CollectionMessageTypes>;
421
+ targetChannels?: { id: string; version: string }[];
422
+ services: CollectionEntry<'services'>[];
423
+ channels: CollectionEntry<'channels'>[];
424
+ currentNodes: Node[];
425
+ target: CollectionEntry<'services'>;
426
+ mode?: 'simple' | 'full';
427
+ }) => {
428
+ let nodes = [] as Node[],
429
+ edges = [] as any;
430
+
431
+ const messageId = generateIdForNode(message);
432
+
433
+ const rootSourceAndTarget = {
434
+ source: { id: generateIdForNode(message), collection: message.collection },
435
+ target: { id: generateIdForNode(target), collection: target.collection },
436
+ };
437
+
438
+ // Render the message node
439
+ nodes.push(
440
+ createNode({
441
+ id: messageId,
442
+ type: message.collection,
443
+ data: { mode, message: { ...message.data } },
444
+ position: { x: 0, y: 0 },
445
+ })
446
+ );
447
+
448
+ // Render the target node
449
+ nodes.push(
450
+ createNode({
451
+ id: generateIdForNode(target),
452
+ type: target.collection,
453
+ data: { mode, service: { ...target.data } },
454
+ position: { x: 0, y: 0 },
455
+ })
456
+ );
457
+
458
+ const targetMessageConfiguration = target.data.receives?.find((receive) => receive.id === message.data.id);
459
+ const channelsFromMessageToTarget = targetMessageConfiguration?.from ?? [];
460
+ const hydratedChannelsFromMessageToTarget = channelsFromMessageToTarget
461
+ .map((channel) => getItemsFromCollectionByIdAndSemverOrLatest(channels, channel.id, channel.version)[0])
462
+ .filter((channel) => channel !== undefined);
463
+
464
+ // Now we get the producers of the message and create nodes and edges for them
465
+ const producers = getProducersOfMessage(services, message);
466
+
467
+ const hasProducers = producers.length > 0;
468
+ const targetHasDefinedChannels = targetChannels.length > 0;
469
+
470
+ const isMessageEvent = message.collection === 'events';
471
+
472
+ // Warning edge if no producers or target channels are defined
473
+ if (!hasProducers && !targetHasDefinedChannels) {
474
+ edges.push(
475
+ createEdge({
476
+ id: generatedIdForEdge(message, target) + '-warning',
477
+ source: messageId,
478
+ target: generateIdForNode(target),
479
+ label: isMessageEvent ? `⚠️ No producers found \n ${message.data.name}` : getEdgeLabelForMessageAsSource(message),
480
+ data: { customColor: getColorFromString(message.data.id), warning: isMessageEvent, rootSourceAndTarget },
481
+ })
482
+ );
483
+ }
484
+
485
+ // If the target defined channels they consume the message from, we need to create the channel nodes and edges
486
+ if (targetHasDefinedChannels) {
487
+ for (const targetChannel of targetChannels) {
488
+ const channel = getItemsFromCollectionByIdAndSemverOrLatest(
489
+ channels,
490
+ targetChannel.id,
491
+ targetChannel.version
492
+ )[0] as CollectionEntry<'channels'>;
493
+
494
+ if (!channel) {
495
+ // No channe found, we just connect the message to the target directly
496
+ edges.push(
497
+ createEdge({
498
+ id: generatedIdForEdge(message, target),
499
+ source: messageId,
500
+ target: generateIdForNode(target),
501
+ label: getEdgeLabelForMessageAsSource(message),
502
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
503
+ })
504
+ );
505
+ continue;
506
+ }
507
+
508
+ const channelId = generateIdForNode(channel);
509
+
510
+ // Create the channel node
511
+ nodes.push(
512
+ createNode({
513
+ id: channelId,
514
+ type: channel.collection,
515
+ data: { mode, channel: { ...channel.data, ...channel } },
516
+ position: { x: 0, y: 0 },
517
+ })
518
+ );
519
+
520
+ // Connect the channel to the target
521
+ edges.push(
522
+ createEdge({
523
+ id: generatedIdForEdge(channel, target),
524
+ source: channelId,
525
+ target: generateIdForNode(target),
526
+ label: `consumes \n ${message.data.name}`,
527
+ data: { customColor: getColorFromString(message.data.id), warning: producers.length === 0, rootSourceAndTarget },
528
+ })
529
+ );
530
+
531
+ // If we dont have any producers, we will connect the message to the channel directly
532
+ if (producers.length === 0) {
533
+ const isEvent = message.collection === 'events';
534
+
535
+ edges.push(
536
+ createEdge({
537
+ id: generatedIdForEdge(message, channel),
538
+ source: messageId,
539
+ target: channelId,
540
+ label: isEvent ? `⚠️ No producers found \n ${message.data.name}` : getEdgeLabelForMessageAsSource(message),
541
+ data: { customColor: getColorFromString(message.data.id), warning: isEvent, rootSourceAndTarget },
542
+ markerEnd: {
543
+ type: MarkerType.ArrowClosed,
544
+ width: 40,
545
+ height: 40,
546
+ color: 'red',
547
+ },
548
+ style: {
549
+ strokeWidth: 5,
550
+ stroke: 'red',
551
+ },
552
+ })
553
+ );
554
+ }
555
+ }
556
+ }
557
+
558
+ // Process the producers for the message
559
+ for (const producer of producers) {
560
+ const producerId = generateIdForNode(producer);
561
+
562
+ // Create the producer node
563
+ nodes.push(
564
+ createNode({
565
+ id: producerId,
566
+ type: producer.collection,
567
+ data: { mode, service: { ...producer.data } },
568
+ position: { x: 0, y: 0 },
569
+ })
570
+ );
571
+
572
+ // The message is always connected directly to the producer
573
+ edges.push(
574
+ createEdge({
575
+ id: generatedIdForEdge(producer, message),
576
+ source: producerId,
577
+ target: messageId,
578
+ label: getEdgeLabelForServiceAsTarget(message),
579
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
580
+ })
581
+ );
582
+
583
+ // Check if the producer is sending the message to a channel
584
+ const producerConfigurationForMessage = producer.data.sends?.find((send) => send.id === message.data.id);
585
+ const producerChannelConfiguration = producerConfigurationForMessage?.to ?? [];
586
+
587
+ const producerHasChannels = producerChannelConfiguration.length > 0;
588
+ const targetHasChannels = hydratedChannelsFromMessageToTarget.length > 0;
589
+
590
+ // If the producer or target (consumer) has no channels defined, we just connect the message to the consumer directly
591
+ // of the target has no channels defined, we just connect the message to the target directly
592
+ if ((!producerHasChannels && !targetHasChannels) || !targetHasChannels) {
593
+ edges.push(
594
+ createEdge({
595
+ id: generatedIdForEdge(message, target),
596
+ source: messageId,
597
+ target: generateIdForNode(target),
598
+ label: getEdgeLabelForMessageAsSource(message),
599
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
600
+ })
601
+ );
602
+ continue;
603
+ }
604
+
605
+ // If the target has channels but the producer does not
606
+ // We then connect the message to the channels directly
607
+ if (targetHasChannels && !producerHasChannels) {
608
+ for (const targetChannel of hydratedChannelsFromMessageToTarget) {
609
+ edges.push(
610
+ createEdge({
611
+ id: generatedIdForEdge(message, targetChannel),
612
+ source: messageId,
613
+ target: generateIdForNode(targetChannel),
614
+ label: 'routes to',
615
+ data: {
616
+ customColor: getColorFromString(message.data.id),
617
+ rootSourceAndTarget: {
618
+ source: { id: generateIdForNode(message), collection: message.collection },
619
+ target: { id: generateIdForNode(target), collection: target.collection },
620
+ },
621
+ },
622
+ })
623
+ );
624
+ }
625
+ continue;
626
+ }
627
+
628
+ // Process each producer channel configuration
629
+ for (const producerChannel of producerChannelConfiguration) {
630
+ const channel = getItemsFromCollectionByIdAndSemverOrLatest(
631
+ channels,
632
+ producerChannel.id,
633
+ producerChannel.version
634
+ )[0] as CollectionEntry<'channels'>;
635
+
636
+ // If we cannot find the channel in EventCatalog, we just connect the message to the target directly
637
+ if (!channel) {
638
+ edges.push(
639
+ createEdge({
640
+ id: generatedIdForEdge(message, target),
641
+ source: messageId,
642
+ target: generateIdForNode(target),
643
+ label: getEdgeLabelForMessageAsSource(message),
644
+ data: {
645
+ customColor: getColorFromString(message.data.id),
646
+ rootSourceAndTarget: {
647
+ source: { id: generateIdForNode(message), collection: message.collection },
648
+ target: { id: generateIdForNode(target), collection: target.collection },
649
+ },
650
+ },
651
+ })
652
+ );
653
+ continue;
654
+ }
655
+
656
+ // Does the producer have any channels defined? If not, we just connect the message to the target directly
657
+ if (!producerHasChannels) {
658
+ edges.push(
659
+ createEdge({
660
+ id: generatedIdForEdge(message, target),
661
+ source: messageId,
662
+ target: generateIdForNode(target),
663
+ label: getEdgeLabelForMessageAsSource(message),
664
+ data: {
665
+ customColor: getColorFromString(message.data.id),
666
+ rootSourceAndTarget: {
667
+ source: { id: generateIdForNode(message), collection: message.collection },
668
+ target: { id: generateIdForNode(target), collection: target.collection },
669
+ },
670
+ },
671
+ })
672
+ );
673
+ continue;
674
+ }
675
+
676
+ // The producer does have channels defined, we need to try and work out the path the message takes to the target
677
+ for (const targetChannel of hydratedChannelsFromMessageToTarget) {
678
+ const channelChainToRender = getChannelChain(channel, targetChannel, channels);
679
+ if (channelChainToRender.length > 0) {
680
+ const { nodes: channelNodes, edges: channelEdges } = getNodesAndEdgesForChannelChain({
681
+ source: message,
682
+ target: target,
683
+ channelChain: channelChainToRender,
684
+ mode,
685
+ });
686
+
687
+ nodes.push(...channelNodes);
688
+ edges.push(...channelEdges);
689
+
690
+ break;
691
+ } else {
692
+ // No chain found create the channel, and connect the message to the target channel directly
693
+ nodes.push(
694
+ createNode({
695
+ id: generateIdForNode(targetChannel),
696
+ type: targetChannel.collection,
697
+ data: { mode, channel: { ...targetChannel.data, ...targetChannel } },
698
+ position: { x: 0, y: 0 },
699
+ })
700
+ );
701
+ edges.push(
702
+ createEdge({
703
+ id: generatedIdForEdge(message, targetChannel),
704
+ source: messageId,
705
+ target: generateIdForNode(targetChannel),
706
+ label: 'routes to',
707
+ data: {
708
+ rootSourceAndTarget: {
709
+ source: { id: generateIdForNode(message), collection: message.collection },
710
+ target: { id: generateIdForNode(targetChannel), collection: targetChannel.collection },
711
+ },
712
+ },
713
+ })
714
+ );
715
+ }
716
+ }
717
+ }
718
+ }
719
+
720
+ // Remove any nodes that are already in the current nodes (already on the UI)
721
+ nodes = nodes.filter((node) => !currentNodes.find((n) => n.id === node.id));
722
+
723
+ // Make sure all nodes are unique
724
+ const uniqueNodes = nodes.filter((node, index, self) => index === self.findIndex((t) => t.id === node.id));
725
+
726
+ const uniqueEdges = edges.filter(
727
+ (edge: any, index: number, self: any[]) => index === self.findIndex((t: any) => t.id === edge.id)
728
+ );
729
+
730
+ return { nodes: uniqueNodes, edges: uniqueEdges };
731
+ };
732
+
733
+ export const getNodesAndEdgesForProducedMessage = ({
734
+ message,
735
+ sourceChannels,
736
+ services,
737
+ channels,
738
+ currentNodes = [],
739
+ currentEdges = [],
740
+ source,
741
+ mode = 'simple',
742
+ }: {
743
+ message: CollectionEntry<CollectionMessageTypes>;
744
+ sourceChannels?: { id: string; version: string }[];
745
+ services: CollectionEntry<'services'>[];
746
+ channels: CollectionEntry<'channels'>[];
747
+ currentNodes: Node[];
748
+ currentEdges: Edge[];
749
+ source: CollectionEntry<'services'>;
750
+ mode?: 'simple' | 'full';
751
+ }) => {
752
+ let nodes = [] as Node[],
753
+ edges = [] as any;
754
+
755
+ const messageId = generateIdForNode(message);
756
+
757
+ const rootSourceAndTarget = {
758
+ source: { id: generateIdForNode(source), collection: source.collection },
759
+ target: { id: generateIdForNode(message), collection: message.collection },
760
+ };
761
+
762
+ // Render the message node
763
+ nodes.push(
764
+ createNode({
765
+ id: messageId,
766
+ type: message.collection,
767
+ data: { mode, message: { ...message.data } },
768
+ position: { x: 0, y: 0 },
769
+ })
770
+ );
771
+
772
+ // Render the producer node
773
+ nodes.push(
774
+ createNode({
775
+ id: generateIdForNode(source),
776
+ type: source.collection,
777
+ data: { mode, service: { ...source.data } },
778
+ position: { x: 0, y: 0 },
779
+ })
780
+ );
781
+
782
+ // Render the edge from the producer to the message
783
+ edges.push(
784
+ createEdge({
785
+ id: generatedIdForEdge(source, message),
786
+ source: generateIdForNode(source),
787
+ target: messageId,
788
+ label: getEdgeLabelForServiceAsTarget(message),
789
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
790
+ })
791
+ );
792
+
793
+ const sourceMessageConfiguration = source.data.sends?.find((send) => send.id === message.data.id);
794
+ const channelsFromSourceToMessage = sourceMessageConfiguration?.to ?? [];
795
+
796
+ const hydratedChannelsFromSourceToMessage = channelsFromSourceToMessage
797
+ .map((channel) => getItemsFromCollectionByIdAndSemverOrLatest(channels, channel.id, channel.version)[0])
798
+ .filter((channel) => channel !== undefined);
799
+
800
+ // If the source defined channels they send the message to, we need to create the channel nodes and edges
801
+ if (sourceChannels && sourceChannels.length > 0) {
802
+ for (const sourceChannel of sourceChannels) {
803
+ const channel = getItemsFromCollectionByIdAndSemverOrLatest(
804
+ channels,
805
+ sourceChannel.id,
806
+ sourceChannel.version
807
+ )[0] as CollectionEntry<'channels'>;
808
+
809
+ if (!channel) {
810
+ // No channel found, we just connect the message to the source directly
811
+ edges.push(
812
+ createEdge({
813
+ id: generatedIdForEdge(message, source),
814
+ source: messageId,
815
+ target: generateIdForNode(source),
816
+ label: getEdgeLabelForMessageAsSource(message),
817
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
818
+ })
819
+ );
820
+ continue;
821
+ }
822
+
823
+ const channelId = generateIdForNode(channel);
824
+
825
+ // Create the channel node
826
+ nodes.push(
827
+ createNode({
828
+ id: channelId,
829
+ type: channel.collection,
830
+ data: { mode, channel: { ...channel.data, ...channel, mode } },
831
+ position: { x: 0, y: 0 },
832
+ })
833
+ );
834
+
835
+ // Connect the produced message to the channel
836
+ edges.push(
837
+ createEdge({
838
+ id: generatedIdForEdge(message, channel),
839
+ source: messageId,
840
+ target: channelId,
841
+ label: 'routes to',
842
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
843
+ })
844
+ );
845
+ }
846
+ }
847
+
848
+ // Now we get the producers of the message and create nodes and edges for them
849
+ const consumers = getConsumersOfMessage(services, message);
850
+
851
+ // TODO: Make this a UI Switch in the future....
852
+ const latestConsumers = consumers.filter(
853
+ (consumer) => getLatestVersionInCollectionById(services, consumer.data.id) === consumer.data.version
854
+ );
855
+
856
+ // Process the consumers for the message
857
+ for (const consumer of latestConsumers) {
858
+ const consumerId = generateIdForNode(consumer);
859
+
860
+ // Create the consumer node
861
+ nodes.push(
862
+ createNode({
863
+ id: consumerId,
864
+ type: consumer.collection,
865
+ data: { mode, service: { ...consumer.data } },
866
+ position: { x: 0, y: 0 },
867
+ })
868
+ );
869
+
870
+ // Check if the consumer is consuming the message from a channel
871
+ const consumerConfigurationForMessage = consumer.data.receives?.find((receive) => receive.id === message.data.id);
872
+ const consumerChannelConfiguration = consumerConfigurationForMessage?.from ?? [];
873
+
874
+ const consumerHasChannels = consumerChannelConfiguration.length > 0;
875
+ const producerHasChannels = hydratedChannelsFromSourceToMessage.length > 0;
876
+
877
+ // If the consumer and producer have no channels defined,
878
+ // or the consumer has no channels defined, we just connect the message to the consumer directly
879
+ if ((!consumerHasChannels && !producerHasChannels) || !consumerHasChannels) {
880
+ edges.push(
881
+ createEdge({
882
+ id: generatedIdForEdge(message, consumer),
883
+ source: messageId,
884
+ target: consumerId,
885
+ label: getEdgeLabelForMessageAsSource(message),
886
+ data: { customColor: getColorFromString(message.data.id), rootSourceAndTarget },
887
+ })
888
+ );
889
+ continue;
890
+ }
891
+
892
+ // Process each consumer channel configuration
893
+ for (const consumerChannel of consumerChannelConfiguration) {
894
+ const channel = getItemsFromCollectionByIdAndSemverOrLatest(
895
+ channels,
896
+ consumerChannel.id,
897
+ consumerChannel.version
898
+ )[0] as CollectionEntry<'channels'>;
899
+
900
+ const edgeProps = { customColor: getColorFromString(message.data.id), rootSourceAndTarget };
901
+
902
+ // If the channel cannot be found in EventCatalog, we just connect the message to the consumer directly
903
+ // as a fallback, rather than just an empty node floating around
904
+ if (!channel) {
905
+ edges.push(
906
+ createEdge({
907
+ id: generatedIdForEdge(message, consumer),
908
+ source: messageId,
909
+ target: consumerId,
910
+ label: 'consumes',
911
+ data: edgeProps,
912
+ })
913
+ );
914
+ continue;
915
+ }
916
+
917
+ // We always add the consumer channel to be rendered
918
+ nodes.push(
919
+ createNode({
920
+ id: generateIdForNode(channel),
921
+ type: channel.collection,
922
+ data: { mode, channel: { ...channel.data, ...channel } },
923
+ position: { x: 0, y: 0 },
924
+ })
925
+ );
926
+
927
+ // If the producer does not have any channels defined, we connect the message to the consumers channel directly
928
+ if (!producerHasChannels) {
929
+ edges.push(
930
+ createEdge({
931
+ id: generatedIdForEdge(message, channel),
932
+ source: messageId,
933
+ target: generateIdForNode(channel),
934
+ label: 'routes to',
935
+ data: edgeProps,
936
+ })
937
+ );
938
+ edges.push(
939
+ createEdge({
940
+ id: generatedIdForEdge(channel, consumer),
941
+ source: generateIdForNode(channel),
942
+ target: generateIdForNode(consumer),
943
+ label: getEdgeLabelForMessageAsSource(message),
944
+ data: {
945
+ ...edgeProps,
946
+ rootSourceAndTarget: {
947
+ source: { id: generateIdForNode(message), collection: message.collection },
948
+ target: { id: generateIdForNode(consumer), collection: consumer.collection },
949
+ },
950
+ },
951
+ })
952
+ );
953
+ continue;
954
+ }
955
+
956
+ // The producer has channels defined, we need to try and work out the path the message takes to the consumer
957
+ for (const sourceChannel of hydratedChannelsFromSourceToMessage) {
958
+ const channelChainToRender = getChannelChain(sourceChannel, channel, channels);
959
+
960
+ if (channelChainToRender.length > 0) {
961
+ const { nodes: channelNodes, edges: channelEdges } = getNodesAndEdgesForChannelChain({
962
+ source: message,
963
+ target: consumer,
964
+ channelChain: channelChainToRender,
965
+ mode,
966
+ });
967
+
968
+ nodes.push(...channelNodes);
969
+ edges.push(...channelEdges);
970
+ } else {
971
+ // No chain found, we need to connect to the message to the channel
972
+ // And the channel to the consumer
973
+ edges.push(
974
+ createEdge({
975
+ id: generatedIdForEdge(message, channel),
976
+ source: messageId,
977
+ target: generateIdForNode(channel),
978
+ label: 'routes to',
979
+ data: {
980
+ ...edgeProps,
981
+ rootSourceAndTarget: {
982
+ source: { id: generateIdForNode(message), collection: message.collection },
983
+ target: { id: generateIdForNode(consumer), collection: consumer.collection },
984
+ },
985
+ },
986
+ })
987
+ );
988
+ edges.push(
989
+ createEdge({
990
+ id: generatedIdForEdge(channel, consumer),
991
+ source: generateIdForNode(channel),
992
+ target: generateIdForNode(consumer),
993
+ label: `${getEdgeLabelForMessageAsSource(message, true)} \n ${message.data.name}`,
994
+ data: {
995
+ ...edgeProps,
996
+ rootSourceAndTarget: {
997
+ source: { id: generateIdForNode(message), collection: message.collection },
998
+ target: { id: generateIdForNode(consumer), collection: consumer.collection },
999
+ },
1000
+ },
1001
+ })
1002
+ );
1003
+ }
1004
+ }
1005
+ }
1006
+ }
1007
+
1008
+ // Remove any nodes that are already in the current nodes (already on the UI)
1009
+ nodes = nodes.filter((node) => !currentNodes.find((n) => n.id === node.id));
1010
+
1011
+ // Make sure all nodes are unique
1012
+ const uniqueNodes = nodes.filter((node, index, self) => index === self.findIndex((t) => t.id === node.id));
1013
+
1014
+ const uniqueEdges = edges.filter(
1015
+ (edge: any, index: number, self: any[]) => index === self.findIndex((t: any) => t.id === edge.id)
1016
+ );
1017
+
1018
+ return { nodes: uniqueNodes, edges: uniqueEdges };
224
1019
  };