@eventcatalog/core 2.25.1 → 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.
@@ -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.25.1";
40
+ var version = "2.26.0";
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-O33THQNO.js";
4
- import "../chunk-DZIMF2ES.js";
3
+ } from "../chunk-3EOBEGSB.js";
4
+ import "../chunk-IY5HYH2G.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.25.1";
109
+ var version = "2.26.0";
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-OZLFIB46.js";
4
- import "../chunk-O33THQNO.js";
5
- import "../chunk-DZIMF2ES.js";
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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-DZIMF2ES.js";
3
+ } from "./chunk-IY5HYH2G.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.25.1";
2
+ var version = "2.26.0";
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-O33THQNO.js";
3
+ } from "./chunk-3EOBEGSB.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.25.1";
28
+ var version = "2.26.0";
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-DZIMF2ES.js";
3
+ } from "./chunk-IY5HYH2G.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -161,7 +161,7 @@ var import_axios = __toESM(require("axios"), 1);
161
161
  var import_os = __toESM(require("os"), 1);
162
162
 
163
163
  // package.json
164
- var version = "2.25.1";
164
+ var version = "2.26.0";
165
165
 
166
166
  // src/constants.ts
167
167
  var VERSION = version;
@@ -6,14 +6,14 @@ import {
6
6
  } from "./chunk-OW2FQPYP.js";
7
7
  import {
8
8
  log_build_default
9
- } from "./chunk-OZLFIB46.js";
10
- import "./chunk-O33THQNO.js";
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-DZIMF2ES.js";
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="pb-8" defaultOpen={owners.length <= 5}>
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="pb-8" defaultOpen={pills.length <= 10}>
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-8 w-full max-w-lg divide-y divide-white/5 rounded-xl bg-white/5">
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-8">
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
- <!-- <select id="version" class="block bg-gray-50 rounded-md border border-gray-200 px-1 py-0.5 text-xs">
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;
@@ -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.map((p) => ({
30
- label: p.data.name,
31
- badge: p.collection,
32
- color: p.collection === 'events' ? 'orange' : 'blue',
33
- collection: p.collection,
34
- tag: `v${p.data.version}`,
35
- href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
36
- }));
37
- const receivesList = receives.map((p) => ({
38
- label: p.data.name,
39
- badge: p.collection,
40
- color: p.collection === 'events' ? 'orange' : 'blue',
41
- tag: `v${p.data.version}`,
42
- collection: p.collection,
43
- href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
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-8 w-full max-w-lg divide-y divide-white/5 rounded-xl bg-white/5">
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
  }
@@ -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}`);
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.25.1",
9
+ "version": "2.26.0",
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },