@truedat/df 8.6.2 → 8.6.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/df",
3
- "version": "8.6.2",
3
+ "version": "8.6.5",
4
4
  "description": "Truedat Web Data Quality Module",
5
5
  "sideEffects": false,
6
6
  "module": "src/index.js",
@@ -51,14 +51,14 @@
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.6.2",
54
+ "@truedat/test": "8.6.5",
55
55
  "identity-obj-proxy": "^3.0.0",
56
56
  "jest": "^29.7.0",
57
57
  "redux-saga-test-plan": "^4.0.6"
58
58
  },
59
59
  "dependencies": {
60
60
  "@apollo/client": "^3.13.8",
61
- "@truedat/core": "8.6.2",
61
+ "@truedat/core": "8.6.5",
62
62
  "axios": "^1.15.0",
63
63
  "graphql": "^16.11.0",
64
64
  "is-hotkey": "^0.2.0",
@@ -87,5 +87,5 @@
87
87
  "semantic-ui-react": "^3.0.0-beta.2",
88
88
  "swr": "^2.3.3"
89
89
  },
90
- "gitHead": "6afdcd7120f8c657a61a125fd95908464392b5ec"
90
+ "gitHead": "32ae31a344185df36f353858af39fbcb64d8d7f8"
91
91
  }
@@ -5,7 +5,7 @@ import { Icon, List, Container } from "semantic-ui-react";
5
5
  import OriginLabel from "@truedat/core/components/OriginLabel";
6
6
  import DomainPreview from "./widgets/DomainPreview";
7
7
  import UserGroupPreview from "./widgets/UserGroupPreview";
8
- import GroupPreview from "./widgets/GroupPreview";
8
+ import { GroupPreview } from "./widgets/GroupPreview";
9
9
  import HierarchyPreview from "./widgets/HierarchyPreview";
10
10
  import SystemPreview from "./widgets/SystemPreview";
11
11
  import FieldViewerValue from "./FieldViewerValue";
