@truedat/core 8.5.8 → 8.6.0

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/core",
3
- "version": "8.5.8",
3
+ "version": "8.6.0",
4
4
  "description": "Truedat Web Core",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -54,7 +54,7 @@
54
54
  "@testing-library/jest-dom": "^6.6.3",
55
55
  "@testing-library/react": "^16.3.0",
56
56
  "@testing-library/user-event": "^14.6.1",
57
- "@truedat/test": "8.5.8",
57
+ "@truedat/test": "8.6.0",
58
58
  "identity-obj-proxy": "^3.0.0",
59
59
  "jest": "^29.7.0",
60
60
  "redux-saga-test-plan": "^4.0.6"
@@ -69,7 +69,7 @@
69
69
  "@tiptap/starter-kit": "^3.20.0",
70
70
  "@xyflow/react": "^12.6.4",
71
71
  "axios": "^1.15.0",
72
- "dompurify": "^3.3.3",
72
+ "dompurify": "^3.4.0",
73
73
  "elkjs": "^0.10.0",
74
74
  "graphql": "^16.11.0",
75
75
  "lodash": "^4.17.21",
@@ -95,5 +95,5 @@
95
95
  "swr": "^2.3.3",
96
96
  "turndown": "^7.2.2"
97
97
  },
98
- "gitHead": "61ad9443b3d822d30dcfa977f5fad3494b3ed5b4"
98
+ "gitHead": "9aa93d0e27de7c0fcb472d58eb69aeaf6a2dd6bc"
99
99
  }
@@ -31,6 +31,8 @@ const roleOptions = (roles) =>
31
31
  _.map(roleOption)
32
32
  )(roles);
33
33
 
