@truedat/dd 7.12.1 → 7.12.3

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/dd",
3
- "version": "7.12.1",
3
+ "version": "7.12.3",
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.12.1",
51
+ "@truedat/test": "7.12.3",
52
52
  "identity-obj-proxy": "^3.0.0",
53
53
  "jest": "^29.7.0",
54
54
  "redux-saga-test-plan": "^4.0.6"
@@ -83,5 +83,5 @@
83
83
  "svg-pan-zoom": "^3.6.2",
84
84
  "swr": "^2.3.3"
85
85
  },
86
- "gitHead": "b89751f9b306e3efd68dc2bbdab718e2fed62d07"
86
+ "gitHead": "8e0b1754cba0fa0420ba556147d08f2889e8c14b"
87
87
  }
package/src/api.js CHANGED
@@ -53,6 +53,8 @@ const API_STRUCTURES_UPLOAD_EVENTS =
53
53
  "/api/data_structures/bulk_update_template_content_events";
54
54
  const API_STRUCTURE_NOTE = "/api/data_structures/:data_structure_id/notes/:id";
55
55
  const API_STRUCTURE_NOTES = "/api/data_structures/:data_structure_id/notes";
56
+ const API_STRUCTURE_NOTES_XLSX_DOWNLOAD =
57
+ "/api/data_structures/:id/notes/xlsx/download";
56
58
 
