@eventcatalog/core 2.64.4 → 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.
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-MDWC22V7.js → chunk-2ZXSFW7J.js} +1 -1
- package/dist/chunk-622JYJWG.js +109 -0
- package/dist/{chunk-KEEW2PJ4.js → chunk-6MJGAOPK.js} +1 -1
- package/dist/chunk-BH3JMNAV.js +12 -0
- package/dist/{chunk-BXXJNRMA.js → chunk-GGFP7ZBX.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +139 -24
- package/dist/eventcatalog.js +9 -3
- package/dist/migrations/index.cjs +150 -0
- package/dist/migrations/index.d.cts +3 -0
- package/dist/migrations/index.d.ts +3 -0
- package/dist/migrations/index.js +7 -0
- package/dist/migrations/message-channels-to-service-channels.cjs +139 -0
- package/dist/migrations/message-channels-to-service-channels.d.cts +6 -0
- package/dist/migrations/message-channels-to-service-channels.d.ts +6 -0
- package/dist/migrations/message-channels-to-service-channels.js +6 -0
- package/eventcatalog/src/components/MDX/NodeGraph/Edges/AnimatedMessageEdge.tsx +42 -28
- package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +41 -35
- package/eventcatalog/src/content.config.ts +31 -3
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +0 -4
- package/eventcatalog/src/hooks/eventcatalog-visualizer.ts +35 -15
- package/eventcatalog/src/utils/channels.ts +73 -1
- package/eventcatalog/src/utils/collections/util.ts +7 -0
- package/eventcatalog/src/utils/node-graphs/channel-node-graph.ts +75 -0
- package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +856 -61
- package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +46 -70
- package/eventcatalog/src/utils/node-graphs/utils/utils.ts +26 -80
- package/package.json +2 -2
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/migrations/message-channels-to-service-channels.ts
|
|
31
|
+
var message_channels_to_service_channels_exports = {};
|
|
32
|
+
__export(message_channels_to_service_channels_exports, {
|
|
33
|
+
default: () => message_channels_to_service_channels_default
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(message_channels_to_service_channels_exports);
|
|
36
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
37
|
+
var import_glob = require("glob");
|
|
38
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
39
|
+
var import_gray_matter = __toESM(require("gray-matter"), 1);
|
|
40
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
41
|
+
var message_channels_to_service_channels_default = async (dir) => {
|
|
42
|
+
const PROJECT_DIR = import_node_path.default.join(dir || process.env.PROJECT_DIR);
|
|
43
|
+
const messages = await (0, import_glob.glob)(
|
|
44
|
+
[
|
|
45
|
+
"**/events/*/index.mdx",
|
|
46
|
+
"**/events/*/index.md",
|
|
47
|
+
"**/events/*/versioned/*/index.mdx",
|
|
48
|
+
"**/events/*/versioned/*/index.md",
|
|
49
|
+
// commands
|
|
50
|
+
"**/commands/*/index.mdx",
|
|
51
|
+
"**/commands/*/index.md",
|
|
52
|
+
"**/commands/*/versioned/*/index.mdx",
|
|
53
|
+
"**/commands/*/versioned/*/index.md",
|
|
54
|
+
// queries
|
|
55
|
+
"**/queries/*/index.mdx",
|
|
56
|
+
"**/queries/*/index.md",
|
|
57
|
+
"**/queries/*/versioned/*/index.mdx",
|
|
58
|
+
"**/queries/*/versioned/*/index.md"
|
|
59
|
+
],
|
|
60
|
+
{
|
|
61
|
+
// const events = await glob(['**/events/*/index.(md|mdx)', '**/events/*/versioned/*/index.(md|mdx)'], {
|
|
62
|
+
cwd: PROJECT_DIR,
|
|
63
|
+
absolute: true,
|
|
64
|
+
nodir: true,
|
|
65
|
+
windowsPathsNoEscape: import_node_os.default.platform() == "win32",
|
|
66
|
+
ignore: ["node_modules/**", "**/dist/**", "**/teams", "**/users", "**/package.json", "**/Dockerfile"]
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
const services = await (0, import_glob.glob)(
|
|
70
|
+
[
|
|
71
|
+
"**/services/*/index.mdx",
|
|
72
|
+
"**/services/*/index.md",
|
|
73
|
+
"**/services/*/versioned/*/index.mdx",
|
|
74
|
+
"**/services/*/versioned/*/index.md"
|
|
75
|
+
],
|
|
76
|
+
{
|
|
77
|
+
cwd: PROJECT_DIR,
|
|
78
|
+
absolute: true
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
const messagesWithChannels = messages.reduce((acc, message) => {
|
|
82
|
+
const file = import_node_fs.default.readFileSync(message, "utf8");
|
|
83
|
+
const { data } = (0, import_gray_matter.default)(file);
|
|
84
|
+
if (data.channels?.length > 0) {
|
|
85
|
+
acc.push({
|
|
86
|
+
...data,
|
|
87
|
+
path: message
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return acc;
|
|
91
|
+
}, []);
|
|
92
|
+
if (messagesWithChannels.length === 0) {
|
|
93
|
+
return { status: "success", message: "No messages with channels found in the catalog" };
|
|
94
|
+
}
|
|
95
|
+
const servicesWithChannels = services.reduce((acc, service) => {
|
|
96
|
+
const file = import_node_fs.default.readFileSync(service, "utf8");
|
|
97
|
+
const { data } = (0, import_gray_matter.default)(file);
|
|
98
|
+
const isSending = data.sends?.some((send) => messagesWithChannels.some((message) => message.id === send.id));
|
|
99
|
+
const isReceiving = data.receives?.some(
|
|
100
|
+
(receive) => messagesWithChannels.some((message) => message.id === receive.id)
|
|
101
|
+
);
|
|
102
|
+
if (isSending || isReceiving) {
|
|
103
|
+
acc.push({
|
|
104
|
+
...data,
|
|
105
|
+
path: service
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return acc;
|
|
109
|
+
}, []);
|
|
110
|
+
for (const service of servicesWithChannels) {
|
|
111
|
+
const file = import_node_fs.default.readFileSync(service.path, "utf8");
|
|
112
|
+
const { data } = (0, import_gray_matter.default)(file);
|
|
113
|
+
const messagesTheServiceSendsThatNeedUpdating = data.sends?.filter((send) => messagesWithChannels.some((message) => message.id === send.id)) ?? [];
|
|
114
|
+
const messagesTheServiceReceivesThatNeedUpdating = data.receives?.filter((receive) => messagesWithChannels.some((message) => message.id === receive.id)) ?? [];
|
|
115
|
+
if (messagesTheServiceSendsThatNeedUpdating.length > 0 || messagesTheServiceReceivesThatNeedUpdating.length > 0) {
|
|
116
|
+
const newSends = messagesTheServiceSendsThatNeedUpdating.map((send) => ({
|
|
117
|
+
...send,
|
|
118
|
+
to: messagesWithChannels.map((message) => message.id === send.id ? message.channels : []).flat().filter((channel) => channel !== null)
|
|
119
|
+
}));
|
|
120
|
+
const newReceives = messagesTheServiceReceivesThatNeedUpdating.map((receive) => ({
|
|
121
|
+
...receive,
|
|
122
|
+
from: messagesWithChannels.map((message) => message.id === receive.id ? message.channels : []).flat().filter((channel) => channel !== null)
|
|
123
|
+
}));
|
|
124
|
+
const newData = {
|
|
125
|
+
...data,
|
|
126
|
+
...newSends.length > 0 ? { sends: newSends } : {},
|
|
127
|
+
...newReceives.length > 0 ? { receives: newReceives } : {}
|
|
128
|
+
};
|
|
129
|
+
import_node_fs.default.writeFileSync(service.path, import_gray_matter.default.stringify(file, newData));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const message of messagesWithChannels) {
|
|
133
|
+
const file = import_node_fs.default.readFileSync(message.path, "utf8");
|
|
134
|
+
const parsed = (0, import_gray_matter.default)(file);
|
|
135
|
+
const { channels, ...newData } = parsed.data;
|
|
136
|
+
import_node_fs.default.writeFileSync(message.path, import_gray_matter.default.stringify(parsed.content, newData));
|
|
137
|
+
}
|
|
138
|
+
console.log("\u2714 Channels migrated to new services API");
|
|
139
|
+
};
|
|
@@ -12,6 +12,7 @@ const AnimatedMessageEdge = ({
|
|
|
12
12
|
data,
|
|
13
13
|
label = '',
|
|
14
14
|
markerEnd,
|
|
15
|
+
markerStart,
|
|
15
16
|
}: any) => {
|
|
16
17
|
const [edgePath, labelX, labelY] = getBezierPath({
|
|
17
18
|
sourceX,
|
|
@@ -22,9 +23,6 @@ const AnimatedMessageEdge = ({
|
|
|
22
23
|
targetPosition,
|
|
23
24
|
});
|
|
24
25
|
|
|
25
|
-
const collection = data?.message?.collection;
|
|
26
|
-
const opacity = data?.opacity ?? 1;
|
|
27
|
-
|
|
28
26
|
const messageColor = useMemo(
|
|
29
27
|
() => (collection: string) => {
|
|
30
28
|
switch (collection) {
|
|
@@ -41,41 +39,57 @@ const AnimatedMessageEdge = ({
|
|
|
41
39
|
[]
|
|
42
40
|
);
|
|
43
41
|
|
|
42
|
+
const collection = data?.message?.collection;
|
|
43
|
+
const opacity = data?.opacity ?? 1;
|
|
44
|
+
const customColor = data?.customColor || messageColor(collection ?? 'default');
|
|
45
|
+
const warning = data?.warning;
|
|
46
|
+
|
|
47
|
+
// For each customColor (string or array of strings), we need to create the animated nodes
|
|
48
|
+
const customColors = Array.isArray(customColor) ? customColor : [customColor];
|
|
49
|
+
|
|
44
50
|
const randomDelay = useMemo(() => Math.random() * 1, []);
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<animateMotion dur="2s" repeatCount="indefinite" path={edgePath} rotate="auto" begin={`${randomDelay}s`}>
|
|
52
|
+
const animatedNodes = customColors.map((color, index) => {
|
|
53
|
+
// Stagger the animations so multiple colored nodes are visible
|
|
54
|
+
const delay = randomDelay + index * 0.3;
|
|
55
|
+
return (
|
|
56
|
+
<g className={`z-30 ${opacity === 1 ? 'opacity-100' : 'opacity-10'}`} key={`${id}-${color}-${index}`}>
|
|
57
|
+
<circle key={`${id}-${color}-${index}`} cx="0" cy="0" r="7" fill={color}>
|
|
58
|
+
<animateMotion dur="2s" repeatCount="indefinite" path={edgePath} rotate="auto" begin={`${delay}s`}>
|
|
54
59
|
<mpath href={`#${id}`} />
|
|
55
60
|
</animateMotion>
|
|
56
61
|
</circle>
|
|
57
62
|
</g>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<rect
|
|
61
|
-
x={labelX - label.length * 3} // Adjust based on text length
|
|
62
|
-
y={labelY - 15} // Position above the text
|
|
63
|
-
width={label.length * 6} // Width based on text length
|
|
64
|
-
height={20} // Fixed height
|
|
65
|
-
fill="white" // Background color
|
|
66
|
-
opacity={0.8} // Opacity
|
|
67
|
-
rx="4" // Rounded corners
|
|
68
|
-
/>
|
|
63
|
+
);
|
|
64
|
+
});
|
|
69
65
|
|
|
66
|
+
// Label can be spit using \n to create multiple lines
|
|
67
|
+
const lines = String(label ?? '').split('\n');
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
<>
|
|
72
|
+
<BaseEdge
|
|
73
|
+
id={id}
|
|
74
|
+
path={edgePath}
|
|
75
|
+
markerEnd={markerEnd}
|
|
76
|
+
markerStart={markerStart}
|
|
77
|
+
style={warning ? { stroke: 'red', strokeWidth: 1 } : {}}
|
|
78
|
+
/>
|
|
79
|
+
{/* Circle Icon */}
|
|
80
|
+
{animatedNodes}
|
|
81
|
+
{/* <g className={`z-30 ${opacity === 1 ? 'opacity-100' : 'opacity-10'}`}>
|
|
82
|
+
</g> */}
|
|
83
|
+
<g>
|
|
70
84
|
{/* Text element */}
|
|
71
|
-
<text x={labelX} y={labelY}
|
|
72
|
-
{
|
|
85
|
+
<text x={labelX} y={labelY} textAnchor="middle" dominantBaseline="middle" fontSize="10px" pointerEvents="none">
|
|
86
|
+
{lines.map((line, i) => (
|
|
87
|
+
<tspan key={i} x={labelX} dy={i === 0 ? 0 : '1.2em'} style={{ fontStyle: i === 0 ? 'normal' : 'italic' }}>
|
|
88
|
+
{line}
|
|
89
|
+
</tspan>
|
|
90
|
+
))}
|
|
73
91
|
</text>
|
|
74
92
|
</g>
|
|
75
|
-
{/* Label */}
|
|
76
|
-
{/* <text x={labelX} y={labelY} fill="black" fontSize="12" textAnchor="middle" dy="-5">
|
|
77
|
-
{label}
|
|
78
|
-
</text> */}
|
|
79
93
|
</>
|
|
80
94
|
);
|
|
81
95
|
};
|
|
@@ -82,6 +82,9 @@ const ServiceItem = React.memo(
|
|
|
82
82
|
const resourceWrites = item.writesTo.filter((writeTo) => !readsAndWritesTo.some((readFrom) => readFrom.id === writeTo.id));
|
|
83
83
|
const hasData = item.writesTo.length > 0 || item.readsFrom.length > 0;
|
|
84
84
|
|
|
85
|
+
const sendsMessages = item.sends && item.sends.length > 0;
|
|
86
|
+
const receivesMessages = item.receives && item.receives.length > 0;
|
|
87
|
+
|
|
85
88
|
return (
|
|
86
89
|
<CollapsibleGroup
|
|
87
90
|
isCollapsed={collapsedGroups[item.href]}
|
|
@@ -167,41 +170,44 @@ const ServiceItem = React.memo(
|
|
|
167
170
|
</CollapsibleGroup>
|
|
168
171
|
)}
|
|
169
172
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
e
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
173
|
+
{receivesMessages && (
|
|
174
|
+
<CollapsibleGroup
|
|
175
|
+
isCollapsed={collapsedGroups[`${item.href}-receives`]}
|
|
176
|
+
onToggle={() => toggleGroupCollapse(`${item.href}-receives`)}
|
|
177
|
+
title={
|
|
178
|
+
<button
|
|
179
|
+
onClick={(e) => {
|
|
180
|
+
e.stopPropagation();
|
|
181
|
+
toggleGroupCollapse(`${item.href}-receives`);
|
|
182
|
+
}}
|
|
183
|
+
className="truncate underline ml-2 text-xs mb-1 py-1"
|
|
184
|
+
>
|
|
185
|
+
Receives messages ({item.receives.length})
|
|
186
|
+
</button>
|
|
187
|
+
}
|
|
188
|
+
>
|
|
189
|
+
<MessageList messages={item.receives} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
|
|
190
|
+
</CollapsibleGroup>
|
|
191
|
+
)}
|
|
192
|
+
{sendsMessages && (
|
|
193
|
+
<CollapsibleGroup
|
|
194
|
+
isCollapsed={collapsedGroups[`${item.href}-sends`]}
|
|
195
|
+
onToggle={() => toggleGroupCollapse(`${item.href}-sends`)}
|
|
196
|
+
title={
|
|
197
|
+
<button
|
|
198
|
+
onClick={(e) => {
|
|
199
|
+
e.stopPropagation();
|
|
200
|
+
toggleGroupCollapse(`${item.href}-sends`);
|
|
201
|
+
}}
|
|
202
|
+
className="truncate underline ml-2 text-xs mb-1 py-1"
|
|
203
|
+
>
|
|
204
|
+
Sends messages ({item.sends.length})
|
|
205
|
+
</button>
|
|
206
|
+
}
|
|
207
|
+
>
|
|
208
|
+
<MessageList messages={item.sends} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
|
|
209
|
+
</CollapsibleGroup>
|
|
210
|
+
)}
|
|
205
211
|
{!isVisualizer && hasData && (
|
|
206
212
|
<CollapsibleGroup
|
|
207
213
|
isCollapsed={collapsedGroups[`${item.href}-data`]}
|
|
@@ -44,6 +44,32 @@ const channelPointer = z
|
|
|
44
44
|
})
|
|
45
45
|
.merge(pointer);
|
|
46
46
|
|
|
47
|
+
const sendsPointer = z.object({
|
|
48
|
+
id: z.string(),
|
|
49
|
+
version: z.string().optional().default('latest'),
|
|
50
|
+
to: z
|
|
51
|
+
.array(
|
|
52
|
+
z.object({
|
|
53
|
+
...channelPointer.shape,
|
|
54
|
+
delivery_mode: z.enum(['push', 'pull', 'push-pull']).optional().default('push'),
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
.optional(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const receivesPointer = z.object({
|
|
61
|
+
id: z.string(),
|
|
62
|
+
version: z.string().optional().default('latest'),
|
|
63
|
+
from: z
|
|
64
|
+
.array(
|
|
65
|
+
z.object({
|
|
66
|
+
...channelPointer.shape,
|
|
67
|
+
delivery_mode: z.enum(['push', 'pull', 'push-pull']).optional().default('push'),
|
|
68
|
+
})
|
|
69
|
+
)
|
|
70
|
+
.optional(),
|
|
71
|
+
});
|
|
72
|
+
|
|
47
73
|
const resourcePointer = z.object({
|
|
48
74
|
id: z.string(),
|
|
49
75
|
version: z.string().optional().default('latest'),
|
|
@@ -359,8 +385,8 @@ const services = defineCollection({
|
|
|
359
385
|
}),
|
|
360
386
|
schema: z
|
|
361
387
|
.object({
|
|
362
|
-
sends: z.array(
|
|
363
|
-
receives: z.array(
|
|
388
|
+
sends: z.array(sendsPointer).optional(),
|
|
389
|
+
receives: z.array(receivesPointer).optional(),
|
|
364
390
|
entities: z.array(pointer).optional(),
|
|
365
391
|
writesTo: z.array(pointer).optional(),
|
|
366
392
|
readsFrom: z.array(pointer).optional(),
|
|
@@ -492,7 +518,7 @@ const domains = defineCollection({
|
|
|
492
518
|
|
|
493
519
|
const channels = defineCollection({
|
|
494
520
|
loader: glob({
|
|
495
|
-
pattern: ['**/channels
|
|
521
|
+
pattern: ['**/channels/**/index.(md|mdx)', '**/channels/**/versioned/*/index.(md|mdx)'],
|
|
496
522
|
base: projectDirBase,
|
|
497
523
|
generateId: ({ data }) => {
|
|
498
524
|
return `${data.id}-${data.version}`;
|
|
@@ -500,8 +526,10 @@ const channels = defineCollection({
|
|
|
500
526
|
}),
|
|
501
527
|
schema: z
|
|
502
528
|
.object({
|
|
529
|
+
channels: z.array(channelPointer).optional(),
|
|
503
530
|
address: z.string().optional(),
|
|
504
531
|
protocols: z.array(z.string()).optional(),
|
|
532
|
+
routes: z.array(channelPointer).optional(),
|
|
505
533
|
parameters: z
|
|
506
534
|
.record(
|
|
507
535
|
z.object({
|
|
@@ -14,8 +14,6 @@ export const getEventCatalogResources = async () => {
|
|
|
14
14
|
const url = new URL('/docs/llm/llms.txt', baseUrl);
|
|
15
15
|
const response = await fetch(url.toString());
|
|
16
16
|
const text = await response.text();
|
|
17
|
-
console.log('URL', url.toString());
|
|
18
|
-
// console.log('TEXT', text);
|
|
19
17
|
return text;
|
|
20
18
|
};
|
|
21
19
|
|
|
@@ -102,7 +100,6 @@ export class AIProvider {
|
|
|
102
100
|
].join('\n'),
|
|
103
101
|
inputSchema: z.object({}),
|
|
104
102
|
execute: async () => {
|
|
105
|
-
console.log('TOOL HAS BEEN CALLED');
|
|
106
103
|
const text = await getEventCatalogResources();
|
|
107
104
|
return text;
|
|
108
105
|
},
|
|
@@ -133,7 +130,6 @@ export class AIProvider {
|
|
|
133
130
|
.describe('The type of resource to find'),
|
|
134
131
|
}),
|
|
135
132
|
execute: async ({ id, version, type }) => {
|
|
136
|
-
console.log('TOOL HAS BEEN CALLED TO GET DETAILED INFORMATION ABOUT A RESOURCE');
|
|
137
133
|
const text = await getResourceInformation(type, id, version);
|
|
138
134
|
return text;
|
|
139
135
|
},
|
|
@@ -27,8 +27,8 @@ export const useEventCatalogVisualiser = ({
|
|
|
27
27
|
skipProcessing = false,
|
|
28
28
|
}: EventCatalogVisualizerProps) => {
|
|
29
29
|
const [hideChannels, setHideChannels] = useState(false);
|
|
30
|
-
const [initialNodes] = useState(nodes);
|
|
31
|
-
const [initialEdges] = useState(edges);
|
|
30
|
+
const [initialNodes, setInitialNodes] = useState(nodes);
|
|
31
|
+
const [initialEdges, setInitialEdges] = useState(edges);
|
|
32
32
|
|
|
33
33
|
// Initialize hideChannels from localStorage
|
|
34
34
|
useEffect(() => {
|
|
@@ -38,6 +38,14 @@ export const useEventCatalogVisualiser = ({
|
|
|
38
38
|
}
|
|
39
39
|
}, []);
|
|
40
40
|
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const hasChannels = nodes.some((node) => node.type === 'channels');
|
|
43
|
+
if (!hideChannels || hasChannels) {
|
|
44
|
+
setInitialNodes(nodes);
|
|
45
|
+
setInitialEdges(edges);
|
|
46
|
+
}
|
|
47
|
+
}, [nodes, edges, hideChannels]);
|
|
48
|
+
|
|
41
49
|
const toggleChannelsVisibility = useCallback(() => {
|
|
42
50
|
setHideChannels((prev) => {
|
|
43
51
|
const newValue = !prev;
|
|
@@ -50,7 +58,7 @@ export const useEventCatalogVisualiser = ({
|
|
|
50
58
|
const updatedNodes = useMemo(() => nodes.filter((node) => node.type !== 'channels'), [nodes]);
|
|
51
59
|
|
|
52
60
|
const updatedEdges = useMemo(() => {
|
|
53
|
-
|
|
61
|
+
const newEdges = edges.reduce<Edge[]>((acc, edge) => {
|
|
54
62
|
const { source, target, data } = edge;
|
|
55
63
|
const targetIsChannel = channels.some((channel) => channel.id === target);
|
|
56
64
|
const sourceIsChannel = channels.some((channel) => channel.id === source);
|
|
@@ -59,30 +67,42 @@ export const useEventCatalogVisualiser = ({
|
|
|
59
67
|
return [...acc, edge];
|
|
60
68
|
}
|
|
61
69
|
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
if (sourceIsChannel || targetIsChannel) {
|
|
71
|
+
const rootSourceAndTarget = data?.rootSourceAndTarget as {
|
|
72
|
+
source: { id: string; collection: string };
|
|
73
|
+
target: { id: string; collection: string };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (!rootSourceAndTarget) {
|
|
77
|
+
return [...acc, edge];
|
|
78
|
+
}
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
80
|
+
// is target services?
|
|
81
|
+
const targetIsService = rootSourceAndTarget?.target?.collection === 'services';
|
|
82
|
+
const edgeLabel = targetIsService
|
|
83
|
+
? getEdgeLabelForMessageAsSource(rootSourceAndTarget.source as any)
|
|
84
|
+
: getEdgeLabelForServiceAsTarget(rootSourceAndTarget.target as any);
|
|
85
|
+
|
|
86
|
+
const newEdgeId = `${rootSourceAndTarget.source.id}-${rootSourceAndTarget.target.id}`;
|
|
70
87
|
|
|
71
88
|
return [
|
|
72
89
|
...acc,
|
|
73
90
|
createEdge({
|
|
74
|
-
id:
|
|
75
|
-
source:
|
|
76
|
-
target:
|
|
91
|
+
id: newEdgeId,
|
|
92
|
+
source: rootSourceAndTarget.source.id,
|
|
93
|
+
target: rootSourceAndTarget.target.id,
|
|
77
94
|
label: edgeLabel,
|
|
78
95
|
}),
|
|
79
96
|
];
|
|
80
97
|
}
|
|
81
|
-
|
|
82
|
-
return [...acc, edge];
|
|
98
|
+
return acc;
|
|
83
99
|
}, []);
|
|
100
|
+
|
|
101
|
+
return newEdges.filter((edge, index, self) => index === self.findIndex((t) => t.id === edge.id));
|
|
84
102
|
}, [edges, channels]);
|
|
85
103
|
|
|
104
|
+
// console.log('UPDATED EDGES', JSON.stringify(updatedEdges, null, 2));
|
|
105
|
+
|
|
86
106
|
useEffect(() => {
|
|
87
107
|
// Skip processing if there are no channels to manage
|
|
88
108
|
if (skipProcessing) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getCollection } from 'astro:content';
|
|
2
2
|
import type { CollectionEntry } from 'astro:content';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { getVersionForCollectionItem, satisfies } from './collections/util';
|
|
4
|
+
import { getItemsFromCollectionByIdAndSemverOrLatest, getVersionForCollectionItem, satisfies } from './collections/util';
|
|
5
5
|
import { getMessages } from './messages';
|
|
6
6
|
import type { CollectionMessageTypes } from '@types';
|
|
7
7
|
import utils from '@eventcatalog/sdk';
|
|
@@ -92,3 +92,75 @@ export const getChannels = async ({ getAllVersions = true }: Props = {}): Promis
|
|
|
92
92
|
|
|
93
93
|
return cachedChannels[cacheKey];
|
|
94
94
|
};
|
|
95
|
+
|
|
96
|
+
// Could be recursive, we need to keep going until we find a loop or until we reach the target channel
|
|
97
|
+
export const isChannelsConnected = (
|
|
98
|
+
sourceChannel: CollectionEntry<'channels'>,
|
|
99
|
+
targetChannel: CollectionEntry<'channels'>,
|
|
100
|
+
channels: CollectionEntry<'channels'>[],
|
|
101
|
+
visited: Set<string> = new Set()
|
|
102
|
+
) => {
|
|
103
|
+
// Create a unique key for this channel (id + version to handle multiple versions)
|
|
104
|
+
const channelKey = `${sourceChannel.data.id}:${sourceChannel.data.version}`;
|
|
105
|
+
|
|
106
|
+
// Base case: we've reached the target channel
|
|
107
|
+
if (sourceChannel.data.id === targetChannel.data.id) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Prevent infinite loops by tracking visited channels
|
|
112
|
+
if (visited.has(channelKey)) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Mark this channel as visited
|
|
117
|
+
visited.add(channelKey);
|
|
118
|
+
|
|
119
|
+
const routes = sourceChannel.data.routes ?? [];
|
|
120
|
+
for (const route of routes) {
|
|
121
|
+
const routeChannel = getItemsFromCollectionByIdAndSemverOrLatest(
|
|
122
|
+
channels,
|
|
123
|
+
route.id,
|
|
124
|
+
route.version
|
|
125
|
+
)[0] as CollectionEntry<'channels'>;
|
|
126
|
+
|
|
127
|
+
if (routeChannel) {
|
|
128
|
+
// Pass the visited set to the recursive call
|
|
129
|
+
if (isChannelsConnected(routeChannel, targetChannel, channels, visited)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Go from the source to the target channel and return the channel chain
|
|
138
|
+
export const getChannelChain = (
|
|
139
|
+
sourceChannel: CollectionEntry<'channels'>,
|
|
140
|
+
targetChannel: CollectionEntry<'channels'>,
|
|
141
|
+
channels: CollectionEntry<'channels'>[]
|
|
142
|
+
): CollectionEntry<'channels'>[] => {
|
|
143
|
+
// Base case: we've reached the target channel
|
|
144
|
+
if (sourceChannel.data.id === targetChannel.data.id && sourceChannel.data.version === targetChannel.data.version) {
|
|
145
|
+
return [sourceChannel];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const routes = sourceChannel.data.routes ?? [];
|
|
149
|
+
|
|
150
|
+
if (routes.length > 0 && isChannelsConnected(sourceChannel, targetChannel, channels)) {
|
|
151
|
+
// Need to check every route and see if any of them are connected to the target channel
|
|
152
|
+
for (const route of routes) {
|
|
153
|
+
const routeChannel = getItemsFromCollectionByIdAndSemverOrLatest(
|
|
154
|
+
channels,
|
|
155
|
+
route.id,
|
|
156
|
+
route.version
|
|
157
|
+
)[0] as CollectionEntry<'channels'>;
|
|
158
|
+
if (routeChannel) {
|
|
159
|
+
if (isChannelsConnected(routeChannel, targetChannel, channels)) {
|
|
160
|
+
return [sourceChannel, ...getChannelChain(routeChannel, targetChannel, channels)];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return [];
|
|
166
|
+
};
|
|
@@ -42,6 +42,13 @@ export function sortVersioned<T>(versioned: T[], versionExtractor: (e: T) => str
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// Takes a collection and a id of a resource, and checks if the version is the latest version in the collection
|
|
46
|
+
export const getLatestVersionInCollectionById = (collection: CollectionEntry<CollectionTypes>[], id: string) => {
|
|
47
|
+
const items = collection.filter((i) => i.data.id === id);
|
|
48
|
+
const sortedVersions = sortVersioned(items, (v) => v.data.version);
|
|
49
|
+
return sortedVersions[0]?.data.version ?? id;
|
|
50
|
+
};
|
|
51
|
+
|
|
45
52
|
export const getVersionForCollectionItem = (
|
|
46
53
|
item: CollectionEntry<CollectionTypes>,
|
|
47
54
|
collection: CollectionEntry<CollectionTypes>[]
|