@eventcatalog/core 2.65.0 → 3.0.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.
Files changed (130) hide show
  1. package/README.md +1 -26
  2. package/dist/analytics/analytics.cjs +1 -1
  3. package/dist/analytics/analytics.js +2 -2
  4. package/dist/analytics/log-build.cjs +1 -1
  5. package/dist/analytics/log-build.js +3 -3
  6. package/dist/{chunk-NK6OYMRD.js → chunk-JB4YT5JY.js} +1 -1
  7. package/dist/{chunk-BMDTX5IN.js → chunk-TQ4HZREX.js} +1 -1
  8. package/dist/{chunk-IJRFYF4B.js → chunk-X4W4YC3U.js} +1 -1
  9. package/dist/constants.cjs +1 -1
  10. package/dist/constants.js +1 -1
  11. package/dist/eventcatalog.cjs +1 -21
  12. package/dist/eventcatalog.config.d.cts +10 -0
  13. package/dist/eventcatalog.config.d.ts +10 -0
  14. package/dist/eventcatalog.js +3 -20
  15. package/eventcatalog/src/components/CopyAsMarkdown.tsx +19 -1
  16. package/eventcatalog/src/components/FavoriteButton.tsx +54 -0
  17. package/eventcatalog/src/components/Grids/DomainGrid.tsx +386 -362
  18. package/eventcatalog/src/components/Grids/MessageGrid.tsx +166 -518
  19. package/eventcatalog/src/components/Header.astro +48 -23
  20. package/eventcatalog/src/components/Lists/VersionList.astro +2 -2
  21. package/eventcatalog/src/components/MDX/Design/Design.astro +4 -1
  22. package/eventcatalog/src/components/MDX/Flow/Flow.astro +2 -1
  23. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +3 -3
  24. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +8 -2
  25. package/eventcatalog/src/components/SchemaExplorer/SchemaPageViewer.tsx +37 -0
  26. package/eventcatalog/src/components/Search/Search.astro +48 -28
  27. package/eventcatalog/src/components/Search/SearchModal.tsx +393 -702
  28. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +298 -0
  29. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/container.ts +66 -0
  30. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/domain.ts +101 -0
  31. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/flow.ts +29 -0
  32. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/message.ts +84 -0
  33. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/service.ts +147 -0
  34. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/shared.ts +146 -0
  35. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +1073 -0
  36. package/eventcatalog/src/components/SideNav/NestedSideBar/sidebar-builder.ts +365 -0
  37. package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +90 -0
  38. package/eventcatalog/src/components/SideNav/SideNav.astro +18 -28
  39. package/eventcatalog/src/content.config.ts +2 -0
  40. package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +10 -4
  41. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +3 -3
  42. package/eventcatalog/src/layouts/DirectoryLayout.astro +2 -2
  43. package/eventcatalog/src/layouts/DiscoverLayout.astro +3 -3
  44. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +85 -63
  45. package/eventcatalog/src/layouts/VisualiserLayout.astro +3 -3
  46. package/eventcatalog/src/pages/_index.astro +530 -110
  47. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +64 -0
  48. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +29 -0
  49. package/eventcatalog/src/pages/directory/[type]/_index.data.ts +4 -4
  50. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +1 -4
  51. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +3 -3
  52. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +1 -5
  53. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +362 -190
  54. package/eventcatalog/src/pages/docs/[type]/[id]/[version].md.ts +1 -1
  55. package/eventcatalog/src/pages/docs/[type]/[id]/index.astro +4 -4
  56. package/eventcatalog/src/pages/docs/[type]/[id]/language/_index.data.ts +1 -4
  57. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +3 -27
  58. package/eventcatalog/src/pages/docs/teams/[id]/_index.data.ts +2 -2
  59. package/eventcatalog/src/pages/docs/users/[id]/_index.data.ts +2 -2
  60. package/eventcatalog/src/pages/index.astro +14 -5
  61. package/eventcatalog/src/pages/nav-index.json.ts +30 -0
  62. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/_index.data.ts +77 -0
  63. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +90 -0
  64. package/eventcatalog/src/pages/schemas/{index.astro → explorer/index.astro} +3 -3
  65. package/eventcatalog/src/pages/studio.astro +3 -3
  66. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +4 -3
  67. package/eventcatalog/src/pages/visualiser/[type]/[id]/index.astro +2 -2
  68. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/_index.data.ts +4 -3
  69. package/eventcatalog/src/stores/favorites-store.ts +83 -0
  70. package/eventcatalog/src/stores/sidebar-store.ts +8 -0
  71. package/eventcatalog/src/utils/collections/changelogs.ts +7 -4
  72. package/eventcatalog/src/utils/{channels.ts → collections/channels.ts} +81 -31
  73. package/eventcatalog/src/utils/collections/commands.ts +134 -0
  74. package/eventcatalog/src/utils/collections/containers.ts +44 -33
  75. package/eventcatalog/src/utils/collections/domains.ts +204 -62
  76. package/eventcatalog/src/utils/{entities.ts → collections/entities.ts} +44 -24
  77. package/eventcatalog/src/utils/collections/events.ts +136 -0
  78. package/eventcatalog/src/utils/collections/flows.ts +59 -25
  79. package/eventcatalog/src/utils/{messages.ts → collections/messages.ts} +13 -4
  80. package/eventcatalog/src/utils/{queries.ts → collections/queries.ts} +49 -28
  81. package/eventcatalog/src/utils/collections/services.ts +100 -68
  82. package/eventcatalog/src/utils/collections/teams.ts +94 -0
  83. package/eventcatalog/src/utils/collections/users.ts +122 -0
  84. package/eventcatalog/src/utils/collections/util.ts +57 -1
  85. package/eventcatalog/src/utils/feature.ts +3 -1
  86. package/eventcatalog/src/utils/{collections/file-diffs.ts → file-diffs.ts} +1 -1
  87. package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +2 -0
  88. package/eventcatalog/src/utils/node-graphs/domain-entity-map.ts +16 -6
  89. package/eventcatalog/src/utils/node-graphs/domains-canvas.ts +14 -10
  90. package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +36 -64
  91. package/eventcatalog/src/utils/node-graphs/flows-node-graph.ts +23 -19
  92. package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +36 -49
  93. package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +22 -18
  94. package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +4 -4
  95. package/eventcatalog/tailwind.config.mjs +14 -0
  96. package/eventcatalog/tsconfig.json +2 -1
  97. package/package.json +7 -4
  98. package/eventcatalog/public/logo_old.png +0 -0
  99. package/eventcatalog/src/components/DiscoverInsight.astro +0 -61
  100. package/eventcatalog/src/components/Grids/ServiceGrid.tsx +0 -534
  101. package/eventcatalog/src/components/Lists/CustomSideBarSectionList.astro +0 -55
  102. package/eventcatalog/src/components/Lists/ProtocolList.tsx +0 -74
  103. package/eventcatalog/src/components/Lists/RepositoryList.astro +0 -37
  104. package/eventcatalog/src/components/Lists/SpecificationsList.astro +0 -67
  105. package/eventcatalog/src/components/SideBars/ChannelSideBar.astro +0 -204
  106. package/eventcatalog/src/components/SideBars/ContainerSideBar.astro +0 -180
  107. package/eventcatalog/src/components/SideBars/DomainSideBar.astro +0 -273
  108. package/eventcatalog/src/components/SideBars/EntitySideBar.astro +0 -139
  109. package/eventcatalog/src/components/SideBars/FlowSideBar.astro +0 -128
  110. package/eventcatalog/src/components/SideBars/MessageSideBar.astro +0 -248
  111. package/eventcatalog/src/components/SideBars/ServiceSideBar.astro +0 -294
  112. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/CollapsibleGroup.tsx +0 -46
  113. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/MessageList.tsx +0 -78
  114. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/SpecificationList.tsx +0 -83
  115. package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +0 -1250
  116. package/eventcatalog/src/components/SideNav/ListViewSideBar/types.ts +0 -91
  117. package/eventcatalog/src/components/SideNav/ListViewSideBar/utils.ts +0 -201
  118. package/eventcatalog/src/components/SideNav/TreeView/getTreeView.ts +0 -190
  119. package/eventcatalog/src/components/SideNav/TreeView/index.tsx +0 -94
  120. package/eventcatalog/src/components/TreeView/index.tsx +0 -328
  121. package/eventcatalog/src/components/TreeView/styles.module.css +0 -264
  122. package/eventcatalog/src/components/TreeView/useSlots.ts +0 -95
  123. package/eventcatalog/src/pages/architecture/[type]/index.astro +0 -14
  124. package/eventcatalog/src/pages/architecture/architecture.astro +0 -101
  125. package/eventcatalog/src/pages/architecture/docs/[type]/index.astro +0 -14
  126. package/eventcatalog/src/utils/commands.ts +0 -112
  127. package/eventcatalog/src/utils/events.ts +0 -108
  128. package/eventcatalog/src/utils/generators/index.ts +0 -10
  129. package/eventcatalog/src/utils/teams.ts +0 -72
  130. package/eventcatalog/src/utils/users.ts +0 -72
