@truedat/dd 8.4.3 → 8.4.5

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": "8.4.3",
3
+ "version": "8.4.5",
4
4
  "description": "Truedat Web Data Dictionary",
5
5
  "sideEffects": false,
6
6
  "module": "src/index.js",
@@ -51,7 +51,7 @@
51
51
  "@testing-library/jest-dom": "^6.6.3",
52
52
  "@testing-library/react": "^16.3.0",
53
53
  "@testing-library/user-event": "^14.6.1",
54
- "@truedat/test": "8.4.3",
54
+ "@truedat/test": "8.4.5",
55
55
  "identity-obj-proxy": "^3.0.0",
56
56
  "jest": "^29.7.0",
57
57
  "redux-saga-test-plan": "^4.0.6"
@@ -86,5 +86,5 @@
86
86
  "svg-pan-zoom": "^3.6.2",
87
87
  "swr": "^2.3.3"
88
88
  },
89
- "gitHead": "4c27fe8b467a2b0b26f69d4931d85d8bdf674e6d"
89
+ "gitHead": "b02bd52fc13d8e811993059bc50085f841415c91"
90
90
  }
@@ -23,36 +23,86 @@ import { useCheckoutGrantRequest } from "../hooks/useCheckoutGrantRequest";
23
23
  import { useTemplatesByIds } from "../hooks/useTemplatesByIds";
24
24
 
25
25
  const SelectableDynamicForm = lazy(
26
- () => import("@truedat/df/components/SelectableDynamicForm")
26
+ () => import("@truedat/df/components/SelectableDynamicForm"),
27
27
  );
28
28
 
