@truedat/dd 7.5.12 → 7.5.13
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/StructureSuggestions.js +88 -0
- package/src/components/StructuresSearchResults.js +6 -2
- package/src/components/StructuresTable.js +10 -8
- package/src/components/__tests__/StructureSuggestions.spec.js +93 -0
- package/src/hooks/useStructures.js +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/dd",
|
|
3
|
-
"version": "7.5.
|
|
3
|
+
"version": "7.5.13",
|
|
4
4
|
"description": "Truedat Web Data Dictionary",
|
|
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.5.
|
|
51
|
+
"@truedat/test": "7.5.13",
|
|
52
52
|
"identity-obj-proxy": "^3.0.0",
|
|
53
53
|
"jest": "^29.7.0",
|
|
54
54
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
"svg-pan-zoom": "^3.6.2",
|
|
85
85
|
"swr": "^2.3.3"
|
|
86
86
|
},
|
|
87
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "83de799e59c0d13d4d52812070d232d24f0b5f82"
|
|
88
88
|
}
|
package/src/api.js
CHANGED
|
@@ -7,6 +7,7 @@ const API_DATA_STRUCTURES_EDITABLE_CSV = "/api/data_structures/editable_csv";
|
|
|
7
7
|
const API_DATA_STRUCTURES_XLSX_DOWNLOAD = "/api/data_structures/xlsx/download";
|
|
8
8
|
const API_DATA_STRUCTURES_XLSX_UPLOAD = "/api/data_structures/xlsx/upload";
|
|
9
9
|
const API_DATA_STRUCTURES_SEARCH = "/api/data_structures/search";
|
|
10
|
+
const API_DATA_STRUCTURES_SUGGESTIONS = "/api/data_structures/suggestions";
|
|
10
11
|
const API_DATA_STRUCTURE_FILTERS_SEARCH = "/api/data_structure_filters/search";
|
|
11
12
|
const API_DATA_STRUCTURE_VERSION = "/api/data_structures/:id/versions/:version";
|
|
12
13
|
const API_GRANT_FILTERS_SEARCH = "/api/grant_filters/search";
|
|
@@ -63,6 +64,7 @@ export {
|
|
|
63
64
|
API_DATA_STRUCTURES_XLSX_DOWNLOAD,
|
|
64
65
|
API_DATA_STRUCTURES_XLSX_UPLOAD,
|
|
65
66
|
API_DATA_STRUCTURES_SEARCH,
|
|
67
|
+
API_DATA_STRUCTURES_SUGGESTIONS,
|
|
66
68
|
API_DATA_STRUCTURE_FILTERS_SEARCH,
|
|
67
69
|
API_DATA_STRUCTURE_VERSION,
|
|
68
70
|
API_PROFILE_EXECUTION,
|
|
@@ -0,0 +1,88 @@
|
|
|
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 { useDataStructureSuggestions } from "../hooks/useStructures";
|
|
9
|
+
import { defaultColumnsForStructureSelector } from "../selectors";
|
|
10
|
+
import StructuresSearchResults from "./StructuresSearchResults";
|
|
11
|
+
|
|
12
|
+
const SimilarityColumn = (similarity) => {
|
|
13
|
+
const { formatMessage } = useIntl();
|
|
14
|
+
return (
|
|
15
|
+
<Popup
|
|
16
|
+
content={formatMessage({ id: "structure.similarity.cosine.popup" })}
|
|
17
|
+
trigger={<span>{similarity.toFixed(3)}</span>}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const similarityColumnDefinition = {
|
|
23
|
+
name: "similarity",
|
|
24
|
+
fieldSelector: _.prop("similarity"),
|
|
25
|
+
fieldDecorator: SimilarityColumn,
|
|
26
|
+
width: 1,
|
|
27
|
+
};
|
|
28
|
+
const suggestionColumns = (state) =>
|
|
29
|
+
_.concat(defaultColumnsForStructureSelector(state))([
|
|
30
|
+
similarityColumnDefinition,
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
export const StructureSuggestions = ({
|
|
34
|
+
selectedStructure,
|
|
35
|
+
handleSelectedStructure,
|
|
36
|
+
selectable,
|
|
37
|
+
}) => {
|
|
38
|
+
const { business_concept_id: id, id: version } = useParams();
|
|
39
|
+
const { conceptLinks, columns } = useSelector((state) => ({
|
|
40
|
+
conceptLinks: state.conceptLinks,
|
|
41
|
+
columns: suggestionColumns(state),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
trigger: fetchSuggestions,
|
|
46
|
+
data,
|
|
47
|
+
isMutating,
|
|
48
|
+
} = useDataStructureSuggestions();
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (id && version && conceptLinks) {
|
|
52
|
+
fetchSuggestions({
|
|
53
|
+
resource: {
|
|
54
|
+
id,
|
|
55
|
+
version,
|
|
56
|
+
type: "concepts",
|
|
57
|
+
links: _.filter(
|
|
58
|
+
({ resource_type }) => resource_type == "data_structure"
|
|
59
|
+
)(conceptLinks),
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}, [id, version, conceptLinks]);
|
|
64
|
+
|
|
65
|
+
const structures = data?.data?.data || [];
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<StructuresSearchResults
|
|
69
|
+
loading={isMutating}
|
|
70
|
+
columns={columns}
|
|
71
|
+
size="small"
|
|
72
|
+
structures={structures}
|
|
73
|
+
selectedStructure={selectedStructure}
|
|
74
|
+
onSelect={handleSelectedStructure}
|
|
75
|
+
labelResults={false}
|
|
76
|
+
pagination={false}
|
|
77
|
+
selectable={selectable}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
StructureSuggestions.propTypes = {
|
|
83
|
+
handleSelectedStructure: PropTypes.func,
|
|
84
|
+
selectedStructure: PropTypes.object,
|
|
85
|
+
selectable: PropTypes.bool,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export default StructureSuggestions;
|
|
@@ -18,6 +18,8 @@ export const StructuresSearchResults = ({
|
|
|
18
18
|
links,
|
|
19
19
|
placeToTop,
|
|
20
20
|
selectable,
|
|
21
|
+
labelResults = true,
|
|
22
|
+
pagination = true,
|
|
21
23
|
}) => (
|
|
22
24
|
<Dimmer.Dimmable dimmed={loading} className="structure-table-overflow">
|
|
23
25
|
{loading ? (
|
|
@@ -27,7 +29,7 @@ export const StructuresSearchResults = ({
|
|
|
27
29
|
) : null}
|
|
28
30
|
{!_.isEmpty(structures) || !_.isEmpty(selectedStructure) ? (
|
|
29
31
|
<>
|
|
30
|
-
<StructuresLabelResults />
|
|
32
|
+
{labelResults && <StructuresLabelResults />}
|
|
31
33
|
<StructuresTable
|
|
32
34
|
columns={columns}
|
|
33
35
|
grantable={grantable}
|
|
@@ -39,7 +41,7 @@ export const StructuresSearchResults = ({
|
|
|
39
41
|
placeToTop={placeToTop}
|
|
40
42
|
selectable={selectable}
|
|
41
43
|
/>
|
|
42
|
-
<StructuresPagination size={size} />
|
|
44
|
+
{pagination && <StructuresPagination size={size} />}
|
|
43
45
|
</>
|
|
44
46
|
) : (
|
|
45
47
|
<Message icon info>
|
|
@@ -64,8 +66,10 @@ StructuresSearchResults.propTypes = {
|
|
|
64
66
|
onSelect: PropTypes.func,
|
|
65
67
|
structures: PropTypes.array,
|
|
66
68
|
selectedStructure: PropTypes.object,
|
|
69
|
+
labelResults: PropTypes.bool,
|
|
67
70
|
loading: PropTypes.bool,
|
|
68
71
|
links: PropTypes.array,
|
|
72
|
+
pagination: PropTypes.bool,
|
|
69
73
|
placeToTop: PropTypes.bool,
|
|
70
74
|
selectable: PropTypes.bool,
|
|
71
75
|
};
|
|
@@ -25,20 +25,21 @@ export const StructuresTable = ({
|
|
|
25
25
|
links = [],
|
|
26
26
|
placeToTop = true,
|
|
27
27
|
selectable = false,
|
|
28
|
+
structures: overwriteStructures,
|
|
28
29
|
}) => {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
const searchContext = useSearchContext();
|
|
31
|
+
|
|
32
|
+
const searchData = searchContext?.searchData;
|
|
33
|
+
const sortColumn = searchContext?.sortColumn;
|
|
34
|
+
const sortDirection = searchContext?.sortDirection;
|
|
35
|
+
const defaultFilters = searchContext?.defaultFilters;
|
|
36
|
+
const handleSortSelection = searchContext?.handleSortSelection;
|
|
36
37
|
|
|
37
38
|
const columns = useSelector((state) =>
|
|
38
39
|
structureColumnsSelector(state, defaultFilters, overwriteColumns)
|
|
39
40
|
);
|
|
40
41
|
|
|
41
|
-
const structures = searchData?.data || [];
|
|
42
|
+
const structures = overwriteStructures || searchData?.data || [];
|
|
42
43
|
|
|
43
44
|
if (_.isEmpty(columns)) {
|
|
44
45
|
return null;
|
|
@@ -103,6 +104,7 @@ StructuresTable.propTypes = {
|
|
|
103
104
|
size: PropTypes.string,
|
|
104
105
|
links: PropTypes.array,
|
|
105
106
|
placeToTop: PropTypes.bool,
|
|
107
|
+
structures: PropTypes.arrayOf(PropTypes.object),
|
|
106
108
|
};
|
|
107
109
|
|
|
108
110
|
export default StructuresTable;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { useSelector } from "react-redux";
|
|
4
|
+
import { useParams } from "react-router";
|
|
5
|
+
import { useDataStructureSuggestions } from "../../hooks/useStructures";
|
|
6
|
+
import StructureSuggestions from "../StructureSuggestions";
|
|
7
|
+
import StructuresSearchResults from "../StructuresSearchResults";
|
|
8
|
+
import * as selectors from "../../selectors";
|
|
9
|
+
|
|
10
|
+
jest.mock("react-router", () => ({
|
|
11
|
+
useParams: jest.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
jest.mock("react-redux", () => ({
|
|
15
|
+
useSelector: jest.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock("../../hooks/useStructures", () => ({
|
|
19
|
+
useDataStructureSuggestions: jest.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock("../StructuresSearchResults", () => jest.fn(() => <div>MockSearchResults</div>));
|
|
23
|
+
|
|
24
|
+
jest.mock("../../selectors", () => ({
|
|
25
|
+
defaultColumnsForStructureSelector: jest.fn(),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
describe("StructureSuggestions", () => {
|
|
29
|
+
const mockFetchSuggestions = jest.fn();
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
jest.clearAllMocks();
|
|
33
|
+
|
|
34
|
+
selectors.defaultColumnsForStructureSelector.mockReturnValue([
|
|
35
|
+
{ name: "name", fieldSelector: (x) => x.name },
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
useParams.mockReturnValue({ business_concept_id: "123", id: "456" });
|
|
39
|
+
|
|
40
|
+
useSelector.mockImplementation((selectorFn) =>
|
|
41
|
+
selectorFn({
|
|
42
|
+
conceptLinks: [
|
|
43
|
+
{ resource_type: "data_structure", id: "ds1" },
|
|
44
|
+
{ resource_type: "other", id: "not-ds" },
|
|
45
|
+
],
|
|
46
|
+
// mock of defaultColumnsForStructureSelector + similarity column
|
|
47
|
+
columns: [
|
|
48
|
+
{ name: "name", fieldSelector: (x) => x.name },
|
|
49
|
+
{ name: "similarity", fieldSelector: (x) => x.similarity },
|
|
50
|
+
],
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
useDataStructureSuggestions.mockReturnValue({
|
|
55
|
+
trigger: mockFetchSuggestions,
|
|
56
|
+
data: {
|
|
57
|
+
data: {
|
|
58
|
+
data: [
|
|
59
|
+
{ id: "structure1", similarity: 0.89 },
|
|
60
|
+
{ id: "structure2", similarity: 0.77 },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
isMutating: false,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("calls fetchSuggestions on mount with correct parameters", () => {
|
|
69
|
+
render(<StructureSuggestions selectedStructure={null} handleSelectedStructure={jest.fn()} selectable={true} />);
|
|
70
|
+
|
|
71
|
+
expect(mockFetchSuggestions).toHaveBeenCalledWith({
|
|
72
|
+
resource: {
|
|
73
|
+
id: "123",
|
|
74
|
+
version: "456",
|
|
75
|
+
type: "concepts",
|
|
76
|
+
links: [{ resource_type: "data_structure", id: "ds1" }],
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("renders StructuresSearchResults with props", () => {
|
|
82
|
+
render(<StructureSuggestions selectedStructure={{}} handleSelectedStructure={jest.fn()} selectable={false} />);
|
|
83
|
+
|
|
84
|
+
expect(screen.getByText("MockSearchResults")).toBeInTheDocument();
|
|
85
|
+
const props = StructuresSearchResults.mock.calls[0][0];
|
|
86
|
+
expect(props.structures).toEqual([
|
|
87
|
+
{ id: "structure1", similarity: 0.89 },
|
|
88
|
+
{ id: "structure2", similarity: 0.77 },
|
|
89
|
+
]);
|
|
90
|
+
expect(props.loading).toBe(false);
|
|
91
|
+
expect(props.selectable).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
API_SYSTEM_STRUCTURES,
|
|
14
14
|
API_DATA_STRUCTURES_SEARCH,
|
|
15
|
+
API_DATA_STRUCTURES_SUGGESTIONS,
|
|
15
16
|
API_DATA_STRUCTURE_FILTERS_SEARCH,
|
|
16
17
|
API_DATA_STRUCTURES_XLSX_DOWNLOAD,
|
|
17
18
|
API_DATA_STRUCTURES_XLSX_UPLOAD,
|
|
@@ -88,3 +89,9 @@ export const useDataStructureUpload = () => {
|
|
|
88
89
|
return apiJsonPost(url, arg, UPLOAD_JSON_OPTS);
|
|
89
90
|
});
|
|
90
91
|
};
|
|
92
|
+
|
|
93
|
+
export const useDataStructureSuggestions = () => {
|
|
94
|
+
return useSWRMutations(API_DATA_STRUCTURES_SUGGESTIONS, (url, { arg }) => {
|
|
95
|
+
return apiJsonPost(url, arg);
|
|
96
|
+
});
|
|
97
|
+
};
|