@eventcatalog/core 2.44.3 → 2.44.5

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.
@@ -37,7 +37,7 @@ var import_axios = __toESM(require("axios"), 1);
37
37
  var import_os = __toESM(require("os"), 1);
38
38
 
39
39
  // package.json
40
- var version = "2.44.3";
40
+ var version = "2.44.5";
41
41
 
42
42
  // src/constants.ts
43
43
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-SHFEPDUF.js";
4
- import "../chunk-EJOD6TNE.js";
3
+ } from "../chunk-ULCIAC3E.js";
4
+ import "../chunk-WG64IAI3.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -106,7 +106,7 @@ var import_axios = __toESM(require("axios"), 1);
106
106
  var import_os = __toESM(require("os"), 1);
107
107
 
108
108
  // package.json
109
- var version = "2.44.3";
109
+ var version = "2.44.5";
110
110
 
111
111
  // src/constants.ts
112
112
  var VERSION = version;
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  log_build_default
3
- } from "../chunk-CGPQJVQ3.js";
4
- import "../chunk-SHFEPDUF.js";
5
- import "../chunk-EJOD6TNE.js";
3
+ } from "../chunk-WO7XQZNY.js";
4
+ import "../chunk-ULCIAC3E.js";
5
+ import "../chunk-WG64IAI3.js";
6
6
  import "../chunk-E7TXTI7G.js";
7
7
  export {
8
8
  log_build_default as default
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-EJOD6TNE.js";
3
+ } from "./chunk-WG64IAI3.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import axios from "axios";
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "2.44.3";
2
+ var version = "2.44.5";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-SHFEPDUF.js";
3
+ } from "./chunk-ULCIAC3E.js";
4
4
  import {
5
5
  getEventCatalogConfigFile,
6
6
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "2.44.3";
28
+ var version = "2.44.5";
29
29
 
30
30
  // src/constants.ts
31
31
  var VERSION = version;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-EJOD6TNE.js";
3
+ } from "./chunk-WG64IAI3.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -157,7 +157,7 @@ var import_axios = __toESM(require("axios"), 1);
157
157
  var import_os = __toESM(require("os"), 1);
158
158
 
159
159
  // package.json
160
- var version = "2.44.3";
160
+ var version = "2.44.5";
161
161
 
162
162
  // src/constants.ts
163
163
  var VERSION = version;
@@ -6,8 +6,8 @@ import {
6
6
  } from "./chunk-DCLTVJDP.js";