29
- const getGroupedStructuresByTemplate = (
29
+ const getUniqueDomainIds = (structures) =>
30
+ _.flow(_.map("domain_ids"), _.flatten, _.compact, _.uniq)(structures ?? []);
31
+
32
+ const getUniqueSystemName = (structures) =>
33
+ _.flow(_.map("system.name"), _.flatten, _.compact, _.uniq)(structures ?? []);
34
+
35
+ const getGroupedStructuresByDomainAndTemplate = (
30
36
  structures,
31
37
  templateResourceRelations,
32
- defaultTemplateId
38
+ defaultTemplateId,
39
+ defaultGroupByDomain = false,
40
+ enableGrouping = true,
33
41
  ) => {
42
+ if (!enableGrouping) {
43
+ return { grouped: { all: structures ?? [] }, groupByDomainByKey: {} };
44
+ }
45
+
46
+ const explicitSystemIds = new Set(
47
+ (templateResourceRelations ?? [])
48
+ .filter((r) => r.resource_id != null)
49
+ .map((r) => String(r.resource_id)),
50
+ );
51
+
34
52
  const systemToTemplateId = _.fromPairs(
35
- (templateResourceRelations ?? []).map((r) => [
36
- r.resource_id != null ? Number(r.resource_id) || r.resource_id : null,
37
- r.template_id != null ? Number(r.template_id) || r.template_id : null,
38
- ]).filter(([sid]) => sid != null)
53
+ (templateResourceRelations ?? [])
54
+ .map((r) => [
55
+ r.resource_id != null ? String(r.resource_id) : null,
56
+ r.template_id != null ? Number(r.template_id) || r.template_id : null,
57
+ ])
58
+ .filter(([sid]) => sid != null),
59
+ );
60
+
61
+ const systemGroupByDomain = _.fromPairs(
62
+ (templateResourceRelations ?? [])
63
+ .filter((r) => r.resource_id != null)
64
+ .map((r) => [String(r.resource_id), r.group_by_domain === true]),
39
65
  );
66
+
40
67
  return _.reduce(
41
68
  (acc, structure) => {
42
69
  const systemId = structure.system_id ?? structure.system?.id ?? null;
43
- const normalizedSystemId =
44
- systemId != null ? Number(systemId) || systemId : null;
45
- const templateId =
46
- normalizedSystemId != null
47
- ? systemToTemplateId[normalizedSystemId] ?? defaultTemplateId
48
- : defaultTemplateId;
49
- const key =
50
- templateId != null ? String(templateId) : "others";
51
- const list = acc[key] ?? [];
52
- return { ...acc, [key]: [...list, structure] };
70
+ const normalizedSystemIdStr = systemId != null ? String(systemId) : null;
71
+
72
+ const isExplicitSystem =
73
+ normalizedSystemIdStr != null &&
74
+ explicitSystemIds.has(normalizedSystemIdStr);
75
+
76
+ const templateId = isExplicitSystem
77
+ ? (systemToTemplateId[normalizedSystemIdStr] ?? defaultTemplateId)
78
+ : defaultTemplateId;
79
+
80
+ const shouldGroupByDomain = isExplicitSystem
81
+ ? systemGroupByDomain[normalizedSystemIdStr] === true
82
+ : defaultGroupByDomain === true && defaultTemplateId != null;
83
+
84
+ const systemKey = isExplicitSystem ? normalizedSystemIdStr : "others";
85
+
86
+ const domainIds = structure.domain_ids || [];
87
+ const sortedDomainIds = [...domainIds].sort((a, b) => a - b);
88
+ const domainKey =
89
+ shouldGroupByDomain && sortedDomainIds.length > 0
90
+ ? sortedDomainIds.join(",")
91
+ : "nodomain";
92
+ const templateKey = templateId != null ? String(templateId) : "notype";
93
+ const key = `${domainKey}_${systemKey}_${templateKey}`;
94
+
95
+ const list = acc.grouped[key] ?? [];
96
+ return {
97
+ grouped: { ...acc.grouped, [key]: [...list, structure] },
98
+ groupByDomainByKey: {
99
+ ...acc.groupByDomainByKey,
100
+ [key]: shouldGroupByDomain && domainKey !== "nodomain",
101
+ },
102
+ };
53
103
  },
54
- {},
55
- structures ?? []
104
+ { grouped: {}, groupByDomainByKey: {} },
105
+ structures ?? [],
56
106
  );
57
107
  };
58
108
 
@@ -86,45 +136,63 @@ export const StructureGrantCartCheckout = ({
86
136
  groupTemplates = {},
87
137
  } = grantRequestsCart;
88
138
 
89
- const groupedStructures = getGroupedStructuresByTemplate(
90
- structures,
91
- templateResourceRelations,
92
- defaultTemplateId
139
+ const enableGrouping =
140
+ !modificationGrant && scopesWithRelations.includes("gr");
141
+
142
+ const { grouped: groupedStructures, groupByDomainByKey } = useMemo(
143
+ () =>
144
+ getGroupedStructuresByDomainAndTemplate(
145
+ structures,
146
+ templateResourceRelations,
147
+ defaultTemplateId,
148
+ defaultRelation?.group_by_domain === true,
149
+ enableGrouping,
150
+ ),
151
+ [
152
+ structures,
153
+ templateResourceRelations,
154
+ defaultTemplateId,
155
+ defaultRelation,
156
+ enableGrouping,
157
+ ],
93
158
  );
94
159
  const groupKeys = _.keys(groupedStructures);
95
- const isMultiGroup =
96
- !modificationGrant &&
97
- scopesWithRelations.includes("gr") &&
98
- groupKeys.length > 1;
160
+ const isMultiGroup = enableGrouping && groupKeys.length > 1;
99
161
 
100
- const singleGroupKey =
101
- !modificationGrant &&
102
- scopesWithRelations.includes("gr") &&
103
- groupKeys.length === 1
104
- ? groupKeys[0]
105
- : null;
162
+ const getTemplateIdFromKey = (key) => {
163
+ const parts = key.split("_");
164
+ const templatePart = parts[2];
165
+ if (templatePart === "notype" || !templatePart) {
166
+ return defaultTemplateId;
167
+ }
168
+ return Number(templatePart) || templatePart;
169
+ };
170
+
171
+ const getDomainIdsFromKey = (key) => {
172
+ const parts = key.split("_");
173
+ const domainPart = parts[0];
174
+ if (domainPart === "nodomain" || !domainPart) {
175
+ return null;
176
+ }
177
+ return domainPart.split(",").map((id) => parseInt(id, 10));
178
+ };
106
179
 
107
180
  const effectiveTemplateIdByKey = useMemo(
108
- () =>
109
- _.fromPairs(
110
- groupKeys.map((k) => [
111
- k,
112
- k === "others" ? defaultTemplateId : Number(k) || k,
113
- ])
114
- ),
115
- [groupKeys.join(","), defaultTemplateId]
181
+ () => _.fromPairs(groupKeys.map((k) => [k, getTemplateIdFromKey(k)])),
182
+ [groupKeys.join(","), defaultTemplateId],
116
183
  );
117
184
 
118
185
  const uniqueTemplateIds = useMemo(
119
186
  () => _.uniq(_.compact(_.values(effectiveTemplateIdByKey))),
120
- [effectiveTemplateIdByKey]
187
+ [effectiveTemplateIdByKey],
121
188
  );
122
189
 
190
+ const singleGroupKey =
191
+ enableGrouping && groupKeys.length === 1 ? groupKeys[0] : null;
192
+
123
193
  const singleTemplateId =
124
194
  !isMultiGroup && singleGroupKey != null
125
- ? singleGroupKey === "others"
126
- ? defaultTemplateId
127
- : Number(singleGroupKey) || singleGroupKey
195
+ ? getTemplateIdFromKey(singleGroupKey)
128
196
  : null;
129
197
 
130
198
  const templateIdsToFetch = isMultiGroup
@@ -141,22 +209,25 @@ export const StructureGrantCartCheckout = ({
141
209
  groupKeys.forEach((k) => {
142
210
  const tid = effectiveTemplateIdByKey[k];
143
211
  const t =
144
- tid != null
145
- ? templatesById[String(tid)] ?? templatesById[tid]
146
- : null;
212
+ tid != null ? (templatesById[String(tid)] ?? templatesById[tid]) : null;
147
213
  if (t) next[k] = t;
148
214
  });
149
215
  return next;
150
- }, [isMultiGroup, groupKeys.join(","), effectiveTemplateIdByKey, templatesById]);
216
+ }, [
217
+ isMultiGroup,
218
+ groupKeys.join(","),
219
+ effectiveTemplateIdByKey,
220
+ templatesById,
221
+ ]);
151
222
 
152
223
  const singleSystemTemplate = useMemo(
153
224
  () =>
154
225
  singleTemplateId != null
155
- ? templatesById[String(singleTemplateId)] ??
226
+ ? (templatesById[String(singleTemplateId)] ??
156
227
  templatesById[singleTemplateId] ??
157
- null
228
+ null)
158
229
  : null,
159
- [singleTemplateId, templatesById]
230
+ [singleTemplateId, templatesById],
160
231
  );
161
232
 
162
233
  useEffect(() => {
@@ -167,35 +238,35 @@ export const StructureGrantCartCheckout = ({
167
238
  groupKeys.map((k) => [
168
239
  k,
169
240
  {
170
- template: k === "others" ? null : undefined,
241
+ template: undefined,
171
242
  templateContent: {},
172
243
  valid: false,
173
244
  },
174
- ])
175
- )
245
+ ]),
246
+ ),
176
247
  );
