@eventcatalog/core 2.12.2 → 2.13.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 (78) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +1 -1
  3. package/public/icons/protocols/kafka.svg +1 -0
  4. package/scripts/catalog-to-astro-content-directory.js +13 -1
  5. package/scripts/default-files-for-collections/channels.md +8 -0
  6. package/scripts/map-catalog-to-astro.js +1 -0
  7. package/src/components/DocsNavigation.astro +4 -4
  8. package/src/components/Header.astro +3 -3
  9. package/src/components/Lists/PillListFlat.tsx +19 -12
  10. package/src/components/Lists/ProtocolList.tsx +88 -0
  11. package/src/components/MDX/ChannelInformation/ChannelInformation.tsx +79 -0
  12. package/src/components/MDX/Flow/Flow.astro +3 -3
  13. package/src/components/MDX/NodeGraph/DownloadButton.tsx +3 -3
  14. package/src/components/MDX/NodeGraph/Edges/AnimatedMessageEdge.tsx +82 -0
  15. package/src/components/MDX/NodeGraph/NodeGraph.astro +24 -67
  16. package/src/components/MDX/NodeGraph/NodeGraph.tsx +188 -17
  17. package/src/components/MDX/NodeGraph/Nodes/Channel.tsx +133 -0
  18. package/src/components/MDX/components.tsx +2 -0
  19. package/src/components/Seo.astro +0 -2
  20. package/src/components/SideBars/CatalogResourcesSideBar/index.tsx +4 -13
  21. package/src/components/SideBars/ChannelSideBar.astro +117 -0
  22. package/src/components/SideBars/MessageSideBar.astro +13 -0
  23. package/src/content/config.ts +37 -0
  24. package/src/icons/protocols/WebSocket.svg +1 -0
  25. package/src/icons/protocols/amqp.svg +1 -0
  26. package/src/icons/protocols/eventbridge.svg +6 -0
  27. package/src/icons/protocols/googlepubsub.svg +1 -0
  28. package/src/icons/protocols/http.svg +1 -0
  29. package/src/icons/protocols/index.ts +15 -0
  30. package/src/icons/protocols/jms.svg +1 -0
  31. package/src/icons/protocols/kafka.svg +1 -0
  32. package/src/icons/protocols/mercure.svg +20 -0
  33. package/src/icons/protocols/mqtt.svg +17 -0
  34. package/src/icons/protocols/nats.svg +13 -0
  35. package/src/icons/protocols/pulsar.svg +4 -0
  36. package/src/icons/protocols/redis.svg +1 -0
  37. package/src/icons/protocols/sns.svg +6 -0
  38. package/src/icons/protocols/solace.svg +1 -0
  39. package/src/icons/protocols/sqs.svg +7 -0
  40. package/src/icons/protocols/ws.svg +1 -0
  41. package/src/layouts/DiscoverLayout.astro +3 -3
  42. package/src/layouts/VerticalSideBarLayout.astro +10 -5
  43. package/src/layouts/VisualiserLayout.astro +3 -3
  44. package/src/pages/discover/[type]/index.astro +3 -3
  45. package/src/pages/docs/[type]/[id]/[version]/asyncapi/index.astro +2 -2
  46. package/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +2 -2
  47. package/src/pages/docs/[type]/[id]/[version]/index.astro +11 -4
  48. package/src/pages/docs/[type]/[id]/[version]/spec/index.astro +2 -2
  49. package/src/pages/docs/[type]/[id]/index.astro +17 -4
  50. package/src/pages/index.astro +3 -3
  51. package/src/pages/visualiser/[type]/[id]/[version]/index.astro +2 -2
  52. package/src/pages/visualiser/[type]/[id]/index.astro +2 -2
  53. package/src/remark-plugins/mermaid.ts +0 -1
  54. package/src/types/index.ts +2 -2
  55. package/src/utils/channels.ts +64 -0
  56. package/src/utils/collections/icons.ts +6 -0
  57. package/src/utils/commands.ts +6 -0
  58. package/src/utils/{config → eventcatalog-config}/catalog.ts +1 -1
  59. package/src/utils/events.ts +5 -0
  60. package/src/utils/{domains/node-graph.ts → node-graphs/domains-node-graph.ts} +2 -2
  61. package/src/utils/{flows/node-graph.ts → node-graphs/flows-node-graph.ts} +2 -2
  62. package/src/utils/node-graphs/message-node-graph.ts +216 -0
  63. package/src/utils/{services/node-graph.ts → node-graphs/services-node-graph.ts} +79 -54
  64. package/src/utils/node-graphs/utils/utils.ts +128 -0
  65. package/src/utils/{pages/pages.ts → page-loaders/page-data-loader.ts} +4 -2
  66. package/src/utils/queries.ts +5 -0
  67. package/tsconfig.json +2 -1
  68. package/src/remark-plugins/remark-modified-time.mjs +0 -9
  69. package/src/utils/commands/node-graph.ts +0 -144
  70. package/src/utils/events/node-graph.ts +0 -145
  71. package/src/utils/node-graph-utils/utils.ts +0 -29
  72. package/src/utils/queries/node-graph.ts +0 -144
  73. /package/src/pages/docs/[type]/[id]/[version]/spec/{styles.css → _styles.css} +0 -0
  74. /package/src/utils/{changelogs → collections}/changelogs.ts +0 -0
  75. /package/src/utils/{domains → collections}/domains.ts +0 -0
  76. /package/src/utils/{flows → collections}/flows.ts +0 -0
  77. /package/src/utils/{services → collections}/services.ts +0 -0
  78. /package/src/utils/{versions → collections}/versions.ts +0 -0
