@eventcatalog/core 2.12.3 → 2.13.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/package.json +1 -1
  4. package/public/icons/protocols/kafka.svg +1 -0
  5. package/scripts/catalog-to-astro-content-directory.js +13 -1
  6. package/scripts/default-files-for-collections/channels.md +8 -0
  7. package/scripts/map-catalog-to-astro.js +1 -0
  8. package/src/components/DocsNavigation.astro +4 -4
  9. package/src/components/Header.astro +1 -1
  10. package/src/components/Lists/PillListFlat.tsx +19 -12
  11. package/src/components/Lists/ProtocolList.tsx +88 -0
  12. package/src/components/MDX/ChannelInformation/ChannelInformation.tsx +79 -0
  13. package/src/components/MDX/Flow/Flow.astro +3 -3
  14. package/src/components/MDX/NodeGraph/DownloadButton.tsx +3 -3
  15. package/src/components/MDX/NodeGraph/Edges/AnimatedMessageEdge.tsx +82 -0
  16. package/src/components/MDX/NodeGraph/NodeGraph.astro +24 -67
  17. package/src/components/MDX/NodeGraph/NodeGraph.tsx +188 -17
  18. package/src/components/MDX/NodeGraph/Nodes/Channel.tsx +133 -0
  19. package/src/components/MDX/components.tsx +2 -0
  20. package/src/components/Seo.astro +0 -2
  21. package/src/components/SideBars/CatalogResourcesSideBar/index.tsx +4 -13
  22. package/src/components/SideBars/ChannelSideBar.astro +117 -0
  23. package/src/components/SideBars/MessageSideBar.astro +13 -0
  24. package/src/content/config.ts +37 -0
  25. package/src/icons/protocols/WebSocket.svg +1 -0
  26. package/src/icons/protocols/amqp.svg +1 -0
  27. package/src/icons/protocols/eventbridge.svg +6 -0
  28. package/src/icons/protocols/googlepubsub.svg +1 -0
  29. package/src/icons/protocols/http.svg +1 -0
  30. package/src/icons/protocols/index.ts +15 -0
  31. package/src/icons/protocols/jms.svg +1 -0
  32. package/src/icons/protocols/kafka.svg +1 -0
  33. package/src/icons/protocols/mercure.svg +20 -0
  34. package/src/icons/protocols/mqtt.svg +17 -0
  35. package/src/icons/protocols/nats.svg +13 -0
  36. package/src/icons/protocols/pulsar.svg +4 -0
  37. package/src/icons/protocols/redis.svg +1 -0
  38. package/src/icons/protocols/sns.svg +6 -0
  39. package/src/icons/protocols/solace.svg +1 -0
  40. package/src/icons/protocols/sqs.svg +7 -0
  41. package/src/icons/protocols/ws.svg +1 -0
  42. package/src/layouts/DiscoverLayout.astro +3 -3
  43. package/src/layouts/VerticalSideBarLayout.astro +10 -5
  44. package/src/layouts/VisualiserLayout.astro +3 -3
  45. package/src/pages/discover/[type]/index.astro +3 -3
  46. package/src/pages/docs/[type]/[id]/[version]/asyncapi/index.astro +2 -2
  47. package/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +2 -2
  48. package/src/pages/docs/[type]/[id]/[version]/index.astro +11 -4
  49. package/src/pages/docs/[type]/[id]/[version]/spec/index.astro +2 -2
  50. package/src/pages/docs/[type]/[id]/index.astro +17 -4
  51. package/src/pages/index.astro +3 -3
  52. package/src/pages/visualiser/[type]/[id]/[version]/index.astro +2 -2
  53. package/src/pages/visualiser/[type]/[id]/index.astro +2 -2
  54. package/src/remark-plugins/mermaid.ts +0 -1
  55. package/src/types/index.ts +2 -2
  56. package/src/utils/channels.ts +64 -0
  57. package/src/utils/collections/icons.ts +6 -0
  58. package/src/utils/commands.ts +6 -0
  59. package/src/utils/{config → eventcatalog-config}/catalog.ts +1 -1
  60. package/src/utils/events.ts +5 -0
  61. package/src/utils/{domains/node-graph.ts → node-graphs/domains-node-graph.ts} +2 -2
  62. package/src/utils/{flows/node-graph.ts → node-graphs/flows-node-graph.ts} +2 -2
  63. package/src/utils/node-graphs/message-node-graph.ts +216 -0
  64. package/src/utils/{services/node-graph.ts → node-graphs/services-node-graph.ts} +79 -54
  65. package/src/utils/node-graphs/utils/utils.ts +128 -0
  66. package/src/utils/{pages/pages.ts → page-loaders/page-data-loader.ts} +4 -2
  67. package/src/utils/queries.ts +5 -0
  68. package/tsconfig.json +2 -1
  69. package/src/remark-plugins/remark-modified-time.mjs +0 -9
  70. package/src/utils/commands/node-graph.ts +0 -144
  71. package/src/utils/events/node-graph.ts +0 -145
  72. package/src/utils/node-graph-utils/utils.ts +0 -29
  73. package/src/utils/queries/node-graph.ts +0 -144
  74. /package/src/pages/docs/[type]/[id]/[version]/spec/{styles.css → _styles.css} +0 -0
  75. /package/src/utils/{changelogs → collections}/changelogs.ts +0 -0
  76. /package/src/utils/{domains → collections}/domains.ts +0 -0
  77. /package/src/utils/{flows → collections}/flows.ts +0 -0
  78. /package/src/utils/{services → collections}/services.ts +0 -0
  79. /package/src/utils/{versions → collections}/versions.ts +0 -0
