datajunction-ui 0.0.1-a1

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 (154) hide show
  1. package/.babel-plugin-macrosrc.js +5 -0
  2. package/.env +3 -0
  3. package/.eslintrc.js +20 -0
  4. package/.gitattributes +201 -0
  5. package/.husky/pre-commit +6 -0
  6. package/.nvmrc +1 -0
  7. package/.prettierignore +6 -0
  8. package/.prettierrc +9 -0
  9. package/.stylelintrc +7 -0
  10. package/LICENSE +22 -0
  11. package/Makefile +3 -0
  12. package/README.md +10 -0
  13. package/dj-logo.svg +10 -0
  14. package/internals/testing/loadable.mock.tsx +6 -0
  15. package/package.json +189 -0
  16. package/public/favicon.ico +0 -0
  17. package/public/index.html +26 -0
  18. package/public/manifest.json +15 -0
  19. package/public/robots.txt +3 -0
  20. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  21. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +9 -0
  22. package/src/app/__tests__/index.test.tsx +14 -0
  23. package/src/app/components/DeleteNode.jsx +55 -0
  24. package/src/app/components/ListGroupItem.jsx +24 -0
  25. package/src/app/components/NamespaceHeader.jsx +31 -0
  26. package/src/app/components/QueryInfo.jsx +77 -0
  27. package/src/app/components/Tab.jsx +25 -0
  28. package/src/app/components/ToggleSwitch.jsx +20 -0
  29. package/src/app/components/__tests__/DeleteNode.test.jsx +53 -0
  30. package/src/app/components/__tests__/ListGroupItem.test.tsx +16 -0
  31. package/src/app/components/__tests__/NamespaceHeader.test.jsx +14 -0
  32. package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
  33. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  34. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  35. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +29 -0
  36. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +47 -0
  37. package/src/app/components/djgraph/Collapse.jsx +46 -0
  38. package/src/app/components/djgraph/DJNode.jsx +89 -0
  39. package/src/app/components/djgraph/DJNodeColumns.jsx +71 -0
  40. package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
  41. package/src/app/components/djgraph/LayoutFlow.jsx +104 -0
  42. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  43. package/src/app/components/djgraph/__tests__/DJNode.test.tsx +24 -0
  44. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  45. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  46. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +117 -0
  47. package/src/app/constants.js +2 -0
  48. package/src/app/icons/AlertIcon.jsx +32 -0
  49. package/src/app/icons/CollapsedIcon.jsx +15 -0
  50. package/src/app/icons/DJLogo.jsx +36 -0
  51. package/src/app/icons/DeleteIcon.jsx +21 -0
  52. package/src/app/icons/EditIcon.jsx +18 -0
  53. package/src/app/icons/ExpandedIcon.jsx +15 -0
  54. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  55. package/src/app/icons/InvalidIcon.jsx +14 -0
  56. package/src/app/icons/LoadingIcon.jsx +14 -0
  57. package/src/app/icons/PythonIcon.jsx +52 -0
  58. package/src/app/icons/TableIcon.jsx +14 -0
  59. package/src/app/icons/ValidIcon.jsx +14 -0
  60. package/src/app/index.tsx +108 -0
  61. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +46 -0
  62. package/src/app/pages/AddEditNodePage/FullNameField.jsx +37 -0
  63. package/src/app/pages/AddEditNodePage/Loadable.jsx +16 -0
  64. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +89 -0
  65. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +103 -0
  66. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +132 -0
  67. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
  68. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
  69. package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
  70. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
  71. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
  72. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
  73. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +180 -0
  74. package/src/app/pages/AddEditNodePage/index.jsx +396 -0
  75. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  76. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  77. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  78. package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
  79. package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
  80. package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
  81. package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
  82. package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
  83. package/src/app/pages/LoginPage/index.jsx +17 -0
  84. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
  85. package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
  86. package/src/app/pages/NamespacePage/Loadable.jsx +16 -0
  87. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +217 -0
  88. package/src/app/pages/NamespacePage/index.jsx +199 -0
  89. package/src/app/pages/NodePage/AddBackfillPopover.jsx +166 -0
  90. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +161 -0
  91. package/src/app/pages/NodePage/ClientCodePopover.jsx +46 -0
  92. package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
  93. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +149 -0
  94. package/src/app/pages/NodePage/Loadable.jsx +16 -0
  95. package/src/app/pages/NodePage/NodeColumnTab.jsx +200 -0
  96. package/src/app/pages/NodePage/NodeGraphTab.jsx +112 -0
  97. package/src/app/pages/NodePage/NodeHistory.jsx +212 -0
  98. package/src/app/pages/NodePage/NodeInfoTab.jsx +212 -0
  99. package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
  100. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +233 -0
  101. package/src/app/pages/NodePage/NodeSQLTab.jsx +82 -0
  102. package/src/app/pages/NodePage/NodeStatus.jsx +28 -0
  103. package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
  104. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +153 -0
  105. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +47 -0
  106. package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +49 -0
  107. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
  108. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +165 -0
  109. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +591 -0
  110. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +57 -0
  111. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +757 -0
  112. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  113. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +403 -0
  114. package/src/app/pages/NodePage/index.jsx +210 -0
  115. package/src/app/pages/NotFoundPage/Loadable.tsx +14 -0
  116. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  117. package/src/app/pages/NotFoundPage/index.tsx +23 -0
  118. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  119. package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +110 -0
  120. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +37 -0
  121. package/src/app/pages/RegisterTablePage/index.jsx +142 -0
  122. package/src/app/pages/Root/Loadable.tsx +14 -0
  123. package/src/app/pages/Root/__tests__/index.test.jsx +77 -0
  124. package/src/app/pages/Root/assets/dj-logo.png +0 -0
  125. package/src/app/pages/Root/index.tsx +70 -0
  126. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  127. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  128. package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
  129. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  130. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  131. package/src/app/pages/TagPage/index.jsx +79 -0
  132. package/src/app/providers/djclient.jsx +5 -0
  133. package/src/app/services/DJService.js +665 -0
  134. package/src/app/services/__tests__/DJService.test.jsx +804 -0
  135. package/src/index.tsx +48 -0
  136. package/src/mocks/mockNodes.jsx +1430 -0
  137. package/src/react-app-env.d.ts +4 -0
  138. package/src/reportWebVitals.ts +15 -0
  139. package/src/setupTests.ts +36 -0
  140. package/src/styles/dag.css +228 -0
  141. package/src/styles/index.css +1083 -0
  142. package/src/styles/loading.css +34 -0
  143. package/src/styles/login.css +81 -0
  144. package/src/styles/node-creation.scss +197 -0
  145. package/src/styles/styles.scss +44 -0
  146. package/src/styles/styles.scss.d.ts +9 -0
  147. package/src/utils/__tests__/__snapshots__/loadable.test.tsx.snap +17 -0
  148. package/src/utils/__tests__/loadable.test.tsx +53 -0
  149. package/src/utils/__tests__/request.test.ts +82 -0
  150. package/src/utils/form.jsx +23 -0
  151. package/src/utils/loadable.tsx +30 -0
  152. package/src/utils/request.ts +54 -0
  153. package/tsconfig.json +34 -0
  154. package/webpack.config.js +118 -0
