@truedat/core 8.13.0 → 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/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__/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
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { render, waitForLoad } from "@truedat/test/render";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
|
-
import { waitFor } from "@testing-library/react";
|
|
4
3
|
|
|
5
4
|
import { useUploadJob } from "../../hooks/useUploadJobs";
|
|
6
5
|
import UploadJob from "../UploadJob";
|
|
@@ -13,25 +12,49 @@ jest.mock("react-router", () => ({
|
|
|
13
12
|
useParams: () => ({ id: "123" }),
|
|
14
13
|
}));
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const messages = {
|
|
16
|
+
en: {
|
|
17
|
+
"uploadJob.implementations.header": "Implementations upload detail",
|
|
18
|
+
"uploadJob.notes.header": "Structure notes upload detail",
|
|
19
|
+
"uploadJob.table.inserted_at": "Inserted At",
|
|
20
|
+
"uploadJob.summary.title": "Summary",
|
|
21
|
+
"uploadJob.summary.total": "Total processed",
|
|
22
|
+
"uploadJob.summary.failed": "Error processing the file: {detail}",
|
|
23
|
+
"uploadJob.parser.error.invalid_format": "Invalid file format",
|
|
24
|
+
"uploadJob.parser.result.summary.created": "Created",
|
|
25
|
+
"uploadJob.parser.result.summary.updated": "Updated",
|
|
26
|
+
"uploadJob.parser.result.summary.unchanged": "Unchanged",
|
|
27
|
+
"uploadJob.parser.result.summary.errors": "Errors",
|
|
28
|
+
"uploadJob.history.PENDING": "File received and pending processing.",
|
|
29
|
+
"uploadJob.history.STARTED": "Record reading and validation started.",
|
|
30
|
+
"uploadJob.history.COMPLETED": "File processed successfully.",
|
|
31
|
+
"uploadJob.history.FAILED": "The file could not be processed.",
|
|
32
|
+
"uploadJob.history.title": "File processing history",
|
|
33
|
+
"uploadJob.history.empty": "No processing history found.",
|
|
34
|
+
"uploadJob.results.title": "Processing results",
|
|
35
|
+
"uploadJob.results.subtitle": "Browse and filter records by upload result.",
|
|
36
|
+
"uploadJob.results.expand": "Expand processing results",
|
|
37
|
+
"uploadJob.results.collapse": "Collapse processing results",
|
|
38
|
+
"uploadJob.results.column.structure": "Structure",
|
|
39
|
+
"uploadJob.parser.event.status.PENDING": "Pending",
|
|
40
|
+
"uploadJob.parser.event.status.STARTED": "Started",
|
|
41
|
+
"uploadJob.parser.event.status.COMPLETED": "Completed",
|
|
42
|
+
"uploadJob.parser.event.status.FAILED": "Failed",
|
|
43
|
+
},
|
|
44
|
+
};
|
|
18
45
|
|
|
46
|
+
describe("<UploadJob />", () => {
|
|
19
47
|
beforeEach(() => {
|
|
20
48
|
useUploadJob.mockReset();
|
|
21
49
|
});
|
|
22
50
|
|
|
23
|
-
it("renders loading state",
|
|
24
|
-
useUploadJob.mockReturnValue({
|
|
25
|
-
|
|
26
|
-
data: null,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const rendered = render(<UploadJob />);
|
|
30
|
-
|
|
51
|
+
it("renders loading state", () => {
|
|
52
|
+
useUploadJob.mockReturnValue({ loading: true, data: null });
|
|
53
|
+
const rendered = render(<UploadJob />, { messages });
|
|
31
54
|
expect(rendered.container.querySelector(".ui.loader")).toBeInTheDocument();
|
|
32
55
|
});
|
|
33
56
|
|
|
34
|
-
it("renders
|
|
57
|
+
it("renders the lifecycle pill with an accessible visually hidden Status header", async () => {
|
|
35
58
|
useUploadJob.mockReturnValue({
|
|
36
59
|
loading: false,
|
|
37
60
|
data: {
|
|
@@ -40,71 +63,124 @@ describe("<UploadJob />", () => {
|
|
|
40
63
|
hash: "abc123",
|
|
41
64
|
latest_status: "COMPLETED",
|
|
42
65
|
latest_event_at: "2023-01-01T00:00:00Z",
|
|
43
|
-
|
|
66
|
+
latest_event_response: {
|
|
67
|
+
insert_count: 0,
|
|
68
|
+
update_count: 0,
|
|
69
|
+
unchanged_count: 0,
|
|
70
|
+
error_count: 0,
|
|
71
|
+
},
|
|
72
|
+
events: [
|
|
73
|
+
{
|
|
74
|
+
status: "COMPLETED",
|
|
75
|
+
inserted_at: "2023-01-01T00:00:00Z",
|
|
76
|
+
response: {},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
44
79
|
},
|
|
45
80
|
},
|
|
46
81
|
});
|
|
47
82
|
|
|
48
|
-
const rendered = render(<UploadJob
|
|
83
|
+
const rendered = render(<UploadJob />, { messages });
|
|
49
84
|
await waitForLoad(rendered);
|
|
50
85
|
|
|
51
86
|
expect(rendered.getAllByText(/test.xlsx/i)).toHaveLength(2);
|
|
52
87
|
expect(rendered.getByText(/abc123/i)).toBeInTheDocument();
|
|
53
|
-
expect(
|
|
88
|
+
expect(
|
|
89
|
+
rendered.getByRole("columnheader", { name: /uploadJob.table.status/i }),
|
|
90
|
+
).toHaveClass("upload-job-visually-hidden");
|
|
91
|
+
expect(rendered.getAllByText("Completed")).toHaveLength(2);
|
|
92
|
+
expect(rendered.getByLabelText("Summary")).toHaveClass("positive");
|
|
54
93
|
});
|
|
55
94
|
|
|
56
|
-
it("
|
|
95
|
+
it("keeps row outcomes out of the processing history", async () => {
|
|
57
96
|
useUploadJob.mockReturnValue({
|
|
58
97
|
loading: false,
|
|
59
98
|
data: {
|
|
60
99
|
data: {
|
|
61
100
|
filename: "test.xlsx",
|
|
62
101
|
hash: "abc123",
|
|
63
|
-
latest_status: "
|
|
102
|
+
latest_status: "FAILED",
|
|
64
103
|
latest_event_at: "2023-01-01T00:00:00Z",
|
|
104
|
+
latest_event_response: { message: "invalid_format" },
|
|
65
105
|
events: [
|
|
106
|
+
{ status: "PENDING", inserted_at: "2023-01-01T00:00:00Z" },
|
|
107
|
+
{ status: "STARTED", inserted_at: "2023-01-01T00:01:00Z" },
|
|
66
108
|
{
|
|
67
|
-
status: "
|
|
68
|
-
inserted_at: "2023-01-01T00:
|
|
69
|
-
response: { type: "
|
|
109
|
+
status: "ERROR",
|
|
110
|
+
inserted_at: "2023-01-01T00:01:30Z",
|
|
111
|
+
response: { type: "invalid_template_name" },
|
|
70
112
|
},
|
|
71
113
|
{
|
|
72
|
-
status: "
|
|
73
|
-
inserted_at: "2023-01-01T00:
|
|
74
|
-
response: {
|
|
114
|
+
status: "FAILED",
|
|
115
|
+
inserted_at: "2023-01-01T00:02:00Z",
|
|
116
|
+
response: { message: "invalid_format" },
|
|
75
117
|
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const rendered = render(<UploadJob />, { messages });
|
|
124
|
+
await waitForLoad(rendered);
|
|
125
|
+
|
|
126
|
+
const history = rendered.getByRole("heading", {
|
|
127
|
+
name: "File processing history",
|
|
128
|
+
}).parentElement;
|
|
129
|
+
expect(history).not.toHaveTextContent("invalid_template_name");
|
|
130
|
+
expect(history).toHaveTextContent("Failed");
|
|
131
|
+
expect(
|
|
132
|
+
rendered.getByRole("button", { name: /^Processing results/i }),
|
|
133
|
+
).toBeDisabled();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("renders structure-note results for the notes scope", async () => {
|
|
137
|
+
const user = userEvent.setup({ delay: null });
|
|
138
|
+
useUploadJob.mockReturnValue({
|
|
139
|
+
loading: false,
|
|
140
|
+
data: {
|
|
141
|
+
data: {
|
|
142
|
+
filename: "notes.xlsx",
|
|
143
|
+
hash: "notes-hash",
|
|
144
|
+
latest_status: "COMPLETED",
|
|
145
|
+
latest_event_at: "2023-01-01T00:00:00Z",
|
|
146
|
+
latest_event_response: {
|
|
147
|
+
insert_count: 1,
|
|
148
|
+
update_count: 0,
|
|
149
|
+
unchanged_count: 0,
|
|
150
|
+
error_count: 0,
|
|
151
|
+
},
|
|
152
|
+
events: [
|
|
76
153
|
{
|
|
77
154
|
status: "COMPLETED",
|
|
78
155
|
inserted_at: "2023-01-01T00:00:00Z",
|
|
79
|
-
response: {
|
|
156
|
+
response: { insert_count: 1 },
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
status: "INFO",
|
|
160
|
+
inserted_at: "2023-01-01T00:00:01Z",
|
|
161
|
+
response: {
|
|
162
|
+
type: "created",
|
|
163
|
+
details: { data_structure_id: 1, external_id: "NOTE-1" },
|
|
164
|
+
},
|
|
80
165
|
},
|
|
81
166
|
],
|
|
82
167
|
},
|
|
83
168
|
},
|
|
84
169
|
});
|
|
85
170
|
|
|
86
|
-
const rendered = render(<UploadJob
|
|
171
|
+
const rendered = render(<UploadJob scope="notes" />, { messages });
|
|
87
172
|
await waitForLoad(rendered);
|
|
88
173
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
});
|
|
174
|
+
expect(
|
|
175
|
+
rendered.getByRole("heading", { name: "Structure notes upload detail" }),
|
|
176
|
+
).toBeInTheDocument();
|
|
98
177
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
expect(rendered.getByText(/error_message/i)).toBeInTheDocument();
|
|
103
|
-
});
|
|
178
|
+
await user.click(
|
|
179
|
+
rendered.getByRole("button", { name: /^Processing results/i }),
|
|
180
|
+
);
|
|
104
181
|
|
|
105
|
-
// COMPLETED events are always visible
|
|
106
182
|
expect(
|
|
107
|
-
rendered.
|
|
108
|
-
).
|
|
183
|
+
rendered.getByRole("columnheader", { name: "Structure" }),
|
|
184
|
+
).toBeInTheDocument();
|
|
109
185
|
});
|
|
110
186
|
});
|
|
@@ -19,6 +19,9 @@ describe("ImplementationUploadJobBreadcrumbs", () => {
|
|
|
19
19
|
expect(
|
|
20
20
|
rendered.getByText(/uploadJobs.myscope.header/i),
|
|
21
21
|
).toBeInTheDocument();
|
|
22
|
+
expect(
|
|
23
|
+
rendered.getByText(/uploadJobs.myscope.header/i).closest("a"),
|
|
24
|
+
).toBeNull();
|
|
22
25
|
});
|
|
23
26
|
|
|
24
27
|
it("renders breadcrumbs with filename", async () => {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { render } from "@truedat/test/render";
|
|
2
|
+
import UploadJobHistory, { getLifecycleEvents } from "../UploadJobHistory";
|
|
3
|
+
|
|
4
|
+
const messages = {
|
|
5
|
+
en: {
|
|
6
|
+
"uploadJob.history.PENDING": "File received and pending processing.",
|
|
7
|
+
"uploadJob.history.STARTED": "Record reading and validation started.",
|
|
8
|
+
"uploadJob.history.COMPLETED": "File processed successfully.",
|
|
9
|
+
"uploadJob.history.FAILED": "The file could not be processed.",
|
|
10
|
+
"uploadJob.history.title": "File processing history",
|
|
11
|
+
"uploadJob.history.empty": "No processing history found.",
|
|
12
|
+
"uploadJob.table.status": "Status",
|
|
13
|
+
"uploadJob.table.inserted_at": "Processed at",
|
|
14
|
+
"uploadJob.table.response": "Detail",
|
|
15
|
+
"uploadJob.parser.event.status.PENDING": "Pending",
|
|
16
|
+
"uploadJob.parser.event.status.STARTED": "Started",
|
|
17
|
+
"uploadJob.parser.event.status.COMPLETED": "Completed",
|
|
18
|
+
"uploadJob.parser.event.status.FAILED": "Failed",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe("<UploadJobHistory />", () => {
|
|
23
|
+
it("shows lifecycle only with generic final messages", () => {
|
|
24
|
+
const events = [
|
|
25
|
+
{ status: "PENDING", inserted_at: "2026-01-01T10:00:00Z" },
|
|
26
|
+
{ status: "STARTED", inserted_at: "2026-01-01T10:01:00Z" },
|
|
27
|
+
{ status: "INFO", response: { type: "created" } },
|
|
28
|
+
{ status: "ERROR", response: { type: "invalid_template_name" } },
|
|
29
|
+
{
|
|
30
|
+
status: "COMPLETED",
|
|
31
|
+
inserted_at: "2026-01-01T10:02:00Z",
|
|
32
|
+
response: { insert_count: 10 },
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
status: "FAILED",
|
|
36
|
+
inserted_at: "2026-01-01T10:03:00Z",
|
|
37
|
+
response: { message: "invalid_format" },
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const rendered = render(<UploadJobHistory events={events} />, { messages });
|
|
42
|
+
|
|
43
|
+
expect(getLifecycleEvents(events)).toHaveLength(4);
|
|
44
|
+
expect(rendered.queryByText(/created: 10/i)).not.toBeInTheDocument();
|
|
45
|
+
expect(
|
|
46
|
+
rendered.getByText("File received and pending processing."),
|
|
47
|
+
).toBeInTheDocument();
|
|
48
|
+
expect(
|
|
49
|
+
rendered.getByText("Record reading and validation started."),
|
|
50
|
+
).toBeInTheDocument();
|
|
51
|
+
expect(
|
|
52
|
+
rendered.getByText("File processed successfully."),
|
|
53
|
+
).toBeInTheDocument();
|
|
54
|
+
expect(
|
|
55
|
+
rendered.getByText("The file could not be processed."),
|
|
56
|
+
).toBeInTheDocument();
|
|
57
|
+
expect(
|
|
58
|
+
rendered.queryByText(/invalid_template_name/i),
|
|
59
|
+
).not.toBeInTheDocument();
|
|
60
|
+
expect(rendered.queryByText(/invalid_format/i)).not.toBeInTheDocument();
|
|
61
|
+
expect(rendered.getByRole("columnheader", { name: "Status" })).toHaveClass(
|
|
62
|
+
"upload-job-visually-hidden",
|
|
63
|
+
);
|
|
64
|
+
expect(
|
|
65
|
+
rendered.getByRole("columnheader", { name: "Processed at" }),
|
|
66
|
+
).toHaveClass("upload-job-visually-hidden");
|
|
67
|
+
expect(rendered.getByRole("columnheader", { name: "Detail" })).toHaveClass(
|
|
68
|
+
"upload-job-visually-hidden",
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
});
|