@eventcatalog/core 3.5.2 → 3.6.1
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/README.md +1 -1
- 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-YVX5C6L3.js → chunk-FCIJEGOL.js} +1 -1
- package/dist/{chunk-WO3AKJVB.js → chunk-N2VBSHPU.js} +1 -1
- package/dist/{chunk-OKWCSRLE.js → chunk-OFHFRJ42.js} +1 -1
- package/dist/{chunk-YOFNY2RC.js → chunk-SI6IEUYS.js} +1 -1
- package/dist/{chunk-YTZSPYJN.js → chunk-XRLZZXIS.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 +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/astro.config.mjs +2 -1
- package/eventcatalog/src/components/EnvironmentDropdown.tsx +1 -1
- package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +477 -0
- package/eventcatalog/src/components/MDX/components.tsx +2 -0
- package/eventcatalog/src/components/Search/SearchDataLoader.astro +23 -11
- package/eventcatalog/src/components/Search/SearchModal.tsx +17 -2
- package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +12 -6
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +37 -16
- package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +816 -0
- package/eventcatalog/src/components/Tables/Discover/FilterComponents.tsx +161 -0
- package/eventcatalog/src/components/Tables/Discover/columns.tsx +565 -0
- package/eventcatalog/src/components/Tables/Discover/index.ts +4 -0
- package/eventcatalog/src/components/Tables/columns/ContainersTableColumns.tsx +1 -1
- package/eventcatalog/src/components/Tables/columns/DomainTableColumns.tsx +1 -1
- package/eventcatalog/src/components/Tables/columns/FlowTableColumns.tsx +1 -1
- package/eventcatalog/src/components/Tables/columns/MessageTableColumns.tsx +1 -1
- package/eventcatalog/src/components/Tables/columns/ServiceTableColumns.tsx +54 -64
- package/eventcatalog/src/components/Tables/columns/SharedColumns.tsx +15 -30
- package/eventcatalog/src/enterprise/plans/index.astro +125 -98
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +1 -1
- package/eventcatalog/src/pages/api/sidebar-data.json.ts +22 -0
- package/eventcatalog/src/pages/discover/[type]/_index.data.ts +5 -1
- package/eventcatalog/src/pages/discover/[type]/index.astro +360 -41
- package/eventcatalog/src/pages/docs/custom/feature.astro +45 -39
- package/eventcatalog/src/remark-plugins/resource-ref.ts +51 -0
- package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +1 -1
- package/eventcatalog/src/stores/sidebar-store/state.ts +25 -22
- package/package.json +3 -2
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Inline resource reference component with hover tooltip.
|
|
4
|
+
*
|
|
5
|
+
* Usage via remark plugin:
|
|
6
|
+
* [[service|OrderService]] -> Links to OrderService with tooltip
|
|
7
|
+
* [[Order]] -> Shorthand, defaults to entity type
|
|
8
|
+
*/
|
|
9
|
+
import { buildUrl } from '@utils/url-builder';
|
|
10
|
+
import {
|
|
11
|
+
getItemsFromCollectionByIdAndSemverOrLatest,
|
|
12
|
+
resourceToCollectionMap,
|
|
13
|
+
getDeprecatedDetails,
|
|
14
|
+
} from '@utils/collections/util';
|
|
15
|
+
import { getCollection } from 'astro:content';
|
|
16
|
+
import { getServiceSpecifications, getSpecUrl, getSpecLabel } from '@components/Grids/specification-utils';
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
type:
|
|
20
|
+
| 'entity'
|
|
21
|
+
| 'service'
|
|
22
|
+
| 'event'
|
|
23
|
+
| 'command'
|
|
24
|
+
| 'query'
|
|
25
|
+
| 'domain'
|
|
26
|
+
| 'flow'
|
|
27
|
+
| 'channel'
|
|
28
|
+
| 'diagram'
|
|
29
|
+
| 'container'
|
|
30
|
+
| 'user'
|
|
31
|
+
| 'team';
|
|
32
|
+
version?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { type = 'entity', version } = Astro.props;
|
|
36
|
+
|
|
37
|
+
// Get resource ID from slot content
|
|
38
|
+
const slotContent = await Astro.slots.render('default');
|
|
39
|
+
const resourceId = slotContent.trim();
|
|
40
|
+
|
|
41
|
+
// Map type to collection name
|
|
42
|
+
const collection = resourceToCollectionMap[type as keyof typeof resourceToCollectionMap];
|
|
43
|
+
|
|
44
|
+
// Type-specific styling using CSS variables from theme.css
|
|
45
|
+
// Maps to --ec-badge-{type}-text variables for consistency
|
|
46
|
+
const typeStyles: Record<string, { borderColor: string; label: string }> = {
|
|
47
|
+
entity: { borderColor: 'var(--ec-badge-query-text)', label: 'Entity' }, // purple
|
|
48
|
+
service: { borderColor: 'var(--ec-badge-service-text)', label: 'Service' }, // pink
|
|
49
|
+
event: { borderColor: 'var(--ec-badge-event-text)', label: 'Event' }, // amber/orange
|
|
50
|
+
command: { borderColor: 'var(--ec-badge-command-text)', label: 'Command' }, // pink
|
|
51
|
+
query: { borderColor: 'var(--ec-badge-query-text)', label: 'Query' }, // purple
|
|
52
|
+
domain: { borderColor: 'var(--ec-badge-domain-text)', label: 'Domain' }, // yellow
|
|
53
|
+
flow: { borderColor: 'var(--ec-badge-design-text)', label: 'Flow' }, // teal
|
|
54
|
+
channel: { borderColor: 'var(--ec-badge-channel-text)', label: 'Channel' }, // indigo
|
|
55
|
+
diagram: { borderColor: 'var(--ec-badge-default-text)', label: 'Diagram' }, // gray
|
|
56
|
+
container: { borderColor: 'var(--ec-badge-default-text)', label: 'Container' }, // gray
|
|
57
|
+
user: { borderColor: 'var(--ec-badge-default-text)', label: 'User' }, // gray
|
|
58
|
+
team: { borderColor: 'var(--ec-badge-default-text)', label: 'Team' }, // gray
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// SVG icons for each type (from heroicons outline)
|
|
62
|
+
const typeIcons: Record<string, string> = {
|
|
63
|
+
entity:
|
|
64
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />', // Box/cube
|
|
65
|
+
service:
|
|
66
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />', // Server
|
|
67
|
+
event:
|
|
68
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" />', // Bolt
|
|
69
|
+
command:
|
|
70
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z" />', // ChatBubble
|
|
71
|
+
query:
|
|
72
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />', // MagnifyingGlass
|
|
73
|
+
domain:
|
|
74
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75zM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-8.25zM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-2.25z" />', // RectangleGroup
|
|
75
|
+
flow: '<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 010 3.75H5.625a1.875 1.875 0 010-3.75z" />', // QueueList
|
|
76
|
+
channel:
|
|
77
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" />', // ArrowsRightLeft
|
|
78
|
+
diagram:
|
|
79
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M9 6.75V15m6-6v8.25m.503 3.498l4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 00-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0z" />', // Map
|
|
80
|
+
container:
|
|
81
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" />', // Database
|
|
82
|
+
user: '<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />', // User
|
|
83
|
+
team: '<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />', // UserGroup
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const style = typeStyles[type] || typeStyles.entity;
|
|
87
|
+
const iconPath = typeIcons[type] || typeIcons.entity;
|
|
88
|
+
|
|
89
|
+
let resource: any = null;
|
|
90
|
+
let href = '#';
|
|
91
|
+
let hasError = false;
|
|
92
|
+
let errorMessage = '';
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
if (!collection) {
|
|
96
|
+
throw new Error(`Unknown resource type: ${type}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const resourcesCollection = await getCollection(collection as any);
|
|
100
|
+
const resources = getItemsFromCollectionByIdAndSemverOrLatest(resourcesCollection, resourceId, version);
|
|
101
|
+
|
|
102
|
+
if (resources.length === 0) {
|
|
103
|
+
throw new Error(`Resource not found: ${resourceId}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
resource = resources[0];
|
|
107
|
+
// Diagrams use /diagrams/ path, other resources use /docs/{collection}/
|
|
108
|
+
href =
|
|
109
|
+
type === 'diagram'
|
|
110
|
+
? buildUrl(`/diagrams/${resourceId}/${resource.data.version}`)
|
|
111
|
+
: buildUrl(`/docs/${collection}/${resourceId}/${resource.data.version}`);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
hasError = true;
|
|
114
|
+
errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Truncate summary if too long
|
|
118
|
+
const maxSummaryLength = 120;
|
|
119
|
+
const summary = resource?.data?.summary || '';
|
|
120
|
+
const truncatedSummary = summary.length > maxSummaryLength ? summary.slice(0, maxSummaryLength) + '...' : summary;
|
|
121
|
+
|
|
122
|
+
// Only these types have visualizers
|
|
123
|
+
const hasVisualizer = ['domain', 'service', 'event', 'query', 'command', 'container'].includes(type);
|
|
124
|
+
|
|
125
|
+
// Check deprecation status
|
|
126
|
+
const deprecation = resource ? getDeprecatedDetails(resource) : null;
|
|
127
|
+
const isDeprecated = deprecation?.isMarkedAsDeprecated || false;
|
|
128
|
+
|
|
129
|
+
// Get owners (first 2)
|
|
130
|
+
const owners = resource?.data?.owners?.slice(0, 2) || [];
|
|
131
|
+
|
|
132
|
+
// Check if message type has a schema
|
|
133
|
+
const isMessageType = ['event', 'command', 'query'].includes(type);
|
|
134
|
+
const hasSchema = isMessageType && resource?.data?.schemaPath;
|
|
135
|
+
|
|
136
|
+
// Check if resource has a repository URL
|
|
137
|
+
const repositoryUrl = resource?.data?.repository?.url;
|
|
138
|
+
|
|
139
|
+
// For services: get messages (sends/receives) with resolved versions
|
|
140
|
+
const maxMessages = 3;
|
|
141
|
+
const sends = resource?.data?.sends || [];
|
|
142
|
+
const receives = resource?.data?.receives || [];
|
|
143
|
+
const isService = type === 'service';
|
|
144
|
+
|
|
145
|
+
// Helper to resolve message version and collection - use specified version or fetch from collection
|
|
146
|
+
const resolveMessage = async (msg: any): Promise<{ version: string | null; collection: string | null }> => {
|
|
147
|
+
// If version is specified and not "latest", use it (assume event as default collection)
|
|
148
|
+
if (msg.version && msg.version !== 'latest') {
|
|
149
|
+
return { version: msg.version, collection: 'events' };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Try to find the message in events, commands, or queries collections
|
|
153
|
+
const collections = ['events', 'commands', 'queries'];
|
|
154
|
+
for (const col of collections) {
|
|
155
|
+
try {
|
|
156
|
+
const items = await getCollection(col as any);
|
|
157
|
+
const found = getItemsFromCollectionByIdAndSemverOrLatest(items, msg.id);
|
|
158
|
+
if (found.length > 0 && found[0].data.version && found[0].data.version !== 'latest') {
|
|
159
|
+
return { version: found[0].data.version, collection: col };
|
|
160
|
+
}
|
|
161
|
+
} catch (e) {
|
|
162
|
+
// Collection might not exist or item not found, continue
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { version: null, collection: null };
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Resolve versions and collections for messages to show
|
|
169
|
+
const sendsWithVersions = await Promise.all(
|
|
170
|
+
sends.slice(0, maxMessages).map(async (msg: any) => {
|
|
171
|
+
const resolved = await resolveMessage(msg);
|
|
172
|
+
return { ...msg, resolvedVersion: resolved.version, resolvedCollection: resolved.collection };
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
const receivesWithVersions = await Promise.all(
|
|
176
|
+
receives.slice(0, maxMessages).map(async (msg: any) => {
|
|
177
|
+
const resolved = await resolveMessage(msg);
|
|
178
|
+
return { ...msg, resolvedVersion: resolved.version, resolvedCollection: resolved.collection };
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Get specifications for services
|
|
183
|
+
const specifications = isService ? getServiceSpecifications(resource?.data) : [];
|
|
184
|
+
const hasSpecifications = specifications.length > 0;
|
|
185
|
+
|
|
186
|
+
// Generate unique ID for this instance
|
|
187
|
+
const tooltipId = `ref-tooltip-${Math.random().toString(36).slice(2, 9)}`;
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
<span class="resource-ref-wrapper inline">
|
|
191
|
+
{
|
|
192
|
+
hasError ? (
|
|
193
|
+
<span
|
|
194
|
+
class="text-[rgb(var(--ec-page-text-muted))] underline decoration-wavy decoration-red-400/50 underline-offset-2 cursor-help"
|
|
195
|
+
title={`Resource not found: ${resourceId}`}
|
|
196
|
+
>
|
|
197
|
+
{resourceId}
|
|
198
|
+
</span>
|
|
199
|
+
) : (
|
|
200
|
+
<>
|
|
201
|
+
<a
|
|
202
|
+
href={href}
|
|
203
|
+
class:list={[
|
|
204
|
+
'resource-ref-trigger underline decoration-dotted hover:decoration-solid underline-offset-2 font-medium transition-colors',
|
|
205
|
+
isDeprecated
|
|
206
|
+
? 'text-amber-600 decoration-amber-500/50'
|
|
207
|
+
: 'text-[rgb(var(--ec-accent))] hover:text-[rgb(var(--ec-accent-hover))]',
|
|
208
|
+
]}
|
|
209
|
+
data-tooltip-id={tooltipId}
|
|
210
|
+
>
|
|
211
|
+
{resource?.data?.name || resourceId}
|
|
212
|
+
{isDeprecated && ' (deprecated)'}
|
|
213
|
+
</a>
|
|
214
|
+
<span
|
|
215
|
+
id={tooltipId}
|
|
216
|
+
class="resource-ref-tooltip fixed w-80 bg-[rgb(var(--ec-card-bg))] border border-[rgb(var(--ec-page-border))] border-l-[3px] rounded-r-lg rounded-l-none shadow-lg shadow-black/8 z-[9999] text-left overflow-hidden opacity-0 pointer-events-none transition-all duration-150 scale-95 origin-top-left"
|
|
217
|
+
style={`border-left-color: rgb(${style.borderColor});`}
|
|
218
|
+
>
|
|
219
|
+
{/* Deprecation banner */}
|
|
220
|
+
{isDeprecated && (
|
|
221
|
+
<span class="flex items-center gap-1.5 px-3 py-1.5 bg-[rgb(var(--ec-badge-event-bg))] text-[rgb(var(--ec-badge-event-text))] text-xs font-medium">
|
|
222
|
+
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
223
|
+
<path
|
|
224
|
+
stroke-linecap="round"
|
|
225
|
+
stroke-linejoin="round"
|
|
226
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
227
|
+
/>
|
|
228
|
+
</svg>
|
|
229
|
+
Deprecated
|
|
230
|
+
</span>
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
<span class="block p-3">
|
|
234
|
+
{/* Header: Name with icon top-right */}
|
|
235
|
+
<span class="flex justify-between items-start mb-2">
|
|
236
|
+
<span class="flex flex-col">
|
|
237
|
+
<span class="flex items-baseline gap-2">
|
|
238
|
+
<span class="font-semibold text-[rgb(var(--ec-page-text))] text-[0.95rem] leading-tight">
|
|
239
|
+
{resource?.data?.name || resourceId}
|
|
240
|
+
</span>
|
|
241
|
+
<span class="text-[0.65rem] text-[rgb(var(--ec-page-text-muted))] uppercase tracking-wide">{style.label}</span>
|
|
242
|
+
</span>
|
|
243
|
+
<span class="text-[0.7rem] font-mono text-[rgb(var(--ec-page-text-muted))] mt-0.5">
|
|
244
|
+
v{resource?.data?.version}
|
|
245
|
+
</span>
|
|
246
|
+
</span>
|
|
247
|
+
<svg
|
|
248
|
+
class="w-5 h-5 flex-shrink-0"
|
|
249
|
+
style={`color: rgb(${style.borderColor});`}
|
|
250
|
+
fill="none"
|
|
251
|
+
viewBox="0 0 24 24"
|
|
252
|
+
stroke="currentColor"
|
|
253
|
+
stroke-width="1.5"
|
|
254
|
+
set:html={iconPath}
|
|
255
|
+
/>
|
|
256
|
+
</span>
|
|
257
|
+
|
|
258
|
+
{/* Summary */}
|
|
259
|
+
{truncatedSummary && (
|
|
260
|
+
<span class="block text-[rgb(var(--ec-page-text-muted))] text-[0.8rem] leading-relaxed mb-3">
|
|
261
|
+
{truncatedSummary}
|
|
262
|
+
</span>
|
|
263
|
+
)}
|
|
264
|
+
|
|
265
|
+
{/* Metadata */}
|
|
266
|
+
{(isService && (sends.length > 0 || receives.length > 0)) || hasSpecifications || owners.length > 0 ? (
|
|
267
|
+
<span class="block text-[0.7rem] mb-3 space-y-1">
|
|
268
|
+
{isService && sends.length > 0 && (
|
|
269
|
+
<span class="flex items-baseline gap-2">
|
|
270
|
+
<span class="text-[rgb(var(--ec-page-text-muted))]">Publishes</span>
|
|
271
|
+
<span class="font-mono text-[rgb(var(--ec-page-text))]">
|
|
272
|
+
{sendsWithVersions.slice(0, 2).map((msg: any, idx: number) => (
|
|
273
|
+
<>
|
|
274
|
+
{msg.resolvedVersion && msg.resolvedCollection ? (
|
|
275
|
+
<a
|
|
276
|
+
href={buildUrl(`/docs/${msg.resolvedCollection}/${msg.id}/${msg.resolvedVersion}`)}
|
|
277
|
+
class="hover:underline hover:text-[rgb(var(--ec-accent))]"
|
|
278
|
+
>
|
|
279
|
+
{msg.id}
|
|
280
|
+
</a>
|
|
281
|
+
) : (
|
|
282
|
+
<span class="text-[rgb(var(--ec-page-text-muted))]">{msg.id}</span>
|
|
283
|
+
)}
|
|
284
|
+
{idx < Math.min(sendsWithVersions.length, 2) - 1 && ', '}
|
|
285
|
+
</>
|
|
286
|
+
))}
|
|
287
|
+
{sends.length > 2 && <span class="text-[rgb(var(--ec-page-text-muted))]"> +{sends.length - 2}</span>}
|
|
288
|
+
</span>
|
|
289
|
+
</span>
|
|
290
|
+
)}
|
|
291
|
+
{isService && receives.length > 0 && (
|
|
292
|
+
<span class="flex items-baseline gap-2">
|
|
293
|
+
<span class="text-[rgb(var(--ec-page-text-muted))]">Subscribes</span>
|
|
294
|
+
<span class="font-mono text-[rgb(var(--ec-page-text))]">
|
|
295
|
+
{receivesWithVersions.slice(0, 2).map((msg: any, idx: number) => (
|
|
296
|
+
<>
|
|
297
|
+
{msg.resolvedVersion && msg.resolvedCollection ? (
|
|
298
|
+
<a
|
|
299
|
+
href={buildUrl(`/docs/${msg.resolvedCollection}/${msg.id}/${msg.resolvedVersion}`)}
|
|
300
|
+
class="hover:underline hover:text-[rgb(var(--ec-accent))]"
|
|
301
|
+
>
|
|
302
|
+
{msg.id}
|
|
303
|
+
</a>
|
|
304
|
+
) : (
|
|
305
|
+
<span class="text-[rgb(var(--ec-page-text-muted))]">{msg.id}</span>
|
|
306
|
+
)}
|
|
307
|
+
{idx < Math.min(receivesWithVersions.length, 2) - 1 && ', '}
|
|
308
|
+
</>
|
|
309
|
+
))}
|
|
310
|
+
{receives.length > 2 && <span class="text-[rgb(var(--ec-page-text-muted))]"> +{receives.length - 2}</span>}
|
|
311
|
+
</span>
|
|
312
|
+
</span>
|
|
313
|
+
)}
|
|
314
|
+
{hasSpecifications && (
|
|
315
|
+
<span class="flex items-baseline gap-2">
|
|
316
|
+
<span class="text-[rgb(var(--ec-page-text-muted))]">APIs</span>
|
|
317
|
+
<span class="font-mono text-[rgb(var(--ec-page-text))]">
|
|
318
|
+
{specifications.map((spec: any, idx: number) => (
|
|
319
|
+
<>
|
|
320
|
+
<a
|
|
321
|
+
href={getSpecUrl(spec, resourceId, resource?.data?.version)}
|
|
322
|
+
class="hover:underline hover:text-[rgb(var(--ec-accent))]"
|
|
323
|
+
>
|
|
324
|
+
{getSpecLabel(spec.type)}
|
|
325
|
+
</a>
|
|
326
|
+
{idx < specifications.length - 1 && ', '}
|
|
327
|
+
</>
|
|
328
|
+
))}
|
|
329
|
+
</span>
|
|
330
|
+
</span>
|
|
331
|
+
)}
|
|
332
|
+
{owners.length > 0 && (
|
|
333
|
+
<span class="flex items-baseline gap-2">
|
|
334
|
+
<span class="text-[rgb(var(--ec-page-text-muted))]">Owner</span>
|
|
335
|
+
<span class="font-mono text-[rgb(var(--ec-page-text))]">
|
|
336
|
+
{owners.map((o: any, idx: number) => {
|
|
337
|
+
const ownerId = typeof o === 'string' ? o : o.id;
|
|
338
|
+
return (
|
|
339
|
+
<>
|
|
340
|
+
<a
|
|
341
|
+
href={buildUrl(`/docs/users/${ownerId}`)}
|
|
342
|
+
class="hover:underline hover:text-[rgb(var(--ec-accent))]"
|
|
343
|
+
>
|
|
344
|
+
{ownerId}
|
|
345
|
+
</a>
|
|
346
|
+
{idx < owners.length - 1 && ', '}
|
|
347
|
+
</>
|
|
348
|
+
);
|
|
349
|
+
})}
|
|
350
|
+
</span>
|
|
351
|
+
</span>
|
|
352
|
+
)}
|
|
353
|
+
</span>
|
|
354
|
+
) : null}
|
|
355
|
+
|
|
356
|
+
{/* Actions */}
|
|
357
|
+
<span class="flex items-center justify-between pt-2 border-t border-[rgb(var(--ec-page-border))] text-[0.7rem]">
|
|
358
|
+
<span class="flex items-center gap-3">
|
|
359
|
+
{type === 'flow' && (
|
|
360
|
+
<a
|
|
361
|
+
href={buildUrl(`/visualiser/${collection}/${resourceId}/${resource?.data?.version}`)}
|
|
362
|
+
class="text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]"
|
|
363
|
+
>
|
|
364
|
+
View flow
|
|
365
|
+
</a>
|
|
366
|
+
)}
|
|
367
|
+
{hasSchema && (
|
|
368
|
+
<a
|
|
369
|
+
href={buildUrl(`/schemas/${collection}/${resourceId}/${resource?.data?.version}`)}
|
|
370
|
+
class="text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]"
|
|
371
|
+
>
|
|
372
|
+
Schema
|
|
373
|
+
</a>
|
|
374
|
+
)}
|
|
375
|
+
{hasVisualizer && type !== 'flow' && (
|
|
376
|
+
<a
|
|
377
|
+
href={buildUrl(`/visualiser/${collection}/${resourceId}/${resource?.data?.version}`)}
|
|
378
|
+
class="text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]"
|
|
379
|
+
>
|
|
380
|
+
Map
|
|
381
|
+
</a>
|
|
382
|
+
)}
|
|
383
|
+
{repositoryUrl && (
|
|
384
|
+
<a
|
|
385
|
+
href={repositoryUrl}
|
|
386
|
+
target="_blank"
|
|
387
|
+
rel="noopener noreferrer"
|
|
388
|
+
class="text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]"
|
|
389
|
+
>
|
|
390
|
+
Repo
|
|
391
|
+
</a>
|
|
392
|
+
)}
|
|
393
|
+
</span>
|
|
394
|
+
<a href={href} class="text-[rgb(var(--ec-accent))] hover:underline font-medium">
|
|
395
|
+
{type === 'diagram' ? 'View diagram' : 'View docs'}
|
|
396
|
+
</a>
|
|
397
|
+
</span>
|
|
398
|
+
</span>
|
|
399
|
+
</span>
|
|
400
|
+
</>
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
</span>
|
|
404
|
+
|
|
405
|
+
<script>
|
|
406
|
+
// Initialize all resource ref tooltips
|
|
407
|
+
function initResourceRefTooltips() {
|
|
408
|
+
const triggers = document.querySelectorAll('.resource-ref-trigger');
|
|
409
|
+
|
|
410
|
+
triggers.forEach((trigger) => {
|
|
411
|
+
const tooltipId = trigger.getAttribute('data-tooltip-id');
|
|
412
|
+
if (!tooltipId) return;
|
|
413
|
+
|
|
414
|
+
const tooltip = document.getElementById(tooltipId);
|
|
415
|
+
if (!tooltip) return;
|
|
416
|
+
|
|
417
|
+
let hideTimeout: ReturnType<typeof setTimeout>;
|
|
418
|
+
|
|
419
|
+
const showTooltip = () => {
|
|
420
|
+
clearTimeout(hideTimeout);
|
|
421
|
+
|
|
422
|
+
// Get trigger position
|
|
423
|
+
const rect = trigger.getBoundingClientRect();
|
|
424
|
+
const tooltipWidth = 320; // w-80 = 20rem = 320px
|
|
425
|
+
const padding = 16;
|
|
426
|
+
|
|
427
|
+
// Calculate left position - try to align with trigger, but keep within viewport
|
|
428
|
+
let left = rect.left;
|
|
429
|
+
|
|
430
|
+
// If tooltip would overflow right edge, align to right edge of viewport
|
|
431
|
+
if (left + tooltipWidth > window.innerWidth - padding) {
|
|
432
|
+
left = window.innerWidth - tooltipWidth - padding;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// If tooltip would overflow left edge, align to left edge
|
|
436
|
+
if (left < padding) {
|
|
437
|
+
left = padding;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Position below the trigger
|
|
441
|
+
const top = rect.bottom + 8;
|
|
442
|
+
|
|
443
|
+
tooltip.style.left = `${left}px`;
|
|
444
|
+
tooltip.style.top = `${top}px`;
|
|
445
|
+
tooltip.classList.remove('opacity-0', 'pointer-events-none', 'scale-95');
|
|
446
|
+
tooltip.classList.add('opacity-100', 'pointer-events-auto', 'scale-100');
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const hideTooltip = () => {
|
|
450
|
+
hideTimeout = setTimeout(() => {
|
|
451
|
+
tooltip.classList.remove('opacity-100', 'pointer-events-auto', 'scale-100');
|
|
452
|
+
tooltip.classList.add('opacity-0', 'pointer-events-none', 'scale-95');
|
|
453
|
+
}, 100);
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const keepTooltipOpen = () => {
|
|
457
|
+
clearTimeout(hideTimeout);
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// Trigger events
|
|
461
|
+
trigger.addEventListener('mouseenter', showTooltip);
|
|
462
|
+
trigger.addEventListener('mouseleave', hideTooltip);
|
|
463
|
+
trigger.addEventListener('focus', showTooltip);
|
|
464
|
+
trigger.addEventListener('blur', hideTooltip);
|
|
465
|
+
|
|
466
|
+
// Tooltip events - keep open when hovering tooltip
|
|
467
|
+
tooltip.addEventListener('mouseenter', keepTooltipOpen);
|
|
468
|
+
tooltip.addEventListener('mouseleave', hideTooltip);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Run on initial load
|
|
473
|
+
initResourceRefTooltips();
|
|
474
|
+
|
|
475
|
+
// Re-run after Astro page transitions
|
|
476
|
+
document.addEventListener('astro:page-load', initResourceRefTooltips);
|
|
477
|
+
</script>
|
|
@@ -20,6 +20,7 @@ import EntityPropertiesTable from '@components/MDX/EntityPropertiesTable/EntityP
|
|
|
20
20
|
import Tabs from '@components/MDX/Tabs/Tabs.astro';
|
|
21
21
|
import TabItem from '@components/MDX/Tabs/TabItem.astro';
|
|
22
22
|
import ResourceLink from '@components/MDX/ResourceLink/ResourceLink.astro';
|
|
23
|
+
import ResourceRef from '@components/MDX/ResourceRef/ResourceRef.astro';
|
|
23
24
|
import Link from '@components/MDX/Link/Link.astro';
|
|
24
25
|
import Miro from '@components/MDX/Miro/Miro.astro';
|
|
25
26
|
import Lucid from '@components/MDX/Lucid/Lucid.astro';
|
|
@@ -54,6 +55,7 @@ const components = (props: any) => {
|
|
|
54
55
|
OpenAPI,
|
|
55
56
|
ResourceGroupTable: (mdxProp: any) => jsx(ResourceGroupTable, { ...props, ...mdxProp }),
|
|
56
57
|
ResourceLink: (mdxProp: any) => jsx(ResourceLink, { ...props, ...mdxProp }),
|
|
58
|
+
ResourceRef: (mdxProp: any) => jsx(ResourceRef, { ...props, ...mdxProp }),
|
|
57
59
|
Schema: (mdxProp: any) => jsx(Schema, { ...props, ...mdxProp }),
|
|
58
60
|
SchemaViewer: (mdxProp: any) => SchemaViewerPortal({ ...props.data, ...mdxProp }),
|
|
59
61
|
Step,
|
|
@@ -5,21 +5,33 @@
|
|
|
5
5
|
* This component loads the sidebar/search data independently of the sidebar UI.
|
|
6
6
|
* It ensures the search functionality works on all pages, even when the nested
|
|
7
7
|
* sidebar is not rendered (e.g., /discover pages).
|
|
8
|
+
*
|
|
9
|
+
* The data is fetched from a static JSON file (/api/sidebar-data.json) instead of
|
|
10
|
+
* being embedded inline in every HTML page. This significantly reduces build size
|
|
11
|
+
* for large catalogs.
|
|
8
12
|
*/
|
|
9
|
-
import { getNestedSideBarData } from '@stores/sidebar-store/state';
|
|
10
|
-
|
|
11
|
-
const props = await getNestedSideBarData();
|
|
12
13
|
---
|
|
13
14
|
|
|
14
|
-
<script is:inline define:vars={{ props }}>
|
|
15
|
-
window.sidebarData = props;
|
|
16
|
-
</script>
|
|
17
|
-
|
|
18
15
|
<script>
|
|
19
|
-
import { setSidebarData } from '@stores/sidebar-store';
|
|
16
|
+
import { setSidebarData, sidebarStore } from '@stores/sidebar-store';
|
|
17
|
+
import { buildUrl } from '@utils/url-builder';
|
|
18
|
+
|
|
19
|
+
// Only fetch if we haven't already loaded the data
|
|
20
|
+
if (!sidebarStore.get()) {
|
|
21
|
+
const apiUrl = buildUrl('/api/sidebar-data.json', true);
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
fetch(apiUrl)
|
|
24
|
+
.then((response) => {
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(`Failed to fetch sidebar data: ${response.status}`);
|
|
27
|
+
}
|
|
28
|
+
return response.json();
|
|
29
|
+
})
|
|
30
|
+
.then((data) => {
|
|
31
|
+
setSidebarData(data);
|
|
32
|
+
})
|
|
33
|
+
.catch((error) => {
|
|
34
|
+
console.error('Error loading sidebar data:', error);
|
|
35
|
+
});
|
|
24
36
|
}
|
|
25
37
|
</script>
|
|
@@ -135,6 +135,9 @@ export default function SearchModal() {
|
|
|
135
135
|
// Extract all items from nodes
|
|
136
136
|
const allItems = Object.entries(data.nodes)
|
|
137
137
|
.map(([key, node]) => {
|
|
138
|
+
// Skip reference entries (string values that point to other keys)
|
|
139
|
+
if (typeof node === 'string') return null;
|
|
140
|
+
|
|
138
141
|
const url = getUrlForItem(node, key);
|
|
139
142
|
if (!url) return null;
|
|
140
143
|
|
|
@@ -232,6 +235,18 @@ export default function SearchModal() {
|
|
|
232
235
|
});
|
|
233
236
|
};
|
|
234
237
|
|
|
238
|
+
// Helper to resolve a node, following references if needed
|
|
239
|
+
const resolveNode = (key: string) => {
|
|
240
|
+
if (!data?.nodes) return null;
|
|
241
|
+
const node = data.nodes[key];
|
|
242
|
+
if (!node) return null;
|
|
243
|
+
// If it's a string reference, follow it
|
|
244
|
+
if (typeof node === 'string') {
|
|
245
|
+
return data.nodes[node] as NavNode | undefined;
|
|
246
|
+
}
|
|
247
|
+
return node;
|
|
248
|
+
};
|
|
249
|
+
|
|
235
250
|
const filteredItems = useMemo(() => {
|
|
236
251
|
if (query === '') {
|
|
237
252
|
// Show favorites when search is empty
|
|
@@ -239,8 +254,8 @@ export default function SearchModal() {
|
|
|
239
254
|
return favorites
|
|
240
255
|
.slice(0, 5)
|
|
241
256
|
.map((fav) => {
|
|
242
|
-
const node =
|
|
243
|
-
if (!node) return null;
|
|
257
|
+
const node = resolveNode(fav.nodeKey);
|
|
258
|
+
if (!node || typeof node === 'string') return null;
|
|
244
259
|
const url = getUrlForItem(node, fav.nodeKey);
|
|
245
260
|
if (!url) return null;
|
|
246
261
|
|
|
@@ -29,7 +29,7 @@ type SearchResult = {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
type Props = {
|
|
32
|
-
nodes: Record<string, NavNode>;
|
|
32
|
+
nodes: Record<string, NavNode | string>;
|
|
33
33
|
onSelectResult: (nodeKey: string, node: NavNode) => void;
|
|
34
34
|
onSearchChange?: (isSearching: boolean) => void;
|
|
35
35
|
};
|
|
@@ -41,14 +41,20 @@ export default function SearchBar({ nodes, onSelectResult, onSearchChange }: Pro
|
|
|
41
41
|
|
|
42
42
|
// Pre-process searchable nodes to avoid iterating object on every render
|
|
43
43
|
// Filter out unversioned keys (e.g., "domain:OrderService") to avoid duplicates with versioned keys (e.g., "domain:OrderService:1.0.0")
|
|
44
|
-
const searchableNodes = useMemo(() => {
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const searchableNodes = useMemo((): Array<[string, NavNode]> => {
|
|
45
|
+
const result: Array<[string, NavNode]> = [];
|
|
46
|
+
for (const [key, node] of Object.entries(nodes)) {
|
|
47
|
+
// Skip string references (unversioned aliases that point to versioned keys)
|
|
48
|
+
if (typeof node === 'string') continue;
|
|
49
|
+
if (node.type === 'group') continue;
|
|
47
50
|
// Only include versioned keys (those with 3+ parts like "type:id:version")
|
|
48
51
|
// Unversioned keys (2 parts like "type:id") are aliases to latest version and would cause duplicates
|
|
49
52
|
const keyParts = key.split(':');
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
if (keyParts.length >= 3) {
|
|
54
|
+
result.push([key, node]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
52
58
|
}, [nodes]);
|
|
53
59
|
|
|
54
60
|
// Get available badges from nodes
|