@@ -11,8 +11,13 @@ import ReactFlow, {
11
11
  type Edge,
12
12
  type Node,
13
13
  useReactFlow,
14
+ getBezierPath,
15
+ BaseEdge,
16
+ SmoothStepEdge,
14
17
  } from 'reactflow';
15
18
  import 'reactflow/dist/style.css';
19
+
20
+ // Nodes and edges
16
21
  import ServiceNode from './Nodes/Service';
17
22
  import EventNode from './Nodes/Event';
18
23
  import QueryNode from './Nodes/Query';
@@ -20,11 +25,15 @@ import UserNode from './Nodes/User';
20
25
  import StepNode from './Nodes/Step';
21
26
  import CommandNode from './Nodes/Command';
22
27
  import ExternalSystemNode from './Nodes/ExternalSystem';
28
+ import AnimatedMessageEdge from './Edges/AnimatedMessageEdge';
29
+
23
30
  import type { CollectionEntry } from 'astro:content';
24
31
  import { navigate } from 'astro:transitions/client';
25
32
  import type { CollectionTypes } from '@types';
26
33
  import DownloadButton from './DownloadButton';
27
34
  import { buildUrl } from '@utils/url-builder';
35
+ import ChannelNode from './Nodes/Channel';
36
+ import { CogIcon } from '@heroicons/react/20/solid';
28
37
 
