@truedat/dq 7.11.0 → 7.11.2
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 +12 -0
- package/src/components/ImplementationSearchResults.js +24 -42
- package/src/components/ImplementationUploadJobBreadcrumbs.js +25 -0
- package/src/components/Implementations.js +31 -17
- package/src/components/ImplementationsRoutes.js +9 -0
- package/src/components/ImplementationsUploadButton.js +38 -50
- package/src/components/ImplementationsUploadJob.js +217 -0
- package/src/components/ImplementationsUploadJobs.js +128 -0
- package/src/components/RuleFormImplementations.js +29 -10
- package/src/components/RuleImplementationActions.js +10 -20
- package/src/components/RuleImplementationsActions.js +15 -37
- package/src/components/RuleImplementationsDownload.js +26 -31
- package/src/components/RuleImplementationsDownloadXlsx.js +47 -0
- package/src/components/RuleImplementationsLabelResults.js +30 -39
- package/src/components/RuleImplementationsOptions.js +5 -3
- package/src/components/RuleImplementationsTable.js +7 -3
- package/src/components/RuleRoutes.js +1 -4
- package/src/components/SimpleRuleImplementationsTable.js +68 -0
- package/src/components/__tests__/ImplementationSearchResults.spec.js +32 -4
- package/src/components/__tests__/ImplementationUploadJobBreadcrumbs.spec.js +28 -0
- package/src/components/__tests__/Implementations.spec.js +43 -0
- package/src/components/__tests__/ImplementationsUploadButton.spec.js +67 -40
- package/src/components/__tests__/ImplementationsUploadJob.spec.js +112 -0
- package/src/components/__tests__/ImplementationsUploadJobs.spec.js +60 -0
- package/src/components/__tests__/RuleImplementationsActions.spec.js +71 -56
- package/src/components/__tests__/RuleImplementationsOptions.spec.js +28 -3
- package/src/components/__tests__/RuleImplementationsTable.spec.js +24 -0
- package/src/components/__tests__/__snapshots__/ImplementationSearchResults.spec.js.snap +113 -46
- package/src/components/__tests__/__snapshots__/ImplementationUploadJobBreadcrumbs.spec.js.snap +42 -0
- package/src/components/__tests__/__snapshots__/Implementations.spec.js.snap +125 -24
- package/src/components/__tests__/__snapshots__/RuleImplementationsActions.spec.js.snap +4 -8
- package/src/components/__tests__/__snapshots__/RuleImplementationsOptions.spec.js.snap +5 -8
- package/src/components/__tests__/implementationsUploadJobParser.spec.js +105 -0
- package/src/components/implementationsUploadJobParser.js +276 -0
- package/src/components/index.js +0 -2
- package/src/hooks/useImplementations.js +80 -0
- package/src/reducers/index.js +2 -0
- package/src/reducers/ruleImplementationSelectedFilter.js +1 -1
- package/src/reducers/ruleImplementationsDownloadingXlsx.js +14 -0
- package/src/routines.js +3 -0
- package/src/sagas/downloadRuleImplementationsXlsx.js +52 -0
- package/src/sagas/index.js +3 -0
- package/src/components/RuleImplementationFilters.js +0 -25
- package/src/components/RuleImplementationSelectedFilters.js +0 -99
- package/src/components/RuleImplementationsFromRuleLoader.js +0 -60
- package/src/components/RuleImplementationsPagination.js +0 -18
- package/src/components/RuleImplementationsSearch.js +0 -39
- package/src/components/__tests__/RuleImplementationsFromRuleLoader.spec.js +0 -63
- package/src/components/__tests__/RuleImplementationsSearch.spec.js +0 -29
- package/src/components/__tests__/__snapshots__/RuleImplementationsFromRuleLoader.spec.js.snap +0 -3
- package/src/components/__tests__/__snapshots__/RuleImplementationsSearch.spec.js.snap +0 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/dq",
|
|
3
|
-
"version": "7.11.
|
|
3
|
+
"version": "7.11.2",
|
|
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": "7.11.
|
|
56
|
+
"@truedat/test": "7.11.2",
|
|
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": "c7c9de44c34d3105c78711fa63f6ee8cf7730eaf"
|
|
90
90
|
}
|
package/src/api.js
CHANGED
|
@@ -14,6 +14,14 @@ const API_RULE_FILTERS_SEARCH = "/api/rule_filters/search";
|
|
|
14
14
|
const API_RULE_IMPLEMENTATION = "/api/rule_implementations/:id";
|
|
15
15
|
const API_RULE_IMPLEMENTATIONS = "/api/rule_implementations";
|
|
16
16
|
const API_RULE_IMPLEMENTATIONS_CSV = "/api/rule_implementations/csv";
|
|
17
|
+
const API_RULE_IMPLEMENTATIONS_XLSX_DOWNLOAD =
|
|
18
|
+
"/api/rule_implementations/xlsx/download";
|
|
19
|
+
const API_RULE_IMPLEMENTATIONS_XLSX_UPLOAD =
|
|
20
|
+
"/api/rule_implementations/xlsx/upload";
|
|
21
|
+
const API_RULE_IMPLEMENTATIONS_XLSX_UPLOAD_JOBS =
|
|
22
|
+
"/api/rule_implementations/xlsx/upload_jobs";
|
|
23
|
+
const API_RULE_IMPLEMENTATIONS_XLSX_UPLOAD_JOB =
|
|
24
|
+
"/api/rule_implementations/xlsx/upload_jobs/:id";
|
|
17
25
|
const API_RULE_IMPLEMENTATIONS_FROM_RULE =
|
|
18
26
|
"/api/rules/:id/rule_implementations";
|
|
19
27
|
const API_RULE_IMPLEMENTATIONS_SEARCH = "/api/rule_implementations/search";
|
|
@@ -42,6 +50,10 @@ export {
|
|
|
42
50
|
API_RULE_IMPLEMENTATION,
|
|
43
51
|
API_RULE_IMPLEMENTATIONS,
|
|
44
52
|
API_RULE_IMPLEMENTATIONS_CSV,
|
|
53
|
+
API_RULE_IMPLEMENTATIONS_XLSX_DOWNLOAD,
|
|
54
|
+
API_RULE_IMPLEMENTATIONS_XLSX_UPLOAD,
|
|
55
|
+
API_RULE_IMPLEMENTATIONS_XLSX_UPLOAD_JOBS,
|
|
56
|
+
API_RULE_IMPLEMENTATIONS_XLSX_UPLOAD_JOB,
|
|
45
57
|
API_RULE_IMPLEMENTATIONS_FROM_RULE,
|
|
46
58
|
API_RULE_IMPLEMENTATIONS_SEARCH,
|
|
47
59
|
API_RULE_IMPLEMENTATION_FILTERS_SEARCH,
|
|
@@ -5,40 +5,33 @@ import { connect } from "react-redux";
|
|
|
5
5
|
import { Dimmer, Loader, Segment } from "semantic-ui-react";
|
|
6
6
|
import { useActiveRoute } from "@truedat/core/hooks";
|
|
7
7
|
import { IMPLEMENTATIONS_PENDING } from "@truedat/core/routes";
|
|
8
|
-
import
|
|
8
|
+
import SearchWidget from "@truedat/core/search/SearchWidget";
|
|
9
|
+
import { useSearchContext } from "@truedat/core/search/SearchContext";
|
|
10
|
+
import Pagination from "@truedat/core/search/Pagination";
|
|
11
|
+
import { getRuleImplementationColumns } from "../selectors";
|
|
9
12
|
import RuleImplementationsActions from "./RuleImplementationsActions";
|
|
10
13
|
import RuleImplementationsLabelResults from "./RuleImplementationsLabelResults";
|
|
11
|
-
import RuleImplementationsPagination from "./RuleImplementationsPagination";
|
|
12
|
-
import RuleImplementationsSearch from "./RuleImplementationsSearch";
|
|
13
14
|
import RuleImplementationsTable from "./RuleImplementationsTable";
|
|
14
|
-
import RuleImplementationSelectedFilters from "./RuleImplementationSelectedFilters";
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
ref.current = value;
|
|
20
|
-
});
|
|
21
|
-
return ref.current;
|
|
22
|
-
};
|
|
16
|
+
export const ImplementationSearchResults = ({ embedded, role, columns }) => {
|
|
17
|
+
const { searchData, loading, toggleHiddenFilterValue, setOnSearchChange } =
|
|
18
|
+
useSearchContext();
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
embedded,
|
|
26
|
-
implementationQuery,
|
|
27
|
-
role,
|
|
28
|
-
loading,
|
|
29
|
-
ruleImplementations,
|
|
30
|
-
columns,
|
|
31
|
-
}) => {
|
|
32
|
-
const [executeImplementationsOn, setMode] = useState(
|
|
33
|
-
_.matches({ filters: { executable: [true] } })(implementationQuery)
|
|
34
|
-
);
|
|
35
|
-
const [selectedImplementations, setSelectedImplementations] = useState([]);
|
|
36
|
-
const previousQuery = usePrevious(implementationQuery);
|
|
37
|
-
const clean = !_.isEqual(implementationQuery)(previousQuery);
|
|
20
|
+
const ruleImplementations = searchData?.data;
|
|
38
21
|
|
|
22
|
+
const [executeImplementationsOn, setExecuteImplementationsOn] =
|
|
23
|
+
useState(false);
|
|
24
|
+
const setMode = (mode) => {
|
|
25
|
+
setExecuteImplementationsOn(mode);
|
|
26
|
+
toggleHiddenFilterValue({
|
|
27
|
+
filter: "executable",
|
|
28
|
+
value: mode ? [true] : [],
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
const [selectedImplementations, setSelectedImplementations] = useState([]);
|
|
39
32
|
useEffect(() => {
|
|
40
|
-
setSelectedImplementations([]);
|
|
41
|
-
}, [
|
|
33
|
+
setOnSearchChange(() => () => setSelectedImplementations([]));
|
|
34
|
+
}, []);
|
|
42
35
|
|
|
43
36
|
const allChecked = () => {
|
|
44
37
|
const ids = _.map(_.prop("id"))(ruleImplementations);
|
|
@@ -86,18 +79,16 @@ export const ImplementationSearchResults = ({
|
|
|
86
79
|
: _.reject(({ name }) => name === "status")(columns);
|
|
87
80
|
|
|
88
81
|
return (
|
|
89
|
-
<Segment attached="bottom">
|
|
82
|
+
<Segment border="1px red" attached="bottom">
|
|
90
83
|
{embedded ? null : (
|
|
91
84
|
<RuleImplementationsActions
|
|
92
85
|
executeImplementationsOn={executeImplementationsOn}
|
|
93
|
-
implementationQuery={implementationQuery}
|
|
94
86
|
role={role}
|
|
95
87
|
setMode={setMode}
|
|
96
88
|
selectedImplementations={selectedImplementations}
|
|
97
89
|
/>
|
|
98
90
|
)}
|
|
99
|
-
<
|
|
100
|
-
<RuleImplementationSelectedFilters />
|
|
91
|
+
<SearchWidget />
|
|
101
92
|
<Dimmer.Dimmable dimmed={loading}>
|
|
102
93
|
{loading ? (
|
|
103
94
|
<Dimmer active inverted>
|
|
@@ -117,30 +108,21 @@ export const ImplementationSearchResults = ({
|
|
|
117
108
|
selectedImplementations={selectedImplementations}
|
|
118
109
|
columns={filteredColumns}
|
|
119
110
|
/>
|
|
120
|
-
<
|
|
111
|
+
<Pagination />
|
|
121
112
|
</Dimmer.Dimmable>
|
|
122
113
|
</Segment>
|
|
123
114
|
);
|
|
124
115
|
};
|
|
125
116
|
|
|
126
117
|
ImplementationSearchResults.propTypes = {
|
|
127
|
-
implementationQuery: PropTypes.object,
|
|
128
|
-
role: PropTypes.string,
|
|
129
|
-
loading: PropTypes.bool,
|
|
130
|
-
ruleImplementations: PropTypes.array,
|
|
131
118
|
embedded: PropTypes.bool,
|
|
119
|
+
role: PropTypes.string,
|
|
132
120
|
columns: PropTypes.array,
|
|
133
121
|
};
|
|
134
122
|
|
|
135
123
|
const mapStateToProps = (state, props) => ({
|
|
136
124
|
columns: getRuleImplementationColumns(state),
|
|
137
|
-
implementationQuery: getExecutionQuery({
|
|
138
|
-
...state,
|
|
139
|
-
ruleImplementationDefaultFilters: _.propOr({}, "defaultFilters")(props),
|
|
140
|
-
}),
|
|
141
125
|
role: state.authentication?.role,
|
|
142
|
-
loading: state.ruleImplementationsLoading,
|
|
143
|
-
ruleImplementations: state.ruleImplementations,
|
|
144
126
|
});
|
|
145
127
|
|
|
146
128
|
export default connect(mapStateToProps)(ImplementationSearchResults);
|
|
@@ -0,0 +1,25 @@
|
|
|
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,28 +1,42 @@
|
|
|
1
1
|
import { lazy } from "react";
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
3
|
import { Segment } from "semantic-ui-react";
|
|
4
|
+
import { SearchContextProvider } from "@truedat/core/search/SearchContext";
|
|
5
|
+
import {
|
|
6
|
+
useRuleImplementationFilters,
|
|
7
|
+
useRuleImplementationSearch,
|
|
8
|
+
} from "../hooks/useImplementations";
|
|
9
|
+
|
|
4
10
|
import ImplementationsHeader from "./ImplementationsHeader";
|
|
5
11
|
import ImplementationFiltersLoader from "./ImplementationFiltersLoader";
|
|
6
12
|
import ImplementationSearchResults from "./ImplementationSearchResults";
|
|
7
|
-
import RuleImplementationsLoader from "./RuleImplementationsLoader";
|
|
8
13
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
export const Implementations = ({ defaultFilters }) => {
|
|
15
|
+
const searchProps = {
|
|
16
|
+
initialSortColumn: "implementation_key.raw",
|
|
17
|
+
initialSortDirection: "ascending",
|
|
18
|
+
useSearch: useRuleImplementationSearch,
|
|
19
|
+
useFilters: useRuleImplementationFilters,
|
|
20
|
+
pageSize: 20,
|
|
21
|
+
userFiltersType: "user_search_filters",
|
|
22
|
+
userFilterScope: "rule_implementation",
|
|
23
|
+
defaultFilters,
|
|
24
|
+
};
|
|
12
25
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
);
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<SearchContextProvider {...searchProps}>
|
|
29
|
+
<ImplementationFiltersLoader defaultFilters={defaultFilters} />
|
|
30
|
+
<Segment>
|
|
31
|
+
<ImplementationsHeader />
|
|
32
|
+
<Segment attached="bottom">
|
|
33
|
+
<ImplementationSearchResults defaultFilters={defaultFilters} />
|
|
34
|
+
</Segment>
|
|
35
|
+
</Segment>
|
|
36
|
+
</SearchContextProvider>
|
|
37
|
+
</>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
26
40
|
|
|
27
41
|
Implementations.propTypes = {
|
|
28
42
|
defaultFilters: PropTypes.object,
|
|
@@ -51,6 +51,8 @@ import RuleResultDetails from "./RuleResultDetails";
|
|
|
51
51
|
import RuleResultRemediationLoader from "./RuleResultRemediationLoader";
|
|
52
52
|
import RuleResultsRoutes from "./RuleResultsRoutes";
|
|
53
53
|
import RuleSubscriptionLoader from "./RuleSubscriptionLoader";
|
|
54
|
+
import ImplementationsUploadJobs from "./ImplementationsUploadJobs";
|
|
55
|
+
import ImplementationsUploadJob from "./ImplementationsUploadJob";
|
|
54
56
|
|
|
55
57
|
const TemplatesLoader = React.lazy(
|
|
56
58
|
() => import("@truedat/core/components/TemplatesLoader")
|
|
@@ -104,6 +106,13 @@ export const ImplementationsRoutes = ({
|
|
|
104
106
|
index
|
|
105
107
|
element={<Implementations defaultFilters={{ status: ["published"] }} />}
|
|
106
108
|
/>
|
|
109
|
+
<Route
|
|
110
|
+
// IMPLEMENTATIONS_UPLOAD_JOBS = "/implementations/uploadJobs";
|
|
111
|
+
path="/uploadJobs"
|
|
112
|
+
>
|
|
113
|
+
<Route index element={<ImplementationsUploadJobs />} />
|
|
114
|
+
<Route path=":id" element={<ImplementationsUploadJob />} />
|
|
115
|
+
</Route>
|
|
107
116
|
|
|
108
117
|
<Route
|
|
109
118
|
// IMPLEMENTATIONS_PENDING = "/implementations/pending";
|
|
@@ -1,42 +1,52 @@
|
|
|
1
|
-
import
|
|
2
|
-
import PropTypes from "prop-types";
|
|
3
|
-
import { connect } from "react-redux";
|
|
1
|
+
import { useWebContext } from "@truedat/core/webContext";
|
|
4
2
|
import { Dropdown } from "semantic-ui-react";
|
|
5
3
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
6
4
|
import { UploadModal } from "@truedat/core/components";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { linkTo } from "@truedat/core/routes";
|
|
6
|
+
import { useSearchContext } from "@truedat/core/search/SearchContext";
|
|
7
|
+
import { useImplementationsUpload } from "../hooks/useImplementations";
|
|
9
8
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const buildAlertResult =
|
|
10
|
+
(setAlertMessage) =>
|
|
11
|
+
({ data }) => {
|
|
12
|
+
setAlertMessage({
|
|
13
|
+
error: false,
|
|
14
|
+
header: "implementations.bulkUpload.alert.success.header",
|
|
15
|
+
icon: "check",
|
|
16
|
+
color: "blue",
|
|
17
|
+
text: "",
|
|
18
|
+
anchor: {
|
|
19
|
+
reference: linkTo.IMPLEMENTATIONS_UPLOAD_JOB({ id: data.job_id }),
|
|
20
|
+
label: "implementations.bulkUpload.job.header",
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
};
|
|
14
24
|
|
|
15
|
-
export const ImplementationsUploadButton = ({
|
|
16
|
-
canAutoPublish,
|
|
17
|
-
uploadImplementations,
|
|
18
|
-
loading,
|
|
19
|
-
}) => {
|
|
25
|
+
export const ImplementationsUploadButton = () => {
|
|
20
26
|
const { formatMessage, locale } = useIntl();
|
|
27
|
+
|
|
28
|
+
const { trigger: triggerUpload, isMutating: loading } =
|
|
29
|
+
useImplementationsUpload();
|
|
30
|
+
const { setAlertMessage } = useWebContext();
|
|
31
|
+
const { searchData } = useSearchContext();
|
|
32
|
+
|
|
33
|
+
const alertResult = buildAlertResult(setAlertMessage);
|
|
34
|
+
const canAutoPublish = !!searchData?._actions?.autoPublish;
|
|
35
|
+
|
|
21
36
|
const extraAction = {
|
|
22
37
|
key: "yesWithAutoPublish",
|
|
23
38
|
primary: true,
|
|
24
39
|
content: formatMessage({ id: "uploadModal.accept.publish" }),
|
|
25
|
-
onClick: (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
action: "upload",
|
|
30
|
-
formData,
|
|
31
|
-
lang: locale,
|
|
32
|
-
...uploadAction,
|
|
33
|
-
});
|
|
40
|
+
onClick: (data) => {
|
|
41
|
+
data.append("auto_publish", canAutoPublish);
|
|
42
|
+
data.append("lang", locale);
|
|
43
|
+
triggerUpload(data).then(alertResult);
|
|
34
44
|
},
|
|
35
45
|
};
|
|
36
46
|
return (
|
|
37
47
|
<UploadModal
|
|
38
48
|
icon="upload"
|
|
39
|
-
extraAction={canAutoPublish
|
|
49
|
+
extraAction={canAutoPublish && extraAction}
|
|
40
50
|
trigger={
|
|
41
51
|
<Dropdown.Item
|
|
42
52
|
icon="upload"
|
|
@@ -53,34 +63,12 @@ export const ImplementationsUploadButton = ({
|
|
|
53
63
|
<FormattedMessage id="ruleImplementations.actions.upload.confirmation.content" />
|
|
54
64
|
}
|
|
55
65
|
param="implementations"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
action: "upload",
|
|
60
|
-
formData,
|
|
61
|
-
lang: locale,
|
|
62
|
-
...uploadAction,
|
|
63
|
-
});
|
|
66
|
+
handleSubmit={(data) => {
|
|
67
|
+
data.append("lang", locale);
|
|
68
|
+
triggerUpload(data).then(alertResult);
|
|
64
69
|
}}
|
|
65
70
|
/>
|
|
66
71
|
);
|
|
67
72
|
};
|
|
68
73
|
|
|
69
|
-
|
|
70
|
-
canAutoPublish: PropTypes.bool,
|
|
71
|
-
uploadImplementations: PropTypes.func,
|
|
72
|
-
loading: PropTypes.bool,
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const mapStateToProps = ({
|
|
76
|
-
uploadingImplementationsFile,
|
|
77
|
-
implementationsActions,
|
|
78
|
-
}) => ({
|
|
79
|
-
canAutoPublish: _.has("autoPublish")(implementationsActions),
|
|
80
|
-
loading: uploadingImplementationsFile,
|
|
81
|
-
implementationsActions,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
export default connect(mapStateToProps, { uploadImplementations })(
|
|
85
|
-
ImplementationsUploadButton
|
|
86
|
-
);
|
|
74
|
+
export default ImplementationsUploadButton;
|
|
@@ -0,0 +1,217 @@
|
|
|
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
|
+
}
|