@truedat/core 4.53.1 → 4.53.3
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/CHANGELOG.md +13 -0
- package/package.json +3 -3
- package/src/api/queries.js +17 -0
- package/src/components/ModalSaveFilter.js +28 -11
- package/src/components/UserFilters.js +64 -51
- package/src/components/__tests__/ModalSaveFilter.spec.js +38 -22
- package/src/components/__tests__/UserFilters.spec.js +76 -33
- package/src/components/__tests__/__snapshots__/ModalSaveFilter.spec.js.snap +16 -63
- package/src/components/__tests__/__snapshots__/UserFilters.spec.js.snap +159 -88
- package/src/hooks/index.js +1 -0
- package/src/hooks/useOperators.js +10 -0
- package/src/messages/en.js +1 -0
- package/src/messages/es.js +1 -0
- package/src/services/__tests__/operators.spec.js +215 -0
- package/src/services/operators.js +54 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.53.3] 2022-10-07
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- [TD-5195] Support for global user filters
|
|
8
|
+
|
|
9
|
+
## [4.53.2] 2022-10-07
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- [TD-5226] `useOperators` hook to load operators using GraphQL `functions`
|
|
14
|
+
query
|
|
15
|
+
|
|
3
16
|
## [4.52.4] 2022-09-30
|
|
4
17
|
|
|
5
18
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/core",
|
|
3
|
-
"version": "4.53.
|
|
3
|
+
"version": "4.53.3",
|
|
4
4
|
"description": "Truedat Web Core",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"jsnext:main": "src/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@testing-library/jest-dom": "^5.16.4",
|
|
36
36
|
"@testing-library/react": "^12.0.0",
|
|
37
37
|
"@testing-library/user-event": "^13.2.1",
|
|
38
|
-
"@truedat/test": "4.53.
|
|
38
|
+
"@truedat/test": "4.53.3",
|
|
39
39
|
"babel-jest": "^28.1.0",
|
|
40
40
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
41
41
|
"babel-plugin-lodash": "^3.3.4",
|
|
@@ -112,5 +112,5 @@
|
|
|
112
112
|
"react-dom": ">= 16.8.6 < 17",
|
|
113
113
|
"semantic-ui-react": ">= 0.88.2 < 2.1"
|
|
114
114
|
},
|
|
115
|
-
"gitHead": "
|
|
115
|
+
"gitHead": "d4f6cc40692e4d1e3f13e42774938765fe49c3b7"
|
|
116
116
|
}
|
package/src/api/queries.js
CHANGED
|
@@ -3,6 +3,7 @@ import React, { useState } from "react";
|
|
|
3
3
|
import PropTypes from "prop-types";
|
|
4
4
|
import { Form } from "semantic-ui-react";
|
|
5
5
|
import { FormattedMessage } from "react-intl";
|
|
6
|
+
import { useAuthorized } from "@truedat/core/hooks";
|
|
6
7
|
import { ConfirmModal } from "./ConfirmModal";
|
|
7
8
|
|
|
8
9
|
export const SaveFilterForm = ({ setFilterName }) => {
|
|
@@ -31,8 +32,22 @@ SaveFilterForm.propTypes = {
|
|
|
31
32
|
|
|
32
33
|
export const ModalSaveFilter = ({ saveFilters, activeFilters, scope }) => {
|
|
33
34
|
const [filterName, setFilterName] = useState("");
|
|
35
|
+
const authorized = useAuthorized();
|
|
34
36
|
|
|
35
|
-
const
|
|
37
|
+
const handleSubmitGlobal = (e) => doSubmit(e, true);
|
|
38
|
+
const handleSubmit = (e) => doSubmit(e, false);
|
|
39
|
+
|
|
40
|
+
const doSubmit = (e, isGlobal) => {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
saveFilters({
|
|
43
|
+
filters: activeFilters,
|
|
44
|
+
filterName: filterName,
|
|
45
|
+
scope,
|
|
46
|
+
isGlobal,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const actions = [
|
|
36
51
|
{
|
|
37
52
|
key: "no",
|
|
38
53
|
secondary: true,
|
|
@@ -45,20 +60,22 @@ export const ModalSaveFilter = ({ saveFilters, activeFilters, scope }) => {
|
|
|
45
60
|
content: <FormattedMessage id="search.save_filter.save" />,
|
|
46
61
|
onClick: handleSubmit,
|
|
47
62
|
},
|
|
63
|
+
...(authorized
|
|
64
|
+
? [
|
|
65
|
+
{
|
|
66
|
+
key: "yes_global",
|
|
67
|
+
primary: true,
|
|
68
|
+
disabled: _.isEmpty(filterName),
|
|
69
|
+
content: <FormattedMessage id="search.save_filter.save_global" />,
|
|
70
|
+
onClick: handleSubmitGlobal,
|
|
71
|
+
},
|
|
72
|
+
]
|
|
73
|
+
: []),
|
|
48
74
|
];
|
|
49
75
|
|
|
50
|
-
const handleSubmit = (e) => {
|
|
51
|
-
e.preventDefault();
|
|
52
|
-
saveFilters({
|
|
53
|
-
filters: activeFilters,
|
|
54
|
-
filterName: filterName,
|
|
55
|
-
scope,
|
|
56
|
-
});
|
|
57
|
-
};
|
|
58
|
-
|
|
59
76
|
return (
|
|
60
77
|
<ConfirmModal
|
|
61
|
-
actions={actions
|
|
78
|
+
actions={actions}
|
|
62
79
|
icon="save"
|
|
63
80
|
trigger={
|
|
64
81
|
<a className="resetFilters">
|
|
@@ -3,6 +3,7 @@ import React from "react";
|
|
|
3
3
|
import PropTypes from "prop-types";
|
|
4
4
|
import { Label, Icon } from "semantic-ui-react";
|
|
5
5
|
import { FormattedMessage } from "react-intl";
|
|
6
|
+
import { useAuthorized } from "@truedat/core/hooks";
|
|
6
7
|
import { ConfirmModal } from "./ConfirmModal";
|
|
7
8
|
|
|
8
9
|
export const UserFilters = ({
|
|
@@ -14,62 +15,74 @@ export const UserFilters = ({
|
|
|
14
15
|
userFilters,
|
|
15
16
|
userFilterScope,
|
|
16
17
|
}) => {
|
|
18
|
+
const authorized = useAuthorized();
|
|
19
|
+
const sortedUserFilters = _.orderBy(
|
|
20
|
+
["is_global", "id"],
|
|
21
|
+
["desc", "asc"]
|
|
22
|
+
)(userFilters);
|
|
17
23
|
return _.isEmpty(userFilters) ? null : (
|
|
18
24
|
<div className="selectedFilters">
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
disabled
|
|
28
|
-
? null
|
|
29
|
-
: (e) => {
|
|
30
|
-
e.preventDefault();
|
|
31
|
-
e.stopPropagation();
|
|
32
|
-
if (selectedUserFilter == userFilter.name) {
|
|
33
|
-
resetFilters();
|
|
34
|
-
} else {
|
|
35
|
-
applyUserFilter({ userFilter, scope: userFilterScope });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
className="userFilter"
|
|
25
|
+
{sortedUserFilters.map((userFilter, key) => {
|
|
26
|
+
const isGlobal = _.prop("is_global")(userFilter);
|
|
27
|
+
return (
|
|
28
|
+
<Label
|
|
29
|
+
basic={selectedUserFilter == userFilter.name ? false : true}
|
|
30
|
+
circular
|
|
31
|
+
key={key}
|
|
32
|
+
color={isGlobal ? "teal" : null}
|
|
40
33
|
>
|
|
41
|
-
{
|
|
42
|
-
|
|
34
|
+
{isGlobal ? <Icon name="globe" /> : null}
|
|
35
|
+
<span
|
|
36
|
+
onClick={
|
|
37
|
+
disabled
|
|
38
|
+
? null
|
|
39
|
+
: (e) => {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
e.stopPropagation();
|
|
42
|
+
if (selectedUserFilter == userFilter.name) {
|
|
43
|
+
resetFilters();
|
|
44
|
+
} else {
|
|
45
|
+
applyUserFilter({ userFilter, scope: userFilterScope });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
className="userFilter"
|
|
50
|
+
>
|
|
51
|
+
{userFilter.name}
|
|
52
|
+
</span>
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
54
|
+
{!isGlobal || authorized ? (
|
|
55
|
+
<ConfirmModal
|
|
56
|
+
icon="trash"
|
|
57
|
+
trigger={
|
|
58
|
+
<Icon
|
|
59
|
+
className="selectable"
|
|
60
|
+
color="grey"
|
|
61
|
+
size="small"
|
|
62
|
+
name="trash alternate outline"
|
|
63
|
+
/>
|
|
64
|
+
}
|
|
65
|
+
header={
|
|
66
|
+
<FormattedMessage id="search.filters.actions.delete.confirmation.header" />
|
|
67
|
+
}
|
|
68
|
+
content={
|
|
69
|
+
<FormattedMessage
|
|
70
|
+
id="search.filters.actions.delete.confirmation.content"
|
|
71
|
+
values={{
|
|
72
|
+
name: (
|
|
73
|
+
<b>
|
|
74
|
+
<i>{userFilter.name}</i>
|
|
75
|
+
</b>
|
|
76
|
+
),
|
|
77
|
+
}}
|
|
78
|
+
/>
|
|
79
|
+
}
|
|
80
|
+
onConfirm={() => deleteUserFilter({ id: userFilter.id })}
|
|
67
81
|
/>
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
))}
|
|
82
|
+
) : null}
|
|
83
|
+
</Label>
|
|
84
|
+
);
|
|
85
|
+
})}
|
|
73
86
|
</div>
|
|
74
87
|
);
|
|
75
88
|
};
|
|
@@ -1,32 +1,48 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { render } from "@truedat/test/render";
|
|
4
|
+
import messages from "@truedat/core/messages";
|
|
5
|
+
import ModalSaveFilter from "../ModalSaveFilter";
|
|
6
|
+
|
|
7
|
+
const renderOpts = {
|
|
8
|
+
messages: {
|
|
9
|
+
en: {
|
|
10
|
+
...messages.en,
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe("<ModalSaveFilter /> admin", () => {
|
|
16
|
+
jest.mock("../../hooks", () => ({
|
|
17
|
+
...jest.requireActual("../../hooks"),
|
|
18
|
+
useAuthorized: jest.fn(() => true),
|
|
19
|
+
}));
|
|
4
20
|
|
|
5
|
-
describe("<ModalSaveFilter />", () => {
|
|
6
21
|
const saveFilters = jest.fn();
|
|
7
22
|
const activeFilters = {};
|
|
23
|
+
const scope = "data_structure";
|
|
8
24
|
|
|
9
25
|
it("matches the latest snapshot", () => {
|
|
10
|
-
const props = {
|
|
11
|
-
|
|
12
|
-
|
|
26
|
+
const props = {
|
|
27
|
+
saveFilters,
|
|
28
|
+
activeFilters,
|
|
29
|
+
scope,
|
|
30
|
+
};
|
|
31
|
+
const { container } = render(<ModalSaveFilter {...props} />, renderOpts);
|
|
32
|
+
expect(container).toMatchSnapshot();
|
|
13
33
|
});
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe("<SaveFilterForm />", () => {
|
|
17
|
-
it("matches the latest snapshot", () => {
|
|
18
|
-
const setFilterName = jest.fn();
|
|
19
|
-
const props = { setFilterName };
|
|
20
|
-
const wrapper = shallow(<SaveFilterForm {...props} />);
|
|
21
|
-
expect(wrapper).toMatchSnapshot();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("calls setFilterName on input change", () => {
|
|
25
|
-
const setFilterName = jest.fn();
|
|
26
|
-
const props = { setFilterName };
|
|
27
|
-
const wrapper = shallow(<SaveFilterForm {...props} />);
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
it("matches the latest snapshot with opened modal", async () => {
|
|
36
|
+
const props = {
|
|
37
|
+
saveFilters,
|
|
38
|
+
activeFilters,
|
|
39
|
+
scope,
|
|
40
|
+
};
|
|
41
|
+
const { container, findByText } = render(
|
|
42
|
+
<ModalSaveFilter {...props} />,
|
|
43
|
+
renderOpts
|
|
44
|
+
);
|
|
45
|
+
userEvent.click(await findByText(/save/i));
|
|
46
|
+
expect(container).toMatchSnapshot();
|
|
31
47
|
});
|
|
32
48
|
});
|
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { render } from "@truedat/test/render";
|
|
4
|
+
import messages from "@truedat/core/messages";
|
|
5
|
+
import UserFilters from "../UserFilters";
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const renderOpts = {
|
|
8
|
+
messages: {
|
|
9
|
+
en: {
|
|
10
|
+
...messages.en,
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const applyUserFilter = jest.fn();
|
|
16
|
+
const deleteUserFilter = jest.fn();
|
|
17
|
+
const resetFilters = jest.fn();
|
|
18
|
+
const selectedUserFilter = null;
|
|
19
|
+
const userFilters = [
|
|
20
|
+
{ id: 1, name: "a" },
|
|
21
|
+
{ id: 2, name: "b" },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
describe("<UserFilters /> admin", () => {
|
|
25
|
+
jest.mock("../../hooks", () => ({
|
|
26
|
+
...jest.requireActual("../../hooks"),
|
|
27
|
+
useAuthorized: jest.fn(() => true),
|
|
28
|
+
}));
|
|
11
29
|
|
|
12
30
|
it("matches the latest snapshot", () => {
|
|
13
31
|
const props = {
|
|
@@ -15,57 +33,82 @@ describe("<UserFilters />", () => {
|
|
|
15
33
|
deleteUserFilter,
|
|
16
34
|
resetFilters,
|
|
17
35
|
selectedUserFilter,
|
|
18
|
-
userFilters
|
|
36
|
+
userFilters,
|
|
19
37
|
};
|
|
20
|
-
const
|
|
21
|
-
expect(
|
|
38
|
+
const { container } = render(<UserFilters {...props} />, renderOpts);
|
|
39
|
+
expect(container).toMatchSnapshot();
|
|
22
40
|
});
|
|
23
41
|
|
|
24
|
-
it("
|
|
42
|
+
it("matches the latest snapshot with global filters", () => {
|
|
25
43
|
const props = {
|
|
26
44
|
applyUserFilter,
|
|
27
45
|
deleteUserFilter,
|
|
28
46
|
resetFilters,
|
|
29
47
|
selectedUserFilter,
|
|
30
|
-
userFilters
|
|
48
|
+
userFilters: [...userFilters, { name: "g", is_global: true }],
|
|
31
49
|
};
|
|
32
|
-
const
|
|
33
|
-
expect(
|
|
50
|
+
const { container } = render(<UserFilters {...props} />, renderOpts);
|
|
51
|
+
expect(container).toMatchSnapshot();
|
|
34
52
|
});
|
|
35
53
|
|
|
36
|
-
it("calls applyUserFilter when selecting unselected filter label", () => {
|
|
54
|
+
it("calls applyUserFilter when selecting unselected filter label", async () => {
|
|
37
55
|
const props = {
|
|
38
56
|
applyUserFilter,
|
|
39
57
|
deleteUserFilter,
|
|
40
58
|
resetFilters,
|
|
41
59
|
selectedUserFilter,
|
|
42
|
-
userFilters
|
|
60
|
+
userFilters,
|
|
43
61
|
};
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
wrapper
|
|
47
|
-
.find("Label")
|
|
48
|
-
.at(0)
|
|
49
|
-
.find("span")
|
|
50
|
-
.simulate("click", e);
|
|
62
|
+
const { findByText } = render(<UserFilters {...props} />, renderOpts);
|
|
63
|
+
userEvent.click(await findByText(/b/));
|
|
51
64
|
expect(applyUserFilter.mock.calls.length).toBe(1);
|
|
65
|
+
expect(applyUserFilter.mock.calls[0][0].userFilter).toEqual({
|
|
66
|
+
id: 2,
|
|
67
|
+
name: "b",
|
|
68
|
+
});
|
|
52
69
|
});
|
|
53
70
|
|
|
54
|
-
it("calls resetFilters when selecting selected filter label", () => {
|
|
71
|
+
it("calls resetFilters when selecting selected filter label", async () => {
|
|
55
72
|
const props = {
|
|
56
73
|
applyUserFilter,
|
|
57
74
|
deleteUserFilter,
|
|
58
75
|
resetFilters,
|
|
59
76
|
selectedUserFilter: "a",
|
|
60
|
-
userFilters
|
|
77
|
+
userFilters,
|
|
61
78
|
};
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
wrapper
|
|
65
|
-
.find("Label")
|
|
66
|
-
.at(0)
|
|
67
|
-
.find("span")
|
|
68
|
-
.simulate("click", e);
|
|
79
|
+
const { findByText } = render(<UserFilters {...props} />, renderOpts);
|
|
80
|
+
userEvent.click(await findByText(/a/));
|
|
69
81
|
expect(resetFilters.mock.calls.length).toBe(1);
|
|
70
82
|
});
|
|
71
83
|
});
|
|
84
|
+
|
|
85
|
+
describe("<UserFilters /> not admin", () => {
|
|
86
|
+
jest.mock("../../hooks", () => ({
|
|
87
|
+
...jest.requireActual("../../hooks"),
|
|
88
|
+
useAuthorized: jest.fn(() => false),
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
it("matches the latest snapshot", () => {
|
|
92
|
+
const props = {
|
|
93
|
+
applyUserFilter,
|
|
94
|
+
deleteUserFilter,
|
|
95
|
+
resetFilters,
|
|
96
|
+
selectedUserFilter,
|
|
97
|
+
userFilters,
|
|
98
|
+
};
|
|
99
|
+
const { container } = render(<UserFilters {...props} />, renderOpts);
|
|
100
|
+
expect(container).toMatchSnapshot();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("matches the latest snapshot with global filters", () => {
|
|
104
|
+
const props = {
|
|
105
|
+
applyUserFilter,
|
|
106
|
+
deleteUserFilter,
|
|
107
|
+
resetFilters,
|
|
108
|
+
selectedUserFilter,
|
|
109
|
+
userFilters: [...userFilters, { name: "g", is_global: true }],
|
|
110
|
+
};
|
|
111
|
+
const { container } = render(<UserFilters {...props} />, renderOpts);
|
|
112
|
+
expect(container).toMatchSnapshot();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -1,68 +1,21 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
|
-
exports[`<ModalSaveFilter /> matches the latest snapshot 1`] = `
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"key": "no",
|
|
12
|
-
"secondary": true,
|
|
13
|
-
},
|
|
14
|
-
Object {
|
|
15
|
-
"content": <Memo(MemoizedFormattedMessage)
|
|
16
|
-
id="search.save_filter.save"
|
|
17
|
-
/>,
|
|
18
|
-
"disabled": true,
|
|
19
|
-
"key": "yes",
|
|
20
|
-
"onClick": [Function],
|
|
21
|
-
"primary": true,
|
|
22
|
-
},
|
|
23
|
-
]
|
|
24
|
-
}
|
|
25
|
-
content={
|
|
26
|
-
<SaveFilterForm
|
|
27
|
-
filterName=""
|
|
28
|
-
setFilterName={[Function]}
|
|
29
|
-
/>
|
|
30
|
-
}
|
|
31
|
-
header={
|
|
32
|
-
<Memo(MemoizedFormattedMessage)
|
|
33
|
-
id="search.filters.actions.save.confirmation.header"
|
|
34
|
-
/>
|
|
35
|
-
}
|
|
36
|
-
icon="save"
|
|
37
|
-
onConfirm={[Function]}
|
|
38
|
-
trigger={
|
|
39
|
-
<a
|
|
40
|
-
className="resetFilters"
|
|
41
|
-
>
|
|
42
|
-
<Memo(MemoizedFormattedMessage)
|
|
43
|
-
id="search.save_filters"
|
|
44
|
-
/>
|
|
45
|
-
</a>
|
|
46
|
-
}
|
|
47
|
-
/>
|
|
3
|
+
exports[`<ModalSaveFilter /> admin matches the latest snapshot 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<a
|
|
6
|
+
class="resetFilters"
|
|
7
|
+
>
|
|
8
|
+
Save
|
|
9
|
+
</a>
|
|
10
|
+
</div>
|
|
48
11
|
`;
|
|
49
12
|
|
|
50
|
-
exports[`<
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
/>
|
|
59
|
-
</label>
|
|
60
|
-
<FormInput
|
|
61
|
-
as={[Function]}
|
|
62
|
-
control={[Function]}
|
|
63
|
-
name="filter_name"
|
|
64
|
-
onChange={[Function]}
|
|
65
|
-
/>
|
|
66
|
-
</FormField>
|
|
67
|
-
</Form>
|
|
13
|
+
exports[`<ModalSaveFilter /> admin matches the latest snapshot with opened modal 1`] = `
|
|
14
|
+
<div>
|
|
15
|
+
<a
|
|
16
|
+
class="resetFilters"
|
|
17
|
+
>
|
|
18
|
+
Save
|
|
19
|
+
</a>
|
|
20
|
+
</div>
|
|
68
21
|
`;
|
|
@@ -1,96 +1,167 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
|
-
exports[`<UserFilters /> matches the latest snapshot 1`] = `
|
|
4
|
-
<div
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<Label
|
|
8
|
-
basic={true}
|
|
9
|
-
circular={true}
|
|
10
|
-
key="0"
|
|
3
|
+
exports[`<UserFilters /> admin matches the latest snapshot 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="selectedFilters"
|
|
11
7
|
>
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
onClick={[Function]}
|
|
8
|
+
<div
|
|
9
|
+
class="ui basic circular label"
|
|
15
10
|
>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
/>
|
|
48
|
-
}
|
|
49
|
-
/>
|
|
50
|
-
</Label>
|
|
51
|
-
<Label
|
|
52
|
-
basic={true}
|
|
53
|
-
circular={true}
|
|
54
|
-
key="1"
|
|
11
|
+
<span
|
|
12
|
+
class="userFilter"
|
|
13
|
+
>
|
|
14
|
+
a
|
|
15
|
+
</span>
|
|
16
|
+
<i
|
|
17
|
+
aria-hidden="true"
|
|
18
|
+
class="grey trash alternate outline small icon selectable"
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
<div
|
|
22
|
+
class="ui basic circular label"
|
|
23
|
+
>
|
|
24
|
+
<span
|
|
25
|
+
class="userFilter"
|
|
26
|
+
>
|
|
27
|
+
b
|
|
28
|
+
</span>
|
|
29
|
+
<i
|
|
30
|
+
aria-hidden="true"
|
|
31
|
+
class="grey trash alternate outline small icon selectable"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
exports[`<UserFilters /> admin matches the latest snapshot with global filters 1`] = `
|
|
39
|
+
<div>
|
|
40
|
+
<div
|
|
41
|
+
class="selectedFilters"
|
|
55
42
|
>
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
43
|
+
<div
|
|
44
|
+
class="ui basic circular label"
|
|
45
|
+
>
|
|
46
|
+
<span
|
|
47
|
+
class="userFilter"
|
|
48
|
+
>
|
|
49
|
+
a
|
|
50
|
+
</span>
|
|
51
|
+
<i
|
|
52
|
+
aria-hidden="true"
|
|
53
|
+
class="grey trash alternate outline small icon selectable"
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
<div
|
|
57
|
+
class="ui basic circular label"
|
|
58
|
+
>
|
|
59
|
+
<span
|
|
60
|
+
class="userFilter"
|
|
61
|
+
>
|
|
62
|
+
b
|
|
63
|
+
</span>
|
|
64
|
+
<i
|
|
65
|
+
aria-hidden="true"
|
|
66
|
+
class="grey trash alternate outline small icon selectable"
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
<div
|
|
70
|
+
class="ui teal basic circular label"
|
|
71
|
+
>
|
|
72
|
+
<i
|
|
73
|
+
aria-hidden="true"
|
|
74
|
+
class="globe icon"
|
|
75
|
+
/>
|
|
76
|
+
<span
|
|
77
|
+
class="userFilter"
|
|
78
|
+
>
|
|
79
|
+
g
|
|
80
|
+
</span>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
exports[`<UserFilters /> not admin matches the latest snapshot 1`] = `
|
|
87
|
+
<div>
|
|
88
|
+
<div
|
|
89
|
+
class="selectedFilters"
|
|
90
|
+
>
|
|
91
|
+
<div
|
|
92
|
+
class="ui basic circular label"
|
|
93
|
+
>
|
|
94
|
+
<span
|
|
95
|
+
class="userFilter"
|
|
96
|
+
>
|
|
97
|
+
a
|
|
98
|
+
</span>
|
|
99
|
+
<i
|
|
100
|
+
aria-hidden="true"
|
|
101
|
+
class="grey trash alternate outline small icon selectable"
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
<div
|
|
105
|
+
class="ui basic circular label"
|
|
106
|
+
>
|
|
107
|
+
<span
|
|
108
|
+
class="userFilter"
|
|
109
|
+
>
|
|
110
|
+
b
|
|
111
|
+
</span>
|
|
112
|
+
<i
|
|
113
|
+
aria-hidden="true"
|
|
114
|
+
class="grey trash alternate outline small icon selectable"
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
exports[`<UserFilters /> not admin matches the latest snapshot with global filters 1`] = `
|
|
122
|
+
<div>
|
|
123
|
+
<div
|
|
124
|
+
class="selectedFilters"
|
|
125
|
+
>
|
|
126
|
+
<div
|
|
127
|
+
class="ui basic circular label"
|
|
128
|
+
>
|
|
129
|
+
<span
|
|
130
|
+
class="userFilter"
|
|
131
|
+
>
|
|
132
|
+
a
|
|
133
|
+
</span>
|
|
134
|
+
<i
|
|
135
|
+
aria-hidden="true"
|
|
136
|
+
class="grey trash alternate outline small icon selectable"
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
<div
|
|
140
|
+
class="ui basic circular label"
|
|
141
|
+
>
|
|
142
|
+
<span
|
|
143
|
+
class="userFilter"
|
|
144
|
+
>
|
|
145
|
+
b
|
|
146
|
+
</span>
|
|
147
|
+
<i
|
|
148
|
+
aria-hidden="true"
|
|
149
|
+
class="grey trash alternate outline small icon selectable"
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
<div
|
|
153
|
+
class="ui teal basic circular label"
|
|
59
154
|
>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
</i>
|
|
72
|
-
</b>,
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/>
|
|
76
|
-
}
|
|
77
|
-
header={
|
|
78
|
-
<Memo(MemoizedFormattedMessage)
|
|
79
|
-
id="search.filters.actions.delete.confirmation.header"
|
|
80
|
-
/>
|
|
81
|
-
}
|
|
82
|
-
icon="trash"
|
|
83
|
-
onConfirm={[Function]}
|
|
84
|
-
trigger={
|
|
85
|
-
<Icon
|
|
86
|
-
as="i"
|
|
87
|
-
className="selectable"
|
|
88
|
-
color="grey"
|
|
89
|
-
name="trash alternate outline"
|
|
90
|
-
size="small"
|
|
91
|
-
/>
|
|
92
|
-
}
|
|
93
|
-
/>
|
|
94
|
-
</Label>
|
|
155
|
+
<i
|
|
156
|
+
aria-hidden="true"
|
|
157
|
+
class="globe icon"
|
|
158
|
+
/>
|
|
159
|
+
<span
|
|
160
|
+
class="userFilter"
|
|
161
|
+
>
|
|
162
|
+
g
|
|
163
|
+
</span>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
95
166
|
</div>
|
|
96
167
|
`;
|
package/src/hooks/index.js
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useQuery } from "@apollo/client";
|
|
2
|
+
import { FUNCTIONS_QUERY } from "../api/queries";
|
|
3
|
+
import { transform } from "../services/operators";
|
|
4
|
+
|
|
5
|
+
export const useOperators = () => {
|
|
6
|
+
const { loading, error, data } = useQuery(FUNCTIONS_QUERY);
|
|
7
|
+
return { loading, error, data: data ? transform(data) : [] };
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default useOperators;
|
package/src/messages/en.js
CHANGED
|
@@ -63,6 +63,7 @@ export default {
|
|
|
63
63
|
"search.saveFilterForm.filter_name": "Name",
|
|
64
64
|
"search.save_filter.cancel": "Cancel",
|
|
65
65
|
"search.save_filter.save": "Save",
|
|
66
|
+
"search.save_filter.save_global": "Save for all users",
|
|
66
67
|
"search.filters.actions.delete.confirmation.header": "Delete custom filter",
|
|
67
68
|
"search.filters.actions.delete.confirmation.content":
|
|
68
69
|
"Are you sure that you want to delete permanently the filter {name}?",
|
package/src/messages/es.js
CHANGED
|
@@ -65,6 +65,7 @@ export default {
|
|
|
65
65
|
"search.saveFilterForm.filter_name": "Nombre",
|
|
66
66
|
"search.save_filter.cancel": "Cancelar",
|
|
67
67
|
"search.save_filter.save": "Guardar",
|
|
68
|
+
"search.save_filter.save_global": "Guardar para todos los usuarios",
|
|
68
69
|
"search.filters.actions.delete.confirmation.header":
|
|
69
70
|
"Eliminar filtro personalizado",
|
|
70
71
|
"search.filters.actions.delete.confirmation.content":
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { toItem, transform } from "../operators";
|
|
2
|
+
|
|
3
|
+
describe("services: operators toItem should transform to existing operator/modifier format", () => {
|
|
4
|
+
const id = "123";
|
|
5
|
+
|
|
6
|
+
it("simple operator", () => {
|
|
7
|
+
expect(
|
|
8
|
+
toItem({
|
|
9
|
+
id,
|
|
10
|
+
name: "name",
|
|
11
|
+
returnType: "boolean",
|
|
12
|
+
args: [{ type: "xyz" }],
|
|
13
|
+
})
|
|
14
|
+
).toEqual({ id, category: "xyz", name: "name", returnType: "boolean" });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("includes scope and group", () => {
|
|
18
|
+
expect(
|
|
19
|
+
toItem({
|
|
20
|
+
id,
|
|
21
|
+
name: "name",
|
|
22
|
+
group: "group",
|
|
23
|
+
scope: "scope",
|
|
24
|
+
returnType: "boolean",
|
|
25
|
+
args: [{ type: "any" }],
|
|
26
|
+
})
|
|
27
|
+
).toEqual({
|
|
28
|
+
id,
|
|
29
|
+
category: "any",
|
|
30
|
+
name: "name",
|
|
31
|
+
group: "group",
|
|
32
|
+
scope: "scope",
|
|
33
|
+
returnType: "boolean",
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("maps as a modifier with params if returnType is not boolean", () => {
|
|
38
|
+
expect(
|
|
39
|
+
toItem({
|
|
40
|
+
id,
|
|
41
|
+
name: "name",
|
|
42
|
+
returnType: "string",
|
|
43
|
+
args: [
|
|
44
|
+
{ type: "cat" },
|
|
45
|
+
{ name: "start", type: "number" },
|
|
46
|
+
{ name: "end", type: "number" },
|
|
47
|
+
],
|
|
48
|
+
})
|
|
49
|
+
).toEqual({
|
|
50
|
+
id,
|
|
51
|
+
category: "cat",
|
|
52
|
+
name: "name",
|
|
53
|
+
type: "string",
|
|
54
|
+
params: [
|
|
55
|
+
{ name: "start", type: "number" },
|
|
56
|
+
{ name: "end", type: "number" },
|
|
57
|
+
],
|
|
58
|
+
returnType: "string",
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("maps arity and values", () => {
|
|
63
|
+
expect(
|
|
64
|
+
toItem({
|
|
65
|
+
id,
|
|
66
|
+
name: "variation_on_count",
|
|
67
|
+
scope: "validation",
|
|
68
|
+
returnType: "boolean",
|
|
69
|
+
args: [
|
|
70
|
+
{ type: "date" },
|
|
71
|
+
{
|
|
72
|
+
type: "string",
|
|
73
|
+
values: ["D-0", "D-1", "M-0", "M-1", "M-2", "Y-0", "Y-1"],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: "string",
|
|
77
|
+
values: ["D-0", "D-1", "M-0", "M-1", "M-2", "Y-0", "Y-1"],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
})
|
|
81
|
+
).toEqual({
|
|
82
|
+
id,
|
|
83
|
+
category: "date",
|
|
84
|
+
name: "variation_on_count",
|
|
85
|
+
value_type: "string",
|
|
86
|
+
fixed_values: ["D-0", "D-1", "M-0", "M-1", "M-2", "Y-0", "Y-1"],
|
|
87
|
+
scope: "validation",
|
|
88
|
+
arity: 2,
|
|
89
|
+
returnType: "boolean",
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("maps value_type_filter if type is 'ref'", () => {
|
|
94
|
+
expect(
|
|
95
|
+
toItem({
|
|
96
|
+
id,
|
|
97
|
+
name: "references",
|
|
98
|
+
scope: "validation_filter",
|
|
99
|
+
group: "references",
|
|
100
|
+
returnType: "boolean",
|
|
101
|
+
args: [{ type: "any" }, { type: "ref" }],
|
|
102
|
+
})
|
|
103
|
+
).toEqual({
|
|
104
|
+
id,
|
|
105
|
+
category: "any",
|
|
106
|
+
group: "references",
|
|
107
|
+
name: "references",
|
|
108
|
+
value_type: "field",
|
|
109
|
+
value_type_filter: "any",
|
|
110
|
+
scope: "validation",
|
|
111
|
+
population: true,
|
|
112
|
+
returnType: "boolean",
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("services: operators transform", () => {
|
|
118
|
+
test("transforms items and groups by category", () => {
|
|
119
|
+
const data = {
|
|
120
|
+
functions: [
|
|
121
|
+
{
|
|
122
|
+
id: "1",
|
|
123
|
+
name: "name",
|
|
124
|
+
returnType: "boolean",
|
|
125
|
+
args: [{ type: "xyz" }],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "2",
|
|
129
|
+
name: "name",
|
|
130
|
+
group: "group",
|
|
131
|
+
scope: "scope",
|
|
132
|
+
returnType: "boolean",
|
|
133
|
+
args: [{ type: "any" }],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "3",
|
|
137
|
+
name: "name",
|
|
138
|
+
returnType: "string",
|
|
139
|
+
args: [
|
|
140
|
+
{ type: "cat" },
|
|
141
|
+
{ name: "start", type: "number" },
|
|
142
|
+
{ name: "end", type: "number" },
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: "4",
|
|
147
|
+
name: "variation_on_count",
|
|
148
|
+
scope: "validation",
|
|
149
|
+
returnType: "boolean",
|
|
150
|
+
args: [
|
|
151
|
+
{ type: "date" },
|
|
152
|
+
{
|
|
153
|
+
type: "string",
|
|
154
|
+
values: ["D-0", "D-1", "M-0", "M-1", "M-2", "Y-0", "Y-1"],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
type: "string",
|
|
158
|
+
values: ["D-0", "D-1", "M-0", "M-1", "M-2", "Y-0", "Y-1"],
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "5",
|
|
164
|
+
name: "references",
|
|
165
|
+
scope: "validation_filter",
|
|
166
|
+
group: "references",
|
|
167
|
+
returnType: "boolean",
|
|
168
|
+
args: [{ type: "any" }, { type: "ref" }],
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
expect(transform(data)).toEqual({
|
|
173
|
+
xyz: { operators: [{ id: "1", name: "name" }] },
|
|
174
|
+
any: {
|
|
175
|
+
operators: [
|
|
176
|
+
{ id: "2", name: "name", group: "group", scope: "scope" },
|
|
177
|
+
{
|
|
178
|
+
id: "5",
|
|
179
|
+
name: "references",
|
|
180
|
+
group: "references",
|
|
181
|
+
scope: "validation",
|
|
182
|
+
value_type: "field",
|
|
183
|
+
value_type_filter: "any",
|
|
184
|
+
population: true,
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
cat: {
|
|
189
|
+
modifiers: [
|
|
190
|
+
{
|
|
191
|
+
id: "3",
|
|
192
|
+
name: "name",
|
|
193
|
+
type: "string",
|
|
194
|
+
params: [
|
|
195
|
+
{ name: "start", type: "number" },
|
|
196
|
+
{ name: "end", type: "number" },
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
date: {
|
|
202
|
+
operators: [
|
|
203
|
+
{
|
|
204
|
+
arity: 2,
|
|
205
|
+
id: "4",
|
|
206
|
+
name: "variation_on_count",
|
|
207
|
+
scope: "validation",
|
|
208
|
+
value_type: "string",
|
|
209
|
+
fixed_values: ["D-0", "D-1", "M-0", "M-1", "M-2", "Y-0", "Y-1"],
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
|
|
3
|
+
export const toItem = ({
|
|
4
|
+
name,
|
|
5
|
+
group,
|
|
6
|
+
returnType,
|
|
7
|
+
scope,
|
|
8
|
+
id,
|
|
9
|
+
args: [{ type: category }, ...args],
|
|
10
|
+
}) => {
|
|
11
|
+
const arity = _.size(args);
|
|
12
|
+
const arg1 = _.head(args);
|
|
13
|
+
const type = arg1?.type;
|
|
14
|
+
const values = arg1?.values;
|
|
15
|
+
const params =
|
|
16
|
+
_.isEmpty(args) || returnType === "boolean"
|
|
17
|
+
? null
|
|
18
|
+
: _.map(_.pick(["name", "type"]))(args);
|
|
19
|
+
const value_type =
|
|
20
|
+
returnType === "boolean" ? (type === "ref" ? "field" : type) : null;
|
|
21
|
+
return _.pickBy(_.identity)({
|
|
22
|
+
arity: returnType === "boolean" && arity > 1 ? arity : null,
|
|
23
|
+
category,
|
|
24
|
+
name,
|
|
25
|
+
group: group,
|
|
26
|
+
scope: scope === "validation_filter" ? "validation" : scope || null,
|
|
27
|
+
id,
|
|
28
|
+
type: returnType === "boolean" ? null : returnType,
|
|
29
|
+
value_type,
|
|
30
|
+
value_type_filter: type === "ref" ? "any" : null,
|
|
31
|
+
population: scope === "validation_filter",
|
|
32
|
+
fixed_values: values,
|
|
33
|
+
returnType,
|
|
34
|
+
params,
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Transfrom functions from GraphQL API to old model (operators + modifiers)
|
|
39
|
+
export const transform = _.flow(
|
|
40
|
+
_.propOr([], "functions"),
|
|
41
|
+
_.map(toItem),
|
|
42
|
+
_.flow(
|
|
43
|
+
_.groupBy("category"),
|
|
44
|
+
_.mapValues(_.map(_.omit(["category"]))),
|
|
45
|
+
_.mapValues(
|
|
46
|
+
_.flow(
|
|
47
|
+
_.groupBy(({ returnType }) =>
|
|
48
|
+
returnType === "boolean" ? "operators" : "modifiers"
|
|
49
|
+
),
|
|
50
|
+
_.mapValues(_.map(_.omit(["returnType"])))
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
);
|