@@ -0,0 +1,64 @@
1
+ import { getCollection } from 'astro:content';
2
+ import type { CollectionEntry } from 'astro:content';
3
+ import path from 'path';
4
+ import { getVersionForCollectionItem, satisfies } from './collections/util';
5
+ import { getMessages } from './messages';
6
+ import type { CollectionMessageTypes } from '@types';
7
+
8
+ const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
9
+
10
+ type Channel = CollectionEntry<'channels'> & {
11
+ catalog: {
12
+ path: string;
13
+ filePath: string;
14
+ type: string;
15
+ };
16
+ };
17
+
18
+ interface Props {
19
+ getAllVersions?: boolean;
20
+ }
21
+
22
+ export const getChannels = async ({ getAllVersions = true }: Props = {}): Promise<Channel[]> => {
23
+ const channels = await getCollection('channels', (query) => {
24
+ return (getAllVersions || !query.slug.includes('versioned')) && query.data.hidden !== true;
25
+ });
26
+
27
+ const { commands, events, queries } = await getMessages();
28
+ const allMessages = [...commands, ...events, ...queries];
29
+
30
+ return channels.map((channel) => {
31
+ const { latestVersion, versions } = getVersionForCollectionItem(channel, channels);
32
+
33
+ const messagesForChannel = allMessages.filter((message) => {
34
+ return message.data.channels?.some((messageChannel) => {
35
+ if (messageChannel.id != channel.data.id) return false;
36
+ if (messageChannel.version == 'latest' || messageChannel.version == undefined)
37
+ return channel.data.version == latestVersion;
38
+ return satisfies(channel.data.version, messageChannel.version);
39
+ });
40
+ });
41
+
42
+ const messages = messagesForChannel.map((message: CollectionEntry<CollectionMessageTypes>) => {
43
+ return { id: message.data.id, name: message.data.name, version: message.data.version, collection: message.collection };
44
+ });
45
+
46
+ return {
47
+ ...channel,
48
+ data: {
49
+ ...channel.data,
50
+ versions,
51
+ latestVersion,
52
+ messages,
53
+ },
54
+ catalog: {
55
+ path: path.join(channel.collection, channel.id.replace('/index.mdx', '')),
56
+ absoluteFilePath: path.join(PROJECT_DIR, channel.collection, channel.id.replace('/index.mdx', '/index.md')),
57
+ astroContentFilePath: path.join(process.cwd(), 'src', 'content', channel.collection, channel.id),
58
+ filePath: path.join(process.cwd(), 'src', 'catalog-files', channel.collection, channel.id.replace('/index.mdx', '')),
59
+ publicPath: path.join('/generated', channel.collection, channel.id.replace('/index.mdx', '')),
60
+ type: 'event',
61
+ },
62
+ };
63
+ });
64
+ };
@@ -7,6 +7,8 @@ import {
7
7
  QueueListIcon,
8
8
  UserGroupIcon,
9
9
  UserIcon,
10
+ ArrowsRightLeftIcon,
11
+ VariableIcon,
10
12
  } from '@heroicons/react/24/outline';
