@eventcatalog/core 3.7.2 → 3.8.0

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 (66) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-O6SRHGZ7.js → chunk-4EJDLNIX.js} +1 -1
  6. package/dist/{chunk-WAX3S32H.js → chunk-EG36OTR7.js} +1 -1
  7. package/dist/{chunk-GQZVIS3Z.js → chunk-GITARDPK.js} +1 -1
  8. package/dist/{chunk-7CTNGTBB.js → chunk-IEEU454Z.js} +1 -1
  9. package/dist/{chunk-M7EPRGHR.js → chunk-ZIG6J4R2.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/eventcatalog.cjs +1 -1
  13. package/dist/eventcatalog.js +5 -5
  14. package/dist/generate.cjs +1 -1
  15. package/dist/generate.js +3 -3
  16. package/dist/utils/cli-logger.cjs +1 -1
  17. package/dist/utils/cli-logger.js +2 -2
  18. package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +13 -1
  19. package/eventcatalog/src/components/Grids/DomainGrid.tsx +109 -6
  20. package/eventcatalog/src/components/Grids/utils.tsx +10 -1
  21. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +2 -0
  22. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +4 -0
  23. package/eventcatalog/src/components/MDX/NodeGraph/Nodes/DataProduct.tsx +132 -0
  24. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +29 -2
  25. package/eventcatalog/src/components/SchemaExplorer/types.ts +5 -1
  26. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +3 -0
  27. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +1 -0
  28. package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +23 -1
  29. package/eventcatalog/src/components/Tables/Discover/columns.tsx +62 -0
  30. package/eventcatalog/src/content.config.ts +34 -0
  31. package/eventcatalog/src/enterprise/ai/chat-api.ts +26 -0
  32. package/eventcatalog/src/enterprise/custom-documentation/utils/custom-docs.ts +1 -1
  33. package/eventcatalog/src/enterprise/tools/catalog-tools.ts +169 -2
  34. package/eventcatalog/src/pages/discover/[type]/_index.data.ts +5 -1
  35. package/eventcatalog/src/pages/discover/[type]/index.astro +57 -1
  36. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +1 -0
  37. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +5 -1
  38. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/_index.data.ts +27 -3
  39. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +74 -25
  40. package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +55 -1
  41. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +10 -1
  42. package/eventcatalog/src/stores/sidebar-store/builders/container.ts +23 -16
  43. package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +130 -0
  44. package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +11 -0
  45. package/eventcatalog/src/stores/sidebar-store/state.ts +68 -13
  46. package/eventcatalog/src/styles/theme.css +4 -0
  47. package/eventcatalog/src/styles/themes/forest.css +4 -0
  48. package/eventcatalog/src/styles/themes/ocean.css +4 -0
  49. package/eventcatalog/src/styles/themes/sapphire.css +4 -0
  50. package/eventcatalog/src/styles/themes/sunset.css +4 -0
  51. package/eventcatalog/src/types/index.ts +4 -2
  52. package/eventcatalog/src/utils/collections/commands.ts +11 -29
  53. package/eventcatalog/src/utils/collections/containers.ts +25 -1
  54. package/eventcatalog/src/utils/collections/data-products.ts +85 -0
  55. package/eventcatalog/src/utils/collections/domains.ts +28 -10
  56. package/eventcatalog/src/utils/collections/events.ts +11 -29
  57. package/eventcatalog/src/utils/collections/icons.ts +5 -0
  58. package/eventcatalog/src/utils/collections/messages.ts +68 -0
  59. package/eventcatalog/src/utils/collections/queries.ts +11 -29
  60. package/eventcatalog/src/utils/collections/util.ts +11 -2
  61. package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +91 -3
  62. package/eventcatalog/src/utils/node-graphs/data-products-node-graph.ts +225 -0
  63. package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +28 -2
  64. package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +74 -20
  65. package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +2 -0
  66. package/package.json +2 -2
@@ -37,7 +37,7 @@ var import_axios = __toESM(require("axios"), 1);
37
37
  var import_os = __toESM(require("os"), 1);
38
38
 
39
39
  // package.json
40
- var version = "3.7.2";
40
+ var version = "3.8.0";
41
41
 
42
42
  // src/constants.ts
43
43
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-GQZVIS3Z.js";
4
- import "../chunk-WAX3S32H.js";
3
+ } from "../chunk-GITARDPK.js";
4
+ import "../chunk-EG36OTR7.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -106,7 +106,7 @@ var import_axios = __toESM(require("axios"), 1);
106
106
  var import_os = __toESM(require("os"), 1);
107
107
 
108
108
  // package.json
109
- var version = "3.7.2";
109
+ var version = "3.8.0";
110
110
 
111
111
  // src/constants.ts
112
112
  var VERSION = version;
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  log_build_default
3
- } from "../chunk-M7EPRGHR.js";
4
- import "../chunk-GQZVIS3Z.js";
5
- import "../chunk-WAX3S32H.js";
3
+ } from "../chunk-ZIG6J4R2.js";
4
+ import "../chunk-GITARDPK.js";
5
+ import "../chunk-EG36OTR7.js";
6
6
  import "../chunk-UPONRQSN.js";
7
7
  export {
8
8
  log_build_default as default
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  logger
3
- } from "./chunk-7CTNGTBB.js";
3
+ } from "./chunk-IEEU454Z.js";
4
4
  import {
5
5
  cleanup,
6
6
  getEventCatalogConfigFile
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "3.7.2";
2
+ var version = "3.8.0";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-WAX3S32H.js";
3
+ } from "./chunk-EG36OTR7.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import axios from "axios";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-WAX3S32H.js";
3
+ } from "./chunk-EG36OTR7.js";
4
4
 
5
5
  // src/utils/cli-logger.ts
6
6
  import pc from "picocolors";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-GQZVIS3Z.js";
3
+ } from "./chunk-GITARDPK.js";
4
4
  import {
5
5
  getEventCatalogConfigFile,
6
6
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "3.7.2";
28
+ var version = "3.8.0";
29
29
 
30
30
  // src/constants.ts
31
31
  var VERSION = version;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-WAX3S32H.js";
3
+ } from "./chunk-EG36OTR7.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -109,7 +109,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
109
109
  var import_picocolors = __toESM(require("picocolors"), 1);
110
110
 
111
111
  // package.json
112
- var version = "3.7.2";
112
+ var version = "3.8.0";
113
113
 
114
114
  // src/constants.ts
115
115
  var VERSION = version;
@@ -6,8 +6,8 @@ import {
6
6
  } from "./chunk-PLNJC7NZ.js";
7
7
  import {
8
8
  log_build_default
9
- } from "./chunk-M7EPRGHR.js";
10
- import "./chunk-GQZVIS3Z.js";
9
+ } from "./chunk-ZIG6J4R2.js";
10
+ import "./chunk-GITARDPK.js";
11
11
  import {
12
12
  runMigrations
13
13
  } from "./chunk-BH3JMNAV.js";
@@ -21,13 +21,13 @@ import {
21
21
  } from "./chunk-5VBIXL6C.js";
22
22
  import {
23
23
  generate
24
- } from "./chunk-O6SRHGZ7.js";
24
+ } from "./chunk-4EJDLNIX.js";
25
25
  import {
26
26
  logger
27
- } from "./chunk-7CTNGTBB.js";
27
+ } from "./chunk-IEEU454Z.js";
28
28
  import {
29
29
  VERSION
30
- } from "./chunk-WAX3S32H.js";
30
+ } from "./chunk-EG36OTR7.js";
31
31
  import "./chunk-UPONRQSN.js";
32
32
 
33
33
  // src/eventcatalog.ts
package/dist/generate.cjs CHANGED
@@ -73,7 +73,7 @@ var getEventCatalogConfigFile = async (projectDirectory) => {
73
73
  var import_picocolors = __toESM(require("picocolors"), 1);
74
74
 
75
75
  // package.json
76
- var version = "3.7.2";
76
+ var version = "3.8.0";
77
77
 
78
78
  // src/constants.ts
79
79
  var VERSION = version;
package/dist/generate.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  generate
3
- } from "./chunk-O6SRHGZ7.js";
4
- import "./chunk-7CTNGTBB.js";
5
- import "./chunk-WAX3S32H.js";
3
+ } from "./chunk-4EJDLNIX.js";
4
+ import "./chunk-IEEU454Z.js";
5
+ import "./chunk-EG36OTR7.js";
6
6
  import "./chunk-UPONRQSN.js";
7
7
  export {
8
8
  generate
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(cli_logger_exports);
36
36
  var import_picocolors = __toESM(require("picocolors"), 1);
37
37
 
38
38
  // package.json
39
- var version = "3.7.2";
39
+ var version = "3.8.0";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  logger
3
- } from "../chunk-7CTNGTBB.js";
4
- import "../chunk-WAX3S32H.js";
3
+ } from "../chunk-IEEU454Z.js";
4
+ import "../chunk-EG36OTR7.js";
5
5
  export {
6
6
  logger
7
7
  };
@@ -171,6 +171,17 @@ const suggestedQuestionsConfig: QuestionConfig[] = [
171
171
  { label: 'Who owns this domain?', prompt: 'Who owns this domain and how do I contact them?' },
172
172
  ],
173
173
  },
174
+ // Data Products page
175
+ {
176
+ pattern: /^\/(docs|visualiser)\/data-products\/.+/,
177
+ questions: [
178
+ { label: 'What are the inputs and outputs?', prompt: 'What are the inputs and outputs of this data product?' },
179
+ { label: 'Show me the data contracts', prompt: 'What are the data contracts for this data product?' },
180
+ { label: 'Is this data product production ready?', prompt: 'Is this data product production ready?' },
181
+ { label: 'What is the quality & SLA of this product?', prompt: 'What is the quality & SLA of this product?' },
182
+ { label: 'Who owns this data product?', prompt: 'Who owns this data product and how do I contact them?' },
183
+ ],
184
+ },
174
185
  // Designs page
