@truedat/df 8.6.6 → 8.7.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 +5 -5
- package/src/components/DynamicFieldValue.js +3 -2
- package/src/components/FieldViewerValue.js +12 -7
- package/src/components/SelectDynamicFormWithTranslations.js +14 -15
- package/src/components/__tests__/DynamicFieldValue.spec.js +39 -5
- package/src/components/__tests__/DynamicFormViewer.spec.js +27 -23
- package/src/components/widgets/GroupPreview.js +7 -4
- package/src/components/widgets/UserGroupPreview.js +12 -4
- package/src/components/widgets/__tests__/GroupPreview.spec.js +81 -28
- package/src/components/widgets/__tests__/UserGroupPreview.spec.js +54 -18
- package/src/templates/utils/__tests__/filterValues.spec.js +82 -12
- package/src/templates/utils/__tests__/parseFieldOptions.spec.js +102 -0
- package/src/templates/utils/filterValues.js +44 -19
- package/src/templates/utils/parseFieldOptions.js +134 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/df",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.7.0",
|
|
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.
|
|
54
|
+
"@truedat/test": "8.7.0",
|
|
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.
|
|
61
|
+
"@truedat/core": "8.7.0",
|
|
62
62
|
"axios": "^1.15.0",
|
|
63
63
|
"graphql": "^16.11.0",
|
|
64
64
|
"is-hotkey": "^0.2.0",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"react-intl": "^7.1.11",
|
|
79
79
|
"react-moment": "^1.1.3",
|
|
80
80
|
"react-redux": "^9.2.0",
|
|
81
|
-
"react-router": "^7.
|
|
81
|
+
"react-router": "^7.15.0",
|
|
82
82
|
"react-sortable-hoc": "^2.0.0",
|
|
83
83
|
"redux": "^5.0.1",
|
|
84
84
|
"redux-saga": "^1.3.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": "
|
|
90
|
+
"gitHead": "375298315086154cea326bfaebc504d6e45873ed"
|
|
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
|
|
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";
|
|
@@ -23,6 +23,7 @@ export const DynamicFieldValue = ({
|
|
|
23
23
|
value: null,
|
|
24
24
|
origin: "default",
|
|
25
25
|
};
|
|
26
|
+
|
|
26
27
|
const { formatMessage } = useIntl();
|
|
27
28
|
return (
|
|
28
29
|
<List.Item>
|
|
@@ -55,7 +56,7 @@ export const DynamicFieldValue = ({
|
|
|
55
56
|
) : type == "domain" ? (
|
|
56
57
|
<DomainPreview domainIds={_.castArray(value)} />
|
|
57
58
|
) : type == "group" ? (
|
|
58
|
-
<GroupPreview
|
|
59
|
+
<GroupPreview groupId={value} />
|
|
59
60
|
) : type == "user_group" ? (
|
|
60
61
|
<UserGroupPreview userGroupId={value} />
|
|
61
62
|
) : type == "hierarchy" ? (
|
|
@@ -37,21 +37,26 @@ const DynamicTableField = (field) => {
|
|
|
37
37
|
);
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
const groupNameOrAlias = (group) =>
|
|
41
|
-
_.isString(group) ? group : group?.alias || group?.name;
|
|
42
|
-
|
|
43
40
|
const stripGroupPrefix = (value) =>
|
|
44
41
|
_.isString(value) && value.startsWith("group:")
|
|
45
42
|
? value.slice("group:".length)
|
|
46
43
|
: value;
|
|
47
44
|
|
|
45
|
+
const groupLabel = (group) =>
|
|
46
|
+
_.isString(group) ? group : group?.alias || group?.name;
|
|
47
|
+
|
|
48
|
+
const groupMatchesName = (group, name) => {
|
|
49
|
+
if (_.isString(group)) return group === name;
|
|
50
|
+
return [group?.name, group?.alias].includes(name);
|
|
51
|
+
};
|
|
52
|
+
|
|
48
53
|
const groupDisplayName = (rawName, values) => {
|
|
49
54
|
const name = stripGroupPrefix(rawName);
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
);
|
|
55
|
+
const groups =
|
|
56
|
+
values?.processed_groups_details || values?.processed_groups || [];
|
|
57
|
+
const group = _.find(groups, (group) => groupMatchesName(group, name));
|
|
53
58
|
|
|
54
|
-
return group ?
|
|
59
|
+
return group ? groupLabel(group) : name;
|
|
55
60
|
};
|
|
56
61
|
|
|
57
62
|
const userGroupDisplayName = (value, values) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
|
-
import {
|
|
2
|
+
import { useState } from "react";
|
|
3
3
|
import PropTypes from "prop-types";
|
|
4
4
|
import { Grid, Header } from "semantic-ui-react";
|
|
5
5
|
import { TemplateSelector } from "@truedat/core/components";
|
|
@@ -29,13 +29,15 @@ export default function SelectDynamicFormWithTranslations({
|
|
|
29
29
|
showTemplateSectionTitle,
|
|
30
30
|
}) {
|
|
31
31
|
const [thisTemplate, setThisTemplate] = useState();
|
|
32
|
-
const template = _.isEmpty(selectedTemplate)
|
|
32
|
+
const template = _.isEmpty(selectedTemplate)
|
|
33
|
+
? thisTemplate
|
|
34
|
+
: selectedTemplate;
|
|
33
35
|
const { enabledLangs } = useLanguage();
|
|
34
36
|
const { formatMessage } = useIntl();
|
|
35
37
|
const domainIdsArray = _.isArray(domainIds) ? domainIds : [];
|
|
36
|
-
const domain =
|
|
38
|
+
const domain =
|
|
39
|
+
_.size(domainIdsArray) > 0 ? { id: _.head(domainIdsArray) } : null;
|
|
37
40
|
const hasDomainSelection = domain !== null;
|
|
38
|
-
const domainKey = domainIdsArray.join(",");
|
|
39
41
|
const templateTitle =
|
|
40
42
|
template &&
|
|
41
43
|
formatMessage({
|
|
@@ -50,17 +52,11 @@ export default function SelectDynamicFormWithTranslations({
|
|
|
50
52
|
});
|
|
51
53
|
};
|
|
52
54
|
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
if (actionKey === "create") {
|
|
55
|
-
setThisTemplate(undefined);
|
|
56
|
-
onChangeTemplate(undefined);
|
|
57
|
-
}
|
|
58
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
59
|
-
}, [domainKey]);
|
|
60
|
-
|
|
61
55
|
const applyTemplateToLangs = (tpl) => {
|
|
62
56
|
enabledLangs.forEach((lang) => {
|
|
63
|
-
const content = tpl
|
|
57
|
+
const content = tpl
|
|
58
|
+
? applyTemplate(tpl)(i18nContent[lang] || {}, domain?.id)
|
|
59
|
+
: {};
|
|
64
60
|
handleChange(content, tpl, lang);
|
|
65
61
|
});
|
|
66
62
|
};
|
|
@@ -89,10 +85,13 @@ export default function SelectDynamicFormWithTranslations({
|
|
|
89
85
|
|
|
90
86
|
return (
|
|
91
87
|
<>
|
|
92
|
-
{(beforeSelector ||
|
|
88
|
+
{(beforeSelector ||
|
|
89
|
+
(hasDomainSelection && (!actionKey || actionKey === "create"))) && (
|
|
93
90
|
<Grid stackable>
|
|
94
91
|
<Grid.Row columns={beforeSelector ? 2 : 1} verticalAlign="bottom">
|
|
95
|
-
{beforeSelector ?
|
|
92
|
+
{beforeSelector ? (
|
|
93
|
+
<Grid.Column>{beforeSelector}</Grid.Column>
|
|
94
|
+
) : null}
|
|
96
95
|
{hasDomainSelection && (!actionKey || actionKey === "create") ? (
|
|
97
96
|
<Grid.Column>
|
|
98
97
|
<TemplateSelector
|
|
@@ -1,7 +1,22 @@
|
|
|
1
|
+
import { useQuery } from "@apollo/client";
|
|
1
2
|
import { render } from "@truedat/test/render";
|
|
2
3
|
import { DynamicFieldValue } from "../DynamicFieldValue";
|
|
3
4
|
|
|
5
|
+
jest.mock("@apollo/client", () => {
|
|
6
|
+
const originalModule = jest.requireActual("@apollo/client");
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
__esModule: true,
|
|
10
|
+
...originalModule,
|
|
11
|
+
useQuery: jest.fn(),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
|
|
4
15
|
describe("<DynamicFieldValue />", () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
5
20
|
it("matches the latest snapshot", () => {
|
|
6
21
|
const props = {
|
|
7
22
|
label: "label",
|
|
@@ -28,31 +43,50 @@ describe("<DynamicFieldValue />", () => {
|
|
|
28
43
|
});
|
|
29
44
|
|
|
30
45
|
it("renders a single group value with a group icon", () => {
|
|
46
|
+
useQuery.mockReturnValue({
|
|
47
|
+
loading: false,
|
|
48
|
+
error: undefined,
|
|
49
|
+
data: {
|
|
50
|
+
groupDetails: [{ id: 1, name: "group:foo bar" }],
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
31
54
|
const rendered = render(
|
|
32
55
|
<DynamicFieldValue
|
|
33
56
|
label="label"
|
|
34
|
-
value={{ value: "
|
|
57
|
+
value={{ value: "foo bar", origin: "user" }}
|
|
35
58
|
type="group"
|
|
36
59
|
widget="dropdown"
|
|
37
60
|
/>
|
|
38
61
|
);
|
|
39
62
|
|
|
40
|
-
expect(rendered.getByText(/
|
|
63
|
+
expect(rendered.getByText(/foo bar/i)).toBeInTheDocument();
|
|
41
64
|
expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
|
|
42
65
|
});
|
|
43
66
|
|
|
44
67
|
it("renders multiple group values with a group icon each", () => {
|
|
68
|
+
useQuery.mockReturnValue({
|
|
69
|
+
loading: false,
|
|
70
|
+
error: undefined,
|
|
71
|
+
data: {
|
|
72
|
+
groupDetails: [
|
|
73
|
+
{ id: 1, name: "group:foo bar" },
|
|
74
|
+
{ id: 2, name: "group:bar baz" },
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
45
79
|
const rendered = render(
|
|
46
80
|
<DynamicFieldValue
|
|
47
81
|
label="label"
|
|
48
|
-
value={{ value: ["
|
|
82
|
+
value={{ value: ["foo bar", "bar baz"], origin: "user" }}
|
|
49
83
|
type="group"
|
|
50
84
|
widget="checkbox"
|
|
51
85
|
/>
|
|
52
86
|
);
|
|
53
87
|
|
|
54
|
-
expect(rendered.getByText(/
|
|
55
|
-
expect(rendered.getByText(/
|
|
88
|
+
expect(rendered.getByText(/foo bar/i)).toBeInTheDocument();
|
|
89
|
+
expect(rendered.getByText(/bar baz/i)).toBeInTheDocument();
|
|
56
90
|
expect(rendered.container.querySelectorAll(".group.icon")).toHaveLength(2);
|
|
57
91
|
});
|
|
58
92
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useQuery } from "@apollo/client";
|
|
1
2
|
import { render } from "@truedat/test/render";
|
|
2
3
|
import { useTemplate } from "@truedat/core/hooks";
|
|
3
4
|
import { markdownToHtml } from "@truedat/core/components/Markdown";
|
|
@@ -5,6 +6,16 @@ import DynamicFormViewerFetcher, {
|
|
|
5
6
|
DynamicFormViewer,
|
|
6
7
|
} from "../DynamicFormViewer";
|
|
7
8
|
|
|
9
|
+
jest.mock("@apollo/client", () => {
|
|
10
|
+
const originalModule = jest.requireActual("@apollo/client");
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
__esModule: true,
|
|
14
|
+
...originalModule,
|
|
15
|
+
useQuery: jest.fn(),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
8
19
|
jest.mock("@truedat/core/components/Loading", () => ({
|
|
9
20
|
__esModule: true,
|
|
10
21
|
default: () => <div data-testid="loading" />,
|
|
@@ -20,16 +31,6 @@ jest.mock("@truedat/core/hooks", () => {
|
|
|
20
31
|
};
|
|
21
32
|
});
|
|
22
33
|
|
|
23
|
-
jest.mock("../widgets/GroupPreview", () => ({
|
|
24
|
-
__esModule: true,
|
|
25
|
-
GroupPreview: ({ groups }) => (
|
|
26
|
-
<div data-testid="group-preview">{[].concat(groups).join(",")}</div>
|
|
27
|
-
),
|
|
28
|
-
default: ({ groupId }) => (
|
|
29
|
-
<div data-testid="group-preview">{[].concat(groupId).join(",")}</div>
|
|
30
|
-
),
|
|
31
|
-
}));
|
|
32
|
-
|
|
33
34
|
const groupTemplate = {
|
|
34
35
|
scope: "bg",
|
|
35
36
|
content: [
|
|
@@ -50,7 +51,7 @@ const groupTemplate = {
|
|
|
50
51
|
|
|
51
52
|
const groupContent = {
|
|
52
53
|
"group-field": {
|
|
53
|
-
value: ["
|
|
54
|
+
value: ["foo bar"],
|
|
54
55
|
origin: "user",
|
|
55
56
|
},
|
|
56
57
|
};
|
|
@@ -75,6 +76,13 @@ const unnamedGroupTemplate = {
|
|
|
75
76
|
describe("<DynamicFormViewer />", () => {
|
|
76
77
|
beforeEach(() => {
|
|
77
78
|
jest.clearAllMocks();
|
|
79
|
+
useQuery.mockReturnValue({
|
|
80
|
+
loading: false,
|
|
81
|
+
error: undefined,
|
|
82
|
+
data: {
|
|
83
|
+
groupDetails: [{ id: 1, name: "group:foo bar" }],
|
|
84
|
+
},
|
|
85
|
+
});
|
|
78
86
|
useTemplate.mockReturnValue({
|
|
79
87
|
data: {},
|
|
80
88
|
loading: false,
|
|
@@ -121,9 +129,8 @@ describe("<DynamicFormViewer />", () => {
|
|
|
121
129
|
<DynamicFormViewer template={groupTemplate} content={groupContent} />
|
|
122
130
|
);
|
|
123
131
|
|
|
124
|
-
expect(rendered.
|
|
125
|
-
|
|
126
|
-
);
|
|
132
|
+
expect(rendered.getByText(/foo bar/i)).toBeInTheDocument();
|
|
133
|
+
expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
|
|
127
134
|
});
|
|
128
135
|
|
|
129
136
|
it("renders groups without heading when the group name is missing", () => {
|
|
@@ -134,9 +141,8 @@ describe("<DynamicFormViewer />", () => {
|
|
|
134
141
|
/>
|
|
135
142
|
);
|
|
136
143
|
|
|
137
|
-
expect(rendered.
|
|
138
|
-
|
|
139
|
-
);
|
|
144
|
+
expect(rendered.getByText(/foo bar/i)).toBeInTheDocument();
|
|
145
|
+
expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
|
|
140
146
|
expect(rendered.container.querySelector("h3, h4")).not.toBeInTheDocument();
|
|
141
147
|
});
|
|
142
148
|
|
|
@@ -157,9 +163,8 @@ describe("<DynamicFormViewer />", () => {
|
|
|
157
163
|
);
|
|
158
164
|
|
|
159
165
|
expect(useTemplate).not.toHaveBeenCalled();
|
|
160
|
-
expect(rendered.
|
|
161
|
-
|
|
162
|
-
);
|
|
166
|
+
expect(rendered.getByText(/foo bar/i)).toBeInTheDocument();
|
|
167
|
+
expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
|
|
163
168
|
});
|
|
164
169
|
|
|
165
170
|
it("renders a loader while fetching string templates", () => {
|
|
@@ -197,8 +202,7 @@ describe("<DynamicFormViewer />", () => {
|
|
|
197
202
|
name: "group-template",
|
|
198
203
|
domainIds: [1],
|
|
199
204
|
});
|
|
200
|
-
expect(rendered.
|
|
201
|
-
|
|
202
|
-
);
|
|
205
|
+
expect(rendered.getByText(/foo bar/i)).toBeInTheDocument();
|
|
206
|
+
expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
|
|
203
207
|
});
|
|
204
208
|
});
|
|
@@ -17,6 +17,9 @@ const groupDisplayName = (group) => stripGroupPrefix(groupName(group));
|
|
|
17
17
|
const isUserValue = (group) =>
|
|
18
18
|
_.isString(groupName(group)) && groupName(group).startsWith("user:");
|
|
19
19
|
|
|
20
|
+
const normalizeGroupId = (value) =>
|
|
21
|
+
value.startsWith("group:") ? value : `group:${value}`;
|
|
22
|
+
|
|
20
23
|
export const GroupLabel = ({ value }) => (
|
|
21
24
|
<Label>
|
|
22
25
|
<Icon name="group" />
|
|
@@ -30,7 +33,7 @@ GroupLabel.propTypes = {
|
|
|
30
33
|
|
|
31
34
|
export const LinkedGroupLabel = ({ group }) => {
|
|
32
35
|
const id = groupId(group);
|
|
33
|
-
const label = <GroupLabel value={
|
|
36
|
+
const label = <GroupLabel value={groupDisplayName(group)} />;
|
|
34
37
|
|
|
35
38
|
return id ? <Link to={linkTo.GROUP({ id })}>{label}</Link> : label;
|
|
36
39
|
};
|
|
@@ -53,7 +56,7 @@ export const GroupPreview = ({ groups }) =>
|
|
|
53
56
|
key={groupId(group) || groupDisplayName(group)}
|
|
54
57
|
group={group}
|
|
55
58
|
/>
|
|
56
|
-
))
|
|
59
|
+
))
|
|
57
60
|
)(groups);
|
|
58
61
|
|
|
59
62
|
GroupPreview.propTypes = {
|
|
@@ -65,13 +68,13 @@ GroupPreview.propTypes = {
|
|
|
65
68
|
name: PropTypes.string.isRequired,
|
|
66
69
|
}),
|
|
67
70
|
PropTypes.string,
|
|
68
|
-
])
|
|
71
|
+
])
|
|
69
72
|
).isRequired,
|
|
70
73
|
};
|
|
71
74
|
|
|
72
75
|
export const GroupPreviewLoader = ({ groupId }) => {
|
|
73
76
|
const { loading, error, data } = useQuery(GROUP_PREVIEW_QUERY, {
|
|
74
|
-
variables: { ids: _.castArray(groupId) },
|
|
77
|
+
variables: { ids: _.map(normalizeGroupId, _.castArray(groupId)) },
|
|
75
78
|
});
|
|
76
79
|
if (error) return null;
|
|
77
80
|
return loading ? (
|
|
@@ -17,11 +17,19 @@ const userGroupName = (userGroup) =>
|
|
|
17
17
|
const userGroupId = (userGroup) =>
|
|
18
18
|
_.isString(userGroup) ? null : userGroup.id;
|
|
19
19
|
|
|
20
|
+
const userGroupDisplayName = (userGroup) =>
|
|
21
|
+
typeAndName(userGroupName(userGroup)).name;
|
|
22
|
+
|
|
20
23
|
const isGroup = (userGroup) => userGroupName(userGroup)?.startsWith("group:");
|
|
21
24
|
|
|
25
|
+
const normalizeUserGroupId = (value) => {
|
|
26
|
+
if (!_.isString(value)) return value;
|
|
27
|
+
if (value.startsWith("user:") || value.startsWith("group:")) return value;
|
|
28
|
+
return `group:${value}`;
|
|
29
|
+
};
|
|
30
|
+
|
|
22
31
|
export const UserGroupLabel = ({ value }) => {
|
|
23
32
|
const { type, name } = typeAndName(value);
|
|
24
|
-
|
|
25
33
|
return (
|
|
26
34
|
<Label>
|
|
27
35
|
<Icon name={type} />
|
|
@@ -58,7 +66,7 @@ LinkedUserGroupLabel.propTypes = {
|
|
|
58
66
|
export const UserGroupPreview = ({ userGroups }) =>
|
|
59
67
|
_.map((userGroup) => (
|
|
60
68
|
<LinkedUserGroupLabel
|
|
61
|
-
key={userGroupId(userGroup) ||
|
|
69
|
+
key={userGroupId(userGroup) || userGroupDisplayName(userGroup)}
|
|
62
70
|
userGroup={userGroup}
|
|
63
71
|
/>
|
|
64
72
|
))(userGroups);
|
|
@@ -71,13 +79,13 @@ UserGroupPreview.propTypes = {
|
|
|
71
79
|
name: PropTypes.string.isRequired,
|
|
72
80
|
}),
|
|
73
81
|
PropTypes.string,
|
|
74
|
-
])
|
|
82
|
+
])
|
|
75
83
|
).isRequired,
|
|
76
84
|
};
|
|
77
85
|
|
|
78
86
|
export const UserGroupPreviewLoader = ({ userGroupId }) => {
|
|
79
87
|
const { loading, error, data } = useQuery(USER_GROUP_PREVIEW_QUERY, {
|
|
80
|
-
variables: { ids: _.castArray(userGroupId) },
|
|
88
|
+
variables: { ids: _.map(normalizeUserGroupId, _.castArray(userGroupId)) },
|
|
81
89
|
});
|
|
82
90
|
if (error) return null;
|
|
83
91
|
return loading ? (
|
|
@@ -20,36 +20,36 @@ describe("<GroupPreview />", () => {
|
|
|
20
20
|
|
|
21
21
|
it("renders group values with a group icon", () => {
|
|
22
22
|
const rendered = render(
|
|
23
|
-
<GroupPreview groups={[{ id: 123, name: "
|
|
23
|
+
<GroupPreview groups={[{ id: 123, name: "foo bar" }]} />
|
|
24
24
|
);
|
|
25
25
|
|
|
26
|
-
expect(rendered.container.textContent).toBe("
|
|
26
|
+
expect(rendered.container.textContent).toBe("foo bar");
|
|
27
27
|
expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
|
|
28
|
-
expect(
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
expect(rendered.getByRole("link", { name: /foo bar/i })).toHaveAttribute(
|
|
29
|
+
"href",
|
|
30
|
+
"/groups/123"
|
|
31
|
+
);
|
|
31
32
|
});
|
|
32
33
|
|
|
33
34
|
it("strips the legacy group: prefix when rendering names", () => {
|
|
34
35
|
const rendered = render(
|
|
35
|
-
<GroupPreview groups={[{ id: 123, name: "group:
|
|
36
|
+
<GroupPreview groups={[{ id: 123, name: "group:foo bar" }]} />
|
|
36
37
|
);
|
|
37
38
|
|
|
38
|
-
expect(rendered.container.textContent).toBe("
|
|
39
|
+
expect(rendered.container.textContent).toBe("foo bar");
|
|
39
40
|
expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
|
|
40
|
-
expect(
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
expect(rendered.getByRole("link", { name: /foo bar/i })).toHaveAttribute(
|
|
42
|
+
"href",
|
|
43
|
+
"/groups/123"
|
|
44
|
+
);
|
|
43
45
|
});
|
|
44
46
|
|
|
45
47
|
it("does not render user values", () => {
|
|
46
48
|
const rendered = render(
|
|
47
|
-
<GroupPreview
|
|
48
|
-
groups={["user:John Doe", { id: 123, name: "Group Alias" }]}
|
|
49
|
-
/>
|
|
49
|
+
<GroupPreview groups={["user:baz qux", { id: 123, name: "foo bar" }]} />
|
|
50
50
|
);
|
|
51
51
|
|
|
52
|
-
expect(rendered.container.textContent).toBe("
|
|
52
|
+
expect(rendered.container.textContent).toBe("foo bar");
|
|
53
53
|
expect(
|
|
54
54
|
rendered.container.querySelector(".user.icon")
|
|
55
55
|
).not.toBeInTheDocument();
|
|
@@ -57,14 +57,22 @@ describe("<GroupPreview />", () => {
|
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
it("renders string groups without a link when there is no group id", () => {
|
|
60
|
-
const rendered = render(
|
|
61
|
-
|
|
62
|
-
);
|
|
60
|
+
const rendered = render(<GroupPreview groups={["lone foo bar"]} />);
|
|
61
|
+
|
|
62
|
+
expect(rendered.container.textContent).toBe("lone foo bar");
|
|
63
|
+
expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
|
|
64
|
+
expect(
|
|
65
|
+
rendered.queryByRole("link", { name: /lone foo bar/i })
|
|
66
|
+
).not.toBeInTheDocument();
|
|
67
|
+
});
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
it("strips the legacy group: prefix from string values stored in the database", () => {
|
|
70
|
+
const rendered = render(<GroupPreview groups={["group:foo bar"]} />);
|
|
71
|
+
|
|
72
|
+
expect(rendered.container.textContent).toBe("foo bar");
|
|
65
73
|
expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
|
|
66
74
|
expect(
|
|
67
|
-
rendered.queryByRole("link", { name: /
|
|
75
|
+
rendered.queryByRole("link", { name: /foo bar/i })
|
|
68
76
|
).not.toBeInTheDocument();
|
|
69
77
|
});
|
|
70
78
|
|
|
@@ -75,10 +83,10 @@ describe("<GroupPreview />", () => {
|
|
|
75
83
|
data: undefined,
|
|
76
84
|
});
|
|
77
85
|
|
|
78
|
-
const rendered = render(<GroupPreviewLoader groupId="
|
|
86
|
+
const rendered = render(<GroupPreviewLoader groupId="foo bar" />);
|
|
79
87
|
|
|
80
88
|
expect(useQuery).toHaveBeenCalledWith(GROUP_PREVIEW_QUERY, {
|
|
81
|
-
variables: { ids: ["
|
|
89
|
+
variables: { ids: ["group:foo bar"] },
|
|
82
90
|
});
|
|
83
91
|
expect(rendered.container.firstChild).toBeNull();
|
|
84
92
|
});
|
|
@@ -90,28 +98,73 @@ describe("<GroupPreview />", () => {
|
|
|
90
98
|
data: undefined,
|
|
91
99
|
});
|
|
92
100
|
|
|
93
|
-
const rendered = render(<GroupPreviewLoader groupId={["
|
|
101
|
+
const rendered = render(<GroupPreviewLoader groupId={["bar baz"]} />);
|
|
94
102
|
|
|
95
103
|
expect(useQuery).toHaveBeenCalledWith(GROUP_PREVIEW_QUERY, {
|
|
96
|
-
variables: { ids: ["
|
|
104
|
+
variables: { ids: ["group:bar baz"] },
|
|
97
105
|
});
|
|
98
106
|
expect(rendered.getByText(/\.\.\./i)).toBeInTheDocument();
|
|
99
107
|
});
|
|
100
108
|
|
|
109
|
+
it("normalizes unprefixed group ids before querying group details", () => {
|
|
110
|
+
useQuery.mockReturnValue({
|
|
111
|
+
loading: false,
|
|
112
|
+
error: undefined,
|
|
113
|
+
data: {
|
|
114
|
+
groupDetails: [{ id: 123, name: "group:foo bar" }],
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const rendered = render(<GroupPreviewLoader groupId="foo bar" />);
|
|
119
|
+
|
|
120
|
+
expect(useQuery).toHaveBeenCalledWith(GROUP_PREVIEW_QUERY, {
|
|
121
|
+
variables: { ids: ["group:foo bar"] },
|
|
122
|
+
});
|
|
123
|
+
expect(rendered.getByRole("link", { name: /foo bar/i })).toHaveAttribute(
|
|
124
|
+
"href",
|
|
125
|
+
"/groups/123"
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("keeps legacy group: prefixed values when querying group details", () => {
|
|
130
|
+
useQuery.mockReturnValue({
|
|
131
|
+
loading: false,
|
|
132
|
+
error: undefined,
|
|
133
|
+
data: {
|
|
134
|
+
groupDetails: [{ id: 123, name: "group:foo bar" }],
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const rendered = render(<GroupPreviewLoader groupId="group:foo bar" />);
|
|
139
|
+
|
|
140
|
+
expect(useQuery).toHaveBeenCalledWith(GROUP_PREVIEW_QUERY, {
|
|
141
|
+
variables: { ids: ["group:foo bar"] },
|
|
142
|
+
});
|
|
143
|
+
expect(rendered.getByRole("link", { name: /foo bar/i })).toHaveAttribute(
|
|
144
|
+
"href",
|
|
145
|
+
"/groups/123"
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
101
149
|
it("renders the fetched group preview when loading finishes", () => {
|
|
102
150
|
useQuery.mockReturnValue({
|
|
103
151
|
loading: false,
|
|
104
152
|
error: undefined,
|
|
105
153
|
data: {
|
|
106
|
-
groupDetails: [{ id: 123, name: "
|
|
154
|
+
groupDetails: [{ id: 123, name: "foo bar" }],
|
|
107
155
|
},
|
|
108
156
|
});
|
|
109
157
|
|
|
110
|
-
const rendered = render(<GroupPreviewLoader groupId="
|
|
158
|
+
const rendered = render(<GroupPreviewLoader groupId="foo bar" />);
|
|
111
159
|
|
|
112
|
-
expect(
|
|
113
|
-
|
|
114
|
-
)
|
|
160
|
+
expect(useQuery).toHaveBeenCalledWith(GROUP_PREVIEW_QUERY, {
|
|
161
|
+
variables: { ids: ["group:foo bar"] },
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(rendered.getByRole("link", { name: /foo bar/i })).toHaveAttribute(
|
|
165
|
+
"href",
|
|
166
|
+
"/groups/123"
|
|
167
|
+
);
|
|
115
168
|
expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
|
|
116
169
|
});
|
|
117
170
|
});
|
|
@@ -20,10 +20,10 @@ describe("<UserGroupPreview />", () => {
|
|
|
20
20
|
|
|
21
21
|
it("renders users and groups with icons instead of prefixes", () => {
|
|
22
22
|
const rendered = render(
|
|
23
|
-
<UserGroupPreview userGroups={["user:
|
|
23
|
+
<UserGroupPreview userGroups={["user:baz qux", "group:foo bar"]} />
|
|
24
24
|
);
|
|
25
25
|
|
|
26
|
-
expect(rendered.container.textContent).toBe("
|
|
26
|
+
expect(rendered.container.textContent).toBe("baz quxfoo bar");
|
|
27
27
|
expect(rendered.container.querySelector(".user.icon")).toBeInTheDocument();
|
|
28
28
|
expect(rendered.container.querySelector(".group.icon")).toBeInTheDocument();
|
|
29
29
|
});
|
|
@@ -32,16 +32,16 @@ describe("<UserGroupPreview />", () => {
|
|
|
32
32
|
const rendered = render(
|
|
33
33
|
<UserGroupPreview
|
|
34
34
|
userGroups={[
|
|
35
|
-
{ name: "user:
|
|
36
|
-
{ id: 123, name: "group:
|
|
35
|
+
{ name: "user:baz qux" },
|
|
36
|
+
{ id: 123, name: "group:foo bar" },
|
|
37
37
|
]}
|
|
38
|
-
|
|
38
|
+
/>
|
|
39
39
|
);
|
|
40
40
|
|
|
41
|
-
expect(rendered.getByText(/
|
|
42
|
-
expect(rendered.getByRole("link", { name: /
|
|
41
|
+
expect(rendered.getByText(/baz qux/i).closest("a")).toBeNull();
|
|
42
|
+
expect(rendered.getByRole("link", { name: /foo bar/i })).toHaveAttribute(
|
|
43
43
|
"href",
|
|
44
|
-
"/groups/123"
|
|
44
|
+
"/groups/123"
|
|
45
45
|
);
|
|
46
46
|
});
|
|
47
47
|
|
|
@@ -52,10 +52,12 @@ describe("<UserGroupPreview />", () => {
|
|
|
52
52
|
data: undefined,
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
const rendered = render(
|
|
55
|
+
const rendered = render(
|
|
56
|
+
<UserGroupPreviewLoader userGroupId="group:foo bar" />
|
|
57
|
+
);
|
|
56
58
|
|
|
57
59
|
expect(useQuery).toHaveBeenCalledWith(USER_GROUP_PREVIEW_QUERY, {
|
|
58
|
-
variables: { ids: ["
|
|
60
|
+
variables: { ids: ["group:foo bar"] },
|
|
59
61
|
});
|
|
60
62
|
expect(rendered.container.firstChild).toBeNull();
|
|
61
63
|
});
|
|
@@ -67,32 +69,66 @@ describe("<UserGroupPreview />", () => {
|
|
|
67
69
|
data: undefined,
|
|
68
70
|
});
|
|
69
71
|
|
|
70
|
-
const rendered = render(
|
|
72
|
+
const rendered = render(
|
|
73
|
+
<UserGroupPreviewLoader userGroupId={["user:baz qux", "bar baz"]} />
|
|
74
|
+
);
|
|
71
75
|
|
|
72
76
|
expect(useQuery).toHaveBeenCalledWith(USER_GROUP_PREVIEW_QUERY, {
|
|
73
|
-
variables: { ids: ["
|
|
77
|
+
variables: { ids: ["user:baz qux", "group:bar baz"] },
|
|
74
78
|
});
|
|
75
79
|
expect(rendered.getByText(/\.\.\./i)).toBeInTheDocument();
|
|
76
80
|
});
|
|
77
81
|
|
|
82
|
+
it("keeps user: and group: prefixes when querying user group details", () => {
|
|
83
|
+
useQuery.mockReturnValue({
|
|
84
|
+
loading: true,
|
|
85
|
+
error: undefined,
|
|
86
|
+
data: undefined,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
render(
|
|
90
|
+
<UserGroupPreviewLoader userGroupId={["user:baz qux", "group:foo bar"]} />
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(useQuery).toHaveBeenCalledWith(USER_GROUP_PREVIEW_QUERY, {
|
|
94
|
+
variables: { ids: ["user:baz qux", "group:foo bar"] },
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("renders group alias from userGroupDetails API response", () => {
|
|
99
|
+
const rendered = render(
|
|
100
|
+
<UserGroupPreview
|
|
101
|
+
userGroups={[{ id: "9", name: "group:foo bar alias" }]}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
expect(rendered.getByText(/foo bar alias/i)).toBeInTheDocument();
|
|
106
|
+
expect(
|
|
107
|
+
rendered.getByRole("link", { name: /foo bar alias/i })
|
|
108
|
+
).toHaveAttribute("href", "/groups/9");
|
|
109
|
+
expect(rendered.container.textContent).not.toContain("group:");
|
|
110
|
+
});
|
|
111
|
+
|
|
78
112
|
it("renders the fetched user groups when loading finishes", () => {
|
|
79
113
|
useQuery.mockReturnValue({
|
|
80
114
|
loading: false,
|
|
81
115
|
error: undefined,
|
|
82
116
|
data: {
|
|
83
117
|
userGroupDetails: [
|
|
84
|
-
{ name: "user:
|
|
85
|
-
{ id: 123, name: "group:
|
|
118
|
+
{ name: "user:baz qux" },
|
|
119
|
+
{ id: 123, name: "group:foo bar" },
|
|
86
120
|
],
|
|
87
121
|
},
|
|
88
122
|
});
|
|
89
123
|
|
|
90
|
-
const rendered = render(
|
|
124
|
+
const rendered = render(
|
|
125
|
+
<UserGroupPreviewLoader userGroupId={["user:baz qux", "group:foo bar"]} />
|
|
126
|
+
);
|
|
91
127
|
|
|
92
|
-
expect(rendered.getByText(/
|
|
93
|
-
expect(rendered.getByRole("link", { name: /
|
|
128
|
+
expect(rendered.getByText(/baz qux/i).closest("a")).toBeNull();
|
|
129
|
+
expect(rendered.getByRole("link", { name: /foo bar/i })).toHaveAttribute(
|
|
94
130
|
"href",
|
|
95
|
-
"/groups/123"
|
|
131
|
+
"/groups/123"
|
|
96
132
|
);
|
|
97
133
|
});
|
|
98
134
|
});
|
|
@@ -44,7 +44,7 @@ describe("utils: validValues", () => {
|
|
|
44
44
|
});
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
it("should validate
|
|
47
|
+
it("should validate user_group values by canonical group name", () => {
|
|
48
48
|
const template = [
|
|
49
49
|
{
|
|
50
50
|
name: "role",
|
|
@@ -52,21 +52,80 @@ describe("utils: validValues", () => {
|
|
|
52
52
|
values: {
|
|
53
53
|
role_groups: "some_role",
|
|
54
54
|
processed_users: ["u1"],
|
|
55
|
-
|
|
55
|
+
processed_groups_details: [
|
|
56
|
+
{ id: 1, name: "group one", alias: "alias1" },
|
|
57
|
+
{ id: 2, name: "group2", alias: null },
|
|
58
|
+
],
|
|
56
59
|
},
|
|
57
60
|
},
|
|
58
61
|
];
|
|
59
62
|
|
|
60
63
|
const result = filterValues(template)({
|
|
61
|
-
role: { value: "group:
|
|
64
|
+
role: { value: "group:group one", origin: "default" },
|
|
62
65
|
});
|
|
63
66
|
|
|
64
67
|
expect(result).toEqual({
|
|
65
|
-
role: { value: "group:
|
|
68
|
+
role: { value: "group:group one", origin: "default" },
|
|
66
69
|
});
|
|
67
70
|
});
|
|
68
71
|
|
|
69
|
-
it("should validate
|
|
72
|
+
it("should validate legacy names when processed_groups_details are present", () => {
|
|
73
|
+
const template = [
|
|
74
|
+
{
|
|
75
|
+
name: "role",
|
|
76
|
+
type: "user_group",
|
|
77
|
+
values: {
|
|
78
|
+
role_groups: "some_role",
|
|
79
|
+
processed_users: ["u1"],
|
|
80
|
+
processed_groups: ["alias1"],
|
|
81
|
+
processed_groups_details: [
|
|
82
|
+
{
|
|
83
|
+
id: 1,
|
|
84
|
+
alias: "alias1",
|
|
85
|
+
name: "group1",
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const result = filterValues(template)({
|
|
93
|
+
role: { value: "group:group1", origin: "default" },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(result).toEqual({
|
|
97
|
+
role: { value: "group:group1", origin: "default" },
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should drop stale alias values from user_group fields on save", () => {
|
|
102
|
+
const template = [
|
|
103
|
+
{
|
|
104
|
+
name: "role",
|
|
105
|
+
type: "user_group",
|
|
106
|
+
values: {
|
|
107
|
+
role_groups: "some_role",
|
|
108
|
+
processed_users: ["u1"],
|
|
109
|
+
processed_groups_details: [
|
|
110
|
+
{ id: 1, name: "bar baz", alias: "foo bar alias" },
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
const result = filterValues(template)({
|
|
117
|
+
role: {
|
|
118
|
+
value: ["group:bar baz", "group:foo bar alias", "group:qux invalid"],
|
|
119
|
+
origin: "default",
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(result).toEqual({
|
|
124
|
+
role: { value: ["group:bar baz"], origin: "default" },
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should validate group values by canonical name without allowing users", () => {
|
|
70
129
|
const template = [
|
|
71
130
|
{
|
|
72
131
|
name: "role",
|
|
@@ -74,31 +133,42 @@ describe("utils: validValues", () => {
|
|
|
74
133
|
values: {
|
|
75
134
|
role_groups: "some_role",
|
|
76
135
|
processed_users: ["u1"],
|
|
77
|
-
|
|
136
|
+
processed_groups_details: [
|
|
137
|
+
{ id: 1, name: "foo bar", alias: "alias1" },
|
|
138
|
+
{ id: 2, name: "group2", alias: null },
|
|
139
|
+
],
|
|
78
140
|
},
|
|
79
141
|
},
|
|
80
142
|
];
|
|
81
143
|
|
|
82
144
|
expect(
|
|
83
145
|
filterValues(template)({
|
|
84
|
-
role: { value: "
|
|
85
|
-
})
|
|
146
|
+
role: { value: "foo bar", origin: "default" },
|
|
147
|
+
})
|
|
86
148
|
).toEqual({
|
|
87
|
-
role: { value: "
|
|
149
|
+
role: { value: "foo bar", origin: "default" },
|
|
88
150
|
});
|
|
89
151
|
|
|
90
152
|
expect(
|
|
91
153
|
filterValues(template)({
|
|
92
154
|
role: { value: "user:u1", origin: "default" },
|
|
93
|
-
})
|
|
155
|
+
})
|
|
156
|
+
).toEqual({
|
|
157
|
+
role: { value: null, origin: "default" },
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(
|
|
161
|
+
filterValues(template)({
|
|
162
|
+
role: { value: "alias1", origin: "default" },
|
|
163
|
+
})
|
|
94
164
|
).toEqual({
|
|
95
165
|
role: { value: null, origin: "default" },
|
|
96
166
|
});
|
|
97
167
|
|
|
98
168
|
expect(
|
|
99
169
|
filterValues(template)({
|
|
100
|
-
role: { value: "group:
|
|
101
|
-
})
|
|
170
|
+
role: { value: "group:foo bar", origin: "default" },
|
|
171
|
+
})
|
|
102
172
|
).toEqual({
|
|
103
173
|
role: { value: null, origin: "default" },
|
|
104
174
|
});
|
|
@@ -150,6 +150,108 @@ describe("utils: parseFieldOptions", () => {
|
|
|
150
150
|
});
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
+
it("normalizes legacy prefixed values for group fields", () => {
|
|
154
|
+
const field = {
|
|
155
|
+
name: "field",
|
|
156
|
+
type: "group",
|
|
157
|
+
values: {
|
|
158
|
+
role_groups: "Some Role",
|
|
159
|
+
processed_groups: [{ alias: "alias1", name: "group1" }],
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
const content = { field: { value: "group:group1", origin: "default" } };
|
|
163
|
+
const result = parseFormatFieldOptions(content)(field);
|
|
164
|
+
expect(result).toEqual({
|
|
165
|
+
...field,
|
|
166
|
+
value: { value: "group1", origin: "default" },
|
|
167
|
+
parsedValues: [{ value: "group1", text: "alias1", icon: "group" }],
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("normalizes legacy group names to canonical names for user_group fields", () => {
|
|
172
|
+
const field = {
|
|
173
|
+
name: "field",
|
|
174
|
+
type: "user_group",
|
|
175
|
+
values: {
|
|
176
|
+
role_groups: "Some Role",
|
|
177
|
+
processed_users: ["x"],
|
|
178
|
+
processed_groups: [{ alias: "alias1", name: "group1" }],
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
const content = { field: { value: "group:group1", origin: "default" } };
|
|
182
|
+
const result = parseFormatFieldOptions(content)(field);
|
|
183
|
+
expect(result).toEqual({
|
|
184
|
+
...field,
|
|
185
|
+
value: { value: "group:group1", origin: "default" },
|
|
186
|
+
parsedValues: [
|
|
187
|
+
{ value: "user:x", text: "x", icon: "user" },
|
|
188
|
+
{ value: "group:group1", text: "alias1", icon: "group" },
|
|
189
|
+
],
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("maps legacy group values to canonical names without extra options", () => {
|
|
194
|
+
const field = {
|
|
195
|
+
name: "field",
|
|
196
|
+
type: "user_group",
|
|
197
|
+
values: {
|
|
198
|
+
role_groups: "Some Role",
|
|
199
|
+
processed_users: ["x"],
|
|
200
|
+
processed_groups: ["alias1", "foo bar alias"],
|
|
201
|
+
processed_groups_details: [
|
|
202
|
+
{
|
|
203
|
+
id: 1,
|
|
204
|
+
alias: "alias1",
|
|
205
|
+
name: "group1",
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: 2,
|
|
209
|
+
alias: "foo bar alias",
|
|
210
|
+
name: "bar baz",
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
const content = {
|
|
216
|
+
field: { value: "group:bar baz", origin: "default" },
|
|
217
|
+
};
|
|
218
|
+
const result = parseFormatFieldOptions(content)(field);
|
|
219
|
+
expect(result).toEqual({
|
|
220
|
+
...field,
|
|
221
|
+
value: { value: "group:bar baz", origin: "default" },
|
|
222
|
+
parsedValues: [
|
|
223
|
+
{ value: "user:x", text: "x", icon: "user" },
|
|
224
|
+
{ value: "group:group1", text: "alias1", icon: "group" },
|
|
225
|
+
{
|
|
226
|
+
value: "group:bar baz",
|
|
227
|
+
text: "foo bar alias",
|
|
228
|
+
icon: "group",
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("normalizes legacy user prefixed values for user fields", () => {
|
|
235
|
+
const field = {
|
|
236
|
+
name: "field",
|
|
237
|
+
type: "user",
|
|
238
|
+
values: {
|
|
239
|
+
role_users: "Some Role",
|
|
240
|
+
processed_users: ["user1", "user2"],
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
const content = { field: { value: "user:user2", origin: "default" } };
|
|
244
|
+
const result = parseFormatFieldOptions(content)(field);
|
|
245
|
+
expect(result).toEqual({
|
|
246
|
+
...field,
|
|
247
|
+
value: { value: "user2", origin: "default" },
|
|
248
|
+
parsedValues: [
|
|
249
|
+
{ text: "user1", value: "user1" },
|
|
250
|
+
{ text: "user2", value: "user2" },
|
|
251
|
+
],
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
153
255
|
it("handles values domain", () => {
|
|
154
256
|
const selectedDomain = { id: 1 };
|
|
155
257
|
const field = {
|
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
_.isString(group) ? group : group?.
|
|
3
|
+
const groupCanonicalName = (group) =>
|
|
4
|
+
_.isString(group) ? group : group?.name;
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const processedGroups = (values = {}) => {
|
|
7
|
+
const details = _.get("processed_groups_details")(values);
|
|
8
|
+
return _.isArray(details) && !_.isEmpty(details)
|
|
9
|
+
? details
|
|
10
|
+
: _.get("processed_groups")(values);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const userGroupValues = (values) =>
|
|
7
14
|
_.concat(
|
|
8
15
|
_.flow(
|
|
9
16
|
_.get("processed_users"),
|
|
10
17
|
_.map((name) => `user:${name}`),
|
|
11
|
-
)(
|
|
18
|
+
)(values),
|
|
12
19
|
_.flow(
|
|
13
|
-
|
|
14
|
-
_.map((group) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}),
|
|
18
|
-
)(field),
|
|
20
|
+
() => processedGroups(values),
|
|
21
|
+
_.map((group) => `group:${groupCanonicalName(group)}`),
|
|
22
|
+
_.compact,
|
|
23
|
+
)(),
|
|
19
24
|
);
|
|
20
25
|
|
|
21
|
-
const groupValues =
|
|
22
|
-
_.
|
|
23
|
-
|
|
24
|
-
)
|
|
26
|
+
const groupValues = (values) =>
|
|
27
|
+
_.flow(
|
|
28
|
+
() => processedGroups(values),
|
|
29
|
+
_.map(groupCanonicalName),
|
|
30
|
+
_.compact,
|
|
31
|
+
_.uniq,
|
|
32
|
+
)();
|
|
25
33
|
|
|
26
34
|
export const validValues = _.flow(
|
|
27
35
|
_.filter(
|
|
@@ -29,6 +37,7 @@ export const validValues = _.flow(
|
|
|
29
37
|
_.has("values.fixed"),
|
|
30
38
|
_.has("values.processed_users"),
|
|
31
39
|
_.has("values.processed_groups"),
|
|
40
|
+
_.has("values.processed_groups_details"),
|
|
32
41
|
]),
|
|
33
42
|
),
|
|
34
43
|
_.map(_.pick(["name", "type", "values"])),
|
|
@@ -37,10 +46,18 @@ export const validValues = _.flow(
|
|
|
37
46
|
_.cond([
|
|
38
47
|
[_.has("fixed"), _.pathOr([], "fixed")],
|
|
39
48
|
[
|
|
40
|
-
() =>
|
|
41
|
-
|
|
49
|
+
() =>
|
|
50
|
+
type === "group" &&
|
|
51
|
+
(_.has("processed_groups", values) ||
|
|
52
|
+
_.has("processed_groups_details", values)),
|
|
53
|
+
() => groupValues(values),
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
() =>
|
|
57
|
+
_.has("processed_groups", values) ||
|
|
58
|
+
_.has("processed_groups_details", values),
|
|
59
|
+
() => userGroupValues(values),
|
|
42
60
|
],
|
|
43
|
-
[_.has("processed_groups"), userGroupValues],
|
|
44
61
|
[_.has("processed_users"), _.pathOr([], "processed_users")],
|
|
45
62
|
])(values),
|
|
46
63
|
]),
|
|
@@ -49,14 +66,22 @@ export const validValues = _.flow(
|
|
|
49
66
|
|
|
50
67
|
export const filterValues = (templateContent) => {
|
|
51
68
|
const vv = validValues(templateContent);
|
|
69
|
+
const fieldTypeByName = _.flow(
|
|
70
|
+
_.map(({ name, type }) => [name, type]),
|
|
71
|
+
_.fromPairs,
|
|
72
|
+
)(templateContent);
|
|
73
|
+
|
|
74
|
+
const isValidFieldValue = (fieldName, candidate) =>
|
|
75
|
+
_.includes(candidate)(_.prop(fieldName)(vv));
|
|
76
|
+
|
|
52
77
|
return _.mapValues.convert({ cap: false })((value, key) =>
|
|
53
78
|
_.has(key)(vv)
|
|
54
79
|
? _.isArray(value.value)
|
|
55
80
|
? _.set(
|
|
56
81
|
"value",
|
|
57
|
-
_.filter((v) =>
|
|
82
|
+
_.filter((v) => isValidFieldValue(key, v))(value.value),
|
|
58
83
|
)(value)
|
|
59
|
-
:
|
|
84
|
+
: isValidFieldValue(key, value.value)
|
|
60
85
|
? value
|
|
61
86
|
: { ...value, value: null }
|
|
62
87
|
: value,
|
|
@@ -7,6 +7,99 @@ const makeOptions = (formatMessage, label) =>
|
|
|
7
7
|
text: formatMessage({ id: `fields.${label}.${v}`, defaultMessage: v }),
|
|
8
8
|
}));
|
|
9
9
|
|
|
10
|
+
const stripUserPrefix = (value) =>
|
|
11
|
+
typeof value === "string" && value.startsWith("user:")
|
|
12
|
+
? value.slice("user:".length)
|
|
13
|
+
: value;
|
|
14
|
+
|
|
15
|
+
const stripGroupPrefix = (value) =>
|
|
16
|
+
typeof value === "string" && value.startsWith("group:")
|
|
17
|
+
? value.slice("group:".length)
|
|
18
|
+
: value;
|
|
19
|
+
|
|
20
|
+
const groupCanonicalName = (group) =>
|
|
21
|
+
typeof group === "string" ? group : group.name;
|
|
22
|
+
|
|
23
|
+
const groupDisplayName = (group) =>
|
|
24
|
+
typeof group === "string" ? group : group.alias || group.name;
|
|
25
|
+
|
|
26
|
+
const groupNameCandidates = (group) =>
|
|
27
|
+
typeof group === "string"
|
|
28
|
+
? [group]
|
|
29
|
+
: _.flow(_.at(["alias", "name"]), _.compact, _.uniq)(group);
|
|
30
|
+
|
|
31
|
+
const groupEntries = (values = {}) => {
|
|
32
|
+
const details = _.get("processed_groups_details")(values);
|
|
33
|
+
if (_.isArray(details) && !_.isEmpty(details)) return details;
|
|
34
|
+
return _.getOr([], "processed_groups")(values);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const buildGroupNameMap = (groups = []) =>
|
|
38
|
+
groups.reduce((acc, group) => {
|
|
39
|
+
const canonicalName = groupCanonicalName(group);
|
|
40
|
+
groupNameCandidates(group).forEach((candidate) => {
|
|
41
|
+
acc[candidate] = canonicalName;
|
|
42
|
+
});
|
|
43
|
+
return acc;
|
|
44
|
+
}, {});
|
|
45
|
+
|
|
46
|
+
const normalizeRoleUsersValue = (fieldValue, processedUsers = []) => {
|
|
47
|
+
const normalizeOne = (value) => {
|
|
48
|
+
if (typeof value !== "string") return value;
|
|
49
|
+
const normalizedUser = stripUserPrefix(value);
|
|
50
|
+
return processedUsers.includes(normalizedUser) ? normalizedUser : value;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return _.isArray(fieldValue)
|
|
54
|
+
? fieldValue.map(normalizeOne)
|
|
55
|
+
: normalizeOne(fieldValue);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const normalizeRoleGroupsValue = (fieldValue, fieldType, values = {}) => {
|
|
59
|
+
const processedUsers = _.getOr([], "processed_users")(values);
|
|
60
|
+
const groupNameMap = buildGroupNameMap(groupEntries(values));
|
|
61
|
+
const isGroupOnly = fieldType === "group";
|
|
62
|
+
|
|
63
|
+
const normalizeOne = (value) => {
|
|
64
|
+
if (typeof value !== "string") return value;
|
|
65
|
+
if (isGroupOnly) {
|
|
66
|
+
const normalizedGroup = stripGroupPrefix(value);
|
|
67
|
+
return groupNameMap[normalizedGroup] || normalizedGroup;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (value.startsWith("user:")) {
|
|
71
|
+
const normalizedUser = stripUserPrefix(value);
|
|
72
|
+
return processedUsers.includes(normalizedUser)
|
|
73
|
+
? `user:${normalizedUser}`
|
|
74
|
+
: value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const normalizedGroup = stripGroupPrefix(value);
|
|
78
|
+
const resolvedGroup = groupNameMap[normalizedGroup];
|
|
79
|
+
if (resolvedGroup) return `group:${resolvedGroup}`;
|
|
80
|
+
if (processedUsers.includes(value)) {
|
|
81
|
+
return `user:${value}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return value;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return _.isArray(fieldValue)
|
|
88
|
+
? fieldValue.map(normalizeOne)
|
|
89
|
+
: normalizeOne(fieldValue);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const normalizeFieldValue = (fieldValue, normalizer) => {
|
|
93
|
+
if (_.isNil(fieldValue)) return fieldValue;
|
|
94
|
+
if (_.isObject(fieldValue) && _.has("value")(fieldValue)) {
|
|
95
|
+
return {
|
|
96
|
+
...fieldValue,
|
|
97
|
+
value: normalizer(fieldValue.value),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return normalizer(fieldValue);
|
|
101
|
+
};
|
|
102
|
+
|
|
10
103
|
const doParseFieldOptions = (formatMessage, content, selectedDomain, field) => {
|
|
11
104
|
const value = _.prop(field?.name)(content);
|
|
12
105
|
|
|
@@ -15,8 +108,8 @@ const doParseFieldOptions = (formatMessage, content, selectedDomain, field) => {
|
|
|
15
108
|
const tableColumns = _.flow(
|
|
16
109
|
_.propOr([], "values.table_columns"),
|
|
17
110
|
_.map((field) =>
|
|
18
|
-
doParseFieldOptions(formatMessage, content, selectedDomain, field)
|
|
19
|
-
)
|
|
111
|
+
doParseFieldOptions(formatMessage, content, selectedDomain, field)
|
|
112
|
+
)
|
|
20
113
|
)(field);
|
|
21
114
|
return {
|
|
22
115
|
...field,
|
|
@@ -26,12 +119,12 @@ const doParseFieldOptions = (formatMessage, content, selectedDomain, field) => {
|
|
|
26
119
|
}
|
|
27
120
|
const translateValues = _.flow(
|
|
28
121
|
makeOptions(formatMessage, field?.label),
|
|
29
|
-
_.sortBy(accentInsensitivePathOrder("text"))
|
|
122
|
+
_.sortBy(accentInsensitivePathOrder("text"))
|
|
30
123
|
);
|
|
31
124
|
const parseSwitch = (values) => {
|
|
32
125
|
const switchOn = _.prop("switch.on")(values);
|
|
33
126
|
const switchedValues = _.prop(`switch.values.${content[switchOn].value}`)(
|
|
34
|
-
values
|
|
127
|
+
values
|
|
35
128
|
);
|
|
36
129
|
return translateValues(switchedValues);
|
|
37
130
|
};
|
|
@@ -42,24 +135,21 @@ const doParseFieldOptions = (formatMessage, content, selectedDomain, field) => {
|
|
|
42
135
|
[_.isArray, _.identity],
|
|
43
136
|
[_.stubTrue, _.constant([])],
|
|
44
137
|
]),
|
|
45
|
-
translateValues
|
|
138
|
+
translateValues
|
|
46
139
|
);
|
|
47
|
-
const groupNameOrAlias = (group) =>
|
|
48
|
-
typeof group === "string" ? group : group.alias || group.name;
|
|
49
140
|
const parseRoleGroups = (values) => {
|
|
50
141
|
const isGroupOnly = field?.type === "group";
|
|
51
142
|
const groups = _.flow(
|
|
52
|
-
|
|
143
|
+
() => groupEntries(values),
|
|
53
144
|
_.map((group) => {
|
|
54
|
-
const name =
|
|
145
|
+
const name = groupCanonicalName(group);
|
|
55
146
|
return {
|
|
56
147
|
value: isGroupOnly ? name : `group:${name}`,
|
|
57
|
-
text:
|
|
148
|
+
text: groupDisplayName(group),
|
|
58
149
|
icon: "group",
|
|
59
150
|
};
|
|
60
|
-
})
|
|
151
|
+
})
|
|
61
152
|
)(values);
|
|
62
|
-
|
|
63
153
|
if (isGroupOnly) return groups;
|
|
64
154
|
|
|
65
155
|
const users = _.flow(
|
|
@@ -68,7 +158,7 @@ const doParseFieldOptions = (formatMessage, content, selectedDomain, field) => {
|
|
|
68
158
|
value: `user:${name}`,
|
|
69
159
|
text: name,
|
|
70
160
|
icon: "user",
|
|
71
|
-
}))
|
|
161
|
+
}))
|
|
72
162
|
)(values);
|
|
73
163
|
|
|
74
164
|
return _.concat(users, groups);
|
|
@@ -88,7 +178,7 @@ const doParseFieldOptions = (formatMessage, content, selectedDomain, field) => {
|
|
|
88
178
|
}) || [];
|
|
89
179
|
return _.uniq(allValues);
|
|
90
180
|
},
|
|
91
|
-
translateValues
|
|
181
|
+
translateValues
|
|
92
182
|
);
|
|
93
183
|
|
|
94
184
|
const parsedValues = _.flow(
|
|
@@ -100,9 +190,37 @@ const doParseFieldOptions = (formatMessage, content, selectedDomain, field) => {
|
|
|
100
190
|
[_.has("role_users"), parseRoleUsers],
|
|
101
191
|
[_.has("role_groups"), parseRoleGroups],
|
|
102
192
|
[_.has("domain"), parseDomain],
|
|
103
|
-
])
|
|
193
|
+
])
|
|
104
194
|
)(field);
|
|
105
|
-
|
|
195
|
+
const normalizedValue = _.flow(
|
|
196
|
+
_.get("values"),
|
|
197
|
+
_.cond([
|
|
198
|
+
[
|
|
199
|
+
_.has("role_users"),
|
|
200
|
+
() =>
|
|
201
|
+
normalizeFieldValue(value, (rawValue) =>
|
|
202
|
+
normalizeRoleUsersValue(
|
|
203
|
+
rawValue,
|
|
204
|
+
_.getOr([], "values.processed_users")(field)
|
|
205
|
+
)
|
|
206
|
+
),
|
|
207
|
+
],
|
|
208
|
+
[
|
|
209
|
+
_.has("role_groups"),
|
|
210
|
+
() =>
|
|
211
|
+
normalizeFieldValue(value, (rawValue) =>
|
|
212
|
+
normalizeRoleGroupsValue(
|
|
213
|
+
rawValue,
|
|
214
|
+
field?.type,
|
|
215
|
+
_.get("values")(field)
|
|
216
|
+
)
|
|
217
|
+
),
|
|
218
|
+
],
|
|
219
|
+
[_.stubTrue, () => value],
|
|
220
|
+
])
|
|
221
|
+
)(field);
|
|
222
|
+
|
|
223
|
+
return { ...field, value: normalizedValue, parsedValues };
|
|
106
224
|
};
|
|
107
225
|
export const parseFieldOptions =
|
|
108
226
|
(formatMessage) => (content, selectedDomain) => (field) =>
|