@truedat/dq 8.1.3 → 8.1.5
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 +3 -3
- package/src/components/ImplementationsRoutes.js +12 -12
- package/src/components/ImplementationUploadJobBreadcrumbs.js +0 -25
- package/src/components/ImplementationsUploadJob.js +0 -217
- package/src/components/ImplementationsUploadJobs.js +0 -128
- package/src/components/__tests__/ImplementationUploadJobBreadcrumbs.spec.js +0 -28
- package/src/components/__tests__/ImplementationsUploadJob.spec.js +0 -112
- package/src/components/__tests__/ImplementationsUploadJobs.spec.js +0 -60
- package/src/components/__tests__/__snapshots__/ImplementationUploadJobBreadcrumbs.spec.js.snap +0 -42
- package/src/components/__tests__/implementationsUploadJobParser.spec.js +0 -105
- package/src/components/implementationsUploadJobParser.js +0 -292
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/dq",
|
|
3
|
-
"version": "8.1.
|
|
3
|
+
"version": "8.1.5",
|
|
4
4
|
"description": "Truedat Web Data Quality Module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@testing-library/jest-dom": "^6.6.3",
|
|
54
54
|
"@testing-library/react": "^16.3.0",
|
|
55
55
|
"@testing-library/user-event": "^14.6.1",
|
|
56
|
-
"@truedat/test": "8.1.
|
|
56
|
+
"@truedat/test": "8.1.5",
|
|
57
57
|
"identity-obj-proxy": "^3.0.0",
|
|
58
58
|
"jest": "^29.7.0",
|
|
59
59
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -86,5 +86,5 @@
|
|
|
86
86
|
"semantic-ui-react": "^3.0.0-beta.2",
|
|
87
87
|
"swr": "^2.3.3"
|
|
88
88
|
},
|
|
89
|
-
"gitHead": "
|
|
89
|
+
"gitHead": "a9bc192d725eff352d0088d29d7b9b7ebea29254"
|
|
90
90
|
}
|
|
@@ -52,8 +52,8 @@ import RuleResultDetails from "./RuleResultDetails";
|
|
|
52
52
|
import RuleResultRemediationLoader from "./RuleResultRemediationLoader";
|
|
53
53
|
import RuleResultsRoutes from "./RuleResultsRoutes";
|
|
54
54
|
import RuleSubscriptionLoader from "./RuleSubscriptionLoader";
|
|
55
|
-
import
|
|
56
|
-
import
|
|
55
|
+
import UploadJobs from "@truedat/core/components/UploadJobs";
|
|
56
|
+
import UploadJob from "@truedat/core/components/UploadJob";
|
|
57
57
|
|
|
58
58
|
const TemplatesLoader = React.lazy(
|
|
59
59
|
() => import("@truedat/core/components/TemplatesLoader")
|
|
@@ -111,8 +111,8 @@ export const ImplementationsRoutes = ({
|
|
|
111
111
|
// IMPLEMENTATIONS_UPLOAD_JOBS = "/implementations/uploadJobs";
|
|
112
112
|
path="/uploadJobs"
|
|
113
113
|
>
|
|
114
|
-
<Route index element={<
|
|
115
|
-
<Route path=":id" element={<
|
|
114
|
+
<Route index element={<UploadJobs scope="implementations" />} />
|
|
115
|
+
<Route path=":id" element={<UploadJob scope="implementations" />} />
|
|
116
116
|
</Route>
|
|
117
117
|
|
|
118
118
|
<Route
|
|
@@ -289,8 +289,8 @@ export const ImplementationsRoutes = ({
|
|
|
289
289
|
/>
|
|
290
290
|
) : null}
|
|
291
291
|
{!structuresAliasesLoading &&
|
|
292
|
-
|
|
293
|
-
|
|
292
|
+
ruleImplementationLoaded &&
|
|
293
|
+
implementationStructuresLoaded ? (
|
|
294
294
|
isRuleImplemenationBasic ? (
|
|
295
295
|
<NewBasicRuleImplementation edition clone />
|
|
296
296
|
) : (
|
|
@@ -316,8 +316,8 @@ export const ImplementationsRoutes = ({
|
|
|
316
316
|
/>
|
|
317
317
|
) : null}
|
|
318
318
|
{!structuresAliasesLoading &&
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
ruleImplementationLoaded &&
|
|
320
|
+
implementationStructuresLoaded ? (
|
|
321
321
|
isRuleImplemenationBasic ? (
|
|
322
322
|
<NewBasicRuleImplementation edition />
|
|
323
323
|
) : (
|
|
@@ -343,8 +343,8 @@ export const ImplementationsRoutes = ({
|
|
|
343
343
|
/>
|
|
344
344
|
) : null}
|
|
345
345
|
{!structuresAliasesLoading &&
|
|
346
|
-
|
|
347
|
-
|
|
346
|
+
ruleImplementationLoaded &&
|
|
347
|
+
implementationStructuresLoaded ? (
|
|
348
348
|
<NewRuleImplementation edition implementationType="default" />
|
|
349
349
|
) : null}
|
|
350
350
|
</>
|
|
@@ -366,8 +366,8 @@ export const ImplementationsRoutes = ({
|
|
|
366
366
|
/>
|
|
367
367
|
) : null}
|
|
368
368
|
{!structuresAliasesLoading &&
|
|
369
|
-
|
|
370
|
-
|
|
369
|
+
ruleImplementationLoaded &&
|
|
370
|
+
implementationStructuresLoaded ? (
|
|
371
371
|
<NewRuleImplementation edition implementationType="raw" />
|
|
372
372
|
) : null}
|
|
373
373
|
</>
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import PropTypes from "prop-types";
|
|
2
|
-
import { Breadcrumb } from "semantic-ui-react";
|
|
3
|
-
import { Link } from "react-router";
|
|
4
|
-
import { FormattedMessage } from "react-intl";
|
|
5
|
-
import { IMPLEMENTATIONS_UPLOAD_JOBS } from "@truedat/core/routes";
|
|
6
|
-
|
|
7
|
-
export const ImplementationUploadJobBreadcrumbs = ({ filename }) => (
|
|
8
|
-
<Breadcrumb>
|
|
9
|
-
<Breadcrumb.Section as={Link} to={IMPLEMENTATIONS_UPLOAD_JOBS}>
|
|
10
|
-
<FormattedMessage id="sidemenu.implementations_upload_jobs" />
|
|
11
|
-
</Breadcrumb.Section>
|
|
12
|
-
{filename ? (
|
|
13
|
-
<>
|
|
14
|
-
<Breadcrumb.Divider icon="right angle" />
|
|
15
|
-
<Breadcrumb.Section active>{filename}</Breadcrumb.Section>
|
|
16
|
-
</>
|
|
17
|
-
) : null}
|
|
18
|
-
</Breadcrumb>
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
ImplementationUploadJobBreadcrumbs.propTypes = {
|
|
22
|
-
filename: PropTypes.string,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export default ImplementationUploadJobBreadcrumbs;
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import _ from "lodash/fp";
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
import {
|
|
4
|
-
Header,
|
|
5
|
-
Icon,
|
|
6
|
-
Segment,
|
|
7
|
-
Dimmer,
|
|
8
|
-
Loader,
|
|
9
|
-
Table,
|
|
10
|
-
Label,
|
|
11
|
-
Button,
|
|
12
|
-
} from "semantic-ui-react";
|
|
13
|
-
import { FormattedMessage, useIntl } from "react-intl";
|
|
14
|
-
import { useParams } from "react-router";
|
|
15
|
-
import { DateTime } from "@truedat/core/components";
|
|
16
|
-
import { useImplementationsUploadJob } from "../hooks/useImplementations";
|
|
17
|
-
import { StatusPill, ResponseCell } from "./implementationsUploadJobParser";
|
|
18
|
-
import ImplementationUploadJobBreadcrumbs from "./ImplementationUploadJobBreadcrumbs";
|
|
19
|
-
|
|
20
|
-
export default function ImplementationsUploadJob() {
|
|
21
|
-
const { id } = useParams();
|
|
22
|
-
const { data, loading } = useImplementationsUploadJob(id);
|
|
23
|
-
const job = data?.data;
|
|
24
|
-
|
|
25
|
-
const { formatMessage } = useIntl();
|
|
26
|
-
const [expandedGroups, setExpandedGroups] = useState({});
|
|
27
|
-
|
|
28
|
-
// Group events by status
|
|
29
|
-
const groupedEvents = job?.events ? _.groupBy("status", job.events) : {};
|
|
30
|
-
|
|
31
|
-
const toggleGroup = (status) => {
|
|
32
|
-
setExpandedGroups((prev) => ({
|
|
33
|
-
...prev,
|
|
34
|
-
[status]: !prev[status],
|
|
35
|
-
}));
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const renderEventGroup = (status, events) => {
|
|
39
|
-
const isExpanded = expandedGroups[status];
|
|
40
|
-
const shouldCollapse = status === "INFO" || status === "ERROR";
|
|
41
|
-
|
|
42
|
-
if (!shouldCollapse) {
|
|
43
|
-
// Render non-collapsible events normally
|
|
44
|
-
return events.map((event, idx) => (
|
|
45
|
-
<Table.Row key={`${status}-${idx}`}>
|
|
46
|
-
<Table.Cell>
|
|
47
|
-
<StatusPill status={event.status} />
|
|
48
|
-
</Table.Cell>
|
|
49
|
-
<Table.Cell>
|
|
50
|
-
<DateTime value={event.inserted_at} />
|
|
51
|
-
</Table.Cell>
|
|
52
|
-
<Table.Cell>
|
|
53
|
-
<ResponseCell {...event} />
|
|
54
|
-
</Table.Cell>
|
|
55
|
-
</Table.Row>
|
|
56
|
-
));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Render collapsible groups for INFO and ERROR
|
|
60
|
-
const groupRows = [];
|
|
61
|
-
|
|
62
|
-
// Add group header row
|
|
63
|
-
groupRows.push(
|
|
64
|
-
<Table.Row
|
|
65
|
-
key={`${status}-header`}
|
|
66
|
-
style={{ backgroundColor: "#f8f9fa" }}
|
|
67
|
-
>
|
|
68
|
-
<Table.Cell>
|
|
69
|
-
<Button
|
|
70
|
-
basic
|
|
71
|
-
size="mini"
|
|
72
|
-
icon={isExpanded ? "chevron down" : "chevron right"}
|
|
73
|
-
content={` ${formatMessage({ id: `implementations.bulkUpload.event.status.${status}` })} (${events.length})`}
|
|
74
|
-
onClick={() => toggleGroup(status)}
|
|
75
|
-
style={{ padding: "0.5em" }}
|
|
76
|
-
/>
|
|
77
|
-
</Table.Cell>
|
|
78
|
-
<Table.Cell>
|
|
79
|
-
<DateTime value={events[0]?.inserted_at} />
|
|
80
|
-
</Table.Cell>
|
|
81
|
-
<Table.Cell></Table.Cell>
|
|
82
|
-
</Table.Row>
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
// Add expanded events if group is expanded
|
|
86
|
-
if (isExpanded) {
|
|
87
|
-
events.forEach((event, idx) => {
|
|
88
|
-
groupRows.push(
|
|
89
|
-
<Table.Row key={`${status}-${idx}`} style={{ paddingLeft: "2em" }}>
|
|
90
|
-
<Table.Cell style={{ paddingLeft: "2em" }}>
|
|
91
|
-
<StatusPill status={event.status} />
|
|
92
|
-
</Table.Cell>
|
|
93
|
-
<Table.Cell>
|
|
94
|
-
<DateTime value={event.inserted_at} />
|
|
95
|
-
</Table.Cell>
|
|
96
|
-
<Table.Cell>
|
|
97
|
-
<ResponseCell {...event} />
|
|
98
|
-
</Table.Cell>
|
|
99
|
-
</Table.Row>
|
|
100
|
-
);
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return groupRows;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<>
|
|
109
|
-
<ImplementationUploadJobBreadcrumbs filename={job?.filename} />
|
|
110
|
-
<Segment>
|
|
111
|
-
<Header as="h2">
|
|
112
|
-
<Icon
|
|
113
|
-
circular
|
|
114
|
-
name={formatMessage({
|
|
115
|
-
id: "implementations.bulkUpload.job.header.icon",
|
|
116
|
-
defaultMessage: "cogs",
|
|
117
|
-
})}
|
|
118
|
-
/>
|
|
119
|
-
<Header.Content>
|
|
120
|
-
<FormattedMessage id="implementations.bulkUpload.job.header" />
|
|
121
|
-
</Header.Content>
|
|
122
|
-
</Header>
|
|
123
|
-
<Dimmer.Dimmable dimmed={loading}>
|
|
124
|
-
<Segment attached="bottom">
|
|
125
|
-
{job ? (
|
|
126
|
-
<>
|
|
127
|
-
<div
|
|
128
|
-
style={{
|
|
129
|
-
display: "flex",
|
|
130
|
-
alignItems: "center",
|
|
131
|
-
gap: "1.5em",
|
|
132
|
-
marginBottom: "1.5em",
|
|
133
|
-
flexWrap: "wrap",
|
|
134
|
-
}}
|
|
135
|
-
>
|
|
136
|
-
<div style={{ display: "flex", flexDirection: "column" }}>
|
|
137
|
-
<span style={{ fontWeight: 600, fontSize: "1.3em" }}>
|
|
138
|
-
{job.filename}
|
|
139
|
-
</span>
|
|
140
|
-
<span style={{ color: "gray", fontSize: "0.7em" }}>
|
|
141
|
-
{job.hash}
|
|
142
|
-
</span>
|
|
143
|
-
</div>
|
|
144
|
-
<span>
|
|
145
|
-
<Label basic size="small" style={{ marginRight: 4 }}>
|
|
146
|
-
<FormattedMessage
|
|
147
|
-
id="implementations.bulkUpload.job.table.status"
|
|
148
|
-
defaultMessage="Status"
|
|
149
|
-
/>
|
|
150
|
-
</Label>
|
|
151
|
-
<StatusPill status={job.latest_status} />
|
|
152
|
-
</span>
|
|
153
|
-
<span>
|
|
154
|
-
<Label basic size="small" style={{ marginRight: 4 }}>
|
|
155
|
-
<FormattedMessage
|
|
156
|
-
id="implementations.bulkUpload.job.table.inserted_at"
|
|
157
|
-
defaultMessage="Inserted At"
|
|
158
|
-
/>
|
|
159
|
-
</Label>
|
|
160
|
-
<DateTime value={job.latest_event_at} />
|
|
161
|
-
</span>
|
|
162
|
-
</div>
|
|
163
|
-
<Table>
|
|
164
|
-
<Table.Header>
|
|
165
|
-
<Table.Row>
|
|
166
|
-
<Table.HeaderCell>
|
|
167
|
-
<FormattedMessage
|
|
168
|
-
id="implementations.bulkUpload.job.table.status"
|
|
169
|
-
defaultMessage="Status"
|
|
170
|
-
/>
|
|
171
|
-
</Table.HeaderCell>
|
|
172
|
-
<Table.HeaderCell>
|
|
173
|
-
<FormattedMessage
|
|
174
|
-
id="implementations.bulkUpload.job.table.inserted_at"
|
|
175
|
-
defaultMessage="Inserted At"
|
|
176
|
-
/>
|
|
177
|
-
</Table.HeaderCell>
|
|
178
|
-
<Table.HeaderCell>
|
|
179
|
-
<FormattedMessage
|
|
180
|
-
id="implementations.bulkUpload.job.table.detail"
|
|
181
|
-
defaultMessage="Detail"
|
|
182
|
-
/>
|
|
183
|
-
</Table.HeaderCell>
|
|
184
|
-
</Table.Row>
|
|
185
|
-
</Table.Header>
|
|
186
|
-
<Table.Body>
|
|
187
|
-
{_.isArray(job.events) && !_.isEmpty(job.events) ? (
|
|
188
|
-
Object.entries(groupedEvents)
|
|
189
|
-
.map(([status, events]) =>
|
|
190
|
-
renderEventGroup(status, events)
|
|
191
|
-
)
|
|
192
|
-
.flat()
|
|
193
|
-
) : (
|
|
194
|
-
<Table.Row>
|
|
195
|
-
<Table.Cell colSpan={3}>
|
|
196
|
-
<FormattedMessage
|
|
197
|
-
id="implementations.bulkUpload.job.table.no_events"
|
|
198
|
-
defaultMessage="No events found."
|
|
199
|
-
/>
|
|
200
|
-
</Table.Cell>
|
|
201
|
-
</Table.Row>
|
|
202
|
-
)}
|
|
203
|
-
</Table.Body>
|
|
204
|
-
</Table>
|
|
205
|
-
</>
|
|
206
|
-
) : null}
|
|
207
|
-
{loading ? (
|
|
208
|
-
<Dimmer active inverted>
|
|
209
|
-
<Loader />
|
|
210
|
-
</Dimmer>
|
|
211
|
-
) : null}
|
|
212
|
-
</Segment>
|
|
213
|
-
</Dimmer.Dimmable>
|
|
214
|
-
</Segment>
|
|
215
|
-
</>
|
|
216
|
-
);
|
|
217
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import _ from "lodash/fp";
|
|
2
|
-
import moment from "moment";
|
|
3
|
-
import {
|
|
4
|
-
Header,
|
|
5
|
-
Icon,
|
|
6
|
-
Segment,
|
|
7
|
-
Dimmer,
|
|
8
|
-
Loader,
|
|
9
|
-
Table,
|
|
10
|
-
} from "semantic-ui-react";
|
|
11
|
-
import { FormattedMessage, useIntl } from "react-intl";
|
|
12
|
-
import { useNavigate } from "react-router";
|
|
13
|
-
import { useImplementationsUploadJobs } from "../hooks/useImplementations";
|
|
14
|
-
import { linkTo } from "@truedat/core/routes";
|
|
15
|
-
import { StatusPill, ResponseCell } from "./implementationsUploadJobParser";
|
|
16
|
-
|
|
17
|
-
import ImplementationUploadJobBreadcrumbs from "./ImplementationUploadJobBreadcrumbs";
|
|
18
|
-
|
|
19
|
-
export default function ImplementationsUploadJobs() {
|
|
20
|
-
const { formatMessage } = useIntl();
|
|
21
|
-
const { data, loading } = useImplementationsUploadJobs();
|
|
22
|
-
const jobs = data?.data;
|
|
23
|
-
const navigate = useNavigate();
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<>
|
|
27
|
-
<ImplementationUploadJobBreadcrumbs />
|
|
28
|
-
<Segment>
|
|
29
|
-
<Header as="h2">
|
|
30
|
-
<Icon
|
|
31
|
-
circular
|
|
32
|
-
name={formatMessage({
|
|
33
|
-
id: "implementations.bulkUpload.jobs.header.icon",
|
|
34
|
-
defaultMessage: "cogs",
|
|
35
|
-
})}
|
|
36
|
-
/>
|
|
37
|
-
<Header.Content>
|
|
38
|
-
<FormattedMessage id="implementations.bulkUpload.jobs.header" />
|
|
39
|
-
<Header.Subheader>
|
|
40
|
-
<FormattedMessage id="implementations.bulkUpload.jobs.subheader" />
|
|
41
|
-
</Header.Subheader>
|
|
42
|
-
</Header.Content>
|
|
43
|
-
</Header>
|
|
44
|
-
<Dimmer.Dimmable dimmed={loading}>
|
|
45
|
-
<Segment attached="bottom">
|
|
46
|
-
{jobs ? (
|
|
47
|
-
<Table selectable>
|
|
48
|
-
<Table.Header>
|
|
49
|
-
<Table.Row>
|
|
50
|
-
<Table.HeaderCell>
|
|
51
|
-
<FormattedMessage
|
|
52
|
-
id="implementations.bulkUpload.job.table.filename"
|
|
53
|
-
defaultMessage="Filename"
|
|
54
|
-
/>
|
|
55
|
-
</Table.HeaderCell>
|
|
56
|
-
<Table.HeaderCell>
|
|
57
|
-
<FormattedMessage
|
|
58
|
-
id="implementations.bulkUpload.job.table.status"
|
|
59
|
-
defaultMessage="Status"
|
|
60
|
-
/>
|
|
61
|
-
</Table.HeaderCell>
|
|
62
|
-
<Table.HeaderCell>
|
|
63
|
-
<FormattedMessage
|
|
64
|
-
id="implementations.bulkUpload.job.table.response"
|
|
65
|
-
defaultMessage="Response"
|
|
66
|
-
/>
|
|
67
|
-
</Table.HeaderCell>
|
|
68
|
-
<Table.HeaderCell>
|
|
69
|
-
<FormattedMessage
|
|
70
|
-
id="implementations.bulkUpload.job.table.latest_event_at"
|
|
71
|
-
defaultMessage="Latest Event"
|
|
72
|
-
/>
|
|
73
|
-
</Table.HeaderCell>
|
|
74
|
-
</Table.Row>
|
|
75
|
-
</Table.Header>
|
|
76
|
-
<Table.Body>
|
|
77
|
-
{_.isArray(jobs) && !_.isEmpty(jobs) ? (
|
|
78
|
-
jobs.map((job, idx) => (
|
|
79
|
-
<Table.Row
|
|
80
|
-
key={idx}
|
|
81
|
-
style={{ cursor: "pointer" }}
|
|
82
|
-
onClick={() =>
|
|
83
|
-
navigate(
|
|
84
|
-
linkTo.IMPLEMENTATIONS_UPLOAD_JOB({ id: job.id })
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
>
|
|
88
|
-
<Table.Cell>{job.filename}</Table.Cell>
|
|
89
|
-
<Table.Cell>
|
|
90
|
-
<StatusPill status={job.latest_status} />
|
|
91
|
-
</Table.Cell>
|
|
92
|
-
<Table.Cell>
|
|
93
|
-
<ResponseCell
|
|
94
|
-
response={job.latest_event_response}
|
|
95
|
-
status={job.latest_status}
|
|
96
|
-
/>
|
|
97
|
-
</Table.Cell>
|
|
98
|
-
<Table.Cell>
|
|
99
|
-
{job.latest_event_at
|
|
100
|
-
? moment(job.latest_event_at).fromNow()
|
|
101
|
-
: ""}
|
|
102
|
-
</Table.Cell>
|
|
103
|
-
</Table.Row>
|
|
104
|
-
))
|
|
105
|
-
) : (
|
|
106
|
-
<Table.Row>
|
|
107
|
-
<Table.Cell colSpan={5}>
|
|
108
|
-
<FormattedMessage
|
|
109
|
-
id="implementations.bulkUpload.job.table.no_jobs"
|
|
110
|
-
defaultMessage="No jobs found."
|
|
111
|
-
/>
|
|
112
|
-
</Table.Cell>
|
|
113
|
-
</Table.Row>
|
|
114
|
-
)}
|
|
115
|
-
</Table.Body>
|
|
116
|
-
</Table>
|
|
117
|
-
) : null}
|
|
118
|
-
{loading ? (
|
|
119
|
-
<Dimmer active inverted>
|
|
120
|
-
<Loader />
|
|
121
|
-
</Dimmer>
|
|
122
|
-
) : null}
|
|
123
|
-
</Segment>
|
|
124
|
-
</Dimmer.Dimmable>
|
|
125
|
-
</Segment>
|
|
126
|
-
</>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { render, waitForLoad } from "@truedat/test/render";
|
|
2
|
-
import { ImplementationUploadJobBreadcrumbs } from "../ImplementationUploadJobBreadcrumbs";
|
|
3
|
-
|
|
4
|
-
describe("ImplementationUploadJobBreadcrumbs", () => {
|
|
5
|
-
it("renders breadcrumbs without filename", async () => {
|
|
6
|
-
const rendered = render(<ImplementationUploadJobBreadcrumbs />);
|
|
7
|
-
await waitForLoad(rendered);
|
|
8
|
-
|
|
9
|
-
expect(
|
|
10
|
-
rendered.getByText(/sidemenu.implementations_upload_jobs/i)
|
|
11
|
-
).toBeInTheDocument();
|
|
12
|
-
expect(rendered.container).toMatchSnapshot();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("renders breadcrumbs with filename", async () => {
|
|
16
|
-
const filename = "test.csv";
|
|
17
|
-
const rendered = render(
|
|
18
|
-
<ImplementationUploadJobBreadcrumbs filename={filename} />
|
|
19
|
-
);
|
|
20
|
-
await waitForLoad(rendered);
|
|
21
|
-
|
|
22
|
-
expect(
|
|
23
|
-
rendered.getByText(/sidemenu.implementations_upload_jobs/i)
|
|
24
|
-
).toBeInTheDocument();
|
|
25
|
-
expect(rendered.getByText(filename)).toBeInTheDocument();
|
|
26
|
-
expect(rendered.container).toMatchSnapshot();
|
|
27
|
-
});
|
|
28
|
-
});
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { render, waitForLoad } from "@truedat/test/render";
|
|
2
|
-
import userEvent from "@testing-library/user-event";
|
|
3
|
-
import { waitFor } from "@testing-library/react";
|
|
4
|
-
|
|
5
|
-
import { useImplementationsUploadJob } from "../../hooks/useImplementations";
|
|
6
|
-
import ImplementationsUploadJob from "../ImplementationsUploadJob";
|
|
7
|
-
|
|
8
|
-
jest.mock("../../hooks/useImplementations", () => ({
|
|
9
|
-
useImplementationsUploadJob: jest.fn(),
|
|
10
|
-
}));
|
|
11
|
-
jest.mock("react-router", () => ({
|
|
12
|
-
...jest.requireActual("react-router"),
|
|
13
|
-
useParams: () => ({ id: "123" }),
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
describe("<ImplementationsUploadJob />", () => {
|
|
17
|
-
const user = userEvent.setup({ delay: null });
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
useImplementationsUploadJob.mockReset();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("renders loading state", async () => {
|
|
24
|
-
useImplementationsUploadJob.mockReturnValue({
|
|
25
|
-
loading: true,
|
|
26
|
-
data: null,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const rendered = render(<ImplementationsUploadJob />);
|
|
30
|
-
|
|
31
|
-
expect(rendered.container.querySelector(".ui.loader")).toBeInTheDocument();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("renders empty state when no events", async () => {
|
|
35
|
-
useImplementationsUploadJob.mockReturnValue({
|
|
36
|
-
loading: false,
|
|
37
|
-
data: {
|
|
38
|
-
data: {
|
|
39
|
-
filename: "test.xlsx",
|
|
40
|
-
hash: "abc123",
|
|
41
|
-
latest_status: "COMPLETED",
|
|
42
|
-
latest_event_at: "2023-01-01T00:00:00Z",
|
|
43
|
-
events: [],
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const rendered = render(<ImplementationsUploadJob />);
|
|
49
|
-
await waitForLoad(rendered);
|
|
50
|
-
|
|
51
|
-
expect(rendered.getAllByText(/test.xlsx/i)).toHaveLength(2);
|
|
52
|
-
expect(rendered.getByText(/abc123/i)).toBeInTheDocument();
|
|
53
|
-
expect(rendered.getByText(/no events found/i)).toBeInTheDocument();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("renders events grouped by status", async () => {
|
|
57
|
-
useImplementationsUploadJob.mockReturnValue({
|
|
58
|
-
loading: false,
|
|
59
|
-
data: {
|
|
60
|
-
data: {
|
|
61
|
-
filename: "test.xlsx",
|
|
62
|
-
hash: "abc123",
|
|
63
|
-
latest_status: "COMPLETED",
|
|
64
|
-
latest_event_at: "2023-01-01T00:00:00Z",
|
|
65
|
-
events: [
|
|
66
|
-
{
|
|
67
|
-
status: "INFO",
|
|
68
|
-
inserted_at: "2023-01-01T00:00:00Z",
|
|
69
|
-
response: { type: "info_message" },
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
status: "ERROR",
|
|
73
|
-
inserted_at: "2023-01-01T00:00:00Z",
|
|
74
|
-
response: { type: "error_message" },
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
status: "COMPLETED",
|
|
78
|
-
inserted_at: "2023-01-01T00:00:00Z",
|
|
79
|
-
response: { type: "completed_message" },
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const rendered = render(<ImplementationsUploadJob />);
|
|
87
|
-
await waitForLoad(rendered);
|
|
88
|
-
|
|
89
|
-
// Check group headers exist
|
|
90
|
-
expect(rendered.getByText(/info \(1\)/i)).toBeInTheDocument();
|
|
91
|
-
expect(rendered.getByText(/error \(1\)/i)).toBeInTheDocument();
|
|
92
|
-
|
|
93
|
-
// Expand INFO group
|
|
94
|
-
await user.click(rendered.getByText(/info \(1\)/i));
|
|
95
|
-
await waitFor(() => {
|
|
96
|
-
expect(rendered.getByText(/info_message/i)).toBeInTheDocument();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// Expand ERROR group
|
|
100
|
-
await user.click(rendered.getByText(/error \(1\)/i));
|
|
101
|
-
await waitFor(() => {
|
|
102
|
-
expect(rendered.getByText(/error_message/i)).toBeInTheDocument();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// COMPLETED events are always visible
|
|
106
|
-
expect(
|
|
107
|
-
rendered.getAllByText(
|
|
108
|
-
/implementations.bulkUpload.event.status.COMPLETED/i
|
|
109
|
-
)
|
|
110
|
-
).toHaveLength(2);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { render, waitForLoad } from "@truedat/test/render";
|
|
2
|
-
import { useImplementationsUploadJobs } from "../../hooks/useImplementations";
|
|
3
|
-
import ImplementationsUploadJobs from "../ImplementationsUploadJobs";
|
|
4
|
-
|
|
5
|
-
jest.mock("../../hooks/useImplementations", () => ({
|
|
6
|
-
useImplementationsUploadJobs: jest.fn(),
|
|
7
|
-
}));
|
|
8
|
-
|
|
9
|
-
describe("<ImplementationsUploadJobs />", () => {
|
|
10
|
-
it("renders loading state", async () => {
|
|
11
|
-
useImplementationsUploadJobs.mockReturnValue({
|
|
12
|
-
loading: true,
|
|
13
|
-
data: null,
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const rendered = render(<ImplementationsUploadJobs />);
|
|
17
|
-
|
|
18
|
-
expect(rendered.container.querySelector(".ui.loader")).toBeInTheDocument();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("renders empty state", async () => {
|
|
22
|
-
useImplementationsUploadJobs.mockReturnValue({
|
|
23
|
-
loading: false,
|
|
24
|
-
data: {
|
|
25
|
-
data: [],
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const rendered = render(<ImplementationsUploadJobs />);
|
|
30
|
-
await waitForLoad(rendered);
|
|
31
|
-
|
|
32
|
-
expect(rendered.getByText(/No jobs found./i)).toBeInTheDocument();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("renders jobs list", async () => {
|
|
36
|
-
const mockJobs = [
|
|
37
|
-
{
|
|
38
|
-
id: 1,
|
|
39
|
-
filename: "test.csv",
|
|
40
|
-
latest_status: "completed",
|
|
41
|
-
latest_event_response: "Success",
|
|
42
|
-
latest_event_at: "2023-01-01T00:00:00Z",
|
|
43
|
-
},
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
useImplementationsUploadJobs.mockReturnValue({
|
|
47
|
-
loading: false,
|
|
48
|
-
data: {
|
|
49
|
-
data: mockJobs,
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const rendered = render(<ImplementationsUploadJobs />);
|
|
54
|
-
await waitForLoad(rendered);
|
|
55
|
-
|
|
56
|
-
expect(rendered.getByText(/test.csv/i)).toBeInTheDocument();
|
|
57
|
-
expect(rendered.getByText(/completed/i)).toBeInTheDocument();
|
|
58
|
-
expect(rendered.getByText(/success/i)).toBeInTheDocument();
|
|
59
|
-
});
|
|
60
|
-
});
|
package/src/components/__tests__/__snapshots__/ImplementationUploadJobBreadcrumbs.spec.js.snap
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
-
|
|
3
|
-
exports[`ImplementationUploadJobBreadcrumbs renders breadcrumbs with filename 1`] = `
|
|
4
|
-
<div>
|
|
5
|
-
<div
|
|
6
|
-
class="ui breadcrumb"
|
|
7
|
-
>
|
|
8
|
-
<a
|
|
9
|
-
class="section"
|
|
10
|
-
data-discover="true"
|
|
11
|
-
href="/implementations/uploadJobs"
|
|
12
|
-
>
|
|
13
|
-
sidemenu.implementations_upload_jobs
|
|
14
|
-
</a>
|
|
15
|
-
<i
|
|
16
|
-
aria-hidden="true"
|
|
17
|
-
class="right angle icon divider"
|
|
18
|
-
/>
|
|
19
|
-
<div
|
|
20
|
-
class="active section"
|
|
21
|
-
>
|
|
22
|
-
test.csv
|
|
23
|
-
</div>
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
`;
|
|
27
|
-
|
|
28
|
-
exports[`ImplementationUploadJobBreadcrumbs renders breadcrumbs without filename 1`] = `
|
|
29
|
-
<div>
|
|
30
|
-
<div
|
|
31
|
-
class="ui breadcrumb"
|
|
32
|
-
>
|
|
33
|
-
<a
|
|
34
|
-
class="section"
|
|
35
|
-
data-discover="true"
|
|
36
|
-
href="/implementations/uploadJobs"
|
|
37
|
-
>
|
|
38
|
-
sidemenu.implementations_upload_jobs
|
|
39
|
-
</a>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
`;
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { render } from "@truedat/test/render";
|
|
2
|
-
import { StatusPill, ResponseCell } from "../implementationsUploadJobParser";
|
|
3
|
-
|
|
4
|
-
describe("<StatusPill />", () => {
|
|
5
|
-
it("renders with correct color based on status", async () => {
|
|
6
|
-
const statuses = ["COMPLETED", "FAILED", "ERROR", "INFO", "OTHER"];
|
|
7
|
-
const expectedColors = ["green", "red", "red", "blue", "grey"];
|
|
8
|
-
|
|
9
|
-
statuses.forEach((status, index) => {
|
|
10
|
-
const rendered = render(<StatusPill status={status} />);
|
|
11
|
-
const label = rendered.container.querySelector(".ui.label");
|
|
12
|
-
expect(label).toHaveClass(expectedColors[index]);
|
|
13
|
-
expect(
|
|
14
|
-
rendered.getByText(
|
|
15
|
-
new RegExp(`implementations.bulkUpload.event.status.${status}`, "i")
|
|
16
|
-
)
|
|
17
|
-
).toBeInTheDocument();
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe("<ResponseCell />", () => {
|
|
23
|
-
it("returns null when response is empty", () => {
|
|
24
|
-
const rendered = render(
|
|
25
|
-
<ResponseCell response={null} status="COMPLETED" />
|
|
26
|
-
);
|
|
27
|
-
expect(rendered.container.firstChild).toBeNull();
|
|
28
|
-
|
|
29
|
-
const rendered2 = render(<ResponseCell response={{}} status="COMPLETED" />);
|
|
30
|
-
expect(rendered2.container.firstChild).toBeNull();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("renders FAILED status with message", () => {
|
|
34
|
-
const response = { message: "missing_required_headers" };
|
|
35
|
-
const rendered = render(
|
|
36
|
-
<ResponseCell response={response} status="FAILED" />
|
|
37
|
-
);
|
|
38
|
-
expect(
|
|
39
|
-
rendered.getByText(
|
|
40
|
-
/implementations.bulkUpload.error.missing_required_headers/i
|
|
41
|
-
)
|
|
42
|
-
).toBeInTheDocument();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("renders ERROR status with details", () => {
|
|
46
|
-
const response = {
|
|
47
|
-
type: "missing_required_headers",
|
|
48
|
-
details: {
|
|
49
|
-
missing_headers: ["header1", "header2"],
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
const rendered = render(
|
|
53
|
-
<ResponseCell response={response} status="ERROR" />
|
|
54
|
-
);
|
|
55
|
-
expect(
|
|
56
|
-
rendered.getByText(
|
|
57
|
-
/implementations.bulkUpload.error.missing_required_headers/i
|
|
58
|
-
)
|
|
59
|
-
).toBeInTheDocument();
|
|
60
|
-
expect(rendered.getByText(/header1, header2/i)).toBeInTheDocument();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("renders COMPLETED status with summary", () => {
|
|
64
|
-
const response = {
|
|
65
|
-
insert_count: 2,
|
|
66
|
-
update_count: 1,
|
|
67
|
-
error_count: 0,
|
|
68
|
-
};
|
|
69
|
-
const rendered = render(
|
|
70
|
-
<ResponseCell response={response} status="COMPLETED" />
|
|
71
|
-
);
|
|
72
|
-
expect(
|
|
73
|
-
rendered.getByText(/implementations.bulkUpload.result.summary.created/i)
|
|
74
|
-
).toBeInTheDocument();
|
|
75
|
-
expect(
|
|
76
|
-
rendered.getByText(/implementations.bulkUpload.result.summary.updated/i)
|
|
77
|
-
).toBeInTheDocument();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("renders INFO status with implementation details", () => {
|
|
81
|
-
const response = {
|
|
82
|
-
type: "implementation_updated",
|
|
83
|
-
details: {
|
|
84
|
-
id: 123,
|
|
85
|
-
changes: {
|
|
86
|
-
name: "new name",
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
const rendered = render(<ResponseCell response={response} status="INFO" />);
|
|
91
|
-
expect(
|
|
92
|
-
rendered.getByText(
|
|
93
|
-
/implementations.bulkUpload.info.implementation_updated/i
|
|
94
|
-
)
|
|
95
|
-
).toBeInTheDocument();
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("renders default case for unknown status", () => {
|
|
99
|
-
const response = "some text";
|
|
100
|
-
const rendered = render(
|
|
101
|
-
<ResponseCell response={response} status="UNKNOWN" />
|
|
102
|
-
);
|
|
103
|
-
expect(rendered.getByText(/some text/i)).toBeInTheDocument();
|
|
104
|
-
});
|
|
105
|
-
});
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
import _ from "lodash/fp";
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
import { FormattedMessage, useIntl } from "react-intl";
|
|
4
|
-
import { Label } from "semantic-ui-react";
|
|
5
|
-
import { RichTextEditor } from "@truedat/core/components";
|
|
6
|
-
|
|
7
|
-
import RuleImplementationLink from "./RuleImplementationLink";
|
|
8
|
-
|
|
9
|
-
export const StatusPill = ({ status }) => (
|
|
10
|
-
<Label
|
|
11
|
-
color={_.cond([
|
|
12
|
-
[_.eq("COMPLETED"), _.constant("green")],
|
|
13
|
-
[_.eq("FAILED"), _.constant("red")],
|
|
14
|
-
[_.eq("ERROR"), _.constant("red")],
|
|
15
|
-
[_.eq("INFO"), _.constant("blue")],
|
|
16
|
-
[_.stubTrue, _.constant("grey")],
|
|
17
|
-
])(status)}
|
|
18
|
-
size="small"
|
|
19
|
-
>
|
|
20
|
-
<FormattedMessage
|
|
21
|
-
id={`implementations.bulkUpload.event.status.${status}`}
|
|
22
|
-
/>
|
|
23
|
-
</Label>
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
export const ResponseCell = ({ response, status }) => {
|
|
27
|
-
if (!response || (_.isObject(response) && _.isEmpty(response))) return null;
|
|
28
|
-
|
|
29
|
-
switch (status) {
|
|
30
|
-
case "FAILED":
|
|
31
|
-
return (
|
|
32
|
-
<FormattedMessage
|
|
33
|
-
id={`implementations.bulkUpload.error.${response.message}`}
|
|
34
|
-
/>
|
|
35
|
-
);
|
|
36
|
-
case "ERROR":
|
|
37
|
-
return <ErrorDetail response={response} />;
|
|
38
|
-
case "COMPLETED":
|
|
39
|
-
return <CompletedDetail response={response} />;
|
|
40
|
-
case "INFO":
|
|
41
|
-
return <InfoDetail response={response} />;
|
|
42
|
-
default:
|
|
43
|
-
return (
|
|
44
|
-
<pre
|
|
45
|
-
style={{
|
|
46
|
-
whiteSpace: "pre-wrap",
|
|
47
|
-
wordBreak: "break-all",
|
|
48
|
-
margin: 0,
|
|
49
|
-
}}
|
|
50
|
-
>
|
|
51
|
-
{typeof response === "string"
|
|
52
|
-
? response
|
|
53
|
-
: JSON.stringify(response, null, 2)}
|
|
54
|
-
</pre>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const SheetAndLine = ({ response }) => (
|
|
60
|
-
<Label size="small">
|
|
61
|
-
<span>{response.sheet}</span>
|
|
62
|
-
{response.row_number ? (
|
|
63
|
-
<span>
|
|
64
|
-
{" "}
|
|
65
|
-
-{" "}
|
|
66
|
-
<FormattedMessage id="implementations.bulkUpload.result.prop.row_number" />
|
|
67
|
-
: {response.row_number}
|
|
68
|
-
</span>
|
|
69
|
-
) : null}
|
|
70
|
-
</Label>
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const CellHeader = ({ header, response }) => (
|
|
74
|
-
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
|
75
|
-
<b>
|
|
76
|
-
<FormattedMessage id={header} />
|
|
77
|
-
</b>
|
|
78
|
-
<div>
|
|
79
|
-
<SheetAndLine response={response} />
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const ErrorDetail = ({ response }) => {
|
|
85
|
-
const { formatMessage } = useIntl();
|
|
86
|
-
|
|
87
|
-
if (!response || !response.type) {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
const { type } = response;
|
|
91
|
-
const messagePrefix = "implementations.bulkUpload.detail";
|
|
92
|
-
|
|
93
|
-
const detailBuilders = {
|
|
94
|
-
missing_required_headers: (details) => [
|
|
95
|
-
[
|
|
96
|
-
formatMessage({ id: `${messagePrefix}.headers` }),
|
|
97
|
-
details.missing_headers?.join(", "),
|
|
98
|
-
],
|
|
99
|
-
],
|
|
100
|
-
invalid_template_name: (details) => [
|
|
101
|
-
[
|
|
102
|
-
formatMessage({ id: `${messagePrefix}.templateName` }),
|
|
103
|
-
details.template_name,
|
|
104
|
-
],
|
|
105
|
-
],
|
|
106
|
-
invalid_domain_external_id: (details) => [
|
|
107
|
-
[
|
|
108
|
-
formatMessage({ id: `${messagePrefix}.domainExternalId` }),
|
|
109
|
-
details.domain_external_id,
|
|
110
|
-
],
|
|
111
|
-
],
|
|
112
|
-
invalid_associated_rule: (details) => [
|
|
113
|
-
[formatMessage({ id: `${messagePrefix}.ruleName` }), details.rule_name],
|
|
114
|
-
],
|
|
115
|
-
deprecated_implementation: (details) => [
|
|
116
|
-
[formatMessage({ id: `${messagePrefix}.implementation`, }), details.implementation_key],
|
|
117
|
-
],
|
|
118
|
-
implementation_creation_error: (details) =>
|
|
119
|
-
details?.map(([field, [error]]) => [field, error]),
|
|
120
|
-
duplicate_field_names: (details) => [
|
|
121
|
-
[formatMessage({ id: `${messagePrefix}.duplicatedNames` }), details?.duplicate_fields?.join(", ")],
|
|
122
|
-
],
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const detailBuilder = _.propOr(() => { }, type)(detailBuilders);
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<div>
|
|
129
|
-
<CellHeader
|
|
130
|
-
header={`implementations.bulkUpload.error.${type}`}
|
|
131
|
-
response={response}
|
|
132
|
-
/>
|
|
133
|
-
|
|
134
|
-
{response.details
|
|
135
|
-
? detailBuilder(response.details).map(([key, value], idx) => (
|
|
136
|
-
<div key={idx} style={{ paddingLeft: "10px" }}>
|
|
137
|
-
<span
|
|
138
|
-
style={{
|
|
139
|
-
fontSize: "1em",
|
|
140
|
-
fontWeight: "bold",
|
|
141
|
-
paddingRight: "5px",
|
|
142
|
-
}}
|
|
143
|
-
>
|
|
144
|
-
{key}:
|
|
145
|
-
</span>
|
|
146
|
-
{value}
|
|
147
|
-
</div>
|
|
148
|
-
))
|
|
149
|
-
: null}
|
|
150
|
-
</div>
|
|
151
|
-
);
|
|
152
|
-
};
|
|
153
|
-
const summaryItems = [
|
|
154
|
-
{
|
|
155
|
-
key: "invalid_sheet_count",
|
|
156
|
-
messageId: "implementations.bulkUpload.result.summary.invalid_sheets",
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
key: "insert_count",
|
|
160
|
-
messageId: "implementations.bulkUpload.result.summary.created",
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
key: "update_count",
|
|
164
|
-
messageId: "implementations.bulkUpload.result.summary.updated",
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
key: "unchanged_count",
|
|
168
|
-
messageId: "implementations.bulkUpload.result.summary.unchanged",
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
key: "error_count",
|
|
172
|
-
messageId: "implementations.bulkUpload.result.summary.errors",
|
|
173
|
-
},
|
|
174
|
-
];
|
|
175
|
-
const CompletedDetail = ({ response }) => {
|
|
176
|
-
const items = _.flow([
|
|
177
|
-
_.filter((item) => _.get(item.key, response) > 0),
|
|
178
|
-
(items) => {
|
|
179
|
-
const result = [];
|
|
180
|
-
for (let i = 0; i < items.length; i++) {
|
|
181
|
-
const item = items[i];
|
|
182
|
-
result.push(
|
|
183
|
-
<span key={item.key}>
|
|
184
|
-
<FormattedMessage id={item.messageId} />
|
|
185
|
-
{`: ${response[item.key]}`}
|
|
186
|
-
{i < items.length - 1 && <span> | </span>}
|
|
187
|
-
</span>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
return result;
|
|
191
|
-
}
|
|
192
|
-
])(summaryItems);
|
|
193
|
-
|
|
194
|
-
if (items.length === 0) return null;
|
|
195
|
-
|
|
196
|
-
return (
|
|
197
|
-
<span> | {items} | </span>
|
|
198
|
-
);
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
const InfoDetail = ({ response }) =>
|
|
202
|
-
!response || !response.type ? null : (
|
|
203
|
-
<div>
|
|
204
|
-
<CellHeader
|
|
205
|
-
header={`implementations.bulkUpload.info.${response.type}`}
|
|
206
|
-
response={response}
|
|
207
|
-
/>
|
|
208
|
-
{response.details ? (
|
|
209
|
-
<div>
|
|
210
|
-
<RuleImplementationLink {...response.details} />{" "}
|
|
211
|
-
</div>
|
|
212
|
-
) : null}
|
|
213
|
-
<ChangesDetail changes={response?.details?.changes} />
|
|
214
|
-
</div>
|
|
215
|
-
);
|
|
216
|
-
const ChangesDetail = ({
|
|
217
|
-
changes,
|
|
218
|
-
header = "implementations.bulkUpload.result.prop.changes",
|
|
219
|
-
}) => {
|
|
220
|
-
const [isExpanded, setIsExpanded] = useState(false);
|
|
221
|
-
if (!changes) return null;
|
|
222
|
-
const changesList = _.toPairs(changes);
|
|
223
|
-
|
|
224
|
-
return (
|
|
225
|
-
<div>
|
|
226
|
-
<div
|
|
227
|
-
onClick={() => setIsExpanded(!isExpanded)}
|
|
228
|
-
style={{
|
|
229
|
-
cursor: "pointer",
|
|
230
|
-
display: "flex",
|
|
231
|
-
gap: "5px",
|
|
232
|
-
alignItems: "center",
|
|
233
|
-
}}
|
|
234
|
-
>
|
|
235
|
-
<span>{isExpanded ? "▼" : "▶"}</span>
|
|
236
|
-
<span>
|
|
237
|
-
<FormattedMessage id={header} />
|
|
238
|
-
</span>
|
|
239
|
-
<span>({_.size(changesList)}) :</span>
|
|
240
|
-
</div>
|
|
241
|
-
{isExpanded &&
|
|
242
|
-
changesList.map(([key, value]) => (
|
|
243
|
-
<div key={key} style={{ paddingLeft: "10px" }}>
|
|
244
|
-
{key == "df_content" ? (
|
|
245
|
-
<ChangesDetail
|
|
246
|
-
changes={value}
|
|
247
|
-
header={`ruleImplementations.props.${key}`}
|
|
248
|
-
/>
|
|
249
|
-
) : (
|
|
250
|
-
<>
|
|
251
|
-
<span
|
|
252
|
-
style={{
|
|
253
|
-
fontSize: "1em",
|
|
254
|
-
fontWeight: "bold",
|
|
255
|
-
paddingRight: "5px",
|
|
256
|
-
}}
|
|
257
|
-
>
|
|
258
|
-
<FormattedMessage
|
|
259
|
-
id={`ruleImplementations.props.${key}`}
|
|
260
|
-
defaultMessage={key}
|
|
261
|
-
/>
|
|
262
|
-
:
|
|
263
|
-
</span>
|
|
264
|
-
{formatValue(value, key)}
|
|
265
|
-
</>
|
|
266
|
-
)}
|
|
267
|
-
</div>
|
|
268
|
-
))}
|
|
269
|
-
</div>
|
|
270
|
-
);
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const formatValue = (value, key) => {
|
|
275
|
-
if (_.has("value")(value)) return formatValue(value.value, key);
|
|
276
|
-
if (_.isArray(value)) {
|
|
277
|
-
return _.flow(_.map((v) => formatValue(v, key)), _.join(", "))(value);
|
|
278
|
-
}
|
|
279
|
-
if (_.isObject(value)) {
|
|
280
|
-
if (_.has("document")(value))
|
|
281
|
-
return <RichTextEditor readOnly value={value} />;
|
|
282
|
-
if (_.has("url_value")(value))
|
|
283
|
-
return `[${value.url_name}] (${value.url_value})`;
|
|
284
|
-
if (_.has("name")(value))
|
|
285
|
-
return value.name;
|
|
286
|
-
if (_.has("external_id")(value))
|
|
287
|
-
return value.external_id;
|
|
288
|
-
|
|
289
|
-
return _.flow(_.keys, _.join(", "))(value);
|
|
290
|
-
}
|
|
291
|
-
return value;
|
|
292
|
-
};
|