@eventcatalog/core 2.25.0 → 2.26.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-JENKAK6L.js → chunk-3EOBEGSB.js} +1 -1
- package/dist/{chunk-H6PSWK2Z.js → chunk-IY5HYH2G.js} +1 -1
- package/dist/{chunk-7674GE3M.js → chunk-TFBAK5C5.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +3 -3
- package/eventcatalog/src/components/Lists/OwnersList.tsx +3 -2
- package/eventcatalog/src/components/Lists/PillListFlat.tsx +6 -4
- package/eventcatalog/src/components/Lists/RepositoryList.astro +3 -2
- package/eventcatalog/src/components/Lists/VersionList.astro +3 -22
- package/eventcatalog/src/components/MDX/MessageTable/MessageTable.astro +87 -0
- package/eventcatalog/src/components/MDX/MessageTable/MessageTable.client.tsx +430 -0
- package/eventcatalog/src/components/MDX/NodeGraph/Edges/FlowEdge.tsx +96 -0
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +7 -5
- package/eventcatalog/src/components/MDX/Tiles/Tile.astro +7 -1
- package/eventcatalog/src/components/MDX/components.tsx +2 -0
- package/eventcatalog/src/components/SideBars/DomainSideBar.astro +38 -1
- package/eventcatalog/src/components/SideBars/ServiceSideBar.astro +22 -17
- package/eventcatalog/src/enterprise/ai-assistant/components/ChatSidebar.tsx +0 -1
- package/eventcatalog/src/pages/chat/index.astro +4 -1
- package/eventcatalog/src/utils/collections/domains.ts +27 -0
- package/eventcatalog/src/utils/node-graphs/flows-node-graph.ts +3 -3
- package/package.json +5 -5
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log_build_default
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-TFBAK5C5.js";
|
|
4
|
+
import "../chunk-3EOBEGSB.js";
|
|
5
|
+
import "../chunk-IY5HYH2G.js";
|
|
6
6
|
import "../chunk-E7TXTI7G.js";
|
|
7
7
|
export {
|
|
8
8
|
log_build_default as default
|
package/dist/constants.cjs
CHANGED
package/dist/constants.js
CHANGED
package/dist/eventcatalog.cjs
CHANGED
package/dist/eventcatalog.js
CHANGED
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
} from "./chunk-OW2FQPYP.js";
|
|
7
7
|
import {
|
|
8
8
|
log_build_default
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-TFBAK5C5.js";
|
|
10
|
+
import "./chunk-3EOBEGSB.js";
|
|
11
11
|
import {
|
|
12
12
|
catalogToAstro
|
|
13
13
|
} from "./chunk-VCR3LHZR.js";
|
|
14
14
|
import {
|
|
15
15
|
VERSION
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-IY5HYH2G.js";
|
|
17
17
|
import {
|
|
18
18
|
isBackstagePluginEnabled
|
|
19
19
|
} from "./chunk-XMDPVKIJ.js";
|
|
@@ -18,9 +18,9 @@ const OwnersList = ({ title, owners, emptyMessage }: Props) => {
|
|
|
18
18
|
return (
|
|
19
19
|
<div>
|
|
20
20
|
<div className="mx-auto w-full max-w-lg divide-y divide-white/5 rounded-xl bg-white/5">
|
|
21
|
-
<Disclosure as="div" className="
|
|
21
|
+
<Disclosure as="div" className="" defaultOpen={owners.length <= 5}>
|
|
22
22
|
<DisclosureButton className="group flex w-full items-center justify-start space-x-4">
|
|
23
|
-
<span className="text-sm text-black group-data-[hover]:text-black/80 capitalize"> {title} </span>
|
|
23
|
+
<span className="text-sm text-black font-semibold group-data-[hover]:text-black/80 capitalize"> {title} </span>
|
|
24
24
|
<ChevronDownIcon className="size-5 fill-black/60 group-data-[hover]:fill-black/50 group-data-[open]:rotate-180" />
|
|
25
25
|
</DisclosureButton>
|
|
26
26
|
<DisclosurePanel className="mt-2 text-sm/5 text-black/50">
|
|
@@ -57,6 +57,7 @@ const OwnersList = ({ title, owners, emptyMessage }: Props) => {
|
|
|
57
57
|
</DisclosurePanel>
|
|
58
58
|
</Disclosure>
|
|
59
59
|
</div>
|
|
60
|
+
<div className="border-b border-gray-100 my-4"></div>
|
|
60
61
|
</div>
|
|
61
62
|
);
|
|
62
63
|
};
|
|
@@ -9,6 +9,7 @@ interface Props {
|
|
|
9
9
|
title: string;
|
|
10
10
|
color: string;
|
|
11
11
|
icon?: any;
|
|
12
|
+
limit?: number;
|
|
12
13
|
pills: {
|
|
13
14
|
label: string;
|
|
14
15
|
badge?: string;
|
|
@@ -22,14 +23,14 @@ interface Props {
|
|
|
22
23
|
emptyMessage?: string;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
const PillList = ({ title, pills, emptyMessage, color = 'gray', ...props }: Props) => {
|
|
26
|
+
const PillList = ({ title, pills, emptyMessage, color = 'gray', limit = 10, ...props }: Props) => {
|
|
26
27
|
const getIconForCollection = useMemo(() => getIconForCollectionOriginal, []);
|
|
27
28
|
return (
|
|
28
|
-
<div>
|
|
29
|
+
<div className="">
|
|
29
30
|
<div className="mx-auto w-full max-w-lg divide-y divide-white/5 rounded-xl bg-white/5">
|
|
30
|
-
<Disclosure as="div" className="
|
|
31
|
+
<Disclosure as="div" className="" defaultOpen={pills.length <= limit}>
|
|
31
32
|
<DisclosureButton className="group flex w-full items-center justify-start space-x-4">
|
|
32
|
-
<span className="text-sm text-black group-data-[hover]:text-black/80 capitalize"> {title} </span>
|
|
33
|
+
<span className="text-sm text-black font-semibold group-data-[hover]:text-black/80 capitalize"> {title} </span>
|
|
33
34
|
<ChevronDownIcon className="size-5 ml-2 fill-black/60 group-data-[hover]:fill-black/50 group-data-[open]:rotate-180" />
|
|
34
35
|
</DisclosureButton>
|
|
35
36
|
<DisclosurePanel className="mt-2 text-sm/5 text-black/50">
|
|
@@ -69,6 +70,7 @@ const PillList = ({ title, pills, emptyMessage, color = 'gray', ...props }: Prop
|
|
|
69
70
|
</DisclosurePanel>
|
|
70
71
|
</Disclosure>
|
|
71
72
|
</div>
|
|
73
|
+
<div className="border-b border-gray-100 my-4"></div>
|
|
72
74
|
</div>
|
|
73
75
|
);
|
|
74
76
|
};
|
|
@@ -9,8 +9,8 @@ interface Props {
|
|
|
9
9
|
const { repository, language } = Astro.props;
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
<div class="mx-auto pb-
|
|
13
|
-
<span class="text-sm text-black group-data-[hover]:text-black/80 capitalize">Repository </span>
|
|
12
|
+
<div class="mx-auto pb-4 w-full max-w-lg divide-y divide-white/5 rounded-xl bg-white/5 border-b border-gray-100 mb-4">
|
|
13
|
+
<span class="text-sm text-black font-semibold group-data-[hover]:text-black/80 capitalize">Repository </span>
|
|
14
14
|
<ul role="list" class="space-y-2 mt-2">
|
|
15
15
|
{
|
|
16
16
|
repository && (
|
|
@@ -33,4 +33,5 @@ const { repository, language } = Astro.props;
|
|
|
33
33
|
)
|
|
34
34
|
}
|
|
35
35
|
</ul>
|
|
36
|
+
<!-- <div class='border-b border-gray-100'></div> -->
|
|
36
37
|
</div>
|
|
@@ -14,8 +14,8 @@ const { versions, collectionItem, title } = Astro.props;
|
|
|
14
14
|
const currentPath = Astro.url.pathname;
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
<div class="space-y-2 pb-
|
|
18
|
-
<span class="text-sm text-black group-data-[hover]:text-black/80 capitalize">
|
|
17
|
+
<div class="space-y-2 pb-4">
|
|
18
|
+
<span class="text-sm text-black font-semibold group-data-[hover]:text-black/80 capitalize">
|
|
19
19
|
{title || `Versions (${collectionItem.data.versions?.length})`}
|
|
20
20
|
</span>
|
|
21
21
|
<ul role="list" class="space-y-2">
|
|
@@ -39,26 +39,7 @@ const currentPath = Astro.url.pathname;
|
|
|
39
39
|
})
|
|
40
40
|
}
|
|
41
41
|
</ul>
|
|
42
|
-
|
|
43
|
-
{
|
|
44
|
-
versions.map((version) => {
|
|
45
|
-
const isCurrent = currentPath.includes(version);
|
|
46
|
-
return (
|
|
47
|
-
<option
|
|
48
|
-
selected={isCurrent}
|
|
49
|
-
value={buildUrl(`/docs/${collectionItem.collection}/${collectionItem.data.id}/${version}`)}
|
|
50
|
-
class={`inline-flex items-center rounded-md px-2 py-1 text-xs text-indigo-700 ring-1 ring-inset ring-indigo-700/10 hover:bg-purple-100 hover:underline ${isCurrent ? 'bg-purple-100 text-primary underline ' : 'bg-white'}`}
|
|
51
|
-
>
|
|
52
|
-
{version === collectionItem.data.latestVersion ? `v${version} (latest)` : `v${version}`}
|
|
53
|
-
</option>
|
|
54
|
-
);
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
</select> -->
|
|
58
|
-
<!-- <a
|
|
59
|
-
href={buildUrl(`/docs/${collectionItem.collection}/${collectionItem.data.id}/${collectionItem.data.latestVersion}/changelog`)}
|
|
60
|
-
class="text-[10px] text-gray-500">View changelogs</a
|
|
61
|
-
> -->
|
|
42
|
+
<div class="border-b border-gray-100 pt-2"></div>
|
|
62
43
|
</div>
|
|
63
44
|
|
|
64
45
|
<script>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { CollectionEntry } from 'astro:content';
|
|
3
|
+
import MessageTableClient from './MessageTable.client';
|
|
4
|
+
import Admonition from '../Admonition';
|
|
5
|
+
import { getMessagesForDomain } from '@utils/collections/domains';
|
|
6
|
+
import type { Domain } from '@utils/collections/domains';
|
|
7
|
+
import type { CollectionMessageTypes } from '@types';
|
|
8
|
+
export interface Props extends CollectionEntry<'services'> {
|
|
9
|
+
format: 'receives' | 'sends' | 'all';
|
|
10
|
+
limit?: number;
|
|
11
|
+
showChannels?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { format, limit, showChannels } = Astro.props;
|
|
15
|
+
|
|
16
|
+
const collection = Astro.props.collection as 'services' | 'domains';
|
|
17
|
+
|
|
18
|
+
// UI does not need everything, just return what is needed for the table
|
|
19
|
+
const getEssentialPropsForMessage = (message: CollectionEntry<CollectionMessageTypes>) => {
|
|
20
|
+
return {
|
|
21
|
+
id: message.data.id,
|
|
22
|
+
name: message.data.name,
|
|
23
|
+
version: message.data.version,
|
|
24
|
+
collection: message.collection,
|
|
25
|
+
type: message.collection === 'events' ? 'event' : message.collection === 'commands' ? 'command' : 'query',
|
|
26
|
+
summary: message.data.summary,
|
|
27
|
+
channels: message.data.channels,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// only enable for services and domains
|
|
32
|
+
const isComponentEnabled = collection === 'services' || collection === 'domains';
|
|
33
|
+
|
|
34
|
+
let data = {
|
|
35
|
+
sends: [],
|
|
36
|
+
receives: [],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
if (collection === 'domains') {
|
|
40
|
+
const { sends, receives } = await getMessagesForDomain(Astro.props as unknown as Domain);
|
|
41
|
+
data = {
|
|
42
|
+
sends: sends.map(getEssentialPropsForMessage) as typeof data.sends,
|
|
43
|
+
receives: receives.map(getEssentialPropsForMessage) as typeof data.receives,
|
|
44
|
+
};
|
|
45
|
+
} else {
|
|
46
|
+
// Try and set the sends and receives from the services collection
|
|
47
|
+
data = {
|
|
48
|
+
sends: Astro.props.data.sends
|
|
49
|
+
? (Astro.props.data.sends.map((message) =>
|
|
50
|
+
getEssentialPropsForMessage(message as unknown as CollectionEntry<CollectionMessageTypes>)
|
|
51
|
+
) as typeof data.sends)
|
|
52
|
+
: [],
|
|
53
|
+
receives: Astro.props.data.receives
|
|
54
|
+
? (Astro.props.data.receives.map((message) =>
|
|
55
|
+
getEssentialPropsForMessage(message as unknown as CollectionEntry<CollectionMessageTypes>)
|
|
56
|
+
) as typeof data.receives)
|
|
57
|
+
: [],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
{
|
|
63
|
+
isComponentEnabled && (
|
|
64
|
+
<MessageTableClient
|
|
65
|
+
client:load
|
|
66
|
+
sends={data.sends}
|
|
67
|
+
receives={data.receives}
|
|
68
|
+
collection={collection}
|
|
69
|
+
limit={limit}
|
|
70
|
+
showChannels={showChannels}
|
|
71
|
+
format={format}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
{
|
|
77
|
+
!isComponentEnabled && (
|
|
78
|
+
<Admonition type="warning">
|
|
79
|
+
<div>
|
|
80
|
+
<span class="font-bold">
|
|
81
|
+
{`<MessageTable/>`} component is not supported for resources of type {collection}.
|
|
82
|
+
</span>
|
|
83
|
+
<span class="block">This component is only supported for services and domains.</span>
|
|
84
|
+
</div>
|
|
85
|
+
</Admonition>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { getColorAndIconForMessageType } from '@components/Tables/columns/MessageTableColumns';
|
|
2
|
+
import { buildUrl } from '@utils/url-builder';
|
|
3
|
+
import { useState, useMemo, useCallback, memo } from 'react';
|
|
4
|
+
|
|
5
|
+
type MessageTableMessage = {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
version: string;
|
|
9
|
+
collection: string;
|
|
10
|
+
type: string;
|
|
11
|
+
summary: string;
|
|
12
|
+
channels: any[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type MessageTableProps = {
|
|
16
|
+
format: 'receives' | 'sends' | 'all';
|
|
17
|
+
limit?: number;
|
|
18
|
+
showChannels?: boolean;
|
|
19
|
+
collection: string;
|
|
20
|
+
sends: MessageTableMessage[];
|
|
21
|
+
receives: MessageTableMessage[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type MessageType = 'event' | 'query' | 'command' | null;
|
|
25
|
+
|
|
26
|
+
const MessageRow = memo(
|
|
27
|
+
({ message, showChannels, collection }: { message: MessageTableMessage; showChannels?: boolean; collection: string }) => {
|
|
28
|
+
const { color, Icon } = getColorAndIconForMessageType(message.type);
|
|
29
|
+
const url = buildUrl(`/docs/${collection}/${message.id}/${message.version}`);
|
|
30
|
+
let type = collection.slice(0, -1);
|
|
31
|
+
type = type === 'querie' ? 'query' : type;
|
|
32
|
+
|
|
33
|
+
const channels = message.channels || [];
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<tr className="group hover:bg-gray-100">
|
|
37
|
+
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 relative">
|
|
38
|
+
<a href={url} className="absolute inset-0 z-10" aria-label={`View details for ${message.name}`} />
|
|
39
|
+
<div className="flex items-center gap-2 relative">
|
|
40
|
+
<Icon className={`h-5 w-5 text-${color}-500`} />
|
|
41
|
+
<span className="group-hover:text-blue-600 break-all">{message.name}</span>
|
|
42
|
+
</div>
|
|
43
|
+
</td>
|
|
44
|
+
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 relative">
|
|
45
|
+
<a href={url} className="absolute inset-0 z-10" aria-hidden="true" />
|
|
46
|
+
<span>v{message.version}</span>
|
|
47
|
+
</td>
|
|
48
|
+
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 relative">
|
|
49
|
+
<a href={url} className="absolute inset-0 z-10" aria-hidden="true" />
|
|
50
|
+
<span>{type}</span>
|
|
51
|
+
</td>
|
|
52
|
+
<td className="px-3 py-4 text-sm text-gray-500 relative">
|
|
53
|
+
<a href={url} className="absolute inset-0 z-10" aria-hidden="true" />
|
|
54
|
+
<span className="line-clamp-2 break-words">{message.summary || '-'}</span>
|
|
55
|
+
</td>
|
|
56
|
+
{showChannels && (
|
|
57
|
+
<td className="px-3 py-4 text-sm text-gray-500 relative">
|
|
58
|
+
<a href={url} className="absolute inset-0 z-10" aria-hidden="true" />
|
|
59
|
+
<div className="flex flex-wrap gap-1">
|
|
60
|
+
{channels.length > 0
|
|
61
|
+
? channels.map((channel, index) => (
|
|
62
|
+
<span
|
|
63
|
+
key={`${channel.id}-${index}`}
|
|
64
|
+
className="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10"
|
|
65
|
+
>
|
|
66
|
+
{channel.id}
|
|
67
|
+
</span>
|
|
68
|
+
))
|
|
69
|
+
: '-'}
|
|
70
|
+
</div>
|
|
71
|
+
</td>
|
|
72
|
+
)}
|
|
73
|
+
</tr>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const FilterButton = memo(
|
|
79
|
+
({
|
|
80
|
+
type,
|
|
81
|
+
label,
|
|
82
|
+
typeFilter,
|
|
83
|
+
setTypeFilter,
|
|
84
|
+
setCurrentPage,
|
|
85
|
+
count,
|
|
86
|
+
}: {
|
|
87
|
+
type: MessageType;
|
|
88
|
+
label: string;
|
|
89
|
+
typeFilter: MessageType;
|
|
90
|
+
setTypeFilter: (type: MessageType) => void;
|
|
91
|
+
setCurrentPage: (page: number) => void;
|
|
92
|
+
count: number;
|
|
93
|
+
}) => (
|
|
94
|
+
<button
|
|
95
|
+
onClick={() => {
|
|
96
|
+
setTypeFilter(typeFilter === type ? null : type);
|
|
97
|
+
setCurrentPage(1);
|
|
98
|
+
}}
|
|
99
|
+
className={`px-3 py-1 rounded-md text-sm font-medium ${
|
|
100
|
+
typeFilter === type
|
|
101
|
+
? 'bg-black text-white border border-gray-200 hover:bg-gray-900'
|
|
102
|
+
: 'bg-white text-black border border-gray-200 hover:bg-gray-100'
|
|
103
|
+
}`}
|
|
104
|
+
>
|
|
105
|
+
{label} ({count})
|
|
106
|
+
</button>
|
|
107
|
+
)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const MessageTable = (props: MessageTableProps) => {
|
|
111
|
+
const { receives, sends, collection = 'services', limit, showChannels = false, format = 'all' } = props;
|
|
112
|
+
const [receivesSearchTerm, setReceivesSearchTerm] = useState('');
|
|
113
|
+
const [sendsSearchTerm, setSendsSearchTerm] = useState('');
|
|
114
|
+
const [receivesPage, setReceivesPage] = useState(1);
|
|
115
|
+
const [sendsPage, setSendsPage] = useState(1);
|
|
116
|
+
const [receivesTypeFilter, setReceivesTypeFilter] = useState<MessageType>(null);
|
|
117
|
+
const [sendsTypeFilter, setSendsTypeFilter] = useState<MessageType>(null);
|
|
118
|
+
const itemsPerPage = limit || 5;
|
|
119
|
+
|
|
120
|
+
const shouldRenderReceives = format === 'receives' || format === 'all';
|
|
121
|
+
const shouldRenderSends = format === 'sends' || format === 'all';
|
|
122
|
+
|
|
123
|
+
const filterMessages = useCallback((messages: MessageTableMessage[], searchTerm: string, typeFilter: MessageType) => {
|
|
124
|
+
let filtered = messages;
|
|
125
|
+
|
|
126
|
+
if (typeFilter) {
|
|
127
|
+
filtered = filtered.filter((message) => {
|
|
128
|
+
const collectionType = message.collection.slice(0, -1);
|
|
129
|
+
const normalizedType = collectionType === 'querie' ? 'query' : collectionType;
|
|
130
|
+
return normalizedType === typeFilter;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (searchTerm) {
|
|
135
|
+
const lowerSearchTerm = searchTerm.toLowerCase();
|
|
136
|
+
filtered = filtered.filter((message) => {
|
|
137
|
+
const collectionType = message.collection.slice(0, -1);
|
|
138
|
+
const normalizedType = collectionType === 'querie' ? 'query' : collectionType;
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
message.name.toLowerCase().includes(lowerSearchTerm) ||
|
|
142
|
+
message.summary?.toLowerCase().includes(lowerSearchTerm) ||
|
|
143
|
+
normalizedType.toLowerCase().includes(lowerSearchTerm)
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return filtered;
|
|
149
|
+
}, []);
|
|
150
|
+
|
|
151
|
+
const renderTable = (
|
|
152
|
+
title: string,
|
|
153
|
+
messages: any[],
|
|
154
|
+
searchTerm: string,
|
|
155
|
+
setSearchTerm: (value: string) => void,
|
|
156
|
+
currentPage: number,
|
|
157
|
+
setCurrentPage: (page: number) => void,
|
|
158
|
+
typeFilter: MessageType,
|
|
159
|
+
setTypeFilter: (type: MessageType) => void
|
|
160
|
+
) => {
|
|
161
|
+
const filteredMessages = useMemo(
|
|
162
|
+
() => filterMessages(messages, searchTerm, typeFilter),
|
|
163
|
+
[messages, searchTerm, typeFilter, filterMessages]
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const totalPages = Math.ceil(filteredMessages.length / itemsPerPage);
|
|
167
|
+
const startIndex = (currentPage - 1) * itemsPerPage;
|
|
168
|
+
const paginatedMessages = useMemo(
|
|
169
|
+
() => filteredMessages.slice(startIndex, startIndex + itemsPerPage),
|
|
170
|
+
[filteredMessages, startIndex, itemsPerPage]
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Get unique message types and their counts
|
|
174
|
+
const messageTypeCounts = useMemo(() => {
|
|
175
|
+
const counts = new Map<MessageType, number>();
|
|
176
|
+
messages.forEach((message) => {
|
|
177
|
+
const collectionType = message.collection.slice(0, -1);
|
|
178
|
+
const normalizedType = (collectionType === 'querie' ? 'query' : collectionType) as MessageType;
|
|
179
|
+
counts.set(normalizedType, (counts.get(normalizedType) || 0) + 1);
|
|
180
|
+
});
|
|
181
|
+
return counts;
|
|
182
|
+
}, [messages]);
|
|
183
|
+
|
|
184
|
+
const availableTypes = useMemo(
|
|
185
|
+
() =>
|
|
186
|
+
Array.from(
|
|
187
|
+
new Set(
|
|
188
|
+
messages.map((message) => {
|
|
189
|
+
const collectionType = message.collection.slice(0, -1);
|
|
190
|
+
return collectionType === 'querie' ? 'query' : collectionType;
|
|
191
|
+
})
|
|
192
|
+
)
|
|
193
|
+
) as MessageType[],
|
|
194
|
+
[messages]
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const filterButtons = useMemo(
|
|
198
|
+
() =>
|
|
199
|
+
[
|
|
200
|
+
{ type: 'event' as MessageType, label: 'Events' },
|
|
201
|
+
{ type: 'query' as MessageType, label: 'Queries' },
|
|
202
|
+
{ type: 'command' as MessageType, label: 'Commands' },
|
|
203
|
+
]
|
|
204
|
+
.filter((button) => availableTypes.includes(button.type))
|
|
205
|
+
.map((button) => ({
|
|
206
|
+
...button,
|
|
207
|
+
count: messageTypeCounts.get(button.type) || 0,
|
|
208
|
+
})),
|
|
209
|
+
[availableTypes, messageTypeCounts]
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<div className="flow-root bg-white border-gray-200 border p-4 pb-2 rounded-lg text-gray-900">
|
|
214
|
+
<div className="space-y-4">
|
|
215
|
+
<h2 className="text-xl font-semibold">
|
|
216
|
+
{title} ({searchTerm || typeFilter ? `${filteredMessages.length}/${messages.length}` : messages.length})
|
|
217
|
+
</h2>
|
|
218
|
+
<span className="text-sm text-gray-700">
|
|
219
|
+
Quickly find the message you need by searching for the name, type, or summary.
|
|
220
|
+
</span>
|
|
221
|
+
|
|
222
|
+
{/* Type filter buttons - only shown if there are filter options */}
|
|
223
|
+
{filterButtons.length > 0 && (
|
|
224
|
+
<div className="flex gap-2 pb-2">
|
|
225
|
+
{filterButtons.map((button) => (
|
|
226
|
+
<FilterButton
|
|
227
|
+
key={button.type}
|
|
228
|
+
type={button.type}
|
|
229
|
+
label={button.label}
|
|
230
|
+
count={button.count}
|
|
231
|
+
typeFilter={typeFilter}
|
|
232
|
+
setTypeFilter={setTypeFilter}
|
|
233
|
+
setCurrentPage={setCurrentPage}
|
|
234
|
+
/>
|
|
235
|
+
))}
|
|
236
|
+
</div>
|
|
237
|
+
)}
|
|
238
|
+
|
|
239
|
+
<div className="relative">
|
|
240
|
+
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
|
241
|
+
<svg className="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
242
|
+
<path
|
|
243
|
+
fillRule="evenodd"
|
|
244
|
+
d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
|
|
245
|
+
clipRule="evenodd"
|
|
246
|
+
/>
|
|
247
|
+
</svg>
|
|
248
|
+
</div>
|
|
249
|
+
<input
|
|
250
|
+
type="text"
|
|
251
|
+
value={searchTerm}
|
|
252
|
+
onChange={(e) => {
|
|
253
|
+
setSearchTerm(e.target.value);
|
|
254
|
+
setCurrentPage(1); // Reset to first page when searching
|
|
255
|
+
}}
|
|
256
|
+
placeholder={`Search by name, type, or summary...`}
|
|
257
|
+
className="block w-full rounded-md border-0 py-1.5 pl-10 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
|
|
258
|
+
/>
|
|
259
|
+
{searchTerm && (
|
|
260
|
+
<button
|
|
261
|
+
onClick={() => {
|
|
262
|
+
setSearchTerm('');
|
|
263
|
+
setCurrentPage(1); // Reset to first page when clearing search
|
|
264
|
+
}}
|
|
265
|
+
className="absolute inset-y-0 right-0 flex items-center pr-3"
|
|
266
|
+
aria-label="Clear search"
|
|
267
|
+
>
|
|
268
|
+
<svg className="h-5 w-5 text-gray-400 hover:text-gray-500" viewBox="0 0 20 20" fill="currentColor">
|
|
269
|
+
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
|
|
270
|
+
</svg>
|
|
271
|
+
</button>
|
|
272
|
+
)}
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
<div className="overflow-x-auto">
|
|
276
|
+
<div className="inline-block min-w-full py-2 align-middle">
|
|
277
|
+
<div className="max-w-full overflow-hidden">
|
|
278
|
+
<table className="min-w-full table-fixed divide-y divide-gray-300 rounded-sm bg-white ">
|
|
279
|
+
<thead>
|
|
280
|
+
<tr>
|
|
281
|
+
<th
|
|
282
|
+
scope="col"
|
|
283
|
+
className={`${showChannels ? 'w-1/4' : 'w-1/3'} py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6`}
|
|
284
|
+
>
|
|
285
|
+
Name
|
|
286
|
+
</th>
|
|
287
|
+
<th scope="col" className="w-[100px] px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
|
288
|
+
Version
|
|
289
|
+
</th>
|
|
290
|
+
<th scope="col" className="w-[100px] py-3.5 pl-3.5 pr-3 text-left text-sm font-semibold text-gray-900">
|
|
291
|
+
Type
|
|
292
|
+
</th>
|
|
293
|
+
<th
|
|
294
|
+
scope="col"
|
|
295
|
+
className={`${showChannels ? 'w-1/3' : 'w-1/2'} px-3 py-3.5 text-left text-sm font-semibold text-gray-900`}
|
|
296
|
+
>
|
|
297
|
+
Summary
|
|
298
|
+
</th>
|
|
299
|
+
{showChannels && (
|
|
300
|
+
<th scope="col" className="w-1/4 px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
|
301
|
+
Channels
|
|
302
|
+
</th>
|
|
303
|
+
)}
|
|
304
|
+
</tr>
|
|
305
|
+
</thead>
|
|
306
|
+
<tbody className="divide-y divide-gray-200">
|
|
307
|
+
{paginatedMessages.length > 0 ? (
|
|
308
|
+
paginatedMessages.map((message) => (
|
|
309
|
+
<MessageRow
|
|
310
|
+
key={message.id}
|
|
311
|
+
message={message}
|
|
312
|
+
showChannels={showChannels}
|
|
313
|
+
collection={message.collection}
|
|
314
|
+
/>
|
|
315
|
+
))
|
|
316
|
+
) : (
|
|
317
|
+
<tr>
|
|
318
|
+
<td colSpan={showChannels ? 5 : 4} className="text-center py-4 text-sm text-gray-500">
|
|
319
|
+
No messages found
|
|
320
|
+
</td>
|
|
321
|
+
</tr>
|
|
322
|
+
)}
|
|
323
|
+
</tbody>
|
|
324
|
+
</table>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
{totalPages > 1 && (
|
|
329
|
+
<div className="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6 -mt-2">
|
|
330
|
+
<div className="flex flex-1 justify-between sm:hidden">
|
|
331
|
+
<button
|
|
332
|
+
onClick={() => setCurrentPage(currentPage - 1)}
|
|
333
|
+
disabled={currentPage === 1}
|
|
334
|
+
className={`relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium ${currentPage === 1 ? 'text-gray-300' : 'text-gray-700 hover:bg-gray-50'}`}
|
|
335
|
+
>
|
|
336
|
+
Previous
|
|
337
|
+
</button>
|
|
338
|
+
<button
|
|
339
|
+
onClick={() => setCurrentPage(currentPage + 1)}
|
|
340
|
+
disabled={currentPage === totalPages}
|
|
341
|
+
className={`relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium ${currentPage === totalPages ? 'text-gray-300' : 'text-gray-700 hover:bg-gray-50'}`}
|
|
342
|
+
>
|
|
343
|
+
Next
|
|
344
|
+
</button>
|
|
345
|
+
</div>
|
|
346
|
+
<div className="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
|
|
347
|
+
<div>
|
|
348
|
+
<p className="text-sm text-gray-700">
|
|
349
|
+
Showing <span className="font-medium">{startIndex + 1}</span> to{' '}
|
|
350
|
+
<span className="font-medium">{Math.min(startIndex + itemsPerPage, filteredMessages.length)}</span> of{' '}
|
|
351
|
+
<span className="font-medium">{filteredMessages.length}</span> results
|
|
352
|
+
</p>
|
|
353
|
+
</div>
|
|
354
|
+
<div>
|
|
355
|
+
<nav className="isolate inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination">
|
|
356
|
+
<button
|
|
357
|
+
onClick={() => setCurrentPage(currentPage - 1)}
|
|
358
|
+
disabled={currentPage === 1}
|
|
359
|
+
className={`relative inline-flex items-center rounded-l-md px-2 py-2 ${currentPage === 1 ? 'text-gray-300' : 'text-gray-400 hover:bg-gray-50'} ring-1 ring-inset ring-gray-300`}
|
|
360
|
+
>
|
|
361
|
+
<span className="sr-only">Previous</span>
|
|
362
|
+
<svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
363
|
+
<path
|
|
364
|
+
fillRule="evenodd"
|
|
365
|
+
d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z"
|
|
366
|
+
clipRule="evenodd"
|
|
367
|
+
/>
|
|
368
|
+
</svg>
|
|
369
|
+
</button>
|
|
370
|
+
<button
|
|
371
|
+
onClick={() => setCurrentPage(currentPage + 1)}
|
|
372
|
+
disabled={currentPage === totalPages}
|
|
373
|
+
className={`relative inline-flex items-center rounded-r-md px-2 py-2 ${currentPage === totalPages ? 'text-gray-300' : 'text-gray-400 hover:bg-gray-50'} ring-1 ring-inset ring-gray-300`}
|
|
374
|
+
>
|
|
375
|
+
<span className="sr-only">Next</span>
|
|
376
|
+
<svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
377
|
+
<path
|
|
378
|
+
fillRule="evenodd"
|
|
379
|
+
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
|
|
380
|
+
clipRule="evenodd"
|
|
381
|
+
/>
|
|
382
|
+
</svg>
|
|
383
|
+
</button>
|
|
384
|
+
</nav>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
)}
|
|
389
|
+
</div>
|
|
390
|
+
);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
<div className={`mx-auto not-prose py-4 space-y-4 my-4`}>
|
|
395
|
+
<h2 className="text-2xl font-semibold">Messages for this {collection.slice(0, -1)}</h2>
|
|
396
|
+
<div>
|
|
397
|
+
{shouldRenderSends && (
|
|
398
|
+
<div>
|
|
399
|
+
{renderTable(
|
|
400
|
+
'Sends messages',
|
|
401
|
+
sends || [],
|
|
402
|
+
sendsSearchTerm,
|
|
403
|
+
setSendsSearchTerm,
|
|
404
|
+
sendsPage,
|
|
405
|
+
setSendsPage,
|
|
406
|
+
sendsTypeFilter,
|
|
407
|
+
setSendsTypeFilter
|
|
408
|
+
)}
|
|
409
|
+
</div>
|
|
410
|
+
)}
|
|
411
|
+
{shouldRenderReceives && (
|
|
412
|
+
<div className={format === 'all' ? 'pt-4' : ''}>
|
|
413
|
+
{renderTable(
|
|
414
|
+
'Receives messages',
|
|
415
|
+
receives || [],
|
|
416
|
+
receivesSearchTerm,
|
|
417
|
+
setReceivesSearchTerm,
|
|
418
|
+
receivesPage,
|
|
419
|
+
setReceivesPage,
|
|
420
|
+
receivesTypeFilter,
|
|
421
|
+
setReceivesTypeFilter
|
|
422
|
+
)}
|
|
423
|
+
</div>
|
|
424
|
+
)}
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
);
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
export default MessageTable;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { BaseEdge, EdgeLabelRenderer, getBezierPath, type EdgeProps as XYFlowEdgeProps } from '@xyflow/react';
|
|
3
|
+
|
|
4
|
+
interface EdgeData {
|
|
5
|
+
message?: {
|
|
6
|
+
collection?: string;
|
|
7
|
+
};
|
|
8
|
+
opacity?: number;
|
|
9
|
+
animated?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface CustomEdgeProps extends Omit<XYFlowEdgeProps, 'data'> {
|
|
13
|
+
data?: EdgeData;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function CustomEdge({
|
|
17
|
+
id,
|
|
18
|
+
sourceX,
|
|
19
|
+
sourceY,
|
|
20
|
+
targetX,
|
|
21
|
+
targetY,
|
|
22
|
+
sourcePosition,
|
|
23
|
+
targetPosition,
|
|
24
|
+
style = {},
|
|
25
|
+
markerEnd,
|
|
26
|
+
label,
|
|
27
|
+
labelStyle,
|
|
28
|
+
data,
|
|
29
|
+
}: CustomEdgeProps) {
|
|
30
|
+
const [edgePath, labelX, labelY] = getBezierPath({
|
|
31
|
+
sourceX,
|
|
32
|
+
sourceY,
|
|
33
|
+
sourcePosition,
|
|
34
|
+
targetX,
|
|
35
|
+
targetY,
|
|
36
|
+
targetPosition,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const randomDelay = useMemo(() => Math.random() * 1, []);
|
|
40
|
+
const collection = data?.message?.collection;
|
|
41
|
+
const opacity = data?.opacity ?? 1;
|
|
42
|
+
|
|
43
|
+
const messageColor = useMemo(
|
|
44
|
+
() => (collection: string) => {
|
|
45
|
+
switch (collection) {
|
|
46
|
+
case 'events':
|
|
47
|
+
return 'orange';
|
|
48
|
+
case 'commands':
|
|
49
|
+
return 'blue';
|
|
50
|
+
case 'queries':
|
|
51
|
+
return 'green';
|
|
52
|
+
default:
|
|
53
|
+
return 'gray';
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
[]
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<BaseEdge
|
|
62
|
+
path={edgePath}
|
|
63
|
+
markerEnd={markerEnd}
|
|
64
|
+
style={{
|
|
65
|
+
strokeWidth: 2,
|
|
66
|
+
stroke: '#fff',
|
|
67
|
+
...style,
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
{data?.animated && (
|
|
71
|
+
<g className={`z-30 ${opacity === 1 ? 'opacity-100' : 'opacity-10'}`}>
|
|
72
|
+
<circle cx="0" cy="0" r="7" fill={messageColor(collection || 'default')}>
|
|
73
|
+
<animateMotion dur="2s" repeatCount="indefinite" path={edgePath} rotate="auto" begin={`${randomDelay}s`}>
|
|
74
|
+
<mpath href={`#${id}`} />
|
|
75
|
+
</animateMotion>
|
|
76
|
+
</circle>
|
|
77
|
+
</g>
|
|
78
|
+
)}
|
|
79
|
+
{label && (
|
|
80
|
+
<EdgeLabelRenderer>
|
|
81
|
+
<div
|
|
82
|
+
style={{
|
|
83
|
+
position: 'absolute',
|
|
84
|
+
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
85
|
+
zIndex: 1000,
|
|
86
|
+
...labelStyle,
|
|
87
|
+
}}
|
|
88
|
+
className="nodrag nopan max-w-[150px] text-xs bg-white px-2 py-1 rounded border border-gray-200 text-gray-600 font-medium shadow-sm"
|
|
89
|
+
>
|
|
90
|
+
{label}
|
|
91
|
+
</div>
|
|
92
|
+
</EdgeLabelRenderer>
|
|
93
|
+
)}
|
|
94
|
+
</>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -24,6 +24,7 @@ import StepNode from './Nodes/Step';
|
|
|
24
24
|
import CommandNode from './Nodes/Command';
|
|
25
25
|
import ExternalSystemNode from './Nodes/ExternalSystem';
|
|
26
26
|
import AnimatedMessageEdge from './Edges/AnimatedMessageEdge';
|
|
27
|
+
import FlowEdge from './Edges/FlowEdge';
|
|
27
28
|
|
|
28
29
|
import type { CollectionEntry } from 'astro:content';
|
|
29
30
|
import { navigate } from 'astro:transitions/client';
|
|
@@ -75,6 +76,7 @@ const NodeGraphBuilder = ({
|
|
|
75
76
|
const edgeTypes = useMemo(
|
|
76
77
|
() => ({
|
|
77
78
|
animated: AnimatedMessageEdge,
|
|
79
|
+
'flow-edge': FlowEdge,
|
|
78
80
|
}),
|
|
79
81
|
[]
|
|
80
82
|
);
|
|
@@ -97,7 +99,7 @@ const NodeGraphBuilder = ({
|
|
|
97
99
|
eds.map((edge) => {
|
|
98
100
|
edge.style = { ...edge.style, opacity: 1 };
|
|
99
101
|
edge.labelStyle = { ...edge.labelStyle, opacity: 1 };
|
|
100
|
-
return { ...edge, data: { ...edge.data, opacity: 1 }, animated: animateMessages };
|
|
102
|
+
return { ...edge, data: { ...edge.data, opacity: 1, animated: animateMessages }, animated: animateMessages };
|
|
101
103
|
})
|
|
102
104
|
);
|
|
103
105
|
}, [setNodes, setEdges, animateMessages]);
|
|
@@ -125,7 +127,7 @@ const NodeGraphBuilder = ({
|
|
|
125
127
|
connectedNodeIds.add(edge.target);
|
|
126
128
|
return {
|
|
127
129
|
...edge,
|
|
128
|
-
data: { ...edge.data, opacity: 1 },
|
|
130
|
+
data: { ...edge.data, opacity: 1, animated: animateMessages },
|
|
129
131
|
style: { ...edge.style, opacity: 1 },
|
|
130
132
|
labelStyle: { ...edge.labelStyle, opacity: 1 },
|
|
131
133
|
animated: true,
|
|
@@ -133,7 +135,7 @@ const NodeGraphBuilder = ({
|
|
|
133
135
|
}
|
|
134
136
|
return {
|
|
135
137
|
...edge,
|
|
136
|
-
data: { ...edge.data, opacity: 0.1 },
|
|
138
|
+
data: { ...edge.data, opacity: 0.1, animated: animateMessages },
|
|
137
139
|
style: { ...edge.style, opacity: 0.1 },
|
|
138
140
|
labelStyle: { ...edge.labelStyle, opacity: 0.1 },
|
|
139
141
|
animated: animateMessages,
|
|
@@ -178,8 +180,8 @@ const NodeGraphBuilder = ({
|
|
|
178
180
|
eds.map((edge) => ({
|
|
179
181
|
...edge,
|
|
180
182
|
animated: animateMessages,
|
|
181
|
-
type: animateMessages ? 'animated' : 'default',
|
|
182
|
-
data: { ...edge.data, animateMessages },
|
|
183
|
+
type: edge.type === 'flow-edge' ? 'flow-edge' : animateMessages ? 'animated' : 'default',
|
|
184
|
+
data: { ...edge.data, animateMessages, animated: animateMessages },
|
|
183
185
|
}))
|
|
184
186
|
);
|
|
185
187
|
}, [animateMessages]);
|
|
@@ -14,10 +14,16 @@ interface Props {
|
|
|
14
14
|
const { href, icon, title, description, openWindow } = Astro.props;
|
|
15
15
|
|
|
16
16
|
const IconComponent: ComponentType<{ className?: string }> | undefined = Icons[icon];
|
|
17
|
+
|
|
18
|
+
function startsWithProtocol(str: string) {
|
|
19
|
+
// Regular expression to match common protocols (http, https, ftp, ws, etc.)
|
|
20
|
+
const regex = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//;
|
|
21
|
+
return regex.test(str);
|
|
22
|
+
}
|
|
17
23
|
---
|
|
18
24
|
|
|
19
25
|
<a
|
|
20
|
-
href={buildUrl(href)}
|
|
26
|
+
href={startsWithProtocol(href) ? href : buildUrl(href)}
|
|
21
27
|
target={openWindow ? '_blank' : '_self'}
|
|
22
28
|
class="block bg-white border border-gray-200 rounded-lg p-6 transition-all duration-300 ease-in-out hover:shadow-md hover:border-primary focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-primary focus:ring-white"
|
|
23
29
|
>
|
|
@@ -12,6 +12,7 @@ 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
14
|
import ChannelInformation from '@components/MDX/ChannelInformation/ChannelInformation';
|
|
15
|
+
import MessageTable from '@components/MDX/MessageTable/MessageTable.astro';
|
|
15
16
|
import Tabs from '@components/MDX/Tabs/Tabs.astro';
|
|
16
17
|
import TabItem from '@components/MDX/Tabs/TabItem.astro';
|
|
17
18
|
|
|
@@ -39,6 +40,7 @@ const components = (props: any) => {
|
|
|
39
40
|
ChannelInformation: (mdxProp: any) => ChannelInformation({ ...props.data, ...mdxProp }),
|
|
40
41
|
SchemaViewer: (mdxProp: any) => SchemaViewerPortal({ ...props.data, ...mdxProp }),
|
|
41
42
|
Schema: (mdxProp: any) => jsx(Schema, { ...props, ...mdxProp }),
|
|
43
|
+
MessageTable: (mdxProp: any) => jsx(MessageTable, { ...props, ...mdxProp }),
|
|
42
44
|
};
|
|
43
45
|
};
|
|
44
46
|
|
|
@@ -3,7 +3,7 @@ import OwnersList from '@components/Lists/OwnersList';
|
|
|
3
3
|
import PillListFlat from '@components/Lists/PillListFlat';
|
|
4
4
|
import RepositoryList from '@components/Lists/RepositoryList.astro';
|
|
5
5
|
import VersionList from '@components/Lists/VersionList.astro';
|
|
6
|
-
import { getUbiquitousLanguage } from '@utils/collections/domains';
|
|
6
|
+
import { getUbiquitousLanguage, getMessagesForDomain } from '@utils/collections/domains';
|
|
7
7
|
import { getOwner } from '@utils/collections/owners';
|
|
8
8
|
import { buildUrl } from '@utils/url-builder';
|
|
9
9
|
import type { CollectionEntry } from 'astro:content';
|
|
@@ -25,6 +25,8 @@ const ownersRaw = domain.data?.owners || [];
|
|
|
25
25
|
const owners = await Promise.all<ReturnType<typeof getOwner>>(ownersRaw.map(getOwner));
|
|
26
26
|
const filteredOwners = owners.filter((o) => o !== undefined);
|
|
27
27
|
|
|
28
|
+
const messagesForDomain = await getMessagesForDomain(domain);
|
|
29
|
+
|
|
28
30
|
const serviceList = services.map((p) => ({
|
|
29
31
|
label: p.data.name,
|
|
30
32
|
badge: p.collection,
|
|
@@ -33,6 +35,26 @@ const serviceList = services.map((p) => ({
|
|
|
33
35
|
href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
|
|
34
36
|
}));
|
|
35
37
|
|
|
38
|
+
const sendsList = messagesForDomain.sends
|
|
39
|
+
.sort((a, b) => a.collection.localeCompare(b.collection))
|
|
40
|
+
.map((p) => ({
|
|
41
|
+
label: p.data.name,
|
|
42
|
+
badge: p.collection,
|
|
43
|
+
tag: `v${p.data.version}`,
|
|
44
|
+
collection: p.collection,
|
|
45
|
+
href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
const receivesList = messagesForDomain.receives
|
|
49
|
+
.sort((a, b) => a.collection.localeCompare(b.collection))
|
|
50
|
+
.map((p) => ({
|
|
51
|
+
label: p.data.name,
|
|
52
|
+
badge: p.collection,
|
|
53
|
+
tag: `v${p.data.version}`,
|
|
54
|
+
collection: p.collection,
|
|
55
|
+
href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
|
|
56
|
+
}));
|
|
57
|
+
|
|
36
58
|
const ubiquitousLanguageList = ubiquitousLanguageDictionary?.map((l) => ({
|
|
37
59
|
label: l.name,
|
|
38
60
|
badge: 'Ubiquitous Language',
|
|
@@ -59,12 +81,27 @@ const ownersList = filteredOwners.map((o) => ({
|
|
|
59
81
|
icon="ServerIcon"
|
|
60
82
|
client:load
|
|
61
83
|
/>
|
|
84
|
+
<PillListFlat
|
|
85
|
+
title={`Sends messages (${sendsList.length})`}
|
|
86
|
+
pills={sendsList}
|
|
87
|
+
emptyMessage={`This domain does not send any messages.`}
|
|
88
|
+
color="orange"
|
|
89
|
+
client:load
|
|
90
|
+
/>
|
|
91
|
+
<PillListFlat
|
|
92
|
+
title={`Receives messages (${receivesList.length})`}
|
|
93
|
+
pills={receivesList}
|
|
94
|
+
emptyMessage={`This domain does not receive any messages.`}
|
|
95
|
+
color="orange"
|
|
96
|
+
client:load
|
|
97
|
+
/>
|
|
62
98
|
{
|
|
63
99
|
ubiquitousLanguageList && hasUbiquitousLanguage && (
|
|
64
100
|
<PillListFlat
|
|
65
101
|
title={`Ubiquitous Language Dictionary (${ubiquitousLanguageDictionary?.length})`}
|
|
66
102
|
pills={ubiquitousLanguageList}
|
|
67
103
|
color="pink"
|
|
104
|
+
limit={4}
|
|
68
105
|
emptyMessage={`This domain does not have any documented ubiquitous language.`}
|
|
69
106
|
client:load
|
|
70
107
|
/>
|
|
@@ -26,22 +26,27 @@ const ownersRaw = service.data?.owners || [];
|
|
|
26
26
|
const owners = await Promise.all<ReturnType<typeof getOwner>>(ownersRaw.map(getOwner));
|
|
27
27
|
const filteredOwners = owners.filter((o) => o !== undefined);
|
|
28
28
|
|
|
29
|
-
const sendsList = sends
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
29
|
+
const sendsList = sends
|
|
30
|
+
.sort((a, b) => a.collection.localeCompare(b.collection))
|
|
31
|
+
.map((p) => ({
|
|
32
|
+
label: p.data.name,
|
|
33
|
+
badge: p.collection,
|
|
34
|
+
color: p.collection === 'events' ? 'orange' : 'blue',
|
|
35
|
+
collection: p.collection,
|
|
36
|
+
tag: `v${p.data.version}`,
|
|
37
|
+
href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
const receivesList = receives
|
|
41
|
+
.sort((a, b) => a.collection.localeCompare(b.collection))
|
|
42
|
+
.map((p) => ({
|
|
43
|
+
label: p.data.name,
|
|
44
|
+
badge: p.collection,
|
|
45
|
+
color: p.collection === 'events' ? 'orange' : 'blue',
|
|
46
|
+
tag: `v${p.data.version}`,
|
|
47
|
+
collection: p.collection,
|
|
48
|
+
href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
|
|
49
|
+
}));
|
|
45
50
|
|
|
46
51
|
const ownersList = filteredOwners.map((o) => ({
|
|
47
52
|
label: o.data.name,
|
|
@@ -94,7 +99,7 @@ const schemaURL = join(publicPath, schemaFilePath || '');
|
|
|
94
99
|
|
|
95
100
|
{
|
|
96
101
|
isRSSEnabled && (
|
|
97
|
-
<div class="mx-auto pb-
|
|
102
|
+
<div class="mx-auto pb-4 w-full max-w-lg divide-y divide-white/5 rounded-xl bg-white/5 border-b border-gray-100 mb-4">
|
|
98
103
|
<span class="text-sm text-black group-data-[hover]:text-black/80 capitalize">Services RSS Feed</span>
|
|
99
104
|
<ul role="list" class="space-y-2 mt-2">
|
|
100
105
|
<li class="has-tooltip rounded-md text-gray-600 group px-1 w-full hover:bg-gradient-to-l hover:from-purple-500 hover:to-purple-700 hover:text-white hover:font-normal ">
|
|
@@ -11,7 +11,6 @@ const Sidebar: React.FC<{}> = () => {
|
|
|
11
11
|
// Check if this is the first visit after component mounts
|
|
12
12
|
const hasVisited = localStorage.getItem('eventCatalogAIVisited');
|
|
13
13
|
if (!hasVisited || hasVisited === 'false') {
|
|
14
|
-
console.log('setting showHelp to true');
|
|
15
14
|
localStorage.setItem('eventCatalogAIVisited', 'true');
|
|
16
15
|
setShowHelp(true);
|
|
17
16
|
}
|
|
@@ -107,7 +107,10 @@ const generatorConfig = `
|
|
|
107
107
|
<div class="bg-gray-700 rounded-lg p-4 md:p-6 mb-4">
|
|
108
108
|
<h3 class="text-lg text-white font-semibold mb-3">Quick Setup</h3>
|
|
109
109
|
<Code code={`npm install @eventcatalog/generator-ai`} lang="bash" frame="none" />
|
|
110
|
-
<a
|
|
110
|
+
<a
|
|
111
|
+
href="https://www.eventcatalog.dev/feature/ai-assistant"
|
|
112
|
+
class="inline-flex items-center text-sm text-white mt-4"
|
|
113
|
+
>
|
|
111
114
|
Learn more about setup →
|
|
112
115
|
</a>
|
|
113
116
|
</div>
|
|
@@ -2,6 +2,7 @@ import { getItemsFromCollectionByIdAndSemverOrLatest, getVersionForCollectionIte
|
|
|
2
2
|
import { getCollection } from 'astro:content';
|
|
3
3
|
import type { CollectionEntry } from 'astro:content';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import type { CollectionMessageTypes } from '@types';
|
|
5
6
|
|
|
6
7
|
const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
|
|
7
8
|
|
|
@@ -73,6 +74,32 @@ export const getDomains = async ({ getAllVersions = true }: Props = {}): Promise
|
|
|
73
74
|
return cachedDomains[cacheKey];
|
|
74
75
|
};
|
|
75
76
|
|
|
77
|
+
export const getMessagesForDomain = async (
|
|
78
|
+
domain: Domain
|
|
79
|
+
): Promise<{ sends: CollectionEntry<CollectionMessageTypes>[]; receives: CollectionEntry<CollectionMessageTypes>[] }> => {
|
|
80
|
+
// We already have the services from the domain
|
|
81
|
+
const services = domain.data.services as unknown as CollectionEntry<'services'>[];
|
|
82
|
+
|
|
83
|
+
const events = await getCollection('events');
|
|
84
|
+
const commands = await getCollection('commands');
|
|
85
|
+
const queries = await getCollection('queries');
|
|
86
|
+
|
|
87
|
+
const allMessages = [...events, ...commands, ...queries];
|
|
88
|
+
|
|
89
|
+
const sends = services.flatMap((service) => service.data.sends || []);
|
|
90
|
+
const receives = services.flatMap((service) => service.data.receives || []);
|
|
91
|
+
|
|
92
|
+
const sendsMessages = sends.map((send) => getItemsFromCollectionByIdAndSemverOrLatest(allMessages, send.id, send.version));
|
|
93
|
+
const receivesMessages = receives.map((receive) =>
|
|
94
|
+
getItemsFromCollectionByIdAndSemverOrLatest(allMessages, receive.id, receive.version)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
sends: sendsMessages.flat(),
|
|
99
|
+
receives: receivesMessages.flat(),
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
|
|
76
103
|
export const getUbiquitousLanguage = async (domain: Domain): Promise<UbiquitousLanguage[]> => {
|
|
77
104
|
const ubiquitousLanguages = await getCollection('ubiquitousLanguages', (ubiquitousLanguage: UbiquitousLanguage) => {
|
|
78
105
|
return ubiquitousLanguage.slug.startsWith(`${domain.collection}/${domain.slug}`);
|
|
@@ -118,18 +118,18 @@ 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: '
|
|
121
|
+
type: 'flow-edge',
|
|
122
122
|
label: path.label,
|
|
123
123
|
animated: true,
|
|
124
124
|
markerEnd: {
|
|
125
125
|
type: MarkerType.ArrowClosed,
|
|
126
126
|
width: 20,
|
|
127
127
|
height: 20,
|
|
128
|
-
color: '#
|
|
128
|
+
color: '#666',
|
|
129
129
|
},
|
|
130
130
|
style: {
|
|
131
131
|
strokeWidth: 2,
|
|
132
|
-
stroke: '#
|
|
132
|
+
stroke: '#ccc',
|
|
133
133
|
},
|
|
134
134
|
});
|
|
135
135
|
});
|
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.
|
|
9
|
+
"version": "2.26.0",
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"default-files-for-collections/"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@astrojs/markdown-remark": "^6.
|
|
25
|
-
"@astrojs/mdx": "^4.0
|
|
26
|
-
"@astrojs/react": "^4.2.
|
|
24
|
+
"@astrojs/markdown-remark": "^6.2.0",
|
|
25
|
+
"@astrojs/mdx": "^4.1.0",
|
|
26
|
+
"@astrojs/react": "^4.2.1",
|
|
27
27
|
"@astrojs/rss": "^4.0.11",
|
|
28
28
|
"@astrojs/tailwind": "^6.0.0",
|
|
29
29
|
"@asyncapi/avro-schema-parser": "^3.0.24",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@tailwindcss/typography": "^0.5.13",
|
|
44
44
|
"@tanstack/react-table": "^8.17.3",
|
|
45
45
|
"@xyflow/react": "^12.3.6",
|
|
46
|
-
"astro": "^5.
|
|
46
|
+
"astro": "^5.4.1",
|
|
47
47
|
"astro-expressive-code": "^0.40.1",
|
|
48
48
|
"astro-pagefind": "^1.6.0",
|
|
49
49
|
"astro-seo": "^0.8.4",
|