@@ -1,534 +0,0 @@
1
- import { useState, useMemo, useEffect, memo } from 'react';
2
- import { ServerIcon, ChevronRightIcon, Squares2X2Icon, QueueListIcon, CircleStackIcon } from '@heroicons/react/24/outline';
3
- import { RectangleGroupIcon } from '@heroicons/react/24/outline';
4
- import { buildUrl, buildUrlWithParams } from '@utils/url-builder';
5
- import type { CollectionEntry } from 'astro:content';
6
- import type { CollectionMessageTypes } from '@types';
7
- import { getCollectionStyles } from './utils';
8
- import { SearchBar, TypeFilters, Pagination } from './components';
9
- import type { ExtendedDomain } from './DomainGrid';
10
- import { BoxIcon } from 'lucide-react';
11
-
12
- // Message component for reuse
13
- const Message = memo(({ message, collection }: { message: any; collection: string }) => {
14
- const { Icon, color } = getCollectionStyles(message.collection);
15
- return (
16
- <a
17
- href={buildUrl(`/docs/${message.collection}/${message.data.id}/${message.data.version}`)}
18
- className="group flex border border-gray-200 items-center gap-1 rounded-md text-[11px] font-medium hover:bg-gray-50 transition-colors duration-200 bg-white"
19
- >
20
- <div className="bg-white border-r border-gray-200 px-2 py-1.5 rounded-l-md">
21
- <Icon className={`h-3 w-3 text-${color}-500`} />
22
- </div>
23
- <span className="px-1 py-1 truncate max-w-[140px]">{message.data.name}</span>
24
- </a>
25
- );
26
- });
27
-
28
- // Messages Container component
29
- const MessagesContainer = memo(
30
- ({ messages, type, selectedTypes }: { messages: any[]; type: 'receives' | 'sends'; selectedTypes: string[] }) => {
31
- const bgColor = type === 'receives' ? 'blue' : 'green';
32
- const MAX_MESSAGES_DISPLAYED = 4;
33
-
34
- const filteredMessages = messages?.filter(
35
- (message: any) => selectedTypes.length === 0 || selectedTypes.includes(message.collection)
36
- );
37
-
38
- const messagesToShow = filteredMessages?.slice(0, MAX_MESSAGES_DISPLAYED);
39
- const remainingMessagesCount = filteredMessages ? filteredMessages.length - MAX_MESSAGES_DISPLAYED : 0;
40
-
41
- return (
42
- <div className={`flex-1 h-full flex flex-col bg-${bgColor}-100 border border-${bgColor}-300 rounded-lg p-4`}>
43
- <div className="space-y-2 flex-1">
44
- {messagesToShow?.map((message: any) => (
45
- <Message key={message.data.name} message={message} collection={message.collection} />
46
- ))}
47
- {remainingMessagesCount > 0 && (
48
- <div className="text-center py-1">
49
- <p className="text-gray-500 text-[10px]">+ {remainingMessagesCount} more</p>
50
- </div>
51
- )}
52
- {(!messages?.length ||
53
- (selectedTypes.length > 0 && !messages?.some((message: any) => selectedTypes.includes(message.collection)))) && (
54
- <div className="text-center py-4">
55
- <p className="text-gray-500 text-[10px]">
56
- {selectedTypes.length > 0
57
- ? `Service does not ${type} ${selectedTypes.join(' or ')}`
58
- : `Service does not ${type} any messages`}
59
- </p>
60
- </div>
61
- )}
62
- </div>
63
- </div>
64
- );
65
- }
66
- );
67
-
68
- // Service Card component
69
- const ServiceCard = memo(({ service, urlParams, selectedTypes }: { service: any; urlParams: any; selectedTypes: string[] }) => {
70
- return (
71
- <a
72
- href={buildUrlWithParams('/architecture/messages', {
73
- serviceName: service.data.name,
74
- serviceId: service.data.id,
75
- domainId: urlParams?.domainId,
76
- domainName: urlParams?.domainName,
77
- })}
78
- className="group hover:bg-pink-50 bg-white border-2 border-dashed border-pink-400 rounded-lg shadow-sm hover:shadow-lg transition-all duration-200 overflow-hidden "
79
- >
80
- <div className="p-6">
81
- <div className="flex items-center justify-between mb-3">
82
- <div className="flex items-center gap-2 w-full">
83
- <ServerIcon className="h-5 w-5 text-pink-500" />
84
- <h3 className="text-lg font-semibold text-gray-900 truncate group-hover:underline transition-colors duration-200 w-full max-w-[90%]">
85
- {service.data.name || service.data.id} (v{service.data.version})
86
- </h3>
87
- </div>
88
- </div>
89
-
90
- {service.data.summary && <p className="text-gray-600 text-sm line-clamp-2 min-h-[2.5rem]">{service.data.summary}</p>}
91
-
92
- {!urlParams?.serviceName && (
93
- <div className="flex items-center gap-4 mt-4">
94
- <MessagesContainer messages={service.data.receives} type="receives" selectedTypes={selectedTypes} />
95
-
96
- <div className="flex items-center gap-2 max-w-[200px]">
97
- <div className="w-4 h-[2px] bg-blue-200"></div>
98
- <div className="bg-white border-2 border-pink-100 rounded-lg p-4 shadow-sm">
99
- <div className="flex flex-col items-center gap-3">
100
- <ServerIcon className="h-8 w-8 text-pink-500" />
101
- <div className="text-center">
102
- <p className="text-sm font-medium text-gray-900">{service.data.name || service.data.id}</p>
103
- <p className="text-xs text-gray-500">v{service.data.version}</p>
104
- </div>
105
- </div>
106
- </div>
107
- <div className="w-4 h-[2px] bg-emerald-200"></div>
108
- </div>
109
-
110
- <MessagesContainer messages={service.data.sends} type="sends" selectedTypes={selectedTypes} />
111
- </div>
112
- )}
113
-
114
- {/* Container lists at the bottom */}
115
- {((service.data.readsFrom && service.data.readsFrom.length > 0) ||
116
- (service.data.writesTo && service.data.writesTo.length > 0)) && (
117
- <div className="mt-4 pt-4 border-t border-gray-200 grid grid-cols-2 gap-4">
118
- {/* Reads From */}
119
- {service.data.readsFrom && service.data.readsFrom.length > 0 && (
120
- <div className="space-y-2">
121
- <div className="flex items-center gap-2">
122
- <CircleStackIcon className="h-4 w-4 text-orange-500" />
123
- <h4 className="text-xs font-semibold text-gray-700">Reads from</h4>
124
- </div>
125
- <div className="flex flex-wrap gap-1">
126
- {service.data.readsFrom.slice(0, 3).map((container: any) => (
127
- <a
128
- key={container.id}
129
- href={buildUrl(`/docs/containers/${container.data.id}/${container.data.version}`)}
130
- className="group inline-flex items-center gap-1 px-2 py-1 bg-orange-100 border border-orange-300 rounded-md text-[11px] font-medium hover:bg-orange-200 transition-colors duration-200"
131
- >
132
- <CircleStackIcon className="h-3 w-3 text-orange-600" />
133
- <span className="text-orange-800">{container.data.name}</span>
134
- </a>
135
- ))}
136
- {service.data.readsFrom.length > 3 && (
137
- <span className="inline-flex items-center px-2 py-1 text-xs text-gray-500">
138
- + {service.data.readsFrom.length - 3} more
139
- </span>
140
- )}
141
- </div>
142
- </div>
143
- )}
144
-
145
- {/* Writes To */}
146
- {service.data.writesTo && service.data.writesTo.length > 0 && (
147
- <div className="space-y-2">
148
- <div className="flex items-center gap-2">
149
- <CircleStackIcon className="h-4 w-4 text-purple-500" />
150
- <h4 className="text-xs font-semibold text-gray-700">Writes to</h4>
151
- </div>
152
- <div className="flex flex-wrap gap-1">
153
- {service.data.writesTo.slice(0, 3).map((container: any) => (
154
- <a
155
- key={container.id}
156
- href={buildUrl(`/docs/containers/${container.data.id}/${container.data.version}`)}
157
- className="group inline-flex items-center gap-1 px-2 py-1 bg-purple-100 border border-purple-300 rounded-md text-[11px] font-medium hover:bg-purple-200 transition-colors duration-200"
158
- >
159
- <CircleStackIcon className="h-3 w-3 text-purple-600" />
160
- <span className="text-purple-800">{container.data.name}</span>
161
- </a>
162
- ))}
163
- {service.data.writesTo.length > 3 && (
164
- <span className="inline-flex items-center px-2 py-1 text-xs text-gray-500">
165
- + {service.data.writesTo.length - 3} more
166
- </span>
167
- )}
168
- </div>
169
- </div>
170
- )}
171
- </div>
172
- )}
173
- </div>
174
- </a>
175
- );
176
- });
177
-
178
- // Domain Section component
179
- const DomainSection = memo(
180
- ({
181
- domain,
182
- services,
183
- urlParams,
184
- selectedTypes,
185
- isMultiColumn,
186
- }: {
187
- domain: any;
188
- services: any[];
189
- urlParams: any;
190
- selectedTypes: string[];
191
- isMultiColumn: boolean;
192
- }) => {
193
- const subdomains = domain.data.domains || [];
194
- const allSubDomainServices = subdomains.map((subdomain: any) => subdomain.data.services || []).flat();
195
-
196
- const servicesWithoutSubdomains = services.filter((service) => {
197
- return !allSubDomainServices.some((s: any) => s.id === service.data.id);
198
- });
199
-
200
- return (
201
- <div className="space-y-6">
202
- {servicesWithoutSubdomains.length > 0 && (
203
- <div
204
- className={`grid gap-6 ${isMultiColumn ? 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2' : 'grid-cols-1'}`}
205
- >
206
- {servicesWithoutSubdomains.map((service) => (
207
- <ServiceCard key={service.data.id} service={service} urlParams={urlParams} selectedTypes={selectedTypes} />
208
- ))}
209
- </div>
210
- )}
211
-
212
- {subdomains.map((subdomainRef: any) => {
213
- const subdomain = domain.data.domains?.find((d: any) => d.data.id === subdomainRef.data.id);
214
- if (!subdomain) return null;
215
-
216
- const subdomainServices = services.filter((service) =>
217
- subdomain.data.services?.some((s: any) => s.id === service.data.id)
218
- );
219
-
220
- if (subdomainServices.length === 0) return null;
221
-
222
- return (
223
- <div key={subdomain.data.id} className="bg-orange-50 border-2 border-orange-400 rounded-lg p-6 space-y-4">
224
- <div className="flex items-center justify-between">
225
- <div className="flex items-center gap-2">
226
- <RectangleGroupIcon className="h-5 w-5 text-orange-500" />
227
- <h3 className="text-xl font-semibold text-gray-900">{subdomain.data.name} (Subdomain)</h3>
228
- </div>
229
- <div className="flex gap-2">
230
- <a
231
- href={buildUrl(`/visualiser/domains/${subdomain.data.id}`)}
232
- className="inline-flex items-center px-3 py-2 text-sm font-medium bg-white border border-gray-300 rounded-md transition-colors duration-200"
233
- >
234
- View in visualizer
235
- </a>
236
- <a
237
- href={buildUrl(`/docs/domains/${subdomain.data.id}`)}
238
- className="inline-flex items-center px-3 py-2 text-sm font-medium text-black border border-gray-300 bg-white rounded-md transition-colors duration-200"
239
- >
240
- Read documentation
241
- </a>
242
- </div>
243
- </div>
244
-
245
- {/* Entities */}
246
- {subdomain.data.entities && subdomain.data.entities.length > 0 && (
247
- <div className="space-y-2">
248
- <div className="flex items-center gap-2">
249
- <BoxIcon className="h-4 w-4 text-purple-500" />
250
- <h4 className="text-xs font-semibold text-gray-700">Entities</h4>
251
- </div>
252
- <div className="flex flex-wrap gap-1">
253
- {subdomain.data.entities.map((entity: any) => (
254
- <a
255
- key={entity.id}
256
- href={buildUrl(`/docs/entities/${entity.id}`)}
257
- className="group inline-flex items-center gap-1 px-2 py-1 bg-purple-100 border border-purple-300 rounded-md text-[11px] font-medium hover:bg-purple-200 transition-colors duration-200"
258
- >
259
- <BoxIcon className="h-3 w-3 text-purple-600" />
260
- <span className="text-purple-800">{entity.id}</span>
261
- </a>
262
- ))}
263
- </div>
264
- </div>
265
- )}
266
-
267
- <div
268
- className={`grid gap-6 ${isMultiColumn ? 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2' : 'grid-cols-1'}`}
269
- >
270
- {subdomainServices.map((service) => (
271
- <ServiceCard
272
- key={service.data.id}
273
- service={service}
274
- urlParams={{
275
- ...urlParams,
276
- domainId: subdomain.data.id,
277
- domainName: `${subdomain.data.name} (Subdomain)`,
278
- }}
279
- selectedTypes={selectedTypes}
280
- />
281
- ))}
282
- </div>
283
- </div>
284
- );
285
- })}
286
- </div>
287
- );
288
- }
289
- );
290
-
291
- interface ServiceGridProps {
292
- services: CollectionEntry<'services'>[];
293
- domains: ExtendedDomain[];
294
- embeded: boolean;
295
- }
296
-
297
- // Main ServiceGrid component
298
- export default function ServiceGrid({ services, domains, embeded }: ServiceGridProps) {
299
- const [searchQuery, setSearchQuery] = useState('');
300
- const [currentPage, setCurrentPage] = useState(1);
301
- const [selectedTypes, setSelectedTypes] = useState<CollectionMessageTypes[]>([]);
302
- const [isMultiColumn, setIsMultiColumn] = useState(false);
303
- const ITEMS_PER_PAGE = 16;
304
- const [urlParams, setUrlParams] = useState<{
305
- serviceIds?: string[];
306
- domainId?: string;
307
- domainName?: string;
308
- serviceName?: string;
309
- } | null>(null);
310
-
311
- useEffect(() => {
312
- const params = new URLSearchParams(window.location.search);
313
- setUrlParams({
314
- serviceIds: params.get('serviceIds')?.split(',').filter(Boolean),
315
- domainId: params.get('domainId') || undefined,
316
- domainName: params.get('domainName') || undefined,
317
- serviceName: params.get('serviceName') || undefined,
318
- });
319
- }, []);
320
-
321
- useEffect(() => {
322
- if (typeof window !== 'undefined') {
323
- const saved = localStorage.getItem('EventCatalog:ServiceColumnLayout');
324
- if (saved !== null) {
325
- setIsMultiColumn(saved === 'multi');
326
- }
327
- }
328
- }, []);
329
-
330
- const toggleColumnLayout = () => {
331
- const newValue = !isMultiColumn;
332
- setIsMultiColumn(newValue);
333
- if (typeof window !== 'undefined') {
334
- localStorage.setItem('EventCatalog:ServiceColumnLayout', newValue ? 'multi' : 'single');
335
- }
336
- };
337
-
338
- const filteredAndSortedServices = useMemo(() => {
339
- if (urlParams === null) return [];
340
-
341
- let result = [...services];
342
-
343
- if (urlParams.serviceIds?.length) {
344
- result = result.filter(
345
- (service) => urlParams.serviceIds?.includes(service.data.id) && !service.data.id.includes('/versioned/')
346
- );
347
- }
348
-
349
- if (searchQuery) {
350
- const query = searchQuery.toLowerCase();
351
- result = result.filter(
352
- (service) =>
353
- service.data.name?.toLowerCase().includes(query) ||
354
- service.data.summary?.toLowerCase().includes(query) ||
355
- service.data.sends?.some((message: any) => message.data.name.toLowerCase().includes(query)) ||
356
- service.data.receives?.some((message: any) => message.data.name.toLowerCase().includes(query))
357
- );
358
- }
359
-
360
- if (selectedTypes.length > 0) {
361
- result = result.filter((service) => {
362
- const hasMatchingSends = service.data.sends?.some((message: any) => selectedTypes.includes(message.collection));
363
- const hasMatchingReceives = service.data.receives?.some((message: any) => selectedTypes.includes(message.collection));
364
- return hasMatchingSends || hasMatchingReceives;
365
- });
366
- }
367
-
368
- result.sort((a, b) => (a.data.name || a.data.id).localeCompare(b.data.name || b.data.id));
369
- return result;
370
- }, [services, searchQuery, urlParams, selectedTypes]);
371
-
372
- const paginatedServices = useMemo(() => {
373
- if (urlParams?.domainId || urlParams?.serviceIds?.length) {
374
- return filteredAndSortedServices;
375
- }
376
- const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
377
- return filteredAndSortedServices.slice(startIndex, startIndex + ITEMS_PER_PAGE);
378
- }, [filteredAndSortedServices, currentPage, urlParams]);
379
-
380
- const totalPages = useMemo(() => {
381
- if (urlParams?.domainId || urlParams?.serviceIds?.length) return 1;
382
- return Math.ceil(filteredAndSortedServices.length / ITEMS_PER_PAGE);
383
- }, [filteredAndSortedServices.length, urlParams]);
384
-
385
- useEffect(() => {
386
- setCurrentPage(1);
387
- }, [searchQuery, selectedTypes]);
388
-
389
- return (
390
- <div>
391
- {/* Breadcrumb */}
392
- <nav className="mb-4 flex items-center space-x-2 text-sm text-gray-500">
393
- <a href={buildUrl('/architecture/domains')} className="hover:text-gray-700 hover:underline flex items-center gap-2">
394
- <RectangleGroupIcon className="h-4 w-4" />
395
- Domains
396
- </a>
397
- <ChevronRightIcon className="h-4 w-4" />
398
- <a href={buildUrl('/architecture/services')} className="hover:text-gray-700 hover:underline flex items-center gap-2">
399
- <ServerIcon className="h-4 w-4" />
400
- Services
401
- </a>
402
- {urlParams?.domainId && (
403
- <>
404
- <ChevronRightIcon className="h-4 w-4" />
405
- <span className="text-gray-900">{urlParams.domainId}</span>
406
- </>
407
- )}
408
- </nav>
409
-
410
- {/* Title Section */}
411
- <div className="relative border-b border-gray-200 mb-4 pb-4">
412
- <div className="md:flex md:items-start md:justify-between">
413
- <div className="min-w-0 flex-1 max-w-lg">
414
- <div className="flex items-center gap-2">
415
- <h1 className="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate">
416
- {urlParams?.domainId ? `${urlParams.domainName} Architecture` : 'All Services'}
417
- </h1>
418
- </div>
419
- <p className="mt-2 text-sm text-gray-500">
420
- {urlParams?.domainId
421
- ? `Browse services and messages in the ${urlParams.domainId} domain`
422
- : 'Browse and discover services in your event-driven architecture'}
423
- </p>
424
- </div>
425
-
426
- <div className="mt-6 md:mt-0 md:ml-4 flex-shrink-0 flex items-center gap-3">
427
- <SearchBar
428
- searchQuery={searchQuery}
429
- onSearchChange={setSearchQuery}
430
- placeholder="Search services by name, summary, or messages..."
431
- totalResults={filteredAndSortedServices.length}
432
- totalItems={services.length}
433
- />
434
- <button
435
- onClick={toggleColumnLayout}
436
- className="flex items-center justify-center p-2 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors duration-200"
437
- title={isMultiColumn ? 'Switch to single column' : 'Switch to multi column'}
438
- >
439
- {isMultiColumn ? (
440
- <QueueListIcon className="h-5 w-5 text-gray-600" />
441
- ) : (
442
- <Squares2X2Icon className="h-5 w-5 text-gray-600" />
443
- )}
444
- </button>
445
- </div>
446
- </div>
447
- </div>
448
-
449
- <div className="mb-8">
450
- {/* Results count and pagination */}
451
- <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
452
- <TypeFilters
453
- selectedTypes={selectedTypes}
454
- onTypeChange={setSelectedTypes}
455
- filteredCount={filteredAndSortedServices.length}
456
- totalCount={services.length}
457
- />
458
- <div className="text-sm text-gray-500">
459
- {urlParams?.domainId || urlParams?.serviceIds?.length ? (
460
- <span>
461
- Showing {filteredAndSortedServices.length} services in the {urlParams.domainId} domain
462
- </span>
463
- ) : (
464
- <span>
465
- Showing {(currentPage - 1) * ITEMS_PER_PAGE + 1} to{' '}
466
- {Math.min(currentPage * ITEMS_PER_PAGE, filteredAndSortedServices.length)} of {filteredAndSortedServices.length}{' '}
467
- services
468
- </span>
469
- )}
470
- </div>
471
- {!(urlParams?.domainId || urlParams?.serviceIds?.length) && (
472
- <Pagination
473
- currentPage={currentPage}
474
- totalPages={totalPages}
475
- totalItems={filteredAndSortedServices.length}
476
- itemsPerPage={ITEMS_PER_PAGE}
477
- onPageChange={setCurrentPage}
478
- />
479
- )}
480
- </div>
481
- </div>
482
-
483
- {filteredAndSortedServices.length > 0 && (
484
- <div className={`rounded-xl overflow-hidden ${urlParams?.domainId ? 'bg-yellow-50 p-8 border-2 border-yellow-400' : ''}`}>
485
- {urlParams?.domainName ? (
486
- domains
487
- .filter((domain: ExtendedDomain) => domain.data.id === urlParams.domainId)
488
- .map((domain: ExtendedDomain) => (
489
- <DomainSection
490
- key={domain.data.id}
491
- domain={domain}
492
- services={paginatedServices}
493
- urlParams={urlParams}
494
- selectedTypes={selectedTypes}
495
- isMultiColumn={isMultiColumn}
496
- />
497
- ))
498
- ) : (
499
- <div
500
- className={`grid gap-6 ${isMultiColumn ? 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2' : 'grid-cols-1'}`}
501
- >
502
- {paginatedServices.map((service) => (
503
- <ServiceCard key={service.data.id} service={service} urlParams={urlParams} selectedTypes={selectedTypes} />
504
- ))}
505
- </div>
506
- )}
507
- </div>
508
- )}
509
-
510
- {filteredAndSortedServices.length === 0 && (
511
- <div className="text-center py-12 bg-gray-50 rounded-lg">
512
- <p className="text-gray-500 text-lg">
513
- {selectedTypes.length > 0
514
- ? `No services found that ${selectedTypes.length > 1 ? 'handle' : 'handles'} ${selectedTypes.join(' or ')} messages`
515
- : 'No services found matching your criteria'}
516
- </p>
517
- </div>
518
- )}
519
-
520
- {/* Bottom pagination */}
521
- {!(urlParams?.domainId || urlParams?.serviceIds?.length) && (
522
- <div className="mt-8 border-t border-gray-200">
523
- <Pagination
524
- currentPage={currentPage}
525
- totalPages={totalPages}
526
- totalItems={filteredAndSortedServices.length}
527
- itemsPerPage={ITEMS_PER_PAGE}
528
- onPageChange={setCurrentPage}
529
- />
530
- </div>
531
- )}
532
- </div>
533
- );
534
- }
@@ -1,55 +0,0 @@
1
- ---
2
- import { buildUrl } from '@utils/url-builder';
3
- import { getCollection } from 'astro:content';
4
- import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
5
- import PillListFlat from './PillListFlat';
6
- import { resourceToCollectionMap } from '@utils/collections/util';
7
- interface Props {
8
- section?: {
9
- title?: string;
10
- limit?: number;
11
- items: {
12
- id: string;
13
- type: string;
14
- version?: string;
15
- }[];
16
- };
17
- }
18
-
19
- const { section } = Astro.props;
20
- const title = section?.title || 'Custom Section';
21
- const limit = section?.limit || 10;
22
- const sectionItems = section?.items || [];
23
-
24
- // Array to store resolved related resources
25
- const resolvedResources = [];
26
-
27
- // Fetch related resources
28
- for (const resource of sectionItems) {
29
- try {
30
- // Use type assertion to ensure TypeScript understands this is a valid collection name
31
- const collectionName = resourceToCollectionMap[resource.type as keyof typeof resourceToCollectionMap];
32
-
33
- // Use getEntry instead of getCollection for single item lookup by ID
34
- const allItemsInCollection = await getCollection(collectionName);
35
- // @ts-ignore
36
- const entryToMatchVersion = getItemsFromCollectionByIdAndSemverOrLatest(allItemsInCollection, resource.id, resource.version);
37
- const entry = entryToMatchVersion[0];
38
-
39
- if (entry) {
40
- resolvedResources.push(entry);
41
- }
42
- } catch (error) {
43
- console.error(`Failed to fetch related resource: ${resource.id} of type ${resource.type}`, error);
44
- }
45
- }
46
-
47
- const sectionList = resolvedResources.map((p: any) => ({
48
- label: `${p.data.name}`,
49
- tag: `v${p.data.version}`,
50
- collection: p.collection,
51
- href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
52
- }));
53
- ---
54
-
55
- <PillListFlat title={`${title} (${sectionList.length})`} pills={sectionList} color="orange" limit={limit} client:load />
@@ -1,74 +0,0 @@
1
- import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react';
2
- import { ChevronDownIcon } from '@heroicons/react/20/solid';
3
- import { getIconForProtocol } from '@utils/protocols';
4
-
5
- import './PillListFlat.styles.css';
6
-
7
- interface Props {
8
- title: string;
9
- color: string;
10
- icon?: any;
11
- pills: {
12
- label: string;
13
- badge?: string;
14
- href?: string;
15
- tag?: string;
16
- color?: string;
17
- collection?: string;
18
- description?: string;
19
- icon?: string;
20
- }[];
21
- emptyMessage?: string;
22
- }
23
-
24
- const ProtocolList = ({ title, pills, emptyMessage, color = 'gray', ...props }: Props) => {
25
- return (
26
- <div>
27
- <div className="mx-auto w-full max-w-lg divide-y divide-white/5 rounded-xl bg-white/5">
28
- <Disclosure as="div" className="pb-8" defaultOpen={pills.length <= 10}>
29
- <DisclosureButton className="group flex w-full items-center justify-start space-x-4">
30
- <span className="text-sm text-black font-semibold group-data-[hover]:text-black/80 capitalize"> {title} </span>
31
- <ChevronDownIcon className="size-5 ml-2 fill-black/60 group-data-[hover]:fill-black/50 group-data-[open]:rotate-180" />
32
- </DisclosureButton>
33
- <DisclosurePanel className="mt-2 text-sm/5 text-black/50">
34
- <ul role="list" className="space-y-2">
35
- {pills.map((item, index) => {
36
- const href = item.href ?? '#';
37
- const Icon = item.icon ? getIconForProtocol(item.icon) : null;
38
-
39
- return (
40
- <li
41
- className=" 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 "
42
- key={`${item.href}-${index}`}
43
- >
44
- <a className={`leading-3`} href={href}>
45
- <span className="space-x-2 flex items-center">
46
- {Icon && <Icon className={`h-4 w-4`} />}
47
- <span className="font-light text-sm truncate">
48
- {item.label} {item.tag && <>({item.tag})</>}
49
- </span>
50
- {item.label.length > 24 && (
51
- <span className="tooltip rounded relative shadow-lg p-1 font-normal text-xs bg-white text-black ml-[30px] mt-12">
52
- {item.label} ({item.tag})
53
- </span>
54
- )}
55
- </span>
56
- {item.description && <span className="text-[9px] block ml-6 mt-1 leading-0">{item.description}</span>}
57
- </a>
58
- </li>
59
- );
60
- })}
61
- {pills.length === 0 && emptyMessage && (
62
- <li className="inline mr-2 leading-tight text-xs">
63
- <span className="text-gray-400">{emptyMessage}</span>
64
- </li>
65
- )}
66
- </ul>
67
- </DisclosurePanel>
68
- </Disclosure>
69
- </div>
70
- </div>
71
- );
72
- };
73
-
74
- export default ProtocolList;