@truedat/bg 7.10.2 → 7.10.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/concepts/api.js +2 -0
- package/src/concepts/components/ConceptSuggestions.js +71 -0
- package/src/concepts/components/__tests__/ConceptSuggestions.spec.js +74 -0
- package/src/concepts/hooks/useConcepts.js +7 -0
- package/src/concepts/relations/components/ConceptSelector.js +117 -75
- package/src/concepts/relations/components/__tests__/ConceptSelector.spec.js +90 -1
- package/src/concepts/relations/components/__tests__/__snapshots__/ConceptSelector.spec.js.snap +99 -0
- package/src/concepts/relations/sagas/linkConcept.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/bg",
|
|
3
|
-
"version": "7.10.
|
|
3
|
+
"version": "7.10.4",
|
|
4
4
|
"description": "Truedat Web Business Glossary",
|
|
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": "7.10.
|
|
51
|
+
"@truedat/test": "7.10.4",
|
|
52
52
|
"identity-obj-proxy": "^3.0.0",
|
|
53
53
|
"jest": "^29.7.0",
|
|
54
54
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"semantic-ui-react": "^3.0.0-beta.2",
|
|
82
82
|
"swr": "^2.3.3"
|
|
83
83
|
},
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "0dcedbfaed3964ee02a3d2c175e0f96e2fbc9316"
|
|
85
85
|
}
|
package/src/concepts/api.js
CHANGED
|
@@ -20,8 +20,10 @@ const API_BUSINESS_CONCEPT_BULK_UPLOAD =
|
|
|
20
20
|
"/api/business_concepts/bulk_upload_event";
|
|
21
21
|
const API_BUSINESS_CONCEPT_SET_CONFIDENTIAL =
|
|
22
22
|
"/api/business_concept_versions/:id/set_confidential";
|
|
23
|
+
const API_CONCEPT_SUGGESTIONS = "/api/business_concept_versions/suggestions"
|
|
23
24
|
|
|
24
25
|
export {
|
|
26
|
+
API_CONCEPT_SUGGESTIONS,
|
|
25
27
|
API_BUSINESS_CONCEPT_VERSION,
|
|
26
28
|
API_BUSINESS_CONCEPT_VERSIONS_ACTIONS,
|
|
27
29
|
API_BUSINESS_CONCEPT_VERSIONS_CSV,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React, { useEffect } from "react";
|
|
3
|
+
import { Popup } from "semantic-ui-react";
|
|
4
|
+
import { useIntl } from "react-intl";
|
|
5
|
+
import { useParams } from "react-router";
|
|
6
|
+
import { useSelector } from "react-redux";
|
|
7
|
+
import PropTypes from "prop-types";
|
|
8
|
+
import { useConceptSuggestions } from "../hooks/useConcepts";
|
|
9
|
+
import { ConceptSelectorTable } from "../relations/components/ConceptSelector";
|
|
10
|
+
|
|
11
|
+
const SimilarityColumn = (similarity) => {
|
|
12
|
+
const { formatMessage } = useIntl();
|
|
13
|
+
return (
|
|
14
|
+
<Popup
|
|
15
|
+
content={formatMessage({ id: "suggestions.similarity.cosine.popup" })}
|
|
16
|
+
trigger={<span>{similarity.toFixed(3)}</span>}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const similarityColumnDefinition = {
|
|
22
|
+
name: "similarity",
|
|
23
|
+
fieldSelector: _.prop("similarity"),
|
|
24
|
+
fieldDecorator: SimilarityColumn,
|
|
25
|
+
width: 1,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const ConceptSuggestions = ({
|
|
29
|
+
selectedConcept,
|
|
30
|
+
handleConceptSelected,
|
|
31
|
+
}) => {
|
|
32
|
+
const { id } = useParams();
|
|
33
|
+
const structureLinks = useSelector((state) => state.structureLinks);
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
trigger: fetchSuggestions,
|
|
37
|
+
data,
|
|
38
|
+
} = useConceptSuggestions();
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (id && structureLinks) {
|
|
42
|
+
fetchSuggestions({
|
|
43
|
+
resource: {
|
|
44
|
+
id,
|
|
45
|
+
type: "structures",
|
|
46
|
+
links: _.filter(
|
|
47
|
+
({ resource_type }) => resource_type == "concept"
|
|
48
|
+
)(structureLinks),
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}, [id, structureLinks]);
|
|
53
|
+
|
|
54
|
+
const concepts = data?.data?.data || [];
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<ConceptSelectorTable
|
|
58
|
+
concepts={concepts}
|
|
59
|
+
selectedConcept={selectedConcept}
|
|
60
|
+
handleConceptSelected={handleConceptSelected}
|
|
61
|
+
extraColumns={[similarityColumnDefinition]}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
ConceptSuggestions.propTypes = {
|
|
67
|
+
handleSelectedStructure: PropTypes.func,
|
|
68
|
+
selectedStructure: PropTypes.object,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default ConceptSuggestions;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, waitForLoad } from "@truedat/test/render";
|
|
3
|
+
import { useParams } from "react-router";
|
|
4
|
+
import { ConceptSuggestions } from "../ConceptSuggestions";
|
|
5
|
+
import { ConceptSelectorTable } from "../../relations/components/ConceptSelector";
|
|
6
|
+
import { useConceptSuggestions } from "../../hooks/useConcepts";
|
|
7
|
+
|
|
8
|
+
jest.mock("react-router", () => ({
|
|
9
|
+
...jest.requireActual("react-router"),
|
|
10
|
+
useParams: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.mock("../../hooks/useConcepts", () => ({
|
|
14
|
+
useConceptSuggestions: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
jest.mock("../../relations/components/ConceptSelector", () => {
|
|
18
|
+
return {
|
|
19
|
+
__esModule: true,
|
|
20
|
+
ConceptSelectorTable: jest.fn(() => <div>MockSearchResults</div>),
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("ConceptSuggestions", () => {
|
|
25
|
+
const mockFetchSuggestions = jest.fn();
|
|
26
|
+
|
|
27
|
+
const structureLinks = [
|
|
28
|
+
{ resource_type: "concept", id: "c1" },
|
|
29
|
+
{ resource_type: "other", id: "not-c" }
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
jest.clearAllMocks();
|
|
34
|
+
useParams.mockReturnValue({ id: "123" });
|
|
35
|
+
|
|
36
|
+
useConceptSuggestions.mockReturnValue({
|
|
37
|
+
trigger: mockFetchSuggestions,
|
|
38
|
+
data: {
|
|
39
|
+
data: {
|
|
40
|
+
data: [
|
|
41
|
+
{ id: "concept1", similarity: 0.89 },
|
|
42
|
+
{ id: "concept2", similarity: 0.77 },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
isMutating: false,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const state = { structureLinks };
|
|
51
|
+
|
|
52
|
+
it("calls fetchSuggestions on mount with correct parameters", async () => {
|
|
53
|
+
const rendered = render(<ConceptSuggestions selectedConcept={null} handleConceptSelected={jest.fn()} />, { state });
|
|
54
|
+
await waitForLoad(rendered);
|
|
55
|
+
expect(mockFetchSuggestions).toHaveBeenCalledWith({
|
|
56
|
+
resource: {
|
|
57
|
+
id: "123",
|
|
58
|
+
type: "structures",
|
|
59
|
+
links: [{ resource_type: "concept", id: "c1" }],
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("renders ConceptSelectorTable with props", async () => {
|
|
65
|
+
const rendered = render(<ConceptSuggestions selectedConcept={{}} handleConceptSelected={jest.fn()} />, { state });
|
|
66
|
+
await waitForLoad(rendered);
|
|
67
|
+
expect(rendered.getByText("MockSearchResults")).toBeInTheDocument();
|
|
68
|
+
const props = ConceptSelectorTable.mock.calls[0][0];
|
|
69
|
+
expect(props.concepts).toEqual([
|
|
70
|
+
{ id: "concept1", similarity: 0.89 },
|
|
71
|
+
{ id: "concept2", similarity: 0.77 },
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -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,14 +1,7 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
3
|
import { connect } from "react-redux";
|
|
4
|
-
import {
|
|
5
|
-
Button,
|
|
6
|
-
Table,
|
|
7
|
-
Message,
|
|
8
|
-
Label,
|
|
9
|
-
Icon,
|
|
10
|
-
Segment,
|
|
11
|
-
} from "semantic-ui-react";
|
|
4
|
+
import { Button, Table, Message, Label, Segment } from "semantic-ui-react";
|
|
12
5
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
13
6
|
import SearchWidget from "@truedat/core/search/SearchWidget";
|
|
14
7
|
import {
|
|
@@ -44,6 +37,7 @@ const ConceptSelectorRow = ({
|
|
|
44
37
|
onClick,
|
|
45
38
|
disabled,
|
|
46
39
|
positive,
|
|
40
|
+
extraColumns = [],
|
|
47
41
|
}) => {
|
|
48
42
|
const { name, status, domain } = concept;
|
|
49
43
|
return (
|
|
@@ -65,6 +59,12 @@ const ConceptSelectorRow = ({
|
|
|
65
59
|
</Label>
|
|
66
60
|
}
|
|
67
61
|
/>
|
|
62
|
+
{extraColumns.map((column, key) => (
|
|
63
|
+
<Table.Cell
|
|
64
|
+
key={key}
|
|
65
|
+
content={column.fieldDecorator(column.fieldSelector(concept))}
|
|
66
|
+
/>
|
|
67
|
+
))}
|
|
68
68
|
</Table.Row>
|
|
69
69
|
);
|
|
70
70
|
};
|
|
@@ -77,20 +77,14 @@ ConceptSelectorRow.propTypes = {
|
|
|
77
77
|
positive: PropTypes.bool,
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
export
|
|
80
|
+
export const ConceptSelectorTable = ({
|
|
81
81
|
businessConceptId,
|
|
82
|
-
|
|
82
|
+
concepts,
|
|
83
83
|
selectedConcept,
|
|
84
|
-
|
|
84
|
+
handleConceptSelected,
|
|
85
85
|
links,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const { trigger: triggerDownload, isMutating: downloading } =
|
|
89
|
-
useConceptLinksDownload();
|
|
90
|
-
const { formatMessage } = useIntl();
|
|
91
|
-
const concepts = searchData?.data;
|
|
92
|
-
const conceptsActions = _.propOr({}, "_actions")(searchData);
|
|
93
|
-
|
|
86
|
+
extraColumns = [],
|
|
87
|
+
}) => {
|
|
94
88
|
const selectedConceptIsFiltered = !_.reduce(
|
|
95
89
|
(acc, c) => (selectedConcept && c.id == selectedConcept.id) || acc,
|
|
96
90
|
false
|
|
@@ -104,7 +98,85 @@ export function ConceptSelectorContent({
|
|
|
104
98
|
)(concept)
|
|
105
99
|
: false;
|
|
106
100
|
};
|
|
107
|
-
|
|
101
|
+
|
|
102
|
+
return !_.isEmpty(concepts) || selectedConcept ? (
|
|
103
|
+
<Table selectable size="small">
|
|
104
|
+
<Table.Header>
|
|
105
|
+
<Table.Row>
|
|
106
|
+
<Table.HeaderCell
|
|
107
|
+
content={<FormattedMessage id="concepts.props.name" />}
|
|
108
|
+
/>
|
|
109
|
+
<Table.HeaderCell
|
|
110
|
+
content={<FormattedMessage id="concepts.props.domain" />}
|
|
111
|
+
/>
|
|
112
|
+
<Table.HeaderCell
|
|
113
|
+
content={<FormattedMessage id="concepts.props.status" />}
|
|
114
|
+
/>
|
|
115
|
+
{extraColumns.map((column, key) => (
|
|
116
|
+
<Table.HeaderCell
|
|
117
|
+
key={key}
|
|
118
|
+
content={
|
|
119
|
+
<FormattedMessage id={`concepts.props.${column.name}`} />
|
|
120
|
+
}
|
|
121
|
+
/>
|
|
122
|
+
))}
|
|
123
|
+
</Table.Row>
|
|
124
|
+
</Table.Header>
|
|
125
|
+
<Table.Body>
|
|
126
|
+
{concepts.map((s, i) => (
|
|
127
|
+
<ConceptSelectorRow
|
|
128
|
+
disabled={
|
|
129
|
+
businessConceptId == s.business_concept_id || isDisabled(s)
|
|
130
|
+
}
|
|
131
|
+
key={i}
|
|
132
|
+
concept={s}
|
|
133
|
+
active={selectedConcept && selectedConcept.id == s.id}
|
|
134
|
+
onClick={handleConceptSelected}
|
|
135
|
+
positive={isPositive(
|
|
136
|
+
links,
|
|
137
|
+
s.business_concept_id,
|
|
138
|
+
businessConceptId
|
|
139
|
+
)}
|
|
140
|
+
extraColumns={extraColumns}
|
|
141
|
+
/>
|
|
142
|
+
))}
|
|
143
|
+
{selectedConcept && selectedConceptIsFiltered && (
|
|
144
|
+
<ConceptSelectorRow
|
|
145
|
+
concept={selectedConcept}
|
|
146
|
+
extraColumns={extraColumns}
|
|
147
|
+
active
|
|
148
|
+
/>
|
|
149
|
+
)}
|
|
150
|
+
</Table.Body>
|
|
151
|
+
</Table>
|
|
152
|
+
) : (
|
|
153
|
+
<Message header={<FormattedMessage id="concepts.search.results.empty" />} />
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
ConceptSelectorTable.propTypes = {
|
|
158
|
+
businessConceptId: PropTypes.number,
|
|
159
|
+
concepts: PropTypes.array,
|
|
160
|
+
selectedConcept: PropTypes.object,
|
|
161
|
+
handleConceptSelected: PropTypes.func,
|
|
162
|
+
links: PropTypes.array,
|
|
163
|
+
extraColumns: PropTypes.array,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export function ConceptSelectorContent({
|
|
167
|
+
businessConceptId,
|
|
168
|
+
handleConceptSelected,
|
|
169
|
+
selectedConcept,
|
|
170
|
+
showTitle = true,
|
|
171
|
+
links,
|
|
172
|
+
}) {
|
|
173
|
+
const { formatMessage } = useIntl();
|
|
174
|
+
const { searchData, filterParams: searchParams } = useSearchContext();
|
|
175
|
+
const { trigger: triggerDownload, isMutating: downloading } =
|
|
176
|
+
useConceptLinksDownload();
|
|
177
|
+
const concepts = searchData?.data;
|
|
178
|
+
const conceptsActions = _.propOr({}, "_actions")(searchData);
|
|
179
|
+
|
|
108
180
|
return (
|
|
109
181
|
<>
|
|
110
182
|
{showTitle && (
|
|
@@ -113,64 +185,34 @@ export function ConceptSelectorContent({
|
|
|
113
185
|
</label>
|
|
114
186
|
)}
|
|
115
187
|
<Segment>
|
|
116
|
-
{conceptsActions?.downloadLinks && (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
188
|
+
{conceptsActions?.downloadLinks && (
|
|
189
|
+
<>
|
|
190
|
+
<Button
|
|
191
|
+
icon="download"
|
|
192
|
+
floated="right"
|
|
193
|
+
secondary
|
|
194
|
+
loading={downloading}
|
|
195
|
+
onClick={() => triggerDownload(searchParams)}
|
|
196
|
+
data-tooltip={formatMessage({
|
|
197
|
+
id: "concepts.actions.downloadLinks.tooltip",
|
|
198
|
+
})}
|
|
199
|
+
/>
|
|
128
200
|
<ConceptLinksUploadButton />
|
|
129
|
-
|
|
201
|
+
</>
|
|
130
202
|
)}
|
|
131
203
|
<SearchWidget />
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
/>
|
|
145
|
-
</Table.Row>
|
|
146
|
-
</Table.Header>
|
|
147
|
-
<Table.Body>
|
|
148
|
-
{concepts.map((s, i) => (
|
|
149
|
-
<ConceptSelectorRow
|
|
150
|
-
disabled={
|
|
151
|
-
businessConceptId == s.business_concept_id || isDisabled(s)
|
|
152
|
-
}
|
|
153
|
-
key={i}
|
|
154
|
-
concept={s}
|
|
155
|
-
active={selectedConcept && selectedConcept.id == s.id}
|
|
156
|
-
onClick={handleConceptSelected}
|
|
157
|
-
positive={isPositive(
|
|
158
|
-
links,
|
|
159
|
-
s.business_concept_id,
|
|
160
|
-
businessConceptId
|
|
161
|
-
)}
|
|
162
|
-
/>
|
|
163
|
-
))}
|
|
164
|
-
{selectedConcept && selectedConceptIsFiltered && (
|
|
165
|
-
<ConceptSelectorRow concept={selectedConcept} active />
|
|
166
|
-
)}
|
|
167
|
-
</Table.Body>
|
|
168
|
-
</Table>
|
|
169
|
-
) : (
|
|
170
|
-
<Message
|
|
171
|
-
header={<FormattedMessage id="concepts.search.results.empty" />}
|
|
172
|
-
/>
|
|
173
|
-
)}
|
|
204
|
+
<ConceptSelectorTable
|
|
205
|
+
concepts={concepts}
|
|
206
|
+
showTitle={showTitle}
|
|
207
|
+
downloading={downloading}
|
|
208
|
+
selectedConcept={selectedConcept}
|
|
209
|
+
handleConceptSelected={handleConceptSelected}
|
|
210
|
+
links={links}
|
|
211
|
+
businessConceptId={businessConceptId}
|
|
212
|
+
triggerDownload={triggerDownload}
|
|
213
|
+
downloadLinks={conceptsActions?.downloadLinks}
|
|
214
|
+
searchParams={searchParams}
|
|
215
|
+
/>
|
|
174
216
|
<ConceptsPagination size="small" />
|
|
175
217
|
</Segment>
|
|
176
218
|
</>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { waitFor } from "@testing-library/react";
|
|
1
|
+
import { waitFor, within } from "@testing-library/react";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
import { render, waitForLoad } from "@truedat/test/render";
|
|
4
4
|
import ConceptSelector from "../ConceptSelector";
|
|
5
|
+
import { ConceptSelectorTable } from "../ConceptSelector";
|
|
5
6
|
|
|
6
7
|
const concept = {
|
|
7
8
|
id: 1,
|
|
@@ -116,3 +117,91 @@ describe("<ConceptSelector />", () => {
|
|
|
116
117
|
);
|
|
117
118
|
});
|
|
118
119
|
});
|
|
120
|
+
|
|
121
|
+
describe("<ConceptSelectorTable />", () => {
|
|
122
|
+
jest.mock("react-intl", () => ({
|
|
123
|
+
FormattedMessage: ({ id }) => <span data-testid="fm">{id}</span>,
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
const concept = {
|
|
127
|
+
id: 1,
|
|
128
|
+
name: "foo",
|
|
129
|
+
domain: { id: 1, name: "bar" },
|
|
130
|
+
status: "draft",
|
|
131
|
+
business_concept_id: 1,
|
|
132
|
+
last_change_at: "2020-01-01T00:00:00.000Z",
|
|
133
|
+
similarity: 0.5,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const links = [
|
|
137
|
+
{
|
|
138
|
+
target_id: 1,
|
|
139
|
+
target_type: "business_concept",
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
const selectedConcept = { id: 2 };
|
|
144
|
+
|
|
145
|
+
const extraColumns = [
|
|
146
|
+
{
|
|
147
|
+
name: "similarity",
|
|
148
|
+
fieldSelector: (concept) => concept.similarity,
|
|
149
|
+
fieldDecorator: (similarity) => (
|
|
150
|
+
<span data-testid="similarity">{similarity}</span>
|
|
151
|
+
),
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
const props = {
|
|
156
|
+
businessConceptId: 1,
|
|
157
|
+
concepts: [concept],
|
|
158
|
+
selectedConcept,
|
|
159
|
+
handleConceptSelected: jest.fn(),
|
|
160
|
+
extraColumns,
|
|
161
|
+
links,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
beforeEach(() => {
|
|
165
|
+
jest.clearAllMocks();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("matches the latest snapshot", async () => {
|
|
169
|
+
const rendered = render(<ConceptSelectorTable {...props} />);
|
|
170
|
+
await waitForLoad(rendered);
|
|
171
|
+
expect(rendered.container).toMatchSnapshot();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("renders Message when there are no concepts and no selectedConcept", () => {
|
|
175
|
+
const rendered = render(
|
|
176
|
+
<ConceptSelectorTable
|
|
177
|
+
{...{ ...props, concepts: [], selectedConcept: null }}
|
|
178
|
+
/>
|
|
179
|
+
);
|
|
180
|
+
// It should render the Message with the empty results header id
|
|
181
|
+
expect(
|
|
182
|
+
rendered.getByText("concepts.search.results.empty")
|
|
183
|
+
).toBeInTheDocument();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("renders Table when there are concepts", async () => {
|
|
187
|
+
const rendered = render(
|
|
188
|
+
<ConceptSelectorTable {...{ ...props, selectedConcept: { id: 1 } }} />
|
|
189
|
+
);
|
|
190
|
+
await waitForLoad(rendered);
|
|
191
|
+
|
|
192
|
+
// Header labels via FormattedMessage
|
|
193
|
+
expect(rendered.getByText("concepts.props.name")).toBeInTheDocument();
|
|
194
|
+
expect(rendered.getByText("concepts.props.domain")).toBeInTheDocument();
|
|
195
|
+
expect(rendered.getByText("concepts.props.status")).toBeInTheDocument();
|
|
196
|
+
expect(rendered.getByText("concepts.props.similarity")).toBeInTheDocument();
|
|
197
|
+
// Rows exist
|
|
198
|
+
const rowgroups = rendered.getAllByRole("rowgroup");
|
|
199
|
+
const tbody = rowgroups[1];
|
|
200
|
+
const rows = within(tbody).getAllByRole("row");
|
|
201
|
+
expect(rows).toHaveLength(1);
|
|
202
|
+
|
|
203
|
+
expect(rows[0]).toHaveTextContent("bar");
|
|
204
|
+
expect(rows[0]).toHaveTextContent("draft");
|
|
205
|
+
expect(rows[0]).toHaveTextContent("0.5");
|
|
206
|
+
});
|
|
207
|
+
});
|
package/src/concepts/relations/components/__tests__/__snapshots__/ConceptSelector.spec.js.snap
CHANGED
|
@@ -201,3 +201,102 @@ exports[`<ConceptSelector /> matches the latest snapshot 1`] = `
|
|
|
201
201
|
</div>
|
|
202
202
|
</div>
|
|
203
203
|
`;
|
|
204
|
+
|
|
205
|
+
exports[`<ConceptSelectorTable /> matches the latest snapshot 1`] = `
|
|
206
|
+
<div>
|
|
207
|
+
<table
|
|
208
|
+
class="ui small selectable table"
|
|
209
|
+
>
|
|
210
|
+
<thead
|
|
211
|
+
class=""
|
|
212
|
+
>
|
|
213
|
+
<tr
|
|
214
|
+
class=""
|
|
215
|
+
>
|
|
216
|
+
<th
|
|
217
|
+
class=""
|
|
218
|
+
>
|
|
219
|
+
concepts.props.name
|
|
220
|
+
</th>
|
|
221
|
+
<th
|
|
222
|
+
class=""
|
|
223
|
+
>
|
|
224
|
+
concepts.props.domain
|
|
225
|
+
</th>
|
|
226
|
+
<th
|
|
227
|
+
class=""
|
|
228
|
+
>
|
|
229
|
+
concepts.props.status
|
|
230
|
+
</th>
|
|
231
|
+
<th
|
|
232
|
+
class=""
|
|
233
|
+
>
|
|
234
|
+
concepts.props.similarity
|
|
235
|
+
</th>
|
|
236
|
+
</tr>
|
|
237
|
+
</thead>
|
|
238
|
+
<tbody
|
|
239
|
+
class=""
|
|
240
|
+
>
|
|
241
|
+
<tr
|
|
242
|
+
class="disabled"
|
|
243
|
+
>
|
|
244
|
+
<td
|
|
245
|
+
class=""
|
|
246
|
+
>
|
|
247
|
+
foo
|
|
248
|
+
</td>
|
|
249
|
+
<td
|
|
250
|
+
class=""
|
|
251
|
+
>
|
|
252
|
+
bar
|
|
253
|
+
</td>
|
|
254
|
+
<td
|
|
255
|
+
class=""
|
|
256
|
+
>
|
|
257
|
+
<div
|
|
258
|
+
class="ui olive label"
|
|
259
|
+
>
|
|
260
|
+
draft
|
|
261
|
+
</div>
|
|
262
|
+
</td>
|
|
263
|
+
<td
|
|
264
|
+
class=""
|
|
265
|
+
>
|
|
266
|
+
<span
|
|
267
|
+
data-testid="similarity"
|
|
268
|
+
>
|
|
269
|
+
0.5
|
|
270
|
+
</span>
|
|
271
|
+
</td>
|
|
272
|
+
</tr>
|
|
273
|
+
<tr
|
|
274
|
+
class="active"
|
|
275
|
+
>
|
|
276
|
+
<td
|
|
277
|
+
class=""
|
|
278
|
+
/>
|
|
279
|
+
<td
|
|
280
|
+
class=""
|
|
281
|
+
/>
|
|
282
|
+
<td
|
|
283
|
+
class=""
|
|
284
|
+
>
|
|
285
|
+
<div
|
|
286
|
+
class="ui label"
|
|
287
|
+
>
|
|
288
|
+
concepts.status.undefined
|
|
289
|
+
</div>
|
|
290
|
+
</td>
|
|
291
|
+
<td
|
|
292
|
+
class=""
|
|
293
|
+
>
|
|
294
|
+
<span
|
|
295
|
+
data-testid="similarity"
|
|
296
|
+
/>
|
|
297
|
+
</td>
|
|
298
|
+
</tr>
|
|
299
|
+
</tbody>
|
|
300
|
+
</table>
|
|
301
|
+
</div>
|
|
302
|
+
`;
|