@eventcatalog/core 2.9.1 → 2.10.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/CHANGELOG.md +12 -0
- package/package.json +4 -3
- package/scripts/catalog-to-astro-content-directory.js +1 -3
- package/scripts/default-files-for-collections/queries.md +8 -0
- package/scripts/eventcatalog-config-file-utils.js +3 -14
- package/scripts/map-catalog-to-astro.js +12 -1
- package/src/components/DiscoverInsight.astro +1 -1
- package/src/components/DocsNavigation.astro +3 -1
- package/src/components/MDX/NodeGraph/NodeGraph.astro +12 -0
- package/src/components/MDX/NodeGraph/NodeGraph.tsx +12 -6
- package/src/components/MDX/NodeGraph/Nodes/Query.tsx +74 -0
- package/src/components/SideBars/MessageSideBar.astro +34 -3
- package/src/components/Tables/columns/MessageTableColumns.tsx +16 -3
- package/src/components/Tables/columns/ServiceTableColumns.tsx +2 -3
- package/src/components/Tables/columns/index.tsx +1 -0
- package/src/content/config.ts +11 -0
- package/src/layouts/DiscoverLayout.astro +11 -0
- package/src/layouts/VisualiserLayout.astro +45 -39
- package/src/pages/discover/[type]/index.astro +1 -1
- package/src/pages/docs/[type]/[id]/[version]/asyncapi/index.astro +1 -1
- package/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +5 -2
- package/src/pages/docs/[type]/[id]/[version]/index.astro +6 -2
- package/src/pages/docs/[type]/[id]/[version]/spec/index.astro +1 -1
- package/src/pages/index.astro +12 -3
- package/src/pages/visualiser/[type]/[id]/[version]/index.astro +1 -1
- package/src/types/index.ts +3 -3
- package/src/utils/flows/node-graph.ts +5 -4
- package/src/utils/messages.ts +3 -1
- package/src/utils/node-graph-utils/utils.ts +3 -3
- package/src/utils/pages/pages.ts +2 -0
- package/src/utils/queries/node-graph.ts +118 -0
- package/src/utils/queries.ts +65 -0
- package/src/utils/services/node-graph.ts +31 -5
- package/src/utils/services/services.ts +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @eventcatalog/core
|
|
2
2
|
|
|
3
|
+
## 2.10.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e2c99e7: fix(core): fixed issue adding additional properties to users frontmatter on build
|
|
8
|
+
|
|
9
|
+
## 2.10.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- aa2090b: feat(core): added support for query message types/resources
|
|
14
|
+
|
|
3
15
|
## 2.9.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"url": "https://github.com/event-catalog/eventcatalog.git"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.10.1",
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"diff": "^7.0.0",
|
|
67
67
|
"diff2html": "^3.4.48",
|
|
68
68
|
"glob": "^10.4.1",
|
|
69
|
+
"gray-matter": "^4.0.3",
|
|
69
70
|
"html-to-image": "^1.11.11",
|
|
70
71
|
"lodash.merge": "4.6.2",
|
|
71
72
|
"mermaid": "^10.9.1",
|
|
@@ -75,12 +76,12 @@
|
|
|
75
76
|
"reactflow": "^11.11.4",
|
|
76
77
|
"rehype-slug": "^6.0.0",
|
|
77
78
|
"remark-gfm": "^3.0.1",
|
|
79
|
+
"rimraf": "^5.0.7",
|
|
78
80
|
"semver": "7.6.3",
|
|
79
81
|
"tailwindcss": "^3.4.3",
|
|
80
82
|
"typescript": "^5.4.5",
|
|
81
83
|
"unist-util-visit": "^5.0.0",
|
|
82
|
-
"uuid": "^10.0.0"
|
|
83
|
-
"rimraf": "^5.0.7"
|
|
84
|
+
"uuid": "^10.0.0"
|
|
84
85
|
},
|
|
85
86
|
"devDependencies": {
|
|
86
87
|
"@changesets/cli": "^2.27.5",
|
|
@@ -41,7 +41,7 @@ const copyFiles = async (source, target) => {
|
|
|
41
41
|
|
|
42
42
|
const ensureAstroCollectionNotEmpty = async (astroDir) => {
|
|
43
43
|
// TODO: maybe import collections from `src/content/config.ts`...
|
|
44
|
-
const COLLECTIONS = ['events', 'commands', 'services', 'users', 'teams', 'domains', 'flows', 'pages', 'changelogs'];
|
|
44
|
+
const COLLECTIONS = ['events', 'commands', 'services', 'users', 'teams', 'domains', 'flows', 'pages', 'changelogs', 'queries'];
|
|
45
45
|
|
|
46
46
|
// Check empty collections
|
|
47
47
|
const emptyCollections = [];
|
|
@@ -69,8 +69,6 @@ const ensureAstroCollectionNotEmpty = async (astroDir) => {
|
|
|
69
69
|
export const catalogToAstro = async (source, astroDir) => {
|
|
70
70
|
const astroContentDir = path.join(astroDir, 'src/content/');
|
|
71
71
|
|
|
72
|
-
console.log(path.join(astroContentDir, 'config.ts'));
|
|
73
|
-
|
|
74
72
|
// Config file
|
|
75
73
|
const astroConfigFile = fs.readFileSync(path.join(astroContentDir, 'config.ts'));
|
|
76
74
|
|
|
@@ -4,6 +4,7 @@ import { copyFile } from 'node:fs/promises';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { v4 as uuidV4 } from 'uuid';
|
|
6
6
|
import { pathToFileURL } from 'url';
|
|
7
|
+
import matter from 'gray-matter';
|
|
7
8
|
|
|
8
9
|
export async function cleanup(projectDirectory) {
|
|
9
10
|
const filePath = path.join(projectDirectory, 'eventcatalog.config.mjs');
|
|
@@ -82,19 +83,7 @@ export const verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirector
|
|
|
82
83
|
};
|
|
83
84
|
|
|
84
85
|
export function addPropertyToFrontMatter(input, newProperty, newValue) {
|
|
85
|
-
|
|
86
|
-
const [_, frontMatter, content] = input.split('---');
|
|
86
|
+
const file = matter(input);
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
const frontMatterLines = frontMatter.trim().split('\n');
|
|
90
|
-
const updatedFrontMatterLines = [...frontMatterLines];
|
|
91
|
-
|
|
92
|
-
// Add the new property
|
|
93
|
-
updatedFrontMatterLines.push(`${newProperty}: ${newValue}`);
|
|
94
|
-
|
|
95
|
-
// Reconstruct the updated input
|
|
96
|
-
const updatedFrontMatter = updatedFrontMatterLines.join('\n');
|
|
97
|
-
const updatedInput = `---\n${updatedFrontMatter}\n---\n${content.trim()}`;
|
|
98
|
-
|
|
99
|
-
return updatedInput;
|
|
88
|
+
return matter.stringify(file.content, { ...file.data, [newProperty]: newValue });
|
|
100
89
|
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
|
|
3
|
-
const COLLECTION_KEYS = [
|
|
3
|
+
const COLLECTION_KEYS = [
|
|
4
|
+
'events',
|
|
5
|
+
'commands',
|
|
6
|
+
'services',
|
|
7
|
+
'users',
|
|
8
|
+
'teams',
|
|
9
|
+
'domains',
|
|
10
|
+
'flows',
|
|
11
|
+
'pages',
|
|
12
|
+
'changelogs',
|
|
13
|
+
'queries',
|
|
14
|
+
];
|
|
4
15
|
|
|
5
16
|
/**
|
|
6
17
|
* @typedef {Object} MapCatalogToAstroParams
|
|
@@ -6,7 +6,7 @@ interface Props {
|
|
|
6
6
|
color: string;
|
|
7
7
|
dataTarget: number;
|
|
8
8
|
icon: React.ElementType;
|
|
9
|
-
label: 'domains' | 'services' | 'commands' | 'events' | 'flows';
|
|
9
|
+
label: 'domains' | 'services' | 'commands' | 'queries' | 'events' | 'flows';
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const { color, dataTarget, icon: Icon, label } = Astro.props;
|
|
@@ -8,16 +8,18 @@ import { getTeams } from '@utils/teams';
|
|
|
8
8
|
import { getUsers } from '@utils/users';
|
|
9
9
|
import config, { type CatalogConfig } from '@eventcatalog';
|
|
10
10
|
import { buildUrl } from '@utils/url-builder';
|
|
11
|
+
import { getQueries } from '@utils/queries';
|
|
11
12
|
|
|
12
13
|
const events = await getEvents({ getAllVersions: false });
|
|
13
14
|
const commands = await getCommands({ getAllVersions: false });
|
|
15
|
+
const queries = await getQueries({ getAllVersions: false });
|
|
14
16
|
const services = await getServices({ getAllVersions: false });
|
|
15
17
|
const domains = await getDomains({ getAllVersions: false });
|
|
16
18
|
const flows = await getFlows({ getAllVersions: false });
|
|
17
19
|
const teams = await getTeams();
|
|
18
20
|
const users = await getUsers();
|
|
19
21
|
|
|
20
|
-
const messages = [...events, ...commands];
|
|
22
|
+
const messages = [...events, ...commands, ...queries];
|
|
21
23
|
|
|
22
24
|
// @ts-ignore for large catalogs https://github.com/event-catalog/eventcatalog/issues/552
|
|
23
25
|
const allData = [...domains, ...services, ...messages, ...flows, ...teams, ...users];
|
|
@@ -3,6 +3,7 @@ import NodeGraphNew from './NodeGraph';
|
|
|
3
3
|
import { getNodesAndEdges as getNodesAndEdgesForService } from '@utils/services/node-graph';
|
|
4
4
|
import { getNodesAndEdges as getNodesAndEdgesForEvent } from '@utils/events/node-graph';
|
|
5
5
|
import { getNodesAndEdges as getNodesAndEdgesForCommand } from '@utils/commands/node-graph';
|
|
6
|
+
import { getNodesAndEdges as getNodesAndEdgesForQueries } from '@utils/queries/node-graph';
|
|
6
7
|
import { getNodesAndEdges as getNodesAndEdgesForDomain } from '@utils/domains/node-graph';
|
|
7
8
|
import { getNodesAndEdges as getNodesAndEdgesForFlows } from '@utils/flows/node-graph';
|
|
8
9
|
|
|
@@ -57,6 +58,17 @@ if (collection === 'commands') {
|
|
|
57
58
|
edges = eventEdges;
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
if (collection === 'queries') {
|
|
62
|
+
const { nodes: eventNodes, edges: eventEdges } = await getNodesAndEdgesForQueries({
|
|
63
|
+
id: id,
|
|
64
|
+
version,
|
|
65
|
+
mode,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
nodes = eventNodes;
|
|
69
|
+
edges = eventEdges;
|
|
70
|
+
}
|
|
71
|
+
|
|
60
72
|
if (collection === 'domains') {
|
|
61
73
|
const { nodes: eventNodes, edges: eventEdges } = await getNodesAndEdgesForDomain({
|
|
62
74
|
id: id,
|
|
@@ -15,6 +15,7 @@ import ReactFlow, {
|
|
|
15
15
|
import 'reactflow/dist/style.css';
|
|
16
16
|
import ServiceNode from './Nodes/Service';
|
|
17
17
|
import EventNode from './Nodes/Event';
|
|
18
|
+
import QueryNode from './Nodes/Query';
|
|
18
19
|
import UserNode from './Nodes/User';
|
|
19
20
|
import StepNode from './Nodes/Step';
|
|
20
21
|
import CommandNode from './Nodes/Command';
|
|
@@ -57,6 +58,7 @@ const NodeGraphBuilder = ({
|
|
|
57
58
|
() => ({
|
|
58
59
|
services: ServiceNode,
|
|
59
60
|
events: EventNode,
|
|
61
|
+
queries: QueryNode,
|
|
60
62
|
commands: CommandNode,
|
|
61
63
|
step: StepNode,
|
|
62
64
|
user: UserNode,
|
|
@@ -160,21 +162,25 @@ const NodeGraphBuilder = ({
|
|
|
160
162
|
{includeBackground && <Controls />}
|
|
161
163
|
{includeKey && (
|
|
162
164
|
<Panel position="bottom-right">
|
|
163
|
-
<div className=" bg-white font-light px-4 text-[
|
|
164
|
-
<span className="font-bold">Key</span>
|
|
165
|
-
<ul>
|
|
166
|
-
<li className="flex space-x-2 items-center text-[
|
|
165
|
+
<div className=" bg-white font-light px-4 text-[12px] shadow-md py-1 rounded-md">
|
|
166
|
+
{/* <span className="font-bold">Key</span> */}
|
|
167
|
+
<ul className="m-0 p-0">
|
|
168
|
+
<li className="flex space-x-2 items-center text-[10px]">
|
|
167
169
|
<span className="w-2 h-2 bg-orange-500 block" />
|
|
168
170
|
<span className="block">Event</span>
|
|
169
171
|
</li>
|
|
170
|
-
<li className="flex space-x-2 items-center text-[
|
|
172
|
+
<li className="flex space-x-2 items-center text-[10px]">
|
|
171
173
|
<span className="w-2 h-2 bg-pink-500 block" />
|
|
172
174
|
<span className="block">Service</span>
|
|
173
175
|
</li>
|
|
174
|
-
<li className="flex space-x-2 items-center text-[
|
|
176
|
+
<li className="flex space-x-2 items-center text-[10px]">
|
|
175
177
|
<span className="w-2 h-2 bg-blue-500 block" />
|
|
176
178
|
<span className="block">Command</span>
|
|
177
179
|
</li>
|
|
180
|
+
<li className="flex space-x-2 items-center text-[10px]">
|
|
181
|
+
<span className="w-2 h-2 bg-green-500 block" />
|
|
182
|
+
<span className="block">Query</span>
|
|
183
|
+
</li>
|
|
178
184
|
</ul>
|
|
179
185
|
</div>
|
|
180
186
|
</Panel>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { MagnifyingGlassIcon } from '@heroicons/react/16/solid';
|
|
2
|
+
import type { CollectionEntry } from 'astro:content';
|
|
3
|
+
import { Handle } from 'reactflow';
|
|
4
|
+
|
|
5
|
+
interface Data {
|
|
6
|
+
title: string;
|
|
7
|
+
label: string;
|
|
8
|
+
bgColor: string;
|
|
9
|
+
color: string;
|
|
10
|
+
mode: 'simple' | 'full';
|
|
11
|
+
message: CollectionEntry<'queries'>;
|
|
12
|
+
showTarget?: boolean;
|
|
13
|
+
showSource?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function classNames(...classes: any) {
|
|
17
|
+
return classes.filter(Boolean).join(' ');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default function QueryNode({ data, sourcePosition, targetPosition }: any) {
|
|
21
|
+
const { mode, message, showTarget = true, showSource = true } = data as Data;
|
|
22
|
+
|
|
23
|
+
const { name, version, summary, owners = [], producers = [], consumers = [] } = message.data;
|
|
24
|
+
|
|
25
|
+
const renderTarget = showTarget || (targetPosition && producers.length > 0);
|
|
26
|
+
const renderSource = showSource || (sourcePosition && consumers.length > 0);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={classNames('w-full rounded-md border flex justify-start bg-white text-black border-green-400')}>
|
|
30
|
+
<div
|
|
31
|
+
className={classNames(
|
|
32
|
+
'bg-gradient-to-b from-green-500 to-green-700 relative flex items-center w-5 justify-center rounded-l-sm text-green-100-500',
|
|
33
|
+
`border-r-[1px] border-green-500`
|
|
34
|
+
)}
|
|
35
|
+
>
|
|
36
|
+
<MagnifyingGlassIcon className="w-4 h-4 opacity-90 text-white absolute top-1 " />
|
|
37
|
+
{mode === 'full' && (
|
|
38
|
+
<span className="rotate -rotate-90 w-1/2 text-center absolute bottom-1 text-[9px] text-white font-bold uppercase tracking-[3px] ">
|
|
39
|
+
Query
|
|
40
|
+
</span>
|
|
41
|
+
)}
|
|
42
|
+
</div>
|
|
43
|
+
<div className="p-1 min-w-60 max-w-[min-content]">
|
|
44
|
+
{renderTarget && <Handle type="target" position={targetPosition} />}
|
|
45
|
+
{renderSource && <Handle type="source" position={sourcePosition} />}
|
|
46
|
+
<div className={classNames(mode === 'full' ? `border-b border-gray-200` : '')}>
|
|
47
|
+
<span className="text-xs font-bold block pb-0.5">{name}</span>
|
|
48
|
+
<div className="flex justify-between">
|
|
49
|
+
<span className="text-[10px] font-light block pt-0.5 pb-0.5 ">v{version}</span>
|
|
50
|
+
{mode === 'simple' && <span className="text-[10px] text-gray-500 font-light block pt-0.5 pb-0.5 ">Query</span>}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
{mode === 'full' && (
|
|
54
|
+
<div className="divide-y divide-gray-200 ">
|
|
55
|
+
<div className="leading-3 py-1">
|
|
56
|
+
<span className="text-[8px] font-light">{summary}</span>
|
|
57
|
+
</div>
|
|
58
|
+
<div className="grid grid-cols-2 gap-x-4 py-1">
|
|
59
|
+
<span className="text-xs" style={{ fontSize: '0.2em' }}>
|
|
60
|
+
Producers: {producers.length}
|
|
61
|
+
</span>
|
|
62
|
+
<span className="text-xs" style={{ fontSize: '0.2em' }}>
|
|
63
|
+
Consumers: {consumers.length}
|
|
64
|
+
</span>
|
|
65
|
+
<span className="text-xs" style={{ fontSize: '0.2em' }}>
|
|
66
|
+
Owners: {owners.length}
|
|
67
|
+
</span>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -38,7 +38,38 @@ const ownersList = owners.map((o) => ({
|
|
|
38
38
|
href: buildUrl(`/docs/${o.collection}/${o.data.id}`),
|
|
39
39
|
}));
|
|
40
40
|
|
|
41
|
-
const
|
|
41
|
+
const getType = (type: string) => {
|
|
42
|
+
switch (type) {
|
|
43
|
+
case 'queries':
|
|
44
|
+
return 'Query';
|
|
45
|
+
default:
|
|
46
|
+
return message.collection.slice(0, -1);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const getProducerEmptyMessage = (type: string) => {
|
|
51
|
+
const value = type.toLowerCase();
|
|
52
|
+
switch (value) {
|
|
53
|
+
case 'query':
|
|
54
|
+
case 'command':
|
|
55
|
+
return `This ${value} does not get invoked by any services.`;
|
|
56
|
+
default:
|
|
57
|
+
return `This ${value} does not get produced by any services.`;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const getConsumerEmptyMessage = (type: string) => {
|
|
62
|
+
const value = type.toLowerCase();
|
|
63
|
+
switch (value) {
|
|
64
|
+
case 'query':
|
|
65
|
+
case 'command':
|
|
66
|
+
return `This ${value} does not invoke any service.`;
|
|
67
|
+
default:
|
|
68
|
+
return `This ${value} does not get consumed by any services.`;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const type = getType(message.collection);
|
|
42
73
|
|
|
43
74
|
// @ts-ignore
|
|
44
75
|
const publicPath = message?.catalog?.publicPath;
|
|
@@ -52,14 +83,14 @@ const schemaURL = path.join(publicPath, schemaFilePath || '');
|
|
|
52
83
|
color="pink"
|
|
53
84
|
title={`${type} Producers (${producerList.length})`}
|
|
54
85
|
pills={producerList}
|
|
55
|
-
emptyMessage={
|
|
86
|
+
emptyMessage={getProducerEmptyMessage(type)}
|
|
56
87
|
client:load
|
|
57
88
|
/>
|
|
58
89
|
<PillList
|
|
59
90
|
color="pink"
|
|
60
91
|
title={`${type} Consumers (${consumerList.length})`}
|
|
61
92
|
pills={consumerList}
|
|
62
|
-
emptyMessage={
|
|
93
|
+
emptyMessage={getConsumerEmptyMessage(type)}
|
|
63
94
|
client:load
|
|
64
95
|
/>
|
|
65
96
|
<OwnersList
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ServerIcon, BoltIcon, ChatBubbleLeftIcon } from '@heroicons/react/24/solid';
|
|
1
|
+
import { ServerIcon, BoltIcon, ChatBubbleLeftIcon, MagnifyingGlassIcon } from '@heroicons/react/24/solid';
|
|
2
2
|
import { createColumnHelper } from '@tanstack/react-table';
|
|
3
3
|
import type { CollectionMessageTypes } from '@types';
|
|
4
4
|
import type { CollectionEntry } from 'astro:content';
|
|
@@ -8,6 +8,20 @@ import { buildUrl } from '@utils/url-builder';
|
|
|
8
8
|
|
|
9
9
|
const columnHelper = createColumnHelper<CollectionEntry<CollectionMessageTypes>>();
|
|
10
10
|
|
|
11
|
+
export const getColorAndIconForMessageType = (type: string) => {
|
|
12
|
+
switch (type) {
|
|
13
|
+
case 'event':
|
|
14
|
+
return { color: 'orange', Icon: BoltIcon };
|
|
15
|
+
case 'command':
|
|
16
|
+
return { color: 'blue', Icon: ChatBubbleLeftIcon };
|
|
17
|
+
case 'querie':
|
|
18
|
+
case 'query':
|
|
19
|
+
return { color: 'green', Icon: MagnifyingGlassIcon };
|
|
20
|
+
default:
|
|
21
|
+
return { color: 'gray', Icon: ChatBubbleLeftIcon };
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
11
25
|
export const columns = () => [
|
|
12
26
|
columnHelper.accessor('data.name', {
|
|
13
27
|
id: 'name',
|
|
@@ -15,8 +29,7 @@ export const columns = () => [
|
|
|
15
29
|
cell: (info) => {
|
|
16
30
|
const messageRaw = info.row.original;
|
|
17
31
|
const type = useMemo(() => messageRaw.collection.slice(0, -1), [messageRaw.collection]);
|
|
18
|
-
const color = type
|
|
19
|
-
const Icon = type === 'event' ? BoltIcon : ChatBubbleLeftIcon;
|
|
32
|
+
const { color, Icon } = getColorAndIconForMessageType(type);
|
|
20
33
|
return (
|
|
21
34
|
<div className=" group ">
|
|
22
35
|
<a
|
|
@@ -4,6 +4,7 @@ import type { CollectionEntry } from 'astro:content';
|
|
|
4
4
|
import { useMemo } from 'react';
|
|
5
5
|
import { filterByName, filterCollectionByName } from '../filters/custom-filters';
|
|
6
6
|
import { buildUrl } from '@utils/url-builder';
|
|
7
|
+
import { getColorAndIconForMessageType } from './MessageTableColumns';
|
|
7
8
|
|
|
8
9
|
const columnHelper = createColumnHelper<CollectionEntry<'services'>>();
|
|
9
10
|
|
|
@@ -13,7 +14,6 @@ export const columns = () => [
|
|
|
13
14
|
header: () => <span>Service</span>,
|
|
14
15
|
cell: (info) => {
|
|
15
16
|
const messageRaw = info.row.original;
|
|
16
|
-
const type = useMemo(() => messageRaw.collection.slice(0, -1), [messageRaw.collection]);
|
|
17
17
|
const color = 'pink';
|
|
18
18
|
return (
|
|
19
19
|
<div className="group font-light">
|
|
@@ -73,8 +73,7 @@ export const columns = () => [
|
|
|
73
73
|
<ul>
|
|
74
74
|
{receives.map((consumer: any) => {
|
|
75
75
|
const type = consumer.collection.slice(0, -1);
|
|
76
|
-
const color
|
|
77
|
-
const Icon = type === 'event' ? BoltIcon : ChatBubbleLeftIcon;
|
|
76
|
+
const { color, Icon } = useMemo(() => getColorAndIconForMessageType(type), [type]);
|
|
78
77
|
return (
|
|
79
78
|
<li key={consumer.data.id} className="py-1 group font-light ">
|
|
80
79
|
<a
|
package/src/content/config.ts
CHANGED
|
@@ -150,6 +150,16 @@ const commands = defineCollection({
|
|
|
150
150
|
.merge(baseSchema),
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
+
const queries = defineCollection({
|
|
154
|
+
type: 'content',
|
|
155
|
+
schema: z
|
|
156
|
+
.object({
|
|
157
|
+
producers: z.array(reference('services')).optional(),
|
|
158
|
+
consumers: z.array(reference('services')).optional(),
|
|
159
|
+
})
|
|
160
|
+
.merge(baseSchema),
|
|
161
|
+
});
|
|
162
|
+
|
|
153
163
|
const services = defineCollection({
|
|
154
164
|
type: 'content',
|
|
155
165
|
schema: z
|
|
@@ -207,6 +217,7 @@ const teams = defineCollection({
|
|
|
207
217
|
export const collections = {
|
|
208
218
|
events,
|
|
209
219
|
commands,
|
|
220
|
+
queries,
|
|
210
221
|
services,
|
|
211
222
|
users,
|
|
212
223
|
teams,
|
|
@@ -9,8 +9,11 @@ import { getFlows } from '@utils/flows/flows';
|
|
|
9
9
|
import { getEvents } from '@utils/events';
|
|
10
10
|
import { getServices } from '@utils/services/services';
|
|
11
11
|
import { buildUrl } from '@utils/url-builder';
|
|
12
|
+
import { getQueries } from '@utils/queries';
|
|
13
|
+
import { MagnifyingGlassIcon } from '@heroicons/react/20/solid';
|
|
12
14
|
|
|
13
15
|
const events = await getEvents();
|
|
16
|
+
const queries = await getQueries();
|
|
14
17
|
const commands = await getCommands();
|
|
15
18
|
const services = await getServices();
|
|
16
19
|
const domains = await getDomains();
|
|
@@ -36,6 +39,14 @@ const tabs = [
|
|
|
36
39
|
activeColor: 'blue',
|
|
37
40
|
enabled: commands.length > 0,
|
|
38
41
|
},
|
|
42
|
+
{
|
|
43
|
+
label: `Queries (${queries.length})`,
|
|
44
|
+
href: buildUrl('/discover/queries'),
|
|
45
|
+
isActive: currentPath === '/discover/queries',
|
|
46
|
+
icon: MagnifyingGlassIcon,
|
|
47
|
+
activeColor: 'green',
|
|
48
|
+
enabled: queries.length > 0,
|
|
49
|
+
},
|
|
39
50
|
{
|
|
40
51
|
label: `Services (${services.length})`,
|
|
41
52
|
href: buildUrl('/discover/services'),
|
|
@@ -5,20 +5,22 @@ import { getCommands } from '@utils/commands';
|
|
|
5
5
|
import { getDomains } from '@utils/domains/domains';
|
|
6
6
|
import { getEvents } from '@utils/events';
|
|
7
7
|
import { getFlows } from '@utils/flows/flows';
|
|
8
|
+
import { getQueries } from '@utils/queries';
|
|
8
9
|
import { getServices } from '@utils/services/services';
|
|
9
10
|
import { buildUrl } from '@utils/url-builder';
|
|
10
11
|
import { ViewTransitions } from 'astro:transitions';
|
|
11
12
|
|
|
12
|
-
const [services, events, commands, domains, flows] = await Promise.all([
|
|
13
|
+
const [services, events, commands, queries, domains, flows] = await Promise.all([
|
|
13
14
|
getServices(),
|
|
14
15
|
getEvents(),
|
|
15
16
|
getCommands(),
|
|
17
|
+
getQueries(),
|
|
16
18
|
getDomains(),
|
|
17
19
|
getFlows(),
|
|
18
20
|
]);
|
|
19
21
|
|
|
20
22
|
// @ts-ignore for large catalogs https://github.com/event-catalog/eventcatalog/issues/552
|
|
21
|
-
const navItems = [...domains, ...services, ...events, ...commands, ...flows];
|
|
23
|
+
const navItems = [...domains, ...services, ...events, ...commands, ...queries, ...flows];
|
|
22
24
|
|
|
23
25
|
const navigation = navItems
|
|
24
26
|
.filter((item) => item.data.version === item.data.latestVersion)
|
|
@@ -41,6 +43,8 @@ const getColor = (collection: string) => {
|
|
|
41
43
|
return 'green';
|
|
42
44
|
case 'events':
|
|
43
45
|
return 'red';
|
|
46
|
+
case 'queries':
|
|
47
|
+
return 'green';
|
|
44
48
|
case 'commands':
|
|
45
49
|
return 'yellow';
|
|
46
50
|
case 'domains':
|
|
@@ -52,45 +56,47 @@ const getColor = (collection: string) => {
|
|
|
52
56
|
---
|
|
53
57
|
|
|
54
58
|
<PlainPage title={title} description={description}>
|
|
55
|
-
<div class="
|
|
56
|
-
<div class="
|
|
57
|
-
<div>
|
|
58
|
-
<aside
|
|
59
|
-
class="
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
59
|
+
<div class="">
|
|
60
|
+
<div class="">
|
|
61
|
+
<div class="hidden md:block">
|
|
62
|
+
<aside class="sm:fixed grow w-56 xl:w-[19em] overflow-y-auto h-full z-10 pb-20" id="visualiser-navigation">
|
|
63
|
+
<div class="w-full">
|
|
64
|
+
<div class="sticky top-0 z-10 h-8 pointer-events-none -mt-7">
|
|
65
|
+
<div class="h-full bg-gradient-to-b from-white to-transparent"></div>
|
|
66
|
+
</div>
|
|
67
|
+
{
|
|
68
|
+
Object.keys(navigation).map((key: any) => {
|
|
69
|
+
const items = navigation[key]
|
|
70
|
+
.map((item: any) => {
|
|
71
|
+
const isCurrent = currentPath.includes(`${item.collection}/${item.data.id}/${item.data.version}`);
|
|
72
|
+
const isLatest = item.data.version === item.data.latestVersion;
|
|
73
|
+
if (!isLatest) return null;
|
|
74
|
+
return {
|
|
75
|
+
label: item.data.name,
|
|
76
|
+
version: item.data.version,
|
|
77
|
+
color: getColor(item.collection),
|
|
78
|
+
href: buildUrl(`/visualiser/${item.collection}/${item.data.id}/${item.data.version}`),
|
|
79
|
+
active: isCurrent,
|
|
80
|
+
};
|
|
81
|
+
})
|
|
82
|
+
.filter((n: any) => n !== null);
|
|
83
|
+
return (
|
|
84
|
+
<BasicList
|
|
85
|
+
title={`${key} (${navigation[key].length})`}
|
|
86
|
+
items={items}
|
|
87
|
+
emptyMessage="Nothing to show"
|
|
88
|
+
color="gray"
|
|
89
|
+
client:load
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
</div>
|
|
89
95
|
</aside>
|
|
90
96
|
</div>
|
|
91
97
|
|
|
92
|
-
<main class="flex-1 h-full w-full relative">
|
|
93
|
-
<button
|
|
98
|
+
<main class="flex-1 h-full w-full relative sm:pl-60 xl:pl-80">
|
|
99
|
+
<!-- <button
|
|
94
100
|
id="fullscreen-toggle"
|
|
95
101
|
class="absolute top-[1em] z-40 left-5 bg-white hover:bg-gray-100/50 border border-gray-200 hover:text-primary text-gray-800 font-semibold py-2 px-4 rounded-lg transition duration-300 ease-in-out flex items-center space-x-2 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75"
|
|
96
102
|
>
|
|
@@ -102,7 +108,7 @@ const getColor = (collection: string) => {
|
|
|
102
108
|
d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path>
|
|
103
109
|
</svg>
|
|
104
110
|
<span>Toggle Fullscreen</span>
|
|
105
|
-
</button>
|
|
111
|
+
</button> -->
|
|
106
112
|
<slot />
|
|
107
113
|
</main>
|
|
108
114
|
</div>
|
|
@@ -12,7 +12,7 @@ export async function getStaticPaths() {
|
|
|
12
12
|
flows: getFlows,
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
const itemTypes: PageTypesWithFlows[] = ['events', 'commands', 'services', 'domains', 'flows'];
|
|
15
|
+
const itemTypes: PageTypesWithFlows[] = ['events', 'commands', 'queries', 'services', 'domains', 'flows'];
|
|
16
16
|
const allItems = await Promise.all(itemTypes.map((type) => loaders[type]()));
|
|
17
17
|
|
|
18
18
|
return allItems.flatMap((items, index) => ({
|
|
@@ -16,7 +16,7 @@ import { pageDataLoader } from '@utils/pages/pages';
|
|
|
16
16
|
import type { CollectionEntry } from 'astro:content';
|
|
17
17
|
|
|
18
18
|
export async function getStaticPaths() {
|
|
19
|
-
const itemTypes: PageTypes[] = ['events', 'commands', 'services', 'domains'];
|
|
19
|
+
const itemTypes: PageTypes[] = ['events', 'commands', 'queries', 'services', 'domains'];
|
|
20
20
|
const allItems = await Promise.all(itemTypes.map((type) => pageDataLoader[type]()));
|
|
21
21
|
|
|
22
22
|
const hasAsyncAPISpec = (item: CollectionEntry<CollectionTypes>) => item.data.specifications?.asyncapiPath !== undefined;
|
|
@@ -4,7 +4,7 @@ import Footer from '@layouts/Footer.astro';
|
|
|
4
4
|
|
|
5
5
|
import type { PageTypes } from '@types';
|
|
6
6
|
import { getChangeLogs } from '@utils/changelogs/changelogs';
|
|
7
|
-
import { RectangleGroupIcon, ServerIcon, BoltIcon, ChatBubbleLeftIcon } from '@heroicons/react/24/outline';
|
|
7
|
+
import { RectangleGroupIcon, ServerIcon, BoltIcon, ChatBubbleLeftIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline';
|
|
8
8
|
import { pageDataLoader } from '@utils/pages/pages';
|
|
9
9
|
import 'diff2html/bundles/css/diff2html.min.css';
|
|
10
10
|
|
|
@@ -13,7 +13,7 @@ import { getVersions, getPreviousVersion } from '@utils/collections/util';
|
|
|
13
13
|
import { getDiffsForCurrentAndPreviousVersion } from '@utils/collections/file-diffs';
|
|
14
14
|
|
|
15
15
|
export async function getStaticPaths() {
|
|
16
|
-
const itemTypes: PageTypes[] = ['events', 'commands', 'services', 'domains'];
|
|
16
|
+
const itemTypes: PageTypes[] = ['events', 'commands', 'queries', 'services', 'domains'];
|
|
17
17
|
const allItems = await Promise.all(itemTypes.map((type) => pageDataLoader[type]()));
|
|
18
18
|
|
|
19
19
|
return allItems.flatMap((items, index) =>
|
|
@@ -85,6 +85,9 @@ const getBadge = () => {
|
|
|
85
85
|
if (props.collection === 'commands') {
|
|
86
86
|
return { backgroundColor: 'blue', textColor: 'blue', content: 'Command', icon: ChatBubbleLeftIcon, class: 'text-blue-400' };
|
|
87
87
|
}
|
|
88
|
+
if (props.collection === 'queries') {
|
|
89
|
+
return { backgroundColor: 'green', textColor: 'green', content: 'Query', icon: MagnifyingGlassIcon, class: 'text-blue-400' };
|
|
90
|
+
}
|
|
88
91
|
if (props.collection === 'domains') {
|
|
89
92
|
return {
|
|
90
93
|
backgroundColor: 'yellow',
|
|
@@ -26,7 +26,7 @@ export async function getStaticPaths() {
|
|
|
26
26
|
flows: getFlows,
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
const itemTypes: PageTypesWithFlows[] = ['events', 'commands', 'services', 'domains', 'flows'];
|
|
29
|
+
const itemTypes: PageTypesWithFlows[] = ['events', 'commands', 'queries', 'services', 'domains', 'flows'];
|
|
30
30
|
const allItems = await Promise.all(itemTypes.map((type) => loaders[type]()));
|
|
31
31
|
|
|
32
32
|
return allItems.flatMap((items, index) =>
|
|
@@ -206,7 +206,11 @@ const badges = [getBadge(), ...contentBadges, ...getSpecificationBadges()];
|
|
|
206
206
|
</main>
|
|
207
207
|
<aside class="hidden lg:block sticky top-20 h-[calc(100vh-theme(spacing.16))] w-56 overflow-y-auto">
|
|
208
208
|
<!-- @ts-ignore -->
|
|
209
|
-
{
|
|
209
|
+
{
|
|
210
|
+
(props?.collection === 'events' || props.collection === 'commands' || props.collection === 'queries') && (
|
|
211
|
+
<MessageSideBar message={props} />
|
|
212
|
+
)
|
|
213
|
+
}
|
|
210
214
|
{props?.collection === 'services' && <ServiceSideBar service={props} />}
|
|
211
215
|
{props?.collection === 'domains' && <DomainSideBar domain={props} />}
|
|
212
216
|
</aside>
|
|
@@ -10,7 +10,7 @@ import { buildUrl } from '@utils/url-builder';
|
|
|
10
10
|
import { pageDataLoader } from '@utils/pages/pages';
|
|
11
11
|
|
|
12
12
|
export async function getStaticPaths() {
|
|
13
|
-
const itemTypes: PageTypes[] = ['events', 'commands', 'services', 'domains'];
|
|
13
|
+
const itemTypes: PageTypes[] = ['events', 'commands', 'queries', 'services', 'domains'];
|
|
14
14
|
|
|
15
15
|
const allItems = await Promise.all(itemTypes.map((type) => pageDataLoader[type]()));
|
|
16
16
|
|
package/src/pages/index.astro
CHANGED
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
import Footer from '@layouts/Footer.astro';
|
|
3
3
|
import PlainPage from '@layouts/PlainPage.astro';
|
|
4
4
|
import { buildUrl } from '@utils/url-builder';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
BoltIcon,
|
|
7
|
+
ChatBubbleLeftIcon,
|
|
8
|
+
QueueListIcon,
|
|
9
|
+
RectangleGroupIcon,
|
|
10
|
+
ServerIcon,
|
|
11
|
+
MagnifyingGlassIcon,
|
|
12
|
+
} from '@heroicons/react/24/outline';
|
|
6
13
|
|
|
7
14
|
import { getMessages } from '@utils/messages';
|
|
8
15
|
import { getDomains } from '@utils/domains/domains';
|
|
@@ -10,7 +17,7 @@ import { getServices } from '@utils/services/services';
|
|
|
10
17
|
import { getFlows } from '@utils/flows/flows';
|
|
11
18
|
import DiscoverInsight from '@components/DiscoverInsight.astro';
|
|
12
19
|
|
|
13
|
-
const { commands = [], events = [] } = await getMessages();
|
|
20
|
+
const { commands = [], events = [], queries = [] } = await getMessages();
|
|
14
21
|
const domains = await getDomains();
|
|
15
22
|
const services = await getServices();
|
|
16
23
|
const flows = await getFlows();
|
|
@@ -30,13 +37,15 @@ const flows = await getFlows();
|
|
|
30
37
|
<div class="hidden md:block">
|
|
31
38
|
<h2 class="text-center text-xl font-bold">Architecture insights</h2>
|
|
32
39
|
<section class="mb-16 bg-white rounded-xl shadow-lg p-6">
|
|
33
|
-
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-
|
|
40
|
+
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
|
34
41
|
<DiscoverInsight color="text-yellow-600" dataTarget={domains.length} icon={RectangleGroupIcon} label="domains" />
|
|
35
42
|
|
|
36
43
|
<DiscoverInsight color="text-pink-500" dataTarget={services.length} icon={ServerIcon} label="services" />
|
|
37
44
|
|
|
38
45
|
<DiscoverInsight color="text-blue-500" dataTarget={commands.length} icon={ChatBubbleLeftIcon} label="commands" />
|
|
39
46
|
|
|
47
|
+
<DiscoverInsight color="text-green-500" dataTarget={queries.length} icon={MagnifyingGlassIcon} label="queries" />
|
|
48
|
+
|
|
40
49
|
<DiscoverInsight color="text-orange-400" dataTarget={events.length} icon={BoltIcon} label="events" />
|
|
41
50
|
|
|
42
51
|
<DiscoverInsight color="text-green-800" dataTarget={flows.length} icon={QueueListIcon} label="flows" />
|
|
@@ -14,7 +14,7 @@ export async function getStaticPaths() {
|
|
|
14
14
|
flows: getFlows,
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
const itemTypes: PageTypesWithFlows[] = ['events', 'commands', 'services', 'domains', 'flows'];
|
|
17
|
+
const itemTypes: PageTypesWithFlows[] = ['events', 'commands', 'queries', 'services', 'domains', 'flows'];
|
|
18
18
|
const allItems = await Promise.all(itemTypes.map((type) => loaders[type]()));
|
|
19
19
|
|
|
20
20
|
return allItems.flatMap((items, index) =>
|
package/src/types/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type CollectionTypes = 'commands' | 'events' | 'domains' | 'services' | 'flows';
|
|
2
|
-
export type CollectionMessageTypes = 'commands' | 'events';
|
|
1
|
+
export type CollectionTypes = 'commands' | 'events' | 'queries' | 'domains' | 'services' | 'flows';
|
|
2
|
+
export type CollectionMessageTypes = 'commands' | 'events' | 'queries';
|
|
3
3
|
|
|
4
|
-
export type PageTypes = 'events' | 'commands' | 'services' | 'domains';
|
|
4
|
+
export type PageTypes = 'events' | 'commands' | 'queries' | 'services' | 'domains';
|
|
@@ -25,7 +25,7 @@ const getServiceNode = (step: any, services: CollectionEntry<'services'>[]) => {
|
|
|
25
25
|
};
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
const getMessageNode = (step: any, messages: CollectionEntry<'events' | 'commands'>[]) => {
|
|
28
|
+
const getMessageNode = (step: any, messages: CollectionEntry<'events' | 'commands' | 'queries'>[]) => {
|
|
29
29
|
const messagesForVersion = getItemsFromCollectionByIdAndSemverOrLatest(messages, step.message.id, step.message.version);
|
|
30
30
|
const message = messagesForVersion[0];
|
|
31
31
|
return {
|
|
@@ -53,9 +53,10 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
|
|
|
53
53
|
|
|
54
54
|
const events = await getCollection('events');
|
|
55
55
|
const commands = await getCollection('commands');
|
|
56
|
+
const queries = await getCollection('queries');
|
|
56
57
|
const services = await getCollection('services');
|
|
57
58
|
|
|
58
|
-
const messages = [...events, ...commands];
|
|
59
|
+
const messages = [...events, ...commands, ...queries];
|
|
59
60
|
|
|
60
61
|
const steps = flow?.data.steps || [];
|
|
61
62
|
|
|
@@ -69,7 +70,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
|
|
|
69
70
|
});
|
|
70
71
|
|
|
71
72
|
// Create nodes
|
|
72
|
-
hydratedSteps.forEach((step, index: number) => {
|
|
73
|
+
hydratedSteps.forEach((step: any, index: number) => {
|
|
73
74
|
const node = {
|
|
74
75
|
id: `step-${step.id}`,
|
|
75
76
|
sourcePosition: 'right',
|
|
@@ -93,7 +94,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
|
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
// Create Edges
|
|
96
|
-
hydratedSteps.forEach((step, index: number) => {
|
|
97
|
+
hydratedSteps.forEach((step: any, index: number) => {
|
|
97
98
|
let paths = step.next_steps || [];
|
|
98
99
|
|
|
99
100
|
if (step.next_step) {
|
package/src/utils/messages.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Exporting getCommands and getEvents directly
|
|
2
2
|
import { getCommands } from '@utils/commands';
|
|
3
3
|
import { getEvents } from '@utils/events';
|
|
4
|
+
import { getQueries } from './queries';
|
|
4
5
|
export { getCommands } from '@utils/commands';
|
|
5
6
|
export { getEvents } from '@utils/events';
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ export { getEvents } from '@utils/events';
|
|
|
8
9
|
export const getMessages = async () => {
|
|
9
10
|
const commands = await getCommands();
|
|
10
11
|
const events = await getEvents();
|
|
12
|
+
const queries = await getQueries();
|
|
11
13
|
|
|
12
|
-
return { commands, events };
|
|
14
|
+
return { commands, events, queries };
|
|
13
15
|
};
|
|
@@ -2,13 +2,13 @@ import type { CollectionEntry } from 'astro:content';
|
|
|
2
2
|
import type { Node } from 'reactflow';
|
|
3
3
|
import dagre from 'dagre';
|
|
4
4
|
|
|
5
|
-
export const generateIdForNode = (node: CollectionEntry<'events' | 'services' | 'commands'>) => {
|
|
5
|
+
export const generateIdForNode = (node: CollectionEntry<'events' | 'services' | 'commands' | 'queries'>) => {
|
|
6
6
|
return `${node.data.id}-${node.data.version}`;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
export const generatedIdForEdge = (
|
|
10
|
-
source: CollectionEntry<'events' | 'services' | 'commands'>,
|
|
11
|
-
target: CollectionEntry<'events' | 'services' | 'commands'>
|
|
10
|
+
source: CollectionEntry<'events' | 'services' | 'commands' | 'queries'>,
|
|
11
|
+
target: CollectionEntry<'events' | 'services' | 'commands' | 'queries'>
|
|
12
12
|
) => {
|
|
13
13
|
return `${source.data.id}-${source.data.version}-${target.data.id}-${target.data.version}`;
|
|
14
14
|
};
|
package/src/utils/pages/pages.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { CollectionTypes, PageTypes } from '@types';
|
|
2
2
|
import { getDomains } from '@utils/domains/domains';
|
|
3
3
|
import { getCommands, getEvents } from '@utils/messages';
|
|
4
|
+
import { getQueries } from '@utils/queries';
|
|
4
5
|
import { getServices } from '@utils/services/services';
|
|
5
6
|
import type { CollectionEntry } from 'astro:content';
|
|
6
7
|
|
|
7
8
|
export const pageDataLoader: Record<PageTypes, () => Promise<CollectionEntry<CollectionTypes>[]>> = {
|
|
8
9
|
events: getEvents,
|
|
9
10
|
commands: getCommands,
|
|
11
|
+
queries: getQueries,
|
|
10
12
|
services: getServices,
|
|
11
13
|
domains: getDomains,
|
|
12
14
|
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { getQueries } from '@utils/queries';
|
|
2
|
+
import type { CollectionEntry } from 'astro:content';
|
|
3
|
+
import dagre from 'dagre';
|
|
4
|
+
import { calculatedNodes, createDagreGraph, generatedIdForEdge, generateIdForNode } from '../node-graph-utils/utils';
|
|
5
|
+
import { MarkerType } from 'reactflow';
|
|
6
|
+
|
|
7
|
+
type DagreGraph = any;
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
id: string;
|
|
11
|
+
version: string;
|
|
12
|
+
defaultFlow?: DagreGraph;
|
|
13
|
+
mode?: 'simple' | 'full';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simple' }: Props) => {
|
|
17
|
+
const flow = defaultFlow || createDagreGraph({ ranksep: 300, nodesep: 50 });
|
|
18
|
+
const nodes = [] as any,
|
|
19
|
+
edges = [] as any;
|
|
20
|
+
|
|
21
|
+
const queries = await getQueries();
|
|
22
|
+
|
|
23
|
+
const query = queries.find((query) => query.data.id === id && query.data.version === version);
|
|
24
|
+
|
|
25
|
+
// Nothing found...
|
|
26
|
+
if (!query) {
|
|
27
|
+
return {
|
|
28
|
+
nodes: [],
|
|
29
|
+
edges: [],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const producers = (query.data.producers as CollectionEntry<'services'>[]) || [];
|
|
34
|
+
const consumers = (query.data.consumers as CollectionEntry<'services'>[]) || [];
|
|
35
|
+
|
|
36
|
+
if (producers && producers.length > 0) {
|
|
37
|
+
producers.forEach((producer) => {
|
|
38
|
+
nodes.push({
|
|
39
|
+
id: generateIdForNode(producer),
|
|
40
|
+
type: producer?.collection,
|
|
41
|
+
sourcePosition: 'right',
|
|
42
|
+
targetPosition: 'left',
|
|
43
|
+
data: { mode, service: producer, showTarget: false },
|
|
44
|
+
position: { x: 250, y: 0 },
|
|
45
|
+
});
|
|
46
|
+
edges.push({
|
|
47
|
+
id: generatedIdForEdge(producer, query),
|
|
48
|
+
source: generateIdForNode(producer),
|
|
49
|
+
target: generateIdForNode(query),
|
|
50
|
+
type: 'smoothstep',
|
|
51
|
+
label: 'requests',
|
|
52
|
+
animated: false,
|
|
53
|
+
markerEnd: {
|
|
54
|
+
type: MarkerType.ArrowClosed,
|
|
55
|
+
width: 40,
|
|
56
|
+
height: 40,
|
|
57
|
+
},
|
|
58
|
+
style: {
|
|
59
|
+
strokeWidth: 1,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// The query itself
|
|
66
|
+
nodes.push({
|
|
67
|
+
id: generateIdForNode(query),
|
|
68
|
+
sourcePosition: 'right',
|
|
69
|
+
targetPosition: 'left',
|
|
70
|
+
data: { mode, message: query, showTarget: producers.length > 0, showSource: consumers.length > 0 },
|
|
71
|
+
position: { x: 0, y: 0 },
|
|
72
|
+
type: query.collection,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// The messages the service sends
|
|
76
|
+
consumers.forEach((consumer) => {
|
|
77
|
+
nodes.push({
|
|
78
|
+
id: generateIdForNode(consumer),
|
|
79
|
+
sourcePosition: 'right',
|
|
80
|
+
targetPosition: 'left',
|
|
81
|
+
data: { title: consumer?.data.id, mode, service: consumer, showSource: false },
|
|
82
|
+
position: { x: 0, y: 0 },
|
|
83
|
+
type: consumer?.collection,
|
|
84
|
+
});
|
|
85
|
+
edges.push({
|
|
86
|
+
id: generatedIdForEdge(query, consumer),
|
|
87
|
+
source: generateIdForNode(query),
|
|
88
|
+
target: generateIdForNode(consumer),
|
|
89
|
+
type: 'smoothstep',
|
|
90
|
+
label: 'accepts',
|
|
91
|
+
animated: false,
|
|
92
|
+
markerEnd: {
|
|
93
|
+
type: MarkerType.ArrowClosed,
|
|
94
|
+
width: 40,
|
|
95
|
+
height: 40,
|
|
96
|
+
},
|
|
97
|
+
style: {
|
|
98
|
+
strokeWidth: 1,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
nodes.forEach((node: any) => {
|
|
104
|
+
flow.setNode(node.id, { width: 150, height: 100 });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
edges.forEach((edge: any) => {
|
|
108
|
+
flow.setEdge(edge.source, edge.target);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Render the diagram in memory getting hte X and Y
|
|
112
|
+
dagre.layout(flow);
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
nodes: calculatedNodes(flow, nodes),
|
|
116
|
+
edges: edges,
|
|
117
|
+
};
|
|
118
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getCollection } from 'astro:content';
|
|
2
|
+
import type { CollectionEntry } from 'astro:content';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { getVersionForCollectionItem, satisfies } from './collections/util';
|
|
5
|
+
|
|
6
|
+
const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
|
|
7
|
+
|
|
8
|
+
type Query = CollectionEntry<'queries'> & {
|
|
9
|
+
catalog: {
|
|
10
|
+
path: string;
|
|
11
|
+
filePath: string;
|
|
12
|
+
type: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
getAllVersions?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const getQueries = async ({ getAllVersions = true }: Props = {}): Promise<Query[]> => {
|
|
21
|
+
const queries = await getCollection('queries', (query) => {
|
|
22
|
+
return (getAllVersions || !query.slug.includes('versioned')) && query.data.hidden !== true;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const services = await getCollection('services');
|
|
26
|
+
|
|
27
|
+
return queries.map((query) => {
|
|
28
|
+
const { latestVersion, versions } = getVersionForCollectionItem(query, queries);
|
|
29
|
+
|
|
30
|
+
const producers = services.filter((service) =>
|
|
31
|
+
service.data.sends?.some((item) => {
|
|
32
|
+
if (item.id != query.data.id) return false;
|
|
33
|
+
if (item.version == 'latest' || item.version == undefined) return query.data.version == latestVersion;
|
|
34
|
+
return satisfies(query.data.version, item.version);
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const consumers = services.filter((service) =>
|
|
39
|
+
service.data.receives?.some((item) => {
|
|
40
|
+
if (item.id != query.data.id) return false;
|
|
41
|
+
if (item.version == 'latest' || item.version == undefined) return query.data.version == latestVersion;
|
|
42
|
+
return satisfies(query.data.version, item.version);
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
...query,
|
|
48
|
+
data: {
|
|
49
|
+
...query.data,
|
|
50
|
+
producers,
|
|
51
|
+
consumers,
|
|
52
|
+
versions,
|
|
53
|
+
latestVersion,
|
|
54
|
+
},
|
|
55
|
+
catalog: {
|
|
56
|
+
path: path.join(query.collection, query.id.replace('/index.mdx', '')),
|
|
57
|
+
absoluteFilePath: path.join(PROJECT_DIR, query.collection, query.id.replace('/index.mdx', '/index.md')),
|
|
58
|
+
astroContentFilePath: path.join(process.cwd(), 'src', 'content', query.collection, query.id),
|
|
59
|
+
filePath: path.join(process.cwd(), 'src', 'catalog-files', query.collection, query.id.replace('/index.mdx', '')),
|
|
60
|
+
publicPath: path.join('/generated', query.collection, query.id.replace('/index.mdx', '')),
|
|
61
|
+
type: 'event',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
};
|
|
@@ -14,6 +14,31 @@ interface Props {
|
|
|
14
14
|
renderAllEdges?: boolean;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
const getSendsMessageByMessageType = (messageType: string) => {
|
|
18
|
+
switch (messageType) {
|
|
19
|
+
case 'events':
|
|
20
|
+
return 'publishes event';
|
|
21
|
+
case 'commands':
|
|
22
|
+
return 'invokes command';
|
|
23
|
+
case 'queries':
|
|
24
|
+
return 'requests';
|
|
25
|
+
default:
|
|
26
|
+
return 'invokes message';
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const getReceivesMessageByMessageType = (messageType: string) => {
|
|
31
|
+
switch (messageType) {
|
|
32
|
+
case 'events':
|
|
33
|
+
return 'receives event';
|
|
34
|
+
case 'commands':
|
|
35
|
+
case 'queries':
|
|
36
|
+
return 'accepts';
|
|
37
|
+
default:
|
|
38
|
+
return 'accepts message';
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
17
42
|
export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simple', renderAllEdges = false }: Props) => {
|
|
18
43
|
const flow = defaultFlow || createDagreGraph({ ranksep: 300, nodesep: 50 });
|
|
19
44
|
const nodes = [] as any,
|
|
@@ -36,8 +61,9 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
|
|
|
36
61
|
|
|
37
62
|
const events = await getCollection('events');
|
|
38
63
|
const commands = await getCollection('commands');
|
|
64
|
+
const queries = await getCollection('queries');
|
|
39
65
|
|
|
40
|
-
const messages = [...events, ...commands];
|
|
66
|
+
const messages = [...events, ...commands, ...queries];
|
|
41
67
|
|
|
42
68
|
const receivesHydrated = receivesRaw
|
|
43
69
|
.map((message) => getItemsFromCollectionByIdAndSemverOrLatest(messages, message.id, message.version))
|
|
@@ -49,8 +75,8 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
|
|
|
49
75
|
.flat()
|
|
50
76
|
.filter((e) => e !== undefined);
|
|
51
77
|
|
|
52
|
-
const receives = (receivesHydrated as CollectionEntry<'events' | 'commands'>[]) || [];
|
|
53
|
-
const sends = (sendsHydrated as CollectionEntry<'events' | 'commands'>[]) || [];
|
|
78
|
+
const receives = (receivesHydrated as CollectionEntry<'events' | 'commands' | 'queries'>[]) || [];
|
|
79
|
+
const sends = (sendsHydrated as CollectionEntry<'events' | 'commands' | 'queries'>[]) || [];
|
|
54
80
|
|
|
55
81
|
// Get all the data from them
|
|
56
82
|
|
|
@@ -70,7 +96,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
|
|
|
70
96
|
source: generateIdForNode(receive),
|
|
71
97
|
target: generateIdForNode(service),
|
|
72
98
|
type: 'smoothstep',
|
|
73
|
-
label: receive?.collection
|
|
99
|
+
label: getReceivesMessageByMessageType(receive?.collection),
|
|
74
100
|
animated: false,
|
|
75
101
|
markerEnd: {
|
|
76
102
|
type: MarkerType.ArrowClosed,
|
|
@@ -109,7 +135,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
|
|
|
109
135
|
source: generateIdForNode(service),
|
|
110
136
|
target: generateIdForNode(send),
|
|
111
137
|
type: 'smoothstep',
|
|
112
|
-
label: send?.collection
|
|
138
|
+
label: getSendsMessageByMessageType(send?.collection),
|
|
113
139
|
animated: false,
|
|
114
140
|
markerEnd: {
|
|
115
141
|
type: MarkerType.ArrowClosed,
|
|
@@ -18,8 +18,9 @@ export const getServices = async ({ getAllVersions = true }: Props = {}): Promis
|
|
|
18
18
|
});
|
|
19
19
|
const events = await getCollection('events');
|
|
20
20
|
const commands = await getCollection('commands');
|
|
21
|
+
const queries = await getCollection('queries');
|
|
21
22
|
|
|
22
|
-
const allMessages = [...events, ...commands];
|
|
23
|
+
const allMessages = [...events, ...commands, ...queries];
|
|
23
24
|
|
|
24
25
|
// @ts-ignore // TODO: Fix this type
|
|
25
26
|
return services.map((service) => {
|
|
@@ -29,14 +30,14 @@ export const getServices = async ({ getAllVersions = true }: Props = {}): Promis
|
|
|
29
30
|
const receivesMessages = service.data.receives || [];
|
|
30
31
|
|
|
31
32
|
const sends = sendsMessages
|
|
32
|
-
.map((message) => getItemsFromCollectionByIdAndSemverOrLatest(allMessages, message.id, message.version))
|
|
33
|
+
.map((message: any) => getItemsFromCollectionByIdAndSemverOrLatest(allMessages, message.id, message.version))
|
|
33
34
|
.flat()
|
|
34
|
-
.filter((e) => e !== undefined);
|
|
35
|
+
.filter((e: any) => e !== undefined);
|
|
35
36
|
|
|
36
37
|
const receives = receivesMessages
|
|
37
|
-
.map((message) => getItemsFromCollectionByIdAndSemverOrLatest(allMessages, message.id, message.version))
|
|
38
|
+
.map((message: any) => getItemsFromCollectionByIdAndSemverOrLatest(allMessages, message.id, message.version))
|
|
38
39
|
.flat()
|
|
39
|
-
.filter((e) => e !== undefined);
|
|
40
|
+
.filter((e: any) => e !== undefined);
|
|
40
41
|
|
|
41
42
|
return {
|
|
42
43
|
...service,
|