datajunction-ui 0.0.1-rc.9 → 0.0.3

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 (205) hide show
  1. package/.env +2 -0
  2. package/.prettierignore +3 -1
  3. package/Makefile +9 -0
  4. package/dj-logo.svg +10 -0
  5. package/package.json +53 -14
  6. package/public/favicon.ico +0 -0
  7. package/public/index.html +1 -1
  8. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  9. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +5 -109
  10. package/src/app/components/AddNodeDropdown.jsx +44 -0
  11. package/src/app/components/ListGroupItem.jsx +9 -1
  12. package/src/app/components/NamespaceHeader.jsx +4 -13
  13. package/src/app/components/NodeListActions.jsx +69 -0
  14. package/src/app/components/NodeMaterializationDelete.jsx +90 -0
  15. package/src/app/components/QueryInfo.jsx +172 -0
  16. package/src/app/components/Search.jsx +94 -0
  17. package/src/app/components/Tab.jsx +8 -1
  18. package/src/app/components/ToggleSwitch.jsx +20 -0
  19. package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
  20. package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
  21. package/src/app/components/__tests__/Search.test.jsx +63 -0
  22. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  23. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  24. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +8 -3
  25. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
  26. package/src/app/components/djgraph/Collapse.jsx +47 -0
  27. package/src/app/components/djgraph/DJNode.jsx +61 -83
  28. package/src/app/components/djgraph/DJNodeColumns.jsx +75 -0
  29. package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
  30. package/src/app/components/djgraph/LayoutFlow.jsx +106 -0
  31. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  32. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  33. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  34. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +84 -40
  35. package/src/app/components/forms/Action.jsx +8 -0
  36. package/src/app/components/forms/NodeNameField.jsx +64 -0
  37. package/src/app/components/search.css +17 -0
  38. package/src/app/constants.js +2 -0
  39. package/src/app/icons/AddItemIcon.jsx +16 -0
  40. package/src/app/icons/AlertIcon.jsx +33 -0
  41. package/src/app/icons/CollapsedIcon.jsx +15 -0
  42. package/src/app/icons/CommitIcon.jsx +45 -0
  43. package/src/app/icons/DJLogo.jsx +36 -0
  44. package/src/app/icons/DeleteIcon.jsx +21 -0
  45. package/src/app/icons/DiffIcon.jsx +63 -0
  46. package/src/app/icons/EditIcon.jsx +18 -0
  47. package/src/app/icons/ExpandedIcon.jsx +15 -0
  48. package/src/app/icons/EyeIcon.jsx +20 -0
  49. package/src/app/icons/FilterIcon.jsx +7 -0
  50. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  51. package/src/app/icons/InvalidIcon.jsx +16 -0
  52. package/src/app/icons/JupyterExportIcon.jsx +25 -0
  53. package/src/app/icons/LoadingIcon.jsx +14 -0
  54. package/src/app/icons/NodeIcon.jsx +49 -0
  55. package/src/app/icons/PythonIcon.jsx +14 -0
  56. package/src/app/icons/TableIcon.jsx +14 -0
  57. package/src/app/icons/ValidIcon.jsx +16 -0
  58. package/src/app/index.tsx +118 -37
  59. package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
  60. package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
  61. package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
  62. package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
  63. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +51 -0
  64. package/src/app/pages/AddEditNodePage/FullNameField.jsx +38 -0
  65. package/src/app/pages/AddEditNodePage/Loadable.jsx +20 -0
  66. package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
  67. package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
  68. package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
  69. package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
  70. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +94 -0
  71. package/src/app/pages/AddEditNodePage/OwnersField.jsx +54 -0
  72. package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
  73. package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
  74. package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
  75. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +109 -0
  76. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +287 -0
  77. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
  78. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
  79. package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
  80. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
  81. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
  82. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
  83. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +224 -0
  84. package/src/app/pages/AddEditNodePage/index.jsx +506 -0
  85. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  86. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  87. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  88. package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +152 -0
  89. package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
  90. package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +75 -0
  91. package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +373 -0
  92. package/src/app/pages/CubeBuilderPage/index.jsx +291 -0
  93. package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
  94. package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
  95. package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
  96. package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
  97. package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
  98. package/src/app/pages/LoginPage/index.jsx +17 -0
  99. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
  100. package/src/app/pages/NamespacePage/Explorer.jsx +61 -0
  101. package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
  102. package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
  103. package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
  104. package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
  105. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +297 -0
  106. package/src/app/pages/NamespacePage/index.jsx +319 -42
  107. package/src/app/pages/NodePage/AddBackfillPopover.jsx +165 -0
  108. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +222 -0
  109. package/src/app/pages/NodePage/AvailabilityStateBlock.jsx +67 -0
  110. package/src/app/pages/NodePage/ClientCodePopover.jsx +94 -0
  111. package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
  112. package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
  113. package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
  114. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +164 -0
  115. package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
  116. package/src/app/pages/NodePage/NodeColumnTab.jsx +256 -30
  117. package/src/app/pages/NodePage/NodeDependenciesTab.jsx +153 -0
  118. package/src/app/pages/NodePage/NodeGraphTab.jsx +119 -148
  119. package/src/app/pages/NodePage/NodeHistory.jsx +236 -0
  120. package/src/app/pages/NodePage/NodeInfoTab.jsx +325 -49
  121. package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
  122. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +585 -0
  123. package/src/app/pages/NodePage/NodeRevisionMaterializationTab.jsx +58 -0
  124. package/src/app/pages/NodePage/NodeStatus.jsx +100 -31
  125. package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
  126. package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
  127. package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
  128. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +151 -0
  129. package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
  130. package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
  131. package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
  132. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +56 -0
  133. package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
  134. package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
  135. package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
  136. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
  137. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +161 -0
  138. package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
  139. package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +151 -0
  140. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +595 -0
  141. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +58 -0
  142. package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +190 -0
  143. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +872 -0
  144. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  145. package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
  146. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +19 -0
  147. package/src/app/pages/NodePage/index.jsx +190 -44
  148. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  149. package/src/app/pages/OverviewPage/ByStatusPanel.jsx +69 -0
  150. package/src/app/pages/OverviewPage/DimensionNodeUsagePanel.jsx +48 -0
  151. package/src/app/pages/OverviewPage/GovernanceWarningsPanel.jsx +107 -0
  152. package/src/app/pages/OverviewPage/Loadable.jsx +16 -0
  153. package/src/app/pages/OverviewPage/NodesByTypePanel.jsx +63 -0
  154. package/src/app/pages/OverviewPage/OverviewPanel.jsx +94 -0
  155. package/src/app/pages/OverviewPage/TrendsPanel.jsx +66 -0
  156. package/src/app/pages/OverviewPage/__tests__/ByStatusPanel.test.jsx +36 -0
  157. package/src/app/pages/OverviewPage/__tests__/DimensionNodeUsagePanel.test.jsx +76 -0
  158. package/src/app/pages/OverviewPage/__tests__/GovernanceWarningsPanel.test.jsx +77 -0
  159. package/src/app/pages/OverviewPage/__tests__/NodesByTypePanel.test.jsx +86 -0
  160. package/src/app/pages/OverviewPage/__tests__/OverviewPanel.test.jsx +78 -0
  161. package/src/app/pages/OverviewPage/__tests__/TrendsPanel.test.jsx +120 -0
  162. package/src/app/pages/OverviewPage/__tests__/index.test.jsx +54 -0
  163. package/src/app/pages/OverviewPage/index.jsx +22 -0
  164. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  165. package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +110 -0
  166. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +38 -0
  167. package/src/app/pages/RegisterTablePage/index.jsx +142 -0
  168. package/src/app/pages/Root/__tests__/index.test.jsx +79 -0
  169. package/src/app/pages/Root/index.tsx +84 -6
  170. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  171. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  172. package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
  173. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  174. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  175. package/src/app/pages/TagPage/index.jsx +79 -0
  176. package/src/app/services/DJService.js +1314 -21
  177. package/src/app/services/__tests__/DJService.test.jsx +1559 -0
  178. package/src/index.tsx +1 -0
  179. package/src/mocks/mockNodes.jsx +1474 -0
  180. package/src/setupTests.ts +31 -1
  181. package/src/styles/dag.css +117 -5
  182. package/src/styles/index.css +1027 -30
  183. package/src/styles/loading.css +34 -0
  184. package/src/styles/login.css +81 -0
  185. package/src/styles/node-creation.scss +276 -0
  186. package/src/styles/node-list.css +4 -0
  187. package/src/styles/overview.css +72 -0
  188. package/src/styles/sorted-table.css +15 -0
  189. package/src/styles/styles.scss +44 -0
  190. package/src/styles/styles.scss.d.ts +9 -0
  191. package/src/utils/form.jsx +23 -0
  192. package/webpack.config.js +16 -6
  193. package/.babelrc +0 -4
  194. package/.env.local +0 -4
  195. package/.env.production +0 -1
  196. package/.github/pull_request_template.md +0 -11
  197. package/.github/workflows/ci.yml +0 -33
  198. package/.vscode/extensions.json +0 -7
  199. package/.vscode/launch.json +0 -15
  200. package/.vscode/settings.json +0 -25
  201. package/Dockerfile +0 -7
  202. package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
  203. package/src/app/pages/ListNamespacesPage/index.jsx +0 -62
  204. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -45
  205. package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
