datajunction-ui 0.0.1-rc.9 → 0.0.2-0.dev1

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 (244) hide show
  1. package/.env +2 -0
  2. package/.prettierignore +3 -1
  3. package/Makefile +9 -0
  4. package/cleanup-deps.sh +70 -0
  5. package/dj-logo.svg +10 -0
  6. package/package.json +53 -14
  7. package/public/favicon.ico +0 -0
  8. package/public/index.html +1 -1
  9. package/runit.sh +30 -0
  10. package/runit2.sh +30 -0
  11. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  12. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +5 -109
  13. package/src/app/components/AddNodeDropdown.jsx +44 -0
  14. package/src/app/components/ListGroupItem.jsx +9 -1
  15. package/src/app/components/NamespaceHeader.jsx +4 -13
  16. package/src/app/components/NodeListActions.jsx +69 -0
  17. package/src/app/components/NodeMaterializationDelete.jsx +90 -0
  18. package/src/app/components/NotificationBell.tsx +223 -0
  19. package/src/app/components/QueryInfo.jsx +172 -0
  20. package/src/app/components/Search.jsx +94 -0
  21. package/src/app/components/Tab.jsx +8 -1
  22. package/src/app/components/ToggleSwitch.jsx +20 -0
  23. package/src/app/components/UserMenu.tsx +100 -0
  24. package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
  25. package/src/app/components/__tests__/NodeMaterializationDelete.test.jsx +263 -0
  26. package/src/app/components/__tests__/NotificationBell.test.tsx +302 -0
  27. package/src/app/components/__tests__/QueryInfo.test.jsx +183 -0
  28. package/src/app/components/__tests__/Search.test.jsx +307 -0
  29. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  30. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  31. package/src/app/components/__tests__/UserMenu.test.tsx +241 -0
  32. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +8 -3
  33. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
  34. package/src/app/components/djgraph/Collapse.jsx +47 -0
  35. package/src/app/components/djgraph/DJNode.jsx +61 -83
  36. package/src/app/components/djgraph/DJNodeColumns.jsx +75 -0
  37. package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
  38. package/src/app/components/djgraph/LayoutFlow.jsx +106 -0
  39. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  40. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  41. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  42. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +84 -40
  43. package/src/app/components/forms/Action.jsx +8 -0
  44. package/src/app/components/forms/NodeNameField.jsx +64 -0
  45. package/src/app/components/search.css +17 -0
  46. package/src/app/constants.js +2 -0
  47. package/src/app/icons/AddItemIcon.jsx +16 -0
  48. package/src/app/icons/AlertIcon.jsx +33 -0
  49. package/src/app/icons/CollapsedIcon.jsx +15 -0
  50. package/src/app/icons/CommitIcon.jsx +45 -0
  51. package/src/app/icons/DJLogo.jsx +36 -0
  52. package/src/app/icons/DeleteIcon.jsx +21 -0
  53. package/src/app/icons/DiffIcon.jsx +63 -0
  54. package/src/app/icons/EditIcon.jsx +18 -0
  55. package/src/app/icons/ExpandedIcon.jsx +15 -0
  56. package/src/app/icons/EyeIcon.jsx +20 -0
  57. package/src/app/icons/FilterIcon.jsx +7 -0
  58. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  59. package/src/app/icons/InvalidIcon.jsx +16 -0
  60. package/src/app/icons/JupyterExportIcon.jsx +25 -0
  61. package/src/app/icons/LoadingIcon.jsx +14 -0
  62. package/src/app/icons/NodeIcon.jsx +49 -0
  63. package/src/app/icons/NotificationIcon.jsx +27 -0
  64. package/src/app/icons/PythonIcon.jsx +14 -0
  65. package/src/app/icons/SettingsIcon.jsx +28 -0
  66. package/src/app/icons/TableIcon.jsx +14 -0
  67. package/src/app/icons/ValidIcon.jsx +16 -0
  68. package/src/app/icons/WrenchIcon.jsx +36 -0
  69. package/src/app/index.tsx +130 -37
  70. package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
  71. package/src/app/pages/AddEditNodePage/ColumnMetadata.jsx +61 -0
  72. package/src/app/pages/AddEditNodePage/ColumnsMetadataInput.jsx +72 -0
  73. package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
  74. package/src/app/pages/AddEditNodePage/CustomMetadataField.jsx +144 -0
  75. package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
  76. package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
  77. package/src/app/pages/AddEditNodePage/ExperimentationExtension.jsx +338 -0
  78. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +64 -0
  79. package/src/app/pages/AddEditNodePage/FullNameField.jsx +38 -0
  80. package/src/app/pages/AddEditNodePage/Loadable.jsx +20 -0
  81. package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
  82. package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
  83. package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
  84. package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
  85. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +94 -0
  86. package/src/app/pages/AddEditNodePage/OwnersField.jsx +54 -0
  87. package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
  88. package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
  89. package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
  90. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +110 -0
  91. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +291 -0
  92. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
  93. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
  94. package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
  95. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
  96. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
  97. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
  98. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +224 -0
  99. package/src/app/pages/AddEditNodePage/index.jsx +545 -0
  100. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  101. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  102. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  103. package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +152 -0
  104. package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
  105. package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +75 -0
  106. package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +373 -0
  107. package/src/app/pages/CubeBuilderPage/index.jsx +291 -0
  108. package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
  109. package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
  110. package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
  111. package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
  112. package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
  113. package/src/app/pages/LoginPage/index.jsx +17 -0
  114. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
  115. package/src/app/pages/NamespacePage/Explorer.jsx +232 -0
  116. package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
  117. package/src/app/pages/NamespacePage/NodeModeSelect.jsx +27 -0
  118. package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
  119. package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
  120. package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
  121. package/src/app/pages/NamespacePage/__tests__/AddNamespacePopover.test.jsx +283 -0
  122. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +331 -0
  123. package/src/app/pages/NamespacePage/index.jsx +356 -42
  124. package/src/app/pages/NodePage/AddBackfillPopover.jsx +165 -0
  125. package/src/app/pages/NodePage/AddComplexDimensionLinkPopover.jsx +367 -0
  126. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +222 -0
  127. package/src/app/pages/NodePage/AvailabilityStateBlock.jsx +67 -0
  128. package/src/app/pages/NodePage/ClientCodePopover.jsx +94 -0
  129. package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
  130. package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
  131. package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
  132. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +164 -0
  133. package/src/app/pages/NodePage/ManageDimensionLinksDialog.jsx +526 -0
  134. package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
  135. package/src/app/pages/NodePage/NodeColumnTab.jsx +421 -30
  136. package/src/app/pages/NodePage/NodeDependenciesTab.jsx +153 -0
  137. package/src/app/pages/NodePage/NodeGraphTab.jsx +119 -148
  138. package/src/app/pages/NodePage/NodeHistory.jsx +236 -0
  139. package/src/app/pages/NodePage/NodeInfoTab.jsx +346 -49
  140. package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
  141. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +585 -0
  142. package/src/app/pages/NodePage/NodeRevisionMaterializationTab.jsx +58 -0
  143. package/src/app/pages/NodePage/NodeStatus.jsx +100 -31
  144. package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
  145. package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
  146. package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
  147. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +151 -0
  148. package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
  149. package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
  150. package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
  151. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +56 -0
  152. package/src/app/pages/NodePage/__tests__/AddComplexDimensionLinkPopover.test.jsx +459 -0
  153. package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
  154. package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
  155. package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
  156. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +144 -0
  157. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +132 -0
  158. package/src/app/pages/NodePage/__tests__/ManageDimensionLinksDialog.test.jsx +390 -0
  159. package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
  160. package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +151 -0
  161. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +595 -0
  162. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +58 -0
  163. package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +190 -0
  164. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +882 -0
  165. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  166. package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
  167. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +19 -0
  168. package/src/app/pages/NodePage/index.jsx +190 -44
  169. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  170. package/src/app/pages/NotificationsPage/Loadable.jsx +6 -0
  171. package/src/app/pages/NotificationsPage/__tests__/index.test.jsx +287 -0
  172. package/src/app/pages/NotificationsPage/index.jsx +136 -0
  173. package/src/app/pages/OverviewPage/ByStatusPanel.jsx +69 -0
  174. package/src/app/pages/OverviewPage/DimensionNodeUsagePanel.jsx +48 -0
  175. package/src/app/pages/OverviewPage/GovernanceWarningsPanel.jsx +107 -0
  176. package/src/app/pages/OverviewPage/Loadable.jsx +16 -0
  177. package/src/app/pages/OverviewPage/NodesByTypePanel.jsx +63 -0
  178. package/src/app/pages/OverviewPage/OverviewPanel.jsx +94 -0
  179. package/src/app/pages/OverviewPage/TrendsPanel.jsx +66 -0
  180. package/src/app/pages/OverviewPage/__tests__/ByStatusPanel.test.jsx +36 -0
  181. package/src/app/pages/OverviewPage/__tests__/DimensionNodeUsagePanel.test.jsx +76 -0
  182. package/src/app/pages/OverviewPage/__tests__/GovernanceWarningsPanel.test.jsx +77 -0
  183. package/src/app/pages/OverviewPage/__tests__/NodesByTypePanel.test.jsx +86 -0
  184. package/src/app/pages/OverviewPage/__tests__/OverviewPanel.test.jsx +78 -0
  185. package/src/app/pages/OverviewPage/__tests__/TrendsPanel.test.jsx +120 -0
  186. package/src/app/pages/OverviewPage/__tests__/index.test.jsx +54 -0
  187. package/src/app/pages/OverviewPage/index.jsx +22 -0
  188. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  189. package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +112 -0
  190. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +38 -0
  191. package/src/app/pages/RegisterTablePage/index.jsx +142 -0
  192. package/src/app/pages/Root/__tests__/index.test.jsx +44 -0
  193. package/src/app/pages/Root/index.tsx +92 -10
  194. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  195. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  196. package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
  197. package/src/app/pages/SettingsPage/CreateServiceAccountModal.jsx +152 -0
  198. package/src/app/pages/SettingsPage/Loadable.jsx +16 -0
  199. package/src/app/pages/SettingsPage/NotificationSubscriptionsSection.jsx +189 -0
  200. package/src/app/pages/SettingsPage/ProfileSection.jsx +41 -0
  201. package/src/app/pages/SettingsPage/ServiceAccountsSection.jsx +95 -0
  202. package/src/app/pages/SettingsPage/__tests__/CreateServiceAccountModal.test.jsx +318 -0
  203. package/src/app/pages/SettingsPage/__tests__/NotificationSubscriptionsSection.test.jsx +233 -0
  204. package/src/app/pages/SettingsPage/__tests__/ProfileSection.test.jsx +65 -0
  205. package/src/app/pages/SettingsPage/__tests__/ServiceAccountsSection.test.jsx +150 -0
  206. package/src/app/pages/SettingsPage/__tests__/index.test.jsx +184 -0
  207. package/src/app/pages/SettingsPage/index.jsx +148 -0
  208. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  209. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  210. package/src/app/pages/TagPage/index.jsx +79 -0
  211. package/src/app/services/DJService.js +1444 -21
  212. package/src/app/services/__tests__/DJService.test.jsx +2118 -0
  213. package/src/app/utils/__tests__/date.test.js +198 -0
  214. package/src/app/utils/date.js +65 -0
  215. package/src/index.tsx +1 -0
  216. package/src/mocks/mockNodes.jsx +1477 -0
  217. package/src/setupTests.ts +31 -1
  218. package/src/styles/dag.css +117 -5
  219. package/src/styles/index.css +1028 -31
  220. package/src/styles/loading.css +34 -0
  221. package/src/styles/login.css +81 -0
  222. package/src/styles/nav-bar.css +274 -0
  223. package/src/styles/node-creation.scss +276 -0
  224. package/src/styles/node-list.css +4 -0
  225. package/src/styles/overview.css +72 -0
  226. package/src/styles/settings.css +787 -0
  227. package/src/styles/sorted-table.css +15 -0
  228. package/src/styles/styles.scss +44 -0
  229. package/src/styles/styles.scss.d.ts +9 -0
  230. package/src/utils/form.jsx +23 -0
  231. package/webpack.config.js +17 -6
  232. package/.babelrc +0 -4
  233. package/.env.local +0 -4
  234. package/.env.production +0 -1
  235. package/.github/pull_request_template.md +0 -11
  236. package/.github/workflows/ci.yml +0 -33
  237. package/.vscode/extensions.json +0 -7
  238. package/.vscode/launch.json +0 -15
  239. package/.vscode/settings.json +0 -25
  240. package/Dockerfile +0 -7
  241. package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
  242. package/src/app/pages/ListNamespacesPage/index.jsx +0 -62
  243. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -45
  244. package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
