@truedat/dq 5.1.1 → 5.2.0
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/CHANGELOG.md +6 -0
- package/package.json +5 -5
- package/src/components/ImplementationsUploadButton.js +30 -6
- package/src/components/__tests__/ImplementationsUploadButton.spec.js +120 -11
- package/src/components/__tests__/UploadModal.spec.js +158 -0
- package/src/sagas/__tests__/uploadImplementations.spec.js +4 -2
- package/src/sagas/uploadImplementations.js +1 -1
- package/src/components/__tests__/__snapshots__/ImplementationsUploadButton.spec.js.snap +0 -3
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/dq",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "Truedat Web Data Quality Module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"jsnext:main": "src/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@testing-library/jest-dom": "^5.16.5",
|
|
35
35
|
"@testing-library/react": "^12.0.0",
|
|
36
36
|
"@testing-library/user-event": "^13.2.1",
|
|
37
|
-
"@truedat/test": "5.0
|
|
37
|
+
"@truedat/test": "5.2.0",
|
|
38
38
|
"babel-jest": "^28.1.0",
|
|
39
39
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
40
40
|
"babel-plugin-lodash": "^3.3.4",
|
|
@@ -92,8 +92,8 @@
|
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
94
|
"@apollo/client": "^3.7.1",
|
|
95
|
-
"@truedat/core": "5.0
|
|
96
|
-
"@truedat/df": "5.0
|
|
95
|
+
"@truedat/core": "5.2.0",
|
|
96
|
+
"@truedat/df": "5.2.0",
|
|
97
97
|
"decode-uri-component": "^0.2.2",
|
|
98
98
|
"graphql": "^15.5.3",
|
|
99
99
|
"moment": "^2.29.4",
|
|
@@ -118,5 +118,5 @@
|
|
|
118
118
|
"react-dom": ">= 16.8.6 < 17",
|
|
119
119
|
"semantic-ui-react": ">= 2.0.3 < 2.2"
|
|
120
120
|
},
|
|
121
|
-
"gitHead": "
|
|
121
|
+
"gitHead": "20710c8ea4c857547cdaeac20dfada1bdf64e9d2"
|
|
122
122
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import PropTypes from "prop-types";
|
|
3
4
|
import { connect } from "react-redux";
|
|
@@ -13,13 +14,29 @@ const uploadAction = {
|
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
export const ImplementationsUploadButton = ({
|
|
17
|
+
canAutoPublish,
|
|
16
18
|
uploadImplementations,
|
|
17
19
|
loading,
|
|
18
20
|
}) => {
|
|
19
21
|
const { formatMessage } = useIntl();
|
|
22
|
+
const extraAction = {
|
|
23
|
+
key: "yesWithAutoPublish",
|
|
24
|
+
primary: true,
|
|
25
|
+
content: formatMessage({ id: "uploadModal.accept.publish" }),
|
|
26
|
+
onClick: (formData) => {
|
|
27
|
+
// UploadModal is a Modal, not a Form, but sends FormData
|
|
28
|
+
formData.append("auto_publish", true);
|
|
29
|
+
return uploadImplementations({
|
|
30
|
+
action: "upload",
|
|
31
|
+
formData,
|
|
32
|
+
...uploadAction,
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
};
|
|
20
36
|
return (
|
|
21
37
|
<UploadModal
|
|
22
38
|
icon="upload"
|
|
39
|
+
extraAction={canAutoPublish ? extraAction : undefined}
|
|
23
40
|
trigger={
|
|
24
41
|
<Dropdown.Item
|
|
25
42
|
icon="upload"
|
|
@@ -36,24 +53,31 @@ export const ImplementationsUploadButton = ({
|
|
|
36
53
|
<FormattedMessage id="ruleImplementations.actions.upload.confirmation.content" />
|
|
37
54
|
}
|
|
38
55
|
param="implementations"
|
|
39
|
-
handleSubmit
|
|
40
|
-
|
|
56
|
+
// handleSubmit is the onClick for the "yes" action
|
|
57
|
+
handleSubmit={(formData) => {
|
|
58
|
+
return uploadImplementations({
|
|
41
59
|
action: "upload",
|
|
42
|
-
|
|
60
|
+
formData,
|
|
43
61
|
...uploadAction,
|
|
44
|
-
})
|
|
45
|
-
}
|
|
62
|
+
});
|
|
63
|
+
}}
|
|
46
64
|
/>
|
|
47
65
|
);
|
|
48
66
|
};
|
|
49
67
|
|
|
50
68
|
ImplementationsUploadButton.propTypes = {
|
|
69
|
+
canAutoPublish: PropTypes.bool,
|
|
51
70
|
uploadImplementations: PropTypes.func,
|
|
52
71
|
loading: PropTypes.bool,
|
|
53
72
|
};
|
|
54
73
|
|
|
55
|
-
const mapStateToProps = ({
|
|
74
|
+
const mapStateToProps = ({
|
|
75
|
+
uploadingImplementationsFile,
|
|
76
|
+
implementationsActions,
|
|
77
|
+
}) => ({
|
|
78
|
+
canAutoPublish: !_.isEmpty(implementationsActions?.autoPublish),
|
|
56
79
|
loading: uploadingImplementationsFile,
|
|
80
|
+
implementationsActions,
|
|
57
81
|
});
|
|
58
82
|
|
|
59
83
|
export default connect(mapStateToProps, { uploadImplementations })(
|
|
@@ -1,23 +1,132 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { render } from "@truedat/test/render";
|
|
3
|
+
import { waitFor, fireEvent } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
3
5
|
import { ImplementationsUploadButton } from "../ImplementationsUploadButton";
|
|
4
6
|
|
|
5
7
|
describe("<ImplementationsUploadButton />", () => {
|
|
8
|
+
const uploadImplementationsMock = jest.fn();
|
|
6
9
|
const props = {
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
uploadImplementations: uploadImplementationsMock,
|
|
11
|
+
loading: false,
|
|
9
12
|
};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
// See https://react-dropzone.js.org/ "Testing" section
|
|
15
|
+
const mockData = (files) => {
|
|
16
|
+
return {
|
|
17
|
+
dataTransfer: {
|
|
18
|
+
files,
|
|
19
|
+
items: files.map((file) => ({
|
|
20
|
+
kind: "file",
|
|
21
|
+
type: file.type,
|
|
22
|
+
getAsFile: () => file,
|
|
23
|
+
})),
|
|
24
|
+
types: ["Files"],
|
|
25
|
+
},
|
|
26
|
+
};
|
|
14
27
|
};
|
|
15
28
|
|
|
16
|
-
it("
|
|
17
|
-
const {
|
|
18
|
-
<ImplementationsUploadButton {...props}
|
|
19
|
-
|
|
29
|
+
it("shows the modal", async () => {
|
|
30
|
+
const { getByRole, getByText } = render(
|
|
31
|
+
<ImplementationsUploadButton {...{ ...props, canAutoPublish: true }} />
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
await waitFor(() => {
|
|
35
|
+
userEvent.click(getByRole("option", { name: "Upload Implementations" }));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const uploadButton = getByRole("button", { name: "Upload and update" });
|
|
39
|
+
expect(uploadButton).toBeDisabled();
|
|
40
|
+
const publishButton = getByRole("button", { name: "Upload and publish" });
|
|
41
|
+
expect(publishButton).toBeDisabled();
|
|
42
|
+
|
|
43
|
+
expect(getByText("Upload Implementations")).toBeInTheDocument();
|
|
44
|
+
expect(
|
|
45
|
+
getByText("Drag an drop file or click to select file")
|
|
46
|
+
).toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("false canAutoPublish makes the modal not show the publish button; update using an implementations CSV", async () => {
|
|
50
|
+
const { getByRole, queryByRole, getByTestId } = render(
|
|
51
|
+
<ImplementationsUploadButton {...{ ...props, canAutoPublish: false }} />
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
await waitFor(() => {
|
|
55
|
+
userEvent.click(getByRole("option", { name: "Upload Implementations" }));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const updateButton = getByRole("button", { name: "Upload and update" });
|
|
59
|
+
expect(updateButton).toBeDisabled();
|
|
60
|
+
|
|
61
|
+
const publishButton = queryByRole("button", { name: "Upload and publish" });
|
|
62
|
+
expect(publishButton).not.toBeInTheDocument();
|
|
63
|
+
|
|
64
|
+
const fakeFile = new File(["implementations"], "implementations.csv", {
|
|
65
|
+
type: "text/csv",
|
|
66
|
+
});
|
|
67
|
+
const formData = new FormData();
|
|
68
|
+
formData.append("implementations", fakeFile);
|
|
69
|
+
const dropZone = getByTestId("fileDropZone");
|
|
70
|
+
const data = mockData([fakeFile]);
|
|
71
|
+
|
|
72
|
+
await waitFor(() => {
|
|
73
|
+
fireEvent.drop(dropZone, data);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(updateButton).toBeEnabled();
|
|
77
|
+
|
|
78
|
+
await waitFor(() => {
|
|
79
|
+
userEvent.click(updateButton);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(uploadImplementationsMock).toHaveBeenCalledWith({
|
|
83
|
+
action: "upload",
|
|
84
|
+
formData,
|
|
85
|
+
href: "/api/rule_implementations/upload",
|
|
86
|
+
method: "POST",
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("true canAutoPublish makes the modal show the publish button; publishing an implementations CSV puts auto_publish FormData to true", async () => {
|
|
91
|
+
const { getByRole, getByTestId } = render(
|
|
92
|
+
<ImplementationsUploadButton {...{ ...props, canAutoPublish: true }} />
|
|
20
93
|
);
|
|
21
|
-
|
|
94
|
+
|
|
95
|
+
await waitFor(() => {
|
|
96
|
+
userEvent.click(getByRole("option", { name: "Upload Implementations" }));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const updateButton = getByRole("button", { name: "Upload and update" });
|
|
100
|
+
expect(updateButton).toBeDisabled();
|
|
101
|
+
|
|
102
|
+
const publishButton = getByRole("button", { name: "Upload and publish" });
|
|
103
|
+
expect(updateButton).toBeDisabled();
|
|
104
|
+
|
|
105
|
+
const fakeFile = new File(["implementations"], "implementations.csv", {
|
|
106
|
+
type: "text/csv",
|
|
107
|
+
});
|
|
108
|
+
const formData = new FormData();
|
|
109
|
+
formData.append("implementations", fakeFile);
|
|
110
|
+
formData.append("auto_publish", true);
|
|
111
|
+
const dropZone = getByTestId("fileDropZone");
|
|
112
|
+
const data = mockData([fakeFile]);
|
|
113
|
+
|
|
114
|
+
await waitFor(() => {
|
|
115
|
+
fireEvent.drop(dropZone, data);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(updateButton).toBeEnabled();
|
|
119
|
+
expect(publishButton).toBeEnabled();
|
|
120
|
+
|
|
121
|
+
await waitFor(() => {
|
|
122
|
+
userEvent.click(publishButton);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(uploadImplementationsMock).toHaveBeenCalledWith({
|
|
126
|
+
action: "upload",
|
|
127
|
+
formData,
|
|
128
|
+
href: "/api/rule_implementations/upload",
|
|
129
|
+
method: "POST",
|
|
130
|
+
});
|
|
22
131
|
});
|
|
23
132
|
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Dropdown } from "semantic-ui-react";
|
|
3
|
+
import { render } from "@truedat/test/render";
|
|
4
|
+
import { waitFor, fireEvent } from "@testing-library/react";
|
|
5
|
+
import userEvent from "@testing-library/user-event";
|
|
6
|
+
import { UploadModal } from "@truedat/core/components";
|
|
7
|
+
|
|
8
|
+
describe("<ImplementationsUploadButton />", () => {
|
|
9
|
+
const trigger = <Dropdown.Item icon="upload" text="Trigger element" />;
|
|
10
|
+
const onClickExtraAction = jest.fn();
|
|
11
|
+
const onClickYesAction = jest.fn();
|
|
12
|
+
|
|
13
|
+
// See https://react-dropzone.js.org/ "Testing" section
|
|
14
|
+
const mockData = (files) => {
|
|
15
|
+
return {
|
|
16
|
+
dataTransfer: {
|
|
17
|
+
files,
|
|
18
|
+
items: files.map((file) => ({
|
|
19
|
+
kind: "file",
|
|
20
|
+
type: file.type,
|
|
21
|
+
getAsFile: () => file,
|
|
22
|
+
})),
|
|
23
|
+
types: ["Files"],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const extraAction = {
|
|
29
|
+
key: "yesWithAutoPublish",
|
|
30
|
+
primary: true,
|
|
31
|
+
content: "Extra action button",
|
|
32
|
+
onClick: onClickExtraAction,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
it("shows the modal", async () => {
|
|
36
|
+
const { getByRole, getByText } = render(
|
|
37
|
+
<UploadModal
|
|
38
|
+
{...{
|
|
39
|
+
trigger,
|
|
40
|
+
extraAction,
|
|
41
|
+
header: "header",
|
|
42
|
+
content: "content",
|
|
43
|
+
param: "implementations",
|
|
44
|
+
handleSubmit: onClickYesAction,
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
await waitFor(() => {
|
|
50
|
+
userEvent.click(getByRole("option", { name: "Trigger element" }));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const yesActionButton = getByRole("button", { name: "Upload and update" });
|
|
54
|
+
expect(yesActionButton).toBeDisabled();
|
|
55
|
+
|
|
56
|
+
const extraActionButton = getByRole("button", {
|
|
57
|
+
name: "Extra action button",
|
|
58
|
+
});
|
|
59
|
+
expect(extraActionButton).toBeDisabled();
|
|
60
|
+
|
|
61
|
+
expect(getByText("Trigger element")).toBeInTheDocument();
|
|
62
|
+
expect(
|
|
63
|
+
getByText("Drag an drop file or click to select file")
|
|
64
|
+
).toBeInTheDocument();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("clicks yes action button", async () => {
|
|
68
|
+
const { getByRole, getByTestId, queryByRole } = render(
|
|
69
|
+
<UploadModal
|
|
70
|
+
{...{
|
|
71
|
+
trigger,
|
|
72
|
+
header: "header",
|
|
73
|
+
content: "content",
|
|
74
|
+
param: "implementations",
|
|
75
|
+
handleSubmit: onClickYesAction,
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
await waitFor(() => {
|
|
81
|
+
userEvent.click(getByRole("option", { name: "Trigger element" }));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const yesActionButton = getByRole("button", { name: "Upload and update" });
|
|
85
|
+
expect(yesActionButton).toBeDisabled();
|
|
86
|
+
|
|
87
|
+
const extraActionButton = queryByRole("button", {
|
|
88
|
+
name: "Extra action button",
|
|
89
|
+
});
|
|
90
|
+
expect(extraActionButton).not.toBeInTheDocument();
|
|
91
|
+
|
|
92
|
+
const fakeFile = new File(["implementations"], "implementations.csv", {
|
|
93
|
+
type: "text/csv",
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const dropZone = getByTestId("fileDropZone");
|
|
97
|
+
const data = mockData([fakeFile]);
|
|
98
|
+
|
|
99
|
+
await waitFor(() => {
|
|
100
|
+
fireEvent.drop(dropZone, data);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(yesActionButton).toBeEnabled();
|
|
104
|
+
|
|
105
|
+
await waitFor(() => {
|
|
106
|
+
userEvent.click(yesActionButton);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(onClickYesAction).toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("clicks extra action button", async () => {
|
|
113
|
+
const { getByRole, getByTestId } = render(
|
|
114
|
+
<UploadModal
|
|
115
|
+
{...{
|
|
116
|
+
trigger,
|
|
117
|
+
extraAction,
|
|
118
|
+
header: "header",
|
|
119
|
+
content: "content",
|
|
120
|
+
param: "implementations",
|
|
121
|
+
handleSubmit: onClickYesAction,
|
|
122
|
+
}}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
await waitFor(() => {
|
|
127
|
+
userEvent.click(getByRole("option", { name: "Trigger element" }));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const yesActionButton = getByRole("button", { name: "Upload and update" });
|
|
131
|
+
expect(yesActionButton).toBeDisabled();
|
|
132
|
+
|
|
133
|
+
const extraActionButton = getByRole("button", {
|
|
134
|
+
name: "Extra action button",
|
|
135
|
+
});
|
|
136
|
+
expect(extraActionButton).toBeDisabled();
|
|
137
|
+
|
|
138
|
+
const fakeFile = new File(["implementations"], "implementations.csv", {
|
|
139
|
+
type: "text/csv",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const dropZone = getByTestId("fileDropZone");
|
|
143
|
+
const data = mockData([fakeFile]);
|
|
144
|
+
|
|
145
|
+
await waitFor(() => {
|
|
146
|
+
fireEvent.drop(dropZone, data);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(yesActionButton).toBeEnabled();
|
|
150
|
+
expect(extraActionButton).toBeEnabled();
|
|
151
|
+
|
|
152
|
+
await waitFor(() => {
|
|
153
|
+
userEvent.click(extraActionButton);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(onClickExtraAction).toHaveBeenCalled();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -29,9 +29,11 @@ describe("sagas: uploadImplementationsRequestsSaga", () => {
|
|
|
29
29
|
|
|
30
30
|
describe("sagas: uploadImplementationsSaga", () => {
|
|
31
31
|
const body = { implementations: "content" };
|
|
32
|
-
const payload = {
|
|
33
|
-
|
|
32
|
+
const payload = { formData: body };
|
|
33
|
+
|
|
34
34
|
it("should put a access action when a response is returned", () => {
|
|
35
|
+
const implementationIds = [1, 2];
|
|
36
|
+
const data = { message: implementationIds };
|
|
35
37
|
expect(() => {
|
|
36
38
|
testSaga(uploadImplementationsSaga, { payload })
|
|
37
39
|
.next()
|
|
@@ -5,7 +5,7 @@ import { API_RULE_IMPLEMENTATIONS_UPLOAD } from "../api";
|
|
|
5
5
|
|
|
6
6
|
export function* uploadImplementationsSaga({ payload }) {
|
|
7
7
|
try {
|
|
8
|
-
const {
|
|
8
|
+
const { formData: body } = payload;
|
|
9
9
|
yield put(uploadImplementations.request());
|
|
10
10
|
const { data } = yield call(
|
|
11
11
|
apiJsonPost,
|