29
38
  interface Props {
30
39
  nodes: any;
@@ -38,9 +47,6 @@ interface Props {
38
47
  linksToVisualiser?: boolean;
39
48
  }
40
49
 
41
- const getDocUrlForCollection = (collectionItem: CollectionEntry<CollectionTypes>) => {
42
- return buildUrl(`/docs/${collectionItem.collection}/${collectionItem.data.id}/${collectionItem.data.version}`);
43
- };
44
50
  const getVisualiserUrlForCollection = (collectionItem: CollectionEntry<CollectionTypes>) => {
45
51
  return buildUrl(`/visualiser/${collectionItem.collection}/${collectionItem.data.id}/${collectionItem.data.version}`);
46
52
  };
@@ -58,6 +64,7 @@ const NodeGraphBuilder = ({
58
64
  () => ({
59
65
  services: ServiceNode,
60
66
  events: EventNode,
67
+ channels: ChannelNode,
61
68
  queries: QueryNode,
62
69
  commands: CommandNode,
63
70
  step: StepNode,
@@ -67,25 +74,35 @@ const NodeGraphBuilder = ({
67
74
  }),
68
75
  []
69
76
  );
77
+ const edgeTypes = useMemo(
78
+ () => ({
79
+ animated: AnimatedMessageEdge,
80
+ }),
81
+ []
82
+ );
70
83
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
71
84
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
85
+ const [isSettingsOpen, setIsSettingsOpen] = useState(false);
86
+ const [isAnimated, setIsAnimated] = useState(false);
87
+ const [animateMessages, setAnimateMessages] = useState(false);
88
+
72
89
  const { fitView } = useReactFlow();
73
90
 
74
91
  const resetNodesAndEdges = useCallback(() => {
75
92
  setNodes((nds) =>
76
93
  nds.map((node) => {
77
94
  node.style = { ...node.style, opacity: 1 };
78
- return { ...node, animated: false };
95
+ return { ...node, animated: animateMessages };
79
96
  })
80
97
  );
81
98
  setEdges((eds) =>
82
99
  eds.map((edge) => {
83
100
  edge.style = { ...edge.style, opacity: 1 };
84
101
  edge.labelStyle = { ...edge.labelStyle, opacity: 1 };
85
- return { ...edge, animated: false };
102
+ return { ...edge, data: { ...edge.data, opacity: 1 }, animated: animateMessages };
86
103
  })
87
104
  );
88
- }, [setNodes, setEdges]);
105
+ }, [setNodes, setEdges, animateMessages]);
89
106
 
90
107
  const handleNodeClick = useCallback(
91
108
  (_: any, node: Node) => {
@@ -110,6 +127,7 @@ const NodeGraphBuilder = ({
110
127
  connectedNodeIds.add(edge.target);
111
128
  return {
112
129
  ...edge,
130
+ data: { ...edge.data, opacity: 1 },
113
131
  style: { ...edge.style, opacity: 1 },
114
132
  labelStyle: { ...edge.labelStyle, opacity: 1 },
115
133
  animated: true,
@@ -117,9 +135,10 @@ const NodeGraphBuilder = ({
117
135
  }
118
136
  return {
119
137
  ...edge,
138
+ data: { ...edge.data, opacity: 0.1 },
120
139
  style: { ...edge.style, opacity: 0.1 },
121
140
  labelStyle: { ...edge.labelStyle, opacity: 0.1 },
122
- animated: false,
141
+ animated: animateMessages,
123
142
  };
124
143
  });
125
144
 
@@ -143,14 +162,105 @@ const NodeGraphBuilder = ({
143
162
  [nodes, edges, setNodes, setEdges, resetNodesAndEdges, fitView]
144
163
  );
145
164
 
165
+ const toggleAnimation = () => {
166
+ setIsAnimated(!isAnimated);
167
+ setEdges((eds) =>
168
+ eds.map((edge) => ({
169
+ ...edge,
170
+ animated: !isAnimated,
171
+ }))
172
+ );
173
+ };
174
+
175
+ const toggleAnimateMessages = () => {
176
+ setAnimateMessages(!animateMessages);
177
+ localStorage.setItem('EventCatalog:animateMessages', JSON.stringify(!animateMessages));
178
+ };
179
+
180
+ // animate messages, between views
181
+ useEffect(() => {
182
+ const storedAnimateMessages = localStorage.getItem('EventCatalog:animateMessages');
183
+ if (storedAnimateMessages !== null) {
184
+ setAnimateMessages(storedAnimateMessages === 'true');
185
+ }
186
+ }, []);
187
+
188
+ useEffect(() => {
189
+ setEdges((eds) =>
190
+ eds.map((edge) => ({
191
+ ...edge,
192
+ animated: animateMessages,
193
+ type: animateMessages ? 'animated' : 'default',
194
+ data: { ...edge.data, animateMessages },
195
+ }))
196
+ );
197
+ }, [animateMessages]);
198
+
146
199
  const handlePaneClick = useCallback(() => {
200
+ setIsSettingsOpen(false);
147
201
  resetNodesAndEdges();
148
202
  fitView({ duration: 800 });
149
203
  }, [resetNodesAndEdges, fitView]);
150
204
 
205
+ const handleLegendClick = useCallback(
206
+ (collectionType: string) => {
207
+ const updatedNodes = nodes.map((node) => {
208
+ if (node.type === collectionType) {
209
+ return { ...node, style: { ...node.style, opacity: 1 } };
210
+ }
211
+ return { ...node, style: { ...node.style, opacity: 0.1 } };
212
+ });
213
+
214
+ const updatedEdges = edges.map((edge) => {
215
+ return {
216
+ ...edge,
217
+ data: { ...edge.data, opacity: 0.1 },
218
+ style: { ...edge.style, opacity: 0.1 },
219
+ labelStyle: { ...edge.labelStyle, opacity: 0.1 },
220
+ animated: animateMessages,
221
+ };
222
+ });
223
+
224
+ setNodes(updatedNodes);
225
+ setEdges(updatedEdges);
226
+
227
+ fitView({
228
+ padding: 0.2,
229
+ duration: 800,
230
+ nodes: updatedNodes.filter((node) => node.type === collectionType),
231
+ });
232
+ },
233
+ [nodes, edges, setNodes, setEdges, fitView]
234
+ );
235
+
236
+ const getNodesByCollectionWithColors = useCallback((nodes: Node[]) => {
237
+ const colors = {
238
+ events: 'orange',
239
+ services: 'pink',
240
+ commands: 'blue',
241
+ queries: 'green',
242
+ channels: 'gray',
243
+ };
244
+
245
+ return nodes.reduce((acc: { [key: string]: { count: number; color: string } }, node) => {
246
+ const collection = node.type;
247
+ if (collection) {
248
+ if (acc[collection]) {
249
+ acc[collection].count += 1;
250
+ } else {
251
+ acc[collection] = { count: 1, color: colors[collection as keyof typeof colors] || 'black' };
252
+ }
253
+ }
254
+ return acc;
255
+ }, {});
256
+ }, []);
257
+
258
+ const legend = getNodesByCollectionWithColors(nodes);
259
+
151
260
  return (
152
261
  <ReactFlow
153
262
  nodeTypes={nodeTypes}
263
+ edgeTypes={edgeTypes}
154
264
  nodes={nodes}
155
265
  edges={edges}
156
266
  fitView
@@ -160,25 +270,82 @@ const NodeGraphBuilder = ({
160
270
  nodeOrigin={[0.1, 0.1]}
161
271
  onNodeClick={handleNodeClick}
162
272
  onPaneClick={handlePaneClick}
273
+ className="relative"
163
274
  >
164
- {title && (
165
- <Panel position="top-right">
166
- <span className="block shadow-sm bg-white text-xl z-10 text-black px-4 py-2 border-gray-200 rounded-md border">
167
- <strong>Visualiser</strong> | {title}
168
- </span>
169
- </Panel>
275
+ <Panel position="top-center" className="w-full pr-6 ">
276
+ <div className="flex space-x-2 justify-between items-center">
277
+ <div>
278
+ <button
279
+ onClick={() => setIsSettingsOpen(!isSettingsOpen)}
280
+ className="py-2.5 px-3 bg-white rounded-md shadow-md hover:bg-purple-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500"
281
+ aria-label="Open settings"
282
+ >
283
+ <CogIcon className="h-5 w-5 text-gray-600" />
284
+ </button>
285
+ </div>
286
+ {title && (
287
+ <span className="block shadow-sm bg-white text-xl z-10 text-black px-4 py-2 border-gray-200 rounded-md border opacity-80">
288
+ {title}
289
+ </span>
290
+ )}
291
+ <div className="flex justify-end ">
292
+ <DownloadButton filename={title} addPadding={false} />
293
+ </div>
294
+ </div>
295
+ </Panel>
296
+
297
+ {isSettingsOpen && (
298
+ <div className="absolute top-[68px] left-5 w-72 p-4 bg-white rounded-lg shadow-lg z-30 border border-gray-200">
299
+ <h3 className="text-lg font-semibold mb-4">Visualizer Settings</h3>
300
+ <div className="space-y-4 ">
301
+ <div>
302
+ <div className="flex items-center justify-between">
303
+ <label htmlFor="message-animation-toggle" className="text-sm font-medium text-gray-700">
304
+ Simulate Messages
305
+ </label>
306
+ <button
307
+ id="message-animation-toggle"
308
+ onClick={toggleAnimateMessages}
309
+ className={`${
310
+ animateMessages ? 'bg-purple-600' : 'bg-gray-200'
311
+ } relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2`}
312
+ >
313
+ <span
314
+ className={`${
315
+ animateMessages ? 'translate-x-6' : 'translate-x-1'
316
+ } inline-block h-4 w-4 transform rounded-full bg-white transition-transform`}
317
+ />
318
+ </button>
319
+ </div>
320
+ <p className="text-[10px] text-gray-500">Animate events, queries and commands.</p>
321
+ </div>
322
+ </div>
323
+ </div>
170
324
  )}
171
- <DownloadButton filename={title} addPadding={!!title} />
172
325
  {includeBackground && <Background color="#bbb" gap={16} />}
173
326
  {includeBackground && <Controls />}
174
327
  {includeKey && (
175
328
  <Panel position="bottom-right">
176
329
  <div className=" bg-white font-light px-4 text-[12px] shadow-md py-1 rounded-md">
330
+ <ul className="m-0 p-0 ">
331
+ {Object.entries(legend).map(([key, { count, color }]) => (
332
+ <li
333
+ key={key}
334
+ className="flex space-x-2 items-center text-[10px] cursor-pointer hover:text-purple-600 hover:underline"
335
+ onClick={() => handleLegendClick(key)}
336
+ >
337
+ <span className={`w-2 h-2 block`} style={{ backgroundColor: color }} />
338
+ <span className="block capitalize">
339
+ {key} ({count})
340
+ </span>
341
+ </li>
342
+ ))}
343
+ </ul>
177
344
  {/* <span className="font-bold">Key</span> */}
178
- <ul className="m-0 p-0">
345
+ {/* <ul className="m-0 p-0">
179
346
  <li className="flex space-x-2 items-center text-[10px]">
180
347
  <span className="w-2 h-2 bg-orange-500 block" />
181
- <span className="block">Event</span>
348
+ <span className="block">Events</span>
182
349
  </li>
183
350
  <li className="flex space-x-2 items-center text-[10px]">
184
351
  <span className="w-2 h-2 bg-pink-500 block" />
@@ -192,7 +359,11 @@ const NodeGraphBuilder = ({
192
359
  <span className="w-2 h-2 bg-green-500 block" />
193
360
  <span className="block">Query</span>
194
361
  </li>
195
- </ul>
362
+ <li className="flex space-x-2 items-center text-[10px]">
363
+ <span className="w-2 h-2 bg-gray-500 block" />
364
+ <span className="block">Channel</span>
365
+ </li>
366
+ </ul> */}
196
367
  </div>
197
368
  </Panel>
198
369
  )}
@@ -0,0 +1,133 @@
1
+ import { BoltIcon } from '@heroicons/react/16/solid';
2
+ import { ArrowsRightLeftIcon } from '@heroicons/react/20/solid';
3
+ import type { CollectionMessageTypes, CollectionTypes } from '@types';
4
+ import type { CollectionEntry } from 'astro:content';
5
+ import { Handle } from 'reactflow';
6
+
7
+ interface Data {
8
+ title: string;
9
+ label: string;
10
+ bgColor: string;
11
+ color: string;
12
+ mode: 'simple' | 'full';
13
+ channel: CollectionEntry<'channels'>;
14
+ source: CollectionEntry<CollectionMessageTypes>;
15
+ target: CollectionEntry<CollectionMessageTypes>;
16
+ showTarget?: boolean;
17
+ showSource?: boolean;
18
+ }
19
+
20
+ function classNames(...classes: any) {
21
+ return classes.filter(Boolean).join(' ');
22
+ }
23
+
24
+ import * as ProtocolIcons from '@icons/protocols';
25
+ import { LinkIcon } from '@heroicons/react/24/outline';
26
+
27
+ const protocolIcons = Object.keys(ProtocolIcons).reduce(
28
+ (icons, key) => {
29
+ const iconKey = key as keyof typeof ProtocolIcons;
30
+ icons[key.toLowerCase()] = ProtocolIcons[iconKey];
31
+ return icons;
32
+ },
33
+ {} as { [key: string]: string }
34
+ );
35
+
36
+ const getIconForProtocol = (icon: keyof typeof protocolIcons) => {
37
+ const Icon = protocolIcons[icon];
38
+ return Icon ? (props: any) => <span {...props} dangerouslySetInnerHTML={{ __html: Icon }} /> : null;
39
+ };
40
+
41
+ export default function ChannelNode({ data, sourcePosition, targetPosition }: any) {
42
+ const { mode, channel, source, target } = data as Data;
43
+
44
+ const { id, name, version, summary, owners = [], address, protocols = [] } = channel.data;
45
+ const protocol = protocols[0];
46
+
47
+ const Icon = getIconForProtocol(protocol);
48
+
49
+ const getAddress = () => {
50
+ const sourceChannel = source.data.channels?.find((channel) => channel.id === id);
51
+ const targetChannel = target.data.channels?.find((channel) => channel.id === id);
52
+ const sourceParams = sourceChannel?.parameters || {};
53
+ const targetParams = targetChannel?.parameters || {};
54
+ const params = { ...sourceParams, ...targetParams };
55
+
56
+ let updatedAddress = address;
57
+ if (params) {
58
+ Object.keys(params).forEach((key) => {
59
+ const placeholder = `{${key}}`;
60
+ if (updatedAddress && updatedAddress.includes(placeholder)) {
61
+ updatedAddress = updatedAddress.replace(new RegExp(`{${key}}`, 'g'), params[key]);
62
+ }
63
+ });
64
+ }
65
+
66
+ return updatedAddress;
67
+ };
68
+
69
+ return (
70
+ <div
71
+ className={classNames(
72
+ mode === 'simple' ? 'min-h-[3em]' : 'min-h-[6.5em]',
73
+ 'w-full rounded-md border flex justify-start bg-white text-black border-gray-400 transform '
74
+ )}
75
+ >
76
+ <div
77
+ className={classNames(
78
+ 'bg-gradient-to-b from-gray-500 to-gray-700 relative flex items-center w-5 justify-center rounded-l-sm text-gray-100-500',
79
+ `border-r-[1px] border-gray-500`
80
+ )}
81
+ >
82
+ <ArrowsRightLeftIcon className="w-4 h-4 opacity-90 text-white absolute top-1 " />
83
+ {mode === 'full' && (
84
+ <span className="rotate -rotate-90 w-1/2 text-center absolute bottom-1 text-[9px] text-white font-bold uppercase tracking-[3px] ">
85
+ Channel
86
+ </span>
87
+ )}
88
+ </div>
89
+ <div className="p-1 min-w-60 max-w-[min-content]">
90
+ {targetPosition && <Handle type="target" position={targetPosition} />}
91
+ {sourcePosition && <Handle type="source" position={sourcePosition} />}
92
+ <div className={classNames(mode === 'full' ? `border-b border-gray-200` : '')}>
93
+ <div className="flex justify-between items-center">
94
+ <span className="text-xs font-bold block pb-0.5">{name}</span>
95
+ {Icon && <Icon className="w-5 h-5 opacity-60 p-0.5" />}
96
+ </div>
97
+ <div className="flex justify-between">
98
+ <span className="text-[10px] font-light block pt-0.5 pb-0.5 ">v{version}</span>
99
+ {mode === 'simple' && <span className="text-[10px] text-gray-500 font-light block pt-0.5 pb-0.5 ">Channel</span>}
100
+ </div>
101
+ </div>
102
+ {mode === 'full' && (
103
+ <div className="divide-y divide-gray-200 ">
104
+ <div className="leading-[10px] py-1 ">
105
+ <span className="text-[8px] font-light">{summary}</span>
106
+ </div>
107
+ {address && (
108
+ <div className="leading-3 py-1 flex flex-col items-start space-y-0.5">
109
+ <div className="text-[6px] flex items-center space-x-0.5 ">
110
+ <LinkIcon className="w-2 h-2 opacity-60" />
111
+ <span className="block font-normal ">{getAddress()}</span>
112
+ </div>
113
+ {protocols.length > 0 && (
114
+ <div className="text-[6px] font-semibold flex space-x-2 items-center ">
115
+ {[...protocols].map((protocol, index) => {
116
+ const ProtoColIcon = getIconForProtocol(protocol);
117
+ return (
118
+ <span key={index} className="font-normal flex items-center -ml-[1px] space-x-0.5">
119
+ {ProtoColIcon && <ProtoColIcon className="w-2 h-2 opacity-60 inline-block" />}
120
+ <span>{protocol}</span>
121
+ </span>
122
+ );
123
+ })}
124
+ </div>
125
+ )}
126
+ </div>
127
+ )}
128
+ </div>
129
+ )}
130
+ </div>
131
+ </div>
132
+ );
133
+ }
@@ -11,6 +11,7 @@ import Step from '@components/MDX/Steps/Step.astro';
11
11
  import Admonition from '@components/MDX/Admonition';
12
12
  import OpenAPI from '@components/MDX/OpenAPI/OpenAPI.astro';
13
13
  import AsyncAPI from '@components/MDX/AsyncAPI/AsyncAPI.astro';
14
+ import ChannelInformation from '@components/MDX/ChannelInformation/ChannelInformation';
14
15
 
15
16
  // Portals: required for server/client components
16
17
  import NodeGraphPortal from '@components/MDX/NodeGraph/NodeGraphPortal';
@@ -30,6 +31,7 @@ const components = (props: any) => {
30
31
  Admonition: (mdxProp: any) => <Admonition {...mdxProp} {...props} />,
31
32
  File: (mdxProp: any) => File({ ...props, ...mdxProp }),
32
33
  NodeGraph: (mdxProp: any) => NodeGraphPortal({ ...props.data, ...mdxProp }),
34
+ ChannelInformation: (mdxProp: any) => ChannelInformation({ ...props.data, ...mdxProp }),
33
35
  SchemaViewer: (mdxProp: any) => SchemaViewerPortal({ ...props.data, ...mdxProp }),
34
36
  Schema: (mdxProp: any) => Schema({ ...props, ...mdxProp }),
35
37
  };
@@ -47,11 +47,9 @@ if (typeof _image === 'string') {
47
47
  },
48
48
  }}
49
49
  twitter={{
50
- creator: '@liran_tal',
51
50
  image: image ? image.toString() : undefined,
52
51
  imageAlt: ogTitle,
53
52
  title: ogTitle,
54
- site: '@liran_tal',
55
53
  description: description,
56
54
  card: image ? 'summary_large_image' : 'summary',
57
55
  }}
@@ -1,17 +1,6 @@
1
1
  import React, { useState, useEffect, useMemo } from 'react';
2
2
  import debounce from 'lodash.debounce';
3
- import {
4
- ChevronDownIcon,
5
- ChevronUpIcon,
6
- ServerIcon,
7
- RectangleGroupIcon,
8
- BoltIcon,
9
- ChatBubbleLeftIcon,
10
- MagnifyingGlassIcon,
11
- QueueListIcon,
12
- UserGroupIcon,
13
- UserIcon,
14
- } from '@heroicons/react/24/outline';
3
+ import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline';
15
4
  import './styles.css';
16
5
  import { getIconForCollection as getIconForCollectionOriginal } from '@utils/collections/icons';
17
6
 
@@ -19,6 +8,7 @@ const CatalogResourcesSideBar = ({ resources, currentPath }: any) => {
19
8
  const [data, setData] = useState(resources);
20
9
  const [searchQuery, setSearchQuery] = useState('');
21
10
  const [collapsedGroups, setCollapsedGroups] = useState<{ [key: string]: boolean }>({});
11
+ const decodedCurrentPath = decodeURIComponent(currentPath);
22
12
 
23
13
  const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
24
14
  setSearchQuery(e.target.value);
@@ -76,6 +66,7 @@ const CatalogResourcesSideBar = ({ resources, currentPath }: any) => {
76
66
  const collection = filteredData[key];
77
67
  if (collection[0] && collection[0].visible === false) return null;
78
68
  const isCollapsed = collapsedGroups[key];
69
+
79
70
  return (
80
71
  <ul className="w-full space-y-1.5 pb-2 pl-1 text-black" key={key}>
81
72
  <li
@@ -92,7 +83,7 @@ const CatalogResourcesSideBar = ({ resources, currentPath }: any) => {
92
83
  const Icon = getIconForCollection(item.collection);
93
84
  return (
94
85
  <li
95
- className={`w-full has-tooltip text-md xl:text-sm space-y-2 scroll-m-20 rounded-md text-black hover:bg-gradient-to-l hover:from-purple-500 hover:to-purple-700 hover:text-white ${currentPath.includes(item.href) ? ' bg-gradient-to-l from-purple-500 to-purple-700 font-normal text-white ' : 'font-thin'}`}
86
+ className={`w-full has-tooltip text-md xl:text-sm space-y-2 scroll-m-20 rounded-md text-black hover:bg-gradient-to-l hover:from-purple-500 hover:to-purple-700 hover:text-white ${decodedCurrentPath.includes(item.href) ? ' bg-gradient-to-l from-purple-500 to-purple-700 font-normal text-white ' : 'font-thin'}`}
96
87
  id={item.href}
97
88
  key={item.href}
98
89
  >
@@ -0,0 +1,117 @@
1
+ ---
2
+ import { getEntry, type CollectionEntry } from 'astro:content';
3
+ import PillListFlat from '@components/Lists/PillListFlat';
4
+ import ProtocolList from '@components/Lists/ProtocolList';
5
+ import OwnersList from '@components/Lists/OwnersList';
6
+ import VersionList from '@components/Lists/VersionList.astro';
7
+ import { buildUrl } from '@utils/url-builder';
8
+ import { ScrollText } from 'lucide-react';
9
+ import RepositoryList from '@components/Lists/RepositoryList.astro';
10
+
11
+ interface Props {
12
+ channel: CollectionEntry<'channels'>;
13
+ }
14
+
15
+ const { channel } = Astro.props;
16
+
17
+ const ownersRaw = channel.data?.owners || [];
18
+ const owners = await Promise.all(ownersRaw.map((o) => getEntry(o)));
19
+
20
+ const channelParameters: Record<string, { enum?: string[]; description?: string }> = channel.data.parameters || {};
21
+ const parameters = Object.keys(channelParameters).map((key) => ({
22
+ key,
23
+ ...channelParameters[key],
24
+ }));
25
+
26
+ const paramsList = parameters.map((param) => ({
27
+ label: param.key,
28
+ description: param?.description,
29
+ collection: 'channels-parameter',
30
+ }));
31
+
32
+ const protocols = channel.data.protocols || [];
33
+ const protocolList = protocols.map((p) => ({
34
+ label: p,
35
+ collection: 'channels-protocol',
36
+ // icon: p,
37
+ icon: p,
38
+ }));
39
+
40
+ const ownersList = owners.map((o) => ({
41
+ label: o.data.name,
42
+ type: o.collection,
43
+ badge: o.collection === 'users' ? o.data.role : 'Team',
44
+ avatarUrl: o.collection === 'users' ? o.data.avatarUrl : '',
45
+ href: buildUrl(`/docs/${o.collection}/${o.data.id}`),
46
+ }));
47
+
48
+ const messageList = (channel.data.messages ?? []).map((p) => ({
49
+ label: p.name,
50
+ badge: p.collection,
51
+ color: p.collection === 'events' ? 'orange' : 'blue',
52
+ collection: p.collection,
53
+ tag: `v${p.version}`,
54
+ href: buildUrl(`/docs/${p.collection}/${p.id}/${p.version}`),
55
+ }));
56
+ ---
57
+
58
+ <aside class="sticky top-28 left-0 space-y-8 h-full overflow-y-auto py-4">
59
+ <div class="">
60
+ {
61
+ messageList.length > 0 && (
62
+ <PillListFlat
63
+ title={`Messages using channel (${messageList.length})`}
64
+ pills={messageList}
65
+ emptyMessage={`This channel does not have any messages.`}
66
+ color="orange"
67
+ client:load
68
+ />
69
+ )
70
+ }
71
+
72
+ {
73
+ protocolList.length > 0 && (
74
+ <ProtocolList color="pink" title={`Protocols (${protocolList.length})`} pills={protocolList} client:load />
75
+ )
76
+ }
77
+
78
+ {
79
+ channel.data.versions && (
80
+ <VersionList
81
+ title={`Channel versions (${channel.data.versions?.length})`}
82
+ versions={channel.data.versions}
83
+ collectionItem={channel}
84
+ />
85
+ )
86
+ }
87
+
88
+ <OwnersList
89
+ title={`Channel owners (${ownersList.length})`}
90
+ owners={ownersList}
91
+ emptyMessage={`This channel does not have any documented owners.`}
92
+ client:load
93
+ />
94
+
95
+ {
96
+ channel.data.repository && (
97
+ <RepositoryList repository={channel.data.repository?.url} language={channel.data.repository?.language} />
98
+ )
99
+ }
100
+
101
+ {
102
+ paramsList.length > 0 && (
103
+ <PillListFlat color="pink" title={`Channel parameters (${paramsList.length})`} pills={paramsList} client:load />
104
+ )
105
+ }
106
+
107
+ <div class="space-y-2">
108
+ <a
109
+ href={buildUrl(`/docs/${channel.collection}/${channel.data.id}/${channel.data.latestVersion}/changelog`)}
110
+ class="flex items-center space-x-2 justify-center text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-primary"
111
+ >
112
+ <ScrollText strokeWidth={2} size={16} />
113
+ <span class="block">Read changelog</span>
114
+ </a>
115
+ </div>
116
+ </div>
117
+ </aside>
@@ -16,6 +16,7 @@ const { message } = Astro.props;
16
16
 
17
17
  const producers = (message.data.producers as CollectionEntry<'services'>[]) || [];
18
18
  const consumers = (message.data.consumers as CollectionEntry<'services'>[]) || [];
19
+ const channels = (message.data.messageChannels as CollectionEntry<'channels'>[]) || [];
19
20
 
20
21
  const ownersRaw = message.data?.owners || [];
21
22
  const owners = await Promise.all(ownersRaw.map((o) => getEntry(o)));
@@ -42,6 +43,13 @@ const ownersList = owners.map((o) => ({
42
43
  href: buildUrl(`/docs/${o.collection}/${o.data.id}`),
43
44
  }));
44
45
 
46
+ const channelList = channels.map((c) => ({
47
+ label: `${c.data.name}`,
48
+ tag: `v${c.data.version}`,
49
+ collection: c.collection,
50
+ href: buildUrl(`/docs/channels/${c.data.id}/${c.data.version}`),
51
+ }));
52
+
45
53
  const getType = (type: string) => {
46
54
  switch (type) {
47
55
  case 'queries':
@@ -105,6 +113,11 @@ const schemaURL = path.join(publicPath, schemaFilePath || '');
105
113
  />
106
114
  )
107
115
  }
116
+ {
117
+ channelList.length > 0 && (
118
+ <PillListFlat color="pink" title={`Channels (${channelList.length})`} pills={channelList} client:load />
119
+ )
120
+ }
108
121
 
109
122
  {
110
123
  message.data.versions && (