@eventcatalog/core 0.1.12 → 0.1.13
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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +31 -25
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/eslint/.cache_1bay4w0 +1 -1
- package/.next/cache/next-server.js.nft.json +1 -1
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/{730.js → 685.js} +411 -2
- package/.next/server/chunks/944.js +25 -2
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages/events/AddedItemToCart/logs.html +1 -1
- package/.next/server/pages/events/AddedItemToCart/v/0.0.1.html +2 -2
- package/.next/server/pages/events/AddedItemToCart/v/0.0.1.json +1 -1
- package/.next/server/pages/events/AddedItemToCart/v/0.0.2.html +2 -2
- package/.next/server/pages/events/AddedItemToCart/v/0.0.2.json +1 -1
- package/.next/server/pages/events/AddedItemToCart.html +2 -2
- package/.next/server/pages/events/AddedItemToCart.json +1 -1
- package/.next/server/pages/events/OrderComplete/logs.html +1 -1
- package/.next/server/pages/events/OrderComplete.html +2 -2
- package/.next/server/pages/events/OrderComplete.json +1 -1
- package/.next/server/pages/events/OrderConfirmed/logs.html +1 -1
- package/.next/server/pages/events/OrderConfirmed.html +3 -21
- package/.next/server/pages/events/OrderConfirmed.json +1 -1
- package/.next/server/pages/events/OrderRequested/logs.html +1 -1
- package/.next/server/pages/events/OrderRequested.html +2 -2
- package/.next/server/pages/events/OrderRequested.json +1 -1
- package/.next/server/pages/events/PaymentProcessed/logs.html +1 -1
- package/.next/server/pages/events/PaymentProcessed.html +2 -2
- package/.next/server/pages/events/PaymentProcessed.json +1 -1
- package/.next/server/pages/events/RemovedItemFromCart/logs.html +1 -1
- package/.next/server/pages/events/RemovedItemFromCart.html +2 -2
- package/.next/server/pages/events/RemovedItemFromCart.json +1 -1
- package/.next/server/pages/events/ShipmentDelivered/logs.html +1 -1
- package/.next/server/pages/events/ShipmentDelivered.html +3 -23
- package/.next/server/pages/events/ShipmentDelivered.json +1 -1
- package/.next/server/pages/events/ShipmentDispatched/logs.html +1 -1
- package/.next/server/pages/events/ShipmentDispatched.html +2 -2
- package/.next/server/pages/events/ShipmentDispatched.json +1 -1
- package/.next/server/pages/events/ShipmentPrepared/logs.html +1 -1
- package/.next/server/pages/events/ShipmentPrepared.html +2 -20
- package/.next/server/pages/events/ShipmentPrepared.json +1 -1
- package/.next/server/pages/events/[name]/v/[version].js +15 -1
- package/.next/server/pages/events/[name]/v/[version].js.nft.json +1 -1
- package/.next/server/pages/events/[name].js +15 -1
- package/.next/server/pages/events/[name].js.nft.json +1 -1
- package/.next/server/pages/events.html +3 -3
- package/.next/server/pages/events.json +1 -1
- package/.next/server/pages/index.html +1 -1
- package/.next/server/pages/overview.html +1 -1
- package/.next/server/pages/overview.json +1 -1
- package/.next/server/pages/services/Basket Service.html +2 -2
- package/.next/server/pages/services/Basket Service.json +1 -1
- package/.next/server/pages/services/Data Lake.html +2 -2
- package/.next/server/pages/services/Data Lake.json +1 -1
- package/.next/server/pages/services/Payment Service.html +2 -2
- package/.next/server/pages/services/Payment Service.json +1 -1
- package/.next/server/pages/services/Shipping Service.html +2 -28
- package/.next/server/pages/services/Shipping Service.json +1 -1
- package/.next/server/pages/services/[name].js +40 -3
- package/.next/server/pages/services/[name].js.nft.json +1 -1
- package/.next/server/pages/services.html +2 -2
- package/.next/server/pages/services.json +1 -1
- package/.next/server/pages/users/dboyne.html +3 -3
- package/.next/server/pages/users/dboyne.json +1 -1
- package/.next/server/pages/users/mSmith.html +3 -3
- package/.next/server/pages/users/mSmith.json +1 -1
- package/.next/static/Wp8i1_b29rcfszKFNI7O4/_buildManifest.js +1 -0
- package/.next/static/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/_middlewareManifest.js +0 -0
- package/.next/static/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/_ssgManifest.js +0 -0
- package/{out/_next/static/chunks/178-9b89f5c036d77ab9.js → .next/static/chunks/178-bc36881df28d1bab.js} +1 -1
- package/.next/static/chunks/280-06401c009983574c.js +1 -0
- package/.next/static/chunks/620-d80b0351ea428525.js +1 -0
- package/.next/static/chunks/651-7a25d8468f22423c.js +1 -0
- package/.next/static/chunks/b744740b-94e91620ba96ccbf.js +1 -0
- package/.next/static/chunks/pages/events/[name]/v/{[version]-f6619ec4056cc70d.js → [version]-d8d4d12f05da9c8a.js} +1 -1
- package/.next/static/chunks/pages/events/{[name]-73ecab7171745a3f.js → [name]-89e1edc81aa51fc3.js} +1 -1
- package/.next/static/chunks/pages/services/[name]-204382bfbc48b1b4.js +1 -0
- package/.next/static/chunks/{webpack-2ebfdb1666dac4ad.js → webpack-edf0c365e149f010.js} +1 -1
- package/.next/static/css/f756e2a0d8f4db27.css +3 -0
- package/.next/trace +56 -54
- package/CHANGELOG.md +6 -0
- package/components/Mdx/NodeGraph/GraphElements.ts +183 -0
- package/components/Mdx/NodeGraph/GraphLayout.ts +57 -0
- package/components/Mdx/NodeGraph/NodeGraph.tsx +130 -0
- package/components/Mdx/NodeGraph/__tests__/GraphElements.spec.ts +98 -0
- package/components/Mdx/NodeGraph/__tests__/GraphLayout.spec.ts +104 -0
- package/components/Mdx/NodeGraph/__tests__/__snapshots__/GraphElements.spec.ts.snap +661 -0
- package/components/Mdx/NodeGraph/__tests__/__snapshots__/GraphLayout.spec.ts.snap +81 -0
- package/out/404/index.html +1 -1
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/AddedItemToCart/logs.json +0 -0
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/AddedItemToCart/v/0.0.1.json +1 -1
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/AddedItemToCart/v/0.0.2.json +1 -1
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/AddedItemToCart.json +1 -1
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/OrderComplete/logs.json +0 -0
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/OrderComplete.json +1 -1
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/OrderConfirmed/logs.json +0 -0
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/OrderConfirmed.json +1 -1
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/OrderRequested/logs.json +0 -0
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/OrderRequested.json +1 -1
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/PaymentProcessed/logs.json +0 -0
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/PaymentProcessed.json +1 -1
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/RemovedItemFromCart/logs.json +0 -0
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/RemovedItemFromCart.json +1 -1
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/ShipmentDelivered/logs.json +0 -0
- package/out/_next/data/Wp8i1_b29rcfszKFNI7O4/events/ShipmentDelivered.json +1 -0
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/ShipmentDispatched/logs.json +0 -0
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/ShipmentDispatched.json +1 -1
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/ShipmentPrepared/logs.json +0 -0
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events/ShipmentPrepared.json +1 -1
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/events.json +1 -1
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/overview.json +1 -1
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/services/Basket Service.json +1 -1
- package/out/_next/data/Wp8i1_b29rcfszKFNI7O4/services/Data Lake.json +1 -0
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/services/Payment Service.json +1 -1
- package/out/_next/data/Wp8i1_b29rcfszKFNI7O4/services/Shipping Service.json +1 -0
- package/out/_next/data/Wp8i1_b29rcfszKFNI7O4/services.json +1 -0
- package/out/_next/data/Wp8i1_b29rcfszKFNI7O4/users/dboyne.json +1 -0
- package/out/_next/data/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/users/mSmith.json +1 -1
- package/out/_next/static/Wp8i1_b29rcfszKFNI7O4/_buildManifest.js +1 -0
- package/out/_next/static/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/_middlewareManifest.js +0 -0
- package/out/_next/static/{bMdE5uGdmIhIs9cgTPp_Z → Wp8i1_b29rcfszKFNI7O4}/_ssgManifest.js +0 -0
- package/{.next/static/chunks/178-9b89f5c036d77ab9.js → out/_next/static/chunks/178-bc36881df28d1bab.js} +1 -1
- package/out/_next/static/chunks/280-06401c009983574c.js +1 -0
- package/out/_next/static/chunks/620-d80b0351ea428525.js +1 -0
- package/out/_next/static/chunks/651-7a25d8468f22423c.js +1 -0
- package/out/_next/static/chunks/b744740b-94e91620ba96ccbf.js +1 -0
- package/out/_next/static/chunks/pages/events/[name]/v/{[version]-f6619ec4056cc70d.js → [version]-d8d4d12f05da9c8a.js} +1 -1
- package/out/_next/static/chunks/pages/events/{[name]-73ecab7171745a3f.js → [name]-89e1edc81aa51fc3.js} +1 -1
- package/out/_next/static/chunks/pages/services/[name]-204382bfbc48b1b4.js +1 -0
- package/out/_next/static/chunks/{webpack-2ebfdb1666dac4ad.js → webpack-edf0c365e149f010.js} +1 -1
- package/out/_next/static/css/f756e2a0d8f4db27.css +3 -0
- package/out/events/AddedItemToCart/index.html +2 -2
- package/out/events/AddedItemToCart/logs/index.html +1 -1
- package/out/events/AddedItemToCart/v/0.0.1/index.html +2 -2
- package/out/events/AddedItemToCart/v/0.0.2/index.html +2 -2
- package/out/events/OrderComplete/index.html +2 -2
- package/out/events/OrderComplete/logs/index.html +1 -1
- package/out/events/OrderConfirmed/index.html +3 -21
- package/out/events/OrderConfirmed/logs/index.html +1 -1
- package/out/events/OrderRequested/index.html +2 -2
- package/out/events/OrderRequested/logs/index.html +1 -1
- package/out/events/PaymentProcessed/index.html +2 -2
- package/out/events/PaymentProcessed/logs/index.html +1 -1
- package/out/events/RemovedItemFromCart/index.html +2 -2
- package/out/events/RemovedItemFromCart/logs/index.html +1 -1
- package/out/events/ShipmentDelivered/index.html +3 -23
- package/out/events/ShipmentDelivered/logs/index.html +1 -1
- package/out/events/ShipmentDispatched/index.html +2 -2
- package/out/events/ShipmentDispatched/logs/index.html +1 -1
- package/out/events/ShipmentPrepared/index.html +2 -20
- package/out/events/ShipmentPrepared/logs/index.html +1 -1
- package/out/events/index.html +3 -3
- package/out/index.html +1 -1
- package/out/overview/index.html +1 -1
- package/out/services/Basket Service/index.html +2 -2
- package/out/services/Data Lake/index.html +2 -2
- package/out/services/Payment Service/index.html +2 -2
- package/out/services/Shipping Service/index.html +2 -28
- package/out/services/index.html +2 -2
- package/out/users/dboyne/index.html +3 -3
- package/out/users/mSmith/index.html +3 -3
- package/package.json +3 -1
- package/pages/events/[name].tsx +36 -0
- package/pages/services/[name].tsx +36 -0
- package/.next/static/bMdE5uGdmIhIs9cgTPp_Z/_buildManifest.js +0 -1
- package/.next/static/chunks/336-7c4681dab353ec23.js +0 -1
- package/.next/static/chunks/650-4004943b2a9623ca.js +0 -1
- package/.next/static/chunks/pages/services/[name]-d8873d29190a6447.js +0 -1
- package/.next/static/css/0b54ec6503b91283.css +0 -3
- package/out/_next/data/bMdE5uGdmIhIs9cgTPp_Z/events/ShipmentDelivered.json +0 -1
- package/out/_next/data/bMdE5uGdmIhIs9cgTPp_Z/services/Data Lake.json +0 -1
- package/out/_next/data/bMdE5uGdmIhIs9cgTPp_Z/services/Shipping Service.json +0 -1
- package/out/_next/data/bMdE5uGdmIhIs9cgTPp_Z/services.json +0 -1
- package/out/_next/data/bMdE5uGdmIhIs9cgTPp_Z/users/dboyne.json +0 -1
- package/out/_next/static/bMdE5uGdmIhIs9cgTPp_Z/_buildManifest.js +0 -1
- package/out/_next/static/chunks/336-7c4681dab353ec23.js +0 -1
- package/out/_next/static/chunks/650-4004943b2a9623ca.js +0 -1
- package/out/_next/static/chunks/pages/services/[name]-d8873d29190a6447.js +0 -1
- package/out/_next/static/css/0b54ec6503b91283.css +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @eventcatalog/core
|
|
2
2
|
|
|
3
|
+
## 0.1.13
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`ca01bef`](https://github.com/boyney123/eventcatalog/commit/ca01bef1659b2c364210a86e0f9e916f6c1fbaa4) [#166](https://github.com/boyney123/eventcatalog/pull/166) Thanks [@thim81](https://github.com/thim81)! - feat: new nodegraph component to render services and events
|
|
8
|
+
|
|
3
9
|
## 0.1.12
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { ArrowHeadType, XYPosition, Node, Edge } from 'react-flow-renderer';
|
|
2
|
+
import getConfig from 'next/config';
|
|
3
|
+
import type { Event, Service } from '@eventcatalog/types';
|
|
4
|
+
|
|
5
|
+
const { publicRuntimeConfig: { basePath = '' } = {} } = getConfig();
|
|
6
|
+
|
|
7
|
+
const MIN_NODE_WIDTH = 150;
|
|
8
|
+
const generateLink = (value, type) => (basePath !== '' ? `${basePath}/${type}/${value}` : `/${type}/${value}`);
|
|
9
|
+
const calcWidth = (value) => (value.length * 7 > MIN_NODE_WIDTH ? value.length * 7 : MIN_NODE_WIDTH);
|
|
10
|
+
|
|
11
|
+
const buildNodeEdge = ({ id, target, source, isAnimated = true }) => ({
|
|
12
|
+
id,
|
|
13
|
+
target,
|
|
14
|
+
source,
|
|
15
|
+
type: 'smoothstep',
|
|
16
|
+
arrowHeadType: ArrowHeadType.ArrowClosed,
|
|
17
|
+
animated: isAnimated,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const buildNodeData = ({ label, type, maxWidth }: { label: string; type: 'service' | 'event'; maxWidth?: number }) => {
|
|
21
|
+
const width = calcWidth(label);
|
|
22
|
+
const linkType = type === 'service' ? 'services' : 'events';
|
|
23
|
+
const link = generateLink(label, linkType);
|
|
24
|
+
return { label, link, width, maxWidth };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Builds a graph for a given event
|
|
29
|
+
* @param {Event} - event
|
|
30
|
+
* @param rootNodeColor - The color of the root node
|
|
31
|
+
* @param isAnimated - whether to animate the graph
|
|
32
|
+
*/
|
|
33
|
+
export const getEventElements = (
|
|
34
|
+
{ name: eventName, producers: eventProducers, consumers: eventConsumers }: Event,
|
|
35
|
+
rootNodeColor = '#2563eb',
|
|
36
|
+
isAnimated = true
|
|
37
|
+
) => {
|
|
38
|
+
const position: XYPosition = { x: 0, y: 0 };
|
|
39
|
+
|
|
40
|
+
const consumerColor = '#818cf8';
|
|
41
|
+
const producerColor = '#75d7b6';
|
|
42
|
+
|
|
43
|
+
const producersNames = eventProducers.map((s) => calcWidth(s));
|
|
44
|
+
const maxProducersWidth = Math.max(...producersNames);
|
|
45
|
+
const consumersNames = eventConsumers.map((s) => calcWidth(s));
|
|
46
|
+
const maxConsumersWidth = Math.max(...consumersNames);
|
|
47
|
+
|
|
48
|
+
const eventNameAsNodeID = `ev-${eventName.replace(/ /g, '_')}`;
|
|
49
|
+
const eventNodeWidth = calcWidth(eventName);
|
|
50
|
+
|
|
51
|
+
const producers = eventProducers.map((node) => ({ label: node, id: `pr-${node.replace(/ /g, '_')}` }));
|
|
52
|
+
const consumers = eventConsumers.map((node) => ({ label: node, id: `co-${node.replace(/ /g, '_')}` }));
|
|
53
|
+
|
|
54
|
+
// Transforms services & event into a graph model
|
|
55
|
+
const producersNodes: Node[] = producers.map(({ label, id }) => {
|
|
56
|
+
const nodeWidth = calcWidth(label);
|
|
57
|
+
const diff = maxProducersWidth - nodeWidth;
|
|
58
|
+
const nodeMaxWidth = diff !== 0 ? nodeWidth - diff : maxProducersWidth;
|
|
59
|
+
return {
|
|
60
|
+
id,
|
|
61
|
+
data: buildNodeData({ label, type: 'service', maxWidth: nodeMaxWidth }),
|
|
62
|
+
style: { border: `2px solid ${producerColor}`, width: nodeWidth },
|
|
63
|
+
type: 'input',
|
|
64
|
+
position,
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
const consumersNodes: Node[] = consumers.map(({ id, label }) => {
|
|
68
|
+
const width = calcWidth(label);
|
|
69
|
+
return {
|
|
70
|
+
id,
|
|
71
|
+
data: buildNodeData({ label, type: 'service', maxWidth: maxConsumersWidth }),
|
|
72
|
+
style: { border: `2px solid ${consumerColor}`, width },
|
|
73
|
+
type: 'output',
|
|
74
|
+
position,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
const eventNode: Node = {
|
|
78
|
+
id: eventNameAsNodeID,
|
|
79
|
+
data: buildNodeData({ label: eventName, type: 'event', maxWidth: eventNodeWidth }),
|
|
80
|
+
style: {
|
|
81
|
+
border: `2px solid ${rootNodeColor}`,
|
|
82
|
+
width: eventNodeWidth,
|
|
83
|
+
},
|
|
84
|
+
position,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Build connections
|
|
88
|
+
const producersEdges: Edge[] = producers.map(({ id, label }) =>
|
|
89
|
+
buildNodeEdge({ id: `epe-${label.replace(/ /g, '_')}`, source: id, target: eventNameAsNodeID, isAnimated })
|
|
90
|
+
);
|
|
91
|
+
const consumersEdges: Edge[] = consumers.map(({ id, label }) =>
|
|
92
|
+
buildNodeEdge({ id: `ece-${label.replace(/ /g, '_')}`, target: id, source: eventNameAsNodeID, isAnimated })
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Merge nodes in order
|
|
96
|
+
const elements: (Node | Edge)[] = [...producersNodes, eventNode, ...consumersNodes, ...producersEdges, ...consumersEdges];
|
|
97
|
+
return elements;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Builds a graph for a given service
|
|
102
|
+
* @param {Service} service
|
|
103
|
+
* @param {string} rootNodeColor of the root node
|
|
104
|
+
* @param isAnimated whether the graph should be animated
|
|
105
|
+
* @returns {string} Mermaid Graph
|
|
106
|
+
*/
|
|
107
|
+
export const getServiceElements = (
|
|
108
|
+
{ publishes, subscribes, name: serviceName }: Service,
|
|
109
|
+
rootNodeColor = '#2563eb',
|
|
110
|
+
isAnimated = true
|
|
111
|
+
) => {
|
|
112
|
+
const position: XYPosition = { x: 0, y: 0 };
|
|
113
|
+
|
|
114
|
+
const publishColor = '#818cf8';
|
|
115
|
+
const subscribeColor = '#75d7b6';
|
|
116
|
+
|
|
117
|
+
const publishesNames = publishes.map((e) => calcWidth(e.name));
|
|
118
|
+
const maxPublishesWidth = Math.max(...publishesNames);
|
|
119
|
+
const subscribesNames = subscribes.map((e) => calcWidth(e.name));
|
|
120
|
+
const maxSubscribesWidth = Math.max(...subscribesNames);
|
|
121
|
+
|
|
122
|
+
const serviceNameAsNodeID = `ser-${serviceName.replace(/ /g, '_')}`;
|
|
123
|
+
|
|
124
|
+
// Transforms services & event into a graph model
|
|
125
|
+
const publishesNodes: Node[] = publishes.map((node) => {
|
|
126
|
+
const nodeWidth = calcWidth(node.name);
|
|
127
|
+
return {
|
|
128
|
+
id: `pub-${node.name.replace(/ /g, '_')}`,
|
|
129
|
+
data: buildNodeData({ label: node.name, type: 'event', maxWidth: maxPublishesWidth }),
|
|
130
|
+
style: { border: `2px solid ${publishColor}`, width: nodeWidth },
|
|
131
|
+
type: 'output',
|
|
132
|
+
position,
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
const subscribesNodes: Node[] = subscribes.map((node) => {
|
|
136
|
+
const nodeWidth = calcWidth(node.name);
|
|
137
|
+
const diff = maxSubscribesWidth - nodeWidth;
|
|
138
|
+
const nodeMaxWidth = diff !== 0 ? nodeWidth - diff : maxSubscribesWidth;
|
|
139
|
+
return {
|
|
140
|
+
id: `sub-${node.name.replace(/ /g, '_')}`,
|
|
141
|
+
data: buildNodeData({ label: node.name, type: 'event', maxWidth: nodeMaxWidth }),
|
|
142
|
+
style: {
|
|
143
|
+
border: `2px solid ${subscribeColor}`,
|
|
144
|
+
width: nodeWidth,
|
|
145
|
+
},
|
|
146
|
+
type: 'input',
|
|
147
|
+
position,
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const serviceNode: Node = {
|
|
152
|
+
id: serviceNameAsNodeID,
|
|
153
|
+
data: buildNodeData({ label: serviceName, type: 'service', maxWidth: calcWidth(serviceName) }),
|
|
154
|
+
style: {
|
|
155
|
+
border: `2px solid ${rootNodeColor}`,
|
|
156
|
+
width: calcWidth(serviceName),
|
|
157
|
+
},
|
|
158
|
+
position,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Build connections
|
|
162
|
+
const publishesEdges: Edge[] = publishes.map((node) =>
|
|
163
|
+
buildNodeEdge({
|
|
164
|
+
id: `ecp-${node.name.replace(/ /g, '_')}`,
|
|
165
|
+
source: serviceNameAsNodeID,
|
|
166
|
+
target: `pub-${node.name.replace(/ /g, '_')}`,
|
|
167
|
+
isAnimated,
|
|
168
|
+
})
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const subscribesEdges: Edge[] = subscribes.map((node) =>
|
|
172
|
+
buildNodeEdge({
|
|
173
|
+
id: `esc-${node.name.replace(/ /g, '_')}`,
|
|
174
|
+
target: serviceNameAsNodeID,
|
|
175
|
+
source: `sub-${node.name.replace(/ /g, '_')}`,
|
|
176
|
+
isAnimated,
|
|
177
|
+
})
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Merge nodes in order
|
|
181
|
+
const elements: (Node | Edge)[] = [...subscribesNodes, serviceNode, ...publishesNodes, ...publishesEdges, ...subscribesEdges];
|
|
182
|
+
return elements;
|
|
183
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { isNode, Elements, Position } from 'react-flow-renderer';
|
|
2
|
+
import dagre from 'dagre';
|
|
3
|
+
|
|
4
|
+
const nodeDefaultWidth = 150;
|
|
5
|
+
const nodeDefaultHeight = 36;
|
|
6
|
+
const offset = 48;
|
|
7
|
+
const verticalOffset = offset / 1.5;
|
|
8
|
+
|
|
9
|
+
export default function createGraphLayout(elements: Elements, isHorizontal: boolean): Elements {
|
|
10
|
+
const dagreGraph = new dagre.graphlib.Graph();
|
|
11
|
+
|
|
12
|
+
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
|
13
|
+
dagreGraph.setGraph({ rankdir: 'LR', ranksep: offset * 2, nodesep: verticalOffset });
|
|
14
|
+
|
|
15
|
+
elements.forEach((element) => {
|
|
16
|
+
if (isNode(element)) {
|
|
17
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
18
|
+
const nodeWidth = element.__rf?.width ? element.__rf?.width : element.data?.width;
|
|
19
|
+
dagreGraph.setNode(element.id, {
|
|
20
|
+
width: nodeWidth || nodeDefaultWidth,
|
|
21
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
22
|
+
height: element.__rf?.height || nodeDefaultHeight,
|
|
23
|
+
});
|
|
24
|
+
} else {
|
|
25
|
+
dagreGraph.setEdge(element.source, element.target);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Calculate the layout, to get the node positions with their widths and heights
|
|
30
|
+
dagre.layout(dagreGraph);
|
|
31
|
+
|
|
32
|
+
return elements.map((element) => {
|
|
33
|
+
if (isNode(element)) {
|
|
34
|
+
const node = dagreGraph.node(element.id);
|
|
35
|
+
element.targetPosition = isHorizontal ? Position.Left : Position.Top;
|
|
36
|
+
element.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
|
|
37
|
+
element.position = {
|
|
38
|
+
x: offset / 2 + node.x - (element?.data?.maxWidth || node.width) / 2,
|
|
39
|
+
y: node.y - node.height / 2,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return element;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Helper - ReactFlow canvas height calculator
|
|
47
|
+
export const calcCanvasHeight = (data, type): number => {
|
|
48
|
+
const minHeight = 300;
|
|
49
|
+
const nodeSpacing = nodeDefaultHeight + verticalOffset;
|
|
50
|
+
let nodesHeight = 0;
|
|
51
|
+
if (type === 'event') {
|
|
52
|
+
nodesHeight = Math.max(data.producers.length, data.consumers.length) * nodeSpacing;
|
|
53
|
+
} else {
|
|
54
|
+
nodesHeight = Math.max(data.publishes.length, data.subscribes.length) * nodeSpacing;
|
|
55
|
+
}
|
|
56
|
+
return Math.max(minHeight, nodesHeight);
|
|
57
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import ReactFlow, { Controls, ReactFlowProvider, useStoreState } from 'react-flow-renderer';
|
|
3
|
+
import { Event, Service } from '@eventcatalog/types';
|
|
4
|
+
import { getEventElements, getServiceElements } from './GraphElements';
|
|
5
|
+
import createGraphLayout, { calcCanvasHeight } from './GraphLayout';
|
|
6
|
+
|
|
7
|
+
interface NodeGraphBuilderProps {
|
|
8
|
+
data: Event | Service;
|
|
9
|
+
source: 'event' | 'service';
|
|
10
|
+
rootNodeColor?: string;
|
|
11
|
+
maxZoom?: number;
|
|
12
|
+
fitView?: boolean;
|
|
13
|
+
zoomOnScroll?: boolean;
|
|
14
|
+
isAnimated?: boolean;
|
|
15
|
+
isDraggable?: boolean;
|
|
16
|
+
isHorizontal?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface NodeGraphProps extends NodeGraphBuilderProps {
|
|
20
|
+
maxHeight?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// NodeGraphBuilder component wrapping ReactFlow
|
|
24
|
+
function NodeGraphBuilder({
|
|
25
|
+
data,
|
|
26
|
+
source,
|
|
27
|
+
rootNodeColor,
|
|
28
|
+
maxZoom = 10,
|
|
29
|
+
isAnimated = true,
|
|
30
|
+
fitView = true,
|
|
31
|
+
zoomOnScroll = false,
|
|
32
|
+
isDraggable = false,
|
|
33
|
+
isHorizontal = true,
|
|
34
|
+
}: NodeGraphBuilderProps) {
|
|
35
|
+
// Load event or service elements for graph
|
|
36
|
+
let initialElements;
|
|
37
|
+
if (source === 'event') {
|
|
38
|
+
initialElements = getEventElements(data as Event, rootNodeColor, isAnimated);
|
|
39
|
+
} else {
|
|
40
|
+
initialElements = getServiceElements(data as Service, rootNodeColor, isAnimated);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Initialize graph layout
|
|
44
|
+
const isInitializedRef = useRef(false);
|
|
45
|
+
const nodes = useStoreState((state) => state.nodes);
|
|
46
|
+
const edges = useStoreState((state) => state.edges);
|
|
47
|
+
|
|
48
|
+
// Calculate initial element layout
|
|
49
|
+
const graphElements = createGraphLayout(initialElements, isHorizontal);
|
|
50
|
+
const [elements, setElements] = useState(graphElements);
|
|
51
|
+
|
|
52
|
+
// Rerender graph layout to get rendered width/height for nodes/edges
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (
|
|
55
|
+
isInitializedRef.current === false &&
|
|
56
|
+
nodes.length > 0 &&
|
|
57
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
58
|
+
nodes?.[0]?.__rf.width != null
|
|
59
|
+
) {
|
|
60
|
+
// Calculate element layout
|
|
61
|
+
const updateElements = () => {
|
|
62
|
+
const updatedElements = createGraphLayout([...nodes, ...edges], isHorizontal);
|
|
63
|
+
setElements(updatedElements);
|
|
64
|
+
isInitializedRef.current = true;
|
|
65
|
+
};
|
|
66
|
+
updateElements();
|
|
67
|
+
}
|
|
68
|
+
}, [nodes, edges, isInitializedRef, isHorizontal]);
|
|
69
|
+
|
|
70
|
+
// ReactFlow operations
|
|
71
|
+
const onElementClick = (event, element) => window.open(element.data.link, '_self');
|
|
72
|
+
const onLoad = useCallback(
|
|
73
|
+
(reactFlowInstance) => {
|
|
74
|
+
if (fitView) {
|
|
75
|
+
reactFlowInstance.fitView();
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
[fitView]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<ReactFlow
|
|
83
|
+
elements={elements}
|
|
84
|
+
onLoad={onLoad}
|
|
85
|
+
onElementClick={onElementClick}
|
|
86
|
+
nodesDraggable={isDraggable}
|
|
87
|
+
zoomOnScroll={zoomOnScroll}
|
|
88
|
+
maxZoom={maxZoom}
|
|
89
|
+
>
|
|
90
|
+
<Controls />
|
|
91
|
+
</ReactFlow>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// NodeGraph wrapping NodeGraphBuilder Component
|
|
96
|
+
function NodeGraph({
|
|
97
|
+
data,
|
|
98
|
+
source,
|
|
99
|
+
rootNodeColor,
|
|
100
|
+
maxHeight,
|
|
101
|
+
maxZoom,
|
|
102
|
+
fitView,
|
|
103
|
+
zoomOnScroll,
|
|
104
|
+
isAnimated,
|
|
105
|
+
isDraggable,
|
|
106
|
+
isHorizontal,
|
|
107
|
+
}: NodeGraphProps) {
|
|
108
|
+
// Set dynamic height of node graph
|
|
109
|
+
const dynamicHeight = maxHeight || calcCanvasHeight(data, source);
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className="node-graph w-full border-dashed border-2 border-slate-300" style={{ height: dynamicHeight }}>
|
|
113
|
+
<ReactFlowProvider>
|
|
114
|
+
<NodeGraphBuilder
|
|
115
|
+
source={source}
|
|
116
|
+
data={data}
|
|
117
|
+
rootNodeColor={rootNodeColor}
|
|
118
|
+
maxZoom={maxZoom}
|
|
119
|
+
fitView={fitView}
|
|
120
|
+
zoomOnScroll={zoomOnScroll}
|
|
121
|
+
isAnimated={isAnimated}
|
|
122
|
+
isDraggable={isDraggable}
|
|
123
|
+
isHorizontal={isHorizontal}
|
|
124
|
+
/>
|
|
125
|
+
</ReactFlowProvider>
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export default NodeGraph;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Event, Service } from '@eventcatalog/types';
|
|
2
|
+
import { getEventElements, getServiceElements } from '../GraphElements';
|
|
3
|
+
|
|
4
|
+
jest.mock('next/config', () => () => ({
|
|
5
|
+
publicRuntimeConfig: {
|
|
6
|
+
basePath: '/docs',
|
|
7
|
+
},
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
describe('GraphElements', () => {
|
|
11
|
+
describe('getServiceElements', () => {
|
|
12
|
+
it('takes a given Service and returns the ReactFlow elements with relations the events it publishes and consumes', () => {
|
|
13
|
+
const event = { name: 'My Event', version: '0.0.1' };
|
|
14
|
+
const event2 = { name: 'My Event 2', version: '0.0.1' };
|
|
15
|
+
const rootNodeColor = '#2563eb';
|
|
16
|
+
const isAnimated = true;
|
|
17
|
+
|
|
18
|
+
const service = {
|
|
19
|
+
id: 'My Service',
|
|
20
|
+
name: 'My Service',
|
|
21
|
+
version: '0.0.1',
|
|
22
|
+
summary: 'Summary',
|
|
23
|
+
publishes: [event],
|
|
24
|
+
subscribes: [event2],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const result = getServiceElements(service as Service, rootNodeColor, isAnimated);
|
|
28
|
+
|
|
29
|
+
expect(result).toMatchSnapshot();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('takes a given Service and returns the ReactFlow elements with multiple events & long names', () => {
|
|
33
|
+
const event = { name: 'My Event', version: '0.0.1' };
|
|
34
|
+
const event2 = { name: 'My Event 2', version: '0.0.1' };
|
|
35
|
+
const event3 = { name: 'My Event 3', version: '0.0.1' };
|
|
36
|
+
const event4 = { name: 'very.very.very.very.very.very.very.very.very.very.very.very.long.name.event.4', version: '0.0.1' };
|
|
37
|
+
const event5 = { name: 'very very very very very very very very very very very very long name event 5', version: '0.0.1' };
|
|
38
|
+
const event6 = { name: 'My Event 6', version: '0.0.1' };
|
|
39
|
+
const event7 = { name: 'My Event 7', version: '0.0.1' };
|
|
40
|
+
const event8 = { name: 'My Event 8', version: '0.0.1' };
|
|
41
|
+
const event9 = { name: 'My Event 9', version: '0.0.1' };
|
|
42
|
+
const rootNodeColor = '#2563eb';
|
|
43
|
+
const isAnimated = true;
|
|
44
|
+
|
|
45
|
+
const service = {
|
|
46
|
+
id: 'My Service',
|
|
47
|
+
name: 'My Service',
|
|
48
|
+
version: '0.0.1',
|
|
49
|
+
summary: 'Summary',
|
|
50
|
+
publishes: [event, event2, event3, event4],
|
|
51
|
+
subscribes: [event5, event6, event7, event8, event9],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const result = getServiceElements(service as Service, rootNodeColor, isAnimated);
|
|
55
|
+
|
|
56
|
+
expect(result).toMatchSnapshot();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('getEventElements', () => {
|
|
61
|
+
it('takes a given event and returns the ReactFlow elements with relations between the event and its consumers and producers', () => {
|
|
62
|
+
const event = {
|
|
63
|
+
name: 'My Event',
|
|
64
|
+
version: '0.0.1',
|
|
65
|
+
producers: ['Service 1'],
|
|
66
|
+
consumers: ['Service 2'],
|
|
67
|
+
};
|
|
68
|
+
const rootNodeColor = '#2563eb';
|
|
69
|
+
const isAnimated = true;
|
|
70
|
+
|
|
71
|
+
const result = getEventElements(event as Event, rootNodeColor, isAnimated);
|
|
72
|
+
|
|
73
|
+
expect(result).toMatchSnapshot();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('takes a given event and returns the ReactFlow elements with multiple services & long names', () => {
|
|
77
|
+
const event = {
|
|
78
|
+
name: 'My Event',
|
|
79
|
+
version: '0.0.1',
|
|
80
|
+
producers: ['Service 1', 'Service 2', 'very very very very very very very very very very very very long name Service 3'],
|
|
81
|
+
consumers: [
|
|
82
|
+
'Service 4',
|
|
83
|
+
'Service 5',
|
|
84
|
+
'Service 6',
|
|
85
|
+
'Service 7',
|
|
86
|
+
'Service 8',
|
|
87
|
+
'very.very.very.very.very.very.very.very.very.very.very.very.long.name.event.8',
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
const rootNodeColor = '#2563eb';
|
|
91
|
+
const isAnimated = true;
|
|
92
|
+
|
|
93
|
+
const result = getEventElements(event as Event, rootNodeColor, isAnimated);
|
|
94
|
+
|
|
95
|
+
expect(result).toMatchSnapshot();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Elements } from 'react-flow-renderer';
|
|
2
|
+
import createGraphLayout, { calcCanvasHeight } from '../GraphLayout';
|
|
3
|
+
|
|
4
|
+
describe('GraphLayout', () => {
|
|
5
|
+
describe('createGraphLayout', () => {
|
|
6
|
+
it('calculate a graph layout', () => {
|
|
7
|
+
const isHorizontal = true;
|
|
8
|
+
const elements = [
|
|
9
|
+
{
|
|
10
|
+
data: {
|
|
11
|
+
label: 'My Event 2',
|
|
12
|
+
link: '/docs/events/My Event 2',
|
|
13
|
+
maxWidth: 150,
|
|
14
|
+
width: 150,
|
|
15
|
+
},
|
|
16
|
+
id: 's-My_Event_2',
|
|
17
|
+
position: {
|
|
18
|
+
x: 0,
|
|
19
|
+
y: 0,
|
|
20
|
+
},
|
|
21
|
+
style: {
|
|
22
|
+
border: '2px solid #75d7b6',
|
|
23
|
+
width: 150,
|
|
24
|
+
},
|
|
25
|
+
type: 'input',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
data: {
|
|
29
|
+
label: 'My Service',
|
|
30
|
+
link: '/docs/services/My Service',
|
|
31
|
+
maxWidth: 150,
|
|
32
|
+
width: 150,
|
|
33
|
+
},
|
|
34
|
+
id: 'c-My_Service',
|
|
35
|
+
position: {
|
|
36
|
+
x: 0,
|
|
37
|
+
y: 0,
|
|
38
|
+
},
|
|
39
|
+
style: {
|
|
40
|
+
border: '2px solid #2563eb',
|
|
41
|
+
width: 150,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
data: {
|
|
46
|
+
label: 'My Event',
|
|
47
|
+
link: '/docs/events/My Event',
|
|
48
|
+
maxWidth: 150,
|
|
49
|
+
width: 150,
|
|
50
|
+
},
|
|
51
|
+
id: 'p-My_Event',
|
|
52
|
+
position: {
|
|
53
|
+
x: 0,
|
|
54
|
+
y: 0,
|
|
55
|
+
},
|
|
56
|
+
style: {
|
|
57
|
+
border: '2px solid #818cf8',
|
|
58
|
+
width: 150,
|
|
59
|
+
},
|
|
60
|
+
type: 'output',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
animated: true,
|
|
64
|
+
arrowHeadType: 'arrowclosed',
|
|
65
|
+
id: 'ecp-My_Event',
|
|
66
|
+
source: 'c-My_Service',
|
|
67
|
+
target: 'p-My_Event',
|
|
68
|
+
type: 'smoothstep',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
animated: true,
|
|
72
|
+
arrowHeadType: 'arrowclosed',
|
|
73
|
+
id: 'esc-My_Event_2',
|
|
74
|
+
source: 's-My_Event_2',
|
|
75
|
+
target: 'c-My_Service',
|
|
76
|
+
type: 'smoothstep',
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const result = createGraphLayout(elements as Elements, isHorizontal);
|
|
81
|
+
expect(result).toMatchSnapshot();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('calcCanvasHeight', () => {
|
|
86
|
+
it('takes all events and calculate the canvas height', () => {
|
|
87
|
+
const data = {
|
|
88
|
+
producers: [1, 2, 3, 4, 5, 6, 7, 8],
|
|
89
|
+
consumers: [1, 2, 3, 4, 5],
|
|
90
|
+
};
|
|
91
|
+
const result = calcCanvasHeight(data, 'event');
|
|
92
|
+
expect(result).toEqual(544);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('takes all services and calculate the canvas height', () => {
|
|
96
|
+
const data = {
|
|
97
|
+
publishes: [1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
98
|
+
subscribes: [1, 2, 3, 4, 5],
|
|
99
|
+
};
|
|
100
|
+
const result = calcCanvasHeight(data, 'service');
|
|
101
|
+
expect(result).toEqual(612);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|