datajunction-ui 0.0.1-rc.19 → 0.0.1-rc.20
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.
- package/package.json +6 -5
- package/src/__tests__/reportWebVitals.test.jsx +44 -0
- package/src/app/components/DeleteNode.jsx +79 -0
- package/src/app/components/ListGroupItem.jsx +8 -1
- package/src/app/components/QueryInfo.jsx +4 -4
- package/src/app/components/Tab.jsx +9 -1
- package/src/app/components/ToggleSwitch.jsx +3 -0
- package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
- package/src/app/components/__tests__/Tab.test.jsx +27 -0
- package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
- package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +3 -0
- package/src/app/components/djgraph/DJNodeColumns.jsx +4 -1
- package/src/app/components/djgraph/DJNodeDimensions.jsx +9 -2
- package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
- package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
- package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
- package/src/app/index.tsx +6 -0
- package/src/app/pages/AddEditNodePage/FormikSelect.jsx +15 -2
- package/src/app/pages/AddEditNodePage/FullNameField.jsx +2 -1
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +77 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +93 -0
- package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +34 -3
- package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +4 -2
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +53 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +53 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +0 -81
- package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +82 -257
- package/src/app/pages/AddEditNodePage/index.jsx +13 -5
- package/src/app/pages/LoginPage/__tests__/index.test.jsx +70 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +95 -0
- package/src/app/pages/NamespacePage/index.jsx +8 -5
- package/src/app/pages/NodePage/ClientCodePopover.jsx +3 -1
- package/src/app/pages/NodePage/EditColumnPopover.jsx +102 -0
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +135 -0
- package/src/app/pages/NodePage/NodeColumnTab.jsx +80 -17
- package/src/app/pages/NodePage/NodeHistory.jsx +63 -30
- package/src/app/pages/NodePage/NodeInfoTab.jsx +52 -7
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +50 -27
- package/src/app/pages/NodePage/NodeSQLTab.jsx +0 -10
- package/src/app/pages/NodePage/NodesWithDimension.jsx +4 -2
- package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +49 -0
- package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
- package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +165 -0
- package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +591 -0
- package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +57 -0
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +725 -0
- package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +402 -0
- package/src/app/pages/NodePage/index.jsx +22 -6
- package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
- package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
- package/src/app/pages/RegisterTablePage/index.jsx +163 -0
- package/src/app/pages/Root/__tests__/index.test.jsx +77 -0
- package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
- package/src/app/pages/SQLBuilderPage/index.jsx +61 -43
- package/src/app/services/DJService.js +125 -54
- package/src/app/services/__tests__/DJService.test.jsx +609 -0
- package/src/mocks/mockNodes.jsx +1397 -0
- package/src/setupTests.ts +30 -0
- package/src/styles/index.css +43 -0
- package/src/styles/node-creation.scss +7 -0
- package/src/utils/form.jsx +23 -0
- package/.github/pull_request_template.md +0 -11
- package/.github/workflows/ci.yml +0 -33
- package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -118
- package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
|
@@ -1,149 +1,114 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
3
|
-
import {
|
|
3
|
+
import { screen, waitFor } from '@testing-library/react';
|
|
4
|
+
import { render } from '../../../../setupTests';
|
|
4
5
|
import fetchMock from 'jest-fetch-mock';
|
|
5
6
|
import { AddEditNodePage } from '../index.jsx';
|
|
7
|
+
import { mocks } from '../../../../mocks/mockNodes';
|
|
6
8
|
import DJClientContext from '../../../providers/djclient';
|
|
7
9
|
import userEvent from '@testing-library/user-event';
|
|
8
10
|
|
|
9
11
|
fetchMock.enableMocks();
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
];
|
|
36
|
-
},
|
|
37
|
-
metrics: {},
|
|
38
|
-
namespaces: () => {
|
|
39
|
-
return [
|
|
40
|
-
{
|
|
41
|
-
namespace: 'default',
|
|
42
|
-
num_nodes: 33,
|
|
43
|
-
},
|
|
44
|
-
];
|
|
45
|
-
},
|
|
46
|
-
createNode: jest.fn(),
|
|
47
|
-
patchNode: jest.fn(),
|
|
48
|
-
node: jest.fn(),
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const testElement = djClient => {
|
|
54
|
-
return (
|
|
55
|
-
<DJClientContext.Provider value={djClient}>
|
|
56
|
-
<AddEditNodePage />
|
|
57
|
-
</DJClientContext.Provider>
|
|
58
|
-
);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const mockMetricNode = {
|
|
62
|
-
namespace: 'default',
|
|
63
|
-
node_revision_id: 23,
|
|
64
|
-
node_id: 23,
|
|
65
|
-
type: 'metric',
|
|
66
|
-
name: 'default.num_repair_orders',
|
|
67
|
-
display_name: 'Default: Num Repair Orders',
|
|
68
|
-
version: 'v1.0',
|
|
69
|
-
status: 'valid',
|
|
70
|
-
mode: 'published',
|
|
71
|
-
catalog: {
|
|
72
|
-
id: 1,
|
|
73
|
-
uuid: '0fc18295-e1a2-4c3c-b72a-894725c12488',
|
|
74
|
-
created_at: '2023-08-21T16:48:51.146121+00:00',
|
|
75
|
-
updated_at: '2023-08-21T16:48:51.146122+00:00',
|
|
76
|
-
extra_params: {},
|
|
77
|
-
name: 'warehouse',
|
|
78
|
-
},
|
|
79
|
-
schema_: null,
|
|
80
|
-
table: null,
|
|
81
|
-
description: 'Number of repair orders',
|
|
82
|
-
query:
|
|
83
|
-
'SELECT count(repair_order_id) default_DOT_num_repair_orders FROM default.repair_orders',
|
|
84
|
-
availability: null,
|
|
85
|
-
columns: [
|
|
86
|
-
{
|
|
87
|
-
name: 'default_DOT_num_repair_orders',
|
|
88
|
-
type: 'bigint',
|
|
89
|
-
attributes: [],
|
|
90
|
-
dimension: null,
|
|
13
|
+
export const initializeMockDJClient = () => {
|
|
14
|
+
return {
|
|
15
|
+
DataJunctionAPI: {
|
|
16
|
+
namespace: _ => {
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
name: 'default.contractors',
|
|
20
|
+
display_name: 'Default: Contractors',
|
|
21
|
+
version: 'v1.0',
|
|
22
|
+
type: 'source',
|
|
23
|
+
status: 'valid',
|
|
24
|
+
mode: 'published',
|
|
25
|
+
updated_at: '2023-08-21T16:48:53.246914+00:00',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'default.num_repair_orders',
|
|
29
|
+
display_name: 'Default: Num Repair Orders',
|
|
30
|
+
version: 'v1.0',
|
|
31
|
+
type: 'metric',
|
|
32
|
+
status: 'valid',
|
|
33
|
+
mode: 'published',
|
|
34
|
+
updated_at: '2023-08-21T16:48:56.841704+00:00',
|
|
35
|
+
},
|
|
36
|
+
];
|
|
91
37
|
},
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
38
|
+
metrics: {},
|
|
39
|
+
namespaces: () => {
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
namespace: 'default',
|
|
43
|
+
num_nodes: 33,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
namespace: 'default123',
|
|
47
|
+
num_nodes: 0,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
98
50
|
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
51
|
+
createNode: jest.fn(),
|
|
52
|
+
patchNode: jest.fn(),
|
|
53
|
+
node: jest.fn(),
|
|
54
|
+
},
|
|
102
55
|
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const testElement = djClient => {
|
|
59
|
+
return (
|
|
60
|
+
<DJClientContext.Provider value={djClient}>
|
|
61
|
+
<AddEditNodePage />
|
|
62
|
+
</DJClientContext.Provider>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const renderCreateNode = element => {
|
|
67
|
+
return render(
|
|
68
|
+
<MemoryRouter initialEntries={['/create/dimension/default']}>
|
|
69
|
+
<Routes>
|
|
70
|
+
<Route path="create/:nodeType/:initialNamespace" element={element} />
|
|
71
|
+
</Routes>
|
|
72
|
+
</MemoryRouter>,
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const renderEditNode = element => {
|
|
77
|
+
return render(
|
|
78
|
+
<MemoryRouter initialEntries={['/nodes/default.num_repair_orders/edit']}>
|
|
79
|
+
<Routes>
|
|
80
|
+
<Route path="nodes/:name/edit" element={element} />
|
|
81
|
+
</Routes>
|
|
82
|
+
</MemoryRouter>,
|
|
83
|
+
);
|
|
84
|
+
};
|
|
103
85
|
|
|
86
|
+
describe('AddEditNodePage', () => {
|
|
104
87
|
beforeEach(() => {
|
|
105
88
|
fetchMock.resetMocks();
|
|
106
89
|
jest.clearAllMocks();
|
|
107
90
|
window.scrollTo = jest.fn();
|
|
108
91
|
});
|
|
109
92
|
|
|
110
|
-
|
|
111
|
-
return render(
|
|
112
|
-
<MemoryRouter initialEntries={['/create/dimension/default']}>
|
|
113
|
-
<Routes>
|
|
114
|
-
<Route path="create/:nodeType/:initialNamespace" element={element} />
|
|
115
|
-
</Routes>
|
|
116
|
-
</MemoryRouter>,
|
|
117
|
-
);
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const renderEditNode = element => {
|
|
121
|
-
return render(
|
|
122
|
-
<MemoryRouter initialEntries={['/nodes/default.num_repair_orders/edit']}>
|
|
123
|
-
<Routes>
|
|
124
|
-
<Route path="nodes/:name/edit" element={element} />
|
|
125
|
-
</Routes>
|
|
126
|
-
</MemoryRouter>,
|
|
127
|
-
);
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
it('Create node page renders with the selected nodeType and namespace', () => {
|
|
93
|
+
it('Create node page renders with the selected nodeType and namespace', async () => {
|
|
131
94
|
const mockDjClient = initializeMockDJClient();
|
|
132
95
|
const element = testElement(mockDjClient);
|
|
133
96
|
const { container } = renderCreateNode(element);
|
|
134
97
|
|
|
135
98
|
// The node type should be included in the page title
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
99
|
+
await waitFor(() => {
|
|
100
|
+
expect(
|
|
101
|
+
container.getElementsByClassName('node_type__metric'),
|
|
102
|
+
).toMatchSnapshot();
|
|
139
103
|
|
|
140
|
-
|
|
141
|
-
|
|
104
|
+
// The namespace should be set to the one provided in params
|
|
105
|
+
expect(screen.getByText('default')).toBeInTheDocument();
|
|
106
|
+
});
|
|
142
107
|
});
|
|
143
108
|
|
|
144
109
|
it('Edit node page renders with the selected node', async () => {
|
|
145
110
|
const mockDjClient = initializeMockDJClient();
|
|
146
|
-
mockDjClient.DataJunctionAPI.node.mockReturnValue(mockMetricNode);
|
|
111
|
+
mockDjClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
|
|
147
112
|
|
|
148
113
|
const element = testElement(mockDjClient);
|
|
149
114
|
renderEditNode(element);
|
|
@@ -170,146 +135,6 @@ describe('AddEditNodePage', () => {
|
|
|
170
135
|
});
|
|
171
136
|
});
|
|
172
137
|
|
|
173
|
-
it('Verify create node page user interaction and successful form submission', async () => {
|
|
174
|
-
const mockDjClient = initializeMockDJClient();
|
|
175
|
-
mockDjClient.DataJunctionAPI.createNode.mockReturnValue({
|
|
176
|
-
status: 200,
|
|
177
|
-
json: { name: 'default.special_forces_contractors', type: 'dimension' },
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
const element = testElement(mockDjClient);
|
|
181
|
-
const { container } = renderCreateNode(element);
|
|
182
|
-
|
|
183
|
-
const query =
|
|
184
|
-
'select \n' +
|
|
185
|
-
' C.contractor_id,\n' +
|
|
186
|
-
' C.address, C.contact_title, C.contact_name, C.city from default.contractors C \n' +
|
|
187
|
-
"where C.contact_title = 'special forces agent'";
|
|
188
|
-
|
|
189
|
-
// Fill in display name
|
|
190
|
-
await userEvent.type(
|
|
191
|
-
screen.getByLabelText('Display Name'),
|
|
192
|
-
'Special Forces Contractors',
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
// After typing in a display name, the full name should be updated based on the display name
|
|
196
|
-
expect(
|
|
197
|
-
screen.getByDisplayValue('default.special_forces_contractors'),
|
|
198
|
-
).toBeInTheDocument();
|
|
199
|
-
|
|
200
|
-
// Fill in the rest of the fields and submit
|
|
201
|
-
await userEvent.type(screen.getByLabelText('Query'), query);
|
|
202
|
-
await userEvent.type(
|
|
203
|
-
screen.getByLabelText('Description'),
|
|
204
|
-
'A curated list of special forces contractors',
|
|
205
|
-
);
|
|
206
|
-
await userEvent.type(screen.getByLabelText('Primary Key'), 'contractor_id');
|
|
207
|
-
await userEvent.click(screen.getByText('Create dimension'));
|
|
208
|
-
|
|
209
|
-
await waitFor(() => {
|
|
210
|
-
expect(mockDjClient.DataJunctionAPI.createNode).toBeCalledTimes(1);
|
|
211
|
-
expect(mockDjClient.DataJunctionAPI.createNode).toBeCalledWith(
|
|
212
|
-
'dimension',
|
|
213
|
-
'default.special_forces_contractors',
|
|
214
|
-
'Special Forces Contractors',
|
|
215
|
-
'A curated list of special forces contractors',
|
|
216
|
-
query,
|
|
217
|
-
'draft',
|
|
218
|
-
'default',
|
|
219
|
-
['contractor_id'],
|
|
220
|
-
);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// After successful creation, it should return a success message
|
|
224
|
-
expect(container.getElementsByClassName('success')).toMatchSnapshot();
|
|
225
|
-
}, 60000);
|
|
226
|
-
|
|
227
|
-
it('Verify create node page form failed submission', async () => {
|
|
228
|
-
const mockDjClient = initializeMockDJClient();
|
|
229
|
-
mockDjClient.DataJunctionAPI.createNode.mockReturnValue({
|
|
230
|
-
status: 500,
|
|
231
|
-
json: { message: 'Some columns in the primary key [] were not found' },
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
const element = testElement(mockDjClient);
|
|
235
|
-
const { container } = renderCreateNode(element);
|
|
236
|
-
|
|
237
|
-
await userEvent.type(
|
|
238
|
-
screen.getByLabelText('Display Name'),
|
|
239
|
-
'Some Test Metric',
|
|
240
|
-
);
|
|
241
|
-
await userEvent.type(screen.getByLabelText('Query'), 'SELECT * FROM test');
|
|
242
|
-
await userEvent.click(screen.getByText('Create dimension'));
|
|
243
|
-
|
|
244
|
-
await waitFor(() => {
|
|
245
|
-
expect(mockDjClient.DataJunctionAPI.createNode).toBeCalledTimes(1);
|
|
246
|
-
expect(mockDjClient.DataJunctionAPI.createNode).toBeCalledWith(
|
|
247
|
-
'dimension',
|
|
248
|
-
'default.some_test_metric',
|
|
249
|
-
'Some Test Metric',
|
|
250
|
-
'',
|
|
251
|
-
'SELECT * FROM test',
|
|
252
|
-
'draft',
|
|
253
|
-
'default',
|
|
254
|
-
null,
|
|
255
|
-
);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
// After failed creation, it should return a failure message
|
|
259
|
-
expect(container.getElementsByClassName('alert')).toMatchSnapshot();
|
|
260
|
-
}, 60000);
|
|
261
|
-
|
|
262
|
-
it('Verify edit node page form submission success', async () => {
|
|
263
|
-
const mockDjClient = initializeMockDJClient();
|
|
264
|
-
|
|
265
|
-
mockDjClient.DataJunctionAPI.node.mockReturnValue(mockMetricNode);
|
|
266
|
-
mockDjClient.DataJunctionAPI.patchNode = jest.fn();
|
|
267
|
-
mockDjClient.DataJunctionAPI.patchNode.mockReturnValue({
|
|
268
|
-
status: 201,
|
|
269
|
-
json: { name: 'default.num_repair_orders', type: 'metric' },
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
const element = testElement(mockDjClient);
|
|
273
|
-
renderEditNode(element);
|
|
274
|
-
|
|
275
|
-
await userEvent.type(await screen.getByLabelText('Display Name'), '!!!');
|
|
276
|
-
await userEvent.type(await screen.getByLabelText('Description'), '!!!');
|
|
277
|
-
await userEvent.click(await screen.getByText('Save'));
|
|
278
|
-
|
|
279
|
-
await waitFor(async () => {
|
|
280
|
-
expect(mockDjClient.DataJunctionAPI.patchNode).toBeCalledTimes(1);
|
|
281
|
-
expect(mockDjClient.DataJunctionAPI.patchNode).toBeCalledWith(
|
|
282
|
-
'default.num_repair_orders',
|
|
283
|
-
'Default: Num Repair Orders!!!',
|
|
284
|
-
'Number of repair orders!!!',
|
|
285
|
-
'SELECT count(repair_order_id) default_DOT_num_repair_orders FROM default.repair_orders',
|
|
286
|
-
'published',
|
|
287
|
-
null,
|
|
288
|
-
);
|
|
289
|
-
});
|
|
290
|
-
}, 60000);
|
|
291
|
-
|
|
292
|
-
it('Verify edit node page form submission failure displays alert', async () => {
|
|
293
|
-
const mockDjClient = initializeMockDJClient();
|
|
294
|
-
mockDjClient.DataJunctionAPI.node.mockReturnValue(mockMetricNode);
|
|
295
|
-
mockDjClient.DataJunctionAPI.patchNode.mockReturnValue({
|
|
296
|
-
status: 500,
|
|
297
|
-
json: { message: 'Update failed' },
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
const element = testElement(mockDjClient);
|
|
301
|
-
renderEditNode(element);
|
|
302
|
-
|
|
303
|
-
await userEvent.type(await screen.getByLabelText('Display Name'), '!!!');
|
|
304
|
-
await userEvent.type(await screen.getByLabelText('Description'), '!!!');
|
|
305
|
-
await userEvent.click(await screen.getByText('Save'));
|
|
306
|
-
|
|
307
|
-
await waitFor(async () => {
|
|
308
|
-
expect(mockDjClient.DataJunctionAPI.patchNode).toBeCalledTimes(1);
|
|
309
|
-
expect(await screen.getByText('Update failed')).toBeInTheDocument();
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
|
|
313
138
|
it('Verify edit page node not found', async () => {
|
|
314
139
|
const mockDjClient = initializeMockDJClient();
|
|
315
140
|
mockDjClient.DataJunctionAPI.node = jest.fn();
|
|
@@ -109,6 +109,10 @@ export function AddEditNodePage() {
|
|
|
109
109
|
</>
|
|
110
110
|
);
|
|
111
111
|
|
|
112
|
+
const primaryKeyToList = primaryKey => {
|
|
113
|
+
return primaryKey.split(',').map(columnName => columnName.trim());
|
|
114
|
+
};
|
|
115
|
+
|
|
112
116
|
const createNode = async (values, setStatus) => {
|
|
113
117
|
const { status, json } = await djClient.createNode(
|
|
114
118
|
nodeType,
|
|
@@ -118,7 +122,7 @@ export function AddEditNodePage() {
|
|
|
118
122
|
values.query,
|
|
119
123
|
values.mode,
|
|
120
124
|
values.namespace,
|
|
121
|
-
values.primary_key ? values.primary_key
|
|
125
|
+
values.primary_key ? primaryKeyToList(values.primary_key) : null,
|
|
122
126
|
);
|
|
123
127
|
if (status === 200 || status === 201) {
|
|
124
128
|
setStatus({
|
|
@@ -143,7 +147,7 @@ export function AddEditNodePage() {
|
|
|
143
147
|
values.description,
|
|
144
148
|
values.query,
|
|
145
149
|
values.mode,
|
|
146
|
-
values.primary_key ? values.primary_key
|
|
150
|
+
values.primary_key ? primaryKeyToList(values.primary_key) : null,
|
|
147
151
|
);
|
|
148
152
|
if (status === 200 || status === 201) {
|
|
149
153
|
setStatus({
|
|
@@ -199,10 +203,14 @@ export function AddEditNodePage() {
|
|
|
199
203
|
'mode',
|
|
200
204
|
];
|
|
201
205
|
fields.forEach(field => {
|
|
202
|
-
if (
|
|
203
|
-
|
|
206
|
+
if (
|
|
207
|
+
field === 'primary_key' &&
|
|
208
|
+
data[field] !== undefined &&
|
|
209
|
+
Array.isArray(data[field])
|
|
210
|
+
) {
|
|
211
|
+
data[field] = data[field].join(', ');
|
|
204
212
|
}
|
|
205
|
-
setFieldValue(field, data[field], false);
|
|
213
|
+
setFieldValue(field, data[field] || '', false);
|
|
206
214
|
});
|
|
207
215
|
};
|
|
208
216
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent, waitFor } from '@testing-library/react';
|
|
3
|
+
import { LoginPage } from '../index';
|
|
4
|
+
|
|
5
|
+
describe('LoginPage', () => {
|
|
6
|
+
const original = window.location;
|
|
7
|
+
|
|
8
|
+
const reloadFn = () => {
|
|
9
|
+
window.location.reload();
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
Object.defineProperty(window, 'location', {
|
|
14
|
+
configurable: true,
|
|
15
|
+
value: { reload: jest.fn() },
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterAll(() => {
|
|
20
|
+
Object.defineProperty(window, 'location', {
|
|
21
|
+
configurable: true,
|
|
22
|
+
value: original,
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
fetch.resetMocks();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
jest.clearAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('displays error messages when fields are empty and form is submitted', async () => {
|
|
35
|
+
const { getByText, queryAllByText } = render(<LoginPage />);
|
|
36
|
+
fireEvent.click(getByText('Login'));
|
|
37
|
+
|
|
38
|
+
await waitFor(() => {
|
|
39
|
+
expect(getByText('DataJunction')).toBeInTheDocument();
|
|
40
|
+
expect(queryAllByText('Required').length).toEqual(2);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('calls fetch with correct data on submit', async () => {
|
|
45
|
+
const username = 'testUser';
|
|
46
|
+
const password = 'testPassword';
|
|
47
|
+
reloadFn();
|
|
48
|
+
|
|
49
|
+
const { getByText, getByPlaceholderText } = render(<LoginPage />);
|
|
50
|
+
fireEvent.change(getByPlaceholderText('Username'), {
|
|
51
|
+
target: { value: username },
|
|
52
|
+
});
|
|
53
|
+
fireEvent.change(getByPlaceholderText('Password'), {
|
|
54
|
+
target: { value: password },
|
|
55
|
+
});
|
|
56
|
+
fireEvent.click(getByText('Login'));
|
|
57
|
+
|
|
58
|
+
await waitFor(() => {
|
|
59
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
60
|
+
`${process.env.REACT_APP_DJ_URL}/basic/login/`,
|
|
61
|
+
expect.objectContaining({
|
|
62
|
+
method: 'POST',
|
|
63
|
+
body: expect.any(FormData),
|
|
64
|
+
credentials: 'include',
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
expect(window.location.reload).toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
3
|
+
import DJClientContext from '../../../providers/djclient';
|
|
4
|
+
import { NamespacePage } from '../index';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
const mockDjClient = {
|
|
8
|
+
namespaces: jest.fn(),
|
|
9
|
+
namespace: jest.fn(),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe('NamespacePage', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
mockDjClient.namespaces.mockResolvedValue([
|
|
15
|
+
{
|
|
16
|
+
namespace: 'common.one',
|
|
17
|
+
num_nodes: 3,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
namespace: 'common.one.a',
|
|
21
|
+
num_nodes: 6,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
namespace: 'common.one.b',
|
|
25
|
+
num_nodes: 17,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
namespace: 'common.one.c',
|
|
29
|
+
num_nodes: 64,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
namespace: 'default',
|
|
33
|
+
num_nodes: 41,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
namespace: 'default.fruits',
|
|
37
|
+
num_nodes: 1,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
namespace: 'default.fruits.citrus.lemons',
|
|
41
|
+
num_nodes: 1,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
namespace: 'default.vegetables',
|
|
45
|
+
num_nodes: 2,
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
mockDjClient.namespace.mockResolvedValue([
|
|
49
|
+
{
|
|
50
|
+
name: 'testNode',
|
|
51
|
+
display_name: 'Test Node',
|
|
52
|
+
type: 'transform',
|
|
53
|
+
mode: 'active',
|
|
54
|
+
updated_at: new Date(),
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('displays namespaces and renders nodes', async () => {
|
|
60
|
+
const element = (
|
|
61
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
62
|
+
<NamespacePage />
|
|
63
|
+
</DJClientContext.Provider>
|
|
64
|
+
);
|
|
65
|
+
render(
|
|
66
|
+
<MemoryRouter initialEntries={['/namespaces/test.namespace']}>
|
|
67
|
+
<Routes>
|
|
68
|
+
<Route path="namespaces/:namespace" element={element} />
|
|
69
|
+
</Routes>
|
|
70
|
+
</MemoryRouter>,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
await waitFor(() => {
|
|
74
|
+
expect(mockDjClient.namespaces).toHaveBeenCalledTimes(1);
|
|
75
|
+
expect(screen.getByText('Namespaces')).toBeInTheDocument();
|
|
76
|
+
|
|
77
|
+
// check that it displays namespaces
|
|
78
|
+
expect(screen.getByText('common')).toBeInTheDocument();
|
|
79
|
+
expect(screen.getByText('one')).toBeInTheDocument();
|
|
80
|
+
expect(screen.getByText('fruits')).toBeInTheDocument();
|
|
81
|
+
expect(screen.getByText('vegetables')).toBeInTheDocument();
|
|
82
|
+
|
|
83
|
+
// check that it renders nodes
|
|
84
|
+
expect(screen.getByText('Test Node')).toBeInTheDocument();
|
|
85
|
+
|
|
86
|
+
// click to open and close tab
|
|
87
|
+
fireEvent.click(screen.getByText('common'));
|
|
88
|
+
fireEvent.click(screen.getByText('common'));
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
afterEach(() => {
|
|
93
|
+
jest.clearAllMocks();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -5,7 +5,7 @@ import NodeStatus from '../NodePage/NodeStatus';
|
|
|
5
5
|
import DJClientContext from '../../providers/djclient';
|
|
6
6
|
import Explorer from '../NamespacePage/Explorer';
|
|
7
7
|
import EditIcon from '../../icons/EditIcon';
|
|
8
|
-
import
|
|
8
|
+
import DeleteNode from '../../components/DeleteNode';
|
|
9
9
|
|
|
10
10
|
export function NamespacePage() {
|
|
11
11
|
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
@@ -85,7 +85,7 @@ export function NamespacePage() {
|
|
|
85
85
|
</td>
|
|
86
86
|
<td>
|
|
87
87
|
<a href={'/nodes/' + node.name} className="link-table">
|
|
88
|
-
{node.display_name}
|
|
88
|
+
{node.type !== 'source' ? node.display_name : ''}
|
|
89
89
|
</a>
|
|
90
90
|
</td>
|
|
91
91
|
<td>
|
|
@@ -108,9 +108,7 @@ export function NamespacePage() {
|
|
|
108
108
|
<a href={`/nodes/${node?.name}/edit`} style={{ marginLeft: '0.5rem' }}>
|
|
109
109
|
<EditIcon />
|
|
110
110
|
</a>
|
|
111
|
-
<
|
|
112
|
-
<DeleteIcon />
|
|
113
|
-
</a>
|
|
111
|
+
<DeleteNode nodeName={node?.name} />
|
|
114
112
|
</td>
|
|
115
113
|
</tr>
|
|
116
114
|
));
|
|
@@ -126,6 +124,11 @@ export function NamespacePage() {
|
|
|
126
124
|
<div className="dropdown">
|
|
127
125
|
<span className="add_node">+ Add Node</span>
|
|
128
126
|
<div className="dropdown-content">
|
|
127
|
+
<a href={`/create/source`}>
|
|
128
|
+
<div className="node_type__source node_type_creation_heading">
|
|
129
|
+
Register Table
|
|
130
|
+
</div>
|
|
131
|
+
</a>
|
|
129
132
|
<a href={`/create/transform/${namespace}`}>
|
|
130
133
|
<div className="node_type__transform node_type_creation_heading">
|
|
131
134
|
Transform
|
|
@@ -10,6 +10,7 @@ export default function ClientCodePopover({ code }) {
|
|
|
10
10
|
<>
|
|
11
11
|
<button
|
|
12
12
|
className="code-button"
|
|
13
|
+
aria-label="code-button"
|
|
13
14
|
tabIndex="0"
|
|
14
15
|
height="45px"
|
|
15
16
|
onClick={() => setCodeAnchor(!codeAnchor)}
|
|
@@ -18,7 +19,8 @@ export default function ClientCodePopover({ code }) {
|
|
|
18
19
|
</button>
|
|
19
20
|
<div
|
|
20
21
|
id={`node-create-code`}
|
|
21
|
-
|
|
22
|
+
role="dialog"
|
|
23
|
+
aria-label="client-code"
|
|
22
24
|
style={{ display: codeAnchor === false ? 'none' : 'block' }}
|
|
23
25
|
>
|
|
24
26
|
<SyntaxHighlighter language="python" style={nightOwl}>
|