@truedat/bg 7.10.3 → 7.11.0

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.
Files changed (33) hide show
  1. package/package.json +3 -3
  2. package/src/concepts/api.js +2 -0
  3. package/src/concepts/components/ConceptLinksUploadButton.js +0 -4
  4. package/src/concepts/components/ConceptRoutes.js +28 -46
  5. package/src/concepts/components/ConceptSuggestions.js +71 -0
  6. package/src/concepts/components/__tests__/ConceptRoutes.spec.js +201 -0
  7. package/src/concepts/components/__tests__/ConceptSuggestions.spec.js +74 -0
  8. package/src/concepts/components/__tests__/__snapshots__/ConceptRoutes.spec.js.snap +165 -0
  9. package/src/concepts/hooks/useConcepts.js +7 -0
  10. package/src/concepts/relations/api.js +9 -1
  11. package/src/concepts/relations/components/ConceptLinksApprovalResults.js +91 -0
  12. package/src/concepts/relations/components/ConceptLinksApprovals.js +225 -0
  13. package/src/concepts/relations/components/ConceptLinksApprovalsLabelResults.js +42 -0
  14. package/src/concepts/relations/components/ConceptLinksApprovalsRow.js +43 -0
  15. package/src/concepts/relations/components/ConceptLinksApprovalsTable.js +104 -0
  16. package/src/concepts/relations/components/ConceptSelector.js +117 -75
  17. package/src/concepts/relations/components/__tests__/ConceptLinksApprovalResults.spec.js +123 -0
  18. package/src/concepts/relations/components/__tests__/ConceptLinksApprovals.spec.js +77 -0
  19. package/src/concepts/relations/components/__tests__/ConceptLinksApprovalsLabelResults.spec.js +47 -0
  20. package/src/concepts/relations/components/__tests__/ConceptLinksApprovalsRow.spec.js +68 -0
  21. package/src/concepts/relations/components/__tests__/ConceptLinksApprovalsTable.spec.js +107 -0
  22. package/src/concepts/relations/components/__tests__/ConceptSelector.spec.js +90 -1
  23. package/src/concepts/relations/components/__tests__/__snapshots__/ConceptLinksApprovalResults.spec.js.snap +217 -0
  24. package/src/concepts/relations/components/__tests__/__snapshots__/ConceptLinksApprovals.spec.js.snap +559 -0
  25. package/src/concepts/relations/components/__tests__/__snapshots__/ConceptLinksApprovalsLabelResults.spec.js.snap +34 -0
  26. package/src/concepts/relations/components/__tests__/__snapshots__/ConceptLinksApprovalsRow.spec.js.snap +47 -0
  27. package/src/concepts/relations/components/__tests__/__snapshots__/ConceptLinksApprovalsTable.spec.js.snap +132 -0
  28. package/src/concepts/relations/components/__tests__/__snapshots__/ConceptSelector.spec.js.snap +99 -0
  29. package/src/concepts/relations/hooks/useLinks.js +25 -0
  30. package/src/concepts/relations/sagas/linkConcept.js +1 -0
  31. package/src/concepts/relations/selectors/getLinksSearchColumns.js +106 -0
  32. package/src/concepts/relations/selectors/index.js +2 -0
  33. package/src/concepts/relations/styles/ConceptLinksApprovals.less +15 -0
@@ -4,6 +4,7 @@ import FileSaver from "file-saver";
4
4
  import { apiJson, apiJsonPost, JSON_OPTS } from "@truedat/core/services/api";
5
5
 
6
6
  import {
7
+ API_CONCEPT_SUGGESTIONS,
7
8
  API_CONCEPT_FILTERS,
8
9
  API_CONCEPT_LINKS_DOWNLOAD,
9
10
  API_BUSINESS_CONCEPT_VERSIONS_SEARCH,
@@ -62,6 +63,12 @@ export const useConceptsUpload = () => {
62
63
  );
63
64
  };
64
65
 
