@eventcatalog/core 5.0.0-beta.1 → 5.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-AYNZXUB5.js → chunk-3PKS2IIP.js} +4 -4
- package/dist/{chunk-Q6SD5SNQ.js → chunk-423S2KHN.js} +1 -1
- package/dist/{chunk-UDS3SYLZ.js → chunk-6OC46WQJ.js} +1 -1
- package/dist/{chunk-K2ACDO7Y.js → chunk-BH2F5WPN.js} +1 -1
- package/dist/{chunk-S2QGT7ZF.js → chunk-GYIVJXOJ.js} +1 -1
- package/dist/{chunk-KYJZJLBD.js → chunk-NDJOAITI.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 +13 -13
- package/dist/federation/federate.js +4 -4
- package/dist/federation/source-provider.js +2 -2
- 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/src/components/Grids/utils.tsx +0 -23
- package/eventcatalog/src/components/MDX/CodeGroup/CodeGroup.tsx +6 -1
- package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +12 -4
- package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +4 -62
- package/eventcatalog/src/components/Tables/Discover/columns.tsx +17 -150
- package/eventcatalog/src/components/Tables/Resources/ResourcesTable.tsx +425 -0
- package/eventcatalog/src/components/Tables/RowActionsMenu.tsx +157 -0
- package/eventcatalog/src/components/Tables/TablePagination.tsx +82 -0
- package/eventcatalog/src/features/ai/chat-api.ts +3 -3
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +75 -74
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +0 -2
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.spec.ts +52 -63
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +34 -46
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +11 -29
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_ResourcesPage.astro +79 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.spec.ts +32 -14
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.ts +63 -58
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +2 -62
- package/eventcatalog/src/pages/docs/services/[id]/[version]/resources/index.astro +13 -0
- package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +24 -42
- package/eventcatalog/src/stores/sidebar-store/builders/flow.ts +2 -0
- package/eventcatalog/src/stores/sidebar-store/builders/message.ts +2 -0
- package/eventcatalog/src/stores/sidebar-store/builders/service.ts +23 -23
- package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +13 -0
- package/eventcatalog/src/stores/sidebar-store/builders/system.ts +18 -27
- package/eventcatalog/src/stores/sidebar-store/state.ts +5 -0
- package/eventcatalog/src/styles/tailwind.css +2 -2
- package/eventcatalog/src/utils/collections/channels.ts +56 -1
- package/eventcatalog/src/utils/collections/flows.ts +50 -1
- package/eventcatalog/src/utils/collections/message-connections.ts +60 -0
- package/eventcatalog/src/utils/collections/message-triggers.ts +2 -1
- package/eventcatalog/src/utils/collections/resource-owners.ts +193 -0
- package/eventcatalog/src/utils/collections/resources.ts +310 -0
- package/eventcatalog/src/utils/collections/util.ts +31 -0
- package/package.json +7 -7
- package/eventcatalog/src/components/Grids/DomainGrid.tsx +0 -838
- package/eventcatalog/src/components/Grids/MessageGrid.tsx +0 -344
- package/eventcatalog/src/components/Grids/SystemGrid.tsx +0 -185
- package/eventcatalog/src/components/Grids/components.tsx +0 -170
- package/eventcatalog/src/components/Grids/message-link.ts +0 -38
- package/eventcatalog/src/components/Tables/SystemResources/SystemResourcesTable.tsx +0 -294
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_resources.spec.ts +0 -65
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_resources.ts +0 -46
- package/dist/{chunk-A2RZR3U4.js → chunk-6K7XZAYI.js} +3 -3
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
MagnifyingGlassIcon,
|
|
4
|
-
ChevronLeftIcon,
|
|
5
|
-
ChevronRightIcon,
|
|
6
|
-
ChevronDoubleLeftIcon,
|
|
7
|
-
ChevronDoubleRightIcon,
|
|
8
|
-
} from '@heroicons/react/24/outline';
|
|
9
|
-
import type { CollectionMessageTypes } from '@types';
|
|
10
|
-
import { getCollectionStyles, type PaginationProps, type SearchBarProps, type TypeFilterProps } from './utils';
|
|
11
|
-
|
|
12
|
-
export function SearchBar({ searchQuery, onSearchChange, placeholder, totalResults, totalItems }: SearchBarProps) {
|
|
13
|
-
return (
|
|
14
|
-
<div className="w-full md:w-96">
|
|
15
|
-
<div className="relative">
|
|
16
|
-
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
|
17
|
-
<MagnifyingGlassIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
|
18
|
-
</div>
|
|
19
|
-
<input
|
|
20
|
-
type="text"
|
|
21
|
-
placeholder={placeholder || 'Search...'}
|
|
22
|
-
value={searchQuery}
|
|
23
|
-
onChange={(e) => onSearchChange(e.target.value)}
|
|
24
|
-
className="block w-full rounded-lg border-0 py-2.5 pl-10 pr-4 text-gray-900 shadow-xs ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-primary sm:text-sm sm:leading-6"
|
|
25
|
-
/>
|
|
26
|
-
{searchQuery && (
|
|
27
|
-
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
|
28
|
-
<button onClick={() => onSearchChange('')} className="text-gray-400 hover:text-gray-500 focus:outline-hidden">
|
|
29
|
-
<span className="sr-only">Clear search</span>
|
|
30
|
-
<svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
31
|
-
<path
|
|
32
|
-
fillRule="evenodd"
|
|
33
|
-
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
34
|
-
clipRule="evenodd"
|
|
35
|
-
/>
|
|
36
|
-
</svg>
|
|
37
|
-
</button>
|
|
38
|
-
</div>
|
|
39
|
-
)}
|
|
40
|
-
</div>
|
|
41
|
-
{searchQuery && totalResults !== undefined && totalItems !== undefined && (
|
|
42
|
-
<div className="mt-2 text-sm text-gray-500 flex items-center justify-between">
|
|
43
|
-
<span>
|
|
44
|
-
Found <span className="font-medium text-gray-900">{totalResults}</span> of{' '}
|
|
45
|
-
<span className="font-medium text-gray-900">{totalItems}</span>
|
|
46
|
-
</span>
|
|
47
|
-
<span className="text-gray-400 text-xs">ESC to clear</span>
|
|
48
|
-
</div>
|
|
49
|
-
)}
|
|
50
|
-
</div>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function TypeFilters({ selectedTypes, onTypeChange, filteredCount, totalCount }: TypeFilterProps) {
|
|
55
|
-
const types: CollectionMessageTypes[] = ['events', 'commands', 'queries'];
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<div className="flex items-center gap-2">
|
|
59
|
-
{types.map((type) => {
|
|
60
|
-
const { color, Icon } = getCollectionStyles(type);
|
|
61
|
-
const isSelected = selectedTypes.includes(type);
|
|
62
|
-
return (
|
|
63
|
-
<button
|
|
64
|
-
key={type}
|
|
65
|
-
onClick={() => {
|
|
66
|
-
onTypeChange(selectedTypes.includes(type) ? selectedTypes.filter((t) => t !== type) : [...selectedTypes, type]);
|
|
67
|
-
}}
|
|
68
|
-
className={`
|
|
69
|
-
inline-flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium
|
|
70
|
-
transition-colors duration-200
|
|
71
|
-
${
|
|
72
|
-
isSelected
|
|
73
|
-
? `bg-${color}-100 text-${color}-700 ring-2 ring-${color}-500`
|
|
74
|
-
: 'bg-gray-50 text-gray-600 hover:bg-gray-100'
|
|
75
|
-
}
|
|
76
|
-
`}
|
|
77
|
-
>
|
|
78
|
-
<Icon className={`h-4 w-4 ${isSelected ? `text-${color}-500` : 'text-gray-400'}`} />
|
|
79
|
-
<span className="capitalize">{type}</span>
|
|
80
|
-
{isSelected && filteredCount !== undefined && (
|
|
81
|
-
<span
|
|
82
|
-
className={`inline-flex items-center justify-center px-2 py-0.5 text-xs font-medium bg-${color}-50 text-${color}-700 rounded-full`}
|
|
83
|
-
>
|
|
84
|
-
{filteredCount}
|
|
85
|
-
</span>
|
|
86
|
-
)}
|
|
87
|
-
</button>
|
|
88
|
-
);
|
|
89
|
-
})}
|
|
90
|
-
{selectedTypes.length > 0 && (
|
|
91
|
-
<button onClick={() => onTypeChange([])} className="text-xs text-gray-500 hover:text-gray-700 hover:underline">
|
|
92
|
-
Clear filters
|
|
93
|
-
</button>
|
|
94
|
-
)}
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function Pagination({ currentPage, totalPages, totalItems, itemsPerPage, onPageChange }: PaginationProps) {
|
|
100
|
-
if (totalPages <= 1) return null;
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<div className="flex items-center justify-between border-gray-200 bg-white px-4 py-3 sm:px-6">
|
|
104
|
-
<div className="flex flex-1 justify-between sm:hidden">
|
|
105
|
-
<button
|
|
106
|
-
onClick={() => onPageChange(Math.max(1, currentPage - 1))}
|
|
107
|
-
disabled={currentPage === 1}
|
|
108
|
-
className="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
|
|
109
|
-
>
|
|
110
|
-
Previous
|
|
111
|
-
</button>
|
|
112
|
-
<button
|
|
113
|
-
onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}
|
|
114
|
-
disabled={currentPage === totalPages}
|
|
115
|
-
className="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
|
|
116
|
-
>
|
|
117
|
-
Next
|
|
118
|
-
</button>
|
|
119
|
-
</div>
|
|
120
|
-
<div className="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
|
|
121
|
-
<div className="pr-4">
|
|
122
|
-
<p className="text-sm text-gray-700">
|
|
123
|
-
Showing <span className="font-medium">{(currentPage - 1) * itemsPerPage + 1}</span> to{' '}
|
|
124
|
-
<span className="font-medium">{Math.min(currentPage * itemsPerPage, totalItems)}</span> of{' '}
|
|
125
|
-
<span className="font-medium">{totalItems}</span> results
|
|
126
|
-
</p>
|
|
127
|
-
</div>
|
|
128
|
-
<div>
|
|
129
|
-
<nav className="isolate inline-flex -space-x-px rounded-md shadow-xs" aria-label="Pagination">
|
|
130
|
-
<button
|
|
131
|
-
onClick={() => onPageChange(1)}
|
|
132
|
-
disabled={currentPage === 1}
|
|
133
|
-
className="relative inline-flex items-center rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0 disabled:opacity-50"
|
|
134
|
-
>
|
|
135
|
-
<span className="sr-only">First</span>
|
|
136
|
-
<ChevronDoubleLeftIcon className="h-5 w-5" aria-hidden="true" />
|
|
137
|
-
</button>
|
|
138
|
-
<button
|
|
139
|
-
onClick={() => onPageChange(Math.max(1, currentPage - 1))}
|
|
140
|
-
disabled={currentPage === 1}
|
|
141
|
-
className="relative inline-flex items-center px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0 disabled:opacity-50"
|
|
142
|
-
>
|
|
143
|
-
<span className="sr-only">Previous</span>
|
|
144
|
-
<ChevronLeftIcon className="h-5 w-5" aria-hidden="true" />
|
|
145
|
-
</button>
|
|
146
|
-
<span className="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 focus:outline-offset-0">
|
|
147
|
-
Page {currentPage} of {totalPages}
|
|
148
|
-
</span>
|
|
149
|
-
<button
|
|
150
|
-
onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}
|
|
151
|
-
disabled={currentPage === totalPages}
|
|
152
|
-
className="relative inline-flex items-center px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0 disabled:opacity-50"
|
|
153
|
-
>
|
|
154
|
-
<span className="sr-only">Next</span>
|
|
155
|
-
<ChevronRightIcon className="h-5 w-5" aria-hidden="true" />
|
|
156
|
-
</button>
|
|
157
|
-
<button
|
|
158
|
-
onClick={() => onPageChange(totalPages)}
|
|
159
|
-
disabled={currentPage === totalPages}
|
|
160
|
-
className="relative inline-flex items-center rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0 disabled:opacity-50"
|
|
161
|
-
>
|
|
162
|
-
<span className="sr-only">Last</span>
|
|
163
|
-
<ChevronDoubleRightIcon className="h-5 w-5" aria-hidden="true" />
|
|
164
|
-
</button>
|
|
165
|
-
</nav>
|
|
166
|
-
</div>
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
169
|
-
);
|
|
170
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { CollectionMessageTypes } from '@types';
|
|
2
|
-
|
|
3
|
-
export const MESSAGE_COLLECTIONS: readonly CollectionMessageTypes[] = ['events', 'commands', 'queries'];
|
|
4
|
-
|
|
5
|
-
export function isMessageCollection(value: unknown): value is CollectionMessageTypes {
|
|
6
|
-
return value === 'events' || value === 'commands' || value === 'queries';
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface MessageLinkProps {
|
|
10
|
-
collection?: CollectionMessageTypes;
|
|
11
|
-
id?: string;
|
|
12
|
-
name?: string;
|
|
13
|
-
version: string;
|
|
14
|
-
href?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Resolve a DomainGrid/SystemGrid receives/sends item into a docs link.
|
|
19
|
-
*
|
|
20
|
-
* Hydrated collection entries carry `collection` + `data.name`. Bare `{id, version}`
|
|
21
|
-
* pointers do not — never guess `events` for those, or commands/queries get the wrong URL.
|
|
22
|
-
*/
|
|
23
|
-
export function getMessageLinkProps(message: any): MessageLinkProps {
|
|
24
|
-
const data = message?.data ?? message;
|
|
25
|
-
const collectionCandidate = message?.collection ?? data?.collection;
|
|
26
|
-
const collection = isMessageCollection(collectionCandidate) ? collectionCandidate : undefined;
|
|
27
|
-
const id = data?.id ?? message?.id;
|
|
28
|
-
const name = data?.name || message?.name || id;
|
|
29
|
-
const version = data?.version ?? message?.version ?? 'latest';
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
collection,
|
|
33
|
-
id,
|
|
34
|
-
name,
|
|
35
|
-
version,
|
|
36
|
-
href: collection && id ? `/docs/${collection}/${id}/${version}` : undefined,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import { useMemo, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
createColumnHelper,
|
|
4
|
-
flexRender,
|
|
5
|
-
getCoreRowModel,
|
|
6
|
-
getSortedRowModel,
|
|
7
|
-
useReactTable,
|
|
8
|
-
type SortingState,
|
|
9
|
-
} from '@tanstack/react-table';
|
|
10
|
-
import { ArrowDown, ArrowUp, ArrowUpDown, Search, SearchX } from 'lucide-react';
|
|
11
|
-
import { getColorAndIconForCollection } from '@utils/collections/icons';
|
|
12
|
-
import { getCollectionTextColorClass } from '@utils/collection-colors';
|
|
13
|
-
import { isIconPath, resolveIconUrl } from '@utils/icon';
|
|
14
|
-
|
|
15
|
-
export type SystemResourceCollection =
|
|
16
|
-
| 'domains'
|
|
17
|
-
| 'systems'
|
|
18
|
-
| 'agents'
|
|
19
|
-
| 'services'
|
|
20
|
-
| 'flows'
|
|
21
|
-
| 'entities'
|
|
22
|
-
| 'data-products'
|
|
23
|
-
| 'containers'
|
|
24
|
-
| 'events'
|
|
25
|
-
| 'commands'
|
|
26
|
-
| 'queries';
|
|
27
|
-
|
|
28
|
-
export interface SystemResourceItem {
|
|
29
|
-
collection: SystemResourceCollection;
|
|
30
|
-
id: string;
|
|
31
|
-
name: string;
|
|
32
|
-
version: string;
|
|
33
|
-
summary?: string;
|
|
34
|
-
icon?: string;
|
|
35
|
-
href: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface SystemResourcesTableProps {
|
|
39
|
-
resources: SystemResourceItem[];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const TYPE_LABELS: Record<SystemResourceCollection, string> = {
|
|
43
|
-
domains: 'Subdomains',
|
|
44
|
-
systems: 'Systems',
|
|
45
|
-
agents: 'Agents',
|
|
46
|
-
services: 'Services',
|
|
47
|
-
flows: 'Flows',
|
|
48
|
-
entities: 'Entities',
|
|
49
|
-
'data-products': 'Data Products',
|
|
50
|
-
containers: 'Data Stores',
|
|
51
|
-
events: 'Events',
|
|
52
|
-
commands: 'Commands',
|
|
53
|
-
queries: 'Queries',
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
// Order the type filter pills follow on the page.
|
|
57
|
-
const TYPE_ORDER: SystemResourceCollection[] = [
|
|
58
|
-
'domains',
|
|
59
|
-
'systems',
|
|
60
|
-
'agents',
|
|
61
|
-
'services',
|
|
62
|
-
'flows',
|
|
63
|
-
'entities',
|
|
64
|
-
'data-products',
|
|
65
|
-
'containers',
|
|
66
|
-
'events',
|
|
67
|
-
'commands',
|
|
68
|
-
'queries',
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
// Small icon for a collection type (used in filter pills and the type column).
|
|
72
|
-
const CollectionTypeIcon = ({ collection, className }: { collection: SystemResourceCollection; className?: string }) => {
|
|
73
|
-
const { color, Icon } = getColorAndIconForCollection(collection);
|
|
74
|
-
const colorClass = getCollectionTextColorClass(color, 'text-[rgb(var(--ec-icon-color))]');
|
|
75
|
-
return <Icon className={`${className ?? 'h-3.5 w-3.5'} ${colorClass}`} aria-hidden="true" />;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const ResourceIcon = ({ item }: { item: SystemResourceItem }) => {
|
|
79
|
-
if (item.icon && isIconPath(item.icon)) {
|
|
80
|
-
return <img src={resolveIconUrl(item.icon)} alt="" className="h-5 w-5" loading="lazy" />;
|
|
81
|
-
}
|
|
82
|
-
return <CollectionTypeIcon collection={item.collection} className="h-5 w-5" />;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const columnHelper = createColumnHelper<SystemResourceItem>();
|
|
86
|
-
|
|
87
|
-
const SortIcon = ({ direction }: { direction: false | 'asc' | 'desc' }) => {
|
|
88
|
-
if (direction === 'asc') return <ArrowUp className="h-3.5 w-3.5" aria-hidden="true" />;
|
|
89
|
-
if (direction === 'desc') return <ArrowDown className="h-3.5 w-3.5" aria-hidden="true" />;
|
|
90
|
-
return <ArrowUpDown className="h-3.5 w-3.5 opacity-40" aria-hidden="true" />;
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
export function SystemResourcesTable({ resources }: SystemResourcesTableProps) {
|
|
94
|
-
const [search, setSearch] = useState('');
|
|
95
|
-
const [selectedTypes, setSelectedTypes] = useState<SystemResourceCollection[]>([]);
|
|
96
|
-
const [sorting, setSorting] = useState<SortingState>([{ id: 'name', desc: false }]);
|
|
97
|
-
|
|
98
|
-
// Only show type filters that actually have resources in this system.
|
|
99
|
-
const availableTypes = useMemo(
|
|
100
|
-
() => TYPE_ORDER.filter((type) => resources.some((resource) => resource.collection === type)),
|
|
101
|
-
[resources]
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
const filtered = useMemo(() => {
|
|
105
|
-
const query = search.trim().toLowerCase();
|
|
106
|
-
return resources.filter((resource) => {
|
|
107
|
-
if (selectedTypes.length > 0 && !selectedTypes.includes(resource.collection)) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
if (query) {
|
|
111
|
-
const haystack = `${resource.name} ${resource.id} ${resource.summary ?? ''}`.toLowerCase();
|
|
112
|
-
if (!haystack.includes(query)) return false;
|
|
113
|
-
}
|
|
114
|
-
return true;
|
|
115
|
-
});
|
|
116
|
-
}, [resources, search, selectedTypes]);
|
|
117
|
-
|
|
118
|
-
const columns = useMemo(
|
|
119
|
-
() => [
|
|
120
|
-
columnHelper.accessor('name', {
|
|
121
|
-
header: 'Name',
|
|
122
|
-
cell: (info) => {
|
|
123
|
-
const resource = info.row.original;
|
|
124
|
-
return (
|
|
125
|
-
<a href={resource.href} className="group flex items-center gap-3">
|
|
126
|
-
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-card-bg))]">
|
|
127
|
-
<ResourceIcon item={resource} />
|
|
128
|
-
</span>
|
|
129
|
-
<span className="text-sm font-medium text-[rgb(var(--ec-page-text))] group-hover:text-[rgb(var(--ec-accent))]">
|
|
130
|
-
{resource.name}
|
|
131
|
-
</span>
|
|
132
|
-
</a>
|
|
133
|
-
);
|
|
134
|
-
},
|
|
135
|
-
}),
|
|
136
|
-
columnHelper.accessor((row) => TYPE_LABELS[row.collection], {
|
|
137
|
-
id: 'type',
|
|
138
|
-
header: 'Type',
|
|
139
|
-
cell: (info) => {
|
|
140
|
-
const { collection } = info.row.original;
|
|
141
|
-
return (
|
|
142
|
-
<span className="inline-flex items-center gap-1.5 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
143
|
-
<CollectionTypeIcon collection={collection} />
|
|
144
|
-
{TYPE_LABELS[collection]}
|
|
145
|
-
</span>
|
|
146
|
-
);
|
|
147
|
-
},
|
|
148
|
-
}),
|
|
149
|
-
columnHelper.accessor('version', {
|
|
150
|
-
header: 'Version',
|
|
151
|
-
cell: (info) => (
|
|
152
|
-
<span className="inline-flex items-center rounded-md border border-[rgb(var(--ec-page-border))] px-2 py-0.5 text-xs text-[rgb(var(--ec-page-text-muted))]">
|
|
153
|
-
v{info.getValue()}
|
|
154
|
-
</span>
|
|
155
|
-
),
|
|
156
|
-
}),
|
|
157
|
-
columnHelper.accessor((row) => row.summary ?? '', {
|
|
158
|
-
id: 'summary',
|
|
159
|
-
header: 'Summary',
|
|
160
|
-
enableSorting: false,
|
|
161
|
-
cell: (info) => (
|
|
162
|
-
<span className="line-clamp-2 text-sm text-[rgb(var(--ec-page-text-muted))]">{info.getValue() || '-'}</span>
|
|
163
|
-
),
|
|
164
|
-
}),
|
|
165
|
-
],
|
|
166
|
-
[]
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
const table = useReactTable({
|
|
170
|
-
data: filtered,
|
|
171
|
-
columns,
|
|
172
|
-
state: { sorting },
|
|
173
|
-
onSortingChange: setSorting,
|
|
174
|
-
getCoreRowModel: getCoreRowModel(),
|
|
175
|
-
getSortedRowModel: getSortedRowModel(),
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
const toggleType = (type: SystemResourceCollection) => {
|
|
179
|
-
setSelectedTypes((prev) => (prev.includes(type) ? prev.filter((t) => t !== type) : [...prev, type]));
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
return (
|
|
183
|
-
<div className="flex flex-col gap-4">
|
|
184
|
-
{/* Controls: search + type filters */}
|
|
185
|
-
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
186
|
-
<div className="relative w-full sm:max-w-xs">
|
|
187
|
-
<Search
|
|
188
|
-
className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[rgb(var(--ec-icon-color))]"
|
|
189
|
-
aria-hidden="true"
|
|
190
|
-
/>
|
|
191
|
-
<input
|
|
192
|
-
type="text"
|
|
193
|
-
value={search}
|
|
194
|
-
onChange={(event) => setSearch(event.target.value)}
|
|
195
|
-
placeholder="Search resources..."
|
|
196
|
-
className="w-full rounded-md border border-[rgb(var(--ec-input-border))] bg-[rgb(var(--ec-input-bg))] py-2 pl-9 pr-3 text-sm text-[rgb(var(--ec-input-text))] placeholder:text-[rgb(var(--ec-page-text-muted))] focus:outline-none focus:ring-2 focus:ring-[rgb(var(--ec-accent)/0.4)]"
|
|
197
|
-
/>
|
|
198
|
-
</div>
|
|
199
|
-
|
|
200
|
-
{availableTypes.length > 1 && (
|
|
201
|
-
<div className="flex flex-wrap gap-2">
|
|
202
|
-
{availableTypes.map((type) => {
|
|
203
|
-
const active = selectedTypes.includes(type);
|
|
204
|
-
return (
|
|
205
|
-
<button
|
|
206
|
-
key={type}
|
|
207
|
-
type="button"
|
|
208
|
-
onClick={() => toggleType(type)}
|
|
209
|
-
className={[
|
|
210
|
-
'inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium transition-colors',
|
|
211
|
-
active
|
|
212
|
-
? 'border-[rgb(var(--ec-accent))] bg-[rgb(var(--ec-accent-subtle))] text-[rgb(var(--ec-accent))]'
|
|
213
|
-
: 'border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]',
|
|
214
|
-
].join(' ')}
|
|
215
|
-
>
|
|
216
|
-
<CollectionTypeIcon collection={type} />
|
|
217
|
-
{TYPE_LABELS[type]}
|
|
218
|
-
</button>
|
|
219
|
-
);
|
|
220
|
-
})}
|
|
221
|
-
</div>
|
|
222
|
-
)}
|
|
223
|
-
</div>
|
|
224
|
-
|
|
225
|
-
{/* Table */}
|
|
226
|
-
<div className="overflow-hidden rounded-lg border border-[rgb(var(--ec-page-border))]">
|
|
227
|
-
<table className="min-w-full divide-y divide-[rgb(var(--ec-page-border))]">
|
|
228
|
-
<thead className="bg-[rgb(var(--ec-card-bg))]">
|
|
229
|
-
{table.getHeaderGroups().map((headerGroup) => (
|
|
230
|
-
<tr key={headerGroup.id}>
|
|
231
|
-
{headerGroup.headers.map((header) => {
|
|
232
|
-
const canSort = header.column.getCanSort();
|
|
233
|
-
const isSummary = header.column.id === 'summary';
|
|
234
|
-
return (
|
|
235
|
-
<th
|
|
236
|
-
key={header.id}
|
|
237
|
-
className={[
|
|
238
|
-
'px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-[rgb(var(--ec-page-text-muted))]',
|
|
239
|
-
isSummary ? 'hidden md:table-cell' : '',
|
|
240
|
-
].join(' ')}
|
|
241
|
-
>
|
|
242
|
-
{canSort ? (
|
|
243
|
-
<button
|
|
244
|
-
type="button"
|
|
245
|
-
onClick={header.column.getToggleSortingHandler()}
|
|
246
|
-
className="inline-flex items-center gap-1.5 uppercase tracking-wider hover:text-[rgb(var(--ec-page-text))]"
|
|
247
|
-
>
|
|
248
|
-
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
249
|
-
<SortIcon direction={header.column.getIsSorted()} />
|
|
250
|
-
</button>
|
|
251
|
-
) : (
|
|
252
|
-
flexRender(header.column.columnDef.header, header.getContext())
|
|
253
|
-
)}
|
|
254
|
-
</th>
|
|
255
|
-
);
|
|
256
|
-
})}
|
|
257
|
-
</tr>
|
|
258
|
-
))}
|
|
259
|
-
</thead>
|
|
260
|
-
<tbody className="divide-y divide-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-page-bg))]">
|
|
261
|
-
{table.getRowModel().rows.length === 0 && (
|
|
262
|
-
<tr>
|
|
263
|
-
<td colSpan={columns.length} className="px-4 py-12">
|
|
264
|
-
<div className="flex flex-col items-center justify-center gap-2 text-center text-[rgb(var(--ec-page-text-muted))]">
|
|
265
|
-
<SearchX className="h-6 w-6" aria-hidden="true" />
|
|
266
|
-
<p className="text-sm">No resources found.</p>
|
|
267
|
-
</div>
|
|
268
|
-
</td>
|
|
269
|
-
</tr>
|
|
270
|
-
)}
|
|
271
|
-
{table.getRowModel().rows.map((row) => (
|
|
272
|
-
<tr key={row.id} className="transition-colors hover:bg-[rgb(var(--ec-card-bg))]">
|
|
273
|
-
{row.getVisibleCells().map((cell) => {
|
|
274
|
-
const isSummary = cell.column.id === 'summary';
|
|
275
|
-
return (
|
|
276
|
-
<td key={cell.id} className={['px-4 py-3', isSummary ? 'hidden max-w-md md:table-cell' : ''].join(' ')}>
|
|
277
|
-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
278
|
-
</td>
|
|
279
|
-
);
|
|
280
|
-
})}
|
|
281
|
-
</tr>
|
|
282
|
-
))}
|
|
283
|
-
</tbody>
|
|
284
|
-
</table>
|
|
285
|
-
</div>
|
|
286
|
-
|
|
287
|
-
<p className="text-xs text-[rgb(var(--ec-page-text-muted))]">
|
|
288
|
-
Showing {filtered.length} of {resources.length} resources
|
|
289
|
-
</p>
|
|
290
|
-
</div>
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
export default SystemResourcesTable;
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { getResourceGroups, hasResources } from './_resources';
|
|
3
|
-
|
|
4
|
-
const resource = (id: string, version = '1.0.0') => ({ data: { id, name: id, version } });
|
|
5
|
-
const message = (collection: 'events' | 'commands' | 'queries', id: string, version = '1.0.0') => ({
|
|
6
|
-
collection,
|
|
7
|
-
data: { id, name: id, version },
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
describe('resource page groups', () => {
|
|
11
|
-
it('includes every resource directly attached to a domain', () => {
|
|
12
|
-
const sharedEvent = message('events', 'OrderPlaced');
|
|
13
|
-
const groups = getResourceGroups(
|
|
14
|
-
{
|
|
15
|
-
domains: [resource('Fulfilment')],
|
|
16
|
-
systems: [resource('OrderingSystem')],
|
|
17
|
-
agents: [resource('OrderAgent')],
|
|
18
|
-
'data-products': [resource('OrderAnalytics')],
|
|
19
|
-
services: [resource('OrdersService')],
|
|
20
|
-
flows: [resource('PlaceOrder')],
|
|
21
|
-
entities: [resource('Order')],
|
|
22
|
-
sends: [sharedEvent, message('commands', 'PlaceOrder')],
|
|
23
|
-
receives: [sharedEvent, message('queries', 'GetOrder')],
|
|
24
|
-
},
|
|
25
|
-
'domains'
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
expect(Object.fromEntries(groups.map(({ collection, items }) => [collection, items.map((item) => item.data.id)]))).toEqual({
|
|
29
|
-
domains: ['Fulfilment'],
|
|
30
|
-
systems: ['OrderingSystem'],
|
|
31
|
-
agents: ['OrderAgent'],
|
|
32
|
-
'data-products': ['OrderAnalytics'],
|
|
33
|
-
services: ['OrdersService'],
|
|
34
|
-
flows: ['PlaceOrder'],
|
|
35
|
-
entities: ['Order'],
|
|
36
|
-
containers: [],
|
|
37
|
-
events: ['OrderPlaced'],
|
|
38
|
-
commands: ['PlaceOrder'],
|
|
39
|
-
queries: ['GetOrder'],
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('keeps domain-only resources off system resource pages', () => {
|
|
44
|
-
const collections = getResourceGroups(
|
|
45
|
-
{
|
|
46
|
-
domains: [resource('Fulfilment')],
|
|
47
|
-
systems: [resource('OrderingSystem')],
|
|
48
|
-
agents: [resource('OrderAgent')],
|
|
49
|
-
'data-products': [resource('OrderAnalytics')],
|
|
50
|
-
services: [resource('OrdersService')],
|
|
51
|
-
containers: [resource('OrdersDatabase')],
|
|
52
|
-
},
|
|
53
|
-
'systems'
|
|
54
|
-
).map(({ collection }) => collection);
|
|
55
|
-
|
|
56
|
-
expect(collections).toEqual(['services', 'flows', 'entities', 'containers', 'events', 'commands', 'queries']);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it.each(['domains', 'systems', 'agents', 'data-products'] as const)(
|
|
60
|
-
'makes a domain with only %s eligible for a Resources page',
|
|
61
|
-
(collection) => {
|
|
62
|
-
expect(hasResources({ data: { [collection]: [resource('direct-resource')] } }, 'domains')).toBe(true);
|
|
63
|
-
}
|
|
64
|
-
);
|
|
65
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { SystemResourceCollection } from '@components/Tables/SystemResources/SystemResourcesTable';
|
|
2
|
-
|
|
3
|
-
export type ResourceOwnerCollection = 'systems' | 'domains';
|
|
4
|
-
|
|
5
|
-
export interface ResourceGroup {
|
|
6
|
-
collection: SystemResourceCollection;
|
|
7
|
-
items: any[];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const getDomainMessages = (data: any) => {
|
|
11
|
-
const seen = new Map<string, any>();
|
|
12
|
-
|
|
13
|
-
for (const message of [...(data.sends || []), ...(data.receives || [])]) {
|
|
14
|
-
if (!message?.data?.id || !message?.collection) continue;
|
|
15
|
-
seen.set(`${message.collection}:${message.data.id}:${message.data.version}`, message);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return Array.from(seen.values());
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const getResourceGroups = (data: any, ownerCollection: ResourceOwnerCollection): ResourceGroup[] => {
|
|
22
|
-
const domainMessages = ownerCollection === 'domains' ? getDomainMessages(data) : [];
|
|
23
|
-
const domainOnlyGroups: ResourceGroup[] =
|
|
24
|
-
ownerCollection === 'domains'
|
|
25
|
-
? [
|
|
26
|
-
{ collection: 'domains', items: data.domains || [] },
|
|
27
|
-
{ collection: 'systems', items: data.systems || [] },
|
|
28
|
-
{ collection: 'agents', items: data.agents || [] },
|
|
29
|
-
{ collection: 'data-products', items: data['data-products'] || [] },
|
|
30
|
-
]
|
|
31
|
-
: [];
|
|
32
|
-
|
|
33
|
-
return [
|
|
34
|
-
...domainOnlyGroups,
|
|
35
|
-
{ collection: 'services', items: data.services || [] },
|
|
36
|
-
{ collection: 'flows', items: data.flows || [] },
|
|
37
|
-
{ collection: 'entities', items: data.entities || [] },
|
|
38
|
-
{ collection: 'containers', items: data.containers || [] },
|
|
39
|
-
{ collection: 'events', items: domainMessages.filter((message) => message.collection === 'events') },
|
|
40
|
-
{ collection: 'commands', items: domainMessages.filter((message) => message.collection === 'commands') },
|
|
41
|
-
{ collection: 'queries', items: domainMessages.filter((message) => message.collection === 'queries') },
|
|
42
|
-
];
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export const hasResources = (owner: any, ownerCollection: ResourceOwnerCollection): boolean =>
|
|
46
|
-
getResourceGroups(owner?.data ?? {}, ownerCollection).some(({ items }) => items.length > 0);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createFileSystemSourceProvider
|
|
3
|
-
} from "./chunk-SDZQJTJW.js";
|
|
4
1
|
import {
|
|
5
2
|
createGitHubSourceProvider
|
|
6
3
|
} from "./chunk-352FGP6W.js";
|
|
4
|
+
import {
|
|
5
|
+
createFileSystemSourceProvider
|
|
6
|
+
} from "./chunk-SDZQJTJW.js";
|
|
7
7
|
|
|
8
8
|
// src/federation/source-provider.ts
|
|
9
9
|
var createFederationSourceProvider = (projectDirectory, providers = {}) => {
|