34
+ const selectOption = _.pick(["key", "text", "value", "id", "icon"]);
35
+
34
36
  export const AddMemberForm = ({ resource, onSuccess, roles, options }) => {
35
37
  const { trigger: addDomainMember, isMutating: isSubmitting } =
36
38
  useAclEntryCreate(resource);
@@ -83,7 +85,7 @@ export const AddMemberForm = ({ resource, onSuccess, roles, options }) => {
83
85
  onSearchChange={onSearch}
84
86
  search={_.identity}
85
87
  selection
86
- options={options}
88
+ options={_.map(selectOption)(options)}
87
89
  required
88
90
  label={{
89
91
  children: formatMessage({ id: "domain.member" }),
@@ -20,7 +20,7 @@ const memberTypeToPopup = {
20
20
  const retrieveNameFromMember = (principal_type, principal) => {
21
21
  switch (principal_type) {
22
22
  case "group":
23
- return null;
23
+ return principal.alias ? principal.name : null;
24
24
  case "user":
25
25
  return principal.user_name;
26
26
  }
@@ -29,7 +29,7 @@ const retrieveNameFromMember = (principal_type, principal) => {
29
29
  const retrieveFullNameFromMember = (principal_type, principal) => {
30
30
  switch (principal_type) {
31
31
  case "group":
32
- return principal.name;
32
+ return principal.alias ? principal.alias : principal.name;
33
33
  case "user":
34
34
  return principal.full_name;
35
35
  }
@@ -121,11 +121,11 @@ export const ResourceMember = ({
121
121
  const fullName = retrieveFullNameFromMember(principal_type, principal);
122
122
  const canDelete = _.flow(
123
123
  _.path("self.methods"),
124
- _.includes("DELETE")
124
+ _.includes("DELETE"),
125
125
  )(_links);
126
126
  const canUpdate = _.flow(
127
127
  _.path("self.methods"),
128
- _.includes("UPDATE")
128
+ _.includes("UPDATE"),
129
129
  )(_links);
130
130
  return (
131
131
  <Card className="domain-member">
@@ -159,7 +159,7 @@ export const ResourceMember = ({
159
159
  id: acl_entry_id,
160
160
  acl_entry: { description },
161
161
  },
162
- { onSuccess: onUpdate }
162
+ { onSuccess: onUpdate },
163
163
  );
164
164
  }}
165
165
  />
@@ -25,6 +25,14 @@ describe("<AddMemberForm />", () => {
25
25
  const options = [
26
26
  { key: 1, text: "john", value: "user_1", id: "1" },
27
27
  { key: 2, text: "mambo", value: "group_2", id: "2" },
28
+ {
29
+ key: 3,
30
+ text: "Group Alias",
31
+ value: "group_3",
32
+ id: "3",
33
+ name: "actual_group_name",
34
+ alias: "Group Alias",
35
+ },
28
36
  ];
29
37
  const roles = [
30
38
  { key: 1, text: "role1", value: "role1" },
@@ -46,8 +54,8 @@ describe("<AddMemberForm />", () => {
46
54
  // Submit button should initially be disabled
47
55
  await waitFor(async () =>
48
56
  expect(
49
- await rendered.findByRole("button", { name: /add_member/i })
50
- ).toBeDisabled()
57
+ await rendered.findByRole("button", { name: /add_member/i }),
58
+ ).toBeDisabled(),
51
59
  );
52
60
 
53
61
  // Description
@@ -57,12 +65,12 @@ describe("<AddMemberForm />", () => {
57
65
  // Submit button should now be enabled
58
66
  await waitFor(async () =>
59
67
  expect(
60
- await rendered.findByRole("button", { name: /add_member/i })
61
- ).not.toBeDisabled()
68
+ await rendered.findByRole("button", { name: /add_member/i }),
69
+ ).not.toBeDisabled(),
62
70
  );
63
71
 
64
72
  await user.click(
65
- await rendered.findByRole("button", { name: /add_member/i })
73
+ await rendered.findByRole("button", { name: /add_member/i }),
66
74
  );
67
75
 
68
76
  await waitFor(() =>
@@ -74,7 +82,31 @@ describe("<AddMemberForm />", () => {
74
82
  principal_id: 1,
75
83
  description: "",
76
84
  },
77
- })
85
+ }),
78
86
  );
79
87
  });
88
+
89
+ it("displays group alias in dropdown when available", async () => {
90
+ const rendered = render(<AddMemberForm {...props} />);
91
+ await waitForLoad(rendered);
92
+
93
+ const [dropdown] = rendered.getAllByRole("combobox");
94
+ await user.click(dropdown);
95
+
96
+ // Should display the alias "Group Alias" in the dropdown, not the actual name
97
+ expect(rendered.getByText(/group alias/i)).toBeInTheDocument();
98
+ // And the group without alias should display its name
99
+ expect(rendered.getByText(/mambo/i)).toBeInTheDocument();
100
+ });
101
+
102
+ it("displays group name when alias is not available", async () => {
103
+ const rendered = render(<AddMemberForm {...props} />);
104
+ await waitForLoad(rendered);
105
+
106
+ const [dropdown] = rendered.getAllByRole("combobox");
107
+ await user.click(dropdown);
108
+
109
+ // Group "mambo" without alias should be displayed
110
+ expect(rendered.getByText(/mambo/i)).toBeInTheDocument();
111
+ });
80
112
  });
@@ -1,4 +1,3 @@
1
- import { waitFor } from "@testing-library/react";
2
1
  import userEvent from "@testing-library/user-event";
3
2
  import { render, waitForLoad } from "@truedat/test/render";
4
3
  import { useAclEntries } from "@truedat/core/hooks/useAclEntries";
@@ -27,9 +26,26 @@ describe("<ResourceMembers />", () => {
27
26
  principal_type: "group",
28
27
  role_id: 2,
29
28
  role_name: "data_owner",
30
- principal: { description: "aaa bbb cc d", id: 2, name: "grupo1 " },
29
+ principal: {
30
+ description: "aaa bbb cc d",
31
+ id: 2,
32
+ name: "grupo1 ",
33
+ alias: "Group Alias 1",
34
+ },
31
35
  acl_entry_id: 1,
32
36
  },
37
+ {
38
+ principal_type: "group",
39
+ role_id: 2,
40
+ role_name: "data_owner",
41
+ principal: {
42
+ description: "bbb ccc dd e",
43
+ id: 3,
44
+ name: "grupo2",
45
+ alias: null,
46
+ },
47
+ acl_entry_id: 5,
48
+ },
33
49
  {
34
50
  principal: {
35
51
  email: "test@test.com",
@@ -91,7 +107,7 @@ describe("<ResourceMembers />", () => {
91
107
  const user = userEvent.setup({ delay: null });
92
108
 
93
109
  await user.type(input, "data");
94
- expect(document.getElementsByClassName("card").length).toBe(2);
110
+ expect(document.getElementsByClassName("card").length).toBe(3);
95
111
 
96
112
  await user.type(input, " owner");
97
113
  expect(document.getElementsByClassName("card").length).toBe(1);
@@ -122,4 +138,23 @@ describe("<ResourceMembers />", () => {
122
138
  expect(rendered.queryByRole("heading", { name: /datos a/i })).toBeNull();
123
139
  expect(rendered.queryByRole("heading", { name: /last r/i })).toBeNull();
124
140
  });
141
+
142
+ it("displays group alias when available", async () => {
143
+ const rendered = render(<ResourceMembers {...props} />);
144
+ await waitForLoad(rendered);
145
+
146
+ // Group with alias should display "Group Alias 1" as header
147
+ expect(rendered.getByText(/group alias 1/i)).toBeInTheDocument();
148
+ // And the actual group name should be in the description
149
+ expect(rendered.getByText(/grupo1/i)).toBeInTheDocument();
150
+ });
151
+
152
+ it("displays group name when alias is not available", async () => {
153
+ const rendered = render(<ResourceMembers {...props} />);
154
+ await waitForLoad(rendered);
155
+
156
+ // Group without alias should display only the name
157
+ const grupo2Cards = rendered.queryAllByText(/grupo2/i);
158
+ expect(grupo2Cards.length).toBeGreaterThan(0);
159
+ });
125
160
  });
@@ -71,6 +71,20 @@ exports[`<AddMemberForm /> matches the latest snapshot 1`] = `
71
71
  mambo
72
72
  </span>
73
73
  </div>
74
+ <div
75
+ aria-checked="false"
76
+ aria-selected="false"
77
+ class="item"
78
+ id="3"
79
+ role="option"
80
+ style="pointer-events: all;"
81
+ >
82
+ <span
83
+ class="text"
84
+ >
85
+ Group Alias
86
+ </span>
87
+ </div>
74
88
  </div>
75
89
  </div>
76
90
  </div>
@@ -56,8 +56,30 @@ exports[`<ResourceMembers /> matches the latest snapshot 1`] = `
56
56
  aria-hidden="true"
57
57
  class="users icon"
58
58
  />
59
+ Group Alias 1
60
+ </div>
61
+ <div
62
+ class="description"
63
+ >
59
64
  grupo1
60
65
  </div>
66
+ </div>
67
+ </div>
68
+ <div
69
+ class="ui card domain-member"
70
+ >
71
+ <div
72
+ class="content domain-member__content"
73
+ >
74
+ <div
75
+ class="header"
76
+ >
77
+ <i
78
+ aria-hidden="true"
79
+ class="users icon"
80
+ />
81
+ grupo2
82
+ </div>
61
83
  <div
62
84
  class="description"
63
85
  />
@@ -0,0 +1,365 @@
1
+ import { getRecipients } from "../getRecipients";
2
+
3
+ describe("getRecipients", () => {
4
+ it("transforms users correctly", () => {
5
+ const state = {
6
+ usersSearch: [
7
+ { id: 1, full_name: "John Doe" },
8
+ { id: 2, full_name: "Jane Smith" },
9
+ ],
10
+ groupsSearch: [],
11
+ };
12
+
13
+ const result = getRecipients(state);
14
+
15
+ expect(result).toEqual(
16
+ expect.arrayContaining([
17
+ expect.objectContaining({
18
+ id: 1,
19
+ full_name: "John Doe",
20
+ role: "user",
21
+ text: "John Doe",
22
+ value: "user_1",
23
+ icon: "user",
24
+ }),
25
+ expect.objectContaining({
26
+ id: 2,
27
+ full_name: "Jane Smith",
28
+ role: "user",
29
+ text: "Jane Smith",
30
+ value: "user_2",
31
+ icon: "user",
32
+ }),
33
+ ]),
34
+ );
35
+ });
36
+
37
+ it("displays group alias when available", () => {
38
+ const state = {
39
+ usersSearch: [],
40
+ groupsSearch: [
41
+ { id: 1, name: "group1", alias: "Group Alias 1", users: [] },
42
+ ],
43
+ };
44
+
45
+ const result = getRecipients(state);
46
+
47
+ expect(result).toContainEqual(
48
+ expect.objectContaining({
49
+ id: 1,
50
+ name: "group1",
51
+ alias: "Group Alias 1",
52
+ role: "group",
53
+ text: "Group Alias 1",
54
+ value: "group_1",
55
+ icon: "group",
56
+ }),
57
+ );
58
+ });
59
+
60
+ it("displays group name when alias is not available", () => {
61
+ const state = {
62
+ usersSearch: [],
63
+ groupsSearch: [{ id: 2, name: "group2", alias: null, users: [] }],
64
+ };
65
+
66
+ const result = getRecipients(state);
67
+
68
+ expect(result).toContainEqual(
69
+ expect.objectContaining({
70
+ id: 2,
71
+ name: "group2",
72
+ alias: null,
73
+ role: "group",
74
+ text: "group2",
75
+ value: "group_2",
76
+ icon: "group",
77
+ }),
78
+ );
79
+ });
80
+
81
+ it("displays group name when alias is empty string", () => {
82
+ const state = {
83
+ usersSearch: [],
84
+ groupsSearch: [{ id: 3, name: "group3", alias: "", users: [] }],
85
+ };
86
+
87
+ const result = getRecipients(state);
88
+
89
+ expect(result).toContainEqual(
90
+ expect.objectContaining({
91
+ id: 3,
92
+ name: "group3",
93
+ alias: "",
94
+ role: "group",
95
+ text: "group3",
96
+ value: "group_3",
97
+ icon: "group",
98
+ }),
99
+ );
100
+ });
101
+
102
+ it("combines users and groups correctly", () => {
103
+ const state = {
104
+ usersSearch: [{ id: 1, full_name: "John Doe" }],
105
+ groupsSearch: [
106
+ { id: 1, name: "group1", alias: "Group Alias", users: [] },
107
+ { id: 2, name: "group2", alias: null, users: [] },
108
+ ],
109
+ };
110
+
111
+ const result = getRecipients(state);
112
+
113
+ expect(result).toHaveLength(3);
114
+ expect(result).toContainEqual(
115
+ expect.objectContaining({
116
+ id: 1,
117
+ full_name: "John Doe",
118
+ role: "user",
119
+ text: "John Doe",
120
+ value: "user_1",
121
+ }),
122
+ );
123
+ expect(result).toContainEqual(
124
+ expect.objectContaining({
125
+ id: 1,
126
+ name: "group1",
127
+ alias: "Group Alias",
128
+ role: "group",
129
+ text: "Group Alias",
130
+ value: "group_1",
131
+ }),
132
+ );
133
+ expect(result).toContainEqual(
134
+ expect.objectContaining({
135
+ id: 2,
136
+ name: "group2",
137
+ alias: null,
138
+ role: "group",
139
+ text: "group2",
140
+ value: "group_2",
141
+ }),
142
+ );
143
+ });
144
+
145
+ it("handles empty users and groups", () => {
146
+ const state = {
147
+ usersSearch: [],
148
+ groupsSearch: [],
149
+ };
150
+
151
+ const result = getRecipients(state);
152
+
153
+ expect(result).toEqual([]);
154
+ });
155
+
156
+ it("handles missing usersSearch and groupsSearch properties", () => {
157
+ const state = {};
158
+
159
+ const result = getRecipients(state);
160
+
161
+ expect(result).toEqual([]);
162
+ });
163
+
164
+ it("filters out unwanted properties from users", () => {
165
+ const state = {
166
+ usersSearch: [
167
+ {
168
+ id: 1,
169
+ full_name: "John Doe",
170
+ email: "john@example.com",
171
+ password: "secret",
172
+ extra_field: "should_be_ignored",
173
+ },
174
+ ],
175
+ groupsSearch: [],
176
+ };
177
+
178
+ const result = getRecipients(state);
179
+
180
+ expect(result).toHaveLength(1);
181
+ expect(result[0]).toHaveProperty("id");
182
+ expect(result[0]).toHaveProperty("full_name");
183
+ expect(result[0]).not.toHaveProperty("email");
184
+ expect(result[0]).not.toHaveProperty("password");
185
+ expect(result[0]).not.toHaveProperty("extra_field");
186
+ });
187
+
188
+ it("filters out unwanted properties from groups", () => {
189
+ const state = {
190
+ usersSearch: [],
191
+ groupsSearch: [
192
+ {
193
+ id: 1,
194
+ name: "group1",
195
+ alias: "Group Alias",
196
+ description: "should_be_ignored",
197
+ users: [],
198
+ created_at: "2024-01-01",
199
+ },
200
+ ],
201
+ };
202
+
203
+ const result = getRecipients(state);
204
+
205
+ expect(result).toHaveLength(1);
206
+ expect(result[0]).toHaveProperty("id");
207
+ expect(result[0]).toHaveProperty("name");
208
+ expect(result[0]).toHaveProperty("alias");
209
+ expect(result[0]).toHaveProperty("users");
210
+ expect(result[0]).not.toHaveProperty("description");
211
+ expect(result[0]).not.toHaveProperty("created_at");
212
+ });
213
+
214
+ it("preserves order of users and groups", () => {
215
+ const state = {
216
+ usersSearch: [
217
+ { id: 1, full_name: "User 1" },
218
+ { id: 2, full_name: "User 2" },
219
+ { id: 3, full_name: "User 3" },
220
+ ],
221
+ groupsSearch: [
222
+ { id: 1, name: "Group 1", alias: null, users: [] },
223
+ { id: 2, name: "Group 2", alias: "GA 2", users: [] },
224
+ ],
225
+ };
226
+
227
+ const result = getRecipients(state);
228
+
229
+ expect(result).toHaveLength(5);
230
+ expect(result[0].value).toBe("user_1");
231
+ expect(result[1].value).toBe("user_2");
232
+ expect(result[2].value).toBe("user_3");
233
+ expect(result[3].value).toBe("group_1");
234
+ expect(result[4].value).toBe("group_2");
235
+ });
236
+
237
+ it("handles groups with special characters in names", () => {
238
+ const state = {
239
+ usersSearch: [],
240
+ groupsSearch: [
241
+ { id: 1, name: "group-with-dashes", alias: "Group & Co.", users: [] },
242
+ { id: 2, name: "group/with/slashes", alias: null, users: [] },
243
+ ],
244
+ };
245
+
246
+ const result = getRecipients(state);
247
+
248
+ expect(result).toHaveLength(2);
249
+ expect(result[0].text).toBe("Group & Co.");
250
+ expect(result[1].text).toBe("group/with/slashes");
251
+ });
252
+
253
+ it("handles users with whitespace in names", () => {
254
+ const state = {
255
+ usersSearch: [
256
+ { id: 1, full_name: " John Doe " },
257
+ { id: 2, full_name: "Jane\nSmith" },
258
+ ],
259
+ groupsSearch: [],
260
+ };
261
+
262
+ const result = getRecipients(state);
263
+
264
+ expect(result).toHaveLength(2);
265
+ expect(result[0].text).toBe(" John Doe ");
266
+ expect(result[1].text).toBe("Jane\nSmith");
267
+ });
268
+
269
+ it("converts numeric IDs to string values correctly", () => {
270
+ const state = {
271
+ usersSearch: [{ id: 123, full_name: "Test" }],
272
+ groupsSearch: [{ id: 456, name: "TestGroup", alias: null, users: [] }],
273
+ };
274
+
275
+ const result = getRecipients(state);
276
+
277
+ expect(result[0].value).toBe("user_123");
278
+ expect(result[1].value).toBe("group_456");
279
+ });
280
+
281
+ it("handles undefined alias correctly", () => {
282
+ const state = {
283
+ usersSearch: [],
284
+ groupsSearch: [{ id: 1, name: "group1", alias: undefined, users: [] }],
285
+ };
286
+
287
+ const result = getRecipients(state);
288
+
289
+ expect(result).toHaveLength(1);
290
+ expect(result[0].text).toBe("group1");
291
+ });
292
+
293
+ it("memoizes results for same state", () => {
294
+ const state = {
295
+ usersSearch: [{ id: 1, full_name: "John" }],
296
+ groupsSearch: [{ id: 1, name: "group1", alias: "GA1", users: [] }],
297
+ };
298
+
299
+ const result1 = getRecipients(state);
300
+ const result2 = getRecipients(state);
301
+
302
+ expect(result1).toBe(result2);
303
+ });
304
+
305
+ it("returns different results for different state", () => {
306
+ const state1 = {
307
+ usersSearch: [{ id: 1, full_name: "John" }],
308
+ groupsSearch: [],
309
+ };
310
+
311
+ const state2 = {
312
+ usersSearch: [{ id: 1, full_name: "Jane" }],
313
+ groupsSearch: [],
314
+ };
315
+
316
+ const result1 = getRecipients(state1);
317
+ const result2 = getRecipients(state2);
318
+
319
+ expect(result1).not.toBe(result2);
320
+ expect(result1[0].full_name).toBe("John");
321
+ expect(result2[0].full_name).toBe("Jane");
322
+ });
323
+
324
+ it("includes all required fields for users", () => {
325
+ const state = {
326
+ usersSearch: [{ id: 1, full_name: "John Doe" }],
327
+ groupsSearch: [],
328
+ };
329
+
330
+ const result = getRecipients(state);
331
+
332
+ expect(result[0]).toEqual(
333
+ expect.objectContaining({
334
+ id: 1,
335
+ full_name: "John Doe",
336
+ role: "user",
337
+ text: "John Doe",
338
+ value: "user_1",
339
+ icon: "user",
340
+ }),
341
+ );
342
+ });
343
+
344
+ it("includes all required fields for groups", () => {
345
+ const state = {
346
+ usersSearch: [],
347
+ groupsSearch: [{ id: 1, name: "group1", alias: "GA1", users: [] }],
348
+ };
349
+
350
+ const result = getRecipients(state);
351
+
352
+ expect(result[0]).toEqual(
353
+ expect.objectContaining({
354
+ id: 1,
355
+ name: "group1",
356
+ alias: "GA1",
357
+ users: [],
358
+ role: "group",
359
+ text: "GA1",
360
+ value: "group_1",
361
+ icon: "group",
362
+ }),
363
+ );
364
+ });
365
+ });
@@ -18,11 +18,11 @@ const usersAsOptions = (users) =>
18
18
 
19
19
  const groupsAsOptions = (groups) =>
20
20
  _.flow(
21
- _.map(_.pick(["id", "name", "users"])),
21
+ _.map(_.pick(["id", "name", "alias", "users"])),
22
22
  _.map((g) => ({
23
23
  ...g,
24
24
  role: "group",
25
- text: _.prop("name")(g),
25
+ text: _.prop("alias")(g) || _.prop("name")(g),
26
26
  value: `group_${_.prop("id")(g)}`,
27
27
  icon: "group",
28
28
  }))