@@ -0,0 +1,757 @@
1
+ import React from 'react';
2
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
3
+ import { mocks } from '../../../../mocks/mockNodes';
4
+ import DJClientContext from '../../../providers/djclient';
5
+ import { NodePage } from '../Loadable';
6
+ import { MemoryRouter, Route, Routes } from 'react-router-dom';
7
+ import userEvent from '@testing-library/user-event';
8
+
9
+ describe('<NodePage />', () => {
10
+ const domTestingLib = require('@testing-library/dom');
11
+ const { queryHelpers } = domTestingLib;
12
+
13
+ const queryByAttribute = attribute =>
14
+ queryHelpers.queryAllByAttribute.bind(null, attribute);
15
+
16
+ function getByAttribute(container, id, attribute, ...rest) {
17
+ const result = queryByAttribute(attribute)(container, id, ...rest);
18
+ return result[0];
19
+ }
20
+
21
+ const mockDJClient = () => {
22
+ return {
23
+ DataJunctionAPI: {
24
+ node: jest.fn(),
25
+ metric: jest.fn(),
26
+ node_dag: jest.fn().mockReturnValue(mocks.mockNodeDAG),
27
+ clientCode: jest.fn().mockReturnValue('dj_client = DJClient()'),
28
+ columns: jest.fn(),
29
+ history: jest.fn(),
30
+ revisions: jest.fn(),
31
+ materializations: jest.fn(),
32
+ sql: jest.fn(),
33
+ cube: jest.fn(),
34
+ compiledSql: jest.fn(),
35
+ node_lineage: jest.fn(),
36
+ nodesWithDimension: jest.fn(),
37
+ attributes: jest.fn(),
38
+ dimensions: jest.fn(),
39
+ setPartition: jest.fn(),
40
+ engines: jest.fn(),
41
+ },
42
+ };
43
+ };
44
+
45
+ const defaultProps = {
46
+ name: 'default.avg_repair_price',
47
+ djNode: {
48
+ namespace: 'default',
49
+ node_revision_id: 24,
50
+ node_id: 24,
51
+ type: 'metric',
52
+ name: 'default.avg_repair_price',
53
+ display_name: 'Default: Avg Repair Price',
54
+ version: 'v1.0',
55
+ status: 'valid',
56
+ mode: 'published',
57
+ catalog: {
58
+ id: 1,
59
+ uuid: '0fc18295-e1a2-4c3c-b72a-894725c12488',
60
+ created_at: '2023-08-21T16:48:51.146121+00:00',
61
+ updated_at: '2023-08-21T16:48:51.146122+00:00',
62
+ extra_params: {},
63
+ name: 'warehouse',
64
+ },
65
+ schema_: null,
66
+ table: null,
67
+ description: 'Average repair price',
68
+ query:
69
+ 'SELECT avg(price) default_DOT_avg_repair_price \n FROM default.repair_order_details\n\n',
70
+ availability: null,
71
+ columns: [
72
+ {
73
+ name: 'default_DOT_avg_repair_price',
74
+ type: 'double',
75
+ display_name: 'Default DOT avg repair price',
76
+ attributes: [],
77
+ dimension: null,
78
+ },
79
+ ],
80
+ updated_at: '2023-08-21T16:48:56.932231+00:00',
81
+ materializations: [],
82
+ parents: [
83
+ {
84
+ name: 'default.repair_order_details',
85
+ },
86
+ ],
87
+ created_at: '2023-08-21T16:48:56.932162+00:00',
88
+ tags: [{ name: 'purpose', display_name: 'Purpose' }],
89
+ primary_key: [],
90
+ createNodeClientCode:
91
+ 'dj = DJBuilder(DJ_URL)\n\navg_repair_price = dj.create_metric(\n description="Average repair price",\n display_name="Default: Avg Repair Price",\n name="default.avg_repair_price",\n primary_key=[],\n query="""SELECT avg(price) default_DOT_avg_repair_price \n FROM default.repair_order_details\n\n"""\n)',
92
+ dimensions: [
93
+ {
94
+ name: 'default.date_dim.dateint',
95
+ type: 'timestamp',
96
+ path: [
97
+ 'default.repair_order_details.repair_order_id',
98
+ 'default.repair_order.hard_hat_id',
99
+ 'default.hard_hat.birth_date',
100
+ ],
101
+ },
102
+ {
103
+ name: 'default.date_dim.dateint',
104
+ type: 'timestamp',
105
+ path: [
106
+ 'default.repair_order_details.repair_order_id',
107
+ 'default.repair_order.hard_hat_id',
108
+ 'default.hard_hat.hire_date',
109
+ ],
110
+ },
111
+ {
112
+ name: 'default.date_dim.day',
113
+ type: 'int',
114
+ path: [
115
+ 'default.repair_order_details.repair_order_id',
116
+ 'default.repair_order.hard_hat_id',
117
+ 'default.hard_hat.birth_date',
118
+ ],
119
+ },
120
+ {
121
+ name: 'default.date_dim.day',
122
+ type: 'int',
123
+ path: [
124
+ 'default.repair_order_details.repair_order_id',
125
+ 'default.repair_order.hard_hat_id',
126
+ 'default.hard_hat.hire_date',
127
+ ],
128
+ },
129
+ {
130
+ name: 'default.date_dim.month',
131
+ type: 'int',
132
+ path: [
133
+ 'default.repair_order_details.repair_order_id',
134
+ 'default.repair_order.hard_hat_id',
135
+ 'default.hard_hat.birth_date',
136
+ ],
137
+ },
138
+ {
139
+ name: 'default.date_dim.month',
140
+ type: 'int',
141
+ path: [
142
+ 'default.repair_order_details.repair_order_id',
143
+ 'default.repair_order.hard_hat_id',
144
+ 'default.hard_hat.hire_date',
145
+ ],
146
+ },
147
+ {
148
+ name: 'default.date_dim.year',
149
+ type: 'int',
150
+ path: [
151
+ 'default.repair_order_details.repair_order_id',
152
+ 'default.repair_order.hard_hat_id',
153
+ 'default.hard_hat.birth_date',
154
+ ],
155
+ },
156
+ {
157
+ name: 'default.date_dim.year',
158
+ type: 'int',
159
+ path: [
160
+ 'default.repair_order_details.repair_order_id',
161
+ 'default.repair_order.hard_hat_id',
162
+ 'default.hard_hat.hire_date',
163
+ ],
164
+ },
165
+ {
166
+ name: 'default.hard_hat.address',
167
+ type: 'string',
168
+ path: [
169
+ 'default.repair_order_details.repair_order_id',
170
+ 'default.repair_order.hard_hat_id',
171
+ ],
172
+ },
173
+ {
174
+ name: 'default.hard_hat.birth_date',
175
+ type: 'date',
176
+ path: [
177
+ 'default.repair_order_details.repair_order_id',
178
+ 'default.repair_order.hard_hat_id',
179
+ ],
180
+ },
181
+ {
182
+ name: 'default.hard_hat.city',
183
+ type: 'string',
184
+ path: [
185
+ 'default.repair_order_details.repair_order_id',
186
+ 'default.repair_order.hard_hat_id',
187
+ ],
188
+ },
189
+ {
190
+ name: 'default.hard_hat.contractor_id',
191
+ type: 'int',
192
+ path: [
193
+ 'default.repair_order_details.repair_order_id',
194
+ 'default.repair_order.hard_hat_id',
195
+ ],
196
+ },
197
+ {
198
+ name: 'default.hard_hat.country',
199
+ type: 'string',
200
+ path: [
201
+ 'default.repair_order_details.repair_order_id',
202
+ 'default.repair_order.hard_hat_id',
203
+ ],
204
+ },
205
+ {
206
+ name: 'default.hard_hat.first_name',
207
+ type: 'string',
208
+ path: [
209
+ 'default.repair_order_details.repair_order_id',
210
+ 'default.repair_order.hard_hat_id',
211
+ ],
212
+ },
213
+ {
214
+ name: 'default.hard_hat.hard_hat_id',
215
+ type: 'int',
216
+ path: [
217
+ 'default.repair_order_details.repair_order_id',
218
+ 'default.repair_order.hard_hat_id',
219
+ ],
220
+ },
221
+ {
222
+ name: 'default.hard_hat.hire_date',
223
+ type: 'date',
224
+ path: [
225
+ 'default.repair_order_details.repair_order_id',
226
+ 'default.repair_order.hard_hat_id',
227
+ ],
228
+ },
229
+ {
230
+ name: 'default.hard_hat.last_name',
231
+ type: 'string',
232
+ path: [
233
+ 'default.repair_order_details.repair_order_id',
234
+ 'default.repair_order.hard_hat_id',
235
+ ],
236
+ },
237
+ {
238
+ name: 'default.hard_hat.manager',
239
+ type: 'int',
240
+ path: [
241
+ 'default.repair_order_details.repair_order_id',
242
+ 'default.repair_order.hard_hat_id',
243
+ ],
244
+ },
245
+ {
246
+ name: 'default.hard_hat.postal_code',
247
+ type: 'string',
248
+ path: [
249
+ 'default.repair_order_details.repair_order_id',
250
+ 'default.repair_order.hard_hat_id',
251
+ ],
252
+ },
253
+ {
254
+ name: 'default.hard_hat.state',
255
+ type: 'string',
256
+ path: [
257
+ 'default.repair_order_details.repair_order_id',
258
+ 'default.repair_order.hard_hat_id',
259
+ ],
260
+ },
261
+ {
262
+ name: 'default.hard_hat.title',
263
+ type: 'string',
264
+ path: [
265
+ 'default.repair_order_details.repair_order_id',
266
+ 'default.repair_order.hard_hat_id',
267
+ ],
268
+ },
269
+ ],
270
+ },
271
+ };
272
+
273
+ it('renders the NodeInfo tab correctly', async () => {
274
+ const djClient = mockDJClient();
275
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
276
+ djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
277
+ const element = (
278
+ <DJClientContext.Provider value={djClient}>
279
+ <NodePage {...defaultProps} />
280
+ </DJClientContext.Provider>
281
+ );
282
+ const { container } = render(
283
+ <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
284
+ <Routes>
285
+ <Route path="nodes/:name" element={element} />
286
+ </Routes>
287
+ </MemoryRouter>,
288
+ );
289
+
290
+ await waitFor(() => {
291
+ expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
292
+ 'default.num_repair_orders',
293
+ );
294
+ userEvent.click(screen.getByRole('button', { name: 'Info' }));
295
+
296
+ expect(
297
+ screen.getByRole('dialog', { name: 'NodeName' }),
298
+ ).toHaveTextContent('default.num_repair_orders');
299
+
300
+ expect(screen.getByRole('button', { name: 'Info' })).toBeInTheDocument();
301
+ expect(
302
+ screen.getByRole('dialog', { name: 'Description' }),
303
+ ).toHaveTextContent('Number of repair orders');
304
+
305
+ expect(screen.getByRole('dialog', { name: 'Version' })).toHaveTextContent(
306
+ 'v1.0',
307
+ );
308
+
309
+ expect(
310
+ screen.getByRole('dialog', { name: 'NodeStatus' }),
311
+ ).toBeInTheDocument();
312
+
313
+ expect(screen.getByRole('dialog', { name: 'Tags' })).toHaveTextContent(
314
+ 'Purpose',
315
+ );
316
+
317
+ expect(
318
+ screen.getByRole('dialog', { name: 'PrimaryKey' }),
319
+ ).toHaveTextContent('repair_order_id, country');
320
+
321
+ expect(
322
+ screen.getByRole('dialog', { name: 'DisplayName' }),
323
+ ).toHaveTextContent('Default: Num Repair Orders');
324
+
325
+ expect(
326
+ screen.getByRole('dialog', { name: 'NodeType' }),
327
+ ).toHaveTextContent('metric');
328
+
329
+ expect(
330
+ container.getElementsByClassName('language-sql'),
331
+ ).toMatchSnapshot();
332
+ });
333
+ }, 60000);
334
+
335
+ it('renders the NodeInfo tab correctly for cube nodes', async () => {
336
+ const djClient = mockDJClient();
337
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockCubeNode);
338
+ djClient.DataJunctionAPI.cube.mockReturnValue(mocks.mockCubesCube);
339
+ const element = (
340
+ <DJClientContext.Provider value={djClient}>
341
+ <NodePage {...defaultProps} />
342
+ </DJClientContext.Provider>
343
+ );
344
+ const { container } = render(
345
+ <MemoryRouter initialEntries={['/nodes/default.repair_orders_cube']}>
346
+ <Routes>
347
+ <Route path="nodes/:name" element={element} />
348
+ </Routes>
349
+ </MemoryRouter>,
350
+ );
351
+
352
+ await waitFor(() => {
353
+ expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
354
+ 'default.repair_orders_cube',
355
+ );
356
+ userEvent.click(screen.getByRole('button', { name: 'Info' }));
357
+
358
+ expect(
359
+ screen.getByRole('dialog', { name: 'NodeName' }),
360
+ ).toHaveTextContent('default.repair_orders_cube');
361
+
362
+ expect(screen.getByRole('button', { name: 'Info' })).toBeInTheDocument();
363
+ expect(
364
+ screen.getByRole('dialog', { name: 'Description' }),
365
+ ).toHaveTextContent('Repair Orders');
366
+
367
+ expect(screen.getByRole('dialog', { name: 'Version' })).toHaveTextContent(
368
+ 'v1.0',
369
+ );
370
+
371
+ expect(
372
+ screen.getByRole('dialog', { name: 'PrimaryKey' }),
373
+ ).toHaveTextContent('');
374
+
375
+ expect(
376
+ screen.getByRole('dialog', { name: 'DisplayName' }),
377
+ ).toHaveTextContent('Default: Repair Orders Cube');
378
+
379
+ expect(
380
+ screen.getByRole('dialog', { name: 'NodeType' }),
381
+ ).toHaveTextContent('cube');
382
+
383
+ expect(
384
+ screen.getByRole('dialog', { name: 'NodeType' }),
385
+ ).toHaveTextContent('cube');
386
+
387
+ expect(screen.getByText('Cube Elements')).toBeInTheDocument();
388
+ screen
389
+ .getAllByRole('cell', { name: 'CubeElement' })
390
+ .map(cube => cube.hasAttribute('a'));
391
+ });
392
+ }, 60000);
393
+
394
+ it('renders the NodeColumns tab correctly', async () => {
395
+ const djClient = mockDJClient();
396
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
397
+ djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
398
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
399
+ djClient.DataJunctionAPI.attributes.mockReturnValue(mocks.attributes);
400
+ djClient.DataJunctionAPI.dimensions.mockReturnValue(mocks.dimensions);
401
+ djClient.DataJunctionAPI.engines.mockReturnValue([]);
402
+ djClient.DataJunctionAPI.setPartition.mockReturnValue({
403
+ status: 200,
404
+ json: { message: '' },
405
+ });
406
+
407
+ const element = (
408
+ <DJClientContext.Provider value={djClient}>
409
+ <NodePage />
410
+ </DJClientContext.Provider>
411
+ );
412
+ render(
413
+ <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
414
+ <Routes>
415
+ <Route path="nodes/:name" element={element} />
416
+ </Routes>
417
+ </MemoryRouter>,
418
+ );
419
+ await waitFor(() => {
420
+ fireEvent.click(screen.getByRole('button', { name: 'Columns' }));
421
+ expect(djClient.DataJunctionAPI.columns).toHaveBeenCalledWith(
422
+ mocks.mockMetricNode,
423
+ );
424
+ expect(
425
+ screen.getByRole('columnheader', { name: 'ColumnName' }),
426
+ ).toHaveTextContent('default_DOT_avg_repair_price');
427
+ expect(
428
+ screen.getByRole('columnheader', { name: 'ColumnDisplayName' }),
429
+ ).toHaveTextContent('Default DOT avg repair price');
430
+ expect(
431
+ screen.getByRole('columnheader', { name: 'ColumnType' }),
432
+ ).toHaveTextContent('double');
433
+
434
+ // check that the edit column popover can be clicked
435
+ const editColumnPopover = screen.getByRole('button', {
436
+ name: 'EditColumn',
437
+ });
438
+ expect(editColumnPopover).toBeInTheDocument();
439
+ fireEvent.click(editColumnPopover);
440
+ expect(
441
+ screen.getByRole('button', { name: 'SaveEditColumn' }),
442
+ ).toBeInTheDocument();
443
+
444
+ // check that the link dimension popover can be clicked
445
+ const linkDimensionPopover = screen.getByRole('button', {
446
+ name: 'LinkDimension',
447
+ });
448
+ expect(linkDimensionPopover).toBeInTheDocument();
449
+ fireEvent.click(linkDimensionPopover);
450
+ expect(
451
+ screen.getByRole('button', { name: 'SaveLinkDimension' }),
452
+ ).toBeInTheDocument();
453
+
454
+ // check that the set column partition popover can be clicked
455
+ const partitionColumnPopover = screen.getByRole('button', {
456
+ name: 'PartitionColumn',
457
+ });
458
+ expect(partitionColumnPopover).toBeInTheDocument();
459
+ fireEvent.click(partitionColumnPopover);
460
+ const savePartition = screen.getByRole('button', {
461
+ name: 'SaveEditColumn',
462
+ });
463
+ expect(savePartition).toBeInTheDocument();
464
+ fireEvent.click(savePartition);
465
+ expect(screen.getByText('Saved!'));
466
+ });
467
+ });
468
+ // check compiled SQL on nodeInfo page
469
+
470
+ it('renders the NodeHistory tab correctly', async () => {
471
+ const djClient = mockDJClient();
472
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
473
+ djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
474
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
475
+ djClient.DataJunctionAPI.history.mockReturnValue(mocks.metricNodeHistory);
476
+ djClient.DataJunctionAPI.revisions.mockReturnValue(
477
+ mocks.metricNodeRevisions,
478
+ );
479
+
480
+ const element = (
481
+ <DJClientContext.Provider value={djClient}>
482
+ <NodePage />
483
+ </DJClientContext.Provider>
484
+ );
485
+ const { container } = render(
486
+ <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
487
+ <Routes>
488
+ <Route path="nodes/:name" element={element} />
489
+ </Routes>
490
+ </MemoryRouter>,
491
+ );
492
+ await waitFor(async () => {
493
+ fireEvent.click(screen.getByRole('button', { name: 'History' }));
494
+ expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
495
+ mocks.mockMetricNode.name,
496
+ );
497
+ expect(djClient.DataJunctionAPI.history).toHaveBeenCalledWith(
498
+ 'node',
499
+ mocks.mockMetricNode.name,
500
+ );
501
+ expect(djClient.DataJunctionAPI.revisions).toHaveBeenCalledWith(
502
+ mocks.mockMetricNode.name,
503
+ );
504
+ expect(
505
+ screen.getByRole('table', { name: 'Revisions' }),
506
+ ).toMatchSnapshot();
507
+ expect(screen.getByRole('table', { name: 'Activity' })).toHaveTextContent(
508
+ 'ActivityTypeNameUserTimestampDetailscreatenodedefault.avg_repair_priceunknown2023-08-21T16:48:56.950482+00:00',
509
+ );
510
+ screen
511
+ .queryAllByRole('cell', {
512
+ name: 'HistoryAttribute',
513
+ })
514
+ .forEach(cell => expect(cell).toHaveTextContent(/Set col1 as /));
515
+
516
+ screen
517
+ .queryAllByRole('cell', {
518
+ name: 'HistoryCreateLink',
519
+ })
520
+ .forEach(cell =>
521
+ expect(cell).toHaveTextContent(
522
+ 'Linked col1 todefault.hard_hat viahard_hat_id',
523
+ ),
524
+ );
525
+
526
+ screen
527
+ .queryAllByRole('cell', {
528
+ name: 'HistoryCreateMaterialization',
529
+ })
530
+ .forEach(cell =>
531
+ expect(cell).toHaveTextContent(
532
+ 'Initialized materialization some_random_materialization',
533
+ ),
534
+ );
535
+
536
+ screen
537
+ .queryAllByRole('cell', {
538
+ name: 'HistoryNodeStatusChange',
539
+ })
540
+ .forEach(cell =>
541
+ expect(cell).toHaveTextContent(
542
+ 'Status changed from valid to invalid Caused by a change in upstream default.repair_order_details',
543
+ ),
544
+ );
545
+ screen.debug();
546
+ });
547
+ });
548
+
549
+ it('renders compiled sql correctly', async () => {
550
+ const djClient = mockDJClient();
551
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
552
+ djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
553
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
554
+ djClient.DataJunctionAPI.compiledSql.mockReturnValue('select 1');
555
+
556
+ const element = (
557
+ <DJClientContext.Provider value={djClient}>
558
+ <NodePage />
559
+ </DJClientContext.Provider>
560
+ );
561
+ render(
562
+ <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
563
+ <Routes>
564
+ <Route path="nodes/:name" element={element} />
565
+ </Routes>
566
+ </MemoryRouter>,
567
+ );
568
+ await waitFor(() => {
569
+ fireEvent.click(screen.getByRole('checkbox', { name: 'ToggleSwitch' }));
570
+ expect(djClient.DataJunctionAPI.compiledSql).toHaveBeenCalledWith(
571
+ mocks.mockMetricNode.name,
572
+ );
573
+ });
574
+ });
575
+
576
+ it('renders an empty NodeMaterialization tab correctly', async () => {
577
+ const djClient = mockDJClient();
578
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
579
+ djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
580
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
581
+ djClient.DataJunctionAPI.materializations.mockReturnValue([]);
582
+
583
+ const element = (
584
+ <DJClientContext.Provider value={djClient}>
585
+ <NodePage />
586
+ </DJClientContext.Provider>
587
+ );
588
+ render(
589
+ <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
590
+ <Routes>
591
+ <Route path="nodes/:name" element={element} />
592
+ </Routes>
593
+ </MemoryRouter>,
594
+ );
595
+ await waitFor(() => {
596
+ fireEvent.click(screen.getByRole('button', { name: 'Materializations' }));
597
+ expect(djClient.DataJunctionAPI.materializations).toHaveBeenCalledWith(
598
+ mocks.mockMetricNode.name,
599
+ );
600
+ screen.getByText(
601
+ 'No materialization workflows configured for this node.',
602
+ );
603
+ screen.getByText('No materialized datasets available for this node.');
604
+ });
605
+ });
606
+
607
+ it('renders the NodeMaterialization tab with materializations correctly', async () => {
608
+ const djClient = mockDJClient();
609
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
610
+ djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
611
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
612
+ djClient.DataJunctionAPI.materializations.mockReturnValue(
613
+ mocks.nodeMaterializations,
614
+ );
615
+
616
+ const element = (
617
+ <DJClientContext.Provider value={djClient}>
618
+ <NodePage />
619
+ </DJClientContext.Provider>
620
+ );
621
+ render(
622
+ <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
623
+ <Routes>
624
+ <Route path="nodes/:name" element={element} />
625
+ </Routes>
626
+ </MemoryRouter>,
627
+ );
628
+ await waitFor(
629
+ () => {
630
+ fireEvent.click(
631
+ screen.getByRole('button', { name: 'Materializations' }),
632
+ );
633
+ expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
634
+ mocks.mockMetricNode.name,
635
+ );
636
+ expect(djClient.DataJunctionAPI.materializations).toHaveBeenCalledWith(
637
+ mocks.mockMetricNode.name,
638
+ );
639
+
640
+ expect(
641
+ screen.getByRole('table', { name: 'Materializations' }),
642
+ ).toMatchSnapshot();
643
+ },
644
+ { timeout: 3000 },
645
+ );
646
+ }, 60000);
647
+
648
+ it('renders the NodeSQL tab', async () => {
649
+ const djClient = mockDJClient();
650
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
651
+ djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
652
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
653
+ djClient.DataJunctionAPI.sql.mockReturnValue(mocks.metricNodeColumns);
654
+ const element = (
655
+ <DJClientContext.Provider value={djClient}>
656
+ <NodePage />
657
+ </DJClientContext.Provider>
658
+ );
659
+ render(
660
+ <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
661
+ <Routes>
662
+ <Route path="nodes/:name" element={element} />
663
+ </Routes>
664
+ </MemoryRouter>,
665
+ );
666
+ await waitFor(() => {
667
+ const sqlButton = screen.getByRole('button', { name: 'SQL' });
668
+ sqlButton.click();
669
+ expect(djClient.DataJunctionAPI.sql).toHaveBeenCalledWith(
670
+ 'default.num_repair_orders',
671
+ { dimensions: [], filters: [] },
672
+ );
673
+ });
674
+ });
675
+
676
+ it('renders a NodeColumnLineage tab correctly', async () => {
677
+ const djClient = mockDJClient();
678
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
679
+ djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNodeJson);
680
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
681
+ djClient.DataJunctionAPI.node_lineage.mockReturnValue(
682
+ mocks.mockNodeLineage,
683
+ );
684
+
685
+ const element = (
686
+ <DJClientContext.Provider value={djClient}>
687
+ <NodePage />
688
+ </DJClientContext.Provider>
689
+ );
690
+ render(
691
+ <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
692
+ <Routes>
693
+ <Route path="nodes/:name" element={element} />
694
+ </Routes>
695
+ </MemoryRouter>,
696
+ );
697
+ await waitFor(() => {
698
+ fireEvent.click(screen.getByRole('button', { name: 'Lineage' }));
699
+ expect(djClient.DataJunctionAPI.node_lineage).toHaveBeenCalledWith(
700
+ mocks.mockMetricNode.name,
701
+ );
702
+ });
703
+ });
704
+
705
+ it('renders a NodeGraph tab correctly', async () => {
706
+ const djClient = mockDJClient();
707
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
708
+ djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNodeJson);
709
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
710
+
711
+ const element = (
712
+ <DJClientContext.Provider value={djClient}>
713
+ <NodePage />
714
+ </DJClientContext.Provider>
715
+ );
716
+ render(
717
+ <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
718
+ <Routes>
719
+ <Route path="nodes/:name" element={element} />
720
+ </Routes>
721
+ </MemoryRouter>,
722
+ );
723
+ await waitFor(() => {
724
+ fireEvent.click(screen.getByRole('button', { name: 'Graph' }));
725
+ expect(djClient.DataJunctionAPI.node_dag).toHaveBeenCalledWith(
726
+ mocks.mockMetricNode.name,
727
+ );
728
+ });
729
+ });
730
+
731
+ it('renders Linked Nodes tab correctly', async () => {
732
+ const djClient = mockDJClient();
733
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockDimensionNode);
734
+ djClient.DataJunctionAPI.nodesWithDimension.mockReturnValue(
735
+ mocks.mockLinkedNodes,
736
+ );
737
+
738
+ const element = (
739
+ <DJClientContext.Provider value={djClient}>
740
+ <NodePage />
741
+ </DJClientContext.Provider>
742
+ );
743
+ render(
744
+ <MemoryRouter initialEntries={['/nodes/default.dispatcher']}>
745
+ <Routes>
746
+ <Route path="nodes/:name" element={element} />
747
+ </Routes>
748
+ </MemoryRouter>,
749
+ );
750
+ await waitFor(() => {
751
+ fireEvent.click(screen.getByRole('button', { name: 'Linked Nodes' }));
752
+ expect(djClient.DataJunctionAPI.nodesWithDimension).toHaveBeenCalledWith(
753
+ mocks.mockDimensionNode.name,
754
+ );
755
+ });
756
+ });
757
+ });