@truedat/core 8.13.4 → 8.13.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/GlossaryMenu.js +4 -4
- package/src/components/UploadJobDetails.js +1 -1
- package/src/components/UploadJobParser.js +76 -11
- package/src/components/UploadJobScope.js +9 -0
- package/src/components/UploadJobSummary.js +2 -1
- package/src/components/__tests__/UploadJobBreadcrumbs.spec.js +10 -0
- package/src/components/__tests__/UploadJobParser.spec.js +217 -0
- package/src/components/__tests__/UploadJobScope.spec.js +17 -0
- package/src/components/__tests__/UploadJobSummary.spec.js +27 -0
- package/src/components/__tests__/UploadJobs.spec.js +15 -0
- package/src/components/__tests__/__snapshots__/GlossaryMenu.spec.js.snap +1 -1
- package/src/components/__tests__/__snapshots__/SideMenu.spec.js.snap +1 -1
- package/src/components/__tests__/__snapshots__/UploadJobBreadcrumbs.spec.js.snap +16 -0
- package/src/routes.js +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/core",
|
|
3
|
-
"version": "8.13.
|
|
3
|
+
"version": "8.13.5",
|
|
4
4
|
"description": "Truedat Web Core",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@testing-library/jest-dom": "^6.6.3",
|
|
58
58
|
"@testing-library/react": "^16.3.0",
|
|
59
59
|
"@testing-library/user-event": "^14.6.1",
|
|
60
|
-
"@truedat/test": "8.13.
|
|
60
|
+
"@truedat/test": "8.13.5",
|
|
61
61
|
"identity-obj-proxy": "^3.0.0",
|
|
62
62
|
"jest": "^29.7.0",
|
|
63
63
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -99,5 +99,5 @@
|
|
|
99
99
|
"swr": "^2.3.3",
|
|
100
100
|
"turndown": "^7.2.2"
|
|
101
101
|
},
|
|
102
|
-
"gitHead": "
|
|
102
|
+
"gitHead": "9b87cbb7143f20b305b84077c1fcf004e5b27726"
|
|
103
103
|
}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
CONCEPTS_DEPRECATED,
|
|
16
16
|
CONCEPT_LINKS_MANAGEMENT,
|
|
17
17
|
CONCEPT_LINKS_APPROVALS,
|
|
18
|
-
|
|
18
|
+
CONCEPTS_UPLOAD_JOBS,
|
|
19
19
|
CONCEPTS_SUBSCOPE,
|
|
20
20
|
CONCEPTS_SIDEMENU_SUBSCOPE,
|
|
21
21
|
CONCEPTS_SIDEMENU_SUBSCOPE_DEPRECATED,
|
|
@@ -101,9 +101,9 @@ function buildMainConceptsSubmenuItems(
|
|
|
101
101
|
};
|
|
102
102
|
const manageUploads = {
|
|
103
103
|
name: "concepts_upload_events",
|
|
104
|
-
routes: [
|
|
104
|
+
routes: [CONCEPTS_UPLOAD_JOBS],
|
|
105
105
|
groups: ["business_glossary_management"],
|
|
106
|
-
isActive: () => pathname ===
|
|
106
|
+
isActive: () => pathname === CONCEPTS_UPLOAD_JOBS,
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
return [
|
|
@@ -171,7 +171,7 @@ function buildManagementSubmenuItems() {
|
|
|
171
171
|
},
|
|
172
172
|
{
|
|
173
173
|
name: "concepts_upload_events",
|
|
174
|
-
routes: [
|
|
174
|
+
routes: [CONCEPTS_UPLOAD_JOBS],
|
|
175
175
|
groups: ["business_glossary_management"],
|
|
176
176
|
},
|
|
177
177
|
];
|
|
@@ -168,7 +168,7 @@ export const ChangesDetail = ({ changes, changeDetails, scope }) => {
|
|
|
168
168
|
const detailsId = useId();
|
|
169
169
|
const details = changeDetails ?? changes;
|
|
170
170
|
|
|
171
|
-
if (!details) return null;
|
|
171
|
+
if (!details || _.isEmpty(details)) return null;
|
|
172
172
|
|
|
173
173
|
const changesList = flattenChanges(details);
|
|
174
174
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
|
+
import { Link } from "react-router";
|
|
3
4
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
4
5
|
import Label from "semantic-ui-react/dist/commonjs/elements/Label";
|
|
5
6
|
import RuleImplementationLink from "@truedat/dq/components/RuleImplementationLink";
|
|
6
7
|
import StructureNoteLink from "@truedat/dd/components/StructureNoteLink";
|
|
8
|
+
import { linkTo } from "../routes";
|
|
7
9
|
import { ChangesDetail, ErrorDetailsDisclosure } from "./UploadJobDetails";
|
|
8
10
|
import { classifyUploadJobErrorMessage } from "./UploadJobSummary";
|
|
9
11
|
|
|
@@ -69,6 +71,19 @@ export const ResponseCell = ({
|
|
|
69
71
|
}
|
|
70
72
|
};
|
|
71
73
|
|
|
74
|
+
const ConceptLink = ({ business_concept_id, id, name }) => (
|
|
75
|
+
<Link to={linkTo.CONCEPT_VERSION({ business_concept_id, id })}>{name}</Link>
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
ConceptLink.propTypes = {
|
|
79
|
+
business_concept_id: PropTypes.oneOfType([
|
|
80
|
+
PropTypes.number,
|
|
81
|
+
PropTypes.string,
|
|
82
|
+
]),
|
|
83
|
+
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
84
|
+
name: PropTypes.string,
|
|
85
|
+
};
|
|
86
|
+
|
|
72
87
|
export const ResultEntityCell = ({ response, status }) => {
|
|
73
88
|
if (!response?.details || !["INFO", "ERROR"].includes(status)) return null;
|
|
74
89
|
|
|
@@ -78,6 +93,8 @@ export const ResultEntityCell = ({ response, status }) => {
|
|
|
78
93
|
|
|
79
94
|
return response.details.implementation_key != null ? (
|
|
80
95
|
<RuleImplementationLink {...response.details} />
|
|
96
|
+
) : response.details.business_concept_id != null ? (
|
|
97
|
+
<ConceptLink {...response.details} />
|
|
81
98
|
) : status === "INFO" &&
|
|
82
99
|
(response.details.data_structure_id != null ||
|
|
83
100
|
response.details.external_id != null) ? (
|
|
@@ -113,6 +130,11 @@ ResultEntityCell.propTypes = {
|
|
|
113
130
|
PropTypes.number,
|
|
114
131
|
PropTypes.string,
|
|
115
132
|
]),
|
|
133
|
+
business_concept_id: PropTypes.oneOfType([
|
|
134
|
+
PropTypes.number,
|
|
135
|
+
PropTypes.string,
|
|
136
|
+
]),
|
|
137
|
+
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
116
138
|
name: PropTypes.string,
|
|
117
139
|
}),
|
|
118
140
|
}),
|
|
@@ -204,6 +226,51 @@ const ErrorDetail = ({ response, showOrigin }) => {
|
|
|
204
226
|
);
|
|
205
227
|
}
|
|
206
228
|
|
|
229
|
+
if (!showOrigin && type === "name_not_available") {
|
|
230
|
+
const name = response.details?.name;
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<span className="upload-job-simple-error">
|
|
234
|
+
<FormattedMessage id="uploadJob.parser.error.name_not_available" />
|
|
235
|
+
{name != null ? `: ${name}` : null}
|
|
236
|
+
</span>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!showOrigin && type === "unexpected_error") {
|
|
241
|
+
const { type: errorType } = classifyUploadJobErrorMessage(
|
|
242
|
+
response.details?.message,
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (errorType === "unexpected_error") {
|
|
246
|
+
return (
|
|
247
|
+
<span className="upload-job-simple-error">
|
|
248
|
+
<FormattedMessage id="uploadJob.parser.error.unexpected_error" />
|
|
249
|
+
</span>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const validationErrorPairs = (errors) => {
|
|
255
|
+
if (_.isArray(errors)) {
|
|
256
|
+
return errors.map((e) => [
|
|
257
|
+
e?.field ?? formatMessage({ id: `${messagePrefix}.message` }),
|
|
258
|
+
e?.message ?? "",
|
|
259
|
+
]);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (_.isObject(errors)) {
|
|
263
|
+
return Object.entries(errors).flatMap(([field, messages]) =>
|
|
264
|
+
(_.isArray(messages) ? messages : [messages]).map((message) => [
|
|
265
|
+
field,
|
|
266
|
+
message ?? "",
|
|
267
|
+
]),
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return null;
|
|
272
|
+
};
|
|
273
|
+
|
|
207
274
|
const detailBuilders = {
|
|
208
275
|
missing_required_headers: (details) => [
|
|
209
276
|
[
|
|
@@ -276,10 +343,9 @@ const ErrorDetail = ({ response, showOrigin }) => {
|
|
|
276
343
|
[null, externalIdDetailValue(details)],
|
|
277
344
|
].filter(([, v]) => v !== undefined && v !== ""),
|
|
278
345
|
field_validation_error: (details) =>
|
|
279
|
-
details?.errors
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
]) ?? [[null, externalIdDetailValue(details)]],
|
|
346
|
+
validationErrorPairs(details?.errors) ?? [
|
|
347
|
+
[null, externalIdDetailValue(details)],
|
|
348
|
+
],
|
|
283
349
|
unprocessable_entity: (details) => [[null, externalIdDetailValue(details)]],
|
|
284
350
|
structure_note_creation_error: (details) => {
|
|
285
351
|
const reason = details?.reason ?? details?.error;
|
|
@@ -289,12 +355,10 @@ const ErrorDetail = ({ response, showOrigin }) => {
|
|
|
289
355
|
? `structures.uploadStructures.failed.note.${reason}`
|
|
290
356
|
: null;
|
|
291
357
|
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
])
|
|
297
|
-
: [
|
|
358
|
+
const validationPairs = validationErrorPairs(details?.errors);
|
|
359
|
+
|
|
360
|
+
const errorPairs = _.isEmpty(validationPairs)
|
|
361
|
+
? [
|
|
298
362
|
[
|
|
299
363
|
formatMessage({ id: `${messagePrefix}.reason` }),
|
|
300
364
|
reasonMessageId
|
|
@@ -304,7 +368,8 @@ const ErrorDetail = ({ response, showOrigin }) => {
|
|
|
304
368
|
})
|
|
305
369
|
: String(reason ?? ""),
|
|
306
370
|
],
|
|
307
|
-
]
|
|
371
|
+
]
|
|
372
|
+
: validationPairs;
|
|
308
373
|
|
|
309
374
|
const externalIdPair = details?.external_id
|
|
310
375
|
? [[null, externalIdDetailValue(details)]]
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CONCEPTS_UPLOAD_JOBS,
|
|
2
3
|
IMPLEMENTATIONS_UPLOAD_JOBS,
|
|
3
4
|
STRUCTURE_NOTES_UPLOAD_JOBS,
|
|
4
5
|
linkTo,
|
|
5
6
|
} from "@truedat/core/routes";
|
|
6
7
|
|
|
7
8
|
export const uploadJobScopes = {
|
|
9
|
+
concepts: {
|
|
10
|
+
listPath: CONCEPTS_UPLOAD_JOBS,
|
|
11
|
+
detailPath: (id) => linkTo.CONCEPTS_UPLOAD_JOB({ id }),
|
|
12
|
+
listHeaderId: "uploadJobs.concepts.header",
|
|
13
|
+
detailHeaderId: "uploadJob.concepts.header",
|
|
14
|
+
entityColumnMessageId: "uploadJob.results.column.concept",
|
|
15
|
+
fieldMessagePrefix: "concepts.props",
|
|
16
|
+
},
|
|
8
17
|
implementations: {
|
|
9
18
|
listPath: IMPLEMENTATIONS_UPLOAD_JOBS,
|
|
10
19
|
detailPath: (id) => linkTo.IMPLEMENTATIONS_UPLOAD_JOB({ id }),
|
|
@@ -52,7 +52,8 @@ export const classifyUploadJobErrorMessage = (message) => {
|
|
|
52
52
|
const isTechnicalError =
|
|
53
53
|
/^%[A-Z][A-Za-z0-9_.]*\{/.test(normalizedMessage) ||
|
|
54
54
|
/^\{:/.test(normalizedMessage) ||
|
|
55
|
-
/^\*\* \(/.test(normalizedMessage)
|
|
55
|
+
/^\*\* \(/.test(normalizedMessage) ||
|
|
56
|
+
/^protocol \S+ not implemented/.test(normalizedMessage);
|
|
56
57
|
|
|
57
58
|
return isTechnicalError
|
|
58
59
|
? { type: "unexpected_error" }
|
|
@@ -37,4 +37,14 @@ describe("ImplementationUploadJobBreadcrumbs", () => {
|
|
|
37
37
|
expect(rendered.getByText(filename)).toBeInTheDocument();
|
|
38
38
|
expect(rendered.container).toMatchSnapshot();
|
|
39
39
|
});
|
|
40
|
+
|
|
41
|
+
it("renders breadcrumbs for concepts scope", async () => {
|
|
42
|
+
const rendered = render(<UploadJobBreadcrumbs scope="concepts" />);
|
|
43
|
+
await waitForLoad(rendered);
|
|
44
|
+
|
|
45
|
+
expect(
|
|
46
|
+
rendered.getByText(/uploadJobs.concepts.header/i),
|
|
47
|
+
).toBeInTheDocument();
|
|
48
|
+
expect(rendered.container).toMatchSnapshot();
|
|
49
|
+
});
|
|
40
50
|
});
|
|
@@ -32,6 +32,10 @@ jest.mock("@truedat/dd/components/StructureNoteLink", () => ({
|
|
|
32
32
|
);
|
|
33
33
|
},
|
|
34
34
|
}));
|
|
35
|
+
jest.mock("react-router", () => ({
|
|
36
|
+
...jest.requireActual("react-router"),
|
|
37
|
+
Link: ({ to, children }) => <a href={to}>{children}</a>,
|
|
38
|
+
}));
|
|
35
39
|
|
|
36
40
|
const changeMessages = {
|
|
37
41
|
en: {
|
|
@@ -50,6 +54,8 @@ const changeMessages = {
|
|
|
50
54
|
"uploadJob.parser.error.field_validation_error": "Field validation error",
|
|
51
55
|
"uploadJob.parser.error.data_structure_not_found":
|
|
52
56
|
"Data structure not found",
|
|
57
|
+
"uploadJob.parser.error.unexpected_error": "Unexpected error",
|
|
58
|
+
"uploadJob.parser.error.name_not_available": "Name not available",
|
|
53
59
|
"uploadJob.parser.detail.message": "Message",
|
|
54
60
|
},
|
|
55
61
|
};
|
|
@@ -99,6 +105,98 @@ describe("<ResultEntityCell />", () => {
|
|
|
99
105
|
);
|
|
100
106
|
});
|
|
101
107
|
|
|
108
|
+
it("renders INFO concept links from row details", () => {
|
|
109
|
+
const rendered = render(
|
|
110
|
+
<ResultEntityCell
|
|
111
|
+
status="INFO"
|
|
112
|
+
response={{
|
|
113
|
+
details: {
|
|
114
|
+
id: 5,
|
|
115
|
+
business_concept_id: 3,
|
|
116
|
+
name: "My Concept",
|
|
117
|
+
},
|
|
118
|
+
}}
|
|
119
|
+
/>,
|
|
120
|
+
);
|
|
121
|
+
expect(rendered.getByRole("link", { name: "My Concept" })).toHaveAttribute(
|
|
122
|
+
"href",
|
|
123
|
+
"/concepts/3/versions/5",
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("renders ERROR concept links from row details", () => {
|
|
128
|
+
const rendered = render(
|
|
129
|
+
<ResultEntityCell
|
|
130
|
+
status="ERROR"
|
|
131
|
+
response={{
|
|
132
|
+
type: "name_not_available",
|
|
133
|
+
details: {
|
|
134
|
+
id: 5,
|
|
135
|
+
business_concept_id: 3,
|
|
136
|
+
name: "My Concept",
|
|
137
|
+
},
|
|
138
|
+
}}
|
|
139
|
+
/>,
|
|
140
|
+
);
|
|
141
|
+
expect(rendered.getByRole("link", { name: "My Concept" })).toHaveAttribute(
|
|
142
|
+
"href",
|
|
143
|
+
"/concepts/3/versions/5",
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("renders concept links for unexpected_error rows with entity data", () => {
|
|
148
|
+
const rendered = render(
|
|
149
|
+
<ResultEntityCell
|
|
150
|
+
status="ERROR"
|
|
151
|
+
response={{
|
|
152
|
+
type: "unexpected_error",
|
|
153
|
+
details: {
|
|
154
|
+
id: 5,
|
|
155
|
+
business_concept_id: 3,
|
|
156
|
+
name: "My Concept",
|
|
157
|
+
},
|
|
158
|
+
}}
|
|
159
|
+
/>,
|
|
160
|
+
);
|
|
161
|
+
expect(rendered.getByRole("link", { name: "My Concept" })).toHaveAttribute(
|
|
162
|
+
"href",
|
|
163
|
+
"/concepts/3/versions/5",
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("renders concept links for unchanged rows", () => {
|
|
168
|
+
const rendered = render(
|
|
169
|
+
<ResultEntityCell
|
|
170
|
+
status="INFO"
|
|
171
|
+
response={{
|
|
172
|
+
type: "unchanged",
|
|
173
|
+
details: {
|
|
174
|
+
id: 5,
|
|
175
|
+
business_concept_id: 3,
|
|
176
|
+
name: "My Concept",
|
|
177
|
+
},
|
|
178
|
+
}}
|
|
179
|
+
/>,
|
|
180
|
+
);
|
|
181
|
+
expect(rendered.getByRole("link", { name: "My Concept" })).toHaveAttribute(
|
|
182
|
+
"href",
|
|
183
|
+
"/concepts/3/versions/5",
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("renders no entity for historical unchanged rows without entity data", () => {
|
|
188
|
+
const rendered = render(
|
|
189
|
+
<ResultEntityCell
|
|
190
|
+
status="INFO"
|
|
191
|
+
response={{
|
|
192
|
+
type: "unchanged",
|
|
193
|
+
details: { row_number: 3, sheet: "date template" },
|
|
194
|
+
}}
|
|
195
|
+
/>,
|
|
196
|
+
);
|
|
197
|
+
expect(rendered.container).toBeEmptyDOMElement();
|
|
198
|
+
});
|
|
199
|
+
|
|
102
200
|
it("renders implementation refs and preferred structure labels with external ids", () => {
|
|
103
201
|
const implementation = render(
|
|
104
202
|
<ResultEntityCell
|
|
@@ -512,6 +610,43 @@ describe("<ResponseCell />", () => {
|
|
|
512
610
|
expect(rendered.getByText(/Already exists/i)).toBeInTheDocument();
|
|
513
611
|
});
|
|
514
612
|
|
|
613
|
+
it("renders ERROR status with field_validation_error details as an object", () => {
|
|
614
|
+
const response = {
|
|
615
|
+
type: "field_validation_error",
|
|
616
|
+
details: {
|
|
617
|
+
id: 693,
|
|
618
|
+
name: "prueba2",
|
|
619
|
+
sheet: "termino",
|
|
620
|
+
errors: { adjuntos: ["is invalid"] },
|
|
621
|
+
row_number: 3,
|
|
622
|
+
business_concept_id: 337,
|
|
623
|
+
},
|
|
624
|
+
};
|
|
625
|
+
const rendered = render(
|
|
626
|
+
<ResponseCell response={response} status="ERROR" />,
|
|
627
|
+
);
|
|
628
|
+
expect(
|
|
629
|
+
rendered.getByText(/uploadJob.parser.error.field_validation_error/i),
|
|
630
|
+
).toBeInTheDocument();
|
|
631
|
+
expect(rendered.getByText(/adjuntos/i)).toBeInTheDocument();
|
|
632
|
+
expect(rendered.getByText(/is invalid/i)).toBeInTheDocument();
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it("renders ERROR status with field_validation_error object details with multiple messages", () => {
|
|
636
|
+
const response = {
|
|
637
|
+
type: "field_validation_error",
|
|
638
|
+
details: {
|
|
639
|
+
errors: { name: ["Required", "Too long"], type: "is invalid" },
|
|
640
|
+
},
|
|
641
|
+
};
|
|
642
|
+
const rendered = render(
|
|
643
|
+
<ResponseCell response={response} status="ERROR" />,
|
|
644
|
+
);
|
|
645
|
+
expect(rendered.getByText(/Required/i)).toBeInTheDocument();
|
|
646
|
+
expect(rendered.getByText(/Too long/i)).toBeInTheDocument();
|
|
647
|
+
expect(rendered.getByText(/is invalid/i)).toBeInTheDocument();
|
|
648
|
+
});
|
|
649
|
+
|
|
515
650
|
it("renders ERROR status with deprecated_implementation details", () => {
|
|
516
651
|
const response = {
|
|
517
652
|
type: "deprecated_implementation",
|
|
@@ -706,6 +841,27 @@ describe("<ResponseCell />", () => {
|
|
|
706
841
|
});
|
|
707
842
|
});
|
|
708
843
|
|
|
844
|
+
it("renders no changes disclosure when change_details is empty", () => {
|
|
845
|
+
const rendered = render(
|
|
846
|
+
<ResponseCell
|
|
847
|
+
response={{
|
|
848
|
+
type: "updated",
|
|
849
|
+
details: {
|
|
850
|
+
change_details: {},
|
|
851
|
+
},
|
|
852
|
+
}}
|
|
853
|
+
status="INFO"
|
|
854
|
+
/>,
|
|
855
|
+
{
|
|
856
|
+
messages: changeMessages,
|
|
857
|
+
},
|
|
858
|
+
);
|
|
859
|
+
|
|
860
|
+
expect(
|
|
861
|
+
rendered.queryByRole("button", { name: /modified fields?/i }),
|
|
862
|
+
).not.toBeInTheDocument();
|
|
863
|
+
});
|
|
864
|
+
|
|
709
865
|
it("renders boolean, null and zero change values without losing their values", async () => {
|
|
710
866
|
const user = userEvent.setup({ delay: null });
|
|
711
867
|
const rendered = render(
|
|
@@ -850,6 +1006,67 @@ describe("<ResultDetailCell /> errors", () => {
|
|
|
850
1006
|
expect(rendered.queryByText("unknown-note")).not.toBeInTheDocument();
|
|
851
1007
|
});
|
|
852
1008
|
|
|
1009
|
+
it("renders unexpected_error with empty details as a generic message only", () => {
|
|
1010
|
+
const rendered = render(
|
|
1011
|
+
<ResultDetailCell
|
|
1012
|
+
response={{ type: "unexpected_error", details: {} }}
|
|
1013
|
+
status="ERROR"
|
|
1014
|
+
/>,
|
|
1015
|
+
{ messages: changeMessages },
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
expect(rendered.getByText("Unexpected error")).toBeInTheDocument();
|
|
1019
|
+
expect(rendered.queryByRole("button")).not.toBeInTheDocument();
|
|
1020
|
+
expect(
|
|
1021
|
+
rendered.queryByText(/protocol String\.Chars/),
|
|
1022
|
+
).not.toBeInTheDocument();
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
it("renders unexpected_error events with technical messages as a generic message only", () => {
|
|
1026
|
+
const technicalMessage =
|
|
1027
|
+
"protocol String.Chars not implemented for type Tuple. This protocol is implemented for the following type(s): Atom, BitString\n\nGot value:\n\n {:array, :map}\n";
|
|
1028
|
+
|
|
1029
|
+
const rendered = render(
|
|
1030
|
+
<ResultDetailCell
|
|
1031
|
+
response={{
|
|
1032
|
+
type: "unexpected_error",
|
|
1033
|
+
details: { message: technicalMessage },
|
|
1034
|
+
}}
|
|
1035
|
+
status="ERROR"
|
|
1036
|
+
/>,
|
|
1037
|
+
{ messages: changeMessages },
|
|
1038
|
+
);
|
|
1039
|
+
|
|
1040
|
+
expect(rendered.getByText("Unexpected error")).toBeInTheDocument();
|
|
1041
|
+
expect(rendered.queryByRole("button")).not.toBeInTheDocument();
|
|
1042
|
+
expect(
|
|
1043
|
+
rendered.queryByText(/protocol String\.Chars/),
|
|
1044
|
+
).not.toBeInTheDocument();
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
it("renders name_not_available with the conflicting name as a simple message", () => {
|
|
1048
|
+
const rendered = render(
|
|
1049
|
+
<ResultDetailCell
|
|
1050
|
+
response={{
|
|
1051
|
+
type: "name_not_available",
|
|
1052
|
+
details: {
|
|
1053
|
+
name: "TEST CREACION",
|
|
1054
|
+
row_number: 3,
|
|
1055
|
+
sheet: "date template",
|
|
1056
|
+
},
|
|
1057
|
+
}}
|
|
1058
|
+
status="ERROR"
|
|
1059
|
+
/>,
|
|
1060
|
+
{ messages: changeMessages },
|
|
1061
|
+
);
|
|
1062
|
+
|
|
1063
|
+
expect(
|
|
1064
|
+
rendered.getByText("Name not available: TEST CREACION"),
|
|
1065
|
+
).toBeInTheDocument();
|
|
1066
|
+
expect(rendered.queryByRole("button")).not.toBeInTheDocument();
|
|
1067
|
+
expect(rendered.queryByText(/row_number/)).not.toBeInTheDocument();
|
|
1068
|
+
});
|
|
1069
|
+
|
|
853
1070
|
it("uses the singular error count for one field", () => {
|
|
854
1071
|
const rendered = render(
|
|
855
1072
|
<ResultDetailCell
|
|
@@ -25,6 +25,23 @@ describe("Upload job scopes", () => {
|
|
|
25
25
|
);
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
+
it("defines concepts upload routes and presentation metadata", () => {
|
|
29
|
+
expect(uploadJobScopes.concepts.listPath).toBe("/concepts/uploadJobs");
|
|
30
|
+
expect(uploadJobScopes.concepts.listHeaderId).toBe(
|
|
31
|
+
"uploadJobs.concepts.header",
|
|
32
|
+
);
|
|
33
|
+
expect(uploadJobScopes.concepts.detailHeaderId).toBe(
|
|
34
|
+
"uploadJob.concepts.header",
|
|
35
|
+
);
|
|
36
|
+
expect(uploadJobScopes.concepts.detailPath(12)).toBe(
|
|
37
|
+
"/concepts/uploadJobs/12",
|
|
38
|
+
);
|
|
39
|
+
expect(uploadJobScopes.concepts.entityColumnMessageId).toBe(
|
|
40
|
+
"uploadJob.results.column.concept",
|
|
41
|
+
);
|
|
42
|
+
expect(uploadJobScopes.concepts.fieldMessagePrefix).toBe("concepts.props");
|
|
43
|
+
});
|
|
44
|
+
|
|
28
45
|
it("falls back to implementation semantics for unknown scopes", () => {
|
|
29
46
|
expect(getUploadJobScope("other")).toMatchObject({
|
|
30
47
|
listPath: null,
|
|
@@ -130,6 +130,33 @@ describe("<UploadJobSummary />", () => {
|
|
|
130
130
|
});
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
it("classifies protocol not implemented messages as unexpected errors", () => {
|
|
134
|
+
expect(
|
|
135
|
+
classifyUploadJobErrorMessage(
|
|
136
|
+
"protocol String.Chars not implemented for type Tuple. This protocol is implemented for the following type(s): Atom, BitString\n\nGot value:\n\n {:array, :map}\n",
|
|
137
|
+
),
|
|
138
|
+
).toEqual({ type: "unexpected_error" });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("replaces protocol failure details with an unexpected error", () => {
|
|
142
|
+
const technicalMessage =
|
|
143
|
+
"protocol String.Chars not implemented for type Tuple. This protocol is implemented for the following type(s): Atom, BitString\n\nGot value:\n\n {:array, :map}\n";
|
|
144
|
+
const rendered = render(
|
|
145
|
+
<UploadJobSummary
|
|
146
|
+
events={[{ status: "FAILED", response: { message: technicalMessage } }]}
|
|
147
|
+
latestStatus="FAILED"
|
|
148
|
+
/>,
|
|
149
|
+
{ messages: summaryMessages },
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
expect(rendered.getByLabelText("Summary")).toHaveTextContent(
|
|
153
|
+
"Error processing the file: Unexpected error",
|
|
154
|
+
);
|
|
155
|
+
expect(rendered.getByLabelText("Summary")).not.toHaveTextContent(
|
|
156
|
+
"protocol String.Chars not implemented",
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
133
160
|
it("renders a human failure message", () => {
|
|
134
161
|
const rendered = render(
|
|
135
162
|
<UploadJobSummary
|
|
@@ -189,4 +189,19 @@ describe("<UploadJobs />", () => {
|
|
|
189
189
|
expect(rendered.getByText(/completed/i)).toBeInTheDocument();
|
|
190
190
|
expect(rendered.getByText(/success/i)).toBeInTheDocument();
|
|
191
191
|
});
|
|
192
|
+
|
|
193
|
+
it("renders concepts scope header", async () => {
|
|
194
|
+
useUploadJobs.mockReturnValue({
|
|
195
|
+
loading: false,
|
|
196
|
+
data: {
|
|
197
|
+
data: [],
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const rendered = render(<UploadJobs scope="concepts" />);
|
|
202
|
+
await waitForLoad(rendered);
|
|
203
|
+
|
|
204
|
+
const elements = rendered.getAllByText(/uploadJobs.concepts.header/i);
|
|
205
|
+
expect(elements.length).toBeGreaterThan(0);
|
|
206
|
+
});
|
|
192
207
|
});
|
|
@@ -132,7 +132,7 @@ exports[`<GlossaryMenu /> matches the latest snapshot, finds fixed and subscopes
|
|
|
132
132
|
aria-checked="false"
|
|
133
133
|
class="item"
|
|
134
134
|
data-discover="true"
|
|
135
|
-
href="/concepts/
|
|
135
|
+
href="/concepts/uploadJobs"
|
|
136
136
|
name="concepts_upload_events"
|
|
137
137
|
role="option"
|
|
138
138
|
>
|
|
@@ -125,7 +125,7 @@ exports[`<SideMenu /> matches the latest snapshot 1`] = `
|
|
|
125
125
|
aria-checked="false"
|
|
126
126
|
class="item"
|
|
127
127
|
data-discover="true"
|
|
128
|
-
href="/concepts/
|
|
128
|
+
href="/concepts/uploadJobs"
|
|
129
129
|
name="concepts_upload_events"
|
|
130
130
|
role="option"
|
|
131
131
|
>
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
|
+
exports[`ImplementationUploadJobBreadcrumbs renders breadcrumbs for concepts scope 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="ui breadcrumb"
|
|
7
|
+
>
|
|
8
|
+
<a
|
|
9
|
+
class="section"
|
|
10
|
+
data-discover="true"
|
|
11
|
+
href="/concepts/uploadJobs"
|
|
12
|
+
>
|
|
13
|
+
uploadJobs.concepts.header
|
|
14
|
+
</a>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
`;
|
|
18
|
+
|
|
3
19
|
exports[`ImplementationUploadJobBreadcrumbs renders breadcrumbs with filename 1`] = `
|
|
4
20
|
<div>
|
|
5
21
|
<div
|
package/src/routes.js
CHANGED
|
@@ -14,8 +14,8 @@ export const BUCKET_VIEW_CONFIG_EDIT = "/bucketViewConfigs/:id/edit";
|
|
|
14
14
|
export const BUCKET_VIEW_CONFIG_NEW = "/bucketViewConfigs/new";
|
|
15
15
|
export const CONCEPTS = "/concepts";
|
|
16
16
|
export const CONCEPTS_BULK_UPDATE = "/concepts/bulk_update";
|
|
17
|
-
export const
|
|
18
|
-
|
|
17
|
+
export const CONCEPTS_UPLOAD_JOBS = "/concepts/uploadJobs";
|
|
18
|
+
export const CONCEPTS_UPLOAD_JOB = "/concepts/uploadJobs/:id";
|
|
19
19
|
export const CONCEPTS_NEW = "/concepts/new";
|
|
20
20
|
export const CONCEPTS_PENDING = "/concepts/pending";
|
|
21
21
|
export const CONCEPTS_DEPRECATED = "/concepts/deprecated";
|
|
@@ -313,6 +313,8 @@ const routes = {
|
|
|
313
313
|
BUCKET_VIEW_CONFIGS,
|
|
314
314
|
CONCEPTS,
|
|
315
315
|
CONCEPTS_BULK_UPDATE,
|
|
316
|
+
CONCEPTS_UPLOAD_JOBS,
|
|
317
|
+
CONCEPTS_UPLOAD_JOB,
|
|
316
318
|
CONCEPT_LINKS_APPROVALS,
|
|
317
319
|
CONCEPT_LINKS_APPROVALS_RESULTS,
|
|
318
320
|
CONCEPTS_NEW,
|