datajunction-ui 0.0.1-rc.9 → 0.0.2

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,1559 @@
1
+ import { DataJunctionAPI } from '../DJService';
2
+ import { mocks } from '../../../mocks/mockNodes';
3
+
4
+ describe('DataJunctionAPI', () => {
5
+ let originalEventSource;
6
+
7
+ const DJ_URL = 'http://localhost:8000';
8
+ process.env.REACT_APP_DJ_URL = DJ_URL;
9
+
10
+ beforeEach(() => {
11
+ fetch.resetMocks();
12
+ originalEventSource = global.EventSource;
13
+ global.EventSource = jest.fn();
14
+ });
15
+
16
+ afterEach(() => {
17
+ global.EventSource = originalEventSource;
18
+ });
19
+
20
+ it('calls whoami correctly', async () => {
21
+ fetch.mockResponseOnce(JSON.stringify({}));
22
+ await DataJunctionAPI.whoami();
23
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/whoami/`, {
24
+ credentials: 'include',
25
+ });
26
+ });
27
+
28
+ it('calls logout correctly', async () => {
29
+ fetch.mockResponseOnce(JSON.stringify({}));
30
+ await DataJunctionAPI.logout();
31
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/logout/`, {
32
+ credentials: 'include',
33
+ method: 'POST',
34
+ });
35
+ });
36
+
37
+ it('calls catalogs correctly', async () => {
38
+ fetch.mockResponseOnce(JSON.stringify({}));
39
+ await DataJunctionAPI.catalogs();
40
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/catalogs`, {
41
+ credentials: 'include',
42
+ });
43
+ });
44
+
45
+ it('calls engines correctly', async () => {
46
+ fetch.mockResponseOnce(JSON.stringify({}));
47
+ await DataJunctionAPI.engines();
48
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/engines`, {
49
+ credentials: 'include',
50
+ });
51
+ });
52
+
53
+ it('calls node correctly', async () => {
54
+ fetch.mockResponseOnce(JSON.stringify(mocks.mockMetricNode));
55
+ const nodeData = await DataJunctionAPI.node(mocks.mockMetricNode.name);
56
+ expect(fetch).toHaveBeenCalledWith(
57
+ `${DJ_URL}/nodes/${mocks.mockMetricNode.name}/`,
58
+ {
59
+ credentials: 'include',
60
+ },
61
+ );
62
+ expect(nodeData.name).toEqual(mocks.mockMetricNode.name);
63
+ expect(nodeData.display_name).toEqual(mocks.mockMetricNode.display_name);
64
+ expect(nodeData.type).toEqual(mocks.mockMetricNode.type);
65
+ expect(nodeData.primary_key).toEqual([]);
66
+ });
67
+
68
+ it('calls nodeDetails correctly', async () => {
69
+ fetch.mockResponseOnce(JSON.stringify([mocks.mockMetricNode]));
70
+ const nodeData = await DataJunctionAPI.nodeDetails();
71
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/details/`, {
72
+ credentials: 'include',
73
+ });
74
+ expect(nodeData).toEqual([mocks.mockMetricNode]);
75
+ });
76
+
77
+ it('calls validate correctly', async () => {
78
+ fetch.mockResponseOnce(JSON.stringify([mocks.mockMetricNode]));
79
+ await DataJunctionAPI.validateNode(
80
+ 'metric',
81
+ 'default.num_repair_orders',
82
+ 'aa',
83
+ 'desc',
84
+ 'select 1',
85
+ );
86
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/validate`, {
87
+ credentials: 'include',
88
+ body: JSON.stringify({
89
+ name: 'default.num_repair_orders',
90
+ display_name: 'aa',
91
+ description: 'desc',
92
+ query: 'select 1',
93
+ type: 'metric',
94
+ mode: 'published',
95
+ }),
96
+
97
+ headers: {
98
+ 'Content-Type': 'application/json',
99
+ },
100
+ method: 'POST',
101
+ });
102
+ });
103
+
104
+ it('calls registerTable correctly', async () => {
105
+ fetch.mockResponseOnce(JSON.stringify([mocks.mockMetricNode]));
106
+ await DataJunctionAPI.registerTable('default', 'xyz', 'abc');
107
+ expect(fetch).toHaveBeenCalledWith(
108
+ `${DJ_URL}/register/table/default/xyz/abc`,
109
+ {
110
+ credentials: 'include',
111
+ headers: {
112
+ 'Content-Type': 'application/json',
113
+ },
114
+ method: 'POST',
115
+ },
116
+ );
117
+ });
118
+
119
+ it('node with errors are handled', async () => {
120
+ const mockNotFound = { message: 'node not found' };
121
+ fetch.mockResponseOnce(JSON.stringify(mockNotFound));
122
+ const nodeData = await DataJunctionAPI.node(mocks.mockMetricNode.name);
123
+ expect(nodeData.message).toEqual(mockNotFound.message);
124
+ });
125
+
126
+ it('calls nodes correctly', async () => {
127
+ const prefix = 'samplePrefix';
128
+ fetch.mockResponseOnce(JSON.stringify({}));
129
+ await DataJunctionAPI.nodes(prefix);
130
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/?prefix=${prefix}`, {
131
+ credentials: 'include',
132
+ });
133
+ });
134
+
135
+ it('calls nodesWithType correctly', async () => {
136
+ const nodeType = 'transform';
137
+ fetch.mockResponseOnce(JSON.stringify({}));
138
+ await DataJunctionAPI.nodesWithType(nodeType);
139
+ expect(fetch).toHaveBeenCalledWith(
140
+ `${DJ_URL}/nodes/?node_type=${nodeType}`,
141
+ {
142
+ credentials: 'include',
143
+ },
144
+ );
145
+ });
146
+
147
+ it('calls createNode correctly', async () => {
148
+ const sampleArgs = [
149
+ 'type',
150
+ 'name',
151
+ 'display_name',
152
+ 'description',
153
+ 'query',
154
+ 'mode',
155
+ 'namespace',
156
+ 'primary_key',
157
+ ];
158
+ fetch.mockResponseOnce(JSON.stringify({}));
159
+ await DataJunctionAPI.createNode(...sampleArgs);
160
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${sampleArgs[0]}`, {
161
+ method: 'POST',
162
+ headers: {
163
+ 'Content-Type': 'application/json',
164
+ },
165
+ body: JSON.stringify({
166
+ name: sampleArgs[1],
167
+ display_name: sampleArgs[2],
168
+ description: sampleArgs[3],
169
+ query: sampleArgs[4],
170
+ mode: sampleArgs[5],
171
+ namespace: sampleArgs[6],
172
+ primary_key: sampleArgs[7],
173
+ metric_metadata: null,
174
+ }),
175
+ credentials: 'include',
176
+ });
177
+ });
178
+
179
+ it('calls patchNode correctly', async () => {
180
+ const sampleArgs = [
181
+ 'name',
182
+ 'display_name',
183
+ 'description',
184
+ 'query',
185
+ 'mode',
186
+ 'primary_key',
187
+ 'neutral',
188
+ '',
189
+ ];
190
+ fetch.mockResponseOnce(JSON.stringify({}));
191
+ await DataJunctionAPI.patchNode(...sampleArgs);
192
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${sampleArgs[0]}`, {
193
+ method: 'PATCH',
194
+ headers: {
195
+ 'Content-Type': 'application/json',
196
+ },
197
+ body: JSON.stringify({
198
+ display_name: sampleArgs[1],
199
+ description: sampleArgs[2],
200
+ query: sampleArgs[3],
201
+ mode: sampleArgs[4],
202
+ primary_key: sampleArgs[5],
203
+ metric_metadata: {
204
+ direction: 'neutral',
205
+ unit: '',
206
+ significant_digits: null,
207
+ },
208
+ }),
209
+ credentials: 'include',
210
+ });
211
+
212
+ fetch.mockResponseOnce([
213
+ JSON.stringify({ message: 'Update failed' }),
214
+ { status: 200 },
215
+ ]);
216
+ const response = await DataJunctionAPI.patchNode(...sampleArgs);
217
+ expect(response).toEqual({
218
+ json: { message: 'Update failed' },
219
+ status: 500,
220
+ });
221
+ });
222
+
223
+ it('calls createCube correctly', async () => {
224
+ const sampleArgs = [
225
+ 'default.node_name',
226
+ 'Node Display Name',
227
+ 'Some readable description',
228
+ 'draft',
229
+ ['default.num_repair_orders'],
230
+ [
231
+ 'default.date_dim.year',
232
+ 'default.date_dim.month',
233
+ 'default.date_dim.day',
234
+ ],
235
+ [],
236
+ ];
237
+ fetch.mockResponseOnce(JSON.stringify({}));
238
+ await DataJunctionAPI.createCube(...sampleArgs);
239
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/cube`, {
240
+ method: 'POST',
241
+ headers: {
242
+ 'Content-Type': 'application/json',
243
+ },
244
+ body: JSON.stringify({
245
+ name: sampleArgs[0],
246
+ display_name: sampleArgs[1],
247
+ description: sampleArgs[2],
248
+ metrics: sampleArgs[4],
249
+ dimensions: sampleArgs[5],
250
+ filters: sampleArgs[6],
251
+ mode: sampleArgs[3],
252
+ }),
253
+ credentials: 'include',
254
+ });
255
+ });
256
+
257
+ it('calls patchCube correctly', async () => {
258
+ const sampleArgs = [
259
+ 'default.node_name',
260
+ 'Node Display Name',
261
+ 'Some readable description',
262
+ 'draft',
263
+ ['default.num_repair_orders'],
264
+ [
265
+ 'default.date_dim.year',
266
+ 'default.date_dim.month',
267
+ 'default.date_dim.day',
268
+ ],
269
+ [],
270
+ ];
271
+ fetch.mockResponseOnce(JSON.stringify({}));
272
+ await DataJunctionAPI.patchCube(...sampleArgs);
273
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/default.node_name`, {
274
+ method: 'PATCH',
275
+ headers: {
276
+ 'Content-Type': 'application/json',
277
+ },
278
+ body: JSON.stringify({
279
+ display_name: sampleArgs[1],
280
+ description: sampleArgs[2],
281
+ metrics: sampleArgs[4],
282
+ dimensions: sampleArgs[5],
283
+ filters: sampleArgs[6],
284
+ mode: sampleArgs[3],
285
+ }),
286
+ credentials: 'include',
287
+ });
288
+ });
289
+
290
+ it('calls upstreams correctly', async () => {
291
+ const nodeName = 'sampleNode';
292
+ fetch.mockResponseOnce(JSON.stringify({}));
293
+ await DataJunctionAPI.upstreams(nodeName);
294
+ expect(fetch).toHaveBeenCalledWith(
295
+ `${DJ_URL}/nodes/${nodeName}/upstream/`,
296
+ {
297
+ credentials: 'include',
298
+ },
299
+ );
300
+ });
301
+
302
+ it('calls downstreams correctly', async () => {
303
+ const nodeName = 'sampleNode';
304
+ fetch.mockResponseOnce(JSON.stringify({}));
305
+ await DataJunctionAPI.downstreams(nodeName);
306
+ expect(fetch).toHaveBeenCalledWith(
307
+ `${DJ_URL}/nodes/${nodeName}/downstream/`,
308
+ {
309
+ credentials: 'include',
310
+ },
311
+ );
312
+ });
313
+
314
+ it('calls node_dag correctly', async () => {
315
+ const nodeName = 'sampleNode';
316
+ fetch.mockResponseOnce(JSON.stringify({}));
317
+ await DataJunctionAPI.node_dag(nodeName);
318
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${nodeName}/dag/`, {
319
+ credentials: 'include',
320
+ });
321
+ });
322
+
323
+ it('calls node_lineage correctly', async () => {
324
+ const nodeName = 'sampleNode';
325
+ fetch.mockResponseOnce(JSON.stringify({}));
326
+ await DataJunctionAPI.node_lineage(nodeName);
327
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${nodeName}/lineage/`, {
328
+ credentials: 'include',
329
+ });
330
+ });
331
+
332
+ it('calls metric correctly', async () => {
333
+ const metricName = 'sampleMetric';
334
+ fetch.mockResponseOnce(JSON.stringify({}));
335
+ await DataJunctionAPI.metric(metricName);
336
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/metrics/${metricName}/`, {
337
+ credentials: 'include',
338
+ });
339
+ });
340
+
341
+ it('calls metrics correctly', async () => {
342
+ fetch.mockResponseOnce(JSON.stringify({}));
343
+ await DataJunctionAPI.metrics('');
344
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/metrics/`, {
345
+ credentials: 'include',
346
+ });
347
+ });
348
+
349
+ it('calls listMetricMetadata correctly', async () => {
350
+ const nodeType = 'transform';
351
+ fetch.mockResponseOnce(JSON.stringify({}));
352
+ await DataJunctionAPI.listMetricMetadata();
353
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/metrics/metadata`, {
354
+ method: 'GET',
355
+ headers: { 'Content-Type': 'application/json' },
356
+ credentials: 'include',
357
+ });
358
+ });
359
+
360
+ it('calls namespaces correctly', async () => {
361
+ fetch.mockResponseOnce(JSON.stringify({}));
362
+ await DataJunctionAPI.namespaces();
363
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/namespaces/`, {
364
+ credentials: 'include',
365
+ });
366
+ });
367
+
368
+ it('calls clientCode correctly', async () => {
369
+ const name = 'sampleName';
370
+ fetch.mockResponseOnce(JSON.stringify({}));
371
+ await DataJunctionAPI.clientCode(name);
372
+ expect(fetch).toHaveBeenCalledWith(
373
+ `${DJ_URL}/datajunction-clients/python/new_node/${name}`,
374
+ {
375
+ credentials: 'include',
376
+ },
377
+ );
378
+ });
379
+
380
+ it('calls cube correctly', async () => {
381
+ const name = 'sampleCube';
382
+ fetch.mockResponseOnce(JSON.stringify({}));
383
+ await DataJunctionAPI.cube(name);
384
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/cubes/${name}/`, {
385
+ credentials: 'include',
386
+ });
387
+ });
388
+
389
+ it('calls commonDimensions correctly', async () => {
390
+ const metrics = ['metric1', 'metric2'];
391
+ const query = metrics.map(m => `metric=${m}`).join('&');
392
+ fetch.mockResponseOnce(JSON.stringify({}));
393
+ await DataJunctionAPI.commonDimensions(metrics);
394
+ expect(fetch).toHaveBeenCalledWith(
395
+ `${DJ_URL}/metrics/common/dimensions/?${query}`,
396
+ {
397
+ credentials: 'include',
398
+ },
399
+ );
400
+ });
401
+
402
+ it('calls history correctly', async () => {
403
+ const type = 'sampleType';
404
+ const name = 'sampleName';
405
+ const offset = 10;
406
+ const limit = 100;
407
+ fetch.mockResponseOnce(JSON.stringify({}));
408
+ await DataJunctionAPI.history(type, name, offset, limit);
409
+ expect(fetch).toHaveBeenCalledWith(
410
+ `${DJ_URL}/history?node=${name}&offset=${offset}&limit=${limit}`,
411
+ {
412
+ credentials: 'include',
413
+ },
414
+ );
415
+ });
416
+
417
+ it('calls revisions correctly', async () => {
418
+ const name = 'sampleName';
419
+ fetch.mockResponseOnce(JSON.stringify({}));
420
+ await DataJunctionAPI.revisions(name);
421
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${name}/revisions/`, {
422
+ credentials: 'include',
423
+ });
424
+ });
425
+
426
+ it('calls namespace correctly', async () => {
427
+ const nmspce = 'sampleNamespace';
428
+ fetch.mockResponseOnce(JSON.stringify({}));
429
+ await DataJunctionAPI.namespace(nmspce);
430
+ expect(fetch).toHaveBeenCalledWith(
431
+ `${DJ_URL}/namespaces/${nmspce}?edited_by=undefined&with_edited_by=true`,
432
+ {
433
+ credentials: 'include',
434
+ },
435
+ );
436
+ });
437
+
438
+ it('calls sql correctly', async () => {
439
+ const metric_name = 'sampleMetric';
440
+ const selection = { key: 'value' };
441
+ const params = new URLSearchParams(selection).toString();
442
+ fetch.mockResponseOnce(JSON.stringify({}));
443
+ await DataJunctionAPI.sql(metric_name, selection);
444
+ expect(fetch).toHaveBeenCalledWith(
445
+ `${DJ_URL}/sql/${metric_name}?${params}`,
446
+ {
447
+ credentials: 'include',
448
+ },
449
+ );
450
+ });
451
+
452
+ it('calls nodesWithDimension correctly', async () => {
453
+ const name = 'sampleName';
454
+ fetch.mockResponseOnce(JSON.stringify({}));
455
+ await DataJunctionAPI.nodesWithDimension(name);
456
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/dimensions/${name}/nodes/`, {
457
+ credentials: 'include',
458
+ });
459
+ });
460
+
461
+ it('calls materializations correctly', async () => {
462
+ const nodeName = 'default.sample_node';
463
+ const mockMaterializations = [
464
+ { name: 'materialization1' },
465
+ { name: 'materialization2' },
466
+ ];
467
+
468
+ // Mock the first fetch call to return the list of materializations
469
+ fetch.mockResponseOnce(JSON.stringify(mockMaterializations));
470
+
471
+ // Mock the subsequent fetch calls to return clientCode for each materialization
472
+ fetch.mockResponses(
473
+ ...mockMaterializations.map(mat => [
474
+ JSON.stringify('from dj import DJClient'),
475
+ ]),
476
+ );
477
+
478
+ const result = await DataJunctionAPI.materializations(nodeName);
479
+
480
+ // Check the first fetch call
481
+ expect(fetch).toHaveBeenCalledWith(
482
+ `${DJ_URL}/nodes/${nodeName}/materializations?show_inactive=true&include_all_revisions=true`,
483
+ {
484
+ credentials: 'include',
485
+ },
486
+ );
487
+
488
+ // Ensure the result contains the clientCode for each materialization
489
+ expect(result).toEqual(mockMaterializations);
490
+ });
491
+
492
+ it('calls columns correctly', async () => {
493
+ const sampleNode = {
494
+ name: 'sampleNode',
495
+ columns: [
496
+ { name: 'column1', dimension: { name: 'dimension1' } },
497
+ { name: 'column2', dimension: null },
498
+ { name: 'column3', dimension: { name: 'dimension2' } },
499
+ ],
500
+ };
501
+
502
+ // Mock the fetch calls to return clientCode for columns with a dimension
503
+ const mockClientCodeResponses = sampleNode.columns
504
+ .filter(col => col.dimension)
505
+ .map(col => [JSON.stringify({ clientCode: col.clientCode })]);
506
+
507
+ fetch.mockResponses(...mockClientCodeResponses);
508
+
509
+ const result = await DataJunctionAPI.columns(sampleNode);
510
+ expect(result).toEqual(sampleNode.columns);
511
+ });
512
+
513
+ it('calls sqls correctly', async () => {
514
+ const metricSelection = ['metric1'];
515
+ const dimensionSelection = ['dimension1'];
516
+ const filters = 'sampleFilter';
517
+ const params = new URLSearchParams();
518
+ metricSelection.forEach(metric => params.append('metrics', metric));
519
+ dimensionSelection.forEach(dimension =>
520
+ params.append('dimensions', dimension),
521
+ );
522
+ params.append('filters', filters);
523
+ fetch.mockResponseOnce(JSON.stringify({}));
524
+ await DataJunctionAPI.sqls(metricSelection, dimensionSelection, filters);
525
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/sql/?${params}`, {
526
+ credentials: 'include',
527
+ });
528
+ });
529
+
530
+ it('calls data correctly', async () => {
531
+ const metricSelection = ['metric1'];
532
+ const dimensionSelection = ['dimension1'];
533
+ const params = new URLSearchParams();
534
+ metricSelection.forEach(metric => params.append('metrics', metric));
535
+ dimensionSelection.forEach(dimension =>
536
+ params.append('dimensions', dimension),
537
+ );
538
+ fetch.mockResponseOnce(JSON.stringify({}));
539
+ await DataJunctionAPI.data(metricSelection, dimensionSelection);
540
+ expect(fetch).toHaveBeenCalledWith(
541
+ `${DJ_URL}/data/?${params}&limit=10000`,
542
+ {
543
+ credentials: 'include',
544
+ },
545
+ );
546
+ });
547
+
548
+ it('calls compiledSql correctly', async () => {
549
+ const node = 'sampleNode';
550
+ fetch.mockResponseOnce(JSON.stringify({}));
551
+ await DataJunctionAPI.compiledSql(node);
552
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/sql/${node}/`, {
553
+ credentials: 'include',
554
+ });
555
+ });
556
+
557
+ it('transforms node data correctly', async () => {
558
+ fetch.mockResolvedValueOnce({
559
+ json: () =>
560
+ Promise.resolve({
561
+ message: undefined,
562
+ columns: [
563
+ {
564
+ name: 'id',
565
+ attributes: [
566
+ {
567
+ attribute_type: {
568
+ name: 'primary_key',
569
+ },
570
+ },
571
+ ],
572
+ },
573
+ ],
574
+ }),
575
+ status: 200,
576
+ });
577
+
578
+ const nodeData = await DataJunctionAPI.node('sampleNodeName');
579
+ expect(nodeData.primary_key).toEqual(['id']);
580
+ });
581
+
582
+ it('calls stream correctly', () => {
583
+ const metricSelection = ['metric1', 'metric2'];
584
+ const dimensionSelection = ['dimension1', 'dimension2'];
585
+ const filters = 'sampleFilter';
586
+
587
+ DataJunctionAPI.stream(metricSelection, dimensionSelection, filters);
588
+
589
+ const params = new URLSearchParams();
590
+ metricSelection.map(metric => params.append('metrics', metric));
591
+ dimensionSelection.map(dimension => params.append('dimensions', dimension));
592
+ params.append('filters', filters);
593
+
594
+ expect(global.EventSource).toHaveBeenCalledWith(
595
+ `${DJ_URL}/stream/?${params}&limit=10000&async_=true`,
596
+ { withCredentials: true },
597
+ );
598
+ });
599
+
600
+ it('calls streamNodeData correctly', () => {
601
+ const nodes = ['transform1'];
602
+ const dimensionSelection = ['dimension1', 'dimension2'];
603
+ const filters = 'sampleFilter';
604
+
605
+ DataJunctionAPI.streamNodeData(nodes[0], {
606
+ dimensions: dimensionSelection,
607
+ filters: filters,
608
+ });
609
+
610
+ const params = new URLSearchParams();
611
+ params.append('dimensions', dimensionSelection, 'filters', filters);
612
+
613
+ expect(global.EventSource).toHaveBeenCalledWith(
614
+ `${DJ_URL}/stream/transform1?filters=sampleFilter&dimensions=dimension1&dimensions=dimension2&limit=1000&async_=true`,
615
+ { withCredentials: true },
616
+ );
617
+ });
618
+
619
+ it('calls nodeData correctly', () => {
620
+ const nodes = ['transform1'];
621
+ const dimensionSelection = ['dimension1', 'dimension2'];
622
+ const filters = 'sampleFilter';
623
+ fetch.mockResponseOnce(JSON.stringify({}));
624
+
625
+ DataJunctionAPI.nodeData(nodes[0], {
626
+ dimensions: dimensionSelection,
627
+ filters: filters,
628
+ });
629
+
630
+ const params = new URLSearchParams();
631
+ params.append('dimensions', dimensionSelection, 'filters', filters);
632
+
633
+ expect(fetch).toHaveBeenCalledWith(
634
+ `${DJ_URL}/data/transform1?filters=sampleFilter&dimensions=dimension1&dimensions=dimension2&limit=1000&async_=true`,
635
+ {
636
+ credentials: 'include',
637
+ headers: {
638
+ 'Cache-Control': 'max-age=86400',
639
+ },
640
+ },
641
+ );
642
+ });
643
+
644
+ it('calls dag correctly and processes response', async () => {
645
+ const mockResponse = [
646
+ {
647
+ name: 'namespace.node1',
648
+ parents: [{ name: 'parent1' }, { name: null }],
649
+ columns: [
650
+ {
651
+ dimension: { name: 'dimension1' },
652
+ name: 'col1',
653
+ type: 'type1',
654
+ attributes: [],
655
+ },
656
+ {
657
+ dimension: null,
658
+ name: 'col2',
659
+ type: 'type2',
660
+ attributes: [{ attribute_type: { name: 'primary_key' } }],
661
+ },
662
+ ],
663
+ table: 'table1',
664
+ schema_: 'schema1',
665
+ display_name: 'DisplayName1',
666
+ type: 'type1',
667
+ },
668
+ ];
669
+
670
+ fetch.mockResponseOnce(JSON.stringify(mockResponse));
671
+ const result = await DataJunctionAPI.dag();
672
+
673
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/`, {
674
+ credentials: 'include',
675
+ });
676
+
677
+ expect(result.edges).toEqual([
678
+ {
679
+ id: 'namespace.node1-parent1',
680
+ target: 'namespace.node1',
681
+ source: 'parent1',
682
+ animated: true,
683
+ markerEnd: { type: 'arrow' },
684
+ },
685
+ {
686
+ id: 'namespace.node1-dimension1',
687
+ target: 'namespace.node1',
688
+ source: 'dimension1',
689
+ draggable: true,
690
+ },
691
+ ]);
692
+
693
+ expect(result.nodes).toEqual([
694
+ {
695
+ id: 'namespace.node1',
696
+ type: 'DJNode',
697
+ data: {
698
+ label: 'schema1.table1',
699
+ table: 'table1',
700
+ name: 'namespace.node1',
701
+ display_name: 'DisplayName1',
702
+ type: 'type1',
703
+ primary_key: ['col2'],
704
+ column_names: [
705
+ { name: 'col1', type: 'type1' },
706
+ { name: 'col2', type: 'type2' },
707
+ ],
708
+ },
709
+ },
710
+ ]);
711
+
712
+ expect(result.namespaces).toEqual([
713
+ {
714
+ id: 'namespace',
715
+ type: 'DJNamespace',
716
+ data: {
717
+ label: 'namespace',
718
+ },
719
+ },
720
+ ]);
721
+ });
722
+
723
+ it('calls link and unlink dimension correctly', async () => {
724
+ const nodeName = 'default.transform1';
725
+ const dimensionName = 'default.dimension1';
726
+ const columnName = 'column1';
727
+ fetch.mockResponseOnce(JSON.stringify({}));
728
+ await DataJunctionAPI.unlinkDimension(nodeName, columnName, dimensionName);
729
+ expect(fetch).toHaveBeenCalledWith(
730
+ `${DJ_URL}/nodes/${nodeName}/columns/${columnName}?dimension=${dimensionName}`,
731
+ {
732
+ credentials: 'include',
733
+ headers: {
734
+ 'Content-Type': 'application/json',
735
+ },
736
+ method: 'DELETE',
737
+ },
738
+ );
739
+
740
+ fetch.mockResponseOnce(JSON.stringify({}));
741
+ await DataJunctionAPI.linkDimension(nodeName, columnName, dimensionName);
742
+ expect(fetch).toHaveBeenCalledWith(
743
+ `${DJ_URL}/nodes/${nodeName}/columns/${columnName}?dimension=${dimensionName}`,
744
+ {
745
+ credentials: 'include',
746
+ headers: {
747
+ 'Content-Type': 'application/json',
748
+ },
749
+ method: 'POST',
750
+ },
751
+ );
752
+ });
753
+
754
+ it('calls add and remove complex dimension link correctly', async () => {
755
+ const nodeName = 'default.transform1';
756
+ const dimensionNode = 'default.dimension1';
757
+ const joinOn = 'blah';
758
+ fetch.mockResponseOnce(JSON.stringify({}));
759
+ await DataJunctionAPI.removeComplexDimensionLink(nodeName, dimensionNode);
760
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${nodeName}/link`, {
761
+ credentials: 'include',
762
+ headers: {
763
+ 'Content-Type': 'application/json',
764
+ },
765
+ body: JSON.stringify({
766
+ dimensionNode: dimensionNode,
767
+ role: null,
768
+ }),
769
+ method: 'DELETE',
770
+ });
771
+
772
+ fetch.mockResponseOnce(JSON.stringify({}));
773
+ await DataJunctionAPI.addComplexDimensionLink(
774
+ nodeName,
775
+ dimensionNode,
776
+ joinOn,
777
+ );
778
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${nodeName}/link`, {
779
+ credentials: 'include',
780
+ headers: {
781
+ 'Content-Type': 'application/json',
782
+ },
783
+ body: JSON.stringify({
784
+ dimensionNode: dimensionNode,
785
+ joinType: null,
786
+ joinOn: joinOn,
787
+ joinCardinality: null,
788
+ role: null,
789
+ }),
790
+ method: 'POST',
791
+ });
792
+ });
793
+
794
+ it('calls deactivate correctly', async () => {
795
+ const nodeName = 'default.transform1';
796
+ fetch.mockResponseOnce(JSON.stringify({}));
797
+ await DataJunctionAPI.deactivate(nodeName);
798
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${nodeName}`, {
799
+ credentials: 'include',
800
+ headers: {
801
+ 'Content-Type': 'application/json',
802
+ },
803
+ method: 'DELETE',
804
+ });
805
+ });
806
+
807
+ it('calls attributes correctly', async () => {
808
+ fetch.mockResponseOnce(JSON.stringify(mocks.attributes));
809
+ await DataJunctionAPI.attributes();
810
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/attributes`, {
811
+ credentials: 'include',
812
+ });
813
+ });
814
+
815
+ it('calls dimensions correctly', async () => {
816
+ fetch.mockResponseOnce(JSON.stringify(mocks.dimensions));
817
+ await DataJunctionAPI.dimensions();
818
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/dimensions`, {
819
+ credentials: 'include',
820
+ });
821
+ });
822
+
823
+ it('calls setAttributes correctly', async () => {
824
+ const nodeName = 'default.transform1';
825
+ const attributes = ['system'];
826
+ const columnName = 'column1';
827
+ fetch.mockResponseOnce(JSON.stringify({}));
828
+ await DataJunctionAPI.setAttributes(nodeName, columnName, attributes);
829
+ expect(fetch).toHaveBeenCalledWith(
830
+ `${DJ_URL}/nodes/${nodeName}/columns/${columnName}/attributes`,
831
+ {
832
+ credentials: 'include',
833
+ body: JSON.stringify([{ namespace: 'system', name: 'system' }]),
834
+ headers: {
835
+ 'Content-Type': 'application/json',
836
+ },
837
+ method: 'POST',
838
+ },
839
+ );
840
+ });
841
+
842
+ it('calls addNamespace correctly', async () => {
843
+ fetch.mockResponseOnce(JSON.stringify({}));
844
+ await DataJunctionAPI.addNamespace('test');
845
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/namespaces/test`, {
846
+ credentials: 'include',
847
+ headers: {
848
+ 'Content-Type': 'application/json',
849
+ },
850
+ method: 'POST',
851
+ });
852
+ });
853
+
854
+ it('calls listTags correctly', async () => {
855
+ fetch.mockResponseOnce(JSON.stringify(mocks.tags));
856
+ const res = await DataJunctionAPI.listTags();
857
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/tags`, {
858
+ credentials: 'include',
859
+ headers: {
860
+ 'Content-Type': 'application/json',
861
+ },
862
+ method: 'GET',
863
+ });
864
+ expect(res).toEqual(mocks.tags);
865
+ });
866
+
867
+ it('calls getTag correctly', async () => {
868
+ fetch.mockResponseOnce(JSON.stringify(mocks.tags[0]));
869
+ const res = await DataJunctionAPI.getTag('report.financials');
870
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/tags/report.financials`, {
871
+ credentials: 'include',
872
+ headers: {
873
+ 'Content-Type': 'application/json',
874
+ },
875
+ method: 'GET',
876
+ });
877
+ expect(res).toEqual(mocks.tags[0]);
878
+ });
879
+
880
+ it('calls listNodesForTag correctly', async () => {
881
+ fetch.mockResponseOnce(JSON.stringify(mocks.mockLinkedNodes));
882
+ const res = await DataJunctionAPI.listNodesForTag('report.financials');
883
+ expect(fetch).toHaveBeenCalledWith(
884
+ `${DJ_URL}/tags/report.financials/nodes`,
885
+ {
886
+ credentials: 'include',
887
+ headers: {
888
+ 'Content-Type': 'application/json',
889
+ },
890
+ method: 'GET',
891
+ },
892
+ );
893
+ expect(res).toEqual(mocks.mockLinkedNodes);
894
+ });
895
+
896
+ it('calls tagsNode correctly', async () => {
897
+ fetch.mockResponseOnce(JSON.stringify(mocks.mockLinkedNodes));
898
+ await DataJunctionAPI.tagsNode('default.num_repair_orders', [
899
+ 'report.financials',
900
+ 'report.forecasts',
901
+ ]);
902
+ expect(fetch).toHaveBeenCalledWith(
903
+ `${DJ_URL}/nodes/default.num_repair_orders/tags?tag_names=report.financials&tag_names=report.forecasts`,
904
+ {
905
+ credentials: 'include',
906
+ headers: {
907
+ 'Content-Type': 'application/json',
908
+ },
909
+ method: 'POST',
910
+ },
911
+ );
912
+ });
913
+
914
+ it('calls addTag correctly', async () => {
915
+ fetch.mockResponseOnce(JSON.stringify(mocks.mockLinkedNodes));
916
+ await DataJunctionAPI.addTag(
917
+ 'report.financials',
918
+ 'Financial Reports',
919
+ 'report',
920
+ 'financial reports',
921
+ );
922
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/tags`, {
923
+ credentials: 'include',
924
+ headers: {
925
+ 'Content-Type': 'application/json',
926
+ },
927
+ body: JSON.stringify({
928
+ name: 'report.financials',
929
+ display_name: 'Financial Reports',
930
+ tag_type: 'report',
931
+ description: 'financial reports',
932
+ }),
933
+ method: 'POST',
934
+ });
935
+ });
936
+
937
+ it('calls editTag correctly', async () => {
938
+ fetch.mockResponseOnce(JSON.stringify({}));
939
+ await DataJunctionAPI.editTag(
940
+ 'report.financials',
941
+ 'Financial reports',
942
+ 'Financial Reports',
943
+ );
944
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/tags/report.financials`, {
945
+ credentials: 'include',
946
+ headers: {
947
+ 'Content-Type': 'application/json',
948
+ },
949
+ body: JSON.stringify({
950
+ description: 'Financial reports',
951
+ display_name: 'Financial Reports',
952
+ }),
953
+ method: 'PATCH',
954
+ });
955
+ });
956
+
957
+ it('calls setPartition correctly', async () => {
958
+ fetch.mockResponseOnce(JSON.stringify({}));
959
+ await DataJunctionAPI.setPartition(
960
+ 'default.hard_hat',
961
+ 'hire_date',
962
+ 'temporal',
963
+ 'yyyyMMdd',
964
+ 'day',
965
+ );
966
+ expect(fetch).toHaveBeenCalledWith(
967
+ `${DJ_URL}/nodes/default.hard_hat/columns/hire_date/partition`,
968
+ {
969
+ credentials: 'include',
970
+ headers: {
971
+ 'Content-Type': 'application/json',
972
+ },
973
+ body: JSON.stringify({
974
+ type_: 'temporal',
975
+ format: 'yyyyMMdd',
976
+ granularity: 'day',
977
+ }),
978
+ method: 'POST',
979
+ },
980
+ );
981
+ });
982
+
983
+ it('calls materialize correctly', async () => {
984
+ fetch.mockResponseOnce(JSON.stringify({}));
985
+ await DataJunctionAPI.materialize(
986
+ 'default.hard_hat',
987
+ 'spark_sql',
988
+ 'full',
989
+ '@daily',
990
+ {},
991
+ );
992
+ expect(fetch).toHaveBeenCalledWith(
993
+ `${DJ_URL}/nodes/default.hard_hat/materialization`,
994
+ {
995
+ credentials: 'include',
996
+ headers: {
997
+ 'Content-Type': 'application/json',
998
+ },
999
+ body: JSON.stringify({
1000
+ job: 'spark_sql',
1001
+ strategy: 'full',
1002
+ schedule: '@daily',
1003
+ config: {},
1004
+ }),
1005
+ method: 'POST',
1006
+ },
1007
+ );
1008
+ });
1009
+
1010
+ it('calls runBackfill correctly', async () => {
1011
+ fetch.mockResponseOnce(JSON.stringify({}));
1012
+ await DataJunctionAPI.runBackfill('default.hard_hat', 'spark', [
1013
+ {
1014
+ columnName: 'hire_date',
1015
+ range: ['20230101', '20230202'],
1016
+ },
1017
+ ]);
1018
+ expect(fetch).toHaveBeenCalledWith(
1019
+ `${DJ_URL}/nodes/default.hard_hat/materializations/spark/backfill`,
1020
+ {
1021
+ credentials: 'include',
1022
+ headers: {
1023
+ 'Content-Type': 'application/json',
1024
+ },
1025
+ body: JSON.stringify([
1026
+ {
1027
+ column_name: 'hire_date',
1028
+ range: ['20230101', '20230202'],
1029
+ },
1030
+ ]),
1031
+ method: 'POST',
1032
+ },
1033
+ );
1034
+ });
1035
+
1036
+ it('calls materializationInfo correctly', async () => {
1037
+ fetch.mockResponseOnce(JSON.stringify({}));
1038
+ await DataJunctionAPI.materializationInfo();
1039
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/materialization/info`, {
1040
+ credentials: 'include',
1041
+ });
1042
+ });
1043
+
1044
+ it('calls revalidate correctly', async () => {
1045
+ fetch.mockResponseOnce(JSON.stringify({}));
1046
+ await DataJunctionAPI.revalidate('default.hard_hat');
1047
+ expect(fetch).toHaveBeenCalledWith(
1048
+ `${DJ_URL}/nodes/default.hard_hat/validate`,
1049
+ {
1050
+ credentials: 'include',
1051
+ method: 'POST',
1052
+ headers: {
1053
+ 'Content-Type': 'application/json',
1054
+ },
1055
+ },
1056
+ );
1057
+ });
1058
+
1059
+ it('calls deleteMaterialization correctly', () => {
1060
+ const nodeName = 'transform1';
1061
+ const materializationName = 'sampleMaterialization';
1062
+ fetch.mockResponseOnce(JSON.stringify({}));
1063
+
1064
+ DataJunctionAPI.deleteMaterialization(nodeName, materializationName);
1065
+ expect(fetch).toHaveBeenCalledWith(
1066
+ `${DJ_URL}/nodes/${nodeName}/materializations?materialization_name=${materializationName}`,
1067
+ {
1068
+ method: 'DELETE',
1069
+ credentials: 'include',
1070
+ headers: {
1071
+ 'Content-Type': 'application/json',
1072
+ },
1073
+ },
1074
+ );
1075
+ });
1076
+
1077
+ it('calls listNodesForLanding correctly', () => {
1078
+ fetch.mockResponseOnce(JSON.stringify({}));
1079
+
1080
+ DataJunctionAPI.listNodesForLanding(
1081
+ '',
1082
+ ['source'],
1083
+ [],
1084
+ '',
1085
+ null,
1086
+ null,
1087
+ 100,
1088
+ {
1089
+ key: 'updatedAt',
1090
+ direction: 'descending',
1091
+ },
1092
+ );
1093
+ expect(fetch).toHaveBeenCalledWith(
1094
+ `${DJ_URL}/graphql`,
1095
+ expect.objectContaining({
1096
+ method: 'POST',
1097
+ credentials: 'include',
1098
+ headers: {
1099
+ 'Content-Type': 'application/json',
1100
+ },
1101
+ }),
1102
+ );
1103
+ });
1104
+
1105
+ it('calls getMetric correctly', async () => {
1106
+ fetch.mockResponseOnce(
1107
+ JSON.stringify({
1108
+ data: { findNodes: [{ name: 'default.num_repair_orders' }] },
1109
+ }),
1110
+ );
1111
+ await DataJunctionAPI.getMetric('default.num_repair_orders');
1112
+ expect(fetch).toHaveBeenCalledWith(
1113
+ `${DJ_URL}/graphql`,
1114
+ expect.objectContaining({
1115
+ method: 'POST',
1116
+ credentials: 'include',
1117
+ headers: {
1118
+ 'Content-Type': 'application/json',
1119
+ },
1120
+ }),
1121
+ );
1122
+ });
1123
+
1124
+ it('calls notebookExportCube correctly', async () => {
1125
+ fetch.mockResponseOnce(JSON.stringify({}));
1126
+ await DataJunctionAPI.notebookExportCube('default.repairs_cube');
1127
+ expect(fetch).toHaveBeenCalledWith(
1128
+ `${DJ_URL}/datajunction-clients/python/notebook/?cube=default.repairs_cube`,
1129
+ {
1130
+ credentials: 'include',
1131
+ },
1132
+ );
1133
+ });
1134
+
1135
+ it('calls notebookExportNamespace correctly', async () => {
1136
+ fetch.mockResponseOnce(JSON.stringify({}));
1137
+ await DataJunctionAPI.notebookExportNamespace('default');
1138
+ expect(fetch).toHaveBeenCalledWith(
1139
+ `${DJ_URL}/datajunction-clients/python/notebook/?namespace=default`,
1140
+ {
1141
+ credentials: 'include',
1142
+ },
1143
+ );
1144
+ });
1145
+
1146
+ it('calls node_counts_by_type correctly', async () => {
1147
+ fetch.mockResponseOnce(
1148
+ JSON.stringify([
1149
+ [
1150
+ {
1151
+ value: 'cube',
1152
+ col: 'system.dj.node_type.type',
1153
+ },
1154
+ {
1155
+ value: 226,
1156
+ col: 'system.dj.number_of_nodes',
1157
+ },
1158
+ ],
1159
+ [
1160
+ {
1161
+ value: 'dimension',
1162
+ col: 'system.dj.node_type.type',
1163
+ },
1164
+ {
1165
+ value: 241,
1166
+ col: 'system.dj.number_of_nodes',
1167
+ },
1168
+ ],
1169
+ [
1170
+ {
1171
+ value: 'metric',
1172
+ col: 'system.dj.node_type.type',
1173
+ },
1174
+ {
1175
+ value: 2853,
1176
+ col: 'system.dj.number_of_nodes',
1177
+ },
1178
+ ],
1179
+ [
1180
+ {
1181
+ value: 'source',
1182
+ col: 'system.dj.node_type.type',
1183
+ },
1184
+ {
1185
+ value: 540,
1186
+ col: 'system.dj.number_of_nodes',
1187
+ },
1188
+ ],
1189
+ [
1190
+ {
1191
+ value: 'transform',
1192
+ col: 'system.dj.node_type.type',
1193
+ },
1194
+ {
1195
+ value: 663,
1196
+ col: 'system.dj.number_of_nodes',
1197
+ },
1198
+ ],
1199
+ ]),
1200
+ );
1201
+ const results = await DataJunctionAPI.system.node_counts_by_type();
1202
+ expect(fetch).toHaveBeenCalledWith(
1203
+ `${DJ_URL}/system/data/system.dj.number_of_nodes?dimensions=system.dj.node_type.type&filters=system.dj.is_active.active_id%3Dtrue&orderby=system.dj.node_type.type`,
1204
+ {
1205
+ credentials: 'include',
1206
+ },
1207
+ );
1208
+ expect(results).toEqual([
1209
+ { name: 'cube', value: 226 },
1210
+ { name: 'dimension', value: 241 },
1211
+ { name: 'metric', value: 2853 },
1212
+ { name: 'source', value: 540 },
1213
+ { name: 'transform', value: 663 },
1214
+ ]);
1215
+ });
1216
+
1217
+ it('calls node_counts_by_active correctly', async () => {
1218
+ fetch.mockResponseOnce(
1219
+ JSON.stringify([
1220
+ [
1221
+ {
1222
+ value: false,
1223
+ col: 'system.dj.is_active.active_id',
1224
+ },
1225
+ {
1226
+ value: 3136,
1227
+ col: 'system.dj.number_of_nodes',
1228
+ },
1229
+ ],
1230
+ [
1231
+ {
1232
+ value: true,
1233
+ col: 'system.dj.is_active.active_id',
1234
+ },
1235
+ {
1236
+ value: 4523,
1237
+ col: 'system.dj.number_of_nodes',
1238
+ },
1239
+ ],
1240
+ ]),
1241
+ );
1242
+ const results = await DataJunctionAPI.system.node_counts_by_active();
1243
+ expect(fetch).toHaveBeenCalledWith(
1244
+ `${DJ_URL}/system/data/system.dj.number_of_nodes?dimensions=system.dj.is_active.active_id`,
1245
+ {
1246
+ credentials: 'include',
1247
+ },
1248
+ );
1249
+ expect(results).toEqual([
1250
+ { name: 'false', value: 3136 },
1251
+ { name: 'true', value: 4523 },
1252
+ ]);
1253
+ });
1254
+
1255
+ it('calls node_counts_by_status correctly', async () => {
1256
+ fetch.mockResponseOnce(
1257
+ JSON.stringify([
1258
+ [
1259
+ {
1260
+ value: 'VALID',
1261
+ col: 'system.dj.nodes.status',
1262
+ },
1263
+ {
1264
+ value: 4333,
1265
+ col: 'system.dj.number_of_nodes',
1266
+ },
1267
+ ],
1268
+ [
1269
+ {
1270
+ value: 'INVALID',
1271
+ col: 'system.dj.nodes.status',
1272
+ },
1273
+ {
1274
+ value: 190,
1275
+ col: 'system.dj.number_of_nodes',
1276
+ },
1277
+ ],
1278
+ ]),
1279
+ );
1280
+ const results = await DataJunctionAPI.system.node_counts_by_status();
1281
+ expect(fetch).toHaveBeenCalledWith(
1282
+ `${DJ_URL}/system/data/system.dj.number_of_nodes?dimensions=system.dj.nodes.status&filters=system.dj.is_active.active_id%3Dtrue&orderby=system.dj.nodes.status`,
1283
+ {
1284
+ credentials: 'include',
1285
+ },
1286
+ );
1287
+ expect(results).toEqual([
1288
+ { name: 'VALID', value: 4333 },
1289
+ { name: 'INVALID', value: 190 },
1290
+ ]);
1291
+ });
1292
+
1293
+ it('calls nodes_without_description correctly', async () => {
1294
+ fetch.mockResponseOnce(
1295
+ JSON.stringify([
1296
+ [
1297
+ {
1298
+ value: 'cube',
1299
+ col: 'system.dj.node_type.type',
1300
+ },
1301
+ {
1302
+ value: 0.1,
1303
+ col: 'system.dj.node_without_description',
1304
+ },
1305
+ ],
1306
+ [
1307
+ {
1308
+ value: 'dimension',
1309
+ col: 'system.dj.node_type.type',
1310
+ },
1311
+ {
1312
+ value: 0.2,
1313
+ col: 'system.dj.node_without_description',
1314
+ },
1315
+ ],
1316
+ [
1317
+ {
1318
+ value: 'metric',
1319
+ col: 'system.dj.node_type.type',
1320
+ },
1321
+ {
1322
+ value: 0.3,
1323
+ col: 'system.dj.node_without_description',
1324
+ },
1325
+ ],
1326
+ [
1327
+ {
1328
+ value: 'source',
1329
+ col: 'system.dj.node_type.type',
1330
+ },
1331
+ {
1332
+ value: 0.4,
1333
+ col: 'system.dj.node_without_description',
1334
+ },
1335
+ ],
1336
+ [
1337
+ {
1338
+ value: 'transform',
1339
+ col: 'system.dj.node_type.type',
1340
+ },
1341
+ {
1342
+ value: 0.5,
1343
+ col: 'system.dj.node_without_description',
1344
+ },
1345
+ ],
1346
+ ]),
1347
+ );
1348
+ const results = await DataJunctionAPI.system.nodes_without_description();
1349
+ expect(fetch).toHaveBeenCalledWith(
1350
+ `${DJ_URL}/system/data/system.dj.node_without_description?dimensions=system.dj.node_type.type&filters=system.dj.is_active.active_id%3Dtrue&orderby=system.dj.node_type.type`,
1351
+ {
1352
+ credentials: 'include',
1353
+ },
1354
+ );
1355
+ expect(results).toEqual([
1356
+ { name: 'cube', value: 0.1 },
1357
+ { name: 'dimension', value: 0.2 },
1358
+ { name: 'metric', value: 0.3 },
1359
+ { name: 'source', value: 0.4 },
1360
+ { name: 'transform', value: 0.5 },
1361
+ ]);
1362
+ });
1363
+ it('calls node_trends correctly', async () => {
1364
+ fetch.mockResponseOnce(
1365
+ JSON.stringify([
1366
+ [
1367
+ {
1368
+ value: 20250630,
1369
+ col: 'system.dj.nodes.created_at_week',
1370
+ },
1371
+ {
1372
+ value: 'metric',
1373
+ col: 'system.dj.node_type.type',
1374
+ },
1375
+ {
1376
+ value: 42,
1377
+ col: 'system.dj.number_of_nodes',
1378
+ },
1379
+ ],
1380
+ [
1381
+ {
1382
+ value: 20250707,
1383
+ col: 'system.dj.nodes.created_at_week',
1384
+ },
1385
+ {
1386
+ value: 'dimension',
1387
+ col: 'system.dj.node_type.type',
1388
+ },
1389
+ {
1390
+ value: 21,
1391
+ col: 'system.dj.number_of_nodes',
1392
+ },
1393
+ ],
1394
+ [
1395
+ {
1396
+ value: 20250707,
1397
+ col: 'system.dj.nodes.created_at_week',
1398
+ },
1399
+ {
1400
+ value: 'metric',
1401
+ col: 'system.dj.node_type.type',
1402
+ },
1403
+ {
1404
+ value: 9,
1405
+ col: 'system.dj.number_of_nodes',
1406
+ },
1407
+ ],
1408
+ [
1409
+ {
1410
+ value: 20250714,
1411
+ col: 'system.dj.nodes.created_at_week',
1412
+ },
1413
+ {
1414
+ value: 'metric',
1415
+ col: 'system.dj.node_type.type',
1416
+ },
1417
+ {
1418
+ value: 3,
1419
+ col: 'system.dj.number_of_nodes',
1420
+ },
1421
+ ],
1422
+ [
1423
+ {
1424
+ value: 20250714,
1425
+ col: 'system.dj.nodes.created_at_week',
1426
+ },
1427
+ {
1428
+ value: 'dimension',
1429
+ col: 'system.dj.node_type.type',
1430
+ },
1431
+ {
1432
+ value: 7,
1433
+ col: 'system.dj.number_of_nodes',
1434
+ },
1435
+ ],
1436
+ ]),
1437
+ );
1438
+ const results = await DataJunctionAPI.system.node_trends();
1439
+ expect(fetch).toHaveBeenCalledWith(
1440
+ `${DJ_URL}/system/data/system.dj.number_of_nodes?dimensions=system.dj.nodes.created_at_week&dimensions=system.dj.node_type.type&filters=system.dj.nodes.created_at_week>=20240101&orderby=system.dj.nodes.created_at_week`,
1441
+ {
1442
+ credentials: 'include',
1443
+ },
1444
+ );
1445
+ expect(results).toEqual([
1446
+ { date: 20250630, metric: 42 },
1447
+ { date: 20250707, dimension: 21, metric: 9 },
1448
+ { date: 20250714, dimension: 7, metric: 3 },
1449
+ ]);
1450
+ });
1451
+
1452
+ it('calls materialization_counts_by_type correctly', async () => {
1453
+ fetch.mockResponseOnce(
1454
+ JSON.stringify([
1455
+ [
1456
+ {
1457
+ value: 'cube',
1458
+ col: 'system.dj.node_type.type',
1459
+ },
1460
+ {
1461
+ value: 76,
1462
+ col: 'system.dj.number_of_materializations',
1463
+ },
1464
+ ],
1465
+ [
1466
+ {
1467
+ value: 'dimension',
1468
+ col: 'system.dj.node_type.type',
1469
+ },
1470
+ {
1471
+ value: 3,
1472
+ col: 'system.dj.number_of_materializations',
1473
+ },
1474
+ ],
1475
+ [
1476
+ {
1477
+ value: 'transform',
1478
+ col: 'system.dj.node_type.type',
1479
+ },
1480
+ {
1481
+ value: 9,
1482
+ col: 'system.dj.number_of_materializations',
1483
+ },
1484
+ ],
1485
+ ]),
1486
+ );
1487
+ const results =
1488
+ await DataJunctionAPI.system.materialization_counts_by_type();
1489
+ expect(fetch).toHaveBeenCalledWith(
1490
+ `${DJ_URL}/system/data/system.dj.number_of_materializations?dimensions=system.dj.node_type.type&filters=system.dj.is_active.active_id%3Dtrue&orderby=system.dj.node_type.type`,
1491
+ {
1492
+ credentials: 'include',
1493
+ },
1494
+ );
1495
+ expect(results).toEqual([
1496
+ { name: 'cube', value: 76 },
1497
+ { name: 'dimension', value: 3 },
1498
+ { name: 'transform', value: 9 },
1499
+ ]);
1500
+ });
1501
+
1502
+ it('calls system.dimensions correctly', async () => {
1503
+ fetch.mockResponseOnce(JSON.stringify([]));
1504
+ const results = await DataJunctionAPI.system.dimensions();
1505
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/system/dimensions`, {
1506
+ credentials: 'include',
1507
+ });
1508
+ });
1509
+
1510
+ it('calls availabilityStates correctly', async () => {
1511
+ const nodeName = 'default.sample_node';
1512
+ const mockAvailabilityStates = [
1513
+ {
1514
+ id: 1,
1515
+ catalog: 'test_catalog',
1516
+ schema_: 'test_schema',
1517
+ table: 'test_table',
1518
+ valid_through_ts: 1640995200,
1519
+ url: 'http://example.com/table',
1520
+ node_revision_id: 123,
1521
+ node_version: '1.0.0',
1522
+ },
1523
+ ];
1524
+
1525
+ fetch.mockResponseOnce(JSON.stringify(mockAvailabilityStates));
1526
+
1527
+ const result = await DataJunctionAPI.availabilityStates(nodeName);
1528
+
1529
+ expect(fetch).toHaveBeenCalledWith(
1530
+ `${DJ_URL}/nodes/${nodeName}/availability/`,
1531
+ {
1532
+ credentials: 'include',
1533
+ },
1534
+ );
1535
+ expect(result).toEqual(mockAvailabilityStates);
1536
+ });
1537
+
1538
+ it('calls refreshLatestMaterialization correctly', async () => {
1539
+ const nodeName = 'default.sample_cube';
1540
+ const mockResponse = { message: 'Materialization refreshed successfully' };
1541
+
1542
+ fetch.mockResponseOnce(JSON.stringify(mockResponse));
1543
+
1544
+ const result = await DataJunctionAPI.refreshLatestMaterialization(nodeName);
1545
+
1546
+ expect(fetch).toHaveBeenCalledWith(
1547
+ `${DJ_URL}/nodes/${nodeName}?refresh_materialization=true`,
1548
+ {
1549
+ method: 'PATCH',
1550
+ headers: {
1551
+ 'Content-Type': 'application/json',
1552
+ },
1553
+ body: JSON.stringify({}),
1554
+ credentials: 'include',
1555
+ },
1556
+ );
1557
+ expect(result).toEqual({ status: 200, json: mockResponse });
1558
+ });
1559
+ });