@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.5",
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.5",
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": "a82fdfd07c75632206f6639dd7c3b3072a3a9379"
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
- links
77
+ enrichedLinks
28
78
  );
29
- const headerRow = columns.map(({ name }, key) => (
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={columns} link={link} />
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={filterColumns(columns, links)}
138
+ columns={columns}
86
139
  links={links}
87
140
  />
88
141
  </Grid.Column>
@@ -1,6 +1,13 @@
1
- import { render } from "@truedat/test/render";
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 { container } = render(<LinksPane {...props} />, renderOpts);
32
- expect(container).toMatchSnapshot();
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
+ `;