@@ -55,7 +55,7 @@ export const DynamicFieldValue = ({
55
55
  ) : type == "domain" ? (
56
56
  <DomainPreview domainIds={_.castArray(value)} />
57
57
  ) : type == "group" ? (
58
- <GroupPreview groupId={value} />
58
+ <GroupPreview groups={_.castArray(value)} />
59
59
  ) : type == "user_group" ? (
60
60
  <UserGroupPreview userGroupId={value} />
61
61
  ) : type == "hierarchy" ? (
@@ -40,8 +40,13 @@ const DynamicTableField = (field) => {
40
40
  const groupNameOrAlias = (group) =>
41
41
  _.isString(group) ? group : group?.alias || group?.name;
42
42
 
43
- const userGroupDisplayName = (value, values) => {
44
- const [_type, name] = value.split(":");
43
+ const stripGroupPrefix = (value) =>
44
+ _.isString(value) && value.startsWith("group:")
45
+ ? value.slice("group:".length)
46
+ : value;
47
+
48
+ const groupDisplayName = (rawName, values) => {
49
+ const name = stripGroupPrefix(rawName);
45
50
  const group = _.find(values?.processed_groups, (group) =>
46
51
  [groupNameOrAlias(group), _.get(group, "name")].includes(name)
47
52
  );
@@ -49,6 +54,11 @@ const userGroupDisplayName = (value, values) => {
49
54
  return group ? groupNameOrAlias(group) : name;
50
55
  };
51
56
 
57
+ const userGroupDisplayName = (value, values) => {
58
+ const [_type, name] = value.split(":");
59
+ return groupDisplayName(name, values);
60
+ };
61
+
52
62
  export const FieldViewerValue = ({
53
63
  label,
54
64
  multiple,
@@ -119,7 +129,21 @@ export const FieldViewerValue = ({
119
129
  return <ImagePreview value={value} />;
120
130
  case "system":
121
131
  return <SystemPreview value={value} />;
122
- case "group":
132
+ case "group": {
133
+ const displayName = groupDisplayName(value, values);
134
+
135
+ return multiple ? (
136
+ <Label>
137
+ <Icon name="group" />
138
+ {displayName}
139
+ </Label>
140
+ ) : (
141
+ <>
142
+ <Icon name="group" />
143
+ {displayName}
144
+ </>
145
+ );
146
+ }
123
147
  case "user_group": {
124
148
  const [type, name] = value.split(":");
125
149
  const displayName =
@@ -26,4 +26,33 @@ describe("<DynamicFieldValue />", () => {
26
26
  const wrapper = render(<DynamicFieldValue {...props} />);
27
27
  expect(wrapper).toMatchSnapshot();
28
28
  });
29
+
30
+ it("renders a single group value with a group icon", () => {
31
+ const rendered = render(
32
+ <DynamicFieldValue
33
+ label="label"
34
+ value={{ value: "Data Owners", origin: "user" }}
35
+ type="group"
36
+ widget="dropdown"
37
+ />
38
+ );
39
+
40
+ expect(rendered.getByText(/data owners/i)).toBeInTheDocument();
41
+ expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
42
+ });
43
+
44
+ it("renders multiple group values with a group icon each", () => {
45
+ const rendered = render(
46
+ <DynamicFieldValue
47
+ label="label"
48
+ value={{ value: ["Data Owners", "Quality"], origin: "user" }}
49
+ type="group"
50
+ widget="checkbox"
51
+ />
52
+ );
53
+
54
+ expect(rendered.getByText(/data owners/i)).toBeInTheDocument();
55
+ expect(rendered.getByText(/quality/i)).toBeInTheDocument();
56
+ expect(rendered.container.querySelectorAll(".group.icon")).toHaveLength(2);
57
+ });
29
58
  });
@@ -22,6 +22,9 @@ jest.mock("@truedat/core/hooks", () => {
22
22
 
23
23
  jest.mock("../widgets/GroupPreview", () => ({
24
24
  __esModule: true,
25
+ GroupPreview: ({ groups }) => (
26
+ <div data-testid="group-preview">{[].concat(groups).join(",")}</div>
27
+ ),
25
28
  default: ({ groupId }) => (
26
29
  <div data-testid="group-preview">{[].concat(groupId).join(",")}</div>
27
30
  ),
@@ -47,7 +50,7 @@ const groupTemplate = {
47
50
 
48
51
  const groupContent = {
49
52
  "group-field": {
50
- value: ["group:Group Alias"],
53
+ value: ["Group Alias"],
51
54
  origin: "user",
52
55
  },
53
56
  };
@@ -119,7 +122,7 @@ describe("<DynamicFormViewer />", () => {
119
122
  );
120
123
 
121
124
  expect(rendered.getByTestId("group-preview")).toHaveTextContent(
122
- "group:Group Alias"
125
+ "Group Alias"
123
126
  );
124
127
  });
125
128
 
@@ -132,7 +135,7 @@ describe("<DynamicFormViewer />", () => {
132
135
  );
133
136
 
134
137
  expect(rendered.getByTestId("group-preview")).toHaveTextContent(
135
- "group:Group Alias"
138
+ "Group Alias"
136
139
  );
137
140
  expect(rendered.container.querySelector("h3, h4")).not.toBeInTheDocument();
138
141
  });
@@ -155,7 +158,7 @@ describe("<DynamicFormViewer />", () => {
155
158
 
156
159
  expect(useTemplate).not.toHaveBeenCalled();
157
160
  expect(rendered.getByTestId("group-preview")).toHaveTextContent(
158
- "group:Group Alias"
161
+ "Group Alias"
159
162
  );
160
163
  });
161
164
 
@@ -195,7 +198,7 @@ describe("<DynamicFormViewer />", () => {
195
198
  domainIds: [1],
196
199
  });
197
200
  expect(rendered.getByTestId("group-preview")).toHaveTextContent(
198
- "group:Group Alias"
201
+ "Group Alias"
199
202
  );
200
203
  });
201
204
  });
@@ -180,7 +180,7 @@ describe("<FieldViewerValue />", () => {
180
180
  const rendered = render(
181
181
  <FieldViewerValue
182
182
  type="group"
183
- value="group:Data Owners"
183
+ value="Data Owners"
184
184
  multiple
185
185
  values={{
186
186
  processed_groups: [{ name: "Data Owners" }],
@@ -190,6 +190,23 @@ describe("<FieldViewerValue />", () => {
190
190
 
191
191
  expect(rendered.getByText("Data Owners")).toBeInTheDocument();
192
192
  expect(rendered.container.querySelector(".ui.label")).toBeInTheDocument();
193
+ expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
194
+ });
195
+
196
+ it("strips legacy group: prefix when rendering group values", () => {
197
+ const rendered = render(
198
+ <FieldViewerValue
199
+ type="group"
200
+ value="group:Data Owners"
201
+ multiple
202
+ values={{
203
+ processed_groups: [{ name: "Data Owners" }],
204
+ }}
205
+ />
206
+ );
207
+
208
+ expect(rendered.getByText("Data Owners")).toBeInTheDocument();
209
+ expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
193
210
  });
194
211
 
195
212
  it("renders user_group values using the raw name when they are not groups", () => {
@@ -1,21 +1,57 @@
1
1
  import _ from "lodash/fp";
2
2
  import PropTypes from "prop-types";
3
3
  import { useQuery } from "@apollo/client";
4
- import { Label } from "semantic-ui-react";
4
+ import { Icon, Label } from "semantic-ui-react";
5
+ import { Link } from "react-router";
6
+ import { linkTo } from "@truedat/core/routes";
5
7
  import { GROUP_PREVIEW_QUERY } from "../../api/queries";
6
- import { LinkedUserGroupLabel } from "./UserGroupPreview";
7
8
 
8
- const groupName = (group) => (_.isString(group) ? group : group.name);
9
- const groupId = (group) => (_.isString(group) ? null : group.id);
10
- const isGroup = (group) => groupName(group)?.startsWith("group:");
9
+ const stripGroupPrefix = (value) =>
10
+ _.isString(value) && value.startsWith("group:")
11
+ ? value.slice("group:".length)
12
+ : value;
13
+
14
+ const groupName = (group) => (_.isString(group) ? group : group?.name);
15
+ const groupId = (group) => (_.isString(group) ? null : group?.id);
16
+ const groupDisplayName = (group) => stripGroupPrefix(groupName(group));
17
+ const isUserValue = (group) =>
18
+ _.isString(groupName(group)) && groupName(group).startsWith("user:");
19
+
20
+ export const GroupLabel = ({ value }) => (
21
+ <Label>
22
+ <Icon name="group" />
23
+ {stripGroupPrefix(value)}
24
+ </Label>
25
+ );
26
+
27
+ GroupLabel.propTypes = {
28
+ value: PropTypes.string.isRequired,
29
+ };
30
+
31
+ export const LinkedGroupLabel = ({ group }) => {
32
+ const id = groupId(group);
33
+ const label = <GroupLabel value={groupName(group)} />;
34
+
35
+ return id ? <Link to={linkTo.GROUP({ id })}>{label}</Link> : label;
36
+ };
37
+
38
+ LinkedGroupLabel.propTypes = {
39
+ group: PropTypes.oneOfType([
40
+ PropTypes.shape({
41
+ id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
42
+ name: PropTypes.string.isRequired,
43
+ }),
44
+ PropTypes.string,
45
+ ]).isRequired,
46
+ };
11
47
 
12
48
  export const GroupPreview = ({ groups }) =>
13
49
  _.flow(
14
- _.filter(isGroup),
50
+ _.reject(isUserValue),
15
51
  _.map((group) => (
16
- <LinkedUserGroupLabel
17
- key={groupId(group) || groupName(group)}
18
- userGroup={group}
52
+ <LinkedGroupLabel
53
+ key={groupId(group) || groupDisplayName(group)}
54
+ group={group}
19
55
  />
20
56
  )),
21
57
  )(groups);
@@ -18,7 +18,19 @@ describe("<GroupPreview />", () => {
18
18
  jest.clearAllMocks();
19
19
  });
20
20
 
21
- it("renders group values with an icon instead of the prefix", () => {
21
+ it("renders group values with a group icon", () => {
22
+ const rendered = render(
23
+ <GroupPreview groups={[{ id: 123, name: "Group Alias" }]} />
24
+ );
25
+
26
+ expect(rendered.container.textContent).toBe("Group Alias");
27
+ expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
28
+ expect(
29
+ rendered.getByRole("link", { name: /group alias/i })
30
+ ).toHaveAttribute("href", "/groups/123");
31
+ });
32
+
33
+ it("strips the legacy group: prefix when rendering names", () => {
22
34
  const rendered = render(
23
35
  <GroupPreview groups={[{ id: 123, name: "group:Group Alias" }]} />
24
36
  );
@@ -33,7 +45,7 @@ describe("<GroupPreview />", () => {
33
45
  it("does not render user values", () => {
34
46
  const rendered = render(
35
47
  <GroupPreview
36
- groups={["user:John Doe", { id: 123, name: "group:Group Alias" }]}
48
+ groups={["user:John Doe", { id: 123, name: "Group Alias" }]}
37
49
  />
38
50
  );
39
51
 
@@ -46,7 +58,7 @@ describe("<GroupPreview />", () => {
46
58
 
47
59
  it("renders string groups without a link when there is no group id", () => {
48
60
  const rendered = render(
49
- <GroupPreview groups={["group:Standalone Group"]} />
61
+ <GroupPreview groups={["Standalone Group"]} />
50
62
  );
51
63
 
52
64
  expect(rendered.container.textContent).toBe("Standalone Group");
@@ -91,7 +103,7 @@ describe("<GroupPreview />", () => {
91
103
  loading: false,
92
104
  error: undefined,
93
105
  data: {
94
- groupDetails: [{ id: 123, name: "group:Group Alias" }],
106
+ groupDetails: [{ id: 123, name: "Group Alias" }],
95
107
  },
96
108
  });
97
109
 
@@ -81,10 +81,10 @@ describe("utils: validValues", () => {
81
81
 
82
82
  expect(
83
83
  filterValues(template)({
84
- role: { value: "group:alias1", origin: "default" },
84
+ role: { value: "alias1", origin: "default" },
85
85
  }),
86
86
  ).toEqual({
87
- role: { value: "group:alias1", origin: "default" },
87
+ role: { value: "alias1", origin: "default" },
88
88
  });
89
89
 
90
90
  expect(
@@ -94,6 +94,14 @@ describe("utils: validValues", () => {
94
94
  ).toEqual({
95
95
  role: { value: null, origin: "default" },
96
96
  });
97
+
98
+ expect(
99
+ filterValues(template)({
100
+ role: { value: "group:alias1", origin: "default" },
101
+ }),
102
+ ).toEqual({
103
+ role: { value: null, origin: "default" },
104
+ });
97
105
  });
98
106
 
99
107
  it("should nullify unlisted fixed values", () => {
@@ -138,14 +138,14 @@ describe("utils: parseFieldOptions", () => {
138
138
  processed_groups: ["alias1", "group2"],
139
139
  },
140
140
  };
141
- const content = { field: { value: "group:alias1", origin: "default" } };
141
+ const content = { field: { value: "alias1", origin: "default" } };
142
142
  const result = parseFormatFieldOptions(content)(field);
143
143
  expect(result).toEqual({
144
144
  ...field,
145
- value: { value: "group:alias1", origin: "default" },
145
+ value: { value: "alias1", origin: "default" },
146
146
  parsedValues: [
147
- { value: "group:alias1", text: "alias1", icon: "group" },
148
- { value: "group:group2", text: "group2", icon: "group" },
147
+ { value: "alias1", text: "alias1", icon: "group" },
148
+ { value: "group2", text: "group2", icon: "group" },
149
149
  ],
150
150
  });
151
151
  });
@@ -20,10 +20,7 @@ const userGroupValues = (field) =>
20
20
 
21
21
  const groupValues = _.flow(
22
22
  _.get("processed_groups"),
23
- _.map((group) => {
24
- const name = groupNameOrAlias(group);
25
- return `group:${name}`;
26
- }),
23
+ _.map(groupNameOrAlias),
27
24
  );
28
25
 
29
26
  export const validValues = _.flow(
@@ -47,19 +47,20 @@ const doParseFieldOptions = (formatMessage, content, selectedDomain, field) => {
47
47
  const groupNameOrAlias = (group) =>
48
48
  typeof group === "string" ? group : group.alias || group.name;
49
49
  const parseRoleGroups = (values) => {
50
+ const isGroupOnly = field?.type === "group";
50
51
  const groups = _.flow(
51
52
  _.get("processed_groups"),
52
53
  _.map((group) => {
53
54
  const name = groupNameOrAlias(group);
54
55
  return {
55
- value: `group:${name}`,
56
+ value: isGroupOnly ? name : `group:${name}`,
56
57
  text: name,
57
58
  icon: "group",
58
59
  };
59
60
  }),
60
61
  )(values);
61
62
 
62
- if (field?.type === "group") return groups;
63
+ if (isGroupOnly) return groups;
63
64
 
64
65
  const users = _.flow(
65
66
  _.get("processed_users"),