11
13
 
12
14
  export const getIconForCollection = (collection: string) => {
@@ -27,6 +29,10 @@ export const getIconForCollection = (collection: string) => {
27
29
  return UserGroupIcon;
28
30
  case 'users':
29
31
  return UserIcon;
32
+ case 'channels':
33
+ return ArrowsRightLeftIcon;
34
+ case 'channels-parameter':
35
+ return VariableIcon;
30
36
  default:
31
37
  return ServerIcon;
32
38
  }
@@ -21,7 +21,9 @@ export const getCommands = async ({ getAllVersions = true }: Props = {}): Promis
21
21
  const commands = await getCollection('commands', (command) => {
22
22
  return (getAllVersions || !command.slug.includes('versioned')) && command.data.hidden !== true;
23
23
  });
24
+
24
25
  const services = await getCollection('services');
26
+ const allChannels = await getCollection('channels');
25
27
 
26
28
  return commands.map((command) => {
27
29
  const { latestVersion, versions } = getVersionForCollectionItem(command, commands);
@@ -42,10 +44,14 @@ export const getCommands = async ({ getAllVersions = true }: Props = {}): Promis
42
44
  });
43
45
  });
44
46
 
47
+ const messageChannels = command.data.channels || [];
48
+ const channelsForCommand = allChannels.filter((c) => messageChannels.some((channel) => c.data.id === channel.id));
49
+
45
50
  return {
46
51
  ...command,
47
52
  data: {
48
53
  ...command.data,
54
+ messageChannels: channelsForCommand,
49
55
  producers,
50
56
  consumers,
51
57
  versions,
@@ -24,7 +24,7 @@ const getConfigValue = (obj: any, key: string, defaultValue: any) => {
24
24
 
25
25
  export const isCollectionVisibleInCatalog = (collection: string) => {
26
26
  const sidebarConfig = config?.default?.docs?.sidebar || {};
27
- const collections = ['events', 'commands', 'queries', 'domains', 'flows', 'services', 'teams', 'users'];
27
+ const collections = ['events', 'commands', 'queries', 'domains', 'channels', 'flows', 'services', 'teams', 'users'];
28
28
 
29
29
  if (!collections.includes(collection)) return false;
30
30
 
@@ -23,6 +23,7 @@ export const getEvents = async ({ getAllVersions = true }: Props = {}): Promise<
23
23
  });
24
24
 
25
25
  const services = await getCollection('services');
26
+ const allChannels = await getCollection('channels');
26
27
 
27
28
  return events.map((event) => {
28
29
  const { latestVersion, versions } = getVersionForCollectionItem(event, events);
@@ -43,10 +44,14 @@ export const getEvents = async ({ getAllVersions = true }: Props = {}): Promise<
43
44
  })
44
45
  );
45
46
 
47
+ const messageChannels = event.data.channels || [];
48
+ const channelsForEvent = allChannels.filter((c) => messageChannels.some((channel) => c.data.id === channel.id));
49
+
46
50
  return {
47
51
  ...event,
48
52
  data: {
49
53
  ...event.data,
54
+ messageChannels: channelsForEvent,
50
55
  producers,
51
56
  consumers,
52
57
  versions,
@@ -1,6 +1,6 @@
1
1
  import { getCollection } from 'astro:content';
2
- import { createDagreGraph, calculatedNodes } from '@utils/node-graph-utils/utils';
3
- import { getNodesAndEdges as getServicesNodeAndEdges } from '../services/node-graph';
2
+ import { createDagreGraph, calculatedNodes } from '@utils/node-graphs/utils/utils';
3
+ import { getNodesAndEdges as getServicesNodeAndEdges } from './services-node-graph';
4
4
  import merge from 'lodash.merge';
5
5
  import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
6
6
 
@@ -1,6 +1,6 @@
1
1
  import { getCollection, type CollectionEntry } from 'astro:content';
2
2
  import dagre from 'dagre';
3
- import { createDagreGraph, calculatedNodes } from '@utils/node-graph-utils/utils';
3
+ import { createDagreGraph, calculatedNodes } from '@utils/node-graphs/utils/utils';
4
4
  import { MarkerType } from 'reactflow';
5
5
  import type { Node as NodeType } from 'reactflow';
6
6
  import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
@@ -118,7 +118,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
118
118
  id: `step-${step.id}-step-${path.id}`,
119
119
  source: `step-${step.id}`,
120
120
  target: `step-${path.id}`,
121
- type: 'smoothstep',
121
+ type: 'bezier',
122
122
  label: path.label,
123
123
  animated: true,
124
124
  markerEnd: {
@@ -0,0 +1,216 @@
1
+ // import { getColor } from '@utils/colors';
2
+ import { getEvents } from '@utils/events';
3
+ import type { CollectionEntry } from 'astro:content';
4
+ import dagre from 'dagre';
5
+ import {
6
+ calculatedNodes,
7
+ createDagreGraph,
8
+ createEdge,
9
+ generatedIdForEdge,
10
+ generateIdForNode,
11
+ getChannelNodesAndEdges,
12
+ } from './utils/utils';
13
+ import { MarkerType } from 'reactflow';
14
+ import { findMatchingNodes } from '@utils/collections/util';
15
+ import type { CollectionMessageTypes } from '@types';
16
+ import { getCommands } from '@utils/commands';
17
+ import { getQueries } from '@utils/queries';
18
+
19
+ type DagreGraph = any;
20
+
21
+ interface Props {
22
+ id: string;
23
+ version: string;
24
+ defaultFlow?: DagreGraph;
25
+ mode?: 'simple' | 'full';
26
+ collection?: CollectionEntry<CollectionMessageTypes>[];
27
+ }
28
+
29
+ const getEdgeLabelForServiceAsTarget = (data: CollectionEntry<CollectionMessageTypes>) => {
30
+ const type = data.collection;
31
+ switch (type) {
32
+ case 'commands':
33
+ return 'invokes';
34
+ case 'events':
35
+ return 'publishes event';
36
+ case 'queries':
37
+ return 'requests';
38
+ default:
39
+ return 'sends to';
40
+ }
41
+ };
42
+ const getEdgeLabelForMessageAsSource = (data: CollectionEntry<CollectionMessageTypes>) => {
43
+ const type = data.collection;
44
+ switch (type) {
45
+ case 'commands':
46
+ return 'accepts';
47
+ case 'events':
48
+ return 'subscribed by';
49
+ case 'queries':
50
+ return 'accepts';
51
+ default:
52
+ return 'sends to';
53
+ }
54
+ };
55
+
56
+ const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simple', collection = [] }: Props) => {
57
+ const flow = defaultFlow || createDagreGraph({ ranksep: 300, nodesep: 50 });
58
+ const nodes = [] as any,
59
+ edges = [] as any;
60
+
61
+ const message = collection.find((message) => {
62
+ return message.data.id === id && message.data.version === version;
63
+ });
64
+
65
+ // Nothing found...
66
+ if (!message) {
67
+ return {
68
+ nodes: [],
69
+ edges: [],
70
+ };
71
+ }
72
+
73
+ const producers = (message.data.producers as CollectionEntry<'services'>[]) || [];
74
+ const consumers = (message.data.consumers as CollectionEntry<'services'>[]) || [];
75
+ const channels = (message.data.messageChannels as CollectionEntry<'channels'>[]) || [];
76
+
77
+ // Track nodes that are both sent and received
78
+ const bothSentAndReceived = findMatchingNodes(producers, consumers);
79
+
80
+ producers.forEach((producer) => {
81
+ nodes.push({
82
+ id: generateIdForNode(producer),
83
+ type: producer?.collection,
84
+ sourcePosition: 'right',
85
+ targetPosition: 'left',
86
+ data: { mode, service: producer },
87
+ position: { x: 250, y: 0 },
88
+ });
89
+
90
+ // If the event has channels, we need to render them, otherwise connect the producer to the event
91
+ if (message.data.channels) {
92
+ const { nodes: channelNodes, edges: channelEdges } = getChannelNodesAndEdges({
93
+ channels,
94
+ channelsToRender: message.data.channels,
95
+ source: producer,
96
+ target: message,
97
+ sourceToChannelLabel: getEdgeLabelForServiceAsTarget(message),
98
+ channelToTargetLabel: getEdgeLabelForServiceAsTarget(message),
99
+ mode,
100
+ currentNodes: nodes,
101
+ });
102
+ nodes.push(...channelNodes);
103
+ edges.push(...channelEdges);
104
+ } else {
105
+ edges.push({
106
+ id: generatedIdForEdge(producer, message),
107
+ source: generateIdForNode(producer),
108
+ target: generateIdForNode(message),
109
+ label: getEdgeLabelForServiceAsTarget(message),
110
+ data: { message },
111
+ animated: false,
112
+ markerEnd: {
113
+ type: MarkerType.ArrowClosed,
114
+ width: 40,
115
+ height: 40,
116
+ },
117
+ style: {
118
+ strokeWidth: 1,
119
+ },
120
+ });
121
+ }
122
+ });
123
+
124
+ // The message itself
125
+ nodes.push({
126
+ id: generateIdForNode(message),
127
+ sourcePosition: 'right',
128
+ targetPosition: 'left',
129
+ data: { mode, message: message },
130
+ position: { x: 0, y: 0 },
131
+ type: message.collection,
132
+ });
133
+
134
+ // The messages the service sends
135
+ consumers.forEach((consumer) => {
136
+ nodes.push({
137
+ id: generateIdForNode(consumer),
138
+ sourcePosition: 'right',
139
+ targetPosition: 'left',
140
+ data: { title: consumer?.data.id, mode, service: consumer },
141
+ position: { x: 0, y: 0 },
142
+ type: consumer?.collection,
143
+ });
144
+
145
+ if (message.data.channels) {
146
+ const { nodes: channelNodes, edges: channelEdges } = getChannelNodesAndEdges({
147
+ channels,
148
+ channelsToRender: channels.map((channel) => ({ id: channel.data.id, version: channel.data.version })),
149
+ source: message,
150
+ target: consumer,
151
+ channelToTargetLabel: getEdgeLabelForMessageAsSource(message),
152
+ mode,
153
+ currentNodes: nodes,
154
+ });
155
+
156
+ nodes.push(...channelNodes);
157
+ edges.push(...channelEdges);
158
+ } else {
159
+ edges.push(
160
+ createEdge({
161
+ id: generatedIdForEdge(message, consumer),
162
+ source: generateIdForNode(message),
163
+ target: generateIdForNode(consumer),
164
+ label: getEdgeLabelForMessageAsSource(message),
165
+ data: { message },
166
+ })
167
+ );
168
+ }
169
+ });
170
+
171
+ // Handle messages that are both sent and received
172
+ bothSentAndReceived.forEach((_message) => {
173
+ if (message) {
174
+ edges.push(
175
+ createEdge({
176
+ id: generatedIdForEdge(message, _message) + '-both',
177
+ source: generateIdForNode(message),
178
+ target: generateIdForNode(_message),
179
+ label: 'publishes and subscribes',
180
+ data: { message },
181
+ })
182
+ );
183
+ }
184
+ });
185
+
186
+ nodes.forEach((node: any) => {
187
+ flow.setNode(node.id, { width: 150, height: 100 });
188
+ });
189
+
190
+ edges.forEach((edge: any) => {
191
+ flow.setEdge(edge.source, edge.target);
192
+ });
193
+
194
+ // Render the diagram in memory getting hte X and Y
195
+ dagre.layout(flow);
196
+
197
+ return {
198
+ nodes: calculatedNodes(flow, nodes),
199
+ edges,
200
+ };
201
+ };
202
+
203
+ export const getNodesAndEdgesForQueries = async ({ id, version, defaultFlow, mode = 'simple' }: Props) => {
204
+ const queries = await getQueries();
205
+ return getNodesAndEdges({ id, version, defaultFlow, mode, collection: queries });
206
+ };
207
+
208
+ export const getNodesAndEdgesForCommands = async ({ id, version, defaultFlow, mode = 'simple' }: Props) => {
209
+ const commands = await getCommands();
210
+ return getNodesAndEdges({ id, version, defaultFlow, mode, collection: commands });
211
+ };
212
+
213
+ export const getNodesAndEdgesForEvents = async ({ id, version, defaultFlow, mode = 'simple' }: Props) => {
214
+ const events = await getEvents();
215
+ return getNodesAndEdges({ id, version, defaultFlow, mode, collection: events });
216
+ };
@@ -1,8 +1,16 @@
1
1
  import { getCollection, type CollectionEntry } from 'astro:content';
2
2
  import dagre from 'dagre';
3
- import { createDagreGraph, generateIdForNode, generatedIdForEdge, calculatedNodes } from '@utils/node-graph-utils/utils';
3
+ import {
4
+ createDagreGraph,
5
+ generateIdForNode,
6
+ generatedIdForEdge,
7
+ calculatedNodes,
8
+ createEdge,
9
+ getChannelNodesAndEdges,
10
+ } from '@utils/node-graphs/utils/utils';
4
11
  import { findMatchingNodes, getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
5
12
  import { MarkerType } from 'reactflow';
13
+ import type { CollectionMessageTypes } from '@types';
6
14
 
7
15
  type DagreGraph = any;
8
16
 
@@ -62,6 +70,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
62
70
  const events = await getCollection('events');
63
71
  const commands = await getCollection('commands');
64
72
  const queries = await getCollection('queries');
73
+ const channels = await getCollection('channels');
65
74
 
66
75
  const messages = [...events, ...commands, ...queries];
67
76
 
@@ -75,50 +84,57 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
75
84
  .flat()
76
85
  .filter((e) => e !== undefined);
77
86
 
78
- const receives = (receivesHydrated as CollectionEntry<'events' | 'commands' | 'queries'>[]) || [];
79
- const sends = (sendsHydrated as CollectionEntry<'events' | 'commands' | 'queries'>[]) || [];
87
+ const receives = (receivesHydrated as CollectionEntry<CollectionMessageTypes>[]) || [];
88
+ const sends = (sendsHydrated as CollectionEntry<CollectionMessageTypes>[]) || [];
80
89
 
81
90
  // Track messages that are both sent and received
82
91
  const bothSentAndReceived = findMatchingNodes(receives, sends);
83
92
 
84
- // Get all the data from them
85
- if (receives && receives.length > 0) {
86
- // All the messages the service receives
87
- receives.forEach((receive, index) => {
88
- nodes.push({
89
- id: generateIdForNode(receive),
90
- type: receive?.collection,
91
- sourcePosition: 'right',
92
- targetPosition: 'left',
93
- data: { mode, message: receive, showTarget: renderAllEdges },
94
- position: { x: 250, y: index * 100 },
95
- });
96
- edges.push({
97
- id: generatedIdForEdge(receive, service),
98
- source: generateIdForNode(receive),
99
- target: generateIdForNode(service),
100
- type: 'smoothstep',
101
- label: getReceivesMessageByMessageType(receive?.collection),
102
- animated: false,
103
- markerEnd: {
104
- type: MarkerType.ArrowClosed,
105
- width: 40,
106
- height: 40,
107
- },
108
- style: {
109
- strokeWidth: 1,
110
- },
111
- });
93
+ // All the messages the service receives
94
+ receives.forEach((receive) => {
95
+ // Create the node for the message
96
+ nodes.push({
97
+ id: generateIdForNode(receive),
98
+ type: receive?.collection,
99
+ sourcePosition: 'right',
100
+ targetPosition: 'left',
101
+ data: { mode, message: receive },
112
102
  });
113
- }
103
+
104
+ // does the message have channels defined?
105
+ if (receive.data.channels) {
106
+ const { nodes: channelNodes, edges: channelEdges } = getChannelNodesAndEdges({
107
+ channels,
108
+ channelsToRender: receive.data.channels,
109
+ source: receive,
110
+ channelToTargetLabel: getReceivesMessageByMessageType(receive?.collection),
111
+ target: service,
112
+ mode,
113
+ currentNodes: nodes,
114
+ });
115
+
116
+ nodes.push(...channelNodes);
117
+ edges.push(...channelEdges);
118
+ } else {
119
+ // No channels, just link the message to the service
120
+ edges.push(
121
+ createEdge({
122
+ id: generatedIdForEdge(receive, service),
123
+ source: generateIdForNode(receive),
124
+ target: generateIdForNode(service),
125
+ label: getReceivesMessageByMessageType(receive?.collection),
126
+ data: { message: receive },
127
+ })
128
+ );
129
+ }
130
+ });
114
131
 
115
132
  // The service itself
116
133
  nodes.push({
117
134
  id: generateIdForNode(service),
118
135
  sourcePosition: 'right',
119
136
  targetPosition: 'left',
120
- data: { mode, service: service, showSource: sends.length > 0, showTarget: receives.length > 0 },
121
- position: { x: 0, y: 0 },
137
+ data: { mode, service: service },
122
138
  type: service.collection,
123
139
  });
124
140
 
@@ -128,26 +144,35 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
128
144
  id: generateIdForNode(send),
129
145
  sourcePosition: 'right',
130
146
  targetPosition: 'left',
131
- data: { mode, message: send, showSource: renderAllEdges },
132
- position: { x: 500, y: index * 100 },
147
+ data: { mode, message: send },
133
148
  type: send?.collection,
134
149
  });
135
- edges.push({
136
- id: generatedIdForEdge(service, send),
137
- source: generateIdForNode(service),
138
- target: generateIdForNode(send),
139
- type: 'smoothstep',
140
- label: getSendsMessageByMessageType(send?.collection),
141
- animated: false,
142
- markerEnd: {
143
- type: MarkerType.ArrowClosed,
144
- width: 40,
145
- height: 40,
146
- },
147
- style: {
148
- strokeWidth: 1,
149
- },
150
- });
150
+
151
+ if (send.data.channels) {
152
+ const { nodes: channelNodes, edges: channelEdges } = getChannelNodesAndEdges({
153
+ channels,
154
+ channelsToRender: send.data.channels,
155
+ source: service,
156
+ target: send,
157
+ mode,
158
+ sourceToChannelLabel: `${getSendsMessageByMessageType(send?.collection)}`,
159
+ channelToTargetLabel: getSendsMessageByMessageType(send?.collection),
160
+ currentNodes: nodes,
161
+ });
162
+ nodes.push(...channelNodes);
163
+ edges.push(...channelEdges);
164
+ } else {
165
+ // No channels, just link the message to the service
166
+ edges.push(
167
+ createEdge({
168
+ id: generatedIdForEdge(service, send),
169
+ source: generateIdForNode(service),
170
+ target: generateIdForNode(send),
171
+ label: getSendsMessageByMessageType(send?.collection),
172
+ data: { message: send },
173
+ })
174
+ );
175
+ }
151
176
  });
152
177
 
153
178
  // Handle messages that are both sent and received
@@ -157,9 +182,9 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
157
182
  id: generatedIdForEdge(service, message) + '-both',
158
183
  source: generateIdForNode(service),
159
184
  target: generateIdForNode(message),
160
- type: 'smoothstep',
161
185
  label: `${getSendsMessageByMessageType(message?.collection)} & ${getReceivesMessageByMessageType(message?.collection)}`,
162
186
  animated: false,
187
+ data: { message },
163
188
  markerEnd: {
164
189
  type: MarkerType.ArrowClosed,
165
190
  width: 40,
@@ -185,6 +210,6 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
185
210
 
186
211
  return {
187
212
  nodes: calculatedNodes(flow, nodes),
188
- edges: edges,
213
+ edges,
189
214
  };
190
215
  };