57
59
  export {
58
60
  API_BUCKET_STRUCTURES,
@@ -104,6 +106,7 @@ export {
104
106
  API_STRUCTURES_UPLOAD_EVENTS,
105
107
  API_STRUCTURE_NOTE,
106
108
  API_STRUCTURE_NOTES,
109
+ API_STRUCTURE_NOTES_XLSX_DOWNLOAD,
107
110
  API_STRUCTURE_TO_STRUCTURE_LINKS,
108
111
  API_STRUCTURE_TO_STRUCTURE_LINK,
109
112
  };
@@ -0,0 +1,80 @@
1
+ import { useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import { Button, Checkbox, Modal, Icon } from "semantic-ui-react";
4
+ import { useIntl } from "react-intl";
5
+ import { STRUCTURE } from "@truedat/core/routes";
6
+ import { useDataStructureDownload } from "../hooks/useStructures";
7
+
8
+ export const StructureNotesDownloadButton = ({ structureId }) => {
9
+ const { formatMessage, locale } = useIntl();
10
+ const [open, setOpen] = useState(false);
11
+ const [includeChildren, setIncludeChildren] = useState(false);
12
+
13
+ const { trigger: triggerDownload, isMutating: isDownloading } =
14
+ useDataStructureDownload();
15
+
16
+ const handleDownload = () => {
17
+ triggerDownload({
18
+ data_structure_id: structureId,
19
+ include_children: includeChildren,
20
+ lang: locale,
21
+ structure_url_schema: window.location.origin + STRUCTURE,
22
+ download_type: "editable",
23
+ note_type: "published",
24
+ });
25
+ setOpen(false);
26
+ };
27
+
28
+ return (
29
+ <Modal
30
+ trigger={
31
+ <Button
32
+ icon="download"
33
+ onClick={() => setOpen(true)}
34
+ loading={isDownloading}
35
+ className="basic structureButton profile"
36
+ />
37
+ }
38
+ open={open}
39
+ onClose={() => setOpen(false)}
40
+ size="small"
41
+ >
42
+ <Modal.Header>
43
+ <Icon name="download" />
44
+ {formatMessage({ id: "structure.actions.downloadNotes.modal.title" })}
45
+ </Modal.Header>
46
+ <Modal.Content>
47
+ <Checkbox
48
+ className="bgOrange"
49
+ toggle
50
+ label={formatMessage({
51
+ id: "structure.actions.downloadNotes.includeChildren",
52
+ })}
53
+ onChange={() => setIncludeChildren(!includeChildren)}
54
+ checked={includeChildren}
55
+ style={{ top: "6px", marginRight: "7.5px" }}
56
+ />
57
+
58
+ </Modal.Content>
59
+ <Modal.Actions>
60
+ <Button onClick={() => setOpen(false)}>
61
+ {formatMessage({ id: "actions.cancel" })}
62
+ </Button>
63
+ <Button
64
+ primary
65
+ onClick={handleDownload}
66
+ loading={isDownloading}
67
+ >
68
+ <Icon name="download" />
69
+ {formatMessage({ id: "actions.download" })}
70
+ </Button>
71
+ </Modal.Actions>
72
+ </Modal>
73
+ );
74
+ };
75
+
76
+ StructureNotesDownloadButton.propTypes = {
77
+ structureId: PropTypes.string.isRequired,
78
+ };
79
+
80
+ export default StructureNotesDownloadButton;
@@ -10,6 +10,7 @@ import StructureProperties from "./StructureProperties";
10
10
  import StructureConfidentialButton from "./StructureConfidentialButton";
11
11
  import StructureDeleteButton from "./StructureDeleteButton";
12
12
  import StructureGrantSummaryButton from "./StructureGrantSummaryButton";
13
+ import StructureNotesDownloadButton from "./StructureNotesDownloadButton";
13
14
  import StructureProfileButton from "./StructureProfileButton";
14
15
  import StructureTags from "./StructureTags";
15
16
 
@@ -50,6 +51,7 @@ const dateProperties = (deleted_at, updatedAt) =>
50
51
  };
51
52
 
52
53
  export const StructureSummary = ({
54
+ id,
53
55
  class: structureClass,
54
56
  classes: structureClasses,
55
57
  name,
@@ -63,7 +65,7 @@ export const StructureSummary = ({
63
65
  const { formatMessage } = useIntl();
64
66
  const shareUrl = window.location.href;
65
67
  const shareTitle = formatMessage({ id: "structure.share.title" });
66
-
68
+
67
69
  return (
68
70
  <>
69
71
  <Grid>
@@ -94,6 +96,7 @@ export const StructureSummary = ({
94
96
  icon="structures"
95
97
  popupType="structures"
96
98
  />
99
+ <StructureNotesDownloadButton structureId={id} />
97
100
  {userPermissions.profile_permission &&
98
101
  structureClass === "field" ? (
99
102
  <StructureProfileButton />
@@ -112,6 +115,7 @@ export const StructureSummary = ({
112
115
  };
113
116
 
114
117
  StructureSummary.propTypes = {
118
+ id: PropTypes.string,
115
119
  class: PropTypes.string,
116
120
  classes: PropTypes.object,
117
121
  name: PropTypes.string,
@@ -19,7 +19,7 @@ import StructuresOptions from "./StructuresOptions";
19
19
  import StructuresSearchResults from "./StructuresSearchResults";
20
20
  import SystemCards from "./SystemCards";
21
21
  import CatalogCustomViewCards from "./CatalogCustomViewCards";
22
-
22
+
23
23
  const StructuresHeader = () => {
24
24
  const { formatMessage } = useIntl();
25
25
  const match = useMatch(BUCKETS_VIEW);
@@ -0,0 +1,260 @@
1
+ import React from "react";
2
+ import { render, screen, fireEvent, waitFor } from "@testing-library/react";
3
+ import { IntlProvider } from "react-intl";
4
+ import { StructureNotesDownloadButton } from "../StructureNotesDownloadButton";
5
+ import { useDataStructureDownload } from "../../hooks/useStructures";
6
+
7
+ // Mock del hook
8
+ jest.mock("../../hooks/useStructures", () => ({
9
+ useDataStructureDownload: jest.fn(),
10
+ }));
11
+
12
+ const messages = {
13
+ "structure.actions.downloadNotes": "Download notes",
14
+ "structure.actions.downloadNotes.modal.title": "Download Structure Notes",
15
+ "structure.actions.downloadNotes.includeChildren": "Include direct children notes",
16
+ "actions.cancel": "Cancel",
17
+ "actions.download": "Download",
18
+ };
19
+
20
+ const renderWithIntl = (component) => {
21
+ return render(
22
+ <IntlProvider locale="en" messages={messages}>
23
+ {component}
24
+ </IntlProvider>
25
+ );
26
+ };
27
+
28
+ describe("StructureNotesDownloadButton", () => {
29
+ const mockTrigger = jest.fn();
30
+ const mockStructureId = "123";
31
+
32
+ beforeEach(() => {
33
+ jest.clearAllMocks();
34
+ // Mock el hook correcto
35
+ useDataStructureDownload.mockReturnValue({
36
+ trigger: mockTrigger,
37
+ isMutating: false,
38
+ });
39
+ });
40
+
41
+ it("always renders download button", () => {
42
+ renderWithIntl(
43
+ <StructureNotesDownloadButton
44
+ structureId={mockStructureId}
45
+ />
46
+ );
47
+
48
+ const button = screen.getByRole("button");
49
+ expect(button).toBeInTheDocument();
50
+ expect(button).toHaveAttribute("class", expect.stringContaining("basic"));
51
+ });
52
+
53
+ it("opens modal when button is clicked", () => {
54
+ renderWithIntl(
55
+ <StructureNotesDownloadButton
56
+ structureId={mockStructureId}
57
+ />
58
+ );
59
+
60
+ const button = screen.getByRole("button");
61
+ fireEvent.click(button);
62
+
63
+ expect(
64
+ screen.getByText("Download Structure Notes")
65
+ ).toBeInTheDocument();
66
+ });
67
+
68
+ it("displays include children checkbox in modal", () => {
69
+ renderWithIntl(
70
+ <StructureNotesDownloadButton
71
+ structureId={mockStructureId}
72
+ />
73
+ );
74
+
75
+ const button = screen.getByRole("button");
76
+ fireEvent.click(button);
77
+
78
+ const includeChildrenLabel = screen.getByText("Include direct children notes");
79
+ expect(includeChildrenLabel).toBeInTheDocument();
80
+
81
+ const checkbox = screen.getByRole("checkbox");
82
+ expect(checkbox).toBeInTheDocument();
83
+ });
84
+
85
+ it("allows toggling include children checkbox", () => {
86
+ renderWithIntl(
87
+ <StructureNotesDownloadButton
88
+ structureId={mockStructureId}
89
+ />
90
+ );
91
+
92
+ const button = screen.getByRole("button");
93
+ fireEvent.click(button);
94
+
95
+ const checkbox = screen.getByRole("checkbox");
96
+
97
+ expect(checkbox).not.toBeChecked();
98
+
99
+ fireEvent.click(checkbox);
100
+ expect(checkbox).toBeChecked();
101
+
102
+ fireEvent.click(checkbox);
103
+ expect(checkbox).not.toBeChecked();
104
+ });
105
+
106
+ it("calls trigger with correct parameters when download button is clicked", async () => {
107
+ renderWithIntl(
108
+ <StructureNotesDownloadButton
109
+ structureId={mockStructureId}
110
+ />
111
+ );
112
+
113
+ const openButton = screen.getByRole("button");
114
+ fireEvent.click(openButton);
115
+
116
+ const checkbox = screen.getByRole("checkbox");
117
+ fireEvent.click(checkbox);
118
+ expect(checkbox).toBeChecked();
119
+
120
+ const downloadButton = screen.getByRole("button", { name: /download/i });
121
+ fireEvent.click(downloadButton);
122
+
123
+ await waitFor(() => {
124
+ expect(mockTrigger).toHaveBeenCalledWith({
125
+ data_structure_id: mockStructureId,
126
+ include_children: true,
127
+ lang: "en",
128
+ structure_url_schema: expect.stringContaining("http"),
129
+ download_type: "editable",
130
+ note_type: "published"
131
+ });
132
+ });
133
+ });
134
+
135
+ it("calls trigger with include_children false when checkbox is not selected", async () => {
136
+ renderWithIntl(
137
+ <StructureNotesDownloadButton
138
+ structureId={mockStructureId}
139
+ />
140
+ );
141
+
142
+ const openButton = screen.getByRole("button");
143
+ fireEvent.click(openButton);
144
+
145
+ const checkbox = screen.getByRole("checkbox");
146
+ expect(checkbox).not.toBeChecked();
147
+
148
+ const downloadButton = screen.getByRole("button", { name: /download/i });
149
+ fireEvent.click(downloadButton);
150
+
151
+ await waitFor(() => {
152
+ expect(mockTrigger).toHaveBeenCalledWith({
153
+ data_structure_id: mockStructureId,
154
+ include_children: false,
155
+ lang: "en",
156
+ structure_url_schema: expect.stringContaining("http"),
157
+ download_type: "editable",
158
+ note_type: "published"
159
+ });
160
+ });
161
+ });
162
+
163
+ it("closes modal after download", async () => {
164
+ renderWithIntl(
165
+ <StructureNotesDownloadButton
166
+ structureId={mockStructureId}
167
+ />
168
+ );
169
+
170
+ const openButton = screen.getByRole("button");
171
+ fireEvent.click(openButton);
172
+
173
+ const modalTitle = screen.getByText("Download Structure Notes");
174
+ expect(modalTitle).toBeInTheDocument();
175
+
176
+ const downloadButton = screen.getByRole("button", { name: /download/i });
177
+ fireEvent.click(downloadButton);
178
+
179
+ await waitFor(() => {
180
+ expect(
181
+ screen.queryByText("Download Structure Notes")
182
+ ).not.toBeInTheDocument();
183
+ });
184
+ });
185
+
186
+ it("shows loading state when downloading", () => {
187
+ useDataStructureDownload.mockReturnValue({
188
+ trigger: mockTrigger,
189
+ isMutating: true,
190
+ });
191
+
192
+ renderWithIntl(
193
+ <StructureNotesDownloadButton
194
+ structureId={mockStructureId}
195
+ />
196
+ );
197
+
198
+ const button = screen.getByRole("button");
199
+ expect(button).toHaveAttribute("class", expect.stringContaining("loading"));
200
+ });
201
+
202
+ it("closes modal when cancel button is clicked", () => {
203
+ renderWithIntl(
204
+ <StructureNotesDownloadButton
205
+ structureId={mockStructureId}
206
+ />
207
+ );
208
+
209
+ const openButton = screen.getByRole("button");
210
+ fireEvent.click(openButton);
211
+
212
+ const modalTitle = screen.getByText("Download Structure Notes");
213
+ expect(modalTitle).toBeInTheDocument();
214
+
215
+ const cancelButton = screen.getByRole("button", { name: /cancel/i });
216
+ fireEvent.click(cancelButton);
217
+
218
+ expect(
219
+ screen.queryByText("Download Structure Notes")
220
+ ).not.toBeInTheDocument();
221
+ });
222
+
223
+ it("handles window location origin correctly", async () => {
224
+ // Guardar y mockear window.location
225
+ const originalLocation = window.location;
226
+ Object.defineProperty(window, 'location', {
227
+ value: { origin: 'http://localhost' },
228
+ writable: true
229
+ });
230
+
231
+ renderWithIntl(
232
+ <StructureNotesDownloadButton
233
+ structureId={mockStructureId}
234
+ />
235
+ );
236
+
237
+ const openButton = screen.getByRole("button");
238
+ fireEvent.click(openButton);
239
+
240
+ const downloadButton = screen.getByRole("button", { name: /download/i });
241
+ fireEvent.click(downloadButton);
242
+
243
+ await waitFor(() => {
244
+ expect(mockTrigger).toHaveBeenCalledWith({
245
+ data_structure_id: mockStructureId,
246
+ include_children: false,
247
+ lang: "en",
248
+ structure_url_schema: "http://localhost/structures/:id",
249
+ download_type: "editable",
250
+ note_type: "published"
251
+ });
252
+ });
253
+
254
+ // Restaurar window.location
255
+ Object.defineProperty(window, 'location', {
256
+ value: originalLocation,
257
+ writable: true
258
+ });
259
+ });
260
+ });
@@ -52,6 +52,14 @@ exports[`<StructureSummary /> matches the latest snapshot 1`] = `
52
52
  class="share alternate icon"
53
53
  />
54
54
  </button>
55
+ <button
56
+ class="ui icon button basic structureButton profile"
57
+ >
58
+ <i
59
+ aria-hidden="true"
60
+ class="download icon"
61
+ />
62
+ </button>
55
63
  <button
56
64
  class="ui icon button basic"
57
65
  >
@@ -200,6 +208,14 @@ exports[`<StructureSummary /> matches the latest snapshot with alias 1`] = `
200
208
  class="share alternate icon"
201
209
  />
202
210
  </button>
211
+ <button
212
+ class="ui icon button basic structureButton profile"
213
+ >
214
+ <i
215
+ aria-hidden="true"
216
+ class="download icon"
217
+ />
218
+ </button>
203
219
  <button
204
220
  class="ui icon button basic"
205
221
  >
@@ -16,6 +16,7 @@ import {
16
16
  API_DATA_STRUCTURE_FILTERS_SEARCH,
17
17
  API_DATA_STRUCTURES_XLSX_DOWNLOAD,
18
18
  API_DATA_STRUCTURES_XLSX_UPLOAD,
19
+ API_STRUCTURE_NOTES_XLSX_DOWNLOAD,
19
20
  } from "../api";
20
21
 
21
22
  const toApiPath = compile(API_SYSTEM_STRUCTURES);
@@ -95,3 +96,4 @@ export const useDataStructureSuggestions = () => {
95
96
  return apiJsonPost(url, arg);
96
97
  });
97
98
  };
99
+