177
248
  return;
178
249
  }
179
250
  const allLoaded = uniqueTemplateIds.every(
180
251
  (id) =>
181
252
  templatesById[String(id)] !== undefined ||
182
- templatesById[id] !== undefined
253
+ templatesById[id] !== undefined,
183
254
  );
184
255
  if (!allLoaded) return;
185
256
  const getTemplateById = (tid) =>
186
- tid != null ? templatesById[String(tid)] ?? templatesById[tid] : null;
257
+ tid != null ? (templatesById[String(tid)] ?? templatesById[tid]) : null;
187
258
  const initial = _.fromPairs(
188
259
  groupKeys.map((k) => {
189
260
  const t = getTemplateById(effectiveTemplateIdByKey[k])?.name;
190
261
  return [
191
262
  k,
192
263
  {
193
- template: t ?? (k === "others" ? null : undefined),
264
+ template: t ?? undefined,
194
265
  templateContent: {},
195
266
  valid: false,
196
267
  },
197
268
  ];
198
- })
269
+ }),
199
270
  );
200
271
  initGroupTemplates(initial);
201
272
  }, [
@@ -223,11 +294,43 @@ export const StructureGrantCartCheckout = ({
223
294
  isMultiGroup &&
224
295
  user?.valid &&
225
296
  groupKeys.every(
226
- (k) => groupTemplates[k]?.template && groupTemplates[k]?.valid
297
+ (k) => groupTemplates[k]?.template && groupTemplates[k]?.valid,
227
298
  );
228
299
 
229
300
  const canSubmit = isMultiGroup ? validCartMulti : validCart && user?.valid;
230
301
 
302
+ const domainNamesByKey = useMemo(() => {
303
+ const domainIdToName = {};
304
+ (structures ?? []).forEach((structure) => {
305
+ const structureDomains =
306
+ structure.domains || structure.data_structure?.domains || [];
307
+ structureDomains.forEach((domain) => {
308
+ if (domain?.id != null) {
309
+ const key = String(domain.id);
310
+ if (!domainIdToName[key]) {
311
+ domainIdToName[key] = domain.name;
312
+ }
313
+ }
314
+ });
315
+ });
316
+
317
+ return _.fromPairs(
318
+ groupKeys.map((key) => {
319
+ const domainIds = getDomainIdsFromKey(key);
320
+
321
+ if (!domainIds) {
322
+ return [key, null];
323
+ }
324
+
325
+ const domainNames = domainIds
326
+ .map((id) => domainIdToName[id] || String(id))
327
+ .join(", ");
328
+
329
+ return [key, domainNames];
330
+ }),
331
+ );
332
+ }, [groupKeys.join(","), structures, groupedStructures]);
333
+
231
334
  const checkout = () => {
232
335
  let payload;
233
336
  if (isMultiGroup) {
@@ -274,9 +377,9 @@ export const StructureGrantCartCheckout = ({
274
377
  checkoutGrantRequest.failure(
275
378
  err?.response
276
379
  ? { status: err.response.status, data: err.response.data }
277
- : err?.message
278
- )
279
- )
380
+ : err?.message,
381
+ ),
382
+ ),
280
383
  )
281
384
  .finally(() => dispatch(checkoutGrantRequest.fulfill()));
282
385
  };
@@ -306,7 +409,7 @@ export const StructureGrantCartCheckout = ({
306
409
  />
307
410
  </GridColumn>
308
411
  </Grid.Row>
309
- {isMultiGroup ? (
412
+ {groupKeys.length > 1 ? (
310
413
  <>
311
414
  <Grid.Row>
312
415
  <Grid.Column width={16}>
@@ -319,24 +422,75 @@ export const StructureGrantCartCheckout = ({
319
422
  {groupKeys.map((key) => {
320
423
  const groupStructures = groupedStructures[key] ?? [];
321
424
  if (_.isEmpty(groupStructures)) return null;
322
- const isOthers = key === "others";
425
+ const groupDomainIds = getUniqueDomainIds(groupStructures);
323
426
  const groupData = groupTemplates[key] ?? {};
324
427
  const selectedTemplate = systemTemplates[key];
428
+ const domainName = domainNamesByKey[key];
429
+ const systemNames = getUniqueSystemName(groupStructures);
430
+
431
+ const shouldGroupByDomain =
432
+ enableGrouping && groupByDomainByKey[key] === true;
433
+
434
+ const shouldShowSystemTitle = enableGrouping;
435
+
436
+ const domainCount = groupDomainIds.length;
437
+ const domainMessageId =
438
+ domainCount === 1
439
+ ? "grants.props.domain_name"
440
+ : "grants.props.domain_names";
441
+
442
+ const systemMessageId = "grants.props.system_name";
443
+
325
444
  return (
326
445
  <Grid.Row key={key}>
327
446
  <Grid.Column width={5}>
328
447
  <Segment className="grant-cart-group-form">
329
- <Header as="h4">
330
- {isOthers ? (
331
- <FormattedMessage id="grantRequest.cart.group.others" />
332
- ) : (
333
- selectedTemplate?.name ?? key
334
- )}
448
+ <Header as="h5">
449
+ {shouldShowSystemTitle &&
450
+ systemNames != null &&
451
+ systemNames.length > 0 && (
452
+ <div
453
+ style={{
454
+ wordBreak: "break-word",
455
+ marginBottom: "5px",
456
+ }}
457
+ >
458
+ {formatMessage({ id: systemMessageId })}:{" "}
459
+ <span style={{ fontWeight: "normal" }}>
460
+ {key.split("_")[1] === "others"
461
+ ? formatMessage({
462
+ id: "grantRequest.cart.group.others",
463
+ })
464
+ : systemNames.join(", ")}
465
+ </span>
466
+ </div>
467
+ )}
468
+
469
+ {shouldGroupByDomain &&
470
+ domainName != null &&
471
+ domainName !== "" && (
472
+ <div
473
+ style={{
474
+ wordBreak: "break-word",
475
+ marginBottom: "5px",
476
+ }}
477
+ >
478
+ {formatMessage({ id: domainMessageId })}:{" "}
479
+ <span style={{ fontWeight: "normal" }}>
480
+ {domainName}
481
+ </span>
482
+ </div>
483
+ )}
484
+ <Header.Subheader>
485
+ {formatMessage({ id: "grants.props.structures" })}:{" "}
486
+ {groupStructures.length}
487
+ </Header.Subheader>
335
488
  </Header>
336
489
  <Suspense fallback={null}>
337
490
  <SelectableDynamicForm
338
491
  scope="gr"
339
492
  content={groupData.templateContent ?? {}}
493
+ domainIds={groupDomainIds}
340
494
  hideLabel
341
495
  isModification={!!modificationGrant}
342
496
  selectedTemplate={selectedTemplate}
@@ -411,10 +565,10 @@ const mapDispatchToProps = (dispatch) =>
411
565
  selectGrantRequestTemplateForGroup,
412
566
  updateGrantRequestTemplateContentForGroup,
413
567
  },
414
- dispatch
568
+ dispatch,
415
569
  );
416
570
 
417
571
  export default connect(
418
572
  mapStateToProps,
419
- mapDispatchToProps
573
+ mapDispatchToProps,
420
574
  )(StructureGrantCartCheckout);
@@ -16,6 +16,14 @@ const SelectableDynamicForm = lazy(
16
16
  () => import("@truedat/df/components/SelectableDynamicForm")
17
17
  );
18
18
 
19
+ const getUniqueDomainIds = (structures) =>
20
+ _.flow(
21
+ _.map("domain_ids"),
22
+ _.flatten,
23
+ _.compact,
24
+ _.uniq
25
+ )(structures ?? []);
26
+
19
27
  export const StructureGrantCartInformation = ({
20
28
  grantRequestsCart,
21
29
  isModification,
@@ -26,6 +34,8 @@ export const StructureGrantCartInformation = ({
26
34
  }) => {
27
35
  const { formatMessage } = useIntl();
28
36
  const templateContent = _.pathOr({}, "templateContent")(grantRequestsCart);
37
+ const structures = _.pathOr([], "structures")(grantRequestsCart);
38
+ const domainIds = getUniqueDomainIds(structures);
29
39
 
30
40
  const { loading, data } = useQuery(DOMAINS_QUERY, {
31
41
  fetchPolicy: "cache-and-network",
@@ -54,6 +64,7 @@ export const StructureGrantCartInformation = ({
54
64
  <SelectableDynamicForm
55
65
  scope="gr"
56
66
  content={templateContent}
67
+ domainIds={domainIds}
57
68
  hideLabel
58
69
  isModification={isModification}
59
70
  onChange={updateGrantRequestTemplateContent}
@@ -1,5 +1,5 @@
1
1
  import _ from "lodash/fp";
2
- import { lazy, useEffect, useState } from "react";
2
+ import { lazy, useEffect, useMemo, useRef, useState } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { Form, Checkbox, Header, Label, Popup } from "semantic-ui-react";
5
5
  import { connect } from "react-redux";
@@ -15,6 +15,7 @@ export const StructureGrantCartUserSelector = ({
15
15
  grantRequestsCart,
16
16
  updateGrantRequestUser,
17
17
  spacedLabel,
18
+ domainIds: externalDomainIds,
18
19
  }) => {
19
20
  const { formatMessage } = useIntl();
20
21
  const [usersGroupsQuery, setUsersGroupsQuery] = useState("");
@@ -25,6 +26,15 @@ export const StructureGrantCartUserSelector = ({
25
26
  const [userId, setUserId] = useState(null);
26
27
  const { users, trigger } = useGrantRequestableUsers();
27
28
 
29
+ const structuresDomainIds = useMemo(
30
+ () =>
31
+ externalDomainIds ||
32
+ _.flatMap(({ domain_ids }) => domain_ids)(
33
+ grantRequestsCart.structures || [],
34
+ ),
35
+ [externalDomainIds, grantRequestsCart.structures],
36
+ );
37
+
28
38
  const { modificationGrant } = grantRequestsCart;
29
39
  const updateGrant = !_.isEmpty(modificationGrant);
30
40
  const domainActions = ["publishGrantRequest"];
@@ -37,6 +47,8 @@ export const StructureGrantCartUserSelector = ({
37
47
 
38
48
  const { data: roles } = useRoles();
39
49
 
50
+ const prevPayloadRef = useRef(null);
51
+
40
52
  useEffect(() => {
41
53
  if (!_.isNil(modificationGrant?.user_id)) {
42
54
  selectUser(modificationGrant?.user_id);
@@ -44,7 +56,7 @@ export const StructureGrantCartUserSelector = ({
44
56
  _.flow(
45
57
  _.find({ value: userId }),
46
58
  _.pathOr(null, "value"),
47
- selectUser
59
+ selectUser,
48
60
  )(options);
49
61
  }
50
62
  }, [grantRequestsCart?.user?.id, modificationGrant?.user_id, options]);
@@ -61,24 +73,26 @@ export const StructureGrantCartUserSelector = ({
61
73
  _.flow(
62
74
  _.filter(({ name }) => _.includes(name)(selectedRoles)),
63
75
  _.map(({ id }) => id),
64
- setSelectedRoleIds
76
+ setSelectedRoleIds,
65
77
  )(roles);
66
78
  }, [selectedRoles, roles]);
67
79
 
68
80
  useEffect(() => {
69
- const structuresDomains = _.flatMap(({ domain_ids }) => domain_ids)(
70
- grantRequestsCart.structures
71
- );
72
81
  const filters = {
73
- structures_domains: structuresDomains,
82
+ structures_domains: structuresDomainIds,
74
83
  query: usersGroupsQuery,
75
84
  filter_domains: domainIds,
76
85
  roles: selectedRoleIds,
77
86
  };
78
87
  const payload = _.omitBy(_.isEmpty)(filters);
79
- trigger(payload);
88
+
89
+ const payloadKey = JSON.stringify(payload);
90
+ if (prevPayloadRef.current !== payloadKey) {
91
+ prevPayloadRef.current = payloadKey;
92
+ trigger(payload);
93
+ }
80
94
  }, [
81
- grantRequestsCart.structures,
95
+ structuresDomainIds,
82
96
  usersGroupsQuery,
83
97
  domainIds,
84
98
  selectedRoleIds,
@@ -201,6 +215,7 @@ StructureGrantCartUserSelector.propTypes = {
201
215
  grantRequestsCart: PropTypes.object,
202
216
  updateGrantRequestUser: PropTypes.func,
203
217
  spacedLabel: PropTypes.bool,
218
+ domainIds: PropTypes.array,
204
219
  };
205
220
 
206
221
  const mapStateToProps = ({ grantRequestsCart }) => ({
@@ -208,5 +223,5 @@ const mapStateToProps = ({ grantRequestsCart }) => ({
208
223
  });
209
224
 
210
225
  export default connect(mapStateToProps, { updateGrantRequestUser })(
211
- StructureGrantCartUserSelector
226
+ StructureGrantCartUserSelector,
212
227
  );