@truedat/core 8.12.12 → 8.13.1
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/TrueModal.js +137 -0
- package/src/components/UploadJob.js +102 -178
- package/src/components/UploadJobBreadcrumbs.js +13 -17
- package/src/components/UploadJobDetails.js +256 -0
- package/src/components/UploadJobHistory.js +90 -0
- package/src/components/UploadJobParser.js +155 -89
- package/src/components/UploadJobResults.js +278 -0
- package/src/components/UploadJobScope.js +37 -0
- package/src/components/UploadJobSummary.js +211 -0
- package/src/components/UploadJobs.js +48 -17
- package/src/components/__tests__/TrueModal.spec.js +193 -0
- package/src/components/__tests__/UploadJob.spec.js +118 -42
- package/src/components/__tests__/UploadJobBreadcrumbs.spec.js +3 -0
- package/src/components/__tests__/UploadJobHistory.spec.js +71 -0
- package/src/components/__tests__/UploadJobParser.spec.js +560 -26
- package/src/components/__tests__/UploadJobResults.spec.js +289 -0
- package/src/components/__tests__/UploadJobScope.spec.js +38 -0
- package/src/components/__tests__/UploadJobSummary.spec.js +208 -0
- package/src/components/__tests__/UploadJobs.spec.js +133 -1
- package/src/components/index.js +2 -0
- package/src/styles/ModalEffects.less +43 -0
- package/src/styles/TrueModal.less +138 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import { useId, useState } from "react";
|
|
4
|
+
import { FormattedMessage } from "react-intl";
|
|
5
|
+
import { getUploadJobScope } from "./UploadJobScope";
|
|
6
|
+
|
|
7
|
+
export const ErrorDetailsDisclosure = ({ details }) => {
|
|
8
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
9
|
+
const detailsId = useId();
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="upload-job-change-details upload-job-error-details">
|
|
13
|
+
<button
|
|
14
|
+
type="button"
|
|
15
|
+
className="upload-job-change-toggle upload-job-error-toggle"
|
|
16
|
+
aria-controls={detailsId}
|
|
17
|
+
aria-expanded={isExpanded}
|
|
18
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
19
|
+
>
|
|
20
|
+
<span aria-hidden="true">{isExpanded ? "▼" : "▶"}</span>
|
|
21
|
+
<FormattedMessage
|
|
22
|
+
id="uploadJob.results.errors.errorFields"
|
|
23
|
+
values={{ count: details.length }}
|
|
24
|
+
/>
|
|
25
|
+
</button>
|
|
26
|
+
<div
|
|
27
|
+
id={detailsId}
|
|
28
|
+
className="upload-job-change-table-wrap"
|
|
29
|
+
hidden={!isExpanded}
|
|
30
|
+
>
|
|
31
|
+
{isExpanded ? (
|
|
32
|
+
<table className="upload-job-change-table upload-job-error-table">
|
|
33
|
+
<thead>
|
|
34
|
+
<tr>
|
|
35
|
+
<th>
|
|
36
|
+
<FormattedMessage id="uploadJob.results.errors.field" />
|
|
37
|
+
</th>
|
|
38
|
+
<th>
|
|
39
|
+
<FormattedMessage id="uploadJob.results.errors.error" />
|
|
40
|
+
</th>
|
|
41
|
+
</tr>
|
|
42
|
+
</thead>
|
|
43
|
+
<tbody>
|
|
44
|
+
{details.map(([field, error], index) => (
|
|
45
|
+
<tr key={`${field ?? "message"}-${index}`}>
|
|
46
|
+
<th scope="row">
|
|
47
|
+
{field ?? (
|
|
48
|
+
<FormattedMessage id="uploadJob.parser.detail.message" />
|
|
49
|
+
)}
|
|
50
|
+
</th>
|
|
51
|
+
<td>{error}</td>
|
|
52
|
+
</tr>
|
|
53
|
+
))}
|
|
54
|
+
</tbody>
|
|
55
|
+
</table>
|
|
56
|
+
) : null}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
ErrorDetailsDisclosure.propTypes = {
|
|
63
|
+
details: PropTypes.arrayOf(PropTypes.array),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
ErrorDetailsDisclosure.defaultProps = {
|
|
67
|
+
details: [],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const isChangePair = (value) =>
|
|
71
|
+
_.isObject(value) &&
|
|
72
|
+
(Object.prototype.hasOwnProperty.call(value, "old") ||
|
|
73
|
+
Object.prototype.hasOwnProperty.call(value, "new"));
|
|
74
|
+
|
|
75
|
+
export const flattenChanges = (changes, parent = "") =>
|
|
76
|
+
_.toPairs(changes).flatMap(([key, value]) => {
|
|
77
|
+
const path = parent ? `${parent}.${key}` : key;
|
|
78
|
+
if (isChangePair(value)) {
|
|
79
|
+
return [{ key: path, oldValue: value.old, newValue: value.new }];
|
|
80
|
+
}
|
|
81
|
+
if (
|
|
82
|
+
_.isObject(value) &&
|
|
83
|
+
!_.isArray(value) &&
|
|
84
|
+
(key === "df_content" || containsChangePair(value))
|
|
85
|
+
) {
|
|
86
|
+
return flattenChanges(value, path);
|
|
87
|
+
}
|
|
88
|
+
return [{ key: path, oldValue: undefined, newValue: value }];
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const containsChangePair = (value) =>
|
|
92
|
+
_.isObject(value) &&
|
|
93
|
+
(isChangePair(value) ||
|
|
94
|
+
_.toPairs(value).some(([, nestedValue]) =>
|
|
95
|
+
containsChangePair(nestedValue),
|
|
96
|
+
));
|
|
97
|
+
|
|
98
|
+
export const ChangeField = ({ path, scope }) => {
|
|
99
|
+
const [root, ...nested] = path.split(".");
|
|
100
|
+
const field = nested.at(-1) ?? root;
|
|
101
|
+
const config = getUploadJobScope(scope);
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<FormattedMessage
|
|
105
|
+
id={
|
|
106
|
+
config.fieldMessageOverrides?.[field] ??
|
|
107
|
+
`${config.fieldMessagePrefix}.${field}`
|
|
108
|
+
}
|
|
109
|
+
defaultMessage={field}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
ChangeField.propTypes = {
|
|
115
|
+
path: PropTypes.string.isRequired,
|
|
116
|
+
scope: PropTypes.string,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
ChangeField.defaultProps = {
|
|
120
|
+
scope: "implementations",
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const formatValue = (value, key) => {
|
|
124
|
+
if (_.has("value")(value)) return formatValue(value.value, key);
|
|
125
|
+
if (_.isArray(value)) {
|
|
126
|
+
return _.flow(
|
|
127
|
+
_.map((v) => formatValue(v, key)),
|
|
128
|
+
_.join(", "),
|
|
129
|
+
)(value);
|
|
130
|
+
}
|
|
131
|
+
if (_.isObject(value)) {
|
|
132
|
+
if (_.has("url_value")(value))
|
|
133
|
+
return `[${value.url_name}] (${value.url_value})`;
|
|
134
|
+
if (_.has("name")(value)) return value.name;
|
|
135
|
+
if (_.has("external_id")(value)) return value.external_id;
|
|
136
|
+
return _.flow(_.keys, _.join(", "))(value);
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const ChangeValue = ({ className, value }) => {
|
|
142
|
+
const displayValue = value == null ? "—" : String(formatValue(value));
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<span className={className} title={displayValue}>
|
|
146
|
+
{displayValue}
|
|
147
|
+
</span>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
ChangeValue.propTypes = {
|
|
152
|
+
className: PropTypes.string.isRequired,
|
|
153
|
+
value: PropTypes.oneOfType([
|
|
154
|
+
PropTypes.string,
|
|
155
|
+
PropTypes.number,
|
|
156
|
+
PropTypes.bool,
|
|
157
|
+
PropTypes.array,
|
|
158
|
+
PropTypes.object,
|
|
159
|
+
]),
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
ChangeValue.defaultProps = {
|
|
163
|
+
value: undefined,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export const ChangesDetail = ({ changes, changeDetails, scope }) => {
|
|
167
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
168
|
+
const detailsId = useId();
|
|
169
|
+
const details = changeDetails ?? changes;
|
|
170
|
+
|
|
171
|
+
if (!details) return null;
|
|
172
|
+
|
|
173
|
+
const changesList = flattenChanges(details);
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<div className="upload-job-change-details">
|
|
177
|
+
<button
|
|
178
|
+
type="button"
|
|
179
|
+
className="upload-job-change-toggle"
|
|
180
|
+
aria-controls={detailsId}
|
|
181
|
+
aria-expanded={isExpanded}
|
|
182
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
183
|
+
>
|
|
184
|
+
<span aria-hidden="true">{isExpanded ? "▼" : "▶"}</span>
|
|
185
|
+
<FormattedMessage
|
|
186
|
+
id="uploadJob.results.changes.modifiedFields"
|
|
187
|
+
values={{ count: changesList.length }}
|
|
188
|
+
/>
|
|
189
|
+
</button>
|
|
190
|
+
<div
|
|
191
|
+
id={detailsId}
|
|
192
|
+
className="upload-job-change-table-wrap"
|
|
193
|
+
hidden={!isExpanded}
|
|
194
|
+
>
|
|
195
|
+
{isExpanded ? (
|
|
196
|
+
<table className="upload-job-change-table">
|
|
197
|
+
<thead>
|
|
198
|
+
<tr>
|
|
199
|
+
<th>
|
|
200
|
+
<FormattedMessage id="uploadJob.results.changes.field" />
|
|
201
|
+
</th>
|
|
202
|
+
<th>
|
|
203
|
+
<FormattedMessage id="uploadJob.results.changes.previous" />
|
|
204
|
+
</th>
|
|
205
|
+
<th>
|
|
206
|
+
<span className="upload-job-visually-hidden">
|
|
207
|
+
<FormattedMessage id="uploadJob.results.changes.change" />
|
|
208
|
+
</span>
|
|
209
|
+
</th>
|
|
210
|
+
<th>
|
|
211
|
+
<FormattedMessage id="uploadJob.results.changes.new" />
|
|
212
|
+
</th>
|
|
213
|
+
</tr>
|
|
214
|
+
</thead>
|
|
215
|
+
<tbody>
|
|
216
|
+
{changesList.map(({ key, oldValue, newValue }) => (
|
|
217
|
+
<tr key={key}>
|
|
218
|
+
<th scope="row">
|
|
219
|
+
<ChangeField path={key} scope={scope} />
|
|
220
|
+
</th>
|
|
221
|
+
<td>
|
|
222
|
+
<ChangeValue
|
|
223
|
+
className="upload-job-old-value upload-job-change-value"
|
|
224
|
+
value={oldValue}
|
|
225
|
+
/>
|
|
226
|
+
</td>
|
|
227
|
+
<td className="upload-job-change-arrow">
|
|
228
|
+
<span aria-hidden="true">→</span>
|
|
229
|
+
</td>
|
|
230
|
+
<td>
|
|
231
|
+
<ChangeValue
|
|
232
|
+
className="upload-job-new-value upload-job-change-value"
|
|
233
|
+
value={newValue}
|
|
234
|
+
/>
|
|
235
|
+
</td>
|
|
236
|
+
</tr>
|
|
237
|
+
))}
|
|
238
|
+
</tbody>
|
|
239
|
+
</table>
|
|
240
|
+
) : null}
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
ChangesDetail.propTypes = {
|
|
247
|
+
changeDetails: PropTypes.object,
|
|
248
|
+
changes: PropTypes.object,
|
|
249
|
+
scope: PropTypes.string,
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
ChangesDetail.defaultProps = {
|
|
253
|
+
changeDetails: undefined,
|
|
254
|
+
changes: undefined,
|
|
255
|
+
scope: "implementations",
|
|
256
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import PropTypes from "prop-types";
|
|
2
|
+
import { FormattedMessage } from "react-intl";
|
|
3
|
+
import { Table } from "semantic-ui-react";
|
|
4
|
+
import DateTime from "@truedat/core/components/DateTime";
|
|
5
|
+
import { StatusPill } from "./UploadJobParser";
|
|
6
|
+
import { lifecycleStatuses } from "./UploadJobSummary";
|
|
7
|
+
|
|
8
|
+
export const getLifecycleEvents = (events = []) =>
|
|
9
|
+
events.filter(({ status }) => lifecycleStatuses.includes(status));
|
|
10
|
+
|
|
11
|
+
const UploadJobHistory = ({ events }) => {
|
|
12
|
+
const lifecycleEvents = getLifecycleEvents(events);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<section
|
|
16
|
+
className="upload-job-history"
|
|
17
|
+
aria-labelledby="upload-job-history-title"
|
|
18
|
+
>
|
|
19
|
+
<h3 id="upload-job-history-title">
|
|
20
|
+
<FormattedMessage id="uploadJob.history.title" />
|
|
21
|
+
</h3>
|
|
22
|
+
<Table
|
|
23
|
+
basic="very"
|
|
24
|
+
className="upload-job-history-table"
|
|
25
|
+
verticalAlign="middle"
|
|
26
|
+
>
|
|
27
|
+
<Table.Header>
|
|
28
|
+
<Table.Row>
|
|
29
|
+
<Table.HeaderCell className="upload-job-visually-hidden">
|
|
30
|
+
<FormattedMessage id="uploadJob.table.status" />
|
|
31
|
+
</Table.HeaderCell>
|
|
32
|
+
<Table.HeaderCell className="upload-job-visually-hidden">
|
|
33
|
+
<FormattedMessage id="uploadJob.table.inserted_at" />
|
|
34
|
+
</Table.HeaderCell>
|
|
35
|
+
<Table.HeaderCell className="upload-job-visually-hidden">
|
|
36
|
+
<FormattedMessage id="uploadJob.table.response" />
|
|
37
|
+
</Table.HeaderCell>
|
|
38
|
+
</Table.Row>
|
|
39
|
+
</Table.Header>
|
|
40
|
+
<Table.Body>
|
|
41
|
+
{lifecycleEvents.map((event, index) => (
|
|
42
|
+
<Table.Row
|
|
43
|
+
key={event.id ?? `${event.status}-${index}`}
|
|
44
|
+
className={
|
|
45
|
+
index === lifecycleEvents.length - 1
|
|
46
|
+
? "upload-job-history-final"
|
|
47
|
+
: "upload-job-history-intermediate"
|
|
48
|
+
}
|
|
49
|
+
>
|
|
50
|
+
<Table.Cell textAlign="center">
|
|
51
|
+
<span className="upload-job-history-pill">
|
|
52
|
+
<StatusPill status={event.status} />
|
|
53
|
+
</span>
|
|
54
|
+
</Table.Cell>
|
|
55
|
+
<Table.Cell>
|
|
56
|
+
<DateTime value={event.inserted_at} />
|
|
57
|
+
</Table.Cell>
|
|
58
|
+
<Table.Cell>
|
|
59
|
+
<FormattedMessage id={`uploadJob.history.${event.status}`} />
|
|
60
|
+
</Table.Cell>
|
|
61
|
+
</Table.Row>
|
|
62
|
+
))}
|
|
63
|
+
{lifecycleEvents.length === 0 ? (
|
|
64
|
+
<Table.Row>
|
|
65
|
+
<Table.Cell colSpan={3}>
|
|
66
|
+
<FormattedMessage id="uploadJob.history.empty" />
|
|
67
|
+
</Table.Cell>
|
|
68
|
+
</Table.Row>
|
|
69
|
+
) : null}
|
|
70
|
+
</Table.Body>
|
|
71
|
+
</Table>
|
|
72
|
+
</section>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
UploadJobHistory.propTypes = {
|
|
77
|
+
events: PropTypes.arrayOf(
|
|
78
|
+
PropTypes.shape({
|
|
79
|
+
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
80
|
+
inserted_at: PropTypes.string,
|
|
81
|
+
status: PropTypes.string,
|
|
82
|
+
}),
|
|
83
|
+
),
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
UploadJobHistory.defaultProps = {
|
|
87
|
+
events: [],
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default UploadJobHistory;
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
|
-
import
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
3
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
4
4
|
import Label from "semantic-ui-react/dist/commonjs/elements/Label";
|
|
5
5
|
import RuleImplementationLink from "@truedat/dq/components/RuleImplementationLink";
|
|
6
6
|
import StructureNoteLink from "@truedat/dd/components/StructureNoteLink";
|
|
7
|
+
import { ChangesDetail, ErrorDetailsDisclosure } from "./UploadJobDetails";
|
|
8
|
+
import { classifyUploadJobErrorMessage } from "./UploadJobSummary";
|
|
7
9
|
|
|
8
10
|
export const StatusPill = ({ status }) => (
|
|
9
11
|
<Label
|
|
10
12
|
color={_.cond([
|
|
11
13
|
[_.eq("COMPLETED"), _.constant("green")],
|
|
12
14
|
[_.eq("FAILED"), _.constant("red")],
|
|
15
|
+
[_.eq("PENDING"), _.constant("yellow")],
|
|
16
|
+
[_.eq("STARTED"), _.constant("blue")],
|
|
13
17
|
[_.eq("ERROR"), _.constant("red")],
|
|
14
18
|
[_.eq("INFO"), _.constant("blue")],
|
|
15
19
|
[_.stubTrue, _.constant("grey")],
|
|
@@ -20,23 +24,34 @@ export const StatusPill = ({ status }) => (
|
|
|
20
24
|
</Label>
|
|
21
25
|
);
|
|
22
26
|
|
|
23
|
-
export const ResponseCell = ({
|
|
27
|
+
export const ResponseCell = ({
|
|
28
|
+
response,
|
|
29
|
+
status,
|
|
30
|
+
showOrigin = true,
|
|
31
|
+
scope,
|
|
32
|
+
}) => {
|
|
24
33
|
if (!response || (_.isObject(response) && _.isEmpty(response))) return null;
|
|
25
34
|
|
|
26
35
|
switch (status) {
|
|
27
|
-
case "FAILED":
|
|
36
|
+
case "FAILED": {
|
|
37
|
+
const error = classifyUploadJobErrorMessage(response.message);
|
|
28
38
|
return (
|
|
29
39
|
<FormattedMessage
|
|
30
|
-
id={`uploadJob.parser.error.${
|
|
31
|
-
|
|
40
|
+
id={`uploadJob.parser.error.${
|
|
41
|
+
error.type === "message" ? error.message : error.type
|
|
42
|
+
}`}
|
|
43
|
+
defaultMessage={error.message}
|
|
32
44
|
/>
|
|
33
45
|
);
|
|
46
|
+
}
|
|
34
47
|
case "ERROR":
|
|
35
|
-
return <ErrorDetail response={response} />;
|
|
48
|
+
return <ErrorDetail response={response} showOrigin={showOrigin} />;
|
|
36
49
|
case "COMPLETED":
|
|
37
50
|
return <CompletedDetail response={response} />;
|
|
38
51
|
case "INFO":
|
|
39
|
-
return
|
|
52
|
+
return (
|
|
53
|
+
<InfoDetail response={response} showOrigin={showOrigin} scope={scope} />
|
|
54
|
+
);
|
|
40
55
|
default:
|
|
41
56
|
return (
|
|
42
57
|
<pre
|
|
@@ -54,6 +69,86 @@ export const ResponseCell = ({ response, status }) => {
|
|
|
54
69
|
}
|
|
55
70
|
};
|
|
56
71
|
|
|
72
|
+
export const ResultEntityCell = ({ response, status }) => {
|
|
73
|
+
if (!response?.details || !["INFO", "ERROR"].includes(status)) return null;
|
|
74
|
+
|
|
75
|
+
if (status === "ERROR" && response.type === "data_structure_not_found") {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return response.details.implementation_key != null ? (
|
|
80
|
+
<RuleImplementationLink {...response.details} />
|
|
81
|
+
) : status === "INFO" &&
|
|
82
|
+
(response.details.data_structure_id != null ||
|
|
83
|
+
response.details.external_id != null) ? (
|
|
84
|
+
<StructureNoteLink
|
|
85
|
+
data_structure_id={response.details.data_structure_id}
|
|
86
|
+
external_id={response.details.external_id}
|
|
87
|
+
alias={response.details.alias}
|
|
88
|
+
name={response.details.name}
|
|
89
|
+
/>
|
|
90
|
+
) : status === "ERROR" &&
|
|
91
|
+
response.details.data_structure_id != null &&
|
|
92
|
+
response.details.external_id != null ? (
|
|
93
|
+
<StructureNoteLink
|
|
94
|
+
data_structure_id={response.details.data_structure_id}
|
|
95
|
+
external_id={response.details.external_id}
|
|
96
|
+
alias={response.details.alias}
|
|
97
|
+
name={response.details.name}
|
|
98
|
+
/>
|
|
99
|
+
) : null;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
ResultEntityCell.propTypes = {
|
|
103
|
+
response: PropTypes.shape({
|
|
104
|
+
details: PropTypes.shape({
|
|
105
|
+
data_structure_id: PropTypes.oneOfType([
|
|
106
|
+
PropTypes.number,
|
|
107
|
+
PropTypes.string,
|
|
108
|
+
]),
|
|
109
|
+
external_id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
110
|
+
alias: PropTypes.string,
|
|
111
|
+
implementation_key: PropTypes.string,
|
|
112
|
+
implementation_ref: PropTypes.oneOfType([
|
|
113
|
+
PropTypes.number,
|
|
114
|
+
PropTypes.string,
|
|
115
|
+
]),
|
|
116
|
+
name: PropTypes.string,
|
|
117
|
+
}),
|
|
118
|
+
}),
|
|
119
|
+
status: PropTypes.string,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const ResultDetailCell = ({ response, status, scope }) => {
|
|
123
|
+
if (status === "ERROR")
|
|
124
|
+
return <ErrorDetail response={response} showOrigin={false} />;
|
|
125
|
+
if (
|
|
126
|
+
status !== "INFO" ||
|
|
127
|
+
!["updated", "implementation_updated"].includes(response?.type)
|
|
128
|
+
)
|
|
129
|
+
return null;
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<ChangesDetail
|
|
133
|
+
changes={response?.details?.changes}
|
|
134
|
+
changeDetails={response?.details?.change_details}
|
|
135
|
+
scope={scope}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
ResultDetailCell.propTypes = {
|
|
141
|
+
response: PropTypes.shape({
|
|
142
|
+
details: PropTypes.shape({
|
|
143
|
+
change_details: PropTypes.object,
|
|
144
|
+
changes: PropTypes.object,
|
|
145
|
+
}),
|
|
146
|
+
type: PropTypes.string,
|
|
147
|
+
}),
|
|
148
|
+
scope: PropTypes.string,
|
|
149
|
+
status: PropTypes.string,
|
|
150
|
+
};
|
|
151
|
+
|
|
57
152
|
const SheetAndLine = ({ response }) => (
|
|
58
153
|
<Label size="small">
|
|
59
154
|
<span>{response.sheet}</span>
|
|
@@ -67,14 +162,16 @@ const SheetAndLine = ({ response }) => (
|
|
|
67
162
|
</Label>
|
|
68
163
|
);
|
|
69
164
|
|
|
70
|
-
const CellHeader = ({ header, response }) => (
|
|
165
|
+
const CellHeader = ({ header, response, showOrigin = true }) => (
|
|
71
166
|
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
|
72
167
|
<b>
|
|
73
168
|
<FormattedMessage id={header} />
|
|
74
169
|
</b>
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
|
|
170
|
+
{showOrigin ? (
|
|
171
|
+
<div>
|
|
172
|
+
<SheetAndLine response={response} />
|
|
173
|
+
</div>
|
|
174
|
+
) : null}
|
|
78
175
|
</div>
|
|
79
176
|
);
|
|
80
177
|
|
|
@@ -83,12 +180,14 @@ const externalIdDetailValue = (details) =>
|
|
|
83
180
|
<StructureNoteLink
|
|
84
181
|
data_structure_id={details.data_structure_id}
|
|
85
182
|
external_id={details.external_id}
|
|
183
|
+
alias={details.alias}
|
|
184
|
+
name={details.name}
|
|
86
185
|
/>
|
|
87
186
|
) : (
|
|
88
187
|
(details?.external_id ?? "")
|
|
89
188
|
);
|
|
90
189
|
|
|
91
|
-
const ErrorDetail = ({ response }) => {
|
|
190
|
+
const ErrorDetail = ({ response, showOrigin }) => {
|
|
92
191
|
const { formatMessage } = useIntl();
|
|
93
192
|
|
|
94
193
|
if (!response || !response.type) {
|
|
@@ -97,6 +196,14 @@ const ErrorDetail = ({ response }) => {
|
|
|
97
196
|
const { type } = response;
|
|
98
197
|
const messagePrefix = "uploadJob.parser.detail";
|
|
99
198
|
|
|
199
|
+
if (!showOrigin && type === "data_structure_not_found") {
|
|
200
|
+
return (
|
|
201
|
+
<span className="upload-job-simple-error">
|
|
202
|
+
<FormattedMessage id="uploadJob.parser.error.data_structure_not_found" />
|
|
203
|
+
</span>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
100
207
|
const detailBuilders = {
|
|
101
208
|
missing_required_headers: (details) => [
|
|
102
209
|
[
|
|
@@ -225,11 +332,29 @@ const ErrorDetail = ({ response }) => {
|
|
|
225
332
|
details,
|
|
226
333
|
);
|
|
227
334
|
|
|
335
|
+
const resultDetails =
|
|
336
|
+
!showOrigin &&
|
|
337
|
+
_.isEmpty(visibleDetails) &&
|
|
338
|
+
response.details != null &&
|
|
339
|
+
(typeof response.details !== "object" || !_.isEmpty(response.details))
|
|
340
|
+
? [
|
|
341
|
+
[
|
|
342
|
+
formatMessage({ id: `${messagePrefix}.message` }),
|
|
343
|
+
formatErrorValue(response.details),
|
|
344
|
+
],
|
|
345
|
+
]
|
|
346
|
+
: visibleDetails;
|
|
347
|
+
|
|
348
|
+
if (!showOrigin && !_.isEmpty(resultDetails)) {
|
|
349
|
+
return <ErrorDetailsDisclosure details={resultDetails} />;
|
|
350
|
+
}
|
|
351
|
+
|
|
228
352
|
return (
|
|
229
353
|
<div>
|
|
230
354
|
<CellHeader
|
|
231
355
|
header={`uploadJob.parser.error.${type}`}
|
|
232
356
|
response={response}
|
|
357
|
+
showOrigin={showOrigin}
|
|
233
358
|
/>
|
|
234
359
|
|
|
235
360
|
{_.isEmpty(visibleDetails)
|
|
@@ -253,6 +378,15 @@ const ErrorDetail = ({ response }) => {
|
|
|
253
378
|
</div>
|
|
254
379
|
);
|
|
255
380
|
};
|
|
381
|
+
|
|
382
|
+
const formatErrorValue = (value) => {
|
|
383
|
+
if (typeof value === "string" || typeof value === "number") return value;
|
|
384
|
+
if (typeof value === "boolean") return String(value);
|
|
385
|
+
if (_.isArray(value)) return value.map(formatErrorValue).join(", ");
|
|
386
|
+
if (_.isObject(value)) return JSON.stringify(value);
|
|
387
|
+
return String(value ?? "");
|
|
388
|
+
};
|
|
389
|
+
|
|
256
390
|
const summaryItems = [
|
|
257
391
|
{
|
|
258
392
|
key: "invalid_sheet_count",
|
|
@@ -301,7 +435,7 @@ const CompletedDetail = ({ response }) => {
|
|
|
301
435
|
return <span> | {items} | </span>;
|
|
302
436
|
};
|
|
303
437
|
|
|
304
|
-
const InfoDetail = ({ response }) =>
|
|
438
|
+
const InfoDetail = ({ response, showOrigin, scope }) =>
|
|
305
439
|
!response || !response.type ? null : (
|
|
306
440
|
<div>
|
|
307
441
|
<CellHeader
|
|
@@ -311,6 +445,7 @@ const InfoDetail = ({ response }) =>
|
|
|
311
445
|
: `uploadJob.parser.info.${response.type}`
|
|
312
446
|
}
|
|
313
447
|
response={response}
|
|
448
|
+
showOrigin={showOrigin}
|
|
314
449
|
/>
|
|
315
450
|
{response.details ? (
|
|
316
451
|
<div>
|
|
@@ -321,85 +456,16 @@ const InfoDetail = ({ response }) =>
|
|
|
321
456
|
<StructureNoteLink
|
|
322
457
|
data_structure_id={response.details.data_structure_id}
|
|
323
458
|
external_id={response.details.external_id}
|
|
459
|
+
alias={response.details.alias}
|
|
460
|
+
name={response.details.name}
|
|
324
461
|
/>
|
|
325
462
|
) : null}{" "}
|
|
326
463
|
</div>
|
|
327
464
|
) : null}
|
|
328
|
-
<ChangesDetail
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
header = "uploadJob.parser.result.prop.changes",
|
|
334
|
-
}) => {
|
|
335
|
-
const [isExpanded, setIsExpanded] = useState(false);
|
|
336
|
-
if (!changes) return null;
|
|
337
|
-
const changesList = _.toPairs(changes);
|
|
338
|
-
|
|
339
|
-
return (
|
|
340
|
-
<div>
|
|
341
|
-
<div
|
|
342
|
-
onClick={() => setIsExpanded(!isExpanded)}
|
|
343
|
-
style={{
|
|
344
|
-
cursor: "pointer",
|
|
345
|
-
display: "flex",
|
|
346
|
-
gap: "5px",
|
|
347
|
-
alignItems: "center",
|
|
348
|
-
}}
|
|
349
|
-
>
|
|
350
|
-
<span>{isExpanded ? "▼" : "▶"}</span>
|
|
351
|
-
<span>
|
|
352
|
-
<FormattedMessage id={header} />
|
|
353
|
-
</span>
|
|
354
|
-
<span>({_.size(changesList)}) :</span>
|
|
355
|
-
</div>
|
|
356
|
-
{isExpanded &&
|
|
357
|
-
changesList.map(([key, value]) => (
|
|
358
|
-
<div key={key} style={{ paddingLeft: "10px" }}>
|
|
359
|
-
{key == "df_content" ? (
|
|
360
|
-
<ChangesDetail
|
|
361
|
-
changes={value}
|
|
362
|
-
header={`ruleImplementations.props.${key}`}
|
|
363
|
-
/>
|
|
364
|
-
) : (
|
|
365
|
-
<>
|
|
366
|
-
<span
|
|
367
|
-
style={{
|
|
368
|
-
fontSize: "1em",
|
|
369
|
-
fontWeight: "bold",
|
|
370
|
-
paddingRight: "5px",
|
|
371
|
-
}}
|
|
372
|
-
>
|
|
373
|
-
<FormattedMessage
|
|
374
|
-
id={`ruleImplementations.props.${key}`}
|
|
375
|
-
defaultMessage={key}
|
|
376
|
-
/>
|
|
377
|
-
:
|
|
378
|
-
</span>
|
|
379
|
-
{formatValue(value, key)}
|
|
380
|
-
</>
|
|
381
|
-
)}
|
|
382
|
-
</div>
|
|
383
|
-
))}
|
|
465
|
+
<ChangesDetail
|
|
466
|
+
changes={response?.details?.changes}
|
|
467
|
+
changeDetails={response?.details?.change_details}
|
|
468
|
+
scope={scope}
|
|
469
|
+
/>
|
|
384
470
|
</div>
|
|
385
471
|
);
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
const formatValue = (value, key) => {
|
|
389
|
-
if (_.has("value")(value)) return formatValue(value.value, key);
|
|
390
|
-
if (_.isArray(value)) {
|
|
391
|
-
return _.flow(
|
|
392
|
-
_.map((v) => formatValue(v, key)),
|
|
393
|
-
_.join(", "),
|
|
394
|
-
)(value);
|
|
395
|
-
}
|
|
396
|
-
if (_.isObject(value)) {
|
|
397
|
-
if (_.has("url_value")(value))
|
|
398
|
-
return `[${value.url_name}] (${value.url_value})`;
|
|
399
|
-
if (_.has("name")(value)) return value.name;
|
|
400
|
-
if (_.has("external_id")(value)) return value.external_id;
|
|
401
|
-
|
|
402
|
-
return _.flow(_.keys, _.join(", "))(value);
|
|
403
|
-
}
|
|
404
|
-
return value;
|
|
405
|
-
};
|