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,120 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import DJClientContext from '../../../providers/djclient';
3
+ import { TrendsPanel } from '../TrendsPanel';
4
+
5
+ jest.mock('recharts', () => {
6
+ const Original = jest.requireActual('recharts');
7
+ return {
8
+ ...Original,
9
+ ResponsiveContainer: ({ children, ...props }) => (
10
+ <div data-testid="responsive-container">{children}</div>
11
+ ),
12
+ BarChart: ({ children, data, ...props }) => (
13
+ <div data-testid="barchart" data-data={JSON.stringify(data)}>
14
+ {children}
15
+ </div>
16
+ ),
17
+ CartesianGrid: props => <div data-testid="cartesian-grid" />,
18
+ XAxis: props => <div data-testid="x-axis" />,
19
+ YAxis: props => <div data-testid="y-axis" />,
20
+ Tooltip: props => <div data-testid="tooltip" />,
21
+ Legend: props => <div data-testid="legend" />,
22
+ Bar: props => (
23
+ <div data-testid={`bar-${props.dataKey}`} data-fill={props.fill} />
24
+ ),
25
+ };
26
+ });
27
+
28
+ describe('<TrendsPanel />', () => {
29
+ it('fetches and renders node trends', async () => {
30
+ const mockNodeTrends = [
31
+ {
32
+ date: '2024-01-01',
33
+ source: 2,
34
+ dimension: 1,
35
+ transform: 3,
36
+ metric: 5,
37
+ cube: 0,
38
+ },
39
+ {
40
+ date: '2024-01-02',
41
+ source: 1,
42
+ dimension: 2,
43
+ transform: 1,
44
+ metric: 3,
45
+ cube: 4,
46
+ },
47
+ ];
48
+
49
+ const mockDjClient = {
50
+ system: {
51
+ node_trends: jest.fn().mockResolvedValue(mockNodeTrends),
52
+ },
53
+ };
54
+
55
+ render(
56
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
57
+ <TrendsPanel />
58
+ </DJClientContext.Provider>,
59
+ );
60
+
61
+ await waitFor(() => {
62
+ expect(mockDjClient.system.node_trends).toHaveBeenCalled();
63
+ });
64
+
65
+ // Chart title
66
+ expect(screen.getByText('Trends')).toBeInTheDocument();
67
+
68
+ // The Recharts containers render
69
+ expect(screen.getByTestId('responsive-container')).toBeInTheDocument();
70
+ expect(screen.getByTestId('barchart')).toBeInTheDocument();
71
+
72
+ // Should contain grid, axes, tooltip, legend
73
+ expect(screen.getByTestId('cartesian-grid')).toBeInTheDocument();
74
+ expect(screen.getByTestId('x-axis')).toBeInTheDocument();
75
+ expect(screen.getByTestId('y-axis')).toBeInTheDocument();
76
+ expect(screen.getByTestId('tooltip')).toBeInTheDocument();
77
+ expect(screen.getByTestId('legend')).toBeInTheDocument();
78
+
79
+ // Should render bars with expected colors
80
+ const expectedBars = {
81
+ source: '#00C49F',
82
+ dimension: '#FFBB28',
83
+ transform: '#0088FE',
84
+ metric: '#FF91A3',
85
+ cube: '#AA46BE',
86
+ };
87
+
88
+ Object.entries(expectedBars).forEach(([key, color]) => {
89
+ const bar = screen.getByTestId(`bar-${key}`);
90
+ expect(bar).toBeInTheDocument();
91
+ expect(bar).toHaveAttribute('data-fill', color);
92
+ });
93
+
94
+ // BarChart gets correct data
95
+ const barChart = screen.getByTestId('barchart');
96
+ expect(barChart.getAttribute('data-data')).toContain('2024-01-01');
97
+ });
98
+
99
+ it('renders with empty trends', async () => {
100
+ const mockDjClient = {
101
+ system: {
102
+ node_trends: jest.fn().mockResolvedValue([]),
103
+ },
104
+ };
105
+
106
+ render(
107
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
108
+ <TrendsPanel />
109
+ </DJClientContext.Provider>,
110
+ );
111
+
112
+ await waitFor(() => {
113
+ expect(mockDjClient.system.node_trends).toHaveBeenCalled();
114
+ });
115
+
116
+ expect(screen.getByText('Trends')).toBeInTheDocument();
117
+ expect(screen.getByTestId('responsive-container')).toBeInTheDocument();
118
+ expect(screen.getByTestId('barchart')).toBeInTheDocument();
119
+ });
120
+ });
@@ -0,0 +1,54 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { OverviewPage } from '../index';
3
+
4
+ jest.mock('../OverviewPanel', () => ({
5
+ OverviewPanel: () => <div data-testid="overview-panel">OverviewPanel</div>,
6
+ }));
7
+ jest.mock('../NodesByTypePanel', () => ({
8
+ NodesByTypePanel: () => (
9
+ <div data-testid="nodes-by-type-panel">NodesByTypePanel</div>
10
+ ),
11
+ }));
12
+ jest.mock('../GovernanceWarningsPanel', () => ({
13
+ GovernanceWarningsPanel: () => (
14
+ <div data-testid="governance-warnings-panel">GovernanceWarningsPanel</div>
15
+ ),
16
+ }));
17
+ jest.mock('../TrendsPanel', () => ({
18
+ TrendsPanel: () => <div data-testid="trends-panel">TrendsPanel</div>,
19
+ }));
20
+ jest.mock('../DimensionNodeUsagePanel', () => ({
21
+ DimensionNodeUsagePanel: () => (
22
+ <div data-testid="dimension-node-usage-panel">DimensionNodeUsagePanel</div>
23
+ ),
24
+ }));
25
+
26
+ describe('<OverviewPage />', () => {
27
+ it('renders the OverviewPage with all panels', () => {
28
+ render(<OverviewPage />);
29
+
30
+ // Check the main containers
31
+ const containers = screen.getAllByClassName
32
+ ? screen.getAllByClassName('chart-container')
33
+ : document.querySelectorAll('.chart-container');
34
+
35
+ expect(containers.length).toBe(2);
36
+
37
+ // Check each mocked panel appears
38
+ expect(screen.getByTestId('overview-panel')).toBeInTheDocument();
39
+ expect(screen.getByTestId('nodes-by-type-panel')).toBeInTheDocument();
40
+ expect(screen.getByTestId('governance-warnings-panel')).toBeInTheDocument();
41
+ expect(screen.getByTestId('trends-panel')).toBeInTheDocument();
42
+ expect(
43
+ screen.getByTestId('dimension-node-usage-panel'),
44
+ ).toBeInTheDocument();
45
+
46
+ // Check panels are in the correct containers
47
+ expect(containers[0].innerHTML).toContain('OverviewPanel');
48
+ expect(containers[0].innerHTML).toContain('NodesByTypePanel');
49
+ expect(containers[0].innerHTML).toContain('GovernanceWarningsPanel');
50
+
51
+ expect(containers[1].innerHTML).toContain('TrendsPanel');
52
+ expect(containers[1].innerHTML).toContain('DimensionNodeUsagePanel');
53
+ });
54
+ });
@@ -0,0 +1,22 @@
1
+ import { OverviewPanel } from './OverviewPanel';
2
+ import { NodesByTypePanel } from './NodesByTypePanel';
3
+ import { GovernanceWarningsPanel } from './GovernanceWarningsPanel';
4
+ import { TrendsPanel } from './TrendsPanel';
5
+ import { DimensionNodeUsagePanel } from './DimensionNodeUsagePanel';
6
+
7
+ export function OverviewPage() {
8
+ return (
9
+ <div className="mid">
10
+ <div className="chart-container">
11
+ <OverviewPanel />
12
+ <NodesByTypePanel />
13
+ <GovernanceWarningsPanel />
14
+ </div>
15
+
16
+ <div className="chart-container">
17
+ <TrendsPanel />
18
+ <DimensionNodeUsagePanel />
19
+ </div>
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Asynchronously loads the component for the Node page
3
+ */
4
+
5
+ import * as React from 'react';
6
+ import { lazyLoad } from '../../../utils/loadable';
7
+
8
+ export const RegisterTablePage = () => {
9
+ return lazyLoad(
10
+ () => import('./index'),
11
+ module => module.RegisterTablePage,
12
+ {
13
+ fallback: <div></div>,
14
+ },
15
+ )();
16
+ };
@@ -0,0 +1,112 @@
1
+ import React from 'react';
2
+ import { fireEvent, screen, waitFor } from '@testing-library/react';
3
+ import fetchMock from 'jest-fetch-mock';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { render } from '../../../../setupTests';
6
+ import { MemoryRouter, Route, Routes } from 'react-router-dom';
7
+ import DJClientContext from '../../../providers/djclient';
8
+ import { RegisterTablePage } from '../index';
9
+
10
+ describe('<RegisterTablePage />', () => {
11
+ const initializeMockDJClient = () => {
12
+ return {
13
+ DataJunctionAPI: {
14
+ catalogs: jest.fn(),
15
+ registerTable: jest.fn(),
16
+ },
17
+ };
18
+ };
19
+
20
+ const mockDjClient = initializeMockDJClient();
21
+
22
+ beforeEach(() => {
23
+ fetchMock.resetMocks();
24
+ jest.clearAllMocks();
25
+ window.scrollTo = jest.fn();
26
+
27
+ mockDjClient.DataJunctionAPI.catalogs.mockReturnValue([
28
+ {
29
+ name: 'warehouse',
30
+ engines: [
31
+ {
32
+ name: 'duckdb',
33
+ version: '0.7.1',
34
+ uri: null,
35
+ dialect: null,
36
+ },
37
+ ],
38
+ },
39
+ ]);
40
+ });
41
+
42
+ const renderRegisterTable = element => {
43
+ return render(
44
+ <MemoryRouter initialEntries={['/create/source']}>
45
+ <Routes>
46
+ <Route path="create/source" element={element} />
47
+ </Routes>
48
+ </MemoryRouter>,
49
+ );
50
+ };
51
+
52
+ const testElement = djClient => {
53
+ return (
54
+ <DJClientContext.Provider value={djClient}>
55
+ <RegisterTablePage />
56
+ </DJClientContext.Provider>
57
+ );
58
+ };
59
+
60
+ it('registers a table correctly', async () => {
61
+ mockDjClient.DataJunctionAPI.registerTable.mockReturnValue({
62
+ status: 201,
63
+ json: { name: 'source.warehouse.schema.some_table' },
64
+ });
65
+
66
+ const element = testElement(mockDjClient);
67
+ const { container, getByTestId } = renderRegisterTable(element);
68
+
69
+ const catalog = getByTestId('choose-catalog');
70
+ await waitFor(async () => {
71
+ fireEvent.keyDown(catalog.firstChild, { key: 'ArrowDown' });
72
+ const warehouseOptions = screen.getAllByText('warehouse');
73
+ fireEvent.click(warehouseOptions[warehouseOptions.length - 1]);
74
+ });
75
+
76
+ await userEvent.type(screen.getByLabelText('Schema'), 'schema');
77
+ await userEvent.type(screen.getByLabelText('Table'), 'some_table');
78
+ await userEvent.click(screen.getByRole('button'));
79
+
80
+ await waitFor(() => {
81
+ expect(mockDjClient.DataJunctionAPI.registerTable).toBeCalled();
82
+ expect(mockDjClient.DataJunctionAPI.registerTable).toBeCalledWith(
83
+ 'warehouse',
84
+ 'schema',
85
+ 'some_table',
86
+ );
87
+ });
88
+ expect(container.getElementsByClassName('message')).toMatchSnapshot();
89
+ }, 60000);
90
+
91
+ it('fails to register a table', async () => {
92
+ mockDjClient.DataJunctionAPI.registerTable.mockReturnValue({
93
+ status: 500,
94
+ json: { message: 'table not found' },
95
+ });
96
+
97
+ const element = testElement(mockDjClient);
98
+ const { getByTestId } = renderRegisterTable(element);
99
+
100
+ const catalog = getByTestId('choose-catalog');
101
+ await waitFor(async () => {
102
+ fireEvent.keyDown(catalog.firstChild, { key: 'ArrowDown' });
103
+ const warehouseOptions = screen.getAllByText('warehouse');
104
+ fireEvent.click(warehouseOptions[warehouseOptions.length - 1]);
105
+ });
106
+
107
+ await userEvent.type(screen.getByLabelText('Schema'), 'schema');
108
+ await userEvent.type(screen.getByLabelText('Table'), 'some_table');
109
+ await userEvent.click(screen.getByRole('button'));
110
+ expect(screen.getByText('table not found')).toBeInTheDocument();
111
+ }, 60000);
112
+ });
@@ -0,0 +1,38 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<RegisterTablePage /> registers a table correctly 1`] = `
4
+ HTMLCollection [
5
+ <div
6
+ class="message success"
7
+ data-testid="success"
8
+ >
9
+ <svg
10
+ class="bi bi-check-circle-fill"
11
+ data-testid="valid-icon"
12
+ fill="currentColor"
13
+ height="25px"
14
+ viewBox="0 0 16 16"
15
+ width="25px"
16
+ xmlns="http://www.w3.org/2000/svg"
17
+ >
18
+ <path
19
+ d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"
20
+ />
21
+ </svg>
22
+ Successfully registered source node
23
+
24
+ <a
25
+ href="/nodes/source.warehouse.schema.some_table"
26
+ >
27
+ source.warehouse.schema.some_table
28
+ </a>
29
+ , which references table
30
+ warehouse
31
+ .
32
+ schema
33
+ .
34
+ some_table
35
+ .
36
+ </div>,
37
+ ]
38
+ `;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Node add + edit page for transforms, metrics, and dimensions. The creation and edit flow for these
3
+ * node types is largely the same, with minor differences handled server-side. For the `query`
4
+ * field, this page will render a CodeMirror SQL editor with autocompletion and syntax highlighting.
5
+ */
6
+ import { ErrorMessage, Field, Form, Formik } from 'formik';
7
+
8
+ import NamespaceHeader from '../../components/NamespaceHeader';
9
+ import React, { useContext, useEffect, useState } from 'react';
10
+ import DJClientContext from '../../providers/djclient';
11
+ import 'styles/node-creation.scss';
12
+ import { FormikSelect } from '../AddEditNodePage/FormikSelect';
13
+ import { displayMessageAfterSubmit } from '../../../utils/form';
14
+
15
+ export function RegisterTablePage() {
16
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
17
+ const [catalogs, setCatalogs] = useState([]);
18
+
19
+ useEffect(() => {
20
+ const fetchData = async () => {
21
+ const catalogs = await djClient.catalogs();
22
+ setCatalogs(
23
+ catalogs.map(catalog => {
24
+ return { value: catalog.name, label: catalog.name };
25
+ }),
26
+ );
27
+ };
28
+ fetchData().catch(console.error);
29
+ }, [djClient, djClient.namespaces]);
30
+
31
+ const initialValues = {
32
+ catalog: '',
33
+ schema: '',
34
+ table: '',
35
+ };
36
+
37
+ const validator = values => {
38
+ const errors = {};
39
+ if (!values.table) {
40
+ errors.table = 'Required';
41
+ }
42
+ if (!values.schema) {
43
+ errors.schema = 'Required';
44
+ }
45
+ return errors;
46
+ };
47
+
48
+ const handleSubmit = async (values, { setSubmitting, setStatus }) => {
49
+ const { status, json } = await djClient.registerTable(
50
+ values.catalog,
51
+ values.schema,
52
+ values.table,
53
+ );
54
+ if (status === 200 || status === 201) {
55
+ setStatus({
56
+ success: (
57
+ <>
58
+ Successfully registered source node{' '}
59
+ <a href={`/nodes/${json.name}`}>{json.name}</a>, which references
60
+ table {values.catalog}.{values.schema}.{values.table}.
61
+ </>
62
+ ),
63
+ });
64
+ } else {
65
+ setStatus({
66
+ failure: `${json.message}`,
67
+ });
68
+ }
69
+ setSubmitting(false);
70
+ window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
71
+ };
72
+
73
+ return (
74
+ <div className="mid">
75
+ <NamespaceHeader namespace="" />
76
+ <div className="card">
77
+ <div className="card-header">
78
+ <h2>
79
+ Register{' '}
80
+ <span className={`node_type__source node_type_creation_heading`}>
81
+ Source
82
+ </span>
83
+ </h2>
84
+ <center>
85
+ <Formik
86
+ initialValues={initialValues}
87
+ validate={validator}
88
+ onSubmit={handleSubmit}
89
+ >
90
+ {function Render({ isSubmitting, status }) {
91
+ return (
92
+ <Form>
93
+ {displayMessageAfterSubmit(status)}
94
+ {
95
+ <>
96
+ <div className="SourceCreationInput">
97
+ <ErrorMessage name="catalog" component="span" />
98
+ <label htmlFor="catalog">Catalog</label>
99
+ <span data-testid="choose-catalog">
100
+ <FormikSelect
101
+ selectOptions={catalogs}
102
+ formikFieldName="catalog"
103
+ placeholder="Choose Catalog"
104
+ defaultValue={catalogs[0]}
105
+ />
106
+ </span>
107
+ </div>
108
+ <div className="SourceCreationInput">
109
+ <ErrorMessage name="schema" component="span" />
110
+ <label htmlFor="schema">Schema</label>
111
+ <Field
112
+ type="text"
113
+ name="schema"
114
+ id="schema"
115
+ placeholder="Schema"
116
+ />
117
+ </div>
118
+ <div className="SourceCreationInput NodeCreationInput">
119
+ <ErrorMessage name="table" component="span" />
120
+ <label htmlFor="table">Table</label>
121
+ <Field
122
+ type="text"
123
+ name="table"
124
+ id="table"
125
+ placeholder="Table"
126
+ />
127
+ </div>
128
+ <button type="submit" disabled={isSubmitting}>
129
+ Register
130
+ </button>
131
+ </>
132
+ }
133
+ </Form>
134
+ );
135
+ }}
136
+ </Formik>
137
+ </center>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ );
142
+ }
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import { Root } from '../index';
4
+ import DJClientContext from '../../../providers/djclient';
5
+ import { HelmetProvider } from 'react-helmet-async';
6
+
7
+ describe('<Root />', () => {
8
+ const mockDjClient = {
9
+ logout: jest.fn(),
10
+ nodeDetails: jest.fn(),
11
+ listTags: jest.fn().mockResolvedValue([]),
12
+ nodes: jest.fn().mockResolvedValue([]),
13
+ whoami: jest.fn().mockResolvedValue({
14
+ id: 1,
15
+ username: 'testuser',
16
+ email: 'test@example.com',
17
+ }),
18
+ getSubscribedHistory: jest.fn().mockResolvedValue([]),
19
+ markNotificationsRead: jest.fn().mockResolvedValue({}),
20
+ getNodesByNames: jest.fn().mockResolvedValue([]),
21
+ };
22
+
23
+ beforeEach(() => {
24
+ jest.clearAllMocks();
25
+ });
26
+
27
+ it('renders with the correct title and navigation', async () => {
28
+ render(
29
+ <HelmetProvider>
30
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
31
+ <Root />
32
+ </DJClientContext.Provider>
33
+ </HelmetProvider>,
34
+ );
35
+
36
+ await waitFor(() => {
37
+ expect(document.title).toEqual('DataJunction');
38
+ });
39
+
40
+ // Check navigation links exist
41
+ expect(screen.getByText('Explore')).toBeInTheDocument();
42
+ expect(screen.getByText('SQL')).toBeInTheDocument();
43
+ });
44
+ });
@@ -1,25 +1,57 @@
1
+ import { useState } from 'react';
1
2
  import { Outlet } from 'react-router-dom';
2
- import logo from './assets/dj-logo.png';
3
+ import DJLogo from '../../icons/DJLogo';
3
4
  import { Helmet } from 'react-helmet-async';
5
+ import Search from '../../components/Search';
6
+ import NotificationBell from '../../components/NotificationBell';
7
+ import UserMenu from '../../components/UserMenu';
8
+ import '../../../styles/nav-bar.css';
9
+
10
+ // Define the type for the docs sites
11
+ type DocsSites = {
12
+ [key: string]: string;
13
+ };
14
+
15
+ // Default docs sites if REACT_APP_DOCS_SITES is not defined
16
+ const defaultDocsSites: DocsSites = {
17
+ 'Open-Source': 'https://www.datajunction.io/',
18
+ };
19
+
20
+ // Parse the JSON map from the environment variable or use the default
21
+ const docsSites: DocsSites = process.env.REACT_APP_DOCS_SITES
22
+ ? (JSON.parse(process.env.REACT_APP_DOCS_SITES as string) as DocsSites)
23
+ : defaultDocsSites;
4
24
 
5
25
  export function Root() {
26
+ // Track which dropdown is open to close others
27
+ const [openDropdown, setOpenDropdown] = useState<
28
+ 'notifications' | 'user' | null
29
+ >(null);
30
+
6
31
  return (
7
32
  <>
8
33
  <Helmet>
9
34
  <title>DataJunction</title>
10
- <meta
11
- name="description"
12
- content="DataJunction Metrics Platform Webapp"
13
- />
35
+ <meta name="description" content="DataJunction UI" />
14
36
  </Helmet>
15
37
  <div className="container d-flex align-items-center justify-content-between">
16
38
  <div className="header">
17
39
  <div className="logo">
18
- <h2>
19
- <img src={logo} alt="DJ Logo" width="15%" />
20
- DataJunction
21
- </h2>
40
+ <a
41
+ href={'/'}
42
+ style={{
43
+ textTransform: 'none',
44
+ textDecoration: 'none',
45
+ color: '#000',
46
+ }}
47
+ >
48
+ <h2>
49
+ <DJLogo />
50
+ Data<b>Junction</b>
51
+ </h2>
52
+ </a>
22
53
  </div>
54
+ <Search />
23
55
  <div className="menu">
24
56
  <div className="menu-item here menu-here-bg menu-lg-down-accordion me-0 me-lg-2 fw-semibold">
25
57
  <span className="menu-link">
@@ -29,12 +61,62 @@ export function Root() {
29
61
  </span>
30
62
  <span className="menu-link">
31
63
  <span className="menu-title">
32
- <a href="/">Help</a>
64
+ <a href="/sql">SQL</a>
65
+ </span>
66
+ </span>
67
+ <span className="menu-link">
68
+ <span className="menu-title">
69
+ <div className="dropdown">
70
+ <a
71
+ className="btn btn-link dropdown-toggle"
72
+ href="/"
73
+ id="docsDropdown"
74
+ role="button"
75
+ aria-expanded="false"
76
+ >
77
+ Docs
78
+ </a>
79
+ <ul
80
+ className="dropdown-menu"
81
+ aria-labelledby="docsDropdown"
82
+ >
83
+ {Object.entries(docsSites).map(([key, value]) => (
84
+ <li key={key}>
85
+ <a
86
+ className="dropdown-item"
87
+ href={value}
88
+ target="_blank"
89
+ rel="noreferrer"
90
+ >
91
+ {key}
92
+ </a>
93
+ </li>
94
+ ))}
95
+ </ul>
96
+ </div>
33
97
  </span>
34
98
  </span>
35
99
  </div>
36
100
  </div>
37
101
  </div>
102
+ {process.env.REACT_DISABLE_AUTH === 'true' ? (
103
+ ''
104
+ ) : (
105
+ <div className="nav-right">
106
+ <NotificationBell
107
+ onDropdownToggle={isOpen => {
108
+ setOpenDropdown(isOpen ? 'notifications' : null);
109
+ }}
110
+ forceClose={openDropdown === 'user'}
111
+ />
112
+ <UserMenu
113
+ onDropdownToggle={isOpen => {
114
+ setOpenDropdown(isOpen ? 'user' : null);
115
+ }}
116
+ forceClose={openDropdown === 'notifications'}
117
+ />
118
+ </div>
119
+ )}
38
120
  </div>
39
121
  <Outlet />
40
122
  </>