@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 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.1",
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.1",
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": "aec23f8f32a131dfa84620ec6e14c31916118d44"
115
+ "gitHead": "d4f6cc40692e4d1e3f13e42774938765fe49c3b7"
116
116
  }
@@ -32,3 +32,20 @@ export const TEMPLATES_QUERY = gql`
32
32
  }
33
33
  }
34
34
  `;
35
+
36
+ export const FUNCTIONS_QUERY = gql`
37
+ query Functions {
38
+ functions {
39
+ id
40
+ name
41
+ group
42
+ returnType
43
+ scope
44
+ args {
45
+ name
46
+ type
47
+ values
48
+ }
49
+ }
50
+ }
51
+ `;
@@ -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 actions = (handleSubmit) => [
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(handleSubmit)}
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
- {userFilters.map((userFilter, key) => (
20
- <Label
21
- basic={selectedUserFilter == userFilter.name ? false : true}
22
- circular
23
- key={key}
24
- >
25
- <span
26
- onClick={
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
- {userFilter.name}
42
- </span>
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
- <ConfirmModal
45
- icon="trash"
46
- trigger={
47
- <Icon
48
- className="selectable"
49
- color="grey"
50
- size="small"
51
- name="trash alternate outline"
52
- />
53
- }
54
- header={
55
- <FormattedMessage id="search.filters.actions.delete.confirmation.header" />
56
- }
57
- content={
58
- <FormattedMessage
59
- id="search.filters.actions.delete.confirmation.content"
60
- values={{
61
- name: (
62
- <b>
63
- <i>{userFilter.name}</i>
64
- </b>
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
- onConfirm={() => deleteUserFilter({ id: userFilter.id })}
70
- />
71
- </Label>
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 { shallow } from "enzyme";
3
- import { ModalSaveFilter, SaveFilterForm } from "../ModalSaveFilter";
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 = { saveFilters, activeFilters };
11
- const wrapper = shallow(<ModalSaveFilter {...props} />);
12
- expect(wrapper).toMatchSnapshot();
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
- wrapper.find("FormInput").simulate("change", null, { value: "fname" });
30
- expect(setFilterName).toBeCalledWith("fname");
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 { shallow } from "enzyme";
3
- import { UserFilters } from "../UserFilters";
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
- describe("<UserFilters />", () => {
6
- const applyUserFilter = jest.fn();
7
- const deleteUserFilter = jest.fn();
8
- const resetFilters = jest.fn();
9
- const selectedUserFilter = null;
10
- const userFilters = [{ name: "a" }, { name: "b" }];
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 wrapper = shallow(<UserFilters {...props} />);
21
- expect(wrapper).toMatchSnapshot();
38
+ const { container } = render(<UserFilters {...props} />, renderOpts);
39
+ expect(container).toMatchSnapshot();
22
40
  });
23
41
 
24
- it("renders one label for each user filter", () => {
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 wrapper = shallow(<UserFilters {...props} />);
33
- expect(wrapper.find("Label")).toHaveLength(2);
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 wrapper = shallow(<UserFilters {...props} />);
45
- const e = { preventDefault: jest.fn(), stopPropagation: jest.fn() };
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 wrapper = shallow(<UserFilters {...props} />);
63
- const e = { preventDefault: jest.fn(), stopPropagation: jest.fn() };
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
- <ConfirmModal
5
- actions={
6
- Array [
7
- Object {
8
- "content": <Memo(MemoizedFormattedMessage)
9
- id="search.save_filter.cancel"
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[`<SaveFilterForm /> matches the latest snapshot 1`] = `
51
- <Form
52
- as="form"
53
- >
54
- <FormField>
55
- <label>
56
- <MemoizedFormattedMessage
57
- id="search.saveFilterForm.filter_name"
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
- className="selectedFilters"
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
- <span
13
- className="userFilter"
14
- onClick={[Function]}
8
+ <div
9
+ class="ui basic circular label"
15
10
  >
16
- a
17
- </span>
18
- <ConfirmModal
19
- content={
20
- <Memo(MemoizedFormattedMessage)
21
- id="search.filters.actions.delete.confirmation.content"
22
- values={
23
- Object {
24
- "name": <b>
25
- <i>
26
- a
27
- </i>
28
- </b>,
29
- }
30
- }
31
- />
32
- }
33
- header={
34
- <Memo(MemoizedFormattedMessage)
35
- id="search.filters.actions.delete.confirmation.header"
36
- />
37
- }
38
- icon="trash"
39
- onConfirm={[Function]}
40
- trigger={
41
- <Icon
42
- as="i"
43
- className="selectable"
44
- color="grey"
45
- name="trash alternate outline"
46
- size="small"
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
- <span
57
- className="userFilter"
58
- onClick={[Function]}
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
- b
61
- </span>
62
- <ConfirmModal
63
- content={
64
- <Memo(MemoizedFormattedMessage)
65
- id="search.filters.actions.delete.confirmation.content"
66
- values={
67
- Object {
68
- "name": <b>
69
- <i>
70
- b
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
  `;
@@ -3,3 +3,4 @@ export * from "./useActiveRoutes";
3
3
  export * from "./useAuthorized";
4
4
  export * from "./usePath";
5
5
  export * from "./useOnScreen";
6
+ export * from "./useOperators";
@@ -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;
@@ -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}?",
@@ -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
+ );