@@ -0,0 +1,872 @@
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
+ getMetric: jest.fn(),
27
+ revalidate: jest.fn().mockReturnValue({ status: 'valid' }),
28
+ node_dag: jest.fn().mockReturnValue(mocks.mockNodeDAG),
29
+ clientCode: jest.fn().mockReturnValue('dj_client = DJClient()'),
30
+ columns: jest.fn(),
31
+ history: jest.fn(),
32
+ revisions: jest.fn(),
33
+ materializations: jest.fn(),
34
+ availabilityStates: jest.fn(),
35
+ refreshLatestMaterialization: jest.fn(),
36
+ materializationInfo: jest.fn(),
37
+ sql: jest.fn(),
38
+ cube: jest.fn(),
39
+ compiledSql: jest.fn(),
40
+ node_lineage: jest.fn(),
41
+ nodesWithDimension: jest.fn(),
42
+ attributes: jest.fn(),
43
+ dimensions: jest.fn(),
44
+ setPartition: jest.fn(),
45
+ engines: jest.fn(),
46
+ streamNodeData: jest.fn(),
47
+ nodeDimensions: jest.fn(),
48
+ getNotificationPreferences: jest.fn().mockResolvedValue([]),
49
+ subscribeToNotifications: jest.fn().mockResolvedValue({ status: 200 }),
50
+ unsubscribeFromNotifications: jest
51
+ .fn()
52
+ .mockResolvedValue({ status: 200 }),
53
+ },
54
+ };
55
+ };
56
+
57
+ const defaultProps = {
58
+ name: 'default.avg_repair_price',
59
+ djNode: {
60
+ namespace: 'default',
61
+ node_revision_id: 24,
62
+ node_id: 24,
63
+ type: 'metric',
64
+ name: 'default.avg_repair_price',
65
+ display_name: 'Default: Avg Repair Price',
66
+ version: 'v1.0',
67
+ status: 'valid',
68
+ mode: 'published',
69
+ catalog: {
70
+ id: 1,
71
+ uuid: '0fc18295-e1a2-4c3c-b72a-894725c12488',
72
+ created_at: '2023-08-21T16:48:51.146121+00:00',
73
+ updated_at: '2023-08-21T16:48:51.146122+00:00',
74
+ extra_params: {},
75
+ name: 'warehouse',
76
+ },
77
+ schema_: null,
78
+ table: null,
79
+ description: 'Average repair price',
80
+ query:
81
+ 'SELECT avg(price) default_DOT_avg_repair_price \n FROM default.repair_order_details\n\n',
82
+ availability: null,
83
+ dimension_links: [],
84
+ columns: [
85
+ {
86
+ name: 'default_DOT_avg_repair_price',
87
+ type: 'double',
88
+ display_name: 'Default DOT avg repair price',
89
+ attributes: [],
90
+ dimension: null,
91
+ },
92
+ ],
93
+ updated_at: '2023-08-21T16:48:56.932231+00:00',
94
+ materializations: [],
95
+ parents: [
96
+ {
97
+ name: 'default.repair_order_details',
98
+ },
99
+ ],
100
+ created_at: '2023-08-21T16:48:56.932162+00:00',
101
+ tags: [{ name: 'purpose', display_name: 'Purpose' }],
102
+ primary_key: [],
103
+ incompatible_druid_functions: ['IF'],
104
+ createNodeClientCode:
105
+ '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)',
106
+ dimensions: [
107
+ {
108
+ name: 'default.date_dim.dateint',
109
+ type: 'timestamp',
110
+ path: [
111
+ 'default.repair_order_details.repair_order_id',
112
+ 'default.repair_order.hard_hat_id',
113
+ 'default.hard_hat.birth_date',
114
+ ],
115
+ },
116
+ {
117
+ name: 'default.date_dim.dateint',
118
+ type: 'timestamp',
119
+ path: [
120
+ 'default.repair_order_details.repair_order_id',
121
+ 'default.repair_order.hard_hat_id',
122
+ 'default.hard_hat.hire_date',
123
+ ],
124
+ },
125
+ {
126
+ name: 'default.date_dim.day',
127
+ type: 'int',
128
+ path: [
129
+ 'default.repair_order_details.repair_order_id',
130
+ 'default.repair_order.hard_hat_id',
131
+ 'default.hard_hat.birth_date',
132
+ ],
133
+ },
134
+ {
135
+ name: 'default.date_dim.day',
136
+ type: 'int',
137
+ path: [
138
+ 'default.repair_order_details.repair_order_id',
139
+ 'default.repair_order.hard_hat_id',
140
+ 'default.hard_hat.hire_date',
141
+ ],
142
+ },
143
+ {
144
+ name: 'default.date_dim.month',
145
+ type: 'int',
146
+ path: [
147
+ 'default.repair_order_details.repair_order_id',
148
+ 'default.repair_order.hard_hat_id',
149
+ 'default.hard_hat.birth_date',
150
+ ],
151
+ },
152
+ {
153
+ name: 'default.date_dim.month',
154
+ type: 'int',
155
+ path: [
156
+ 'default.repair_order_details.repair_order_id',
157
+ 'default.repair_order.hard_hat_id',
158
+ 'default.hard_hat.hire_date',
159
+ ],
160
+ },
161
+ {
162
+ name: 'default.date_dim.year',
163
+ type: 'int',
164
+ path: [
165
+ 'default.repair_order_details.repair_order_id',
166
+ 'default.repair_order.hard_hat_id',
167
+ 'default.hard_hat.birth_date',
168
+ ],
169
+ },
170
+ {
171
+ name: 'default.date_dim.year',
172
+ type: 'int',
173
+ path: [
174
+ 'default.repair_order_details.repair_order_id',
175
+ 'default.repair_order.hard_hat_id',
176
+ 'default.hard_hat.hire_date',
177
+ ],
178
+ },
179
+ {
180
+ name: 'default.hard_hat.address',
181
+ type: 'string',
182
+ path: [
183
+ 'default.repair_order_details.repair_order_id',
184
+ 'default.repair_order.hard_hat_id',
185
+ ],
186
+ },
187
+ {
188
+ name: 'default.hard_hat.birth_date',
189
+ type: 'date',
190
+ path: [
191
+ 'default.repair_order_details.repair_order_id',
192
+ 'default.repair_order.hard_hat_id',
193
+ ],
194
+ },
195
+ {
196
+ name: 'default.hard_hat.city',
197
+ type: 'string',
198
+ path: [
199
+ 'default.repair_order_details.repair_order_id',
200
+ 'default.repair_order.hard_hat_id',
201
+ ],
202
+ },
203
+ {
204
+ name: 'default.hard_hat.contractor_id',
205
+ type: 'int',
206
+ path: [
207
+ 'default.repair_order_details.repair_order_id',
208
+ 'default.repair_order.hard_hat_id',
209
+ ],
210
+ },
211
+ {
212
+ name: 'default.hard_hat.country',
213
+ type: 'string',
214
+ path: [
215
+ 'default.repair_order_details.repair_order_id',
216
+ 'default.repair_order.hard_hat_id',
217
+ ],
218
+ },
219
+ {
220
+ name: 'default.hard_hat.first_name',
221
+ type: 'string',
222
+ path: [
223
+ 'default.repair_order_details.repair_order_id',
224
+ 'default.repair_order.hard_hat_id',
225
+ ],
226
+ },
227
+ {
228
+ name: 'default.hard_hat.hard_hat_id',
229
+ type: 'int',
230
+ path: [
231
+ 'default.repair_order_details.repair_order_id',
232
+ 'default.repair_order.hard_hat_id',
233
+ ],
234
+ },
235
+ {
236
+ name: 'default.hard_hat.hire_date',
237
+ type: 'date',
238
+ path: [
239
+ 'default.repair_order_details.repair_order_id',
240
+ 'default.repair_order.hard_hat_id',
241
+ ],
242
+ },
243
+ {
244
+ name: 'default.hard_hat.last_name',
245
+ type: 'string',
246
+ path: [
247
+ 'default.repair_order_details.repair_order_id',
248
+ 'default.repair_order.hard_hat_id',
249
+ ],
250
+ },
251
+ {
252
+ name: 'default.hard_hat.manager',
253
+ type: 'int',
254
+ path: [
255
+ 'default.repair_order_details.repair_order_id',
256
+ 'default.repair_order.hard_hat_id',
257
+ ],
258
+ },
259
+ {
260
+ name: 'default.hard_hat.postal_code',
261
+ type: 'string',
262
+ path: [
263
+ 'default.repair_order_details.repair_order_id',
264
+ 'default.repair_order.hard_hat_id',
265
+ ],
266
+ },
267
+ {
268
+ name: 'default.hard_hat.state',
269
+ type: 'string',
270
+ path: [
271
+ 'default.repair_order_details.repair_order_id',
272
+ 'default.repair_order.hard_hat_id',
273
+ ],
274
+ },
275
+ {
276
+ name: 'default.hard_hat.title',
277
+ type: 'string',
278
+ path: [
279
+ 'default.repair_order_details.repair_order_id',
280
+ 'default.repair_order.hard_hat_id',
281
+ ],
282
+ },
283
+ ],
284
+ },
285
+ };
286
+
287
+ it('renders the NodeInfo tab correctly for a metric node', async () => {
288
+ const djClient = mockDJClient();
289
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
290
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
291
+ mocks.mockMetricNodeJson,
292
+ );
293
+ const element = (
294
+ <DJClientContext.Provider value={djClient}>
295
+ <NodePage {...defaultProps} />
296
+ </DJClientContext.Provider>
297
+ );
298
+ const { container } = render(
299
+ <MemoryRouter initialEntries={['/nodes/default.num_repair_orders/info']}>
300
+ <Routes>
301
+ <Route path="nodes/:name/:tab" element={element} />
302
+ </Routes>
303
+ </MemoryRouter>,
304
+ );
305
+
306
+ await waitFor(() => {
307
+ expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
308
+ 'default.num_repair_orders',
309
+ );
310
+
311
+ expect(
312
+ screen.getByRole('dialog', { name: 'NodeName' }),
313
+ ).toHaveTextContent('default.num_repair_orders');
314
+
315
+ expect(screen.getByRole('button', { name: 'Info' })).toBeInTheDocument();
316
+ expect(
317
+ screen.getByRole('dialog', { name: 'Description' }),
318
+ ).toHaveTextContent('Number of repair orders');
319
+
320
+ expect(screen.getByRole('dialog', { name: 'Version' })).toHaveTextContent(
321
+ 'v1.0',
322
+ );
323
+
324
+ expect(
325
+ screen.getByRole('dialog', { name: 'NodeStatus' }),
326
+ ).toBeInTheDocument();
327
+
328
+ expect(screen.getByRole('dialog', { name: 'Tags' })).toHaveTextContent(
329
+ 'Purpose',
330
+ );
331
+
332
+ expect(
333
+ screen.getByRole('dialog', { name: 'RequiredDimensions' }),
334
+ ).toHaveTextContent('');
335
+
336
+ expect(
337
+ screen.getByRole('dialog', { name: 'DisplayName' }),
338
+ ).toHaveTextContent('Default: Num Repair Orders');
339
+
340
+ expect(
341
+ screen.getByRole('dialog', { name: 'NodeType' }),
342
+ ).toHaveTextContent('metric');
343
+
344
+ expect(
345
+ container.getElementsByClassName('language-sql'),
346
+ ).toMatchSnapshot();
347
+ });
348
+ }, 60000);
349
+
350
+ it('renders the NodeInfo tab correctly for cube nodes', async () => {
351
+ const djClient = mockDJClient();
352
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockCubeNode);
353
+ djClient.DataJunctionAPI.cube.mockReturnValue(mocks.mockCubesCube);
354
+ const element = (
355
+ <DJClientContext.Provider value={djClient}>
356
+ <NodePage {...defaultProps} />
357
+ </DJClientContext.Provider>
358
+ );
359
+ const { container } = render(
360
+ <MemoryRouter initialEntries={['/nodes/default.repair_orders_cube']}>
361
+ <Routes>
362
+ <Route path="nodes/:name" element={element} />
363
+ </Routes>
364
+ </MemoryRouter>,
365
+ );
366
+
367
+ await waitFor(() => {
368
+ expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
369
+ 'default.repair_orders_cube',
370
+ );
371
+ userEvent.click(screen.getByRole('button', { name: 'Info' }));
372
+
373
+ expect(
374
+ screen.getByRole('dialog', { name: 'NodeName' }),
375
+ ).toHaveTextContent('default.repair_orders_cube');
376
+
377
+ expect(screen.getByRole('button', { name: 'Info' })).toBeInTheDocument();
378
+ expect(
379
+ screen.getByRole('dialog', { name: 'Description' }),
380
+ ).toHaveTextContent('Repair Orders');
381
+
382
+ expect(screen.getByRole('dialog', { name: 'Version' })).toHaveTextContent(
383
+ 'v1.0',
384
+ );
385
+
386
+ expect(
387
+ screen.getByRole('dialog', { name: 'PrimaryKey' }),
388
+ ).toHaveTextContent('');
389
+
390
+ expect(
391
+ screen.getByRole('dialog', { name: 'DisplayName' }),
392
+ ).toHaveTextContent('Default: Repair Orders Cube');
393
+
394
+ expect(
395
+ screen.getByRole('dialog', { name: 'NodeType' }),
396
+ ).toHaveTextContent('cube');
397
+
398
+ expect(
399
+ screen.getByRole('dialog', { name: 'NodeType' }),
400
+ ).toHaveTextContent('cube');
401
+
402
+ expect(screen.getByText('Cube Elements')).toBeInTheDocument();
403
+ screen
404
+ .getAllByRole('cell', { name: 'CubeElement' })
405
+ .map(cube => cube.hasAttribute('a'));
406
+ });
407
+ }, 60000);
408
+
409
+ it('renders the NodeColumns tab correctly', async () => {
410
+ const djClient = mockDJClient();
411
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
412
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
413
+ mocks.mockMetricNodeJson,
414
+ );
415
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
416
+ djClient.DataJunctionAPI.attributes.mockReturnValue(mocks.attributes);
417
+ djClient.DataJunctionAPI.dimensions.mockReturnValue(mocks.dimensions);
418
+ djClient.DataJunctionAPI.engines.mockReturnValue([]);
419
+ djClient.DataJunctionAPI.setPartition.mockReturnValue({
420
+ status: 200,
421
+ json: { message: '' },
422
+ });
423
+
424
+ const element = (
425
+ <DJClientContext.Provider value={djClient}>
426
+ <NodePage />
427
+ </DJClientContext.Provider>
428
+ );
429
+ render(
430
+ <MemoryRouter
431
+ initialEntries={['/nodes/default.num_repair_orders/columns']}
432
+ >
433
+ <Routes>
434
+ <Route path="nodes/:name/:tab" element={element} />
435
+ </Routes>
436
+ </MemoryRouter>,
437
+ );
438
+ await waitFor(() => {
439
+ expect(djClient.DataJunctionAPI.columns).toHaveBeenCalledWith(
440
+ mocks.mockMetricNode,
441
+ );
442
+ expect(
443
+ screen.getByRole('columnheader', { name: 'ColumnName' }),
444
+ ).toHaveTextContent('default_DOT_avg_repair_price');
445
+ expect(
446
+ screen.getByRole('columnheader', { name: 'ColumnDisplayName' }),
447
+ ).toHaveTextContent('Default DOT avg repair price');
448
+ expect(
449
+ screen.getByRole('columnheader', { name: 'ColumnType' }),
450
+ ).toHaveTextContent('double');
451
+
452
+ // check that the edit column popover can be clicked
453
+ const editColumnPopover = screen.getByRole('button', {
454
+ name: 'EditColumn',
455
+ });
456
+ expect(editColumnPopover).toBeInTheDocument();
457
+ fireEvent.click(editColumnPopover);
458
+ expect(
459
+ screen.getByRole('button', { name: 'SaveEditColumn' }),
460
+ ).toBeInTheDocument();
461
+
462
+ // check that the link dimension popover can be clicked
463
+ const linkDimensionPopover = screen.getByRole('button', {
464
+ name: 'LinkDimension',
465
+ });
466
+ expect(linkDimensionPopover).toBeInTheDocument();
467
+ fireEvent.click(linkDimensionPopover);
468
+ expect(
469
+ screen.getByRole('button', { name: 'SaveLinkDimension' }),
470
+ ).toBeInTheDocument();
471
+
472
+ // check that the set column partition popover can be clicked
473
+ const partitionColumnPopover = screen.getByRole('button', {
474
+ name: 'PartitionColumn',
475
+ });
476
+ expect(partitionColumnPopover).toBeInTheDocument();
477
+ fireEvent.click(partitionColumnPopover);
478
+ const savePartition = screen.getByRole('button', {
479
+ name: 'SaveEditColumn',
480
+ });
481
+ expect(savePartition).toBeInTheDocument();
482
+ fireEvent.click(savePartition);
483
+ expect(screen.getByText('Saved!'));
484
+ });
485
+ }, 60000);
486
+ // check compiled SQL on nodeInfo page
487
+
488
+ it('renders the NodeHistory tab correctly', async () => {
489
+ const djClient = mockDJClient();
490
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
491
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
492
+ mocks.mockMetricNodeJson,
493
+ );
494
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
495
+ djClient.DataJunctionAPI.history.mockReturnValue(mocks.metricNodeHistory);
496
+ djClient.DataJunctionAPI.revisions.mockReturnValue(
497
+ mocks.metricNodeRevisions,
498
+ );
499
+
500
+ const element = (
501
+ <DJClientContext.Provider value={djClient}>
502
+ <NodePage />
503
+ </DJClientContext.Provider>
504
+ );
505
+ const { container } = render(
506
+ <MemoryRouter
507
+ initialEntries={['/nodes/default.num_repair_orders/history']}
508
+ >
509
+ <Routes>
510
+ <Route path="nodes/:name/:tab" element={element} />
511
+ </Routes>
512
+ </MemoryRouter>,
513
+ );
514
+ await waitFor(async () => {
515
+ fireEvent.click(screen.getByRole('button', { name: 'History' }));
516
+ expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
517
+ mocks.mockMetricNode.name,
518
+ );
519
+ expect(djClient.DataJunctionAPI.history).toHaveBeenCalledWith(
520
+ 'node',
521
+ mocks.mockMetricNode.name,
522
+ );
523
+ expect(
524
+ screen.getByRole('list', { name: 'Activity' }),
525
+ ).toBeInTheDocument();
526
+ screen
527
+ .queryAllByRole('cell', {
528
+ name: 'HistoryAttribute',
529
+ })
530
+ .forEach(cell => expect(cell).toHaveTextContent(/Set col1 as /));
531
+
532
+ screen
533
+ .queryAllByRole('cell', {
534
+ name: 'HistoryCreateLink',
535
+ })
536
+ .forEach(cell =>
537
+ expect(cell).toHaveTextContent(
538
+ 'Linked col1 todefault.hard_hat viahard_hat_id',
539
+ ),
540
+ );
541
+
542
+ screen
543
+ .queryAllByRole('cell', {
544
+ name: 'HistoryCreateMaterialization',
545
+ })
546
+ .forEach(cell =>
547
+ expect(cell).toHaveTextContent(
548
+ 'Initialized materialization some_random_materialization',
549
+ ),
550
+ );
551
+
552
+ screen
553
+ .queryAllByRole('cell', {
554
+ name: 'HistoryNodeStatusChange',
555
+ })
556
+ .forEach(cell =>
557
+ expect(cell).toHaveTextContent(
558
+ 'Status changed from valid to invalid Caused by a change in upstream default.repair_order_details',
559
+ ),
560
+ );
561
+ });
562
+ });
563
+
564
+ it('renders compiled sql correctly', async () => {
565
+ const djClient = mockDJClient();
566
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockTransformNode);
567
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
568
+ djClient.DataJunctionAPI.compiledSql.mockReturnValue('select 1');
569
+
570
+ const element = (
571
+ <DJClientContext.Provider value={djClient}>
572
+ <NodePage />
573
+ </DJClientContext.Provider>
574
+ );
575
+ render(
576
+ <MemoryRouter initialEntries={[`/nodes/${mocks.mockTransformNode.name}`]}>
577
+ <Routes>
578
+ <Route path="nodes/:name" element={element} />
579
+ </Routes>
580
+ </MemoryRouter>,
581
+ );
582
+ await waitFor(() => {
583
+ fireEvent.click(screen.getByRole('checkbox', { name: 'ToggleSwitch' }));
584
+ expect(djClient.DataJunctionAPI.compiledSql).toHaveBeenCalledWith(
585
+ mocks.mockTransformNode.name,
586
+ );
587
+ });
588
+ });
589
+
590
+ it('renders an empty NodeMaterialization tab correctly', async () => {
591
+ const djClient = mockDJClient();
592
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
593
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
594
+ mocks.mockMetricNodeJson,
595
+ );
596
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
597
+ djClient.DataJunctionAPI.materializations.mockReturnValue([]);
598
+ djClient.DataJunctionAPI.availabilityStates.mockReturnValue([]);
599
+
600
+ const element = (
601
+ <DJClientContext.Provider value={djClient}>
602
+ <NodePage />
603
+ </DJClientContext.Provider>
604
+ );
605
+ render(
606
+ <MemoryRouter
607
+ initialEntries={['/nodes/default.num_repair_orders/materializations']}
608
+ >
609
+ <Routes>
610
+ <Route path="nodes/:name/:tab" element={element} />
611
+ </Routes>
612
+ </MemoryRouter>,
613
+ );
614
+ await waitFor(
615
+ () => {
616
+ fireEvent.click(
617
+ screen.getByRole('button', { name: 'Materializations' }),
618
+ );
619
+ expect(djClient.DataJunctionAPI.materializations).toHaveBeenCalledWith(
620
+ mocks.mockMetricNode.name,
621
+ );
622
+ screen.getByText(
623
+ 'No materialization workflows configured for this revision.',
624
+ );
625
+ },
626
+ { timeout: 5000 },
627
+ );
628
+ });
629
+
630
+ it('renders the NodeMaterialization tab with materializations correctly', async () => {
631
+ const djClient = mockDJClient();
632
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockTransformNode);
633
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
634
+ mocks.mockMetricNodeJson,
635
+ );
636
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
637
+ djClient.DataJunctionAPI.materializations.mockReturnValue(
638
+ mocks.nodeMaterializations,
639
+ );
640
+ djClient.DataJunctionAPI.availabilityStates.mockReturnValue([]);
641
+
642
+ djClient.DataJunctionAPI.materializationInfo.mockReturnValue(
643
+ mocks.materializationInfo,
644
+ );
645
+
646
+ const element = (
647
+ <DJClientContext.Provider value={djClient}>
648
+ <NodePage />
649
+ </DJClientContext.Provider>
650
+ );
651
+ render(
652
+ <MemoryRouter
653
+ initialEntries={[
654
+ '/nodes/default.repair_order_transform/materializations',
655
+ ]}
656
+ >
657
+ <Routes>
658
+ <Route path="nodes/:name/:tab" element={element} />
659
+ </Routes>
660
+ </MemoryRouter>,
661
+ );
662
+ await waitFor(
663
+ () => {
664
+ fireEvent.click(
665
+ screen.getByRole('button', { name: 'Materializations' }),
666
+ );
667
+ expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
668
+ mocks.mockTransformNode.name,
669
+ );
670
+ expect(djClient.DataJunctionAPI.materializations).toHaveBeenCalledWith(
671
+ mocks.mockTransformNode.name,
672
+ );
673
+ },
674
+ { timeout: 3000 },
675
+ );
676
+ }, 60000);
677
+
678
+ it('renders the NodeValidate tab', async () => {
679
+ const djClient = mockDJClient();
680
+ window.scrollTo = jest.fn();
681
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
682
+ djClient.DataJunctionAPI.nodeDimensions.mockReturnValue([]);
683
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
684
+ mocks.mockMetricNodeJson,
685
+ );
686
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
687
+ djClient.DataJunctionAPI.sql.mockReturnValue({
688
+ sql: 'SELECT * FROM testNode',
689
+ });
690
+ const streamNodeData = {
691
+ onmessage: jest.fn(),
692
+ onerror: jest.fn(),
693
+ close: jest.fn(),
694
+ };
695
+ djClient.DataJunctionAPI.streamNodeData.mockResolvedValue(streamNodeData);
696
+ djClient.DataJunctionAPI.streamNodeData.mockResolvedValueOnce({
697
+ state: 'FINISHED',
698
+ results: [
699
+ {
700
+ columns: [{ name: 'column1' }, { name: 'column2' }],
701
+ rows: [
702
+ [1, 'value1'],
703
+ [2, 'value2'],
704
+ ],
705
+ },
706
+ ],
707
+ });
708
+
709
+ const element = (
710
+ <DJClientContext.Provider value={djClient}>
711
+ <NodePage />
712
+ </DJClientContext.Provider>
713
+ );
714
+ render(
715
+ <MemoryRouter
716
+ initialEntries={['/nodes/default.num_repair_orders/validate']}
717
+ >
718
+ <Routes>
719
+ <Route path="nodes/:name/:tab" element={element} />
720
+ </Routes>
721
+ </MemoryRouter>,
722
+ );
723
+
724
+ await waitFor(() => {
725
+ expect(screen.getByText('Group By')).toBeInTheDocument();
726
+ expect(screen.getByText('Add Filters')).toBeInTheDocument();
727
+ expect(screen.getByText('Generated Query')).toBeInTheDocument();
728
+ expect(screen.getByText('Results')).toBeInTheDocument();
729
+ });
730
+ // Click on the 'Validate' tab
731
+ fireEvent.click(screen.getByRole('button', { name: '► Validate' }));
732
+
733
+ await waitFor(() => {
734
+ expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
735
+ mocks.mockMetricNode.name,
736
+ );
737
+ expect(djClient.DataJunctionAPI.sql).toHaveBeenCalledWith(
738
+ mocks.mockMetricNode.name,
739
+ { dimensions: [], filters: [] },
740
+ );
741
+ expect(djClient.DataJunctionAPI.nodeDimensions).toHaveBeenCalledWith(
742
+ mocks.mockMetricNode.name,
743
+ );
744
+ });
745
+
746
+ // Click on 'Run' to run the node query
747
+ const runButton = screen.getByText('► Run');
748
+ fireEvent.click(runButton);
749
+
750
+ await waitFor(() => {
751
+ expect(djClient.DataJunctionAPI.streamNodeData).toHaveBeenCalledWith(
752
+ mocks.mockMetricNode.name,
753
+ { dimensions: [], filters: [] },
754
+ );
755
+ expect(streamNodeData.onmessage).toBeDefined();
756
+ expect(streamNodeData.onerror).toBeDefined();
757
+ });
758
+
759
+ const infoTab = screen.getByRole('button', { name: 'QueryInfo' });
760
+ const resultsTab = screen.getByText('Results');
761
+
762
+ // Initially, the Results tab should be active
763
+ expect(resultsTab).toHaveClass('active');
764
+ expect(infoTab).not.toHaveClass('active');
765
+
766
+ // Click on the Info tab first
767
+ fireEvent.click(infoTab);
768
+
769
+ await waitFor(() => {
770
+ // Now, the Info tab should be active
771
+ expect(infoTab).toHaveClass('active');
772
+ expect(resultsTab).not.toHaveClass('active');
773
+ });
774
+
775
+ // Click on the Results tab
776
+ fireEvent.click(resultsTab);
777
+
778
+ await waitFor(() => {
779
+ // Now, the Results tab should be active again
780
+ expect(resultsTab).toHaveClass('active');
781
+ expect(infoTab).not.toHaveClass('active');
782
+ });
783
+ });
784
+
785
+ it('renders a NodeColumnLineage tab correctly', async () => {
786
+ const djClient = mockDJClient();
787
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
788
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
789
+ mocks.mockMetricNodeJson,
790
+ );
791
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
792
+ djClient.DataJunctionAPI.node_lineage.mockReturnValue(
793
+ mocks.mockNodeLineage,
794
+ );
795
+
796
+ const element = (
797
+ <DJClientContext.Provider value={djClient}>
798
+ <NodePage />
799
+ </DJClientContext.Provider>
800
+ );
801
+ render(
802
+ <MemoryRouter
803
+ initialEntries={['/nodes/default.num_repair_orders/lineage']}
804
+ >
805
+ <Routes>
806
+ <Route path="nodes/:name/:tab" element={element} />
807
+ </Routes>
808
+ </MemoryRouter>,
809
+ );
810
+ await waitFor(() => {
811
+ fireEvent.click(screen.getByRole('button', { name: 'Lineage' }));
812
+ expect(djClient.DataJunctionAPI.node_lineage).toHaveBeenCalledWith(
813
+ mocks.mockMetricNode.name,
814
+ );
815
+ });
816
+ });
817
+
818
+ it('renders a NodeGraph tab correctly', async () => {
819
+ const djClient = mockDJClient();
820
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
821
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
822
+ mocks.mockMetricNodeJson,
823
+ );
824
+ djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
825
+
826
+ const element = (
827
+ <DJClientContext.Provider value={djClient}>
828
+ <NodePage />
829
+ </DJClientContext.Provider>
830
+ );
831
+ render(
832
+ <MemoryRouter initialEntries={['/nodes/default.num_repair_orders/graph']}>
833
+ <Routes>
834
+ <Route path="nodes/:name/:tab" element={element} />
835
+ </Routes>
836
+ </MemoryRouter>,
837
+ );
838
+ await waitFor(() => {
839
+ fireEvent.click(screen.getByRole('button', { name: 'Graph' }));
840
+ expect(djClient.DataJunctionAPI.node_dag).toHaveBeenCalledWith(
841
+ mocks.mockMetricNode.name,
842
+ );
843
+ });
844
+ });
845
+
846
+ it('renders Linked Nodes tab correctly', async () => {
847
+ const djClient = mockDJClient();
848
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockDimensionNode);
849
+ djClient.DataJunctionAPI.nodesWithDimension.mockReturnValue(
850
+ mocks.mockLinkedNodes,
851
+ );
852
+
853
+ const element = (
854
+ <DJClientContext.Provider value={djClient}>
855
+ <NodePage />
856
+ </DJClientContext.Provider>
857
+ );
858
+ render(
859
+ <MemoryRouter initialEntries={['/nodes/default.dispatcher/linked']}>
860
+ <Routes>
861
+ <Route path="nodes/:name/:tab" element={element} />
862
+ </Routes>
863
+ </MemoryRouter>,
864
+ );
865
+ await waitFor(() => {
866
+ fireEvent.click(screen.getByRole('button', { name: 'Linked Nodes' }));
867
+ expect(djClient.DataJunctionAPI.nodesWithDimension).toHaveBeenCalledWith(
868
+ mocks.mockDimensionNode.name,
869
+ );
870
+ });
871
+ });
872
+ });