@@ -0,0 +1,63 @@
1
+ import { useContext, useEffect, useState } from 'react';
2
+ import DJClientContext from '../../providers/djclient';
3
+ import NodeIcon from '../../icons/NodeIcon';
4
+ import '../../../styles/overview.css';
5
+
6
+ const COLOR_MAPPING = {
7
+ source: '#00C49F',
8
+ dimension: '#FFBB28', //'#FF8042',
9
+ transform: '#0088FE',
10
+ metric: '#FF91A3', //'#FFBB28',
11
+ cube: '#AA46BE',
12
+ valid: '#00B368',
13
+ invalid: '#B34B00',
14
+ };
15
+
16
+ export const NodesByTypePanel = () => {
17
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
18
+ const [nodesByType, setNodesByType] = useState(null);
19
+ const [materializationsByType, setMaterializationsByType] = useState(null);
20
+
21
+ useEffect(() => {
22
+ const fetchData = async () => {
23
+ setNodesByType(await djClient.system.node_counts_by_type());
24
+ setMaterializationsByType(
25
+ await djClient.system.materialization_counts_by_type(),
26
+ );
27
+ };
28
+ fetchData().catch(console.error);
29
+ }, [djClient]);
30
+
31
+ return (
32
+ <>
33
+ <div className="chart-box" style={{ flex: '0 0 30%', maxWidth: '350px' }}>
34
+ <div className="chart-title">Nodes by Type</div>
35
+ <div className="horiz-box">
36
+ {nodesByType?.map(entry => (
37
+ <div className="vert-box" key={entry.name}>
38
+ <NodeIcon color={COLOR_MAPPING[entry.name]} />
39
+ <strong style={{ color: COLOR_MAPPING[entry.name] }}>
40
+ {entry.value}
41
+ </strong>
42
+ <span>{entry.name}s</span>
43
+ </div>
44
+ ))}
45
+ </div>
46
+ </div>
47
+ <div className="chart-box" style={{ flex: '0 0 30%', maxWidth: '350px' }}>
48
+ <div className="chart-title">Materializations by Type</div>
49
+ <div className="horiz-box">
50
+ {materializationsByType?.map(entry => (
51
+ <div className="vert-box" key={entry.name}>
52
+ <NodeIcon color={COLOR_MAPPING[entry.name]} />
53
+ <strong style={{ color: COLOR_MAPPING[entry.name] }}>
54
+ {entry.value}
55
+ </strong>
56
+ <span>{entry.name}s</span>
57
+ </div>
58
+ ))}
59
+ </div>
60
+ </div>
61
+ </>
62
+ );
63
+ };
@@ -0,0 +1,94 @@
1
+ import { useContext, useEffect, useState } from 'react';
2
+ import DJClientContext from '../../providers/djclient';
3
+ import NodeIcon from '../../icons/NodeIcon';
4
+
5
+ import ValidIcon from '../../icons/ValidIcon';
6
+ import InvalidIcon from '../../icons/InvalidIcon';
7
+
8
+ const COLOR_MAPPING = {
9
+ valid: '#00b368',
10
+ invalid: '#FF91A3', // '#b34b00',
11
+ };
12
+
13
+ export const OverviewPanel = () => {
14
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
15
+ const [nodesByActive, setNodesByActive] = useState(null);
16
+ const [nodesByStatus, setNodesByStatus] = useState(null);
17
+
18
+ useEffect(() => {
19
+ const fetchData = async () => {
20
+ setNodesByActive(await djClient.system.node_counts_by_active());
21
+ setNodesByStatus(await djClient.system.node_counts_by_status());
22
+ };
23
+ fetchData().catch(console.error);
24
+ }, [djClient]);
25
+
26
+ return (
27
+ <div className="chart-box" style={{ flex: '0 0 2%' }}>
28
+ <div className="chart-title">Overview</div>
29
+ <div className="horiz-box">
30
+ {nodesByActive
31
+ ?.filter(entry => entry.name === 'true')
32
+ .map(entry => (
33
+ <div
34
+ className="jss316 badge"
35
+ style={{ color: '#000', margin: '0.2em' }}
36
+ >
37
+ <NodeIcon color="#FFBB28" style={{ marginTop: '0.75em' }} />
38
+ <div style={{ display: 'inline-grid', alignItems: 'center' }}>
39
+ <strong className="horiz-box-value">{entry.value}</strong>
40
+ <span className={'horiz-box-label'}>
41
+ {entry.name === 'true' ? 'Active Nodes' : 'Deactivated'}
42
+ </span>
43
+ </div>
44
+ </div>
45
+ ))}
46
+ </div>
47
+ <div className="horiz-box">
48
+ {nodesByStatus?.map(entry => (
49
+ <div
50
+ className="jss316 badge"
51
+ style={{ color: '#000', margin: '0.2em', marginLeft: '1.2em' }}
52
+ >
53
+
54
+ <span
55
+ style={{
56
+ color: COLOR_MAPPING[entry.name.toLowerCase()],
57
+ margin: '0 0.2em 0 0.4em',
58
+ }}
59
+ >
60
+ {entry.name === 'VALID' ? (
61
+ <ValidIcon
62
+ width={'25px'}
63
+ height={'25px'}
64
+ style={{ marginTop: '0.2em' }}
65
+ />
66
+ ) : (
67
+ <InvalidIcon
68
+ width={'25px'}
69
+ height={'25px'}
70
+ style={{ marginTop: '0.2em' }}
71
+ />
72
+ )}
73
+ </span>
74
+ <div style={{ display: 'inline-flex', alignItems: 'center' }}>
75
+ <strong
76
+ style={{
77
+ color: COLOR_MAPPING[entry.name.toLowerCase()],
78
+ margin: '0 2px',
79
+ fontSize: '16px',
80
+ textAlign: 'left',
81
+ }}
82
+ >
83
+ {entry.value}
84
+ </strong>
85
+ <span style={{ fontSize: 'smaller', padding: '5px 2px' }}>
86
+ {entry.name.toLowerCase()}
87
+ </span>
88
+ </div>
89
+ </div>
90
+ ))}
91
+ </div>
92
+ </div>
93
+ );
94
+ };
@@ -0,0 +1,66 @@
1
+ import { useContext, useEffect, useState } from 'react';
2
+ import DJClientContext from '../../providers/djclient';
3
+ import {
4
+ Legend,
5
+ Tooltip,
6
+ ResponsiveContainer,
7
+ BarChart,
8
+ CartesianGrid,
9
+ XAxis,
10
+ YAxis,
11
+ Bar,
12
+ } from 'recharts';
13
+
14
+ const COLOR_MAPPING = {
15
+ source: '#00C49F',
16
+ dimension: '#FFBB28',
17
+ transform: '#0088FE',
18
+ metric: '#FF91A3',
19
+ cube: '#AA46BE',
20
+ };
21
+
22
+ export const TrendsPanel = () => {
23
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
24
+ const [nodeTrends, setNodeTrends] = useState([]);
25
+
26
+ useEffect(() => {
27
+ const fetchData = async () => {
28
+ setNodeTrends(await djClient.system.node_trends());
29
+ };
30
+ fetchData().catch(console.error);
31
+ }, [djClient]);
32
+
33
+ return (
34
+ <div className="chart-box" style={{ maxWidth: '60%', flex: '1 1 20%' }}>
35
+ <div className="chart-title">Trends</div>
36
+ <ResponsiveContainer width="100%" height={400}>
37
+ <BarChart
38
+ width={1000}
39
+ height={400}
40
+ data={nodeTrends}
41
+ margin={{
42
+ top: 20,
43
+ right: 30,
44
+ left: 20,
45
+ bottom: 5,
46
+ }}
47
+ >
48
+ <CartesianGrid strokeDasharray="3 3" />
49
+ <XAxis dataKey="date" />
50
+ <YAxis />
51
+ <Tooltip />
52
+ <Legend />
53
+ {Object.entries(COLOR_MAPPING).map(([key, color]) => (
54
+ <Bar
55
+ key={key}
56
+ dataKey={key}
57
+ stackId="nodeCount"
58
+ fill={color}
59
+ name={key.charAt(0).toUpperCase() + key.slice(1)}
60
+ />
61
+ ))}
62
+ </BarChart>
63
+ </ResponsiveContainer>
64
+ </div>
65
+ );
66
+ };
@@ -0,0 +1,36 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import { ByStatusPanel } from '../ByStatusPanel';
3
+ import DJClientContext from '../../../providers/djclient';
4
+
5
+ describe('<ByStatusPanel />', () => {
6
+ it('fetches nodes by status and displays them correctly', async () => {
7
+ const mockNodeCounts = [
8
+ { name: 'VALID', value: 10 },
9
+ { name: 'INVALID', value: 5 },
10
+ ];
11
+
12
+ const mockDjClient = {
13
+ system: {
14
+ node_counts_by_status: jest.fn().mockResolvedValue(mockNodeCounts),
15
+ },
16
+ };
17
+
18
+ render(
19
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
20
+ <ByStatusPanel />
21
+ </DJClientContext.Provider>,
22
+ );
23
+
24
+ await waitFor(() => {
25
+ expect(mockDjClient.system.node_counts_by_status).toHaveBeenCalled();
26
+ });
27
+
28
+ // Check that counts are rendered
29
+ expect(screen.getByText('10')).toBeInTheDocument();
30
+ expect(screen.getByText('5')).toBeInTheDocument();
31
+
32
+ // Check that labels are rendered
33
+ expect(screen.getByText('valid nodes')).toBeInTheDocument();
34
+ expect(screen.getByText('invalid nodes')).toBeInTheDocument();
35
+ });
36
+ });
@@ -0,0 +1,76 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import DJClientContext from '../../../providers/djclient';
3
+ import { DimensionNodeUsagePanel } from '../DimensionNodeUsagePanel';
4
+
5
+ describe('<DimensionNodeUsagePanel />', () => {
6
+ it('fetches dimension nodes and displays them in sorted order', async () => {
7
+ const mockDimensions = [
8
+ { name: 'dimension_a', indegree: 2, cube_count: 5 }, // 7
9
+ { name: 'dimension_b', indegree: 1, cube_count: 10 }, // 11
10
+ { name: 'dimension_c', indegree: 3, cube_count: 3 }, // 6
11
+ ];
12
+
13
+ const mockDjClient = {
14
+ system: {
15
+ dimensions: jest.fn().mockResolvedValue(mockDimensions),
16
+ },
17
+ };
18
+
19
+ render(
20
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
21
+ <DimensionNodeUsagePanel />
22
+ </DJClientContext.Provider>,
23
+ );
24
+
25
+ // Wait for the API to be called
26
+ await waitFor(() => {
27
+ expect(mockDjClient.system.dimensions).toHaveBeenCalled();
28
+ });
29
+
30
+ // Check that rows are rendered
31
+ expect(screen.getByText('dimension_b')).toBeInTheDocument();
32
+ expect(screen.getByText('dimension_a')).toBeInTheDocument();
33
+ expect(screen.getByText('dimension_c')).toBeInTheDocument();
34
+
35
+ // Check that sorting is correct: b (11), a (7), c (6)
36
+ const rows = screen.getAllByRole('row').slice(1); // skip the header row
37
+ const names = rows.map(row => row.querySelector('a').textContent);
38
+ expect(names).toEqual(['dimension_b', 'dimension_a', 'dimension_c']);
39
+
40
+ // Check that links have correct hrefs
41
+ expect(screen.getByText('dimension_b').closest('a')).toHaveAttribute(
42
+ 'href',
43
+ '/nodes/dimension_b',
44
+ );
45
+
46
+ // Check indegree and cube_count cells
47
+ expect(screen.getByText('1')).toBeInTheDocument(); // b indegree
48
+ expect(screen.getByText('10')).toBeInTheDocument(); // b cube_count
49
+ expect(screen.getByText('2')).toBeInTheDocument(); // a indegree
50
+ expect(screen.getByText('5')).toBeInTheDocument(); // a cube_count
51
+ });
52
+
53
+ it('handles empty dimensions gracefully', async () => {
54
+ const mockDjClient = {
55
+ system: {
56
+ dimensions: jest.fn().mockResolvedValue([]),
57
+ },
58
+ };
59
+
60
+ render(
61
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
62
+ <DimensionNodeUsagePanel />
63
+ </DJClientContext.Provider>,
64
+ );
65
+
66
+ await waitFor(() => {
67
+ expect(mockDjClient.system.dimensions).toHaveBeenCalled();
68
+ });
69
+
70
+ // Table should still be there, but no rows
71
+ expect(screen.getByText('Dimension Node Usage')).toBeInTheDocument();
72
+ expect(
73
+ screen.queryByRole('row', { name: /dimension_/ }),
74
+ ).not.toBeInTheDocument();
75
+ });
76
+ });
@@ -0,0 +1,77 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import DJClientContext from '../../../providers/djclient';
3
+ import { GovernanceWarningsPanel } from '../GovernanceWarningsPanel';
4
+
5
+ describe('<GovernanceWarningsPanel />', () => {
6
+ it('fetches governance warnings and displays percentages and orphaned dimension count', async () => {
7
+ const mockNodesWithoutDescription = [
8
+ { name: 'SOURCE', value: 0.1 }, // 10%
9
+ { name: 'METRIC', value: 0.2 }, // 20%
10
+ ];
11
+
12
+ const mockDimensions = [
13
+ { name: 'dim_1', indegree: 0, cube_count: 1 }, // orphaned
14
+ { name: 'dim_2', indegree: 1, cube_count: 0 }, // orphaned
15
+ { name: 'dim_3', indegree: 2, cube_count: 1 }, // not orphaned
16
+ ];
17
+
18
+ const mockDjClient = {
19
+ system: {
20
+ nodes_without_description: jest
21
+ .fn()
22
+ .mockResolvedValue(mockNodesWithoutDescription),
23
+ dimensions: jest.fn().mockResolvedValue(mockDimensions),
24
+ },
25
+ };
26
+
27
+ render(
28
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
29
+ <GovernanceWarningsPanel />
30
+ </DJClientContext.Provider>,
31
+ );
32
+
33
+ // Wait for both calls to be made and data rendered
34
+ await waitFor(() => {
35
+ expect(mockDjClient.system.nodes_without_description).toHaveBeenCalled();
36
+ expect(mockDjClient.system.dimensions).toHaveBeenCalled();
37
+ });
38
+
39
+ // Check missing description badges
40
+ expect(screen.getByText('10%')).toBeInTheDocument();
41
+ expect(screen.getByText('20%')).toBeInTheDocument();
42
+ expect(screen.getByText('sources')).toBeInTheDocument();
43
+ expect(screen.getByText('metrics')).toBeInTheDocument();
44
+
45
+ // Check orphaned dimension count: should be 2
46
+ expect(screen.getByText('2')).toBeInTheDocument();
47
+ expect(screen.getByText(/dimension nodes/)).toBeInTheDocument();
48
+
49
+ // Should show the title
50
+ expect(screen.getByText('Governance Warnings')).toBeInTheDocument();
51
+ expect(screen.getByText('Missing Description')).toBeInTheDocument();
52
+ expect(screen.getByText('Orphaned Dimensions')).toBeInTheDocument();
53
+ });
54
+
55
+ it('shows fallback if no data returned', async () => {
56
+ const mockDjClient = {
57
+ system: {
58
+ nodes_without_description: jest.fn().mockResolvedValue([]),
59
+ dimensions: jest.fn().mockResolvedValue([]),
60
+ },
61
+ };
62
+
63
+ render(
64
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
65
+ <GovernanceWarningsPanel />
66
+ </DJClientContext.Provider>,
67
+ );
68
+
69
+ await waitFor(() => {
70
+ expect(mockDjClient.system.nodes_without_description).toHaveBeenCalled();
71
+ expect(mockDjClient.system.dimensions).toHaveBeenCalled();
72
+ });
73
+
74
+ // Should show fallback value for orphaned nodes
75
+ expect(screen.getByText('...')).toBeInTheDocument();
76
+ });
77
+ });
@@ -0,0 +1,86 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import DJClientContext from '../../../providers/djclient';
3
+ import { NodesByTypePanel } from '../NodesByTypePanel';
4
+
5
+ describe('<NodesByTypePanel />', () => {
6
+ it('fetches nodes & materializations by type and renders them correctly', async () => {
7
+ const mockNodesByType = [
8
+ { name: 'source', value: 5 },
9
+ { name: 'cube', value: 2 },
10
+ ];
11
+ const mockMaterializationsByType = [
12
+ { name: 'transform', value: 3 },
13
+ { name: 'metric', value: 1 },
14
+ ];
15
+
16
+ const mockDjClient = {
17
+ system: {
18
+ node_counts_by_type: jest.fn().mockResolvedValue(mockNodesByType),
19
+ materialization_counts_by_type: jest
20
+ .fn()
21
+ .mockResolvedValue(mockMaterializationsByType),
22
+ },
23
+ };
24
+
25
+ render(
26
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
27
+ <NodesByTypePanel />
28
+ </DJClientContext.Provider>,
29
+ );
30
+
31
+ // Wait for async calls to complete
32
+ await waitFor(() => {
33
+ expect(mockDjClient.system.node_counts_by_type).toHaveBeenCalled();
34
+ expect(
35
+ mockDjClient.system.materialization_counts_by_type,
36
+ ).toHaveBeenCalled();
37
+ });
38
+
39
+ // Check that nodes by type appear
40
+ expect(screen.getByText('Nodes by Type')).toBeInTheDocument();
41
+ expect(screen.getByText('5')).toBeInTheDocument();
42
+ expect(screen.getByText('sources')).toBeInTheDocument();
43
+ expect(screen.getByText('2')).toBeInTheDocument();
44
+ expect(screen.getByText('cubes')).toBeInTheDocument();
45
+
46
+ // Check that materializations by type appear
47
+ expect(screen.getByText('Materializations by Type')).toBeInTheDocument();
48
+ expect(screen.getByText('3')).toBeInTheDocument();
49
+ expect(screen.getByText('transforms')).toBeInTheDocument();
50
+ expect(screen.getByText('1')).toBeInTheDocument();
51
+ expect(screen.getByText('metrics')).toBeInTheDocument();
52
+
53
+ // Should render an icon for each entry
54
+ const icons = screen.getAllByTestId('node-icon');
55
+ expect(icons).toHaveLength(
56
+ mockNodesByType.length + mockMaterializationsByType.length,
57
+ );
58
+ });
59
+
60
+ it('renders nothing if no data returned', async () => {
61
+ const mockDjClient = {
62
+ system: {
63
+ node_counts_by_type: jest.fn().mockResolvedValue([]),
64
+ materialization_counts_by_type: jest.fn().mockResolvedValue([]),
65
+ },
66
+ };
67
+
68
+ render(
69
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
70
+ <NodesByTypePanel />
71
+ </DJClientContext.Provider>,
72
+ );
73
+
74
+ await waitFor(() => {
75
+ expect(mockDjClient.system.node_counts_by_type).toHaveBeenCalled();
76
+ expect(
77
+ mockDjClient.system.materialization_counts_by_type,
78
+ ).toHaveBeenCalled();
79
+ });
80
+
81
+ expect(screen.getByText('Nodes by Type')).toBeInTheDocument();
82
+ expect(screen.getByText('Materializations by Type')).toBeInTheDocument();
83
+ expect(screen.queryByText('sources')).not.toBeInTheDocument();
84
+ expect(screen.queryByText('metrics')).not.toBeInTheDocument();
85
+ });
86
+ });
@@ -0,0 +1,78 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import DJClientContext from '../../../providers/djclient';
3
+ import { OverviewPanel } from '../OverviewPanel';
4
+
5
+ describe('<OverviewPanel />', () => {
6
+ it('renders Active Nodes and Valid/Invalid nodes correctly', async () => {
7
+ const mockNodesByActive = [
8
+ { name: 'true', value: 7 },
9
+ { name: 'false', value: 2 }, // Should be filtered out
10
+ ];
11
+
12
+ const mockNodesByStatus = [
13
+ { name: 'VALID', value: 5 },
14
+ { name: 'INVALID', value: 3 },
15
+ ];
16
+
17
+ const mockDjClient = {
18
+ system: {
19
+ node_counts_by_active: jest.fn().mockResolvedValue(mockNodesByActive),
20
+ node_counts_by_status: jest.fn().mockResolvedValue(mockNodesByStatus),
21
+ },
22
+ };
23
+
24
+ render(
25
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
26
+ <OverviewPanel />
27
+ </DJClientContext.Provider>,
28
+ );
29
+
30
+ await waitFor(() => {
31
+ expect(mockDjClient.system.node_counts_by_active).toHaveBeenCalled();
32
+ expect(mockDjClient.system.node_counts_by_status).toHaveBeenCalled();
33
+ });
34
+
35
+ // Chart title
36
+ expect(screen.getByText('Overview')).toBeInTheDocument();
37
+
38
+ // Active Nodes section
39
+ expect(screen.getByText('7')).toBeInTheDocument();
40
+ expect(screen.getByText('Active Nodes')).toBeInTheDocument();
41
+ expect(screen.getAllByTestId('node-icon')).toHaveLength(1);
42
+
43
+ // Valid/Invalid status section
44
+ expect(screen.getByText('5')).toBeInTheDocument();
45
+ expect(screen.getByText('valid')).toBeInTheDocument();
46
+ expect(screen.getByText('3')).toBeInTheDocument();
47
+ expect(screen.getByText('invalid')).toBeInTheDocument();
48
+
49
+ // Should render one ValidIcon and one InvalidIcon
50
+ expect(screen.getAllByTestId('valid-icon')).toHaveLength(1);
51
+ expect(screen.getAllByTestId('invalid-icon')).toHaveLength(1);
52
+ });
53
+
54
+ it('renders no badges if data is empty', async () => {
55
+ const mockDjClient = {
56
+ system: {
57
+ node_counts_by_active: jest.fn().mockResolvedValue([]),
58
+ node_counts_by_status: jest.fn().mockResolvedValue([]),
59
+ },
60
+ };
61
+
62
+ render(
63
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
64
+ <OverviewPanel />
65
+ </DJClientContext.Provider>,
66
+ );
67
+
68
+ await waitFor(() => {
69
+ expect(mockDjClient.system.node_counts_by_active).toHaveBeenCalled();
70
+ expect(mockDjClient.system.node_counts_by_status).toHaveBeenCalled();
71
+ });
72
+
73
+ expect(screen.getByText('Overview')).toBeInTheDocument();
74
+ expect(screen.queryByTestId('node-icon')).not.toBeInTheDocument();
75
+ expect(screen.queryByTestId('valid-icon')).not.toBeInTheDocument();
76
+ expect(screen.queryByTestId('invalid-icon')).not.toBeInTheDocument();
77
+ });
78
+ });