@truedat/dd 8.12.1 → 8.12.4

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.
Files changed (82) hide show
  1. package/package.json +3 -3
  2. package/src/api/__tests__/queries.spec.js +55 -0
  3. package/src/api/queries.js +31 -0
  4. package/src/components/Grant.js +106 -16
  5. package/src/components/GrantRequest.js +2 -2
  6. package/src/components/GrantRequestBulkApprovalPopup.js +0 -1
  7. package/src/components/GrantRequestTemplateForm.js +76 -0
  8. package/src/components/GrantsDownloadButton.js +9 -1
  9. package/src/components/GrantsTable.js +4 -1
  10. package/src/components/StructureFields.js +7 -4
  11. package/src/components/StructureGrantCartCheckout.js +122 -59
  12. package/src/components/StructureGrantCartInformation.js +38 -9
  13. package/src/components/StructureGrantCartUserSelector.js +29 -4
  14. package/src/components/StructureGrantDropdown.js +7 -1
  15. package/src/components/StructureGrants.js +4 -1
  16. package/src/components/StructuresUpdateOption.js +2 -2
  17. package/src/components/SystemStructures.js +3 -3
  18. package/src/components/__tests__/CatalogViewConfigForm.spec.js +2 -0
  19. package/src/components/__tests__/EditDomainForm.spec.js +3 -1
  20. package/src/components/__tests__/Grant.spec.js +171 -0
  21. package/src/components/__tests__/GrantApprovalRuleForm.spec.js +2 -0
  22. package/src/components/__tests__/GrantApprovalRuleNew.spec.js +8 -0
  23. package/src/components/__tests__/GrantApprovalRules.spec.js +4 -0
  24. package/src/components/__tests__/GrantRequest.spec.js +4 -4
  25. package/src/components/__tests__/GrantRequestBulkActions.spec.js +1 -4
  26. package/src/components/__tests__/GrantRequestsSearchResults.spec.js +2 -0
  27. package/src/components/__tests__/GrantsDownloadButton.spec.js +58 -2
  28. package/src/components/__tests__/GrantsTable.spec.js +22 -2
  29. package/src/components/__tests__/LineageDefs.spec.js +6 -1
  30. package/src/components/__tests__/LineageForm.spec.js +1 -0
  31. package/src/components/__tests__/LineageGraphToolbar.spec.js +19 -17
  32. package/src/components/__tests__/LineageInspectorPanel.spec.js +17 -9
  33. package/src/components/__tests__/ProfileGroup.spec.js +7 -1
  34. package/src/components/__tests__/ReferenceDatasetForm.spec.js +2 -0
  35. package/src/components/__tests__/ReferenceDatasetRoutes.spec.js +9 -0
  36. package/src/components/__tests__/StructureGrantCartCheckout.grouping.spec.js +95 -0
  37. package/src/components/__tests__/StructureGrantCartCheckout.selection.spec.js +416 -0
  38. package/src/components/__tests__/StructureGrantCartCheckout.spec.js +216 -4
  39. package/src/components/__tests__/StructureGrantCartInformation.spec.js +5 -2
  40. package/src/components/__tests__/StructureGrantCartUserSelector.spec.js +128 -0
  41. package/src/components/__tests__/StructureGrantDropdown.spec.js +92 -0
  42. package/src/components/__tests__/StructureGrantListButton.spec.js +70 -0
  43. package/src/components/__tests__/StructureGrantSummaryButton.spec.js +36 -1
  44. package/src/components/__tests__/StructureGrants.spec.js +12 -0
  45. package/src/components/__tests__/StructureRelationRow.spec.js +72 -4
  46. package/src/components/__tests__/StructureRelations.spec.js +18 -0
  47. package/src/components/__tests__/StructureRoles.spec.js +9 -0
  48. package/src/components/__tests__/StructureRow.spec.js +13 -4
  49. package/src/components/__tests__/StructureSelector.spec.js +2 -0
  50. package/src/components/__tests__/StructureTag.spec.js +2 -0
  51. package/src/components/__tests__/StructureTagForm.spec.js +2 -0
  52. package/src/components/__tests__/StructureType.spec.js +3 -1
  53. package/src/components/__tests__/StructureTypeForm.spec.js +3 -1
  54. package/src/components/__tests__/StructureView.spec.js +17 -1
  55. package/src/components/__tests__/StructuresGrantRequestView.spec.js +2 -0
  56. package/src/components/__tests__/StructuresView.spec.js +2 -1
  57. package/src/components/__tests__/Tag.spec.js +4 -1
  58. package/src/components/__tests__/Tags.spec.js +7 -1
  59. package/src/components/__tests__/__snapshots__/GrantsTable.spec.js.snap +34 -2
  60. package/src/components/__tests__/__snapshots__/LineageDefs.spec.js.snap +32 -30
  61. package/src/components/__tests__/__snapshots__/StructureFields.spec.js.snap +1 -16
  62. package/src/components/__tests__/__snapshots__/StructureGrantCartCheckout.spec.js.snap +217 -1
  63. package/src/components/__tests__/__snapshots__/StructureGrantCartInformation.spec.js.snap +54 -0
  64. package/src/components/__tests__/__snapshots__/StructureRelationRow.spec.js.snap +23 -19
  65. package/src/components/__tests__/__snapshots__/StructureRow.spec.js.snap +28 -24
  66. package/src/components/__tests__/__snapshots__/StructuresUpdateOption.spec.js.snap +7 -3
  67. package/src/messages/en.js +2 -2
  68. package/src/messages/es.js +2 -2
  69. package/src/reducers/__tests__/grantRequestsCart.spec.js +86 -0
  70. package/src/reducers/__tests__/structureRelations.spec.js +28 -0
  71. package/src/reducers/grantRequestsCart.js +20 -2
  72. package/src/selectors/__tests__/__snapshots__/getGrantsColumns.spec.js.snap +1 -9
  73. package/src/selectors/__tests__/getGrantRequestsSearchColumns.spec.js +11 -4
  74. package/src/selectors/__tests__/getGrantsColumns.spec.js +295 -0
  75. package/src/selectors/getBusinessConceptsLinksToStructuresColumnsSelector.js +3 -5
  76. package/src/selectors/getGrantRequestsSearchColumns.js +3 -5
  77. package/src/selectors/getGrantsColumns.js +85 -11
  78. package/src/selectors/getLinkedImplementationsToStructuresColumns.js +3 -7
  79. package/src/selectors/getStructureFieldColumns.js +7 -6
  80. package/src/selectors/getStructureProperties.js +2 -5
  81. package/src/selectors/getStructuresUploadEventColumns.js +2 -7
  82. package/src/selectors/structureColumnsSelector.js +13 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dd",
3
- "version": "8.12.1",
3
+ "version": "8.12.4",
4
4
  "description": "Truedat Web Data Dictionary",
5
5
  "sideEffects": false,
6
6
  "module": "src/index.js",
@@ -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.12.1",
57
+ "@truedat/test": "8.12.4",
58
58
  "identity-obj-proxy": "^3.0.0",
59
59
  "jest": "^29.7.0",
60
60
  "redux-saga-test-plan": "^4.0.6"
@@ -90,5 +90,5 @@
90
90
  "svg-pan-zoom": "^3.6.2",
91
91
  "swr": "^2.3.3"
92
92
  },
93
- "gitHead": "db02aab92701c55a73d5a5b469a7114f9c3a596e"
93
+ "gitHead": "d5bc1af84c4d828bdec676b0da58385cf0d6015f"
94
94
  }
@@ -0,0 +1,55 @@
1
+ import { LEGACY_DATA_STRUCTURE_VERSION_QUERY, buildDSVQuery } from "../queries";
2
+
3
+ const PARENT_PATH = ["dataStructureVersion", "relations", "parents"];
4
+ const CHILD_PATH = ["dataStructureVersion", "relations", "children"];
5
+
6
+ const fieldAtPath = (document, path) =>
7
+ path.reduce((node, name) => {
8
+ const selection = node.selectionSet?.selections.find(
9
+ (s) => s.kind === "Field" && s.name.value === name,
10
+ );
11
+ if (!selection) {
12
+ throw new Error(`Missing field ${path.join(".")}`);
13
+ }
14
+ return selection;
15
+ }, document.definitions[0]);
16
+
17
+ const selectionNames = (field) =>
18
+ field.selectionSet.selections
19
+ .filter((s) => s.kind === "Field")
20
+ .map((s) => s.name.value);
21
+
22
+ const linksField = (field) =>
23
+ field.selectionSet.selections.find(
24
+ (s) => s.kind === "Field" && s.name.value === "links",
25
+ );
26
+
27
+ const expectRelationLinks = (document) => {
28
+ const parents = fieldAtPath(document, PARENT_PATH);
29
+ const children = fieldAtPath(document, CHILD_PATH);
30
+
31
+ expect(selectionNames(parents)).toEqual(expect.arrayContaining(["links"]));
32
+ expect(selectionNames(children)).toEqual(expect.arrayContaining(["links"]));
33
+
34
+ const resourceType = linksField(parents).arguments.find(
35
+ (argument) => argument.name.value === "resource_type",
36
+ );
37
+ expect(resourceType.value.value).toBe("business_concept");
38
+ expect(linksField(children).arguments).toEqual([]);
39
+ };
40
+
41
+ describe("LEGACY_DATA_STRUCTURE_VERSION_QUERY", () => {
42
+ it("requests concept links on parent and child structure relations", () => {
43
+ expectRelationLinks(LEGACY_DATA_STRUCTURE_VERSION_QUERY);
44
+ });
45
+ });
46
+
47
+ describe("buildDSVQuery", () => {
48
+ it("requests concept links on parent and child structure relations", () => {
49
+ expectRelationLinks(buildDSVQuery(false));
50
+ });
51
+
52
+ it("requests concept links on parent and child structure relations when loading metadata", () => {
53
+ expectRelationLinks(buildDSVQuery(true));
54
+ });
55
+ });
@@ -360,6 +360,13 @@ export const LEGACY_DATA_STRUCTURE_VERSION_QUERY = gql`
360
360
  user_name
361
361
  }
362
362
  user_id
363
+ user_group_id
364
+ source_group_name
365
+ user_group {
366
+ id
367
+ name
368
+ alias
369
+ }
363
370
  }
364
371
  grant @skip(if: $withoutGrants) {
365
372
  data_structure {
@@ -391,6 +398,13 @@ export const LEGACY_DATA_STRUCTURE_VERSION_QUERY = gql`
391
398
  user_name
392
399
  }
393
400
  user_id
401
+ user_group_id
402
+ source_group_name
403
+ user_group {
404
+ id
405
+ name
406
+ alias
407
+ }
394
408
  }
395
409
 
396
410
  classes
@@ -467,6 +481,7 @@ export const LEGACY_DATA_STRUCTURE_VERSION_QUERY = gql`
467
481
  name
468
482
  description
469
483
  }
484
+ links(resource_type: "business_concept")
470
485
  }
471
486
  children {
472
487
  id
@@ -599,6 +614,13 @@ export const buildDSVQuery = (loadStructureMetadata) => gql`
599
614
  user_name
600
615
  }
601
616
  user_id
617
+ user_group_id
618
+ source_group_name
619
+ user_group {
620
+ id
621
+ name
622
+ alias
623
+ }
602
624
  }
603
625
 
604
626
  classes
@@ -651,6 +673,7 @@ export const buildDSVQuery = (loadStructureMetadata) => gql`
651
673
  name
652
674
  description
653
675
  }
676
+ links(resource_type: "business_concept")
654
677
  }
655
678
  children {
656
679
  id
@@ -736,6 +759,14 @@ export const DATA_STRUCTURE_VERSION_GRANTS_QUERY = gql`
736
759
  user_name
737
760
  }
738
761
  user_id
762
+ user_group_id
763
+ current_user_group_member
764
+ source_group_name
765
+ user_group {
766
+ id
767
+ name
768
+ alias
769
+ }
739
770
  }
740
771
  }
741
772
  }
@@ -1,11 +1,21 @@
1
1
  import _ from "lodash/fp";
2
- import { lazy } from "react";
2
+ import { lazy, useState } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import Moment from "react-moment";
5
5
  import { useIntl } from "react-intl";
6
6
  import { Link } from "react-router";
7
7
  import { useParams } from "react-router";
8
- import { Grid, Segment, Header, Icon, List, Button } from "semantic-ui-react";
8
+ import {
9
+ Accordion,
10
+ Grid,
11
+ Segment,
12
+ Header,
13
+ Icon,
14
+ Label,
15
+ List,
16
+ Button,
17
+ Table,
18
+ } from "semantic-ui-react";
9
19
  import { linkTo } from "@truedat/core/routes";
10
20
  import { useGrant, useGrantRemoval, useStructure } from "../hooks/useGrant";
11
21
  import GrantCrumbs from "./GrantCrumbs";
@@ -110,8 +120,16 @@ GrantHeader.propTypes = {
110
120
  onStatusChange: PropTypes.func,
111
121
  };
112
122
 
123
+ const groupDisplayName = (userGroup) =>
124
+ userGroup
125
+ ? _.isEmpty(userGroup.alias)
126
+ ? userGroup.name
127
+ : userGroup.alias
128
+ : undefined;
129
+
113
130
  export const Grant = ({ grant, actions, loading, onStatusChange }) => {
114
131
  const { formatMessage } = useIntl();
132
+ const [membersOpen, setMembersOpen] = useState(false);
115
133
 
116
134
  const data_structure_version = _.pathOr(
117
135
  undefined,
@@ -119,10 +137,37 @@ export const Grant = ({ grant, actions, loading, onStatusChange }) => {
119
137
  )(grant);
120
138
  const data_structure = _.pathOr(undefined, "data_structure")(grant);
121
139
 
122
- const getTitle = ({ user, data_structure_version } = grant) => {
123
- return `${user?.user_name ? `(${user?.user_name})` : ""} -> ${
124
- data_structure_version?.name
125
- }`;
140
+ const userGroup = grant?.user_group;
141
+ const userGroupId = grant?.user_group_id;
142
+ const user = grant?.user;
143
+ const userId = grant?.user_id;
144
+ const sourceGroupName = grant?.source_group_name;
145
+ const userGroupText = groupDisplayName(userGroup);
146
+ const hasUsableUser = user && !_.isEmpty(user.full_name);
147
+ const isGroup = Boolean(userGroup || userGroupId || sourceGroupName);
148
+ const grantFor = isGroup
149
+ ? userGroupText ||
150
+ (userGroupId ? formatMessage({ id: "deleted.group" }) : sourceGroupName)
151
+ : hasUsableUser
152
+ ? user.full_name
153
+ : userId
154
+ ? formatMessage({ id: "deleted.user" })
155
+ : undefined;
156
+ const members = _.isArray(userGroup?.users) ? userGroup.users : [];
157
+
158
+ const getTitle = ({
159
+ user,
160
+ user_group,
161
+ source_group_name,
162
+ data_structure_version,
163
+ } = grant) => {
164
+ const groupText = groupDisplayName(user_group) || source_group_name;
165
+ const who = user?.user_name
166
+ ? `(${user.user_name})`
167
+ : groupText
168
+ ? `(${groupText})`
169
+ : "";
170
+ return `${who} -> ${data_structure_version?.name}`;
126
171
  };
127
172
  const path = _.flow(
128
173
  _.map("name"),
@@ -161,20 +206,65 @@ export const Grant = ({ grant, actions, loading, onStatusChange }) => {
161
206
  </List.Content>
162
207
  </List.Item>
163
208
  )}
164
- {grant?.user && (
209
+ {grantFor && (
165
210
  <List.Item>
166
211
  <List.Header>
167
- {formatMessage({ id: "grant.header.user" })}
212
+ {formatMessage({
213
+ id: isGroup
214
+ ? "grant.header.group"
215
+ : "grant.header.user",
216
+ })}
168
217
  </List.Header>
169
218
  <List.Description>
170
- <Header as="h4">
171
- <Header.Content>
172
- {grant?.user?.full_name}
173
- <Header.Subheader>
174
- {grant?.user?.email}
175
- </Header.Subheader>
176
- </Header.Content>
177
- </Header>
219
+ {userGroup ? (
220
+ <Accordion>
221
+ <Accordion.Title
222
+ active={membersOpen}
223
+ icon={false}
224
+ onClick={() => setMembersOpen(!membersOpen)}
225
+ >
226
+ <Label>{grantFor}</Label>
227
+ <Icon
228
+ name={
229
+ membersOpen ? "angle down" : "angle right"
230
+ }
231
+ />
232
+ </Accordion.Title>
233
+ <Accordion.Content active={membersOpen}>
234
+ <Table compact collapsing size="small">
235
+ <Table.Header>
236
+ <Table.Row>
237
+ <Table.HeaderCell>
238
+ {formatMessage({
239
+ id: "grantRequests.props.user_full_name",
240
+ })}
241
+ </Table.HeaderCell>
242
+ </Table.Row>
243
+ </Table.Header>
244
+ <Table.Body>
245
+ {members.map((member) => (
246
+ <Table.Row
247
+ key={member.id ?? member.user_name}
248
+ >
249
+ <Table.Cell>
250
+ {member.full_name}
251
+ </Table.Cell>
252
+ </Table.Row>
253
+ ))}
254
+ </Table.Body>
255
+ </Table>
256
+ </Accordion.Content>
257
+ </Accordion>
258
+ ) : hasUsableUser ? (
259
+ <Header as="h4">
260
+ <Header.Content>
261
+ {user.full_name}
262
+ <Header.Subheader>{user.email}</Header.Subheader>
263
+ </Header.Content>
264
+ </Header>
265
+ ) : (
266
+ <Label>{grantFor}</Label>
267
+ )}
178
268
  </List.Description>
179
269
  </List.Item>
180
270
  )}
@@ -51,9 +51,9 @@ export const GrantRequest = ({ grantRequest, template }) => {
51
51
  user ||
52
52
  userGroupText ||
53
53
  (userGroupId
54
- ? formatMessage({ id: "grantRequest.deleted.group" })
54
+ ? formatMessage({ id: "deleted.group" })
55
55
  : group
56
- ? formatMessage({ id: "grantRequest.deleted.user" })
56
+ ? formatMessage({ id: "deleted.user" })
57
57
  : undefined);
58
58
  const members = _.isArray(userGroup?.users) ? userGroup.users : [];
59
59
  const createdBy =
@@ -21,7 +21,6 @@ export const GrantRequestBulkApprovalPopup = ({
21
21
  return (
22
22
  <Popup
23
23
  on="click"
24
- modal
25
24
  onClose={() => setOpen(false)}
26
25
  onOpen={() => setOpen(true)}
27
26
  open={open}
@@ -0,0 +1,76 @@
1
+ import { useId } from "react";
2
+ import PropTypes from "prop-types";
3
+ import { useQuery } from "@apollo/client";
4
+ import { TEMPLATES_QUERY } from "@truedat/core/api/queries";
5
+ import { TemplateSelector } from "@truedat/core/components/TemplateSelector";
6
+ import DynamicForm from "@truedat/df/components/DynamicForm";
7
+ import { validateContent } from "@truedat/df/utils";
8
+
9
+ // Redux owns the selection. Query refreshes only resolve its definition;
10
+ // they must never select a default or clear a saved draft.
11
+ export default function GrantRequestTemplateForm({
12
+ name,
13
+ selectedTemplate,
14
+ disableSelector,
15
+ domainIds,
16
+ content,
17
+ isModification,
18
+ onNameChange,
19
+ onChange,
20
+ placeholder,
21
+ }) {
22
+ const instanceId = useId();
23
+ const { data, loading } = useQuery(TEMPLATES_QUERY, {
24
+ variables: { scope: "gr" },
25
+ fetchPolicy: "cache-and-network",
26
+ skip: !!disableSelector && !!selectedTemplate,
27
+ });
28
+ const templates = data?.templates ?? [];
29
+ const template =
30
+ selectedTemplate?.name === name
31
+ ? selectedTemplate
32
+ : templates.find((candidate) => candidate.name === name);
33
+
34
+ return (
35
+ <>
36
+ {!disableSelector && (
37
+ <TemplateSelector
38
+ templates={templates}
39
+ loading={loading}
40
+ selectedValue={template?.id}
41
+ onChange={(_event, { template }) => onNameChange(template?.name)}
42
+ placeholder={placeholder}
43
+ hideLabel
44
+ required
45
+ showSingleOption
46
+ />
47
+ )}
48
+ {template && (
49
+ <DynamicForm
50
+ instanceId={instanceId}
51
+ template={template}
52
+ content={content}
53
+ selectedDomain={
54
+ domainIds?.length ? { id: domainIds[0], ids: domainIds } : null
55
+ }
56
+ isModification={isModification}
57
+ onChange={(content) =>
58
+ onChange({ content, valid: validateContent(template)(content) })
59
+ }
60
+ />
61
+ )}
62
+ </>
63
+ );
64
+ }
65
+
66
+ GrantRequestTemplateForm.propTypes = {
67
+ name: PropTypes.string,
68
+ selectedTemplate: PropTypes.object,
69
+ disableSelector: PropTypes.bool,
70
+ domainIds: PropTypes.array,
71
+ content: PropTypes.object,
72
+ isModification: PropTypes.bool,
73
+ onNameChange: PropTypes.func.isRequired,
74
+ onChange: PropTypes.func.isRequired,
75
+ placeholder: PropTypes.string,
76
+ };
@@ -40,7 +40,15 @@ export const GrantsDownloadButton = ({
40
40
  const grants = searchData?.data;
41
41
 
42
42
  const headerLabels = _.flow(
43
- _.map((l) => [l, formatMessage({ id: `grants.props.${l}` })]),
43
+ _.map((l) => [
44
+ l,
45
+ formatMessage({
46
+ id:
47
+ l === "user_name"
48
+ ? "grants.props.user_or_group"
49
+ : `grants.props.${l}`,
50
+ }),
51
+ ]),
44
52
  _.fromPairs,
45
53
  )(staticLabels);
46
54
 
@@ -48,7 +48,10 @@ export const GrantsTable = ({ columns, setDownloading }) => {
48
48
  key={i}
49
49
  width={column.width}
50
50
  content={formatMessage({
51
- id: `grant.header.${column.name}`,
51
+ id:
52
+ column.name === "user"
53
+ ? "grants.props.user_or_group"
54
+ : `grant.header.${column.name}`,
52
55
  defaultMessage: column.name,
53
56
  })}
54
57
  sorted={
@@ -1,5 +1,5 @@
1
1
  import _ from "lodash/fp";
2
- import { useState } from "react";
2
+ import { useMemo, useState } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import queryString from "query-string";
5
5
  import { useLocation } from "react-router";
@@ -32,6 +32,7 @@ const getColumns = (columns, fields) =>
32
32
  _.filter((c) => _.any(columnPred(c))(fields))(columns);
33
33
 
34
34
  const PAGE_SIZE = 50;
35
+ const EMPTY_FIELDS = [];
35
36
 
36
37
  const ProfileActions = ({
37
38
  emptyFields,
@@ -151,13 +152,15 @@ export const StructureFields = ({
151
152
  : { after, before, first: PAGE_SIZE, search: q };
152
153
  const variables = { ...structureVariables, ...searchVariables };
153
154
  const { data, loading } = useQuery(DATA_FIELDS_QUERY, { variables });
154
- const fields = data?.dataFields?.page || [];
155
+ const fields = data?.dataFields?.page || EMPTY_FIELDS;
155
156
  const pageInfo = data?.dataFields?.pageInfo || {};
156
157
  const pageStructureIds = _.map("data_structure_id")(fields);
157
158
  const emptyFields = _.isEmpty(fields);
158
159
 
159
- const columns = useSelector((state) =>
160
- getColumns(getStructureFieldColumns(state), fields),
160
+ const structureFieldColumns = useSelector(getStructureFieldColumns);
161
+ const columns = useMemo(
162
+ () => getColumns(structureFieldColumns, fields),
163
+ [structureFieldColumns, fields],
161
164
  );
162
165
 
163
166
  const allCheckedInPage = _.every((structureId) =>