7
7
  import {
8
8
  log_build_default
9
- } from "./chunk-CGPQJVQ3.js";
10
- import "./chunk-SHFEPDUF.js";
9
+ } from "./chunk-WO7XQZNY.js";
10
+ import "./chunk-ULCIAC3E.js";
11
11
  import {
12
12
  catalogToAstro,
13
13
  checkAndConvertMdToMdx
@@ -15,7 +15,7 @@ import {
15
15
  import "./chunk-EXAALOQA.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-EJOD6TNE.js";
18
+ } from "./chunk-WG64IAI3.js";
19
19
  import {
20
20
  isAuthEnabled,
21
21
  isBackstagePluginEnabled,
@@ -40,6 +40,7 @@ import ChannelNode from './Nodes/Channel';
40
40
  import { CogIcon } from '@heroicons/react/20/solid';
41
41
  import { useEventCatalogVisualiser } from 'src/hooks/eventcatalog-visualizer';
42
42
  import VisualiserSearch, { type VisualiserSearchRef } from './VisualiserSearch';
43
+ import StepWalkthrough from './StepWalkthrough';
43
44
  interface Props {
44
45
  nodes: any;
45
46
  edges: any;
@@ -97,6 +98,7 @@ const NodeGraphBuilder = ({
97
98
  const [isSettingsOpen, setIsSettingsOpen] = useState(false);
98
99
  const [isAnimated, setIsAnimated] = useState(false);
99
100
  const [animateMessages, setAnimateMessages] = useState(false);
101
+ const [activeStepIndex, setActiveStepIndex] = useState<number | null>(null);
100
102
  const { hideChannels, toggleChannelsVisibility } = useEventCatalogVisualiser({ nodes, edges, setNodes, setEdges });
101
103
  const { fitView, getNodes } = useReactFlow();
102
104
  const searchRef = useRef<VisualiserSearchRef>(null);
@@ -343,6 +345,106 @@ const NodeGraphBuilder = ({
343
345
 
344
346
  const legend = getNodesByCollectionWithColors(nodes);
345
347
 
348
+ const handleStepChange = useCallback(
349
+ (nodeId: string | null, highlightPaths?: string[], shouldZoomOut?: boolean) => {
350
+ if (nodeId === null) {
351
+ // Reset all nodes and edges
352
+ resetNodesAndEdges();
353
+ setActiveStepIndex(null);
354
+
355
+ // If shouldZoomOut is true, fit the entire view
356
+ if (shouldZoomOut) {
357
+ setTimeout(() => {
358
+ fitView({ duration: 800, padding: 0.1 });
359
+ }, 100);
360
+ }
361
+ return;
362
+ }
363
+
364
+ const activeNode = nodes.find((node: Node) => node.id === nodeId);
365
+ if (!activeNode) return;
366
+
367
+ // Create set of highlighted nodes and edges
368
+ const highlightedNodeIds = new Set<string>();
369
+ const highlightedEdgeIds = new Set<string>();
370
+
371
+ // Add current node
372
+ highlightedNodeIds.add(activeNode.id);
373
+
374
+ // Add incoming edges and their source nodes
375
+ edges.forEach((edge: Edge) => {
376
+ if (edge.target === activeNode.id) {
377
+ highlightedEdgeIds.add(edge.id);
378
+ highlightedNodeIds.add(edge.source);
379
+ }
380
+ });
381
+
382
+ // Add outgoing edges
383
+ if (highlightPaths) {
384
+ // Highlight all possible paths when at a fork
385
+ highlightPaths.forEach((pathId) => {
386
+ const [source, target] = pathId.split('-');
387
+ edges.forEach((edge: Edge) => {
388
+ if (edge.source === source && edge.target === target) {
389
+ highlightedEdgeIds.add(edge.id);
390
+ highlightedNodeIds.add(edge.target);
391
+ }
392
+ });
393
+ });
394
+ } else {
395
+ // Highlight all outgoing edges normally
396
+ edges.forEach((edge: Edge) => {
397
+ if (edge.source === activeNode.id) {
398
+ highlightedEdgeIds.add(edge.id);
399
+ highlightedNodeIds.add(edge.target);
400
+ }
401
+ });
402
+ }
403
+
404
+ // Update nodes
405
+ const updatedNodes = nodes.map((node: Node) => {
406
+ if (highlightedNodeIds.has(node.id)) {
407
+ return { ...node, style: { ...node.style, opacity: 1 } };
408
+ }
409
+ return { ...node, style: { ...node.style, opacity: 0.2 } };
410
+ });
411
+
412
+ // Update edges
413
+ const updatedEdges = edges.map((edge: Edge) => {
414
+ if (highlightedEdgeIds.has(edge.id)) {
415
+ return {
416
+ ...edge,
417
+ data: { ...edge.data, opacity: 1, animated: true },
418
+ style: { ...edge.style, opacity: 1, strokeWidth: 3 },
419
+ labelStyle: { ...edge.labelStyle, opacity: 1 },
420
+ animated: true,
421
+ };
422
+ }
423
+ return {
424
+ ...edge,
425
+ data: { ...edge.data, opacity: 0.2, animated: false },
426
+ style: { ...edge.style, opacity: 0.2, strokeWidth: 2 },
427
+ labelStyle: { ...edge.labelStyle, opacity: 0.2 },
428
+ animated: false,
429
+ };
430
+ });
431
+
432
+ setNodes(updatedNodes);
433
+ setEdges(updatedEdges);
434
+
435
+ // Fit view to active node
436
+ fitView({
437
+ padding: 0.4,
438
+ duration: 800,
439
+ nodes: [activeNode],
440
+ });
441
+ },
442
+ [nodes, edges, setNodes, setEdges, resetNodesAndEdges, fitView]
443
+ );
444
+
445
+ // Check if this is a flow visualization by checking if edges use flow-edge type
446
+ const isFlowVisualization = edges.some((edge: Edge) => edge.type === 'flow-edge');
447
+
346
448
  return (
347
449
  <ReactFlow
348
450
  nodeTypes={nodeTypes}
@@ -471,6 +573,17 @@ const NodeGraphBuilder = ({
471
573
  )}
472
574
  {includeBackground && <Background color="#bbb" gap={16} />}
473
575
  {includeBackground && <Controls />}
576
+ {isFlowVisualization && (
577
+ <Panel position="bottom-left">
578
+ <StepWalkthrough
579
+ nodes={nodes}
580
+ edges={edges}
581
+ isFlowVisualization={isFlowVisualization}
582
+ onStepChange={handleStepChange}
583
+ mode={mode}
584
+ />
585
+ </Panel>
586
+ )}
474
587
  {includeKey && (
475
588
  <Panel position="bottom-right">
476
589
  <div className=" bg-white font-light px-4 text-[12px] shadow-md py-1 rounded-md">
@@ -0,0 +1,296 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
3
+ import type { Node, Edge } from '@xyflow/react';
4
+
5
+ interface NodeData {
6
+ step?: {
7
+ title?: string;
8
+ summary?: string;
9
+ };
10
+ service?: {
11
+ data?: {
12
+ name?: string;
13
+ summary?: string;
14
+ };
15
+ };
16
+ message?: {
17
+ data?: {
18
+ name?: string;
19
+ summary?: string;
20
+ };
21
+ };
22
+ flow?: {
23
+ data?: {
24
+ name?: string;
25
+ };
26
+ };
27
+ custom?: {
28
+ title?: string;
29
+ label?: string;
30
+ summary?: string;
31
+ };
32
+ actor?: {
33
+ label?: string;
34
+ };
35
+ externalSystem?: {
36
+ label?: string;
37
+ };
38
+ }
39
+
40
+ interface CustomNode {
41
+ id: string;
42
+ data: NodeData;
43
+ }
44
+
45
+ interface StepWalkthroughProps {
46
+ nodes: CustomNode[];
47
+ edges: Edge[];
48
+ isFlowVisualization: boolean;
49
+ onStepChange: (nodeId: string | null, highlightPaths?: string[], shouldZoomOut?: boolean) => void;
50
+ mode?: 'full' | 'simple';
51
+ }
52
+
53
+ interface PathOption {
54
+ targetId: string;
55
+ label?: string;
56
+ targetNode: CustomNode;
57
+ }
58
+
59
+ export default function StepWalkthrough({
60
+ nodes,
61
+ edges,
62
+ isFlowVisualization,
63
+ onStepChange,
64
+ mode = 'full',
65
+ }: StepWalkthroughProps) {
66
+ const [currentNodeId, setCurrentNodeId] = useState<string | null>(null);
67
+ const [pathHistory, setPathHistory] = useState<string[]>([]);
68
+ const [currentStepIndex, setCurrentStepIndex] = useState<number>(-1); // -1 means not started
69
+ const [availablePaths, setAvailablePaths] = useState<PathOption[]>([]);
70
+ const [selectedPathIndex, setSelectedPathIndex] = useState<number>(0);
71
+ const [startNodeId, setStartNodeId] = useState<string | null>(null);
72
+
73
+ useEffect(() => {
74
+ if (isFlowVisualization && nodes.length > 0) {
75
+ // Find the starting node (node with no incoming edges)
76
+ const incomingEdgeMap = new Map<string, number>();
77
+ nodes.forEach((node: CustomNode) => incomingEdgeMap.set(node.id, 0));
78
+
79
+ edges.forEach((edge: Edge) => {
80
+ if (incomingEdgeMap.has(edge.target)) {
81
+ incomingEdgeMap.set(edge.target, (incomingEdgeMap.get(edge.target) || 0) + 1);
82
+ }
83
+ });
84
+
85
+ const startNodes = nodes.filter((node: CustomNode) => incomingEdgeMap.get(node.id) === 0);
86
+ if (startNodes.length > 0 && !startNodeId) {
87
+ const firstStartNode = startNodes[0];
88
+ setStartNodeId(firstStartNode.id);
89
+ }
90
+ }
91
+ }, [nodes, edges, isFlowVisualization, startNodeId]);
92
+
93
+ useEffect(() => {
94
+ if (currentNodeId) {
95
+ // Find available paths from current node
96
+ const outgoingEdges = edges.filter((edge: Edge) => edge.source === currentNodeId);
97
+ const paths: PathOption[] = outgoingEdges.map((edge: Edge) => {
98
+ const targetNode = nodes.find((n: CustomNode) => n.id === edge.target);
99
+ return {
100
+ targetId: edge.target,
101
+ label: edge.label as string | undefined,
102
+ targetNode: targetNode!,
103
+ };
104
+ });
105
+ setAvailablePaths(paths);
106
+ setSelectedPathIndex(0);
107
+ } else {
108
+ setAvailablePaths([]);
109
+ }
110
+ }, [currentNodeId, nodes, edges]);
111
+
112
+ const handleNextStep = useCallback(() => {
113
+ if (currentStepIndex === -1) {
114
+ // Start the walkthrough
115
+ if (startNodeId) {
116
+ setPathHistory([startNodeId]);
117
+ setCurrentNodeId(startNodeId);
118
+ setCurrentStepIndex(0);
119
+ onStepChange(startNodeId);
120
+ }
121
+ } else if (availablePaths.length > 0) {
122
+ // Move to the selected path
123
+ const selectedPath = availablePaths[selectedPathIndex];
124
+ const newHistory = [...pathHistory, selectedPath.targetId];
125
+ setPathHistory(newHistory);
126
+ setCurrentNodeId(selectedPath.targetId);
127
+ setCurrentStepIndex((prev) => prev + 1);
128
+
129
+ // Highlight the selected path
130
+ const allPaths = availablePaths.map((p) => `${currentNodeId}-${p.targetId}`);
131
+ onStepChange(selectedPath.targetId, allPaths);
132
+ }
133
+ }, [currentStepIndex, startNodeId, availablePaths, selectedPathIndex, currentNodeId, onStepChange]);
134
+
135
+ const handlePreviousStep = useCallback(() => {
136
+ if (currentStepIndex > 0) {
137
+ // Go back to previous step
138
+ const newIndex = currentStepIndex - 1;
139
+ const prevNodeId = pathHistory[newIndex];
140
+ setCurrentNodeId(prevNodeId);
141
+ setCurrentStepIndex(newIndex);
142
+ onStepChange(prevNodeId);
143
+ } else if (currentStepIndex === 0) {
144
+ // Go back to the start (no selection)
145
+ setCurrentNodeId(null);
146
+ setCurrentStepIndex(-1);
147
+ onStepChange(null);
148
+ }
149
+ }, [currentStepIndex, pathHistory, onStepChange]);
150
+
151
+ const handlePathSelection = useCallback((index: number) => {
152
+ setSelectedPathIndex(index);
153
+ }, []);
154
+
155
+ const handleFinish = useCallback(() => {
156
+ setCurrentNodeId(null);
157
+ setCurrentStepIndex(-1);
158
+ setPathHistory([]);
159
+ onStepChange(null, [], true); // Pass true to indicate full reset with zoom out
160
+ }, [onStepChange]);
161
+
162
+ if (!isFlowVisualization || nodes.length === 0 || mode !== 'full') {
163
+ return null;
164
+ }
165
+
166
+ const getCurrentStepInfo = () => {
167
+ if (currentStepIndex === -1) {
168
+ return { title: 'Walk through business flow', description: 'Step through the flow to understand the business process' };
169
+ }
170
+
171
+ const currentNode = nodes.find((n: CustomNode) => n.id === currentNodeId);
172
+ if (!currentNode) return { title: 'Unknown step', description: '' };
173
+
174
+ let stepNumber = currentStepIndex + 1;
175
+ let title = `Step ${stepNumber}`;
176
+ let description = '';
177
+
178
+ // Get node information based on type - check step data first, then type-specific data
179
+ if (currentNode.data.step?.title) {
180
+ title += `: ${currentNode.data.step.title}`;
181
+ } else if (currentNode.data.service?.data?.name) {
182
+ title += `: ${currentNode.data.service.data.name}`;
183
+ } else if (currentNode.data.message?.data?.name) {
184
+ title += `: ${currentNode.data.message.data.name}`;
185
+ } else if (currentNode.data.flow?.data?.name) {
186
+ title += `: ${currentNode.data.flow.data.name}`;
187
+ } else if (currentNode.data.custom?.title) {
188
+ title += `: ${currentNode.data.custom.title}`;
189
+ } else if (currentNode.data.custom?.label) {
190
+ title += `: ${currentNode.data.custom.label}`;
191
+ } else if (currentNode.data.actor?.label) {
192
+ title += `: ${currentNode.data.actor.label}`;
193
+ } else if (currentNode.data.externalSystem?.label) {
194
+ title += `: ${currentNode.data.externalSystem.label}`;
195
+ }
196
+
197
+ // Get description - check step data first, then type-specific data
198
+ if (currentNode.data.step?.summary) {
199
+ description = currentNode.data.step.summary;
200
+ } else if (currentNode.data.service?.data?.summary) {
201
+ description = currentNode.data.service.data.summary;
202
+ } else if (currentNode.data.message?.data?.summary) {
203
+ description = currentNode.data.message.data.summary;
204
+ } else if (currentNode.data.custom?.summary) {
205
+ description = currentNode.data.custom.summary;
206
+ }
207
+
208
+ return { title, description };
209
+ };
210
+
211
+ const { title, description } = getCurrentStepInfo();
212
+
213
+ return (
214
+ <div className="ml-8 bg-white rounded-lg shadow-sm p-[8px] z-30 border border-gray-200 w-[350px]">
215
+ <div className="mb-3">
216
+ <h3 className="text-sm font-semibold text-gray-900">{title}</h3>
217
+ {description && <p className="text-xs text-gray-600 mt-1">{description}</p>}
218
+ </div>
219
+
220
+ {/* Show path options when there are multiple paths */}
221
+ {currentNodeId && availablePaths.length > 1 && (
222
+ <div className="mb-3">
223
+ <label className="block text-xs font-medium text-gray-700 mb-2">Choose next path:</label>
224
+ <select
225
+ value={selectedPathIndex}
226
+ onChange={(e: React.ChangeEvent<HTMLSelectElement>) => handlePathSelection(parseInt(e.target.value))}
227
+ className="w-full px-3 py-2 text-xs border border-gray-300 rounded-md bg-white focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
228
+ >
229
+ {availablePaths.map((path, index) => {
230
+ // @ts-ignore
231
+ const nodeLabel =
232
+ path.targetNode.data.step?.title ||
233
+ (path.targetNode.data as any).service?.data?.name ||
234
+ (path.targetNode.data as any).message?.data?.name ||
235
+ (path.targetNode.data as any).flow?.data?.name ||
236
+ (path.targetNode.data as any).custom?.title ||
237
+ (path.targetNode.data as any).custom?.label ||
238
+ (path.targetNode.data as any).actor?.label ||
239
+ (path.targetNode.data as any).externalSystem?.label ||
240
+ 'Unknown';
241
+
242
+ return (
243
+ <option key={path.targetId} value={index}>
244
+ {path.label ? `${path.label}: ${nodeLabel}` : nodeLabel}
245
+ </option>
246
+ );
247
+ })}
248
+ </select>
249
+ </div>
250
+ )}
251
+
252
+ <div className="flex items-center justify-between">
253
+ {currentStepIndex === -1 ? (
254
+ // Initial state - show only Start button on the right
255
+ <>
256
+ <div className="flex-1"></div>
257
+ <button
258
+ onClick={handleNextStep}
259
+ className="flex items-center justify-center px-6 py-2 text-xs font-medium bg-purple-600 text-white rounded-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transition-colors"
260
+ >
261
+ Start
262
+ </button>
263
+ </>
264
+ ) : (
265
+ // In walkthrough - show Previous on left, Next on right (only if paths available)
266
+ <>
267
+ <button
268
+ onClick={handlePreviousStep}
269
+ className="flex items-center justify-center px-4 py-2 text-xs font-medium bg-purple-600 text-white rounded-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transition-colors"
270
+ >
271
+ <ChevronLeftIcon className="w-4 h-4 mr-1" />
272
+ Previous
273
+ </button>
274
+
275
+ {availablePaths.length > 0 ? (
276
+ <button
277
+ onClick={handleNextStep}
278
+ className="flex items-center justify-center px-4 py-2 text-xs font-medium bg-purple-600 text-white rounded-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transition-colors"
279
+ >
280
+ Next
281
+ <ChevronRightIcon className="w-4 h-4 ml-1" />
282
+ </button>
283
+ ) : (
284
+ <button
285
+ onClick={handleFinish}
286
+ className="flex items-center justify-center px-4 py-2 text-xs font-medium bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition-colors"
287
+ >
288
+ Finish
289
+ </button>
290
+ )}
291
+ </>
292
+ )}
293
+ </div>
294
+ </div>
295
+ );
296
+ }
@@ -140,7 +140,7 @@ const VisualiserSearch = forwardRef<VisualiserSearchRef, VisualiserSearchProps>(
140
140
  // Close suggestions when clicking outside
141
141
  useEffect(() => {
142
142
  const handleClickOutside = (event: MouseEvent) => {
143
- if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
143
+ if (containerRef.current && !containerRef.current.contains(event.target as any)) {
144
144
  setShowSuggestions(false);
145
145
  setSelectedSuggestionIndex(-1);
146
146
  }
@@ -151,7 +151,7 @@ function processSchema(schema: any, rootSchema?: any): any {
151
151
 
152
152
  // Merge all properties from variants for display
153
153
  const allProperties: Record<string, any> = {};
154
- processedVariants.forEach((variant) => {
154
+ processedVariants.forEach((variant: any) => {
155
155
  if (variant.properties) {
156
156
  Object.assign(allProperties, variant.properties);
157
157
  }
@@ -1,10 +1,33 @@
1
1
  import React from 'react';
2
2
  import { getMessageColorByCollection, getMessageCollectionName } from '../index';
3
+
3
4
  interface MessageListProps {
4
5
  messages: any[];
5
6
  decodedCurrentPath: string;
7
+ searchTerm?: string;
6
8
  }
7
9
 
10
+ const HighlightedText = React.memo(({ text, searchTerm }: { text: string; searchTerm?: string }) => {
11
+ if (!searchTerm) return <>{text}</>;
12
+
13
+ const regex = new RegExp(`(${searchTerm})`, 'gi');
14
+ const parts = text.split(regex);
15
+
16
+ return (
17
+ <>
18
+ {parts.map((part, index) =>
19
+ regex.test(part) ? (
20
+ <span key={index} className="bg-yellow-200 text-gray-900 font-semibold">
21
+ {part}
22
+ </span>
23
+ ) : (
24
+ <span key={index}>{part}</span>
25
+ )
26
+ )}
27
+ </>
28
+ );
29
+ });
30
+
8
31
  const getMessageColorByLabelOrCollection = (collection: string, badge?: string) => {
9
32
  if (!badge) {
10
33
  return getMessageColorByCollection(collection);
@@ -25,7 +48,7 @@ const getMessageColorByLabelOrCollection = (collection: string, badge?: string)
25
48
  return getMessageColorByCollection(collection);
26
49
  };
27
50
 
28
- const MessageList: React.FC<MessageListProps> = ({ messages, decodedCurrentPath }) => (
51
+ const MessageList: React.FC<MessageListProps> = ({ messages, decodedCurrentPath, searchTerm }) => (
29
52
  <ul className="space-y-0.5 border-l border-gray-200/80 ml-[9px] pl-4">
30
53
  {messages.map((message: any) => (
31
54
  <li key={message.id} data-active={decodedCurrentPath === message.href}>
@@ -35,7 +58,9 @@ const MessageList: React.FC<MessageListProps> = ({ messages, decodedCurrentPath
35
58
  decodedCurrentPath.includes(message.href) ? 'bg-purple-100 ' : 'hover:bg-purple-100'
36
59
  }`}
37
60
  >
38
- <span className="truncate">{message.data?.sidebar?.label || message.data.name}</span>
61
+ <span className="truncate">
62
+ <HighlightedText text={message.data?.sidebar?.label || message.data.name} searchTerm={searchTerm} />
63
+ </span>
39
64
  <span
40
65
  className={`ml-2 text-[10px] flex items-center gap-1 font-medium px-2 uppercase py-0.5 rounded ${getMessageColorByLabelOrCollection(message.collection, message.data?.sidebar?.badge)}`}
41
66
  >
@@ -8,6 +8,27 @@ import type { MessageItem, ServiceItem, ListViewSideBarProps } from './types';
8
8
  const STORAGE_KEY = 'EventCatalog:catalogSidebarCollapsedGroups';
9
9
  const DEBOUNCE_DELAY = 300; // 300ms debounce delay
10
10
 
11
+ const HighlightedText = React.memo(({ text, searchTerm }: { text: string; searchTerm: string }) => {
12
+ if (!searchTerm) return <>{text}</>;
13
+
14
+ const regex = new RegExp(`(${searchTerm})`, 'gi');
15
+ const parts = text.split(regex);
16
+
17
+ return (
18
+ <>
19
+ {parts.map((part, index) =>
20
+ regex.test(part) ? (
21
+ <span key={index} className="bg-yellow-200 text-gray-900 font-semibold">
22
+ {part}
23
+ </span>
24
+ ) : (
25
+ <span key={index}>{part}</span>
26
+ )
27
+ )}
28
+ </>
29
+ );
30
+ });
31
+
11
32
  export const getMessageColorByCollection = (collection: string) => {
12
33
  if (collection === 'commands') return 'bg-blue-50 text-blue-600';
13
34
  if (collection === 'queries') return 'bg-green-50 text-green-600';
@@ -46,12 +67,14 @@ const ServiceItem = React.memo(
46
67
  collapsedGroups,
47
68
  toggleGroupCollapse,
48
69
  isVisualizer,
70
+ searchTerm,
49
71
  }: {
50
72
  item: ServiceItem;
51
73
  decodedCurrentPath: string;
52
74
  collapsedGroups: { [key: string]: boolean };
53
75
  toggleGroupCollapse: (group: string) => void;
54
76
  isVisualizer: boolean;
77
+ searchTerm: string;
55
78
  }) => {
56
79
  const asyncAPISpecifications = item.specifications?.filter((spec) => spec.type === 'asyncapi');
57
80
  const openAPISpecifications = item.specifications?.filter((spec) => spec.type === 'openapi');
@@ -68,7 +91,9 @@ const ServiceItem = React.memo(
68
91
  }}
69
92
  className="flex justify-between items-center pl-2 w-full text-xs"
70
93
  >
71
- <span className="truncate text-xs font-bold">{item.label}</span>
94
+ <span className="truncate text-xs font-bold">
95
+ <HighlightedText text={item.label} searchTerm={searchTerm} />
96
+ </span>
72
97
  <span className="text-purple-600 ml-2 text-[10px] font-medium bg-purple-50 px-2 py-0.5 rounded">SERVICE</span>
73
98
  </button>
74
99
  }
@@ -131,7 +156,7 @@ const ServiceItem = React.memo(
131
156
  </button>
132
157
  }
133
158
  >
134
- <MessageList messages={item.receives} decodedCurrentPath={decodedCurrentPath} />
159
+ <MessageList messages={item.receives} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
135
160
  </CollapsibleGroup>
136
161
 
137
162
  <CollapsibleGroup
@@ -149,7 +174,7 @@ const ServiceItem = React.memo(
149
174
  </button>
150
175
  }
151
176
  >
152
- <MessageList messages={item.sends} decodedCurrentPath={decodedCurrentPath} />
177
+ <MessageList messages={item.sends} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
153
178
  </CollapsibleGroup>
154
179
  {!isVisualizer && item.entities.length > 0 && (
155
180
  <CollapsibleGroup
@@ -167,7 +192,7 @@ const ServiceItem = React.memo(
167
192
  </button>
168
193
  }
169
194
  >
170
- <MessageList messages={item.entities} decodedCurrentPath={decodedCurrentPath} />
195
+ <MessageList messages={item.entities} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
171
196
  </CollapsibleGroup>
172
197
  )}
173
198
  </div>
@@ -206,12 +231,17 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
206
231
  const filteredData = useMemo(() => {
207
232
  if (!debouncedSearchTerm) return data;
208
233
 
209
- const filterItem = (item: { label: string }) => {
210
- return item.label.toLowerCase().includes(debouncedSearchTerm);
234
+ const filterItem = (item: { label: string; id?: string }) => {
235
+ return (
236
+ item.label.toLowerCase().includes(debouncedSearchTerm) || (item.id && item.id.toLowerCase().includes(debouncedSearchTerm))
237
+ );
211
238
  };
212
239
 
213
240
  const filterMessages = (messages: MessageItem[]) => {
214
- return messages.filter((message) => message.data.name.toLowerCase().includes(debouncedSearchTerm));
241
+ return messages.filter(
242
+ (message) =>
243
+ message.data.name.toLowerCase().includes(debouncedSearchTerm) || message.id.toLowerCase().includes(debouncedSearchTerm)
244
+ );
215
245
  };
216
246
 
217
247
  return {
@@ -224,13 +254,22 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
224
254
  receives: filterMessages(service.receives),
225
255
  isVisible:
226
256
  filterItem(service) ||
227
- service.sends.some((msg: MessageItem) => msg.data.name.toLowerCase().includes(debouncedSearchTerm)) ||
228
- service.receives.some((msg: MessageItem) => msg.data.name.toLowerCase().includes(debouncedSearchTerm)),
257
+ service.sends.some(
258
+ (msg: MessageItem) =>
259
+ msg.data.name.toLowerCase().includes(debouncedSearchTerm) || msg.id.toLowerCase().includes(debouncedSearchTerm)
260
+ ) ||
261
+ service.receives.some(
262
+ (msg: MessageItem) =>
263
+ msg.data.name.toLowerCase().includes(debouncedSearchTerm) || msg.id.toLowerCase().includes(debouncedSearchTerm)
264
+ ),
229
265
  }))
230
266
  .filter((service: ServiceItem & { isVisible: boolean }) => service.isVisible) || [],
231
267
  flows: data.flows?.filter(filterItem) || [],
232
268
  messagesNotInService:
233
- data.messagesNotInService?.filter((msg: MessageItem) => msg.label.toLowerCase().includes(debouncedSearchTerm)) || [],
269
+ data.messagesNotInService?.filter(
270
+ (msg: MessageItem) =>
271
+ msg.label.toLowerCase().includes(debouncedSearchTerm) || msg.id.toLowerCase().includes(debouncedSearchTerm)
272
+ ) || [],
234
273
  };
235
274
  }, [data, debouncedSearchTerm]);
236
275
 
@@ -333,7 +372,9 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
333
372
  decodedCurrentPath === item.href
334
373
  }`}
335
374
  >
336
- <span className="truncate">{item.label}</span>
375
+ <span className="truncate">
376
+ <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
377
+ </span>
337
378
  <span className="text-yellow-600 ml-2 text-[10px] font-medium bg-yellow-50 px-2 py-0.5 rounded">
338
379
  {isDomainSubDomain(item) ? 'SUBDOMAIN' : 'DOMAIN'}
339
380
  </span>
@@ -395,7 +436,11 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
395
436
  </button>
396
437
  }
397
438
  >
398
- <MessageList messages={item.entities} decodedCurrentPath={decodedCurrentPath} />
439
+ <MessageList
440
+ messages={item.entities}
441
+ decodedCurrentPath={decodedCurrentPath}
442
+ searchTerm={debouncedSearchTerm}
443
+ />
399
444
  </CollapsibleGroup>
400
445
  )}
401
446
  </div>
@@ -417,6 +462,7 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
417
462
  collapsedGroups={collapsedGroups}
418
463
  toggleGroupCollapse={toggleGroupCollapse}
419
464
  isVisualizer={isVisualizer}
465
+ searchTerm={debouncedSearchTerm}
420
466
  />
421
467
  ))}
422
468
  </ul>
@@ -434,7 +480,9 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
434
480
  decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
435
481
  }`}
436
482
  >
437
- <span className="truncate">{item.label}</span>
483
+ <span className="truncate">
484
+ <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
485
+ </span>
438
486
  <span
439
487
  className={`ml-2 text-[10px] font-medium px-2 uppercase py-0.5 rounded ${getMessageColorByCollection(item.collection)}`}
440
488
  >
@@ -458,7 +506,9 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
458
506
  decodedCurrentPath === item.href ? 'bg-cyan-100 text-cyan-900' : 'hover:bg-purple-100'
459
507
  }`}
460
508
  >
461
- <span className="truncate">{item.label}</span>
509
+ <span className="truncate">
510
+ <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
511
+ </span>
462
512
  <span className="text-cyan-600 ml-2 text-[10px] font-medium bg-cyan-50 px-2 py-0.5 rounded">FLOW</span>
463
513
  </a>
464
514
  </li>
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/event-catalog/eventcatalog.git"
7
7
  },
8
8
  "type": "module",
9
- "version": "2.44.3",
9
+ "version": "2.44.5",
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },