@eventcatalog/core 2.58.1 → 2.59.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 (53) 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-X4GKQ2ZE.js → chunk-F5WMB6Q5.js} +1 -1
  6. package/dist/{chunk-N7MEYFUO.js → chunk-SCDNNFXH.js} +1 -1
  7. package/dist/{chunk-F3YDLMMR.js → chunk-T3QXQPTB.js} +1 -1
  8. package/dist/constants.cjs +1 -1
  9. package/dist/constants.js +1 -1
  10. package/dist/eventcatalog.cjs +1 -1
  11. package/dist/eventcatalog.js +3 -3
  12. package/eventcatalog/astro.config.mjs +13 -7
  13. package/eventcatalog/src/components/Grids/DomainGrid.tsx +67 -2
  14. package/eventcatalog/src/components/Grids/MessageGrid.tsx +157 -41
  15. package/eventcatalog/src/components/Grids/ServiceGrid.tsx +78 -14
  16. package/eventcatalog/src/components/MDX/NodeGraph/Edges/MultilineEdgeLabel.tsx +52 -0
  17. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +13 -0
  18. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +4 -1
  19. package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Data.tsx +55 -16
  20. package/eventcatalog/src/components/SideBars/ContainerSideBar.astro +180 -0
  21. package/eventcatalog/src/components/SideBars/ServiceSideBar.astro +41 -0
  22. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/MessageList.tsx +1 -1
  23. package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +250 -59
  24. package/eventcatalog/src/components/SideNav/ListViewSideBar/types.ts +3 -0
  25. package/eventcatalog/src/components/SideNav/ListViewSideBar/utils.ts +35 -1
  26. package/eventcatalog/src/components/SideNav/TreeView/getTreeView.ts +2 -2
  27. package/eventcatalog/src/components/Tables/Table.tsx +22 -2
  28. package/eventcatalog/src/components/Tables/columns/ContainersTableColumns.tsx +152 -0
  29. package/eventcatalog/src/components/Tables/columns/index.tsx +3 -0
  30. package/eventcatalog/src/content.config.ts +57 -1
  31. package/eventcatalog/src/layouts/DiscoverLayout.astro +11 -1
  32. package/eventcatalog/src/pages/architecture/architecture.astro +9 -1
  33. package/eventcatalog/src/pages/discover/[type]/_index.data.ts +1 -1
  34. package/eventcatalog/src/pages/discover/[type]/index.astro +11 -1
  35. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +11 -1
  36. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +50 -1
  37. package/eventcatalog/src/pages/docs/[type]/[id]/[version].md.ts +2 -0
  38. package/eventcatalog/src/pages/docs/llm/llms-full.txt.ts +4 -1
  39. package/eventcatalog/src/pages/docs/llm/llms-services.txt.ts +19 -1
  40. package/eventcatalog/src/pages/docs/llm/llms.txt.ts +3 -0
  41. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +1 -1
  42. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/_index.data.ts +80 -0
  43. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/index.astro +52 -0
  44. package/eventcatalog/src/pages/visualiser/[type]/[id]/index.astro +9 -2
  45. package/eventcatalog/src/types/index.ts +20 -2
  46. package/eventcatalog/src/utils/collections/containers.ts +94 -0
  47. package/eventcatalog/src/utils/collections/icons.ts +3 -1
  48. package/eventcatalog/src/utils/collections/services.ts +15 -1
  49. package/eventcatalog/src/utils/collections/util.ts +4 -2
  50. package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +155 -0
  51. package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +188 -82
  52. package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +2 -0
  53. package/package.json +3 -2
@@ -77,6 +77,11 @@ const ServiceItem = React.memo(
77
77
  isVisualizer: boolean;
78
78
  searchTerm: string;
79
79
  }) => {
80
+ const readsAndWritesTo = item.writesTo.filter((writeTo) => item.readsFrom.some((readFrom) => readFrom.id === writeTo.id));
81
+ const resourceReads = item.readsFrom.filter((readFrom) => !readsAndWritesTo.some((writeTo) => writeTo.id === readFrom.id));
82
+ const resourceWrites = item.writesTo.filter((writeTo) => !readsAndWritesTo.some((readFrom) => readFrom.id === writeTo.id));
83
+ const hasData = item.writesTo.length > 0 || item.readsFrom.length > 0;
84
+
80
85
  return (
81
86
  <CollapsibleGroup
82
87
  isCollapsed={collapsedGroups[item.href]}
@@ -115,6 +120,17 @@ const ServiceItem = React.memo(
115
120
  >
116
121
  <span className="truncate">Overview</span>
117
122
  </a>
123
+ {isVisualizer && hasData && (
124
+ <a
125
+ href={buildUrl(`/${item.href}/data`)}
126
+ data-active={decodedCurrentPath === `${item.href}/data`}
127
+ className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
128
+ decodedCurrentPath === `${item.href}/data` ? 'bg-purple-100 ' : 'hover:bg-purple-100'
129
+ }`}
130
+ >
131
+ <span className="truncate">Data Diagram</span>
132
+ </a>
133
+ )}
118
134
  {!isVisualizer && (
119
135
  <a
120
136
  href={buildUrlWithParams('/architecture/docs/messages', {
@@ -185,6 +201,84 @@ const ServiceItem = React.memo(
185
201
  >
186
202
  <MessageList messages={item.sends} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
187
203
  </CollapsibleGroup>
204
+ {!isVisualizer && hasData && (
205
+ <CollapsibleGroup
206
+ isCollapsed={collapsedGroups[`${item.href}-data`]}
207
+ onToggle={() => toggleGroupCollapse(`${item.href}-data`)}
208
+ title={
209
+ <button
210
+ onClick={(e) => {
211
+ e.stopPropagation();
212
+ toggleGroupCollapse(`${item.href}-data`);
213
+ }}
214
+ className="truncate underline ml-2 text-xs mb-1 py-1"
215
+ >
216
+ Data Stores ({readsAndWritesTo.length + resourceWrites.length + resourceReads.length})
217
+ </button>
218
+ }
219
+ >
220
+ {readsAndWritesTo.length > 0 && (
221
+ <CollapsibleGroup
222
+ className="ml-4"
223
+ isCollapsed={collapsedGroups[`${item.href}-writesTo-data`]}
224
+ onToggle={() => toggleGroupCollapse(`${item.href}-writesTo-data`)}
225
+ title={
226
+ <button
227
+ onClick={(e) => {
228
+ e.stopPropagation();
229
+ toggleGroupCollapse(`${item.href}-writesTo-data`);
230
+ }}
231
+ className="truncate underline ml-2 text-xs mb-1 py-1"
232
+ >
233
+ Reads and writes to
234
+ </button>
235
+ }
236
+ >
237
+ <MessageList messages={readsAndWritesTo} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
238
+ </CollapsibleGroup>
239
+ )}
240
+ {resourceWrites.length > 0 && (
241
+ <CollapsibleGroup
242
+ className="ml-4"
243
+ isCollapsed={collapsedGroups[`${item.href}-writesTo-data`]}
244
+ onToggle={() => toggleGroupCollapse(`${item.href}-writesTo-data`)}
245
+ title={
246
+ <button
247
+ onClick={(e) => {
248
+ e.stopPropagation();
249
+ toggleGroupCollapse(`${item.href}-writesTo-data`);
250
+ }}
251
+ className="truncate underline ml-2 text-xs mb-1 py-1"
252
+ >
253
+ Writes to
254
+ </button>
255
+ }
256
+ >
257
+ <MessageList messages={resourceWrites} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
258
+ </CollapsibleGroup>
259
+ )}
260
+ {resourceReads.length > 0 && (
261
+ <CollapsibleGroup
262
+ className="ml-4"
263
+ isCollapsed={collapsedGroups[`${item.href}-readsFrom-data`]}
264
+ onToggle={() => toggleGroupCollapse(`${item.href}-readsFrom-data`)}
265
+ title={
266
+ <button
267
+ onClick={(e) => {
268
+ e.stopPropagation();
269
+ toggleGroupCollapse(`${item.href}-readsFrom-data`);
270
+ }}
271
+ className="truncate underline ml-2 text-xs mb-1 py-1"
272
+ >
273
+ Reads From
274
+ </button>
275
+ }
276
+ >
277
+ <MessageList messages={resourceReads} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
278
+ </CollapsibleGroup>
279
+ )}
280
+ </CollapsibleGroup>
281
+ )}
188
282
  {!isVisualizer && item.entities.length > 0 && (
189
283
  <CollapsibleGroup
190
284
  isCollapsed={collapsedGroups[`${item.href}-entities`]}
@@ -513,6 +607,7 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
513
607
  >
514
608
  <span className="truncate">Overview</span>
515
609
  </a>
610
+
516
611
  {isVisualizer && hasEntities && (
517
612
  <a
518
613
  href={buildUrl(`/${item.href}/entity-map`)}
@@ -720,73 +815,169 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
720
815
 
721
816
  {filteredData['messagesNotInService'] && filteredData['messagesNotInService'].length > 0 && (
722
817
  <div className="pt-4 pb-2">
723
- <ul className="space-y-4">
724
- {filteredData['messagesNotInService'].map((item: any) => (
725
- <li key={item.href} className="space-y-0" data-active={decodedCurrentPath === item.href}>
726
- <a
727
- href={item.href}
728
- data-active={decodedCurrentPath === item.href}
729
- className={`flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md ${
730
- decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
731
- }`}
732
- >
733
- <span className="truncate">
734
- <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
735
- </span>
736
- <span
737
- className={`ml-2 text-[10px] font-medium px-2 uppercase py-0.5 rounded ${getMessageColorByCollection(item.collection)}`}
818
+ <CollapsibleGroup
819
+ isCollapsed={collapsedGroups['messagesNotInService-group']}
820
+ onToggle={() => toggleGroupCollapse('messagesNotInService-group')}
821
+ title={
822
+ <button
823
+ onClick={(e) => {
824
+ e.stopPropagation();
825
+ toggleGroupCollapse('messagesNotInService-group');
826
+ }}
827
+ className="flex justify-between items-center pl-2 w-full text-xs"
828
+ >
829
+ <span className="truncate text-xs font-bold">Orphaned Messages</span>
830
+ </button>
831
+ }
832
+ >
833
+ <div className="space-y-2 border-gray-200/80 border-l pl-3 ml-[9px] mt-3">
834
+ {filteredData['messagesNotInService'].map((item: any) => (
835
+ <div key={item.href} data-active={decodedCurrentPath === item.href}>
836
+ <a
837
+ href={item.href}
838
+ data-active={decodedCurrentPath === item.href}
839
+ className={`flex items-center justify-between px-2 py-0.5 text-xs font-thin rounded-md ${
840
+ decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
841
+ }`}
738
842
  >
739
- {getMessageCollectionName(item.collection, item)}
740
- </span>
741
- </a>
742
- </li>
743
- ))}
744
- </ul>
843
+ <span className="truncate">
844
+ <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
845
+ </span>
846
+ <span
847
+ className={`ml-2 text-[10px] font-medium px-2 uppercase py-0.5 rounded ${getMessageColorByCollection(item.collection)}`}
848
+ >
849
+ {getMessageCollectionName(item.collection, item)}
850
+ </span>
851
+ </a>
852
+ </div>
853
+ ))}
854
+ </div>
855
+ </CollapsibleGroup>
745
856
  </div>
746
857
  )}
747
858
 
748
- {filteredData['flows'] && (
859
+ {/* Flows Group */}
860
+ {filteredData['flows'] && filteredData['flows'].length > 0 && (
749
861
  <div className="pt-4 pb-2">
750
- <ul className="space-y-4">
751
- {filteredData['flows'].map((item: any) => (
752
- <li key={item.href} className="space-y-0" data-active={decodedCurrentPath === item.href}>
753
- <a
754
- href={item.href}
755
- data-active={decodedCurrentPath === item.href}
756
- className={`flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md ${
757
- decodedCurrentPath === item.href ? 'bg-cyan-100 text-cyan-900' : 'hover:bg-purple-100'
758
- }`}
759
- >
760
- <span className="truncate">
761
- <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
762
- </span>
763
- <span className="text-cyan-600 ml-2 text-[10px] font-medium bg-cyan-50 px-2 py-0.5 rounded">FLOW</span>
764
- </a>
765
- </li>
766
- ))}
767
- </ul>
862
+ <CollapsibleGroup
863
+ isCollapsed={collapsedGroups['flows-group']}
864
+ onToggle={() => toggleGroupCollapse('flows-group')}
865
+ title={
866
+ <button
867
+ onClick={(e) => {
868
+ e.stopPropagation();
869
+ toggleGroupCollapse('flows-group');
870
+ }}
871
+ className="flex justify-between items-center pl-2 w-full text-xs"
872
+ >
873
+ <span className="truncate text-xs font-bold">Flows</span>
874
+ </button>
875
+ }
876
+ >
877
+ <div className="space-y-2 border-gray-200/80 border-l pl-3 ml-[9px] mt-3">
878
+ {filteredData['flows'].map((item: any) => (
879
+ <div key={item.href} data-active={decodedCurrentPath === item.href}>
880
+ <a
881
+ href={item.href}
882
+ data-active={decodedCurrentPath === item.href}
883
+ className={`flex items-center justify-between px-2 py-0.5 text-xs font-thin rounded-md ${
884
+ decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
885
+ }`}
886
+ >
887
+ <span className="truncate">
888
+ <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
889
+ </span>
890
+ <span className={`ml-2 text-[10px] font-medium px-2 uppercase py-0.5 rounded bg-teal-50 text-teal-600`}>
891
+ FLOW
892
+ </span>
893
+ </a>
894
+ </div>
895
+ ))}
896
+ </div>
897
+ </CollapsibleGroup>
768
898
  </div>
769
899
  )}
770
- {filteredData['designs'] && isVisualizer && (
900
+
901
+ {/* Data Group */}
902
+ {filteredData['containers'] && filteredData['containers'].length > 0 && (
771
903
  <div className="pt-4 pb-2">
772
- <ul className="space-y-4">
773
- {filteredData['designs'].map((item: any) => (
774
- <li key={item.href} className="space-y-0" data-active={decodedCurrentPath === item.href}>
775
- <a
776
- href={item.href}
777
- data-active={decodedCurrentPath === item.href}
778
- className={`flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md ${
779
- decodedCurrentPath === item.href ? 'bg-cyan-100 text-cyan-900' : 'hover:bg-purple-100'
780
- }`}
781
- >
782
- <span className="truncate">
783
- <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
784
- </span>
785
- <span className="text-cyan-600 ml-2 text-[10px] font-medium bg-cyan-50 px-2 py-0.5 rounded">DESIGN</span>
786
- </a>
787
- </li>
788
- ))}
789
- </ul>
904
+ <CollapsibleGroup
905
+ isCollapsed={collapsedGroups['data-group']}
906
+ onToggle={() => toggleGroupCollapse('data-group')}
907
+ title={
908
+ <button
909
+ onClick={(e) => {
910
+ e.stopPropagation();
911
+ toggleGroupCollapse('data-group');
912
+ }}
913
+ className="flex justify-between items-center pl-2 w-full text-xs"
914
+ >
915
+ <span className="truncate text-xs font-bold">Data</span>
916
+ </button>
917
+ }
918
+ >
919
+ <div className="space-y-2 border-gray-200/80 border-l pl-3 ml-[9px] mt-3">
920
+ {filteredData['containers'].map((item: any) => (
921
+ <div key={item.href} data-active={decodedCurrentPath === item.href}>
922
+ <a
923
+ href={item.href}
924
+ data-active={decodedCurrentPath === item.href}
925
+ className={`flex items-center justify-between px-2 py-0.5 text-xs font-thin rounded-md ${
926
+ decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
927
+ }`}
928
+ >
929
+ <span className="truncate">
930
+ <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
931
+ </span>
932
+ <span className={`ml-2 text-[10px] font-medium px-2 uppercase py-0.5 rounded bg-blue-50 text-blue-600`}>
933
+ DATA
934
+ </span>
935
+ </a>
936
+ </div>
937
+ ))}
938
+ </div>
939
+ </CollapsibleGroup>
940
+ </div>
941
+ )}
942
+
943
+ {filteredData['designs'] && filteredData['designs'].length > 0 && (
944
+ <div className="pt-4 pb-2">
945
+ <CollapsibleGroup
946
+ isCollapsed={collapsedGroups['designs-group']}
947
+ onToggle={() => toggleGroupCollapse('designs-group')}
948
+ title={
949
+ <button
950
+ onClick={(e) => {
951
+ e.stopPropagation();
952
+ toggleGroupCollapse('designs-group');
953
+ }}
954
+ className="flex justify-between items-center pl-2 w-full text-xs"
955
+ >
956
+ <span className="truncate text-xs font-bold">Designs</span>
957
+ </button>
958
+ }
959
+ >
960
+ <div className="space-y-2 border-gray-200/80 border-l pl-3 ml-[9px] mt-3">
961
+ {filteredData['designs'].map((item: any) => (
962
+ <div key={item.href} data-active={decodedCurrentPath === item.href}>
963
+ <a
964
+ href={item.href}
965
+ data-active={decodedCurrentPath === item.href}
966
+ className={`flex items-center justify-between px-2 py-0.5 text-xs font-thin rounded-md ${
967
+ decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
968
+ }`}
969
+ >
970
+ <span className="truncate">
971
+ <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
972
+ </span>
973
+ <span className={`ml-2 text-[10px] font-medium px-2 uppercase py-0.5 rounded bg-teal-50 text-teal-600`}>
974
+ DESIGN
975
+ </span>
976
+ </a>
977
+ </div>
978
+ ))}
979
+ </div>
980
+ </CollapsibleGroup>
790
981
  </div>
791
982
  )}
792
983
  </>
@@ -33,6 +33,8 @@ export interface ServiceItem {
33
33
  sends: MessageItem[];
34
34
  receives: MessageItem[];
35
35
  entities: EntityItem[];
36
+ writesTo: MessageItem[];
37
+ readsFrom: MessageItem[];
36
38
  specifications?: {
37
39
  type: string;
38
40
  path: string;
@@ -79,6 +81,7 @@ export interface Resources {
79
81
  commands?: MessageItem[];
80
82
  queries?: MessageItem[];
81
83
  events?: MessageItem[];
84
+ containers?: MessageItem[];
82
85
  }
83
86
 
84
87
  export interface ListViewSideBarProps {
@@ -8,6 +8,7 @@ import { getCommands } from '@utils/commands';
8
8
  import { getEvents } from '@utils/events';
9
9
  import { getQueries } from '@utils/queries';
10
10
  import { getDesigns } from '@utils/collections/designs';
11
+ import { getContainers } from '@utils/collections/containers';
11
12
 
12
13
  const stripCollection = (collection: any) => {
13
14
  return collection.map((item: any) => ({
@@ -26,6 +27,7 @@ export async function getResourcesForNavigation({ currentPath }: { currentPath:
26
27
  const domains = await getDomains({ getAllVersions: false });
27
28
  const channels = await getChannels({ getAllVersions: false });
28
29
  const flows = await getFlows({ getAllVersions: false });
30
+ const containers = await getContainers({ getAllVersions: false });
29
31
  const designs = await getDesigns({ getAllVersions: false });
30
32
 
31
33
  const messages = [...events, ...commands, ...queries];
@@ -43,7 +45,7 @@ export async function getResourcesForNavigation({ currentPath }: { currentPath:
43
45
  const route = currentPath.includes('visualiser') ? 'visualiser' : 'docs';
44
46
 
45
47
  // Just the domains for now.
46
- const allDataAsSideNav = [...domains, ...services, ...flows, ...channels].reduce((acc, item) => {
48
+ const allDataAsSideNav = [...domains, ...services, ...flows, ...channels, ...containers].reduce((acc, item) => {
47
49
  const title = item.collection;
48
50
  const group = acc[title] || [];
49
51
 
@@ -54,6 +56,10 @@ export async function getResourcesForNavigation({ currentPath }: { currentPath:
54
56
  const sends = isCollectionService ? item.data.sends || null : null;
55
57
  const receives = isCollectionService ? item.data.receives || null : null;
56
58
  const entities = isCollectionDomain || isCollectionService ? item.data.entities || null : null;
59
+
60
+ const writesTo = isCollectionService ? item.data.writesTo || null : null;
61
+ const readsFrom = isCollectionService ? item.data.readsFrom || null : null;
62
+
57
63
  // Add href to the sends and receives
58
64
  const sendsWithHref = sends?.map((send: any) => ({
59
65
  id: send.data.id,
@@ -89,6 +95,32 @@ export async function getResourcesForNavigation({ currentPath }: { currentPath:
89
95
  href: buildUrl(`/${route}/${entity.collection}/${entity.data.id}/${entity.data.version}`),
90
96
  }));
91
97
 
98
+ const writesToWithHref = writesTo?.map((writeTo: any) => ({
99
+ id: writeTo.data.id,
100
+ data: {
101
+ name: writeTo.data.name,
102
+ sidebar: {
103
+ badge: writeTo.data.container_type || writeTo.collection,
104
+ backgroundColor: 'bg-blue-100 text-blue-600',
105
+ },
106
+ },
107
+ collection: writeTo.collection,
108
+ href: buildUrl(`/${route}/${writeTo.collection}/${writeTo.data.id}/${writeTo.data.version}`),
109
+ }));
110
+
111
+ const readsFromWithHref = readsFrom?.map((readFrom: any) => ({
112
+ id: readFrom.data.id,
113
+ data: {
114
+ name: readFrom.data.name,
115
+ sidebar: {
116
+ badge: readFrom.data.container_type || readFrom.collection,
117
+ backgroundColor: 'bg-blue-100 text-indigo-600',
118
+ },
119
+ },
120
+ collection: readFrom.collection,
121
+ href: buildUrl(`/${route}/${readFrom.collection}/${readFrom.data.id}/${readFrom.data.version}`),
122
+ }));
123
+
92
124
  // don't render items if we are in the visualiser and the item has visualiser set to false
93
125
  if (currentPath.includes('visualiser') && item.data.visualiser === false) {
94
126
  return acc;
@@ -115,6 +147,8 @@ export async function getResourcesForNavigation({ currentPath }: { currentPath:
115
147
  receives: receivesWithHref,
116
148
  entities: entitiesWithHref,
117
149
  specifications: isCollectionService ? getSpecificationsForService(item) : null,
150
+ writesTo: writesToWithHref,
151
+ readsFrom: readsFromWithHref,
118
152
  sidebar: item.data?.sidebar,
119
153
  renderInVisualiser: item.data?.visualiser ?? true,
120
154
  };
@@ -18,7 +18,7 @@ export type TreeNode = {
18
18
  /**
19
19
  * Resource types that should be in the sidenav
20
20
  */
21
- const RESOURCE_TYPES = ['domains', 'entities', 'services', 'events', 'commands', 'queries', 'flows', 'channels'];
21
+ const RESOURCE_TYPES = ['domains', 'entities', 'services', 'events', 'commands', 'queries', 'flows', 'channels', 'containers'];
22
22
  // const RESOURCE_TYPES = ['domains', 'services', 'events', 'commands', 'queries', 'flows', 'channels'];
23
23
 
24
24
  /**
@@ -125,7 +125,7 @@ function groupChildrenByType(parentNode: TreeNode) {
125
125
  .map(([type, nodes]) => {
126
126
  return {
127
127
  id: `${parentNode.id}/${type}`,
128
- name: type,
128
+ name: type === 'containers' ? 'Data' : type,
129
129
  type: type as CollectionKey,
130
130
  version: '0',
131
131
  children: nodes,
@@ -31,14 +31,16 @@ declare module '@tanstack/react-table' {
31
31
  | 'ownedQueries'
32
32
  | 'ownedEvents'
33
33
  | 'ownedServices'
34
- | 'associatedTeams';
34
+ | 'associatedTeams'
35
+ | 'servicesThatWriteToContainer'
36
+ | 'servicesThatReadFromContainer';
35
37
  filteredItemHasVersion?: boolean;
36
38
  showFilter?: boolean;
37
39
  className?: string;
38
40
  }
39
41
  }
40
42
 
41
- export type TCollectionTypes = 'domains' | 'services' | CollectionMessageTypes | 'flows' | 'users' | 'teams';
43
+ export type TCollectionTypes = 'domains' | 'services' | CollectionMessageTypes | 'flows' | 'users' | 'teams' | 'containers';
42
44
 
43
45
  export type TData<T extends TCollectionTypes> = {
44
46
  collection: T;
@@ -103,6 +105,24 @@ export type TData<T extends TCollectionTypes> = {
103
105
  version: string;
104
106
  };
105
107
  }>;
108
+ // Only for containers
109
+ servicesThatWriteToContainer?: Array<{
110
+ collection: string; // Specify only 'services'?
111
+ data: {
112
+ id: string;
113
+ name: string;
114
+ version: string;
115
+ };
116
+ }>;
117
+ // Only for containers
118
+ servicesThatReadFromContainer?: Array<{
119
+ collection: string; // Specify only 'services'?
120
+ data: {
121
+ id: string;
122
+ name: string;
123
+ version: string;
124
+ };
125
+ }>;
106
126
  // ---------------------------------------------------------------------------
107
127
  // Users
108
128
  avatarUrl?: string;
@@ -0,0 +1,152 @@
1
+ import { createColumnHelper } from '@tanstack/react-table';
2
+ import { filterByName } from '../filters/custom-filters';
3
+ import { buildUrl } from '@utils/url-builder';
4
+ import { ServerIcon } from '@heroicons/react/24/solid';
5
+ import { DatabaseIcon } from 'lucide-react';
6
+ import { createBadgesColumn } from './SharedColumns';
7
+ import type { TData } from '../Table';
8
+ import { filterCollectionByName } from '../filters/custom-filters';
9
+
10
+ const columnHelper = createColumnHelper<TData<'containers'>>();
11
+
12
+ export const columns = () => [
13
+ columnHelper.accessor('data.name', {
14
+ id: 'name',
15
+ header: () => <span>Storage</span>,
16
+ cell: (info) => {
17
+ const containerRaw = info.row.original;
18
+ const color = 'blue';
19
+ return (
20
+ <div className=" group ">
21
+ <a
22
+ href={buildUrl(`/docs/${containerRaw.collection}/${containerRaw.data.id}/${containerRaw.data.version}`)}
23
+ className={`group-hover:text-${color}-500 flex space-x-1 items-center`}
24
+ >
25
+ <div className={`flex items-center border border-gray-300 shadow-sm rounded-md group-hover:border-${color}-400`}>
26
+ <span className="flex items-center">
27
+ <span className={`bg-${color}-500 group-hover:bg-${color}-600 h-full rounded-tl rounded-bl p-1`}>
28
+ <DatabaseIcon className="h-4 w-4 text-white" />
29
+ </span>
30
+ <span className="leading-none px-2 group-hover:underline group-hover:text-primary font-light">
31
+ {containerRaw.data.name} (v{containerRaw.data.version})
32
+ </span>
33
+ </span>
34
+ </div>
35
+ </a>
36
+ </div>
37
+ );
38
+ },
39
+ footer: (info) => info.column.id,
40
+ meta: {
41
+ filterVariant: 'name',
42
+ },
43
+ filterFn: filterByName,
44
+ }),
45
+ columnHelper.accessor('data.summary', {
46
+ id: 'summary',
47
+ header: () => 'Summary',
48
+ cell: (info) => <span className="font-light ">{info.renderValue()}</span>,
49
+ footer: (info) => info.column.id,
50
+ meta: {
51
+ showFilter: false,
52
+ className: 'max-w-md',
53
+ },
54
+ }),
55
+ columnHelper.accessor('data.servicesThatWriteToContainer', {
56
+ header: () => <span>Writes</span>,
57
+ meta: {
58
+ filterVariant: 'collection',
59
+ collectionFilterKey: 'servicesThatWriteToContainer',
60
+ },
61
+ cell: (info) => {
62
+ const services = info.getValue();
63
+ if (services?.length === 0 || !services)
64
+ return <div className="font-light text-sm text-gray-400/60 text-left italic">No services documented</div>;
65
+ return (
66
+ <ul className="">
67
+ {services.map((service, index) => {
68
+ return (
69
+ <li className="py-2 group flex items-center space-x-2" key={`${service.data.id}-${index}`}>
70
+ <a
71
+ href={buildUrl(`/docs/${service.collection}/${service.data.id}/${service.data.version}`)}
72
+ className="group-hover:text-primary flex space-x-1 items-center "
73
+ >
74
+ <div className="flex items-center border border-gray-300 shadow-sm rounded-md">
75
+ <span className="flex items-center">
76
+ <span className="bg-pink-500 h-full rounded-tl rounded-bl p-1">
77
+ <ServerIcon className="h-4 w-4 text-white" />
78
+ </span>
79
+ <span className="font-light leading-none px-2 group-hover:underline">
80
+ {service.data.name} (v{service.data.version})
81
+ </span>
82
+ </span>{' '}
83
+ </div>
84
+ </a>
85
+ </li>
86
+ );
87
+ })}
88
+ </ul>
89
+ );
90
+ },
91
+ footer: (info) => info.column.id,
92
+ filterFn: filterCollectionByName('servicesThatWriteToContainer'),
93
+ }),
94
+ columnHelper.accessor('data.servicesThatReadFromContainer', {
95
+ header: () => <span>Reads</span>,
96
+ meta: {
97
+ filterVariant: 'collection',
98
+ collectionFilterKey: 'servicesThatReadFromContainer',
99
+ },
100
+ cell: (info) => {
101
+ const services = info.getValue();
102
+ if (services?.length === 0 || !services)
103
+ return <div className="font-light text-sm text-gray-400/60 text-left italic">No services documented</div>;
104
+ return (
105
+ <ul className="">
106
+ {services.map((service, index) => {
107
+ return (
108
+ <li className="py-2 group flex items-center space-x-2" key={`${service.data.id}-${index}`}>
109
+ <a
110
+ href={buildUrl(`/docs/${service.collection}/${service.data.id}/${service.data.version}`)}
111
+ className="group-hover:text-primary flex space-x-1 items-center "
112
+ >
113
+ <div className="flex items-center border border-gray-300 shadow-sm rounded-md">
114
+ <span className="flex items-center">
115
+ <span className="bg-pink-500 h-full rounded-tl rounded-bl p-1">
116
+ <ServerIcon className="h-4 w-4 text-white" />
117
+ </span>
118
+ <span className="font-light leading-none px-2 group-hover:underline">
119
+ {service.data.name} (v{service.data.version})
120
+ </span>
121
+ </span>{' '}
122
+ </div>
123
+ </a>
124
+ </li>
125
+ );
126
+ })}
127
+ </ul>
128
+ );
129
+ },
130
+ footer: (info) => info.column.id,
131
+ filterFn: filterCollectionByName('servicesThatReadFromContainer'),
132
+ }),
133
+ createBadgesColumn(columnHelper),
134
+ columnHelper.accessor('data.name', {
135
+ header: () => <span />,
136
+ cell: (info) => {
137
+ const container = info.row.original;
138
+ return (
139
+ <a
140
+ className="hover:text-primary hover:underline px-4 font-light"
141
+ href={buildUrl(`/visualiser/${container.collection}/${container.data.id}/${container.data.version}`)}
142
+ >
143
+ Visualiser &rarr;
144
+ </a>
145
+ );
146
+ },
147
+ id: 'actions',
148
+ meta: {
149
+ showFilter: false,
150
+ },
151
+ }),
152
+ ];