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