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.
Files changed (66) hide show
  1. package/package.json +6 -5
  2. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  3. package/src/app/components/DeleteNode.jsx +79 -0
  4. package/src/app/components/ListGroupItem.jsx +8 -1
  5. package/src/app/components/QueryInfo.jsx +4 -4
  6. package/src/app/components/Tab.jsx +9 -1
  7. package/src/app/components/ToggleSwitch.jsx +3 -0
  8. package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
  9. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  10. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  11. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +3 -0
  12. package/src/app/components/djgraph/DJNodeColumns.jsx +4 -1
  13. package/src/app/components/djgraph/DJNodeDimensions.jsx +9 -2
  14. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  15. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  16. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  17. package/src/app/index.tsx +6 -0
  18. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +15 -2
  19. package/src/app/pages/AddEditNodePage/FullNameField.jsx +2 -1
  20. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +77 -0
  21. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +93 -0
  22. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +34 -3
  23. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +4 -2
  24. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +53 -0
  25. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +53 -0
  26. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +0 -81
  27. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +82 -257
  28. package/src/app/pages/AddEditNodePage/index.jsx +13 -5
  29. package/src/app/pages/LoginPage/__tests__/index.test.jsx +70 -0
  30. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +95 -0
  31. package/src/app/pages/NamespacePage/index.jsx +8 -5
  32. package/src/app/pages/NodePage/ClientCodePopover.jsx +3 -1
  33. package/src/app/pages/NodePage/EditColumnPopover.jsx +102 -0
  34. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +135 -0
  35. package/src/app/pages/NodePage/NodeColumnTab.jsx +80 -17
  36. package/src/app/pages/NodePage/NodeHistory.jsx +63 -30
  37. package/src/app/pages/NodePage/NodeInfoTab.jsx +52 -7
  38. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +50 -27
  39. package/src/app/pages/NodePage/NodeSQLTab.jsx +0 -10
  40. package/src/app/pages/NodePage/NodesWithDimension.jsx +4 -2
  41. package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +49 -0
  42. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
  43. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +165 -0
  44. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +591 -0
  45. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +57 -0
  46. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +725 -0
  47. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  48. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +402 -0
  49. package/src/app/pages/NodePage/index.jsx +22 -6
  50. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  51. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  52. package/src/app/pages/RegisterTablePage/index.jsx +163 -0
  53. package/src/app/pages/Root/__tests__/index.test.jsx +77 -0
  54. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  55. package/src/app/pages/SQLBuilderPage/index.jsx +61 -43
  56. package/src/app/services/DJService.js +125 -54
  57. package/src/app/services/__tests__/DJService.test.jsx +609 -0
  58. package/src/mocks/mockNodes.jsx +1397 -0
  59. package/src/setupTests.ts +30 -0
  60. package/src/styles/index.css +43 -0
  61. package/src/styles/node-creation.scss +7 -0
  62. package/src/utils/form.jsx +23 -0
  63. package/.github/pull_request_template.md +0 -11
  64. package/.github/workflows/ci.yml +0 -33
  65. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -118
  66. 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 { act, render, screen, waitFor } from '@testing-library/react';
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
- describe('AddEditNodePage', () => {
12
- const initializeMockDJClient = () => {
13
- return {
14
- DataJunctionAPI: {
15
- namespace: _ => {
16
- return [
17
- {
18
- name: 'default.contractors',
19
- display_name: 'Default: Contractors',
20
- version: 'v1.0',
21
- type: 'source',
22
- status: 'valid',
23
- mode: 'published',
24
- updated_at: '2023-08-21T16:48:53.246914+00:00',
25
- },
26
- {
27
- name: 'default.num_repair_orders',
28
- display_name: 'Default: Num Repair Orders',
29
- version: 'v1.0',
30
- type: 'metric',
31
- status: 'valid',
32
- mode: 'published',
33
- updated_at: '2023-08-21T16:48:56.841704+00:00',
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
- updated_at: '2023-08-21T16:48:56.841704+00:00',
94
- materializations: [],
95
- parents: [
96
- {
97
- name: 'default.repair_orders',
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
- created_at: '2023-08-21T16:48:56.841631+00:00',
101
- tags: [],
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
- const renderCreateNode = element => {
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
- expect(
137
- container.getElementsByClassName('node_type__metric'),
138
- ).toMatchSnapshot();
99
+ await waitFor(() => {
100
+ expect(
101
+ container.getElementsByClassName('node_type__metric'),
102
+ ).toMatchSnapshot();
139
103
 
140
- // The namespace should be set to the one provided in params
141
- expect(screen.getByText('default')).toBeInTheDocument();
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.split(',') : null,
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.split(',') : null,
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 (field === 'primary_key' && data[field] !== undefined) {
203
- data[field] = data[field].join(',');
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 DeleteIcon from '../../icons/DeleteIcon';
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
- <a href="#" style={{ marginLeft: '0.5rem' }}>
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
- onClose={() => setCodeAnchor(null)}
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}>