@eventcatalog/core 0.1.9 → 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.
Files changed (205) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +35 -29
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/config.json +3 -3
  5. package/.next/cache/eslint/.cache_1bay4w0 +1 -1
  6. package/.next/cache/next-server.js.nft.json +1 -1
  7. package/.next/cache/webpack/client-production/0.pack +0 -0
  8. package/.next/cache/webpack/client-production/index.pack +0 -0
  9. package/.next/cache/webpack/server-production/0.pack +0 -0
  10. package/.next/cache/webpack/server-production/index.pack +0 -0
  11. package/.next/next-server.js.nft.json +1 -1
  12. package/.next/prerender-manifest.json +1 -1
  13. package/.next/routes-manifest.json +1 -1
  14. package/.next/server/chunks/565.js +6 -6
  15. package/.next/server/chunks/{730.js → 685.js} +411 -2
  16. package/.next/server/chunks/944.js +25 -2
  17. package/.next/server/pages/404.html +1 -1
  18. package/.next/server/pages/500.html +1 -1
  19. package/.next/server/pages/_app.js +7 -3
  20. package/.next/server/pages/events/AddedItemToCart/logs.html +1 -1
  21. package/.next/server/pages/events/AddedItemToCart/v/0.0.1.html +6 -6
  22. package/.next/server/pages/events/AddedItemToCart/v/0.0.1.json +1 -1
  23. package/.next/server/pages/events/AddedItemToCart/v/0.0.2.html +6 -6
  24. package/.next/server/pages/events/AddedItemToCart/v/0.0.2.json +1 -1
  25. package/.next/server/pages/events/AddedItemToCart.html +6 -6
  26. package/.next/server/pages/events/AddedItemToCart.json +1 -1
  27. package/.next/server/pages/events/OrderComplete/logs.html +1 -1
  28. package/.next/server/pages/events/OrderComplete.html +6 -6
  29. package/.next/server/pages/events/OrderComplete.json +1 -1
  30. package/.next/server/pages/events/OrderConfirmed/logs.html +1 -1
  31. package/.next/server/pages/events/OrderConfirmed.html +3 -21
  32. package/.next/server/pages/events/OrderConfirmed.json +1 -1
  33. package/.next/server/pages/events/OrderRequested/logs.html +1 -1
  34. package/.next/server/pages/events/OrderRequested.html +6 -6
  35. package/.next/server/pages/events/OrderRequested.json +1 -1
  36. package/.next/server/pages/events/PaymentProcessed/logs.html +1 -1
  37. package/.next/server/pages/events/PaymentProcessed.html +6 -6
  38. package/.next/server/pages/events/PaymentProcessed.json +1 -1
  39. package/.next/server/pages/events/RemovedItemFromCart/logs.html +1 -1
  40. package/.next/server/pages/events/RemovedItemFromCart.html +6 -6
  41. package/.next/server/pages/events/RemovedItemFromCart.json +1 -1
  42. package/.next/server/pages/events/ShipmentDelivered/logs.html +1 -1
  43. package/.next/server/pages/events/ShipmentDelivered.html +3 -23
  44. package/.next/server/pages/events/ShipmentDelivered.json +1 -1
  45. package/.next/server/pages/events/ShipmentDispatched/logs.html +1 -1
  46. package/.next/server/pages/events/ShipmentDispatched.html +6 -6
  47. package/.next/server/pages/events/ShipmentDispatched.json +1 -1
  48. package/.next/server/pages/events/ShipmentPrepared/logs.html +1 -1
  49. package/.next/server/pages/events/ShipmentPrepared.html +2 -20
  50. package/.next/server/pages/events/ShipmentPrepared.json +1 -1
  51. package/.next/server/pages/events/[name]/v/[version].js +15 -1
  52. package/.next/server/pages/events/[name]/v/[version].js.nft.json +1 -1
  53. package/.next/server/pages/events/[name].js +15 -1
  54. package/.next/server/pages/events/[name].js.nft.json +1 -1
  55. package/.next/server/pages/events.html +3 -3
  56. package/.next/server/pages/events.json +1 -1
  57. package/.next/server/pages/index.html +1 -1
  58. package/.next/server/pages/overview.html +1 -1
  59. package/.next/server/pages/overview.json +1 -1
  60. package/.next/server/pages/services/Basket Service.html +8 -8
  61. package/.next/server/pages/services/Basket Service.json +1 -1
  62. package/.next/server/pages/services/Data Lake.html +10 -10
  63. package/.next/server/pages/services/Data Lake.json +1 -1
  64. package/.next/server/pages/services/Payment Service.html +6 -6
  65. package/.next/server/pages/services/Payment Service.json +1 -1
  66. package/.next/server/pages/services/Shipping Service.html +2 -28
  67. package/.next/server/pages/services/Shipping Service.json +1 -1
  68. package/.next/server/pages/services/[name].js +40 -3
  69. package/.next/server/pages/services/[name].js.nft.json +1 -1
  70. package/.next/server/pages/services.html +2 -2
  71. package/.next/server/pages/services.json +1 -1
  72. package/.next/server/pages/users/dboyne.html +3 -3
  73. package/.next/server/pages/users/dboyne.json +1 -1
  74. package/.next/server/pages/users/mSmith.html +3 -3
  75. package/.next/server/pages/users/mSmith.json +1 -1
  76. package/.next/static/Wp8i1_b29rcfszKFNI7O4/_buildManifest.js +1 -0
  77. package/.next/static/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/_middlewareManifest.js +0 -0
  78. package/.next/static/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/_ssgManifest.js +0 -0
  79. package/{out/_next/static/chunks/178-9b89f5c036d77ab9.js → .next/static/chunks/178-bc36881df28d1bab.js} +1 -1
  80. package/.next/static/chunks/280-06401c009983574c.js +1 -0
  81. package/.next/static/chunks/620-d80b0351ea428525.js +1 -0
  82. package/.next/static/chunks/651-7a25d8468f22423c.js +1 -0
  83. package/.next/static/chunks/b744740b-94e91620ba96ccbf.js +1 -0
  84. package/.next/static/chunks/pages/_app-46c2e0f1ff3b4efb.js +1 -0
  85. package/.next/static/chunks/pages/events/[name]/v/{[version]-f6619ec4056cc70d.js → [version]-d8d4d12f05da9c8a.js} +1 -1
  86. package/.next/static/chunks/pages/events/{[name]-73ecab7171745a3f.js → [name]-89e1edc81aa51fc3.js} +1 -1
  87. package/.next/static/chunks/pages/events-1f39499146c9c75f.js +1 -0
  88. package/.next/static/chunks/pages/services/[name]-204382bfbc48b1b4.js +1 -0
  89. package/.next/static/chunks/pages/services-f52121c6dc1211aa.js +1 -0
  90. package/{out/_next/static/chunks/pages/users/[id]-a4274bfad4d9a9ec.js → .next/static/chunks/pages/users/[id]-d3140bb155f8fb45.js} +1 -1
  91. package/.next/static/chunks/{webpack-2ebfdb1666dac4ad.js → webpack-edf0c365e149f010.js} +1 -1
  92. package/.next/static/css/f756e2a0d8f4db27.css +3 -0
  93. package/.next/trace +56 -54
  94. package/CHANGELOG.md +24 -0
  95. package/components/Header.tsx +4 -3
  96. package/components/Mdx/NodeGraph/GraphElements.ts +183 -0
  97. package/components/Mdx/NodeGraph/GraphLayout.ts +57 -0
  98. package/components/Mdx/NodeGraph/NodeGraph.tsx +130 -0
  99. package/components/Mdx/NodeGraph/__tests__/GraphElements.spec.ts +98 -0
  100. package/components/Mdx/NodeGraph/__tests__/GraphLayout.spec.ts +104 -0
  101. package/components/Mdx/NodeGraph/__tests__/__snapshots__/GraphElements.spec.ts.snap +661 -0
  102. package/components/Mdx/NodeGraph/__tests__/__snapshots__/GraphLayout.spec.ts.snap +81 -0
  103. package/lib/__tests__/graphs.spec.ts +8 -8
  104. package/lib/graphs.ts +6 -6
  105. package/out/404/index.html +1 -1
  106. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/AddedItemToCart/logs.json +0 -0
  107. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/AddedItemToCart/v/0.0.1.json +1 -1
  108. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/AddedItemToCart/v/0.0.2.json +1 -1
  109. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/AddedItemToCart.json +1 -1
  110. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/OrderComplete/logs.json +0 -0
  111. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/OrderComplete.json +1 -1
  112. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/OrderConfirmed/logs.json +0 -0
  113. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/OrderConfirmed.json +1 -1
  114. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/OrderRequested/logs.json +0 -0
  115. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/OrderRequested.json +1 -1
  116. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/PaymentProcessed/logs.json +0 -0
  117. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/PaymentProcessed.json +1 -1
  118. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/RemovedItemFromCart/logs.json +0 -0
  119. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/RemovedItemFromCart.json +1 -1
  120. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/ShipmentDelivered/logs.json +0 -0
  121. package/out/_next/data/Wp8i1_b29rcfszKFNI7O4/events/ShipmentDelivered.json +1 -0
  122. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/ShipmentDispatched/logs.json +0 -0
  123. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/ShipmentDispatched.json +1 -1
  124. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/ShipmentPrepared/logs.json +0 -0
  125. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events/ShipmentPrepared.json +1 -1
  126. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/events.json +1 -1
  127. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/overview.json +1 -1
  128. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/services/Basket Service.json +1 -1
  129. package/out/_next/data/Wp8i1_b29rcfszKFNI7O4/services/Data Lake.json +1 -0
  130. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/services/Payment Service.json +1 -1
  131. package/out/_next/data/Wp8i1_b29rcfszKFNI7O4/services/Shipping Service.json +1 -0
  132. package/out/_next/data/Wp8i1_b29rcfszKFNI7O4/services.json +1 -0
  133. package/out/_next/data/Wp8i1_b29rcfszKFNI7O4/users/dboyne.json +1 -0
  134. package/out/_next/data/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/users/mSmith.json +1 -1
  135. package/out/_next/static/Wp8i1_b29rcfszKFNI7O4/_buildManifest.js +1 -0
  136. package/out/_next/static/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/_middlewareManifest.js +0 -0
  137. package/out/_next/static/{_D78gGWWcjFmvj_jqGXW3 → Wp8i1_b29rcfszKFNI7O4}/_ssgManifest.js +0 -0
  138. package/{.next/static/chunks/178-9b89f5c036d77ab9.js → out/_next/static/chunks/178-bc36881df28d1bab.js} +1 -1
  139. package/out/_next/static/chunks/280-06401c009983574c.js +1 -0
  140. package/out/_next/static/chunks/620-d80b0351ea428525.js +1 -0
  141. package/out/_next/static/chunks/651-7a25d8468f22423c.js +1 -0
  142. package/out/_next/static/chunks/b744740b-94e91620ba96ccbf.js +1 -0
  143. package/out/_next/static/chunks/pages/_app-46c2e0f1ff3b4efb.js +1 -0
  144. package/out/_next/static/chunks/pages/events/[name]/v/{[version]-f6619ec4056cc70d.js → [version]-d8d4d12f05da9c8a.js} +1 -1
  145. package/out/_next/static/chunks/pages/events/{[name]-73ecab7171745a3f.js → [name]-89e1edc81aa51fc3.js} +1 -1
  146. package/out/_next/static/chunks/pages/events-1f39499146c9c75f.js +1 -0
  147. package/out/_next/static/chunks/pages/services/[name]-204382bfbc48b1b4.js +1 -0
  148. package/out/_next/static/chunks/pages/services-f52121c6dc1211aa.js +1 -0
  149. package/{.next/static/chunks/pages/users/[id]-a4274bfad4d9a9ec.js → out/_next/static/chunks/pages/users/[id]-d3140bb155f8fb45.js} +1 -1
  150. package/out/_next/static/chunks/{webpack-2ebfdb1666dac4ad.js → webpack-edf0c365e149f010.js} +1 -1
  151. package/out/_next/static/css/f756e2a0d8f4db27.css +3 -0
  152. package/out/events/AddedItemToCart/index.html +6 -6
  153. package/out/events/AddedItemToCart/logs/index.html +1 -1
  154. package/out/events/AddedItemToCart/v/0.0.1/index.html +6 -6
  155. package/out/events/AddedItemToCart/v/0.0.2/index.html +6 -6
  156. package/out/events/OrderComplete/index.html +6 -6
  157. package/out/events/OrderComplete/logs/index.html +1 -1
  158. package/out/events/OrderConfirmed/index.html +3 -21
  159. package/out/events/OrderConfirmed/logs/index.html +1 -1
  160. package/out/events/OrderRequested/index.html +6 -6
  161. package/out/events/OrderRequested/logs/index.html +1 -1
  162. package/out/events/PaymentProcessed/index.html +6 -6
  163. package/out/events/PaymentProcessed/logs/index.html +1 -1
  164. package/out/events/RemovedItemFromCart/index.html +6 -6
  165. package/out/events/RemovedItemFromCart/logs/index.html +1 -1
  166. package/out/events/ShipmentDelivered/index.html +3 -23
  167. package/out/events/ShipmentDelivered/logs/index.html +1 -1
  168. package/out/events/ShipmentDispatched/index.html +6 -6
  169. package/out/events/ShipmentDispatched/logs/index.html +1 -1
  170. package/out/events/ShipmentPrepared/index.html +2 -20
  171. package/out/events/ShipmentPrepared/logs/index.html +1 -1
  172. package/out/events/index.html +3 -3
  173. package/out/index.html +1 -1
  174. package/out/overview/index.html +1 -1
  175. package/out/services/Basket Service/index.html +8 -8
  176. package/out/services/Data Lake/index.html +10 -10
  177. package/out/services/Payment Service/index.html +6 -6
  178. package/out/services/Shipping Service/index.html +2 -28
  179. package/out/services/index.html +2 -2
  180. package/out/users/dboyne/index.html +3 -3
  181. package/out/users/mSmith/index.html +3 -3
  182. package/package.json +3 -1
  183. package/pages/events/[name].tsx +36 -0
  184. package/pages/services/[name].tsx +36 -0
  185. package/.next/static/_D78gGWWcjFmvj_jqGXW3/_buildManifest.js +0 -1
  186. package/.next/static/chunks/336-b1acb0b6aa306baf.js +0 -1
  187. package/.next/static/chunks/650-4004943b2a9623ca.js +0 -1
  188. package/.next/static/chunks/pages/_app-c205def1388372e3.js +0 -1
  189. package/.next/static/chunks/pages/events-c9e5a0fa7d23d620.js +0 -1
  190. package/.next/static/chunks/pages/services/[name]-d8873d29190a6447.js +0 -1
  191. package/.next/static/chunks/pages/services-30e4132ef02b1784.js +0 -1
  192. package/.next/static/css/0b54ec6503b91283.css +0 -3
  193. package/out/_next/data/_D78gGWWcjFmvj_jqGXW3/events/ShipmentDelivered.json +0 -1
  194. package/out/_next/data/_D78gGWWcjFmvj_jqGXW3/services/Data Lake.json +0 -1
  195. package/out/_next/data/_D78gGWWcjFmvj_jqGXW3/services/Shipping Service.json +0 -1
  196. package/out/_next/data/_D78gGWWcjFmvj_jqGXW3/services.json +0 -1
  197. package/out/_next/data/_D78gGWWcjFmvj_jqGXW3/users/dboyne.json +0 -1
  198. package/out/_next/static/_D78gGWWcjFmvj_jqGXW3/_buildManifest.js +0 -1
  199. package/out/_next/static/chunks/336-b1acb0b6aa306baf.js +0 -1
  200. package/out/_next/static/chunks/650-4004943b2a9623ca.js +0 -1
  201. package/out/_next/static/chunks/pages/_app-c205def1388372e3.js +0 -1
  202. package/out/_next/static/chunks/pages/events-c9e5a0fa7d23d620.js +0 -1
  203. package/out/_next/static/chunks/pages/services/[name]-d8873d29190a6447.js +0 -1
  204. package/out/_next/static/chunks/pages/services-30e4132ef02b1784.js +0 -1
  205. package/out/_next/static/css/0b54ec6503b91283.css +0 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
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
+
9
+ ## 0.1.12
10
+
11
+ ### Patch Changes
12
+
13
+ - [`5dcc188`](https://github.com/boyney123/eventcatalog/commit/5dcc188dbd68f70b867e38df91211ba077f14189) [#175](https://github.com/boyney123/eventcatalog/pull/175) Thanks [@thim81](https://github.com/thim81)! - fix: mermaid diagram with pub/sub of same service now shows correctly
14
+
15
+ ## 0.1.11
16
+
17
+ ### Patch Changes
18
+
19
+ - [`0976201`](https://github.com/boyney123/eventcatalog/commit/0976201a6949f4a78934a4ddfb75e7ea4598caae) [#172](https://github.com/boyney123/eventcatalog/pull/172) Thanks [@boyney123](https://github.com/boyney123)! - fix: header logo now reads from config
20
+
21
+ ## 0.1.10
22
+
23
+ ### Patch Changes
24
+
25
+ - [`b44bd6c`](https://github.com/boyney123/eventcatalog/commit/b44bd6ca73194165e6448abebd020e3d6f3007a2) [#169](https://github.com/boyney123/eventcatalog/pull/169) Thanks [@thim81](https://github.com/thim81)! - fix: logic for services publish & subscribe nodes
26
+
3
27
  ## 0.1.9
4
28
 
5
29
  ### Patch Changes
@@ -14,10 +14,11 @@ function classNames(...classes) {
14
14
  }
15
15
 
16
16
  export default function Example() {
17
- const { title, homepageLink } = useConfig();
17
+ const { title, homepageLink, logo } = useConfig();
18
18
  const router = useRouter();
19
19
 
20
20
  const { publicRuntimeConfig: { basePath = '' } = {} } = getConfig();
21
+ const logoToLoad = logo || { alt: 'EventCatalog Logo', src: `logo.svg` };
21
22
 
22
23
  return (
23
24
  <div className="bg-gray-800">
@@ -28,14 +29,14 @@ export default function Example() {
28
29
  {!homepageLink && (
29
30
  <Link href="/">
30
31
  <a className="flex items-center">
31
- <img alt="logo" className="text-white w-8 inline-block mr-3" src={`${basePath}/logo.svg`} />
32
+ <img alt="logo" className="text-white w-8 inline-block mr-3" src={`${basePath}/${logoToLoad.src}`} />
32
33
  <span className="text-xl">{title}</span>
33
34
  </a>
34
35
  </Link>
35
36
  )}
36
37
  {homepageLink && (
37
38
  <a href={homepageLink} className="flex items-center">
38
- <img alt="logo" className="text-white w-8 inline-block mr-3" src={`${basePath}/logo.svg`} />
39
+ <img alt="logo" className="text-white w-8 inline-block mr-3" src={`${basePath}/${logoToLoad.src}`} />
39
40
  <span className="text-xl">{title}</span>
40
41
  </a>
41
42
  )}
@@ -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
+ });