175
186
  {
176
187
  pattern: /^\/diagrams\/.+/,
@@ -490,7 +501,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
490
501
  // Memoize page context to avoid recalculating on every render
491
502
  const pageContext = useMemo(() => {
492
503
  const match = pathname.match(
493
- /^\/(docs|visualiser|architecture)\/(events|services|commands|queries|flows|domains|channels|entities|containers)\/([^/]+)(?:\/([^/]+))?/
504
+ /^\/(docs|visualiser|architecture)\/(events|services|commands|queries|flows|domains|channels|entities|containers|data-products)\/([^/]+)(?:\/([^/]+))?/
494
505
  );
495
506
  if (match) {
496
507
  const [, , collection, id, version] = match;
@@ -504,6 +515,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
504
515
  channels: 'Channel',
505
516
  entities: 'Entity',
506
517
  containers: 'Container',
518
+ 'data-products': 'Data Product',
507
519
  };
508
520
  return {
509
521
  type: collectionNames[collection] || collection,
@@ -11,6 +11,7 @@ import {
11
11
  ArrowTopRightOnSquareIcon,
12
12
  ArrowLongRightIcon,
13
13
  ArrowLongLeftIcon,
14
+ CubeIcon,
14
15
  } from '@heroicons/react/24/outline';
15
16
  import { buildUrl } from '@utils/url-builder';
16
17
  import { BoxIcon } from 'lucide-react';
@@ -324,6 +325,43 @@ const ServiceCard = memo(({ service }: { service: any }) => {
324
325
  );
325
326
  });
326
327
 
328
+ const DataProductCard = memo(({ dataProduct }: { dataProduct: any }) => {
329
+ const data = dataProduct?.data || dataProduct;
330
+
331
+ if (!data?.id) return null;
332
+
333
+ const href = buildUrl(`/docs/data-products/${data.id}/${data.version}`);
334
+
335
+ return (
336
+ <a
337
+ href={href}
338
+ className="block bg-[rgb(var(--ec-card-bg,var(--ec-page-bg)))] border border-[rgb(var(--ec-page-border))] rounded-xl shadow-sm hover:shadow-md hover:border-[rgb(var(--ec-accent)/0.5)] transition-all"
339
+ >
340
+ <div className="flex items-center justify-between px-4 py-3">
341
+ <div className="flex items-center gap-2.5">
342
+ <div className="flex items-center justify-center w-8 h-8 bg-purple-100 dark:bg-purple-500/20 rounded-lg">
343
+ <CubeIcon className="h-4 w-4 text-purple-600 dark:text-purple-400" />
344
+ </div>
345
+ <div>
346
+ <div className="flex items-center gap-2">
347
+ <span className="font-semibold text-[rgb(var(--ec-page-text))]">{data.name || data.id}</span>
348
+ <span className="text-[11px] text-[rgb(var(--ec-page-text-muted))] font-medium bg-[rgb(var(--ec-content-hover))] px-1.5 py-0.5 rounded">
349
+ v{data.version}
350
+ </span>
351
+ </div>
352
+ {data.summary && (
353
+ <p className="text-xs text-[rgb(var(--ec-page-text-muted))] line-clamp-1 mt-0.5 max-w-md">{data.summary}</p>
354
+ )}
355
+ </div>
356
+ </div>
357
+ <div className="p-1.5 text-[rgb(var(--ec-icon-color))]">
358
+ <ArrowTopRightOnSquareIcon className="h-4 w-4" />
359
+ </div>
360
+ </div>
361
+ </a>
362
+ );
363
+ });
364
+
327
365
  const SubdomainSection = memo(({ subdomain }: { subdomain: any }) => {
328
366
  const data = subdomain?.data || subdomain;
329
367
  const [isCollapsed, setIsCollapsed] = useState(true);
@@ -332,6 +370,7 @@ const SubdomainSection = memo(({ subdomain }: { subdomain: any }) => {
332
370
 
333
371
  const services = data.services || [];
334
372
  const entities = data.entities || [];
373
+ const dataProducts = data['data-products'] || [];
335
374
 
336
375
  return (
337
376
  <div className="bg-[rgb(var(--ec-card-bg,var(--ec-page-bg)))] border border-[rgb(var(--ec-page-border))] rounded-xl overflow-hidden shadow-sm">
@@ -351,11 +390,13 @@ const SubdomainSection = memo(({ subdomain }: { subdomain: any }) => {
351
390
  v{data.version}
352
391
  </span>
353
392
  {/* Show counts when collapsed */}
354
- {isCollapsed && (services.length > 0 || entities.length > 0) && (
393
+ {isCollapsed && (services.length > 0 || entities.length > 0 || dataProducts.length > 0) && (
355
394
  <span className="text-[11px] text-[rgb(var(--ec-icon-color))] ml-1">
356
395
  {services.length > 0 && `${services.length} service${services.length > 1 ? 's' : ''}`}
357
- {services.length > 0 && entities.length > 0 && ', '}
396
+ {services.length > 0 && (entities.length > 0 || dataProducts.length > 0) && ', '}
358
397
  {entities.length > 0 && `${entities.length} entit${entities.length > 1 ? 'ies' : 'y'}`}
398
+ {entities.length > 0 && dataProducts.length > 0 && ', '}
399
+ {dataProducts.length > 0 && `${dataProducts.length} data product${dataProducts.length > 1 ? 's' : ''}`}
359
400
  </span>
360
401
  )}
361
402
  </div>
@@ -417,9 +458,28 @@ const SubdomainSection = memo(({ subdomain }: { subdomain: any }) => {
417
458
  </div>
418
459
  )}
419
460
 
420
- {entities.length === 0 && services.length === 0 && (
461
+ {/* Subdomain Data Products */}
462
+ {dataProducts.length > 0 && (
463
+ <div>
464
+ <div className="flex items-center gap-2 mb-3">
465
+ <CubeIcon className="h-4 w-4 text-purple-600 dark:text-purple-400" />
466
+ <h4 className="text-sm font-semibold text-[rgb(var(--ec-page-text))]">Data Products</h4>
467
+ <span className="text-xs text-[rgb(var(--ec-page-text-muted))] bg-[rgb(var(--ec-content-hover))] px-2 py-0.5 rounded-full font-medium">
468
+ {dataProducts.length}
469
+ </span>
470
+ </div>
471
+ <div className="space-y-3">
472
+ {dataProducts.map((dataProduct: any) => {
473
+ const dataProductId = dataProduct?.data?.id || dataProduct?.id;
474
+ return dataProductId ? <DataProductCard key={dataProductId} dataProduct={dataProduct} /> : null;
475
+ })}
476
+ </div>
477
+ </div>
478
+ )}
479
+
480
+ {entities.length === 0 && services.length === 0 && dataProducts.length === 0 && (
421
481
  <p className="text-sm text-[rgb(var(--ec-icon-color))] italic text-center py-4">
422
- No entities or services in this subdomain
482
+ No entities, services, or data products in this subdomain
423
483
  </p>
424
484
  )}
425
485
  </div>
@@ -439,6 +499,7 @@ export default function DomainGrid({ domain }: DomainGridProps) {
439
499
  const subdomains = data.domains || [];
440
500
  const entities = data.entities || [];
441
501
  const services = data.services || [];
502
+ const dataProducts = data['data-products'] || [];
442
503
 
443
504
  // Get services that are NOT in any subdomain
444
505
  const subdomainServiceIds = useMemo(
@@ -452,6 +513,18 @@ export default function DomainGrid({ domain }: DomainGridProps) {
452
513
  [subdomains]
453
514
  );
454
515
 
516
+ // Get data products that are NOT in any subdomain
517
+ const subdomainDataProductIds = useMemo(
518
+ () =>
519
+ new Set(
520
+ subdomains.flatMap((sd: any) => {
521
+ const sdData = sd?.data || sd;
522
+ return (sdData?.['data-products'] || []).map((dp: any) => dp?.data?.id || dp?.id);
523
+ })
524
+ ),
525
+ [subdomains]
526
+ );
527
+
455
528
  const topLevelServices = useMemo(
456
529
  () =>
457
530
  services.filter((s: any) => {
@@ -461,6 +534,15 @@ export default function DomainGrid({ domain }: DomainGridProps) {
461
534
  [services, subdomainServiceIds]
462
535
  );
463
536
 
537
+ const topLevelDataProducts = useMemo(
538
+ () =>
539
+ dataProducts.filter((dp: any) => {
540
+ const dpId = dp?.data?.id || dp?.id;
541
+ return dpId && !subdomainDataProductIds.has(dpId);
542
+ }),
543
+ [dataProducts, subdomainDataProductIds]
544
+ );
545
+
464
546
  return (
465
547
  <div className="w-full">
466
548
  {/* Domain Header - Doc style */}
@@ -548,13 +630,34 @@ export default function DomainGrid({ domain }: DomainGridProps) {
548
630
  </div>
549
631
  )}
550
632
 
633
+ {/* Top-level Data Products */}
634
+ {topLevelDataProducts.length > 0 && (
635
+ <div>
636
+ <div className="flex items-center gap-2 mb-4">
637
+ <CubeIcon className="h-5 w-5 text-purple-600 dark:text-purple-400" />
638
+ <h3 className="text-lg font-semibold text-[rgb(var(--ec-page-text))]">Data Products</h3>
639
+ <span className="text-sm text-[rgb(var(--ec-page-text-muted))] bg-[rgb(var(--ec-content-hover))] px-2.5 py-0.5 rounded-full font-medium">
640
+ {topLevelDataProducts.length}
641
+ </span>
642
+ </div>
643
+ <div className="space-y-3">
644
+ {topLevelDataProducts.map((dataProduct: any) => {
645
+ const dataProductId = dataProduct?.data?.id || dataProduct?.id;
646
+ return dataProductId ? <DataProductCard key={dataProductId} dataProduct={dataProduct} /> : null;
647
+ })}
648
+ </div>
649
+ </div>
650
+ )}
651
+
551
652
  {/* Empty state */}
552
- {entities.length === 0 && services.length === 0 && subdomains.length === 0 && (
653
+ {entities.length === 0 && services.length === 0 && dataProducts.length === 0 && subdomains.length === 0 && (
553
654
  <div className="text-center py-12">
554
655
  <div className="flex items-center justify-center w-16 h-16 mx-auto mb-4 bg-[rgb(var(--ec-content-hover))] rounded-2xl">
555
656
  <RectangleGroupIcon className="h-8 w-8 text-[rgb(var(--ec-icon-color))]" />
556
657
  </div>
557
- <p className="text-[rgb(var(--ec-page-text-muted))]">This domain has no entities, services, or subdomains defined.</p>
658
+ <p className="text-[rgb(var(--ec-page-text-muted))]">
659
+ This domain has no entities, services, data products, or subdomains defined.
660
+ </p>
558
661
  </div>
559
662
  )}
560
663
  </div>
@@ -1,4 +1,11 @@
1
- import { BoltIcon, ChatBubbleLeftIcon, MagnifyingGlassIcon, EnvelopeIcon, ServerIcon } from '@heroicons/react/24/outline';
1
+ import {
2
+ BoltIcon,
3
+ ChatBubbleLeftIcon,
4
+ MagnifyingGlassIcon,
5
+ EnvelopeIcon,
6
+ ServerIcon,
7
+ DocumentCheckIcon,
8
+ } from '@heroicons/react/24/outline';
2
9
  import type { CollectionMessageTypes, CollectionTypes } from '@types';
3
10
 
4
11
  export const getCollectionStyles = (collection: CollectionMessageTypes | CollectionTypes) => {
@@ -11,6 +18,8 @@ export const getCollectionStyles = (collection: CollectionMessageTypes | Collect
11
18
  return { color: 'green', Icon: MagnifyingGlassIcon };
12
19
  case 'services':
13
20
  return { color: 'pink', Icon: ServerIcon };
21
+ case 'data-products':
22
+ return { color: 'purple', Icon: DocumentCheckIcon };
14
23
  default:
15
24
  return { color: 'gray', Icon: EnvelopeIcon };
16
25
  }
@@ -13,6 +13,7 @@ import {
13
13
  import { getNodesAndEdges as getNodesAndEdgesForDomainEntityMap } from '@utils/node-graphs/domain-entity-map';
14
14
  import { getDomainsCanvasData } from '@utils/node-graphs/domains-canvas';
15
15
  import { getNodesAndEdges as getNodesAndEdgesForFlows } from '@utils/node-graphs/flows-node-graph';
16
+ import { getNodesAndEdges as getNodesAndEdgesForDataProduct } from '@utils/node-graphs/data-products-node-graph';
16
17
  import { buildUrl } from '@utils/url-builder';
17
18
  import { getVersionFromCollection } from '@utils/collections/versions';
18
19
  import { pageDataLoader } from '@utils/page-loaders/page-data-loader';
@@ -61,6 +62,7 @@ const getNodesAndEdgesFunctions = {
61
62
  domains: getNodesAndEdgesForDomain,
62
63
  flows: getNodesAndEdgesForFlows,
63
64
  containers: getNodesAndEdgesForContainer,
65
+ 'data-products': getNodesAndEdgesForDataProduct,
64
66
  };
65
67
 
66
68
  let links: { label: string; url: string }[] = [];
@@ -39,6 +39,7 @@ import DataNode from './Nodes/Data';
39
39
  import ViewNode from './Nodes/View';
40
40
  import ActorNode from './Nodes/Actor';
41
41
  import ExternalSystemNode2 from './Nodes/ExternalSystem2';
42
+ import DataProductNode from './Nodes/DataProduct';
42
43
  import { Note as NoteNode } from '@eventcatalog/visualizer';
43
44
 
44
45
  import type { CollectionEntry } from 'astro:content';
@@ -118,6 +119,8 @@ const NodeGraphBuilder = ({
118
119
  data: DataNode,
119
120
  view: ViewNode,
120
121
  actor: ActorNode,
122
+ 'data-product': DataProductNode,
123
+ 'data-products': DataProductNode,
121
124
  note: (props: any) => <NoteNode {...props} readOnly={true} />,
122
125
  }) as unknown as NodeTypes,
123
126
  []
@@ -462,6 +465,7 @@ const NodeGraphBuilder = ({
462
465
  actor: 'bg-yellow-500',
463
466
  step: 'bg-gray-700',
464
467
  data: 'bg-blue-600',
468
+ 'data-products': 'bg-indigo-600',
465
469
  };
466
470
 
467
471
  let legendForDomains: { [key: string]: { count: number; colorClass: string; groupId: string } } = {};
@@ -0,0 +1,132 @@
1
+ import { Handle } from '@xyflow/react';
2
+ import * as ContextMenu from '@radix-ui/react-context-menu';
3
+ import { buildUrl } from '@utils/url-builder';
4
+ import { Position } from '@xyflow/react';
5
+ import { Package } from 'lucide-react';
6
+
7
+ function classNames(...classes: any) {
8
+ return classes.filter(Boolean).join(' ');
9
+ }
10
+
11
+ interface DataProductNodeProps {
12
+ data: {
13
+ mode: 'simple' | 'full';
14
+ dataProduct: {
15
+ id: string;
16
+ version: string;
17
+ name: string;
18
+ summary?: string;
19
+ inputs?: any[];
20
+ outputs?: any[];
21
+ owners?: any[];
22
+ };
23
+ };
24
+ selected?: boolean;
25
+ }
26
+
27
+ export default function DataProductNode(props: DataProductNodeProps) {
28
+ const { id, version, name, summary, inputs = [], outputs = [], owners = [] } = props.data.dataProduct;
29
+ const mode = props.data.mode || 'simple';
30
+ const nodeLabel = 'Data Product';
31
+ const rotatedLabel = 'Product';
32
+
33
+ return (
34
+ <ContextMenu.Root>
35
+ <ContextMenu.Trigger>
36
+ <div className="relative">
37
+ <Handle
38
+ type="target"
39
+ position={Position.Left}
40
+ style={{ width: 10, height: 10, background: '#6366f1', zIndex: 10 }}
41
+ className="bg-indigo-500"
42
+ />
43
+ <Handle
44
+ type="source"
45
+ position={Position.Right}
46
+ style={{ width: 10, height: 10, background: '#6366f1', zIndex: 10 }}
47
+ className="bg-indigo-500"
48
+ />
49
+ <div
50
+ className={classNames(
51
+ 'w-full rounded-md border flex justify-start bg-white text-black',
52
+ props.selected ? 'border-indigo-600 ring-2 ring-indigo-500 shadow-lg' : 'border-indigo-400'
53
+ )}
54
+ >
55
+ <div
56
+ className={`bg-gradient-to-b from-indigo-500 to-indigo-700 relative flex items-center w-5 justify-center rounded-l-sm text-indigo-100 border-r-[1px] border-indigo-500`}
57
+ >
58
+ <Package className="w-4 h-4 opacity-90 text-white absolute top-1" />
59
+ {mode === 'full' && (
60
+ <span
61
+ className={'w-1/2 text-center absolute bottom-1 text-[8px] text-white font-bold uppercase tracking-[3px]'}
62
+ style={{ transform: 'rotate(-90deg)' }}
63
+ >
64
+ {rotatedLabel}
65
+ </span>
66
+ )}
67
+ </div>
68
+ <div className="p-1 min-w-60 max-w-[min-content]">
69
+ <div className={classNames(mode === 'full' ? `border-b border-gray-200` : '')}>
70
+ <span className="text-xs font-bold block pt-0.5 pb-0.5">{name}</span>
71
+ <div className="flex justify-between">
72
+ <span className="text-[10px] font-light block pt-0.5 pb-0.5">v{version}</span>
73
+ {mode === 'simple' && (
74
+ <span className="text-[10px] text-gray-500 font-light block pt-0.5 pb-0.5">{nodeLabel}</span>
75
+ )}
76
+ </div>
77
+ </div>
78
+ {mode === 'full' && (
79
+ <div className="divide-y divide-gray-200">
80
+ <div className="leading-3 py-1">
81
+ <div
82
+ className="text-[8px] font-light overflow-hidden"
83
+ style={{
84
+ display: '-webkit-box',
85
+ WebkitLineClamp: 2,
86
+ WebkitBoxOrient: 'vertical',
87
+ }}
88
+ title={summary}
89
+ >
90
+ {summary}
91
+ </div>
92
+ </div>
93
+
94
+ <div className="grid grid-cols-2 gap-x-4 py-1">
95
+ <span className="text-xs" style={{ fontSize: '0.2em' }}>
96
+ Inputs: {inputs.length}
97
+ </span>
98
+ <span className="text-xs" style={{ fontSize: '0.2em' }}>
99
+ Outputs: {outputs.length}
100
+ </span>
101
+ <span className="text-xs" style={{ fontSize: '0.2em' }}>
102
+ Owners: {owners.length}
103
+ </span>
104
+ </div>
105
+ </div>
106
+ )}
107
+ </div>
108
+ </div>
109
+ </div>
110
+ </ContextMenu.Trigger>
111
+ <ContextMenu.Portal>
112
+ <ContextMenu.Content className="min-w-[220px] bg-white rounded-md p-1 shadow-md border border-gray-200">
113
+ <ContextMenu.Item
114
+ asChild
115
+ className="text-sm px-2 py-1.5 outline-none cursor-pointer hover:bg-indigo-100 rounded-sm flex items-center"
116
+ >
117
+ <a href={buildUrl(`/docs/data-products/${id}/${version}`)}>Read documentation</a>
118
+ </ContextMenu.Item>
119
+ <ContextMenu.Separator className="h-[1px] bg-gray-200 m-1" />
120
+ <ContextMenu.Item asChild>
121
+ <a
122
+ href={buildUrl(`/visualiser/data-products/${id}/${version}`)}
123
+ className="text-sm px-2 py-1.5 outline-none cursor-pointer hover:bg-indigo-100 rounded-sm flex items-center"
124
+ >
125
+ View in Visualiser
126
+ </a>
127
+ </ContextMenu.Item>
128
+ </ContextMenu.Content>
129
+ </ContextMenu.Portal>
130
+ </ContextMenu.Root>
131
+ );
132
+ }