@truedat/core 8.1.1 → 8.1.4
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/api.js +2 -0
- package/src/components/CatalogMenu.js +2 -2
- package/src/components/GlossaryMenu.js +11 -15
- package/src/components/QualityMenu.js +34 -9
- package/src/components/Submenu.js +48 -7
- package/src/components/UploadJob.js +217 -0
- package/src/components/UploadJobBreadcrumbs.js +38 -0
- package/src/components/UploadJobParser.js +370 -0
- package/src/components/UploadJobs.js +136 -0
- package/src/components/__tests__/Submenu.spec.js +13 -0
- package/src/components/__tests__/UploadJob.spec.js +112 -0
- package/src/components/__tests__/UploadJobBreadcrumbs.spec.js +37 -0
- package/src/components/__tests__/UploadJobParser.spec.js +103 -0
- package/src/components/__tests__/UploadJobs.spec.js +60 -0
- package/src/components/__tests__/__snapshots__/AdminMenu.spec.js.snap +10 -0
- package/src/components/__tests__/__snapshots__/CatalogMenu.spec.js.snap +1 -1
- package/src/components/__tests__/__snapshots__/SideMenu.spec.js.snap +1 -1
- package/src/components/__tests__/__snapshots__/Submenu.spec.js.snap +37 -0
- package/src/components/__tests__/__snapshots__/UploadJobBreadcrumbs.spec.js.snap +42 -0
- package/src/hooks/__tests__/useActiveRoutes.spec.js +83 -0
- package/src/hooks/index.js +1 -0
- package/src/hooks/useActiveRoutes.js +27 -21
- package/src/hooks/useUploadJobs.js +24 -0
- package/src/routes.js +6 -2
- package/src/selectors/__tests__/getRiSubscopes.spec.js +53 -0
- package/src/selectors/getRiSubscopes.js +8 -0
- package/src/selectors/index.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/core",
|
|
3
|
-
"version": "8.1.
|
|
3
|
+
"version": "8.1.4",
|
|
4
4
|
"description": "Truedat Web Core",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@testing-library/jest-dom": "^6.6.3",
|
|
49
49
|
"@testing-library/react": "^16.3.0",
|
|
50
50
|
"@testing-library/user-event": "^14.6.1",
|
|
51
|
-
"@truedat/test": "8.1.
|
|
51
|
+
"@truedat/test": "8.1.4",
|
|
52
52
|
"identity-obj-proxy": "^3.0.0",
|
|
53
53
|
"jest": "^29.7.0",
|
|
54
54
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -85,5 +85,5 @@
|
|
|
85
85
|
"slate-react": "^0.22.10",
|
|
86
86
|
"swr": "^2.3.3"
|
|
87
87
|
},
|
|
88
|
-
"gitHead": "
|
|
88
|
+
"gitHead": "49d0d43e081d0f631f221d8f5992e390098b3630"
|
|
89
89
|
}
|
package/src/api.js
CHANGED
|
@@ -14,3 +14,5 @@ export const API_ACL_RESOURCE_ENTRIES = "/api/acl_entries/:type/:id";
|
|
|
14
14
|
export const API_HIERARCHIES = "/api/hierarchies";
|
|
15
15
|
export const API_HIERARCHY = "/api/hierarchies/:id";
|
|
16
16
|
export const API_SYSTEMS = "/api/systems";
|
|
17
|
+
export const API_UPLOAD_JOBS = "/api/upload_jobs";
|
|
18
|
+
export const API_UPLOAD_JOB = "/api/upload_jobs/:id";
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
PENDING_STRUCTURE_NOTES,
|
|
11
11
|
REFERENCE_DATASETS,
|
|
12
12
|
STRUCTURES,
|
|
13
|
-
|
|
13
|
+
STRUCTURE_NOTES_UPLOAD_JOBS,
|
|
14
14
|
STRUCTURE_TYPES,
|
|
15
15
|
SYSTEMS,
|
|
16
16
|
STRUCTURE_TAGS,
|
|
@@ -29,7 +29,7 @@ const adminItems = [
|
|
|
29
29
|
|
|
30
30
|
const structureNoteItems = [
|
|
31
31
|
{ name: "pending_structure_notes", routes: [PENDING_STRUCTURE_NOTES] },
|
|
32
|
-
{ name: "structures_upload_events", routes: [
|
|
32
|
+
{ name: "structures_upload_events", routes: [STRUCTURE_NOTES_UPLOAD_JOBS] },
|
|
33
33
|
];
|
|
34
34
|
|
|
35
35
|
const catalogViewConfigItems = (formatMessage) =>
|
|
@@ -26,20 +26,17 @@ import {
|
|
|
26
26
|
import Submenu from "./Submenu";
|
|
27
27
|
|
|
28
28
|
function isMenuSubscope(pathname, subscope) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// return !!match?.params?.subscope && subscope === match.params.subscope;
|
|
42
|
-
return false;
|
|
29
|
+
const pathsToCheck = [
|
|
30
|
+
CONCEPTS_SUBSCOPE,
|
|
31
|
+
CONCEPTS_SIDEMENU_SUBSCOPE,
|
|
32
|
+
CONCEPTS_SIDEMENU_SUBSCOPE_DEPRECATED,
|
|
33
|
+
CONCEPTS_SIDEMENU_SUBSCOPE_PENDING,
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
return pathsToCheck.some(path => {
|
|
37
|
+
const match = matchPath({ path }, pathname);
|
|
38
|
+
return match?.params?.subscope && subscope === decodeURIComponent(match.params.subscope);
|
|
39
|
+
});
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
function mainMenuSubscopes(allSubscopes, rootSubscopes) {
|
|
@@ -247,7 +244,6 @@ export const GlossaryMenu = ({ bgSubscopes, rootSubscopes }) => {
|
|
|
247
244
|
|
|
248
245
|
const isActiveSubscope = () =>
|
|
249
246
|
conceptSubscope === name || isMenuSubscope(location.pathname, name);
|
|
250
|
-
|
|
251
247
|
return (
|
|
252
248
|
<Submenu
|
|
253
249
|
key={name}
|
|
@@ -6,16 +6,17 @@ import { useAuthorized } from "../hooks";
|
|
|
6
6
|
import {
|
|
7
7
|
EXECUTION_GROUPS,
|
|
8
8
|
IMPLEMENTATIONS,
|
|
9
|
+
IMPLEMENTATIONS_BY_SUBSCOPE,
|
|
9
10
|
IMPLEMENTATIONS_DEPRECATED,
|
|
10
11
|
IMPLEMENTATIONS_PENDING,
|
|
11
12
|
IMPLEMENTATIONS_UPLOAD_JOBS,
|
|
12
13
|
QUALITY_DASHBOARD,
|
|
13
14
|
RULES,
|
|
14
15
|
} from "../routes";
|
|
15
|
-
import { getQualityDashboardConfig } from "../selectors";
|
|
16
|
+
import { getQualityDashboardConfig, getRiSubscopes } from "../selectors";
|
|
16
17
|
import Submenu from "./Submenu";
|
|
17
18
|
|
|
18
|
-
export const
|
|
19
|
+
export const BASE_ITEMS = [
|
|
19
20
|
{ name: "rules", routes: [RULES], groups: ["quality"] },
|
|
20
21
|
{ name: "implementations", routes: [IMPLEMENTATIONS], groups: ["quality"] },
|
|
21
22
|
{
|
|
@@ -45,7 +46,7 @@ export const ITEMS = [
|
|
|
45
46
|
},
|
|
46
47
|
];
|
|
47
48
|
|
|
48
|
-
export const QualityMenu = ({ dashboardConfig }) => {
|
|
49
|
+
export const QualityMenu = ({ dashboardConfig, riSubscopes }) => {
|
|
49
50
|
const { formatMessage } = useIntl();
|
|
50
51
|
const iconQuality = formatMessage({
|
|
51
52
|
id: "sidemenu.quality.icon",
|
|
@@ -57,21 +58,45 @@ export const QualityMenu = ({ dashboardConfig }) => {
|
|
|
57
58
|
"quality_implementation_additional_actions"
|
|
58
59
|
);
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
if (!authorized) return null;
|
|
62
|
+
|
|
63
|
+
const filteredBaseItems = _.filter(
|
|
61
64
|
({ name }) => name != "quality_dashboard" || !_.isEmpty(dashboardConfig)
|
|
62
|
-
)(
|
|
65
|
+
)(BASE_ITEMS);
|
|
66
|
+
|
|
67
|
+
const extendedItems = filteredBaseItems.reduce((acc, item) => {
|
|
68
|
+
acc.push(item);
|
|
69
|
+
|
|
70
|
+
if (item.name === "implementations") {
|
|
71
|
+
riSubscopes.forEach(subscope => {
|
|
72
|
+
acc.push({
|
|
73
|
+
name: subscope,
|
|
74
|
+
routes: [IMPLEMENTATIONS_BY_SUBSCOPE.replace(':subscope', subscope)],
|
|
75
|
+
groups: ["quality"],
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return acc;
|
|
81
|
+
}, []);
|
|
63
82
|
|
|
64
|
-
return
|
|
65
|
-
<Submenu items={
|
|
66
|
-
)
|
|
83
|
+
return (
|
|
84
|
+
<Submenu items={extendedItems} icon={iconQuality} name="quality" />
|
|
85
|
+
);
|
|
67
86
|
};
|
|
68
87
|
|
|
69
88
|
QualityMenu.propTypes = {
|
|
70
89
|
dashboardConfig: PropTypes.object,
|
|
90
|
+
riSubscopes: PropTypes.array,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
QualityMenu.defaultProps = {
|
|
94
|
+
riSubscopes: [],
|
|
71
95
|
};
|
|
72
96
|
|
|
73
97
|
export const mapStateToProps = (state) => ({
|
|
74
98
|
dashboardConfig: getQualityDashboardConfig(state),
|
|
99
|
+
riSubscopes: getRiSubscopes(state),
|
|
75
100
|
});
|
|
76
101
|
|
|
77
|
-
export default connect(mapStateToProps)(QualityMenu);
|
|
102
|
+
export default connect(mapStateToProps)(QualityMenu);
|
|
@@ -3,7 +3,7 @@ import { useEffect, useState } from "react";
|
|
|
3
3
|
import PropTypes from "prop-types";
|
|
4
4
|
import { useIntl, FormattedMessage } from "react-intl";
|
|
5
5
|
import { connect } from "react-redux";
|
|
6
|
-
import { useLocation, Link, useNavigate } from "react-router";
|
|
6
|
+
import { useLocation, Link, useNavigate, matchPath } from "react-router";
|
|
7
7
|
import { Dropdown, Icon, Menu } from "semantic-ui-react";
|
|
8
8
|
import { useAuthorizedItems, useActiveRoutes } from "@truedat/core/hooks";
|
|
9
9
|
import { clearNavFilter as clearBucketFilterRoutine } from "@truedat/core/routines";
|
|
@@ -84,14 +84,49 @@ export const Submenu = ({
|
|
|
84
84
|
}, [location]);
|
|
85
85
|
|
|
86
86
|
const primaryRoute = _.flow(_.flatMap("routes"), _.head)(filteredItems);
|
|
87
|
+
const pathname = location.pathname;
|
|
88
|
+
const decodedPathname = decodeURIComponent(pathname);
|
|
89
|
+
const routes = _.flatMap("routes")(filteredItems);
|
|
90
|
+
|
|
91
|
+
const pathParts = pathname.split('/').filter(part => part !== '');
|
|
92
|
+
const hasSubscopeInPath = pathname.includes('/subscope/');
|
|
93
|
+
let subscopeName = null;
|
|
94
|
+
if (hasSubscopeInPath && pathParts.length >= 3 && pathParts[1] === 'subscope') {
|
|
95
|
+
subscopeName = decodeURIComponent(pathParts[2]).replace(/%20/g, ' ');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const isSubmenuActiveForSubscope = hasSubscopeInPath && subscopeName &&
|
|
99
|
+
_.some(item =>
|
|
100
|
+
item.name.toLowerCase() === subscopeName.toLowerCase()
|
|
101
|
+
)(filteredItems);
|
|
102
|
+
|
|
103
|
+
const isSubmenuActiveForRoutes = _.some(item =>
|
|
104
|
+
_.some(route => {
|
|
105
|
+
const decodedPathname = decodeURIComponent(pathname);
|
|
106
|
+
return decodedPathname === route ||
|
|
107
|
+
decodedPathname.startsWith(`${route}/`) ||
|
|
108
|
+
matchPath({ path: route }, decodedPathname);
|
|
109
|
+
})(item.routes)
|
|
110
|
+
)(filteredItems);
|
|
111
|
+
|
|
112
|
+
// Determine if submenu should be active, with special handling for management URLs
|
|
113
|
+
const submenuIsActive = (isActive || isSubmenuActiveForSubscope || isSubmenuActiveForRoutes) &&
|
|
114
|
+
!(name === 'glossary' && location.pathname.startsWith('/concepts/management/'));
|
|
115
|
+
|
|
87
116
|
|
|
88
|
-
if (
|
|
117
|
+
if (submenuIsActive && sidebarVisible) {
|
|
89
118
|
const menuItems = filteredItems.map((item, i) => {
|
|
90
119
|
const isItemActive =
|
|
91
|
-
|
|
120
|
+
submenuIsActive &&
|
|
92
121
|
(item.isActive
|
|
93
122
|
? item.isActive()
|
|
94
|
-
:
|
|
123
|
+
: hasSubscopeInPath && subscopeName
|
|
124
|
+
? item.name.toLowerCase() === subscopeName.toLowerCase()
|
|
125
|
+
: item.routes.some(route =>
|
|
126
|
+
route === activeRoute ||
|
|
127
|
+
route === pathname || route === decodedPathname ||
|
|
128
|
+
matchPath({ path: route }, pathname) || matchPath({ path: route }, decodedPathname) // Pattern match
|
|
129
|
+
));
|
|
95
130
|
|
|
96
131
|
return (
|
|
97
132
|
<MenuItem
|
|
@@ -120,7 +155,7 @@ export const Submenu = ({
|
|
|
120
155
|
</Menu.Item>
|
|
121
156
|
);
|
|
122
157
|
} else {
|
|
123
|
-
const className =
|
|
158
|
+
const className = submenuIsActive ? "active" : null;
|
|
124
159
|
|
|
125
160
|
const trigger = (
|
|
126
161
|
<Link to={primaryRoute} className="ui">
|
|
@@ -132,10 +167,16 @@ export const Submenu = ({
|
|
|
132
167
|
);
|
|
133
168
|
const dropdownItems = filteredItems.map((item, i) => {
|
|
134
169
|
const isItemActive =
|
|
135
|
-
|
|
170
|
+
submenuIsActive &&
|
|
136
171
|
(item.isActive
|
|
137
172
|
? item.isActive()
|
|
138
|
-
:
|
|
173
|
+
: hasSubscopeInPath && subscopeName
|
|
174
|
+
? item.name.toLowerCase() === subscopeName.toLowerCase()
|
|
175
|
+
: item.routes.some(route =>
|
|
176
|
+
route === activeRoute ||
|
|
177
|
+
route === pathname || route === decodedPathname ||
|
|
178
|
+
matchPath({ path: route }, pathname) || matchPath({ path: route }, decodedPathname)
|
|
179
|
+
));
|
|
139
180
|
|
|
140
181
|
return (
|
|
141
182
|
<DropdownItem
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { FormattedMessage, useIntl } from "react-intl";
|
|
4
|
+
import { useParams } from "react-router";
|
|
5
|
+
import {
|
|
6
|
+
Header,
|
|
7
|
+
Icon,
|
|
8
|
+
Segment,
|
|
9
|
+
Dimmer,
|
|
10
|
+
Loader,
|
|
11
|
+
Table,
|
|
12
|
+
Label,
|
|
13
|
+
Button,
|
|
14
|
+
} from "semantic-ui-react";
|
|
15
|
+
import { DateTime } from "@truedat/core/components";
|
|
16
|
+
import { useUploadJob } from "../hooks/useUploadJobs";
|
|
17
|
+
import { StatusPill, ResponseCell } from "./UploadJobParser";
|
|
18
|
+
import UploadJobBreadcrumbs from "./UploadJobBreadcrumbs";
|
|
19
|
+
|
|
20
|
+
export default function UploadJob({ scope }) {
|
|
21
|
+
const { id } = useParams();
|
|
22
|
+
const { data, loading } = useUploadJob(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: `uploadJob.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
|
+
<UploadJobBreadcrumbs filename={job?.filename} scope={scope} />
|
|
110
|
+
<Segment>
|
|
111
|
+
<Header as="h2">
|
|
112
|
+
<Icon
|
|
113
|
+
circular
|
|
114
|
+
name={formatMessage({
|
|
115
|
+
id: "uploadJob.header.icon",
|
|
116
|
+
defaultMessage: "cogs",
|
|
117
|
+
})}
|
|
118
|
+
/>
|
|
119
|
+
<Header.Content>
|
|
120
|
+
<FormattedMessage id={`uploadJob.${scope}.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="uploadJob.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="uploadJob.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="uploadJob.table.status"
|
|
169
|
+
defaultMessage="Status"
|
|
170
|
+
/>
|
|
171
|
+
</Table.HeaderCell>
|
|
172
|
+
<Table.HeaderCell>
|
|
173
|
+
<FormattedMessage
|
|
174
|
+
id="uploadJob.table.inserted_at"
|
|
175
|
+
defaultMessage="Inserted At"
|
|
176
|
+
/>
|
|
177
|
+
</Table.HeaderCell>
|
|
178
|
+
<Table.HeaderCell>
|
|
179
|
+
<FormattedMessage
|
|
180
|
+
id="uploadJob.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="uploadJob.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
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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, STRUCTURE_NOTES_UPLOAD_JOBS } from "@truedat/core/routes";
|
|
6
|
+
|
|
7
|
+
export const UploadJobBreadcrumbs = ({ filename, scope }) => {
|
|
8
|
+
const uploadJobsPath = (scope) => {
|
|
9
|
+
switch (scope) {
|
|
10
|
+
case "implementations":
|
|
11
|
+
return IMPLEMENTATIONS_UPLOAD_JOBS;
|
|
12
|
+
case "notes":
|
|
13
|
+
return STRUCTURE_NOTES_UPLOAD_JOBS;
|
|
14
|
+
default:
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
return (
|
|
19
|
+
<Breadcrumb>
|
|
20
|
+
<Breadcrumb.Section as={Link} to={uploadJobsPath(scope)}>
|
|
21
|
+
<FormattedMessage id={`uploadJobs.${scope}.header`} />
|
|
22
|
+
</Breadcrumb.Section>
|
|
23
|
+
{filename ? (
|
|
24
|
+
<>
|
|
25
|
+
<Breadcrumb.Divider icon="right angle" />
|
|
26
|
+
<Breadcrumb.Section active>{filename}</Breadcrumb.Section>
|
|
27
|
+
</>
|
|
28
|
+
) : null}
|
|
29
|
+
</Breadcrumb>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
UploadJobBreadcrumbs.propTypes = {
|
|
34
|
+
filename: PropTypes.string,
|
|
35
|
+
scope: PropTypes.string.isRequired,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default UploadJobBreadcrumbs;
|