datajunction-ui 0.0.1-rc.23 → 0.0.1-rc.25
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/.env +1 -0
- package/package.json +3 -2
- package/src/app/components/Tab.jsx +0 -1
- package/src/app/constants.js +2 -2
- package/src/app/icons/LoadingIcon.jsx +14 -0
- package/src/app/index.tsx +11 -1
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +28 -2
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +44 -9
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +1 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +0 -50
- package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +2 -0
- package/src/app/pages/AddEditNodePage/index.jsx +60 -6
- package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
- package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
- package/src/app/pages/AddEditTagPage/index.jsx +132 -0
- package/src/app/pages/LoginPage/LoginForm.jsx +116 -0
- package/src/app/pages/LoginPage/SignupForm.jsx +144 -0
- package/src/app/pages/LoginPage/__tests__/index.test.jsx +34 -2
- package/src/app/pages/LoginPage/index.jsx +9 -82
- package/src/app/pages/NamespacePage/index.jsx +5 -0
- package/src/app/pages/NodePage/ClientCodePopover.jsx +15 -1
- package/src/app/pages/NodePage/EditColumnPopover.jsx +15 -1
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +16 -1
- package/src/app/pages/NodePage/NodeColumnTab.jsx +11 -0
- package/src/app/pages/NodePage/NodeInfoTab.jsx +5 -1
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +6 -3
- package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +1 -0
- package/src/app/pages/Root/index.tsx +1 -1
- package/src/app/pages/TagPage/Loadable.jsx +16 -0
- package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
- package/src/app/pages/TagPage/index.jsx +79 -0
- package/src/app/services/DJService.js +79 -1
- package/src/app/services/__tests__/DJService.test.jsx +84 -1
- package/src/mocks/mockNodes.jsx +88 -44
- package/src/styles/index.css +19 -0
- package/src/styles/loading.css +35 -0
- package/src/styles/login.css +17 -3
- package/src/utils/form.jsx +2 -2
package/.env
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "datajunction-ui",
|
|
3
|
-
"version": "0.0.1-rc.
|
|
3
|
+
"version": "0.0.1-rc.25",
|
|
4
4
|
"description": "DataJunction Metrics Platform UI",
|
|
5
5
|
"module": "src/index.tsx",
|
|
6
6
|
"repository": {
|
|
@@ -98,7 +98,8 @@
|
|
|
98
98
|
"web-vitals": "2.1.4",
|
|
99
99
|
"webpack": "5.81.0",
|
|
100
100
|
"webpack-cli": "5.0.2",
|
|
101
|
-
"webpack-dev-server": "4.13.3"
|
|
101
|
+
"webpack-dev-server": "4.13.3",
|
|
102
|
+
"yup": "1.3.2"
|
|
102
103
|
},
|
|
103
104
|
"scripts": {
|
|
104
105
|
"webpack-start": "webpack-dev-server --open",
|
package/src/app/constants.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const
|
|
2
|
-
export const
|
|
1
|
+
export const AUTH_COOKIE = '__dj';
|
|
2
|
+
export const LOGGED_IN_FLAG_COOKIE = '__djlif';
|
package/src/app/index.tsx
CHANGED
|
@@ -10,7 +10,9 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
|
|
10
10
|
import { NamespacePage } from './pages/NamespacePage/Loadable';
|
|
11
11
|
import { NodePage } from './pages/NodePage/Loadable';
|
|
12
12
|
import { SQLBuilderPage } from './pages/SQLBuilderPage/Loadable';
|
|
13
|
+
import { TagPage } from './pages/TagPage/Loadable';
|
|
13
14
|
import { AddEditNodePage } from './pages/AddEditNodePage/Loadable';
|
|
15
|
+
import { AddEditTagPage } from './pages/AddEditTagPage/Loadable';
|
|
14
16
|
import { NotFoundPage } from './pages/NotFoundPage/Loadable';
|
|
15
17
|
import { LoginPage } from './pages/LoginPage';
|
|
16
18
|
import { RegisterTablePage } from './pages/RegisterTablePage';
|
|
@@ -21,7 +23,7 @@ import { CookiesProvider, useCookies } from 'react-cookie';
|
|
|
21
23
|
import * as Constants from './constants';
|
|
22
24
|
|
|
23
25
|
export function App() {
|
|
24
|
-
const [cookies] = useCookies([Constants.
|
|
26
|
+
const [cookies] = useCookies([Constants.LOGGED_IN_FLAG_COOKIE]);
|
|
25
27
|
return (
|
|
26
28
|
<CookiesProvider>
|
|
27
29
|
<BrowserRouter>
|
|
@@ -60,6 +62,11 @@ export function App() {
|
|
|
60
62
|
key="namespaces"
|
|
61
63
|
/>
|
|
62
64
|
</Route>
|
|
65
|
+
<Route
|
|
66
|
+
path="create/tag"
|
|
67
|
+
key="createtag"
|
|
68
|
+
element={<AddEditTagPage />}
|
|
69
|
+
></Route>
|
|
63
70
|
<Route
|
|
64
71
|
path="create/source"
|
|
65
72
|
key="register"
|
|
@@ -82,6 +89,9 @@ export function App() {
|
|
|
82
89
|
key="sql"
|
|
83
90
|
element={<SQLBuilderPage />}
|
|
84
91
|
/>
|
|
92
|
+
<Route path="tags" key="tags">
|
|
93
|
+
<Route path=":name" element={<TagPage />} />
|
|
94
|
+
</Route>
|
|
85
95
|
</>
|
|
86
96
|
}
|
|
87
97
|
/>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
|
3
3
|
import fetchMock from 'jest-fetch-mock';
|
|
4
4
|
import userEvent from '@testing-library/user-event';
|
|
5
5
|
import {
|
|
@@ -23,6 +23,10 @@ describe('AddEditNodePage submission failed', () => {
|
|
|
23
23
|
status: 500,
|
|
24
24
|
json: { message: 'Some columns in the primary key [] were not found' },
|
|
25
25
|
});
|
|
26
|
+
mockDjClient.DataJunctionAPI.listTags.mockReturnValue([
|
|
27
|
+
{ name: 'purpose', display_name: 'Purpose' },
|
|
28
|
+
{ name: 'intent', display_name: 'Intent' },
|
|
29
|
+
]);
|
|
26
30
|
|
|
27
31
|
const element = testElement(mockDjClient);
|
|
28
32
|
const { container } = renderCreateNode(element);
|
|
@@ -63,6 +67,16 @@ describe('AddEditNodePage submission failed', () => {
|
|
|
63
67
|
json: { message: 'Update failed' },
|
|
64
68
|
});
|
|
65
69
|
|
|
70
|
+
mockDjClient.DataJunctionAPI.tagsNode.mockReturnValue({
|
|
71
|
+
status: 404,
|
|
72
|
+
json: { message: 'Some tags were not found' },
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
mockDjClient.DataJunctionAPI.listTags.mockReturnValue([
|
|
76
|
+
{ name: 'purpose', display_name: 'Purpose' },
|
|
77
|
+
{ name: 'intent', display_name: 'Intent' },
|
|
78
|
+
]);
|
|
79
|
+
|
|
66
80
|
const element = testElement(mockDjClient);
|
|
67
81
|
renderEditNode(element);
|
|
68
82
|
|
|
@@ -71,7 +85,19 @@ describe('AddEditNodePage submission failed', () => {
|
|
|
71
85
|
await userEvent.click(screen.getByText('Save'));
|
|
72
86
|
await waitFor(async () => {
|
|
73
87
|
expect(mockDjClient.DataJunctionAPI.patchNode).toBeCalledTimes(1);
|
|
74
|
-
expect(
|
|
88
|
+
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalled();
|
|
89
|
+
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalledWith(
|
|
90
|
+
'default.num_repair_orders',
|
|
91
|
+
[{ display_name: 'Purpose', name: 'purpose' }],
|
|
92
|
+
);
|
|
93
|
+
expect(mockDjClient.DataJunctionAPI.tagsNode).toReturnWith({
|
|
94
|
+
json: { message: 'Some tags were not found' },
|
|
95
|
+
status: 404,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(
|
|
99
|
+
await screen.getByText('Update failed, Some tags were not found'),
|
|
100
|
+
).toBeInTheDocument();
|
|
75
101
|
});
|
|
76
102
|
}, 60000);
|
|
77
103
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
|
3
3
|
import fetchMock from 'jest-fetch-mock';
|
|
4
4
|
import userEvent from '@testing-library/user-event';
|
|
5
5
|
import {
|
|
@@ -24,10 +24,20 @@ describe('AddEditNodePage submission succeeded', () => {
|
|
|
24
24
|
it('for creating a node', async () => {
|
|
25
25
|
const mockDjClient = initializeMockDJClient();
|
|
26
26
|
mockDjClient.DataJunctionAPI.createNode.mockReturnValue({
|
|
27
|
-
status:
|
|
28
|
-
json: {
|
|
27
|
+
status: 200,
|
|
28
|
+
json: { name: 'default.some_test_metric' },
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
mockDjClient.DataJunctionAPI.tagsNode.mockReturnValue({
|
|
32
|
+
status: 200,
|
|
33
|
+
json: { message: 'Success' },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
mockDjClient.DataJunctionAPI.listTags.mockReturnValue([
|
|
37
|
+
{ name: 'purpose', display_name: 'Purpose' },
|
|
38
|
+
{ name: 'intent', display_name: 'Intent' },
|
|
39
|
+
]);
|
|
40
|
+
|
|
31
41
|
const element = testElement(mockDjClient);
|
|
32
42
|
const { container } = renderCreateNode(element);
|
|
33
43
|
|
|
@@ -50,13 +60,18 @@ describe('AddEditNodePage submission succeeded', () => {
|
|
|
50
60
|
'default',
|
|
51
61
|
null,
|
|
52
62
|
);
|
|
53
|
-
expect(
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalled();
|
|
64
|
+
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalledWith(
|
|
65
|
+
'default.some_test_metric',
|
|
66
|
+
undefined,
|
|
67
|
+
);
|
|
68
|
+
expect(screen.getByText(/default.some_test_metric/)).toBeInTheDocument();
|
|
56
69
|
});
|
|
57
70
|
|
|
58
|
-
// After
|
|
59
|
-
expect(
|
|
71
|
+
// After successful creation, it should return a success message
|
|
72
|
+
expect(screen.getByTestId('success')).toHaveTextContent(
|
|
73
|
+
'Successfully created node default.some_test_metric',
|
|
74
|
+
);
|
|
60
75
|
}, 60000);
|
|
61
76
|
|
|
62
77
|
it('for editing a node', async () => {
|
|
@@ -69,13 +84,27 @@ describe('AddEditNodePage submission succeeded', () => {
|
|
|
69
84
|
json: { name: 'default.num_repair_orders', type: 'metric' },
|
|
70
85
|
});
|
|
71
86
|
|
|
87
|
+
mockDjClient.DataJunctionAPI.tagsNode.mockReturnValue({
|
|
88
|
+
status: 200,
|
|
89
|
+
json: { message: 'Success' },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
mockDjClient.DataJunctionAPI.listTags.mockReturnValue([
|
|
93
|
+
{ name: 'purpose', display_name: 'Purpose' },
|
|
94
|
+
{ name: 'intent', display_name: 'Intent' },
|
|
95
|
+
]);
|
|
96
|
+
|
|
72
97
|
const element = testElement(mockDjClient);
|
|
73
|
-
renderEditNode(element);
|
|
98
|
+
const { getByTestId } = renderEditNode(element);
|
|
74
99
|
|
|
75
100
|
await userEvent.type(screen.getByLabelText('Display Name'), '!!!');
|
|
76
101
|
await userEvent.type(screen.getByLabelText('Description'), '!!!');
|
|
77
102
|
await userEvent.click(screen.getByText('Save'));
|
|
78
103
|
|
|
104
|
+
const selectTags = getByTestId('select-tags');
|
|
105
|
+
await fireEvent.keyDown(selectTags.firstChild, { key: 'ArrowDown' });
|
|
106
|
+
await fireEvent.click(screen.getByText('Purpose'));
|
|
107
|
+
|
|
79
108
|
await waitFor(async () => {
|
|
80
109
|
expect(mockDjClient.DataJunctionAPI.patchNode).toBeCalledTimes(1);
|
|
81
110
|
expect(mockDjClient.DataJunctionAPI.patchNode).toBeCalledWith(
|
|
@@ -86,6 +115,12 @@ describe('AddEditNodePage submission succeeded', () => {
|
|
|
86
115
|
'published',
|
|
87
116
|
['repair_order_id', 'country'],
|
|
88
117
|
);
|
|
118
|
+
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalledTimes(1);
|
|
119
|
+
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalledWith(
|
|
120
|
+
'default.num_repair_orders',
|
|
121
|
+
[{ display_name: 'Purpose', name: 'purpose' }],
|
|
122
|
+
);
|
|
123
|
+
|
|
89
124
|
expect(
|
|
90
125
|
await screen.getByDisplayValue('repair_order_id, country'),
|
|
91
126
|
).toBeInTheDocument();
|
|
@@ -1,53 +1,3 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
3
|
exports[`AddEditNodePage Create node page renders with the selected nodeType and namespace 1`] = `HTMLCollection []`;
|
|
4
|
-
|
|
5
|
-
exports[`AddEditNodePage submission succeeded for creating a node 1`] = `
|
|
6
|
-
HTMLCollection [
|
|
7
|
-
<div
|
|
8
|
-
class="message alert"
|
|
9
|
-
>
|
|
10
|
-
<svg
|
|
11
|
-
height="2em"
|
|
12
|
-
version="1.1"
|
|
13
|
-
viewBox="0 0 24 24"
|
|
14
|
-
width="2em"
|
|
15
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
16
|
-
>
|
|
17
|
-
<title>
|
|
18
|
-
alert_fill
|
|
19
|
-
</title>
|
|
20
|
-
<g
|
|
21
|
-
fill="none"
|
|
22
|
-
fill-rule="evenodd"
|
|
23
|
-
id="page-1"
|
|
24
|
-
stroke="none"
|
|
25
|
-
stroke-width="1"
|
|
26
|
-
>
|
|
27
|
-
<g
|
|
28
|
-
fill-rule="nonzero"
|
|
29
|
-
id="System"
|
|
30
|
-
transform="translate(-48.000000, -48.000000)"
|
|
31
|
-
>
|
|
32
|
-
<g
|
|
33
|
-
id="alert_fill"
|
|
34
|
-
transform="translate(48.000000, 48.000000)"
|
|
35
|
-
>
|
|
36
|
-
<path
|
|
37
|
-
d="M24,0 L24,24 L0,24 L0,0 L24,0 Z M12.5934901,23.257841 L12.5819402,23.2595131 L12.5108777,23.2950439 L12.4918791,23.2987469 L12.4918791,23.2987469 L12.4767152,23.2950439 L12.4056548,23.2595131 C12.3958229,23.2563662 12.3870493,23.2590235 12.3821421,23.2649074 L12.3780323,23.275831 L12.360941,23.7031097 L12.3658947,23.7234994 L12.3769048,23.7357139 L12.4804777,23.8096931 L12.4953491,23.8136134 L12.4953491,23.8136134 L12.5071152,23.8096931 L12.6106902,23.7357139 L12.6232938,23.7196733 L12.6232938,23.7196733 L12.6266527,23.7031097 L12.609561,23.275831 C12.6075724,23.2657013 12.6010112,23.2592993 12.5934901,23.257841 L12.5934901,23.257841 Z M12.8583906,23.1452862 L12.8445485,23.1473072 L12.6598443,23.2396597 L12.6498822,23.2499052 L12.6498822,23.2499052 L12.6471943,23.2611114 L12.6650943,23.6906389 L12.6699349,23.7034178 L12.6699349,23.7034178 L12.678386,23.7104931 L12.8793402,23.8032389 C12.8914285,23.8068999 12.9022333,23.8029875 12.9078286,23.7952264 L12.9118235,23.7811639 L12.8776777,23.1665331 C12.8752882,23.1545897 12.8674102,23.1470016 12.8583906,23.1452862 L12.8583906,23.1452862 Z M12.1430473,23.1473072 C12.1332178,23.1423925 12.1221763,23.1452606 12.1156365,23.1525954 L12.1099173,23.1665331 L12.0757714,23.7811639 C12.0751323,23.7926639 12.0828099,23.8018602 12.0926481,23.8045676 L12.108256,23.8032389 L12.3092106,23.7104931 L12.3186497,23.7024347 L12.3186497,23.7024347 L12.3225043,23.6906389 L12.340401,23.2611114 L12.337245,23.2485176 L12.337245,23.2485176 L12.3277531,23.2396597 L12.1430473,23.1473072 Z"
|
|
38
|
-
fill-rule="nonzero"
|
|
39
|
-
id="MingCute"
|
|
40
|
-
/>
|
|
41
|
-
<path
|
|
42
|
-
d="M13.299,3.1477 L21.933,18.1022 C22.5103,19.1022 21.7887,20.3522 20.634,20.3522 L3.36601,20.3522 C2.21131,20.3522 1.48962,19.1022 2.06697,18.1022 L10.7009,3.14771 C11.2783,2.14771 12.7217,2.1477 13.299,3.1477 Z M12,15 C11.4477,15 11,15.4477 11,16 C11,16.5523 11.4477,17 12,17 C12.5523,17 13,16.5523 13,16 C13,15.4477 12.5523,15 12,15 Z M12,8 C11.48715,8 11.0644908,8.38604429 11.0067275,8.88337975 L11,9 L11,13 C11,13.5523 11.4477,14 12,14 C12.51285,14 12.9355092,13.613973 12.9932725,13.1166239 L13,13 L13,9 C13,8.44772 12.5523,8 12,8 Z"
|
|
43
|
-
fill="#09244B"
|
|
44
|
-
id="shape"
|
|
45
|
-
/>
|
|
46
|
-
</g>
|
|
47
|
-
</g>
|
|
48
|
-
</g>
|
|
49
|
-
</svg>
|
|
50
|
-
Some columns in the primary key [] were not found
|
|
51
|
-
</div>,
|
|
52
|
-
]
|
|
53
|
-
`;
|
|
@@ -10,12 +10,11 @@ import { useContext, useEffect, useState } from 'react';
|
|
|
10
10
|
import DJClientContext from '../../providers/djclient';
|
|
11
11
|
import 'styles/node-creation.scss';
|
|
12
12
|
import AlertIcon from '../../icons/AlertIcon';
|
|
13
|
-
import ValidIcon from '../../icons/ValidIcon';
|
|
14
13
|
import { useParams } from 'react-router-dom';
|
|
15
14
|
import { FullNameField } from './FullNameField';
|
|
16
15
|
import { FormikSelect } from './FormikSelect';
|
|
17
16
|
import { NodeQueryField } from './NodeQueryField';
|
|
18
|
-
import { displayMessageAfterSubmit } from '../../../utils/form';
|
|
17
|
+
import { displayMessageAfterSubmit, labelize } from '../../../utils/form';
|
|
19
18
|
|
|
20
19
|
class Action {
|
|
21
20
|
static Add = new Action('add');
|
|
@@ -33,6 +32,7 @@ export function AddEditNodePage() {
|
|
|
33
32
|
const action = name !== undefined ? Action.Edit : Action.Add;
|
|
34
33
|
|
|
35
34
|
const [namespaces, setNamespaces] = useState([]);
|
|
35
|
+
const [tags, setTags] = useState([]);
|
|
36
36
|
|
|
37
37
|
const initialValues = {
|
|
38
38
|
name: action === Action.Edit ? name : '',
|
|
@@ -110,6 +110,7 @@ export function AddEditNodePage() {
|
|
|
110
110
|
values.primary_key ? primaryKeyToList(values.primary_key) : null,
|
|
111
111
|
);
|
|
112
112
|
if (status === 200 || status === 201) {
|
|
113
|
+
await djClient.tagsNode(values.name, values.tags);
|
|
113
114
|
setStatus({
|
|
114
115
|
success: (
|
|
115
116
|
<>
|
|
@@ -134,7 +135,8 @@ export function AddEditNodePage() {
|
|
|
134
135
|
values.mode,
|
|
135
136
|
values.primary_key ? primaryKeyToList(values.primary_key) : null,
|
|
136
137
|
);
|
|
137
|
-
|
|
138
|
+
const tagsResponse = await djClient.tagsNode(values.name, values.tags);
|
|
139
|
+
if ((status === 200 || status === 201) && tagsResponse.status === 200) {
|
|
138
140
|
setStatus({
|
|
139
141
|
success: (
|
|
140
142
|
<>
|
|
@@ -145,7 +147,7 @@ export function AddEditNodePage() {
|
|
|
145
147
|
});
|
|
146
148
|
} else {
|
|
147
149
|
setStatus({
|
|
148
|
-
failure: `${json.message}`,
|
|
150
|
+
failure: `${json.message}, ${tagsResponse.json.message}`,
|
|
149
151
|
});
|
|
150
152
|
}
|
|
151
153
|
};
|
|
@@ -186,6 +188,7 @@ export function AddEditNodePage() {
|
|
|
186
188
|
'description',
|
|
187
189
|
'primary_key',
|
|
188
190
|
'mode',
|
|
191
|
+
'tags',
|
|
189
192
|
];
|
|
190
193
|
fields.forEach(field => {
|
|
191
194
|
if (
|
|
@@ -194,8 +197,9 @@ export function AddEditNodePage() {
|
|
|
194
197
|
Array.isArray(data[field])
|
|
195
198
|
) {
|
|
196
199
|
data[field] = data[field].join(', ');
|
|
200
|
+
} else {
|
|
201
|
+
setFieldValue(field, data[field] || '', false);
|
|
197
202
|
}
|
|
198
|
-
setFieldValue(field, data[field] || '', false);
|
|
199
203
|
});
|
|
200
204
|
};
|
|
201
205
|
|
|
@@ -224,6 +228,20 @@ export function AddEditNodePage() {
|
|
|
224
228
|
}
|
|
225
229
|
}, [action, djClient, djClient.metrics]);
|
|
226
230
|
|
|
231
|
+
// Get list of tags
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
const fetchData = async () => {
|
|
234
|
+
const tags = await djClient.listTags();
|
|
235
|
+
setTags(
|
|
236
|
+
tags.map(tag => ({
|
|
237
|
+
value: tag.name,
|
|
238
|
+
label: tag.display_name,
|
|
239
|
+
})),
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
fetchData().catch(console.error);
|
|
243
|
+
}, [djClient, djClient.listTags]);
|
|
244
|
+
|
|
227
245
|
return (
|
|
228
246
|
<div className="mid">
|
|
229
247
|
<NamespaceHeader namespace="" />
|
|
@@ -238,7 +256,31 @@ export function AddEditNodePage() {
|
|
|
238
256
|
>
|
|
239
257
|
{function Render({ isSubmitting, status, setFieldValue }) {
|
|
240
258
|
const [node, setNode] = useState([]);
|
|
259
|
+
const [selectTags, setSelectTags] = useState(null);
|
|
241
260
|
const [message, setMessage] = useState('');
|
|
261
|
+
|
|
262
|
+
const tagsInput = (
|
|
263
|
+
<div
|
|
264
|
+
className="TagsInput"
|
|
265
|
+
style={{ width: '25%', margin: '1rem 0 1rem 1.2rem' }}
|
|
266
|
+
>
|
|
267
|
+
<ErrorMessage name="tags" component="span" />
|
|
268
|
+
<label htmlFor="react-select-3-input">Tags</label>
|
|
269
|
+
<span data-testid="select-tags">
|
|
270
|
+
{action === Action.Edit ? (
|
|
271
|
+
selectTags
|
|
272
|
+
) : (
|
|
273
|
+
<FormikSelect
|
|
274
|
+
isMulti={true}
|
|
275
|
+
selectOptions={tags}
|
|
276
|
+
formikFieldName="tags"
|
|
277
|
+
placeholder="Choose Tags"
|
|
278
|
+
/>
|
|
279
|
+
)}
|
|
280
|
+
</span>
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
|
|
242
284
|
useEffect(() => {
|
|
243
285
|
const fetchData = async () => {
|
|
244
286
|
if (action === Action.Edit) {
|
|
@@ -263,10 +305,21 @@ export function AddEditNodePage() {
|
|
|
263
305
|
// Update fields with existing data to prepare for edit
|
|
264
306
|
updateFieldsWithNodeData(data, setFieldValue);
|
|
265
307
|
setNode(data);
|
|
308
|
+
setSelectTags(
|
|
309
|
+
<FormikSelect
|
|
310
|
+
isMulti={true}
|
|
311
|
+
selectOptions={tags}
|
|
312
|
+
formikFieldName="tags"
|
|
313
|
+
placeholder="Choose Tags"
|
|
314
|
+
defaultValue={data.tags.map(t => {
|
|
315
|
+
return { value: t.name, label: t.display_name };
|
|
316
|
+
})}
|
|
317
|
+
/>,
|
|
318
|
+
);
|
|
266
319
|
}
|
|
267
320
|
};
|
|
268
321
|
fetchData().catch(console.error);
|
|
269
|
-
}, [setFieldValue]);
|
|
322
|
+
}, [setFieldValue, tags]);
|
|
270
323
|
return (
|
|
271
324
|
<Form>
|
|
272
325
|
{displayMessageAfterSubmit(status)}
|
|
@@ -317,6 +370,7 @@ export function AddEditNodePage() {
|
|
|
317
370
|
placeholder="Comma-separated list of PKs"
|
|
318
371
|
/>
|
|
319
372
|
</div>
|
|
373
|
+
{tagsInput}
|
|
320
374
|
<div className="NodeModeInput NodeCreationInput">
|
|
321
375
|
<ErrorMessage name="mode" component="span" />
|
|
322
376
|
<label htmlFor="Mode">Mode</label>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asynchronously loads the component for the Node page
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { lazyLoad } from '../../../utils/loadable';
|
|
7
|
+
|
|
8
|
+
export const AddEditTagPage = () => {
|
|
9
|
+
return lazyLoad(
|
|
10
|
+
() => import('./index'),
|
|
11
|
+
module => module.AddEditTagPage,
|
|
12
|
+
{
|
|
13
|
+
fallback: <div></div>,
|
|
14
|
+
},
|
|
15
|
+
)();
|
|
16
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import fetchMock from 'jest-fetch-mock';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import { render } from '../../../../setupTests';
|
|
6
|
+
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
7
|
+
import DJClientContext from '../../../providers/djclient';
|
|
8
|
+
import { AddEditTagPage } from '../index';
|
|
9
|
+
|
|
10
|
+
describe('<AddEditTagPage />', () => {
|
|
11
|
+
const initializeMockDJClient = () => {
|
|
12
|
+
return {
|
|
13
|
+
DataJunctionAPI: {
|
|
14
|
+
addTag: jest.fn(),
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const mockDjClient = initializeMockDJClient();
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
fetchMock.resetMocks();
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
window.scrollTo = jest.fn();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const renderAddEditTagPage = element => {
|
|
28
|
+
return render(
|
|
29
|
+
<MemoryRouter initialEntries={['/create/tag']}>
|
|
30
|
+
<Routes>
|
|
31
|
+
<Route path="create/tag" element={element} />
|
|
32
|
+
</Routes>
|
|
33
|
+
</MemoryRouter>,
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const testElement = djClient => {
|
|
38
|
+
return (
|
|
39
|
+
<DJClientContext.Provider value={djClient}>
|
|
40
|
+
<AddEditTagPage />
|
|
41
|
+
</DJClientContext.Provider>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
it('adds a tag correctly', async () => {
|
|
46
|
+
mockDjClient.DataJunctionAPI.addTag.mockReturnValue({
|
|
47
|
+
status: 200,
|
|
48
|
+
json: {
|
|
49
|
+
name: 'amanita_muscaria',
|
|
50
|
+
display_name: 'Amanita Muscaria',
|
|
51
|
+
tag_type: 'fungi',
|
|
52
|
+
description: 'Fly agaric, is poisonous',
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const element = testElement(mockDjClient);
|
|
57
|
+
renderAddEditTagPage(element);
|
|
58
|
+
|
|
59
|
+
await userEvent.type(screen.getByLabelText('Name'), 'amanita_muscaria');
|
|
60
|
+
await userEvent.type(
|
|
61
|
+
screen.getByLabelText('Display Name'),
|
|
62
|
+
'Amanita Muscaria',
|
|
63
|
+
);
|
|
64
|
+
await userEvent.type(screen.getByLabelText('Tag Type'), 'fungi');
|
|
65
|
+
await userEvent.click(screen.getByRole('button'));
|
|
66
|
+
|
|
67
|
+
await waitFor(() => {
|
|
68
|
+
expect(mockDjClient.DataJunctionAPI.addTag).toBeCalled();
|
|
69
|
+
expect(mockDjClient.DataJunctionAPI.addTag).toBeCalledWith(
|
|
70
|
+
'amanita_muscaria',
|
|
71
|
+
'Amanita Muscaria',
|
|
72
|
+
'fungi',
|
|
73
|
+
undefined,
|
|
74
|
+
);
|
|
75
|
+
expect(screen.getByTestId('success')).toHaveTextContent(
|
|
76
|
+
'Successfully added tag Amanita Muscaria',
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
}, 60000);
|
|
80
|
+
|
|
81
|
+
it('fails to add a tag', async () => {
|
|
82
|
+
mockDjClient.DataJunctionAPI.addTag.mockReturnValue({
|
|
83
|
+
status: 500,
|
|
84
|
+
json: { message: 'Tag exists' },
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const element = testElement(mockDjClient);
|
|
88
|
+
renderAddEditTagPage(element);
|
|
89
|
+
|
|
90
|
+
await userEvent.click(screen.getByRole('button'));
|
|
91
|
+
|
|
92
|
+
await userEvent.type(screen.getByLabelText('Name'), 'amanita_muscaria');
|
|
93
|
+
await userEvent.type(
|
|
94
|
+
screen.getByLabelText('Display Name'),
|
|
95
|
+
'Amanita Muscaria',
|
|
96
|
+
);
|
|
97
|
+
await userEvent.type(screen.getByLabelText('Tag Type'), 'fungi');
|
|
98
|
+
await userEvent.click(screen.getByRole('button'));
|
|
99
|
+
|
|
100
|
+
await waitFor(() => {
|
|
101
|
+
expect(mockDjClient.DataJunctionAPI.addTag).toBeCalled();
|
|
102
|
+
expect(screen.getByTestId('failure')).toHaveTextContent(
|
|
103
|
+
'alert_fillTag exists',
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
}, 60000);
|
|
107
|
+
});
|