@truedat/lm 7.13.5 → 7.13.6
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/lm",
|
|
3
|
-
"version": "7.13.
|
|
3
|
+
"version": "7.13.6",
|
|
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": "7.13.
|
|
51
|
+
"@truedat/test": "7.13.6",
|
|
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": "5c369d5a90a39be6362493db19e597a91d5a907d"
|
|
86
86
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
3
4
|
import { Table, Header, Grid } from "semantic-ui-react";
|
|
4
5
|
import { FormattedMessage } from "react-intl";
|
|
5
6
|
import { columnDecorator, columnPredicate } from "@truedat/core/services";
|
|
7
|
+
import { useDataStructureSearch } from "@truedat/dd/hooks/useStructures";
|
|
6
8
|
|
|
7
9
|
const Row = ({ columns, link }) => (
|
|
8
10
|
<Table.Row>
|
|
@@ -23,10 +25,61 @@ Row.propTypes = {
|
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
export const Links = ({ tag, sourceType, targetType, columns, links }) => {
|
|
28
|
+
const [enrichedLinks, setEnrichedLinks] = useState(links);
|
|
29
|
+
const { trigger } = useDataStructureSearch();
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const fetchStructureNotes = async () => {
|
|
33
|
+
const structureIds = _.flow(
|
|
34
|
+
_.filter(_.propEq("resource_type", "data_structure")),
|
|
35
|
+
_.map(({ resource_id }) => parseInt(resource_id)),
|
|
36
|
+
_.uniq
|
|
37
|
+
)(links);
|
|
38
|
+
|
|
39
|
+
if (_.isEmpty(structureIds)) {
|
|
40
|
+
setEnrichedLinks(links);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const response = await trigger({
|
|
46
|
+
must: { ids: structureIds },
|
|
47
|
+
page: 0,
|
|
48
|
+
size: structureIds.length,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const structuresMap = _.flow(
|
|
52
|
+
_.getOr([], "data.data"),
|
|
53
|
+
_.keyBy("id")
|
|
54
|
+
)(response);
|
|
55
|
+
|
|
56
|
+
const enriched = _.map((link) => {
|
|
57
|
+
if (link.resource_type === "data_structure") {
|
|
58
|
+
const structure = structuresMap[link.resource_id];
|
|
59
|
+
return {
|
|
60
|
+
...link,
|
|
61
|
+
note: structure?.note || null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return link;
|
|
65
|
+
})(links);
|
|
66
|
+
|
|
67
|
+
setEnrichedLinks(enriched);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
setEnrichedLinks(links);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
fetchStructureNotes();
|
|
74
|
+
}, [links, trigger]);
|
|
75
|
+
|
|
26
76
|
const linksData = _.orderBy(["business_concept_id", "name"])(["desc", "asc"])(
|
|
27
|
-
|
|
77
|
+
enrichedLinks
|
|
28
78
|
);
|
|
29
|
-
|
|
79
|
+
|
|
80
|
+
const filteredColumns = filterColumns(columns, enrichedLinks);
|
|
81
|
+
|
|
82
|
+
const headerRow = filteredColumns.map(({ name }, key) => (
|
|
30
83
|
<Table.HeaderCell
|
|
31
84
|
key={key}
|
|
32
85
|
content={
|
|
@@ -35,7 +88,7 @@ export const Links = ({ tag, sourceType, targetType, columns, links }) => {
|
|
|
35
88
|
/>
|
|
36
89
|
));
|
|
37
90
|
const renderBodyRow = (link, i) => (
|
|
38
|
-
<Row key={i} columns={
|
|
91
|
+
<Row key={i} columns={filteredColumns} link={link} />
|
|
39
92
|
);
|
|
40
93
|
return (
|
|
41
94
|
<>
|
|
@@ -82,7 +135,7 @@ export const LinksPane = ({ groups, sourceType, targetType, linksActions }) => (
|
|
|
82
135
|
tag={tag}
|
|
83
136
|
sourceType={sourceType}
|
|
84
137
|
targetType={targetType}
|
|
85
|
-
columns={
|
|
138
|
+
columns={columns}
|
|
86
139
|
links={links}
|
|
87
140
|
/>
|
|
88
141
|
</Grid.Column>
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import { waitFor } from "@testing-library/react";
|
|
3
|
+
import { render, waitForLoad } from "@truedat/test/render";
|
|
4
|
+
import { useDataStructureSearch } from "@truedat/dd/hooks/useStructures";
|
|
2
5
|
import { LinksPane } from "../LinksPane";
|
|
3
6
|
|
|
7
|
+
jest.mock("@truedat/dd/hooks/useStructures", () => ({
|
|
8
|
+
useDataStructureSearch: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
4
11
|
describe("<LinksPane />", () => {
|
|
5
12
|
const renderOpts = {
|
|
6
13
|
messages: {
|
|
@@ -14,6 +21,16 @@ describe("<LinksPane />", () => {
|
|
|
14
21
|
},
|
|
15
22
|
};
|
|
16
23
|
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
useDataStructureSearch.mockReturnValue({
|
|
26
|
+
trigger: jest.fn().mockResolvedValue({
|
|
27
|
+
data: {
|
|
28
|
+
data: [],
|
|
29
|
+
},
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
17
34
|
const columns = [
|
|
18
35
|
{
|
|
19
36
|
name: "column",
|
|
@@ -27,8 +44,135 @@ describe("<LinksPane />", () => {
|
|
|
27
44
|
targetType: "concept",
|
|
28
45
|
linksActions: <button>action</button>,
|
|
29
46
|
};
|
|
30
|
-
it("matches the latest snapshot", () => {
|
|
31
|
-
const
|
|
32
|
-
|
|
47
|
+
it("matches the latest snapshot", async () => {
|
|
48
|
+
const rendered = render(<LinksPane {...props} />, renderOpts);
|
|
49
|
+
await waitForLoad(rendered);
|
|
50
|
+
expect(rendered.container).toMatchSnapshot();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("fetches structure notes for data_structure links", async () => {
|
|
54
|
+
const mockTrigger = jest.fn().mockResolvedValue({
|
|
55
|
+
data: {
|
|
56
|
+
data: [
|
|
57
|
+
{ id: 123, note: { status: "published", content: "Test note" } },
|
|
58
|
+
{ id: 456, note: { status: "draft", content: "Another note" } },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
useDataStructureSearch.mockReturnValue({
|
|
64
|
+
trigger: mockTrigger,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const structureLinks = [
|
|
68
|
+
{ id: 1, resource_type: "data_structure", resource_id: 123, name: "Structure 1" },
|
|
69
|
+
{ id: 2, resource_type: "data_structure", resource_id: 456, name: "Structure 2" },
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const propsWithStructures = {
|
|
73
|
+
...props,
|
|
74
|
+
groups: [[columns, ["tag", structureLinks]]],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const rendered = render(<LinksPane {...propsWithStructures} />, renderOpts);
|
|
78
|
+
await waitForLoad(rendered);
|
|
79
|
+
|
|
80
|
+
expect(mockTrigger).toHaveBeenCalledWith({
|
|
81
|
+
must: { ids: [123, 456] },
|
|
82
|
+
page: 0,
|
|
83
|
+
size: 2,
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("handles empty structure links", async () => {
|
|
88
|
+
const mockTrigger = jest.fn();
|
|
89
|
+
|
|
90
|
+
useDataStructureSearch.mockReturnValue({
|
|
91
|
+
trigger: mockTrigger,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const nonStructureLinks = [
|
|
95
|
+
{ id: 1, resource_type: "other", resource_id: 123, name: "Other 1" },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const propsWithOther = {
|
|
99
|
+
...props,
|
|
100
|
+
groups: [[columns, ["tag", nonStructureLinks]]],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const rendered = render(<LinksPane {...propsWithOther} />, renderOpts);
|
|
104
|
+
await waitForLoad(rendered);
|
|
105
|
+
|
|
106
|
+
expect(mockTrigger).not.toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should display note columns only when note values are present for that column", async () => {
|
|
110
|
+
const mockTrigger = jest.fn().mockResolvedValue({
|
|
111
|
+
data: {
|
|
112
|
+
data: [
|
|
113
|
+
{ id: 123, name: "Structure 1", note: { foo: "baz" } },
|
|
114
|
+
{ id: 456, name: "Structure 2", note: {} },
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
useDataStructureSearch.mockReturnValue({
|
|
120
|
+
trigger: mockTrigger,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const structureLinksMixedNotes = [
|
|
124
|
+
{ id: 1, resource_type: "data_structure", resource_id: 123, name: "Structure 1" },
|
|
125
|
+
{ id: 2, resource_type: "data_structure", resource_id: 456, name: "Structure 2" },
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
const columnsWithNotes = [
|
|
129
|
+
...columns,
|
|
130
|
+
{
|
|
131
|
+
name: "note.foo",
|
|
132
|
+
header: "note.foo",
|
|
133
|
+
fieldSelector: _.path(["note", "foo"]),
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "note.bar",
|
|
137
|
+
header: "note.bar",
|
|
138
|
+
fieldSelector: _.path(["note", "bar"]),
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
const renderOptsWithNotes = {
|
|
143
|
+
messages: {
|
|
144
|
+
en: {
|
|
145
|
+
...renderOpts.messages.en,
|
|
146
|
+
"concept.note.foo": "Foo",
|
|
147
|
+
"concept.note.bar": "Bar",
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const propsWithMixedNotes = {
|
|
153
|
+
...props,
|
|
154
|
+
groups: [[columnsWithNotes, ["tag", structureLinksMixedNotes]]],
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const rendered = render(<LinksPane {...propsWithMixedNotes} />, renderOptsWithNotes);
|
|
158
|
+
await waitForLoad(rendered);
|
|
159
|
+
|
|
160
|
+
await waitFor(() => {
|
|
161
|
+
expect(rendered.queryByText(/foo/i)).toBeInTheDocument();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(mockTrigger).toHaveBeenCalledWith({
|
|
165
|
+
must: { ids: [123, 456] },
|
|
166
|
+
page: 0,
|
|
167
|
+
size: 2,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const fooCells = rendered.queryAllByText(/baz/i);
|
|
171
|
+
expect(fooCells.length).toBeGreaterThan(0);
|
|
172
|
+
|
|
173
|
+
const barHeader = rendered.queryByText(/^bar$/i);
|
|
174
|
+
expect(barHeader).not.toBeInTheDocument();
|
|
175
|
+
|
|
176
|
+
expect(rendered.container).toMatchSnapshot();
|
|
33
177
|
});
|
|
34
178
|
});
|
|
@@ -50,3 +50,73 @@ exports[`<LinksPane /> matches the latest snapshot 1`] = `
|
|
|
50
50
|
</div>
|
|
51
51
|
</div>
|
|
52
52
|
`;
|
|
53
|
+
|
|
54
|
+
exports[`<LinksPane /> should display note columns only when note values are present for that column 1`] = `
|
|
55
|
+
<div>
|
|
56
|
+
<div
|
|
57
|
+
class="ui grid"
|
|
58
|
+
>
|
|
59
|
+
<div
|
|
60
|
+
class="row"
|
|
61
|
+
>
|
|
62
|
+
<div
|
|
63
|
+
class="column"
|
|
64
|
+
>
|
|
65
|
+
<button>
|
|
66
|
+
action
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
<div
|
|
71
|
+
class="row"
|
|
72
|
+
>
|
|
73
|
+
<div
|
|
74
|
+
class="column"
|
|
75
|
+
>
|
|
76
|
+
<h3
|
|
77
|
+
class="ui header"
|
|
78
|
+
>
|
|
79
|
+
Master title
|
|
80
|
+
</h3>
|
|
81
|
+
<table
|
|
82
|
+
class="ui table"
|
|
83
|
+
>
|
|
84
|
+
<thead
|
|
85
|
+
class=""
|
|
86
|
+
>
|
|
87
|
+
<tr
|
|
88
|
+
class=""
|
|
89
|
+
>
|
|
90
|
+
<th
|
|
91
|
+
class=""
|
|
92
|
+
>
|
|
93
|
+
Foo
|
|
94
|
+
</th>
|
|
95
|
+
</tr>
|
|
96
|
+
</thead>
|
|
97
|
+
<tbody
|
|
98
|
+
class=""
|
|
99
|
+
>
|
|
100
|
+
<tr
|
|
101
|
+
class=""
|
|
102
|
+
>
|
|
103
|
+
<td
|
|
104
|
+
class=""
|
|
105
|
+
>
|
|
106
|
+
baz
|
|
107
|
+
</td>
|
|
108
|
+
</tr>
|
|
109
|
+
<tr
|
|
110
|
+
class=""
|
|
111
|
+
>
|
|
112
|
+
<td
|
|
113
|
+
class=""
|
|
114
|
+
/>
|
|
115
|
+
</tr>
|
|
116
|
+
</tbody>
|
|
117
|
+
</table>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
`;
|