@truedat/lm 8.2.0 → 8.2.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/api.js +4 -0
- package/src/components/ConceptLinkForm.js +111 -0
- package/src/components/LinksPagination.js +15 -0
- package/src/components/LinksSearch.js +139 -0
- package/src/components/QualityControlConcepts.js +156 -0
- package/src/components/QualityControlStructures.js +183 -0
- package/src/components/RelationTagForm.js +4 -0
- package/src/components/StructureLinkForm.js +105 -0
- package/src/components/__tests__/ConceptLinkForm.spec.js +252 -0
- package/src/components/__tests__/LinksSearch.spec.js +299 -0
- package/src/components/__tests__/QualityControlConcepts.spec.js +262 -0
- package/src/components/__tests__/QualityControlStructures.spec.js +213 -0
- package/src/components/__tests__/StructureLinkForm.spec.js +284 -0
- package/src/components/__tests__/__snapshots__/ConceptLinkForm.spec.js.snap +48 -0
- package/src/components/__tests__/__snapshots__/LinksSearch.spec.js.snap +29 -0
- package/src/components/__tests__/__snapshots__/NewRelationTag.spec.js.snap +13 -0
- package/src/components/__tests__/__snapshots__/QualityControlConcepts.spec.js.snap +49 -0
- package/src/components/__tests__/__snapshots__/QualityControlStructures.spec.js.snap +49 -0
- package/src/components/__tests__/__snapshots__/RelationTagForm.spec.js.snap +13 -0
- package/src/components/__tests__/__snapshots__/StructureLinkForm.spec.js.snap +48 -0
- package/src/hooks/useRelations.js +53 -0
- package/src/messages/en.js +1 -0
- package/src/messages/es.js +1 -0
- package/src/selectors/index.js +1 -0
- package/src/selectors/messages.js +48 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/lm",
|
|
3
|
-
"version": "8.2.
|
|
3
|
+
"version": "8.2.1",
|
|
4
4
|
"description": "Truedat Link Manager",
|
|
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.2.
|
|
51
|
+
"@truedat/test": "8.2.1",
|
|
52
52
|
"identity-obj-proxy": "^3.0.0",
|
|
53
53
|
"jest": "^29.7.0",
|
|
54
54
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -82,5 +82,5 @@
|
|
|
82
82
|
"semantic-ui-react": "^3.0.0-beta.2",
|
|
83
83
|
"swr": "^2.3.3"
|
|
84
84
|
},
|
|
85
|
-
"gitHead": "
|
|
85
|
+
"gitHead": "3008f4a5a734dc1f12e9f25a790542b56aed61d2"
|
|
86
86
|
}
|
package/src/api.js
CHANGED
|
@@ -2,6 +2,8 @@ const API_RELATION = "/api/relations/:id";
|
|
|
2
2
|
const API_RELATIONS = "/api/relations";
|
|
3
3
|
const API_RELATIONS_GRAPH = "/api/relations/:id/graph";
|
|
4
4
|
const API_RELATIONS_SEARCH = "/api/relations/search";
|
|
5
|
+
const API_RELATIONS_INDEX_SEARCH = "/api/relations/index_search";
|
|
6
|
+
const API_RELATIONS_FILTERS = "/api/relations/filters";
|
|
5
7
|
const API_TAG = "/api/tags/:id";
|
|
6
8
|
const API_TAGS = "/api/tags";
|
|
7
9
|
const API_TAGS_SEARCH = "/api/tags/search";
|
|
@@ -11,6 +13,8 @@ export {
|
|
|
11
13
|
API_RELATIONS,
|
|
12
14
|
API_RELATIONS_GRAPH,
|
|
13
15
|
API_RELATIONS_SEARCH,
|
|
16
|
+
API_RELATIONS_INDEX_SEARCH,
|
|
17
|
+
API_RELATIONS_FILTERS,
|
|
14
18
|
API_TAG,
|
|
15
19
|
API_TAGS,
|
|
16
20
|
API_TAGS_SEARCH,
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import { lazy, useState, useEffect } from "react";
|
|
3
|
+
import { useSelector, useDispatch } from "react-redux";
|
|
4
|
+
import { useIntl } from "react-intl";
|
|
5
|
+
import { clearSelectedRelationTags } from "../routines";
|
|
6
|
+
import { Divider, Button } from "semantic-ui-react";
|
|
7
|
+
import { HistoryBackButton } from "@truedat/core/components";
|
|
8
|
+
import { useWebContext } from "@truedat/core/webContext";
|
|
9
|
+
import RelationTagsLoader from "./RelationTagsLoader";
|
|
10
|
+
import { useCreateRelation } from "../hooks/useRelations";
|
|
11
|
+
import { useNavigate } from "react-router";
|
|
12
|
+
import { getRelationErrorAlertMessage } from "../selectors/messages";
|
|
13
|
+
|
|
14
|
+
const TagTypeDropdownSelector = lazy(
|
|
15
|
+
() => import("@truedat/lm/components/TagTypeDropdownSelector")
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const ConceptSelector = lazy(
|
|
19
|
+
() => import("@truedat/bg/concepts/relations/components/ConceptSelector")
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const filters = {
|
|
23
|
+
current: [true],
|
|
24
|
+
status: ["pending_approval", "draft", "published"],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const ConceptLinkForm = ({
|
|
28
|
+
targetId,
|
|
29
|
+
targetType,
|
|
30
|
+
redirectUrl,
|
|
31
|
+
selectTagOptions,
|
|
32
|
+
}) => {
|
|
33
|
+
const navigate = useNavigate();
|
|
34
|
+
const selectedRelationTags = useSelector(
|
|
35
|
+
(state) => state?.selectedRelationTags
|
|
36
|
+
);
|
|
37
|
+
const tagOptions = useSelector(selectTagOptions);
|
|
38
|
+
const { trigger: createRelation, isMutating: creatingRelation } =
|
|
39
|
+
useCreateRelation();
|
|
40
|
+
const dispatch = useDispatch();
|
|
41
|
+
const [selectedConcept, setSelectedConcept] = useState(null);
|
|
42
|
+
const { formatMessage } = useIntl();
|
|
43
|
+
const { setAlertMessage } = useWebContext();
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
return () => {
|
|
47
|
+
dispatch(clearSelectedRelationTags.trigger());
|
|
48
|
+
};
|
|
49
|
+
}, [clearSelectedRelationTags, dispatch]);
|
|
50
|
+
|
|
51
|
+
const handleSubmit = () => {
|
|
52
|
+
const link = {
|
|
53
|
+
source_id: selectedConcept?.business_concept_id,
|
|
54
|
+
source_type: "business_concept",
|
|
55
|
+
target_id: targetId,
|
|
56
|
+
target_type: targetType,
|
|
57
|
+
tag_ids: selectedRelationTags ? selectedRelationTags : [],
|
|
58
|
+
};
|
|
59
|
+
createRelation({ relation: link })
|
|
60
|
+
.then(({ data }) => {
|
|
61
|
+
navigate(redirectUrl, {
|
|
62
|
+
state: {
|
|
63
|
+
createdRelation: {
|
|
64
|
+
...data?.data,
|
|
65
|
+
source_name: selectedConcept?.name,
|
|
66
|
+
source_data: selectedConcept,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
})
|
|
71
|
+
.catch((error) => {
|
|
72
|
+
const alertMessage = getRelationErrorAlertMessage(error, formatMessage);
|
|
73
|
+
if (alertMessage) {
|
|
74
|
+
setAlertMessage(alertMessage);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const disabled = !(selectedConcept && selectedRelationTags);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<>
|
|
83
|
+
<Divider hidden />
|
|
84
|
+
<RelationTagsLoader />
|
|
85
|
+
{!_.isEmpty(tagOptions) && (
|
|
86
|
+
<>
|
|
87
|
+
<TagTypeDropdownSelector options={tagOptions} />
|
|
88
|
+
<Divider hidden />
|
|
89
|
+
</>
|
|
90
|
+
)}
|
|
91
|
+
<ConceptSelector
|
|
92
|
+
selectedConcept={selectedConcept}
|
|
93
|
+
handleConceptSelected={setSelectedConcept}
|
|
94
|
+
defaultFilters={filters}
|
|
95
|
+
/>
|
|
96
|
+
<Divider hidden />
|
|
97
|
+
<Button.Group>
|
|
98
|
+
<Button
|
|
99
|
+
primary
|
|
100
|
+
loading={creatingRelation}
|
|
101
|
+
content={formatMessage({ id: "actions.create" })}
|
|
102
|
+
disabled={disabled}
|
|
103
|
+
onClick={handleSubmit}
|
|
104
|
+
/>
|
|
105
|
+
<HistoryBackButton content={formatMessage({ id: "actions.cancel" })} />
|
|
106
|
+
</Button.Group>
|
|
107
|
+
</>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export default ConceptLinkForm;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Pagination } from "@truedat/core/components";
|
|
2
|
+
import { useSearchContext } from "@truedat/core/search/SearchContext";
|
|
3
|
+
|
|
4
|
+
export default function LinksPagination() {
|
|
5
|
+
const { count, size, page, selectPage } = useSearchContext();
|
|
6
|
+
const totalPages = Math.ceil(count / size);
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<Pagination
|
|
10
|
+
totalPages={totalPages}
|
|
11
|
+
activePage={page}
|
|
12
|
+
selectPage={selectPage}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import {
|
|
3
|
+
SearchContextProvider,
|
|
4
|
+
useSearchContext,
|
|
5
|
+
} from "@truedat/core/search/SearchContext";
|
|
6
|
+
import { useEffect, useRef } from "react";
|
|
7
|
+
import { useNavigate, useLocation } from "react-router";
|
|
8
|
+
import SearchWidget from "@truedat/core/search/SearchWidget";
|
|
9
|
+
import { useRelations, useRelationFilters } from "../hooks/useRelations";
|
|
10
|
+
import { Table, Segment, Header, Icon } from "semantic-ui-react";
|
|
11
|
+
import { FormattedMessage } from "react-intl";
|
|
12
|
+
import { columnDecorator } from "@truedat/core/services";
|
|
13
|
+
import LinksPagination from "./LinksPagination";
|
|
14
|
+
|
|
15
|
+
const LinkRow = ({ link, columns, highlight }) => (
|
|
16
|
+
<Table.Row positive={highlight}>
|
|
17
|
+
{columns.map((column, i) => (
|
|
18
|
+
<Table.Cell
|
|
19
|
+
key={i}
|
|
20
|
+
textAlign={_.prop("textAlign")(column)}
|
|
21
|
+
content={columnDecorator(column)(link)}
|
|
22
|
+
/>
|
|
23
|
+
))}
|
|
24
|
+
</Table.Row>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const LinksTable = ({ relations, columns, extraRow }) => {
|
|
28
|
+
const headerRow = columns.map(({ header }, key) => (
|
|
29
|
+
<Table.HeaderCell
|
|
30
|
+
key={key}
|
|
31
|
+
content={header ? <FormattedMessage id={header} /> : null}
|
|
32
|
+
/>
|
|
33
|
+
));
|
|
34
|
+
|
|
35
|
+
const renderBodyRow = (link, i) => (
|
|
36
|
+
<LinkRow
|
|
37
|
+
key={i}
|
|
38
|
+
link={link}
|
|
39
|
+
columns={columns}
|
|
40
|
+
highlight={link.id === extraRow?.id}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Table
|
|
46
|
+
headerRow={headerRow}
|
|
47
|
+
renderBodyRow={renderBodyRow}
|
|
48
|
+
tableData={relations}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
const LinksSearchContent = ({
|
|
53
|
+
columns,
|
|
54
|
+
extraRow,
|
|
55
|
+
deletedLinkId,
|
|
56
|
+
refetchSearch,
|
|
57
|
+
setRefetchSearch,
|
|
58
|
+
targetType = "business_concept",
|
|
59
|
+
}) => {
|
|
60
|
+
const { searchData, loading, filterParams } = useSearchContext();
|
|
61
|
+
const { trigger: searchRelations } = useRelations();
|
|
62
|
+
const { trigger: searchRelationsFilters } = useRelationFilters();
|
|
63
|
+
const previousParamsRef = useRef(filterParams);
|
|
64
|
+
const navigate = useNavigate();
|
|
65
|
+
const location = useLocation();
|
|
66
|
+
const emptyMessage =
|
|
67
|
+
targetType === "business_concept"
|
|
68
|
+
? "linked_concepts.empty"
|
|
69
|
+
: "linked_structures.empty";
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (refetchSearch) {
|
|
73
|
+
setRefetchSearch(false);
|
|
74
|
+
searchRelations(filterParams);
|
|
75
|
+
searchRelationsFilters(filterParams);
|
|
76
|
+
}
|
|
77
|
+
}, [refetchSearch, filterParams, searchRelations, searchRelationsFilters]);
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (_.isEmpty(previousParamsRef.current)) {
|
|
81
|
+
previousParamsRef.current = filterParams;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (extraRow && !_.isEqual(previousParamsRef, filterParams)) {
|
|
86
|
+
navigate(location.pathname, {
|
|
87
|
+
replace: true,
|
|
88
|
+
state: null,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}, [extraRow, previousParamsRef, location.pathname, filterParams]);
|
|
92
|
+
const data = _.reject(_.propEq("id", deletedLinkId))(searchData?.data || []);
|
|
93
|
+
const relations =
|
|
94
|
+
extraRow && !_.some(_.propEq("id", extraRow.id))(data)
|
|
95
|
+
? [extraRow, ...data]
|
|
96
|
+
: data;
|
|
97
|
+
return (
|
|
98
|
+
<Segment loading={loading}>
|
|
99
|
+
<SearchWidget />
|
|
100
|
+
{!_.isEmpty(relations) ? (
|
|
101
|
+
<LinksTable
|
|
102
|
+
relations={relations}
|
|
103
|
+
columns={columns}
|
|
104
|
+
extraRow={extraRow}
|
|
105
|
+
/>
|
|
106
|
+
) : (
|
|
107
|
+
<Header as="h4">
|
|
108
|
+
<Icon name="search" />
|
|
109
|
+
<Header.Content>
|
|
110
|
+
<FormattedMessage id={emptyMessage} />
|
|
111
|
+
</Header.Content>
|
|
112
|
+
</Header>
|
|
113
|
+
)}
|
|
114
|
+
<LinksPagination size="small" />
|
|
115
|
+
</Segment>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export function LinksSearch(props) {
|
|
120
|
+
const defaultFilters = _.propOr({}, "defaultFilters")(props);
|
|
121
|
+
const useSearch = _.propOr(useRelations, "useSearch")(props);
|
|
122
|
+
const useFilters = _.propOr(useRelationFilters, "useFilters")(props);
|
|
123
|
+
const searchProps = {
|
|
124
|
+
initialSortColumn: _.propOr("source_name.raw", "initialSortColumn")(props),
|
|
125
|
+
initialSortDirection: "ascending",
|
|
126
|
+
useSearch: useSearch,
|
|
127
|
+
useFilters: useFilters,
|
|
128
|
+
pageSize: 10,
|
|
129
|
+
omitFilters: ["current", "domain_ids"],
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<SearchContextProvider {...searchProps} defaultFilters={defaultFilters}>
|
|
134
|
+
<LinksSearchContent {...props} />
|
|
135
|
+
</SearchContextProvider>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export default LinksSearch;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import { Button, Grid, Label, Segment } from "semantic-ui-react";
|
|
5
|
+
import { FormattedMessage } from "react-intl";
|
|
6
|
+
import { useDeleteRelation } from "../hooks/useRelations";
|
|
7
|
+
import { Link, useLocation } from "react-router";
|
|
8
|
+
import { linkTo } from "@truedat/core/routes";
|
|
9
|
+
import { ConfirmDeleteRelation } from "./ConfirmDeleteRelation";
|
|
10
|
+
import LinksSearch from "./LinksSearch";
|
|
11
|
+
|
|
12
|
+
const mapStatusColor = {
|
|
13
|
+
deprecated: "grey",
|
|
14
|
+
draft: "olive",
|
|
15
|
+
pending_approval: "teal",
|
|
16
|
+
published: "green",
|
|
17
|
+
rejected: "red",
|
|
18
|
+
versioned: "yellow",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const ConceptLink = ({ source_id: business_concept_id, source_name: name }) => (
|
|
22
|
+
<Link
|
|
23
|
+
to={linkTo.CONCEPT_LINKS_QUALITY_CONTROLS({
|
|
24
|
+
business_concept_id,
|
|
25
|
+
id: "current",
|
|
26
|
+
})}
|
|
27
|
+
>
|
|
28
|
+
{name}
|
|
29
|
+
</Link>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const RelationTag = ({ tag_type }) => {
|
|
33
|
+
if (!tag_type) return null;
|
|
34
|
+
return (
|
|
35
|
+
<FormattedMessage
|
|
36
|
+
id={`relation.tag.${tag_type}`}
|
|
37
|
+
defaultMessage={tag_type}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const ConfirmDeleteQualityControlRelation = ({ id, deleteRelation }) => (
|
|
43
|
+
<ConfirmDeleteRelation
|
|
44
|
+
id={id}
|
|
45
|
+
resourceType="quality_control_concept"
|
|
46
|
+
deleteRelation={deleteRelation}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const StatusDecorator = ({ status }) => (
|
|
51
|
+
<Label color={mapStatusColor[status]}>
|
|
52
|
+
<FormattedMessage id={`concepts.status.${status}`} />
|
|
53
|
+
</Label>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
export const QualityControlConcepts = ({ qualityControl, actions }) => {
|
|
57
|
+
const target_id = qualityControl?.id;
|
|
58
|
+
const target_type = "quality_control";
|
|
59
|
+
const source_type = "business_concept";
|
|
60
|
+
const defaultFilters = {
|
|
61
|
+
target_id,
|
|
62
|
+
target_type,
|
|
63
|
+
source_type,
|
|
64
|
+
};
|
|
65
|
+
const [refetchSearch, setRefetchSearch] = useState(false);
|
|
66
|
+
const [deletedRelationId, setDeletedRelationId] = useState(null);
|
|
67
|
+
const { trigger: deleteRelation, isMutating: deleting } = useDeleteRelation();
|
|
68
|
+
const canCreateLink = _.includes("link_to_business_concept")(actions);
|
|
69
|
+
const { state } = useLocation();
|
|
70
|
+
const extraRow = state?.createdRelation;
|
|
71
|
+
|
|
72
|
+
const onDeleteRelation = ({ id }) => {
|
|
73
|
+
deleteRelation(id).then(({ status }) => {
|
|
74
|
+
if (status === 204) {
|
|
75
|
+
setRefetchSearch(true);
|
|
76
|
+
setDeletedRelationId(id);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const deleteColumn = canCreateLink
|
|
82
|
+
? [
|
|
83
|
+
{
|
|
84
|
+
fieldSelector: _.identity,
|
|
85
|
+
fieldDecorator: ({ id }) => (
|
|
86
|
+
<ConfirmDeleteQualityControlRelation
|
|
87
|
+
id={id}
|
|
88
|
+
deleteRelation={onDeleteRelation}
|
|
89
|
+
/>
|
|
90
|
+
),
|
|
91
|
+
},
|
|
92
|
+
]
|
|
93
|
+
: [];
|
|
94
|
+
|
|
95
|
+
const columns = [
|
|
96
|
+
{
|
|
97
|
+
header: "concepts.props.name",
|
|
98
|
+
fieldSelector: _.identity,
|
|
99
|
+
fieldDecorator: ConceptLink,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
header: "concepts.props.domain",
|
|
103
|
+
fieldSelector: _.path("source_data.domain.name"),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
header: "concepts.props.tags",
|
|
107
|
+
fieldSelector: _.identity,
|
|
108
|
+
fieldDecorator: RelationTag,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
header: "concepts.props.status",
|
|
112
|
+
fieldSelector: _.path("source_data.status"),
|
|
113
|
+
fieldDecorator: (status) => <StatusDecorator status={status} />,
|
|
114
|
+
},
|
|
115
|
+
...deleteColumn,
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<Segment basic loading={deleting}>
|
|
120
|
+
<Grid>
|
|
121
|
+
<Grid.Column width={16}>
|
|
122
|
+
{canCreateLink && (
|
|
123
|
+
<Button
|
|
124
|
+
floated="right"
|
|
125
|
+
primary
|
|
126
|
+
as={Link}
|
|
127
|
+
to={linkTo.QUALITY_CONTROL_CONCEPT_LINKS_NEW({
|
|
128
|
+
id: qualityControl?.id,
|
|
129
|
+
version: qualityControl?.version,
|
|
130
|
+
})}
|
|
131
|
+
state={null}
|
|
132
|
+
content={<FormattedMessage id="links.actions.create" />}
|
|
133
|
+
/>
|
|
134
|
+
)}
|
|
135
|
+
</Grid.Column>
|
|
136
|
+
<Grid.Column width={16}>
|
|
137
|
+
<LinksSearch
|
|
138
|
+
columns={columns}
|
|
139
|
+
extraRow={extraRow}
|
|
140
|
+
deletedLinkId={deletedRelationId}
|
|
141
|
+
defaultFilters={defaultFilters}
|
|
142
|
+
refetchSearch={refetchSearch}
|
|
143
|
+
setRefetchSearch={setRefetchSearch}
|
|
144
|
+
/>
|
|
145
|
+
</Grid.Column>
|
|
146
|
+
</Grid>
|
|
147
|
+
</Segment>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
QualityControlConcepts.propTypes = {
|
|
152
|
+
qualityControl: PropTypes.object,
|
|
153
|
+
actions: PropTypes.array,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export default QualityControlConcepts;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import { Button, Grid, Icon, Label, Segment } from "semantic-ui-react";
|
|
5
|
+
import { FormattedMessage, useIntl } from "react-intl";
|
|
6
|
+
import { useDeleteRelation } from "../hooks/useRelations";
|
|
7
|
+
import { Link, useLocation } from "react-router";
|
|
8
|
+
import { linkTo } from "@truedat/core/routes";
|
|
9
|
+
import { useRelations, useRelationFilters } from "../hooks/useRelations";
|
|
10
|
+
import { ConfirmDeleteRelation } from "./ConfirmDeleteRelation";
|
|
11
|
+
import LinksSearch from "./LinksSearch";
|
|
12
|
+
import Moment from "react-moment";
|
|
13
|
+
|
|
14
|
+
const StructureLink = ({ target_id: id, target_name: name }) => (
|
|
15
|
+
<Link to={linkTo.STRUCTURE({ id })} title={name}>
|
|
16
|
+
{name}
|
|
17
|
+
</Link>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const ConfirmDeleteQualityControlRelation = ({ id, deleteRelation }) => (
|
|
21
|
+
<ConfirmDeleteRelation
|
|
22
|
+
id={id}
|
|
23
|
+
resourceType="quality_control_structure"
|
|
24
|
+
deleteRelation={deleteRelation}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const PathDecorator = (path) => (
|
|
29
|
+
<span title={_.join(" › ")(path)}>
|
|
30
|
+
{_.flow(_.join(" › "), _.truncate({ length: 90 }))(path)}{" "}
|
|
31
|
+
</span>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const DateDecorator = (date) => {
|
|
35
|
+
const { locale } = useIntl();
|
|
36
|
+
return (
|
|
37
|
+
date && <Moment locale={locale} date={date} format="YYYY-MM-DD HH:mm" />
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const DeletedDateDecorator = (date) => {
|
|
42
|
+
const { locale } = useIntl();
|
|
43
|
+
return (
|
|
44
|
+
date && (
|
|
45
|
+
<Label className="alert warning">
|
|
46
|
+
<Icon name="warning circle" color="red" />
|
|
47
|
+
<Moment locale={locale} date={date} format="YYYY-MM-DD HH:mm" />
|
|
48
|
+
</Label>
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const RelationTag = ({ tag_type }) => {
|
|
54
|
+
if (!tag_type) return null;
|
|
55
|
+
return (
|
|
56
|
+
<FormattedMessage
|
|
57
|
+
id={`relation.tag.${tag_type}`}
|
|
58
|
+
defaultMessage={tag_type}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const QualityControlStructures = ({ qualityControl, actions }) => {
|
|
64
|
+
const source_id = qualityControl?.id;
|
|
65
|
+
const source_type = "quality_control";
|
|
66
|
+
const target_type = "data_structure";
|
|
67
|
+
const defaultFilters = {
|
|
68
|
+
source_id,
|
|
69
|
+
target_type,
|
|
70
|
+
source_type,
|
|
71
|
+
};
|
|
72
|
+
const [refetchSearch, setRefetchSearch] = useState(false);
|
|
73
|
+
const [deletedRelationId, setDeletedRelationId] = useState(null);
|
|
74
|
+
const { trigger: deleteRelation, isMutating: deleting } = useDeleteRelation();
|
|
75
|
+
const canCreateLink = _.includes("link_to_data_structure")(actions);
|
|
76
|
+
const { state } = useLocation();
|
|
77
|
+
const extraRow = state?.createdRelation;
|
|
78
|
+
|
|
79
|
+
const onDeleteRelation = ({ id }) => {
|
|
80
|
+
deleteRelation(id).then(({ status }) => {
|
|
81
|
+
if (status === 204) {
|
|
82
|
+
setRefetchSearch(true);
|
|
83
|
+
setDeletedRelationId(id);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const deleteColumn = canCreateLink
|
|
89
|
+
? [
|
|
90
|
+
{
|
|
91
|
+
fieldSelector: _.identity,
|
|
92
|
+
fieldDecorator: ({ id }) => (
|
|
93
|
+
<ConfirmDeleteQualityControlRelation
|
|
94
|
+
id={id}
|
|
95
|
+
deleteRelation={onDeleteRelation}
|
|
96
|
+
/>
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
: [];
|
|
101
|
+
|
|
102
|
+
const columns = [
|
|
103
|
+
{
|
|
104
|
+
header: "structures.props.name",
|
|
105
|
+
fieldSelector: _.identity,
|
|
106
|
+
fieldDecorator: StructureLink,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
header: "structures.props.domain",
|
|
110
|
+
fieldSelector: _.path("target_data.domain.name"),
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
header: "structures.props.system",
|
|
114
|
+
fieldSelector: _.path("target_data.system.name"),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
header: "structures.props.path",
|
|
118
|
+
fieldSelector: _.path("target_data.path"),
|
|
119
|
+
fieldDecorator: PathDecorator,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
header: "structures.props.last_change_at",
|
|
123
|
+
fieldSelector: _.path("target_data.last_change_at"),
|
|
124
|
+
fieldDecorator: DateDecorator,
|
|
125
|
+
width: 1,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
header: "structures.props.relation_tags",
|
|
129
|
+
fieldSelector: _.identity,
|
|
130
|
+
fieldDecorator: RelationTag,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
header: "structures.props.deleted_at",
|
|
134
|
+
fieldSelector: _.path("target_data.deleted_at"),
|
|
135
|
+
fieldDecorator: DeletedDateDecorator,
|
|
136
|
+
width: 1,
|
|
137
|
+
},
|
|
138
|
+
...deleteColumn,
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<Segment basic loading={deleting}>
|
|
143
|
+
<Grid>
|
|
144
|
+
<Grid.Column width={16}>
|
|
145
|
+
{canCreateLink && (
|
|
146
|
+
<Button
|
|
147
|
+
floated="right"
|
|
148
|
+
primary
|
|
149
|
+
as={Link}
|
|
150
|
+
to={linkTo.QUALITY_CONTROL_STRUCTURE_LINKS_NEW({
|
|
151
|
+
id: qualityControl?.id,
|
|
152
|
+
version: qualityControl?.version,
|
|
153
|
+
})}
|
|
154
|
+
state={null}
|
|
155
|
+
content={<FormattedMessage id="links.actions.create" />}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
158
|
+
</Grid.Column>
|
|
159
|
+
<Grid.Column width={16}>
|
|
160
|
+
<LinksSearch
|
|
161
|
+
columns={columns}
|
|
162
|
+
extraRow={extraRow}
|
|
163
|
+
deletedLinkId={deletedRelationId}
|
|
164
|
+
defaultFilters={defaultFilters}
|
|
165
|
+
refetchSearch={refetchSearch}
|
|
166
|
+
setRefetchSearch={setRefetchSearch}
|
|
167
|
+
targetType={target_type}
|
|
168
|
+
initialSortColumn="target_name.raw"
|
|
169
|
+
useSearch={() => useRelations({ document: "all" })}
|
|
170
|
+
useFilters={() => useRelationFilters({ document: "all" })}
|
|
171
|
+
/>
|
|
172
|
+
</Grid.Column>
|
|
173
|
+
</Grid>
|
|
174
|
+
</Segment>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
QualityControlStructures.propTypes = {
|
|
179
|
+
qualityControl: PropTypes.object,
|
|
180
|
+
actions: PropTypes.array,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export default QualityControlStructures;
|
|
@@ -31,6 +31,10 @@ export const RelationTagForm = ({ relationTag, isSubmitting, onSubmit }) => {
|
|
|
31
31
|
value: "data_field",
|
|
32
32
|
text: formatMessage({ id: "target_type.data_field" }),
|
|
33
33
|
},
|
|
34
|
+
{
|
|
35
|
+
value: "quality_control",
|
|
36
|
+
text: formatMessage({ id: "target_type.quality_control" }),
|
|
37
|
+
},
|
|
34
38
|
];
|
|
35
39
|
const currentTargetType = watch("target_type");
|
|
36
40
|
return (
|