66
+ export const useConceptSuggestions = () => {
67
+ return useSWRMutations(API_CONCEPT_SUGGESTIONS, (url, { arg }) => {
68
+ return apiJsonPost(url, arg);
69
+ });
70
+ };
71
+
65
72
  export const useConceptLinksUpload = () => {
66
73
  return useSWRMutations(API_CONCEPT_LINKS_UPLOAD, (url, { arg }) =>
67
74
  apiJsonPost(url, arg)
@@ -1,3 +1,11 @@
1
1
  const API_RELATIONS = "/api/relations";
2
+ const API_RELATIONS_SEARCH = "/api/relations/index_search";
3
+ const API_RELATIONS_FILTERS = "/api/relations/filters";
4
+ const API_RELATIONS_BULK_UPDATE_STATUS = "/api/relations/status";
2
5
 
3
- export { API_RELATIONS };
6
+ export {
7
+ API_RELATIONS,
8
+ API_RELATIONS_SEARCH,
9
+ API_RELATIONS_FILTERS,
10
+ API_RELATIONS_BULK_UPDATE_STATUS,
11
+ };
@@ -0,0 +1,91 @@
1
+ import _ from "lodash/fp";
2
+ import { useIntl, FormattedMessage } from "react-intl";
3
+ import { Breadcrumb, Message } from "semantic-ui-react";
4
+ import { useLocation, Link } from "react-router";
5
+ import { linkTo } from "@truedat/core/routes";
6
+ import { CONCEPT_LINKS_APPROVALS } from "@truedat/core/routes";
7
+
8
+ export default function LinksApprovalResults() {
9
+ const { state } = useLocation();
10
+
11
+ if (!state) {
12
+ window.location.replace(CONCEPT_LINKS_APPROVALS);
13
+ return null;
14
+ }
15
+
16
+ const { formatMessage } = useIntl();
17
+ const relations = state?.relations || [];
18
+ const isRejection = state.status === "rejected";
19
+
20
+ const relationDecorator = (relation) => (
21
+ <Message.Item key={relation.id}>
22
+ <FormattedMessage id="conceptRelations.approvals.results.message.source_name" />
23
+ <Link
24
+ to={linkTo.CONCEPT_VERSION({
25
+ business_concept_id: relation.source_id,
26
+ id: "current",
27
+ })}
28
+ target="_blank"
29
+ >
30
+ {relation.source_name}
31
+ </Link>
32
+ {relation.tag_type ? (
33
+ <FormattedMessage
34
+ id="conceptRelations.approvals.results.message.tag_type"
35
+ values={{ tag_type: relation.tag_type }}
36
+ />
37
+ ) : null}
38
+ <FormattedMessage id="conceptRelations.approvals.results.message.target_name" />
39
+ <Link
40
+ to={linkTo.STRUCTURE({
41
+ id: relation.target_id,
42
+ })}
43
+ target="_blank"
44
+ >
45
+ {relation.target_name}
46
+ </Link>
47
+ </Message.Item>
48
+ );
49
+
50
+ return (
51
+ <>
52
+ <Breadcrumb>
53
+ <Breadcrumb.Section
54
+ as={Link}
55
+ to={CONCEPT_LINKS_APPROVALS}
56
+ active={false}
57
+ >
58
+ {formatMessage({ id: "conceptRelations.approvals.header" })}
59
+ </Breadcrumb.Section>
60
+ <Breadcrumb.Divider icon="right angle" />
61
+ <Breadcrumb.Section active>
62
+ {formatMessage({ id: "conceptRelations.approvals.results.header" })}
63
+ </Breadcrumb.Section>
64
+ </Breadcrumb>
65
+
66
+ {_.isEmpty(relations) ? null : (
67
+ <Message positive={!isRejection} warning={isRejection}>
68
+ <Message.Header>
69
+ {formatMessage({
70
+ id: `conceptRelations.approvals.results.${state.status}.header`,
71
+ })}
72
+ </Message.Header>
73
+ {_.map((relation) => relationDecorator(relation))(state.relations)}
74
+ </Message>
75
+ )}
76
+
77
+ {_.map((errorGroup) => (
78
+ <Message error key={errorGroup.reason}>
79
+ <Message.Header>
80
+ {formatMessage({
81
+ id: `conceptRelations.approvals.results.errors.${errorGroup.reason}.header`,
82
+ })}
83
+ </Message.Header>
84
+ {_.map((relation) => relationDecorator(relation))(
85
+ errorGroup.relations
86
+ )}
87
+ </Message>
88
+ ))(state.errors)}
89
+ </>
90
+ );
91
+ }
@@ -0,0 +1,225 @@
1
+ import _ from "lodash/fp";
2
+ import { useEffect, useState } from "react";
3
+ import { useNavigate } from "react-router";
4
+ import { CONCEPT_LINKS_APPROVALS_RESULTS } from "@truedat/core/routes";
5
+ import {
6
+ Header,
7
+ Button,
8
+ Checkbox,
9
+ Icon,
10
+ Segment,
11
+ Dimmer,
12
+ Loader,
13
+ Grid,
14
+ GridColumn,
15
+ GridRow,
16
+ } from "semantic-ui-react";
17
+ import { FormattedMessage, useIntl } from "react-intl";
18
+ import SearchWidget from "@truedat/core/search/SearchWidget";
19
+ import { useSearchContext } from "@truedat/core/search/SearchContext";
20
+ import { useLinksBulkUpdateStatus } from "../hooks/useLinks";
21
+ import Pagination from "@truedat/core/search/Pagination";
22
+ import ConceptLinksApprovalsLabelResults from "./ConceptLinksApprovalsLabelResults";
23
+ import ConceptLinksApprovalsTable from "./ConceptLinksApprovalsTable";
24
+
25
+ export const ConceptLinksApprovals = () => {
26
+ const { searchData, loading, setOnSearchChange, searchMust, setSearchMust } =
27
+ useSearchContext();
28
+
29
+ const navigate = useNavigate();
30
+ const { formatMessage } = useIntl();
31
+ const { trigger: triggerUpdateStatus, isMutating: mutatingStatus } =
32
+ useLinksBulkUpdateStatus();
33
+
34
+ const links = _.propOr([], "data")(searchData);
35
+
36
+ const [selectedLinks, setSelectedLinks] = useState([]);
37
+
38
+ const cleanSelection = () => setSelectedLinks([]);
39
+ const approveView = _.isEqual(searchMust.status, ["pending"]);
40
+
41
+ useEffect(() => {
42
+ setOnSearchChange(() => cleanSelection);
43
+ }, []);
44
+
45
+ const allChecked = () => {
46
+ const ids = _.map(_.prop("id"))(links);
47
+ return (
48
+ _.negate(_.isEmpty)(selectedLinks) &&
49
+ _.every((id) => _.includes(id)(selectedLinks))(ids)
50
+ );
51
+ };
52
+
53
+ const addAll = (_e, target) => {
54
+ const checkedAll = _.prop("checked")(target);
55
+ const ids = _.map(_.prop("id"))(links);
56
+ checkedAll
57
+ ? setSelectedLinks(_.flow(_.concat(ids), _.uniq)(selectedLinks))
58
+ : setSelectedLinks(
59
+ _.flow(_.difference(selectedLinks), _.uniq)(selectedLinks)
60
+ );
61
+ };
62
+
63
+ const checkRow = (link) => {
64
+ const id = _.prop("id")(link);
65
+ const exists = _.some((selectedId) => _.eq(id, selectedId))(selectedLinks);
66
+ exists
67
+ ? setSelectedLinks(
68
+ _.flow(
69
+ _.remove((selectedId) => _.eq(id, selectedId)),
70
+ _.uniq()
71
+ )(selectedLinks)
72
+ )
73
+ : setSelectedLinks(_.flow(_.concat(id), _.uniq)(selectedLinks));
74
+ };
75
+
76
+ const isRowChecked = (link) => {
77
+ const id = _.prop("id")(link);
78
+ return _.some((selectedId) => _.eq(id, selectedId))(selectedLinks);
79
+ };
80
+
81
+ const disableUpdate = mutatingStatus || _.isEmpty(selectedLinks);
82
+
83
+ const handleStatusFilterChange = (_e, { value }) =>
84
+ _.includes(value, searchMust.status)
85
+ ? setSearchMust({
86
+ ...searchMust,
87
+ status: _.without([value], searchMust.status),
88
+ })
89
+ : setSearchMust({
90
+ ...searchMust,
91
+ status: _.concat(value, searchMust.status),
92
+ });
93
+
94
+ const handleUpdateStatus = (newState) => {
95
+ const params = { relation_ids: selectedLinks, status: newState };
96
+
97
+ triggerUpdateStatus(params).then((response) => {
98
+ navigate(CONCEPT_LINKS_APPROVALS_RESULTS, {
99
+ state: {
100
+ ..._.prop("data.data")(response),
101
+ status: newState,
102
+ },
103
+ });
104
+ });
105
+ };
106
+
107
+ return (
108
+ <Segment>
109
+ <Header as="h2">
110
+ <Icon
111
+ circular
112
+ name={formatMessage({
113
+ id: "conceptRelations.approvals.header.icon",
114
+ defaultMessage: "linkify",
115
+ })}
116
+ />
117
+ <Header.Content>
118
+ <FormattedMessage id={"conceptRelations.approvals.header"} />
119
+ <Header.Subheader>
120
+ <FormattedMessage id={"conceptRelations.approvals.subheader"} />
121
+ </Header.Subheader>
122
+ </Header.Content>
123
+ </Header>
124
+ <Segment attached="bottom">
125
+ <Grid className="grant-requests-bulk-actions">
126
+ <GridRow>
127
+ <GridColumn width={8}>
128
+ <SearchWidget />
129
+ </GridColumn>
130
+ <GridColumn width={8} textAlign="right">
131
+ {approveView ? (
132
+ <>
133
+ <Button
134
+ primary
135
+ disabled={disableUpdate}
136
+ onClick={() => handleUpdateStatus("approved")}
137
+ >
138
+ <FormattedMessage id="conceptRelations.approvals.actions.approve" />
139
+ </Button>
140
+ <Button
141
+ secondary
142
+ disabled={disableUpdate}
143
+ onClick={() => handleUpdateStatus("rejected")}
144
+ >
145
+ <FormattedMessage id="conceptRelations.approvals.actions.reject" />
146
+ </Button>
147
+ </>
148
+ ) : null}
149
+ </GridColumn>
150
+ </GridRow>
151
+ <GridRow>
152
+ <GridColumn
153
+ width={8}
154
+ className="concepts-links-approvals-status-selector"
155
+ >
156
+ <Checkbox
157
+ label={
158
+ <label>
159
+ <FormattedMessage
160
+ id={`conceptRelations.approvals.status.pending`}
161
+ />
162
+ </label>
163
+ }
164
+ value="pending"
165
+ checked={_.includes("pending", searchMust.status)}
166
+ onChange={handleStatusFilterChange}
167
+ />
168
+ <Checkbox
169
+ label={
170
+ <label>
171
+ <FormattedMessage
172
+ id={`conceptRelations.approvals.status.approved`}
173
+ />
174
+ </label>
175
+ }
176
+ value="approved"
177
+ checked={_.includes("approved", searchMust.status)}
178
+ onChange={handleStatusFilterChange}
179
+ />
180
+ <Checkbox
181
+ label={
182
+ <label>
183
+ <FormattedMessage
184
+ id={`conceptRelations.approvals.status.rejected`}
185
+ />
186
+ </label>
187
+ }
188
+ value="rejected"
189
+ checked={_.includes("rejected", searchMust.status)}
190
+ onChange={handleStatusFilterChange}
191
+ />
192
+ </GridColumn>
193
+ <GridColumn width={8} textAlign="right">
194
+ <ConceptLinksApprovalsLabelResults
195
+ approveView={approveView}
196
+ selectedCount={_.size(selectedLinks)}
197
+ />
198
+ </GridColumn>
199
+ </GridRow>
200
+ </Grid>
201
+ <Segment attached="bottom">
202
+ <Dimmer.Dimmable dimmed={loading}>
203
+ {loading ? (
204
+ <Dimmer active inverted>
205
+ <Loader />
206
+ </Dimmer>
207
+ ) : null}
208
+
209
+ <ConceptLinksApprovalsTable
210
+ approveView={approveView}
211
+ addAll={addAll}
212
+ checkedAll={allChecked()}
213
+ checkRow={checkRow}
214
+ isRowChecked={isRowChecked}
215
+ />
216
+
217
+ <Pagination />
218
+ </Dimmer.Dimmable>
219
+ </Segment>
220
+ </Segment>
221
+ </Segment>
222
+ );
223
+ };
224
+
225
+ export default ConceptLinksApprovals;
@@ -0,0 +1,42 @@
1
+ import PropTypes from "prop-types";
2
+ import { FormattedMessage } from "react-intl";
3
+ import { Label } from "semantic-ui-react";
4
+ import { useSearchContext } from "@truedat/core/search/SearchContext";
5
+
6
+ export const ConceptLinksApprovalsLabelResults = ({
7
+ selectedCount,
8
+ approveView,
9
+ }) => {
10
+ const { count, loading } = useSearchContext();
11
+ return (
12
+ <div className="concepts-links-bulk-label-results">
13
+ <Label>
14
+ {loading ? (
15
+ <FormattedMessage id="conceptRelations.searching" />
16
+ ) : (
17
+ <FormattedMessage
18
+ id="conceptRelations.retrieved.results"
19
+ values={{ count }}
20
+ />
21
+ )}
22
+ </Label>
23
+ {!loading && approveView ? (
24
+ <Label>
25
+ <FormattedMessage
26
+ id="conceptRelations.bulk.selected"
27
+ values={{
28
+ count: selectedCount,
29
+ }}
30
+ />
31
+ </Label>
32
+ ) : null}
33
+ </div>
34
+ );
35
+ };
36
+
37
+ ConceptLinksApprovalsLabelResults.propTypes = {
38
+ selectedCount: PropTypes.number,
39
+ approveView: PropTypes.bool,
40
+ };
41
+
42
+ export default ConceptLinksApprovalsLabelResults;
@@ -0,0 +1,43 @@
1
+ import _ from "lodash/fp";
2
+ import PropTypes from "prop-types";
3
+ import { Checkbox, Table } from "semantic-ui-react";
4
+ import { columnDecoratorComponent } from "@truedat/core/services";
5
+
6
+ export const ConceptLinksApprovalsRow = ({
7
+ checked,
8
+ columns,
9
+ onCheckboxChange,
10
+ link,
11
+ approveView,
12
+ }) => {
13
+ return _.isEmpty(columns) || _.isEmpty(link) ? null : (
14
+ <Table.Row>
15
+ {approveView ? (
16
+ <Table.Cell collapsing width="1" textAlign="center">
17
+ <Checkbox
18
+ id={link.id}
19
+ checked={checked}
20
+ onChange={onCheckboxChange}
21
+ />
22
+ </Table.Cell>
23
+ ) : null}
24
+ {columns.map((column, key) => (
25
+ <Table.Cell
26
+ key={key}
27
+ textAlign={column.textAlign}
28
+ content={columnDecoratorComponent(column)(link)}
29
+ />
30
+ ))}
31
+ </Table.Row>
32
+ );
33
+ };
34
+
35
+ ConceptLinksApprovalsRow.propTypes = {
36
+ approveView: PropTypes.bool,
37
+ checked: PropTypes.bool,
38
+ link: PropTypes.object,
39
+ columns: PropTypes.array,
40
+ onCheckboxChange: PropTypes.func,
41
+ };
42
+
43
+ export default ConceptLinksApprovalsRow;
@@ -0,0 +1,104 @@
1
+ import _ from "lodash/fp";
2
+ import PropTypes from "prop-types";
3
+ import { connect } from "react-redux";
4
+ import { useIntl } from "react-intl";
5
+ import { useSearchContext } from "@truedat/core/search/SearchContext";
6
+ import { Checkbox, Table, Header, Icon } from "semantic-ui-react";
7
+ import { getLinksSearchColumns } from "../selectors";
8
+ import { ConceptLinksApprovalsRow } from "./ConceptLinksApprovalsRow";
9
+
10
+ export const ConceptLinksApprovalsTable = ({
11
+ addAll,
12
+ checkedAll,
13
+ checkRow,
14
+ isRowChecked,
15
+ columns,
16
+ approveView,
17
+ }) => {
18
+ const { formatMessage } = useIntl();
19
+ const {
20
+ searchData,
21
+ loading,
22
+ sortColumn,
23
+ sortDirection,
24
+ handleSortSelection,
25
+ } = useSearchContext();
26
+
27
+ const links = _.propOr([], "data")(searchData);
28
+
29
+ return (
30
+ <>
31
+ {!_.isEmpty(links) ? (
32
+ <Table sortable selectable>
33
+ <Table.Header>
34
+ <Table.Row>
35
+ {approveView ? (
36
+ <Table.HeaderCell textAlign="center">
37
+ <Checkbox
38
+ id="selectLink"
39
+ onChange={addAll}
40
+ checked={checkedAll}
41
+ />
42
+ </Table.HeaderCell>
43
+ ) : null}
44
+ {columns.map((column, key) => (
45
+ <Table.HeaderCell
46
+ key={key}
47
+ width={column.width}
48
+ content={formatMessage({
49
+ id: `conceptRelations.approvals.header.${column.name}`,
50
+ defaultMessage: column.name,
51
+ })}
52
+ sorted={
53
+ _.path("sort.name")(column) === sortColumn
54
+ ? sortDirection
55
+ : null
56
+ }
57
+ className={_.path("sort.name")(column) ? "" : "disabled"}
58
+ onClick={() =>
59
+ handleSortSelection(_.path("sort.name")(column))
60
+ }
61
+ />
62
+ ))}
63
+ </Table.Row>
64
+ </Table.Header>
65
+ <Table.Body>
66
+ {links.map((link, key) => (
67
+ <ConceptLinksApprovalsRow
68
+ key={key}
69
+ checked={isRowChecked && isRowChecked(link)}
70
+ link={link}
71
+ columns={columns}
72
+ approveView={approveView}
73
+ onCheckboxChange={() => checkRow && checkRow(link)}
74
+ />
75
+ ))}
76
+ </Table.Body>
77
+ </Table>
78
+ ) : null}
79
+ {_.isEmpty(links) && !loading ? (
80
+ <Header as="h4">
81
+ <Icon name="search" />
82
+ <Header.Content>
83
+ {formatMessage({ id: "conceptRelations.approvals.links.empty" })}
84
+ </Header.Content>
85
+ </Header>
86
+ ) : null}
87
+ </>
88
+ );
89
+ };
90
+
91
+ ConceptLinksApprovalsTable.propTypes = {
92
+ columns: PropTypes.array,
93
+ approveView: PropTypes.bool,
94
+ checkedAll: PropTypes.bool,
95
+ addAll: PropTypes.func,
96
+ checkRow: PropTypes.func,
97
+ isRowChecked: PropTypes.func,
98
+ };
99
+
100
+ const mapStateToProps = (state) => ({
101
+ columns: getLinksSearchColumns(state),
102
+ });
103
+
104
+ export default connect(mapStateToProps)(ConceptLinksApprovalsTable);