@truedat/core 6.6.0 → 6.6.2
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 +3 -3
- package/src/api.js +1 -1
- package/src/components/DateFilter.js +16 -14
- package/src/components/ResourceMembers.js +0 -1
- package/src/components/__tests__/SearchDateFilter.spec.js +64 -0
- package/src/components/__tests__/__snapshots__/SearchDateFilter.spec.js.snap +183 -0
- package/src/components/common/SearchContextWrapper.js +51 -0
- package/src/hooks/useUserFilters.js +1 -3
- package/src/messages/en.js +1 -0
- package/src/messages/es.js +1 -0
- package/src/routes.js +2 -0
- package/src/search/SearchContext.js +28 -6
- package/src/search/SearchDateFilter.js +35 -0
- package/src/search/SearchSelectedFilters.js +7 -2
- package/src/search/SearchWidget.js +49 -7
- package/src/search/__tests__/SearchWidget.spec.js +45 -8
- package/src/search/__tests__/__snapshots__/SearchWidget.spec.js.snap +60 -1
- package/src/selectors/__tests__/makeDateFiltersSelector.spec.js +4 -4
- package/src/selectors/makeDateFiltersSelector.js +3 -46
- package/src/services/__tests__/dateFilterFormatter.spec.js +93 -0
- package/src/services/dateFilterFormatter.js +48 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/core",
|
|
3
|
-
"version": "6.6.
|
|
3
|
+
"version": "6.6.2",
|
|
4
4
|
"description": "Truedat Web Core",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"jsnext:main": "src/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@testing-library/react": "^12.0.0",
|
|
37
37
|
"@testing-library/react-hooks": "^8.0.1",
|
|
38
38
|
"@testing-library/user-event": "^13.2.1",
|
|
39
|
-
"@truedat/test": "6.6.
|
|
39
|
+
"@truedat/test": "6.6.2",
|
|
40
40
|
"babel-jest": "^28.1.0",
|
|
41
41
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
42
42
|
"babel-plugin-lodash": "^3.3.4",
|
|
@@ -118,5 +118,5 @@
|
|
|
118
118
|
"react-dom": ">= 16.8.6 < 17",
|
|
119
119
|
"semantic-ui-react": ">= 2.0.3 < 2.2"
|
|
120
120
|
},
|
|
121
|
-
"gitHead": "
|
|
121
|
+
"gitHead": "b5defab13b213876e57046dc7d9885c6adc4a64d"
|
|
122
122
|
}
|
package/src/api.js
CHANGED
|
@@ -8,6 +8,6 @@ export const API_REINDEX_GRANTS = "/api/grants/search/reindex_all";
|
|
|
8
8
|
export const API_REINDEX_STRUCTURES = "/api/data_structures/search/reindex_all";
|
|
9
9
|
export const API_USER_FILTERS = "/api/:type";
|
|
10
10
|
export const API_USER_FILTER = "/api/:type/:id";
|
|
11
|
-
export const API_GET_USER_FILTERS = "/api/:type/
|
|
11
|
+
export const API_GET_USER_FILTERS = "/api/:type/me";
|
|
12
12
|
export const API_ACL_ENTRY = "/api/acl_entries/:id";
|
|
13
13
|
export const API_ACL_RESOURCE_ENTRIES = "/api/acl_entries/:type/:id";
|
|
@@ -9,45 +9,45 @@ const typeOptions = ({ formatMessage }) => [
|
|
|
9
9
|
{
|
|
10
10
|
key: "since",
|
|
11
11
|
text: formatMessage({ id: "dateFilter.since" }),
|
|
12
|
-
value: "since"
|
|
12
|
+
value: "since",
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
key: "before",
|
|
16
16
|
text: formatMessage({ id: "dateFilter.before" }),
|
|
17
|
-
value: "before"
|
|
17
|
+
value: "before",
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
key: "range",
|
|
21
21
|
text: formatMessage({ id: "dateFilter.range" }),
|
|
22
|
-
value: "range"
|
|
23
|
-
}
|
|
22
|
+
value: "range",
|
|
23
|
+
},
|
|
24
24
|
];
|
|
25
25
|
|
|
26
26
|
const dateUnitOptions = ({ formatMessage }) => [
|
|
27
27
|
{
|
|
28
28
|
key: "days",
|
|
29
29
|
text: formatMessage({ id: "dateFilter.days" }),
|
|
30
|
-
value: "d"
|
|
30
|
+
value: "d",
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
key: "weeks",
|
|
34
34
|
text: formatMessage({ id: "dateFilter.weeks" }),
|
|
35
|
-
value: "w"
|
|
35
|
+
value: "w",
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
key: "months",
|
|
39
39
|
text: formatMessage({ id: "dateFilter.months" }),
|
|
40
|
-
value: "M"
|
|
40
|
+
value: "M",
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
key: "years",
|
|
44
44
|
text: formatMessage({ id: "dateFilter.years" }),
|
|
45
|
-
value: "y"
|
|
46
|
-
}
|
|
45
|
+
value: "y",
|
|
46
|
+
},
|
|
47
47
|
];
|
|
48
48
|
|
|
49
|
-
const validNumber = value => RegExp("^\\d+$").test(value);
|
|
50
|
-
const validRange = value =>
|
|
49
|
+
const validNumber = (value) => RegExp("^\\d+$").test(value);
|
|
50
|
+
const validRange = (value) =>
|
|
51
51
|
RegExp("^\\d{4}-\\d\\d-\\d\\d *- *\\d{4}-\\d\\d-\\d\\d$").test(value);
|
|
52
52
|
|
|
53
53
|
export const DateFilter = ({
|
|
@@ -56,19 +56,21 @@ export const DateFilter = ({
|
|
|
56
56
|
defaultValues,
|
|
57
57
|
size = "small",
|
|
58
58
|
name = "date",
|
|
59
|
-
dateFormat = "YYYY-MM-DD"
|
|
59
|
+
dateFormat = "YYYY-MM-DD",
|
|
60
60
|
}) => {
|
|
61
61
|
const intl = useIntl();
|
|
62
62
|
const [type, setType] = useState(defaultValues?.type || "since");
|
|
63
63
|
const [unit, setUnit] = useState(defaultValues?.unit || "d");
|
|
64
64
|
const [value, setValue] = useState(defaultValues?.value || "1");
|
|
65
65
|
const [range, setRange] = useState(defaultValues?.range || "");
|
|
66
|
+
|
|
66
67
|
useEffect(() => {
|
|
67
68
|
const valid = type === "range" ? validRange(range) : validNumber(value);
|
|
68
69
|
if (valid) {
|
|
69
70
|
onChange({ name, type, range, unit, value });
|
|
70
71
|
}
|
|
71
|
-
}, [name, type, unit, value, range
|
|
72
|
+
}, [name, type, unit, value, range]);
|
|
73
|
+
|
|
72
74
|
const currentDate = moment();
|
|
73
75
|
return (
|
|
74
76
|
<Form size={size} autoComplete="off">
|
|
@@ -125,7 +127,7 @@ DateFilter.propTypes = {
|
|
|
125
127
|
label: PropTypes.node,
|
|
126
128
|
name: PropTypes.string,
|
|
127
129
|
onChange: PropTypes.func,
|
|
128
|
-
size: PropTypes.string
|
|
130
|
+
size: PropTypes.string,
|
|
129
131
|
};
|
|
130
132
|
|
|
131
133
|
export default DateFilter;
|
|
@@ -37,7 +37,6 @@ export const ResourceMembers = ({ type }) => {
|
|
|
37
37
|
mutate: refreshAclEntries,
|
|
38
38
|
} = useAclEntries({ type, id });
|
|
39
39
|
const { aclEntries, actions } = data;
|
|
40
|
-
// debugger
|
|
41
40
|
const { formatMessage } = useIntl();
|
|
42
41
|
const [searchFilter, setSearchFilter] = useState("");
|
|
43
42
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@truedat/test/render";
|
|
3
|
+
import SearchDateFilter from "@truedat/core/search/SearchDateFilter";
|
|
4
|
+
import SearchContextWrapper from "@truedat/core/components/common/SearchContextWrapper";
|
|
5
|
+
|
|
6
|
+
describe("<SearchDateFilter/>", () => {
|
|
7
|
+
const defaultValues = {
|
|
8
|
+
active: false,
|
|
9
|
+
name: "updated_at",
|
|
10
|
+
type: "since",
|
|
11
|
+
unit: "d",
|
|
12
|
+
value: "1",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const renderOpts = {
|
|
16
|
+
messages: {
|
|
17
|
+
en: {
|
|
18
|
+
"queryables.form.resource": "resource",
|
|
19
|
+
"queryables.resource.type": "type",
|
|
20
|
+
"queryables.resource.type.data_structure": "data_structure",
|
|
21
|
+
"queryables.resource.type.reference_dataset": "reference_dataset",
|
|
22
|
+
"queryables.resource.type.data_view": "data_view",
|
|
23
|
+
"queryables.resource.selector.data_view": "data_view",
|
|
24
|
+
"queryables.resource.selector.reference_dataset": "reference_dataset",
|
|
25
|
+
"structures.not_found.body": "body",
|
|
26
|
+
"structures.not_found.header": "header",
|
|
27
|
+
"structures.search.placeholder": "placeholder",
|
|
28
|
+
"structures.loading.header": "loading",
|
|
29
|
+
"filter.updated_at": "Updated at",
|
|
30
|
+
"dateFilter.years": "Years",
|
|
31
|
+
"dateFilter.months": "Months",
|
|
32
|
+
"dateFilter.weeks": "Weeks",
|
|
33
|
+
"dateFilter.days": "Days",
|
|
34
|
+
"dateFilter.range": "Range",
|
|
35
|
+
"dateFilter.before": "Before",
|
|
36
|
+
"dateFilter.since": "Since",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const searchProps = {
|
|
42
|
+
setDateFilters: jest.fn(),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
it("matches the latest snapshot", () => {
|
|
46
|
+
const { container } = render(
|
|
47
|
+
<SearchContextWrapper props={searchProps}>
|
|
48
|
+
<SearchDateFilter active={true} defaultValues={defaultValues} />,
|
|
49
|
+
</SearchContextWrapper>,
|
|
50
|
+
renderOpts
|
|
51
|
+
);
|
|
52
|
+
expect(container).toMatchSnapshot();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("active false", () => {
|
|
56
|
+
const { container } = render(
|
|
57
|
+
<SearchContextWrapper props={searchProps}>
|
|
58
|
+
<SearchDateFilter active={false} defaultValues={defaultValues} />,
|
|
59
|
+
</SearchContextWrapper>,
|
|
60
|
+
renderOpts
|
|
61
|
+
);
|
|
62
|
+
expect(container).toMatchSnapshot();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<SearchDateFilter/> active false 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
,
|
|
6
|
+
</div>
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
exports[`<SearchDateFilter/> matches the latest snapshot 1`] = `
|
|
10
|
+
<div>
|
|
11
|
+
<form
|
|
12
|
+
autocomplete="off"
|
|
13
|
+
class="ui small form"
|
|
14
|
+
>
|
|
15
|
+
<div
|
|
16
|
+
class="fields date filter"
|
|
17
|
+
>
|
|
18
|
+
<div
|
|
19
|
+
class="inline field"
|
|
20
|
+
>
|
|
21
|
+
<label>
|
|
22
|
+
Updated at
|
|
23
|
+
</label>
|
|
24
|
+
<div
|
|
25
|
+
aria-expanded="false"
|
|
26
|
+
class="ui selection dropdown"
|
|
27
|
+
role="listbox"
|
|
28
|
+
tabindex="0"
|
|
29
|
+
>
|
|
30
|
+
<div
|
|
31
|
+
aria-atomic="true"
|
|
32
|
+
aria-live="polite"
|
|
33
|
+
class="divider text"
|
|
34
|
+
role="alert"
|
|
35
|
+
>
|
|
36
|
+
Since
|
|
37
|
+
</div>
|
|
38
|
+
<i
|
|
39
|
+
aria-hidden="true"
|
|
40
|
+
class="dropdown icon"
|
|
41
|
+
/>
|
|
42
|
+
<div
|
|
43
|
+
class="menu transition"
|
|
44
|
+
>
|
|
45
|
+
<div
|
|
46
|
+
aria-checked="true"
|
|
47
|
+
aria-selected="true"
|
|
48
|
+
class="active selected item"
|
|
49
|
+
role="option"
|
|
50
|
+
style="pointer-events: all;"
|
|
51
|
+
>
|
|
52
|
+
<span
|
|
53
|
+
class="text"
|
|
54
|
+
>
|
|
55
|
+
Since
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
<div
|
|
59
|
+
aria-checked="false"
|
|
60
|
+
aria-selected="false"
|
|
61
|
+
class="item"
|
|
62
|
+
role="option"
|
|
63
|
+
style="pointer-events: all;"
|
|
64
|
+
>
|
|
65
|
+
<span
|
|
66
|
+
class="text"
|
|
67
|
+
>
|
|
68
|
+
Before
|
|
69
|
+
</span>
|
|
70
|
+
</div>
|
|
71
|
+
<div
|
|
72
|
+
aria-checked="false"
|
|
73
|
+
aria-selected="false"
|
|
74
|
+
class="item"
|
|
75
|
+
role="option"
|
|
76
|
+
style="pointer-events: all;"
|
|
77
|
+
>
|
|
78
|
+
<span
|
|
79
|
+
class="text"
|
|
80
|
+
>
|
|
81
|
+
Range
|
|
82
|
+
</span>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
<div
|
|
88
|
+
class="field since"
|
|
89
|
+
>
|
|
90
|
+
<div
|
|
91
|
+
class="ui right labeled input"
|
|
92
|
+
>
|
|
93
|
+
<input
|
|
94
|
+
placeholder="5"
|
|
95
|
+
type="number"
|
|
96
|
+
value="1"
|
|
97
|
+
/>
|
|
98
|
+
<div
|
|
99
|
+
class="ui label"
|
|
100
|
+
>
|
|
101
|
+
<div
|
|
102
|
+
aria-expanded="false"
|
|
103
|
+
class="ui dropdown"
|
|
104
|
+
role="listbox"
|
|
105
|
+
tabindex="0"
|
|
106
|
+
>
|
|
107
|
+
<div
|
|
108
|
+
aria-atomic="true"
|
|
109
|
+
aria-live="polite"
|
|
110
|
+
class="divider text"
|
|
111
|
+
role="alert"
|
|
112
|
+
>
|
|
113
|
+
Days
|
|
114
|
+
</div>
|
|
115
|
+
<i
|
|
116
|
+
aria-hidden="true"
|
|
117
|
+
class="dropdown icon"
|
|
118
|
+
/>
|
|
119
|
+
<div
|
|
120
|
+
class="menu transition"
|
|
121
|
+
>
|
|
122
|
+
<div
|
|
123
|
+
aria-checked="true"
|
|
124
|
+
aria-selected="true"
|
|
125
|
+
class="active selected item"
|
|
126
|
+
role="option"
|
|
127
|
+
style="pointer-events: all;"
|
|
128
|
+
>
|
|
129
|
+
<span
|
|
130
|
+
class="text"
|
|
131
|
+
>
|
|
132
|
+
Days
|
|
133
|
+
</span>
|
|
134
|
+
</div>
|
|
135
|
+
<div
|
|
136
|
+
aria-checked="false"
|
|
137
|
+
aria-selected="false"
|
|
138
|
+
class="item"
|
|
139
|
+
role="option"
|
|
140
|
+
style="pointer-events: all;"
|
|
141
|
+
>
|
|
142
|
+
<span
|
|
143
|
+
class="text"
|
|
144
|
+
>
|
|
145
|
+
Weeks
|
|
146
|
+
</span>
|
|
147
|
+
</div>
|
|
148
|
+
<div
|
|
149
|
+
aria-checked="false"
|
|
150
|
+
aria-selected="false"
|
|
151
|
+
class="item"
|
|
152
|
+
role="option"
|
|
153
|
+
style="pointer-events: all;"
|
|
154
|
+
>
|
|
155
|
+
<span
|
|
156
|
+
class="text"
|
|
157
|
+
>
|
|
158
|
+
Months
|
|
159
|
+
</span>
|
|
160
|
+
</div>
|
|
161
|
+
<div
|
|
162
|
+
aria-checked="false"
|
|
163
|
+
aria-selected="false"
|
|
164
|
+
class="item"
|
|
165
|
+
role="option"
|
|
166
|
+
style="pointer-events: all;"
|
|
167
|
+
>
|
|
168
|
+
<span
|
|
169
|
+
class="text"
|
|
170
|
+
>
|
|
171
|
+
Years
|
|
172
|
+
</span>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</form>
|
|
181
|
+
,
|
|
182
|
+
</div>
|
|
183
|
+
`;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import SearchContext from "@truedat/core/search/SearchContext";
|
|
4
|
+
import SearchContextProvider from "@truedat/core/search/SearchContext";
|
|
5
|
+
|
|
6
|
+
export default function SearchContextWrapper({
|
|
7
|
+
provider = true,
|
|
8
|
+
props,
|
|
9
|
+
children,
|
|
10
|
+
}) {
|
|
11
|
+
const useSearch = () => ({
|
|
12
|
+
trigger: () => ({
|
|
13
|
+
then: (callback) =>
|
|
14
|
+
callback({
|
|
15
|
+
data: [],
|
|
16
|
+
headers: {},
|
|
17
|
+
}),
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const useFilters = () => ({
|
|
22
|
+
trigger: () => ({
|
|
23
|
+
then: (callback) =>
|
|
24
|
+
callback({
|
|
25
|
+
data: [],
|
|
26
|
+
}),
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const searchProps = {
|
|
31
|
+
useSearch,
|
|
32
|
+
useFilters,
|
|
33
|
+
translations: jest.fn(),
|
|
34
|
+
defaultFilters: {},
|
|
35
|
+
...props,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return provider ? (
|
|
39
|
+
<SearchContext.Provider value={searchProps}>
|
|
40
|
+
{children}
|
|
41
|
+
</SearchContext.Provider>
|
|
42
|
+
) : (
|
|
43
|
+
<SearchContextProvider {...searchProps}>{children}</SearchContextProvider>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
SearchContextWrapper.propTypes = {
|
|
48
|
+
props: PropTypes.object,
|
|
49
|
+
children: PropTypes.node,
|
|
50
|
+
provider: PropTypes.bool,
|
|
51
|
+
};
|
|
@@ -18,9 +18,7 @@ import {
|
|
|
18
18
|
export const useUserFilters = (type, scope) => {
|
|
19
19
|
const url = _.flow(
|
|
20
20
|
() => compile(API_GET_USER_FILTERS)({ type }),
|
|
21
|
-
|
|
22
|
-
_.compact,
|
|
23
|
-
_.join("")
|
|
21
|
+
(baseUrl) => `${baseUrl}${scope ? `?scope=${scope}` : ""}`
|
|
24
22
|
)("");
|
|
25
23
|
const { data, error, mutate } = useSWR(url, apiJson);
|
|
26
24
|
const userFilters = data?.data?.data;
|
package/src/messages/en.js
CHANGED
|
@@ -151,6 +151,7 @@ export default {
|
|
|
151
151
|
"sidemenu.taxonomy": "Taxonomy",
|
|
152
152
|
"sidemenu.templates": "Templates",
|
|
153
153
|
"sidemenu.users": "Users",
|
|
154
|
+
"structures.search.placeholder": "Search structures...",
|
|
154
155
|
"view.unauthorized.content":
|
|
155
156
|
"If you want to update your permissions, please contact the administrator.",
|
|
156
157
|
"view.unauthorized.head": "You are not authorized to view this content",
|
package/src/messages/es.js
CHANGED
|
@@ -155,6 +155,7 @@ export default {
|
|
|
155
155
|
"sidemenu.taxonomy": "Dominios",
|
|
156
156
|
"sidemenu.templates": "Plantillas",
|
|
157
157
|
"sidemenu.users": "Usuarios",
|
|
158
|
+
"structures.search.placeholder": "Buscar estructuras...",
|
|
158
159
|
"view.unauthorized.head":
|
|
159
160
|
"No dispones de autorización para ver este contenido",
|
|
160
161
|
"view.unauthorized.content":
|
package/src/routes.js
CHANGED
|
@@ -69,6 +69,7 @@ export const GRANT_REQUEST = "/grantRequests/:id";
|
|
|
69
69
|
export const GRANT_REQUESTS = "/grantRequests";
|
|
70
70
|
export const GRANT_REQUESTS_APPROVALS_RESULT =
|
|
71
71
|
"/grantRequests/approvals/result";
|
|
72
|
+
export const GRANT_REQUEST_GROUP = "/grantRequestGroups/:id";
|
|
72
73
|
export const GRAPH = "/graphs/:id";
|
|
73
74
|
export const GRAPHS = "/graphs";
|
|
74
75
|
export const GROUP = "/groups/:id";
|
|
@@ -309,6 +310,7 @@ const routes = {
|
|
|
309
310
|
GRANT_REQUEST,
|
|
310
311
|
GRANT_REQUESTS,
|
|
311
312
|
GRANT_REQUESTS_APPROVALS_RESULT,
|
|
313
|
+
GRANT_REQUEST_GROUP,
|
|
312
314
|
GRAPH,
|
|
313
315
|
GRAPHS,
|
|
314
316
|
GROUP,
|
|
@@ -20,27 +20,31 @@ export const SearchContextProvider = (props) => {
|
|
|
20
20
|
const children = _.prop("children")(props);
|
|
21
21
|
const initialSortColumn = _.prop("initialSortColumn")(props);
|
|
22
22
|
const initialSortDirection = _.prop("initialSortDirection")(props);
|
|
23
|
-
const defaultFilters = _.prop("defaultFilters")(props);
|
|
24
23
|
const useSearch = _.prop("useSearch")(props);
|
|
25
24
|
const useFilters = _.prop("useFilters")(props);
|
|
26
|
-
const pageSize = _.propOr(
|
|
25
|
+
const pageSize = _.propOr(7, "pageSize")(props);
|
|
27
26
|
const userFiltersType = _.prop("userFiltersType")(props);
|
|
28
27
|
const userFilterScope = _.prop("userFilterScope")(props);
|
|
29
28
|
const omitFilters = _.propOr([], "omitFilters")(props);
|
|
30
29
|
const translations = _.propOr(() => ({}), "translations")(props);
|
|
31
30
|
const filtersGroup = _.propOr([], "filtersGroup")(props);
|
|
31
|
+
const defaultFilters = useState(_.prop("defaultFilters")(props))[0];
|
|
32
|
+
const enrichSearchPayload = useState(_.prop("enrichSearchPayload")(props))[0];
|
|
32
33
|
|
|
33
34
|
const { formatMessage } = useIntl();
|
|
34
35
|
|
|
35
36
|
const [loading, setLoading] = useState(true);
|
|
36
37
|
const [searchData, setSearchData] = useState([]);
|
|
37
38
|
|
|
38
|
-
const [filtersPayload, setFiltersPayload] = useState(
|
|
39
|
+
const [filtersPayload, setFiltersPayload] = useState({});
|
|
39
40
|
const [loadingFilters, setLoadingFilters] = useState(true);
|
|
40
41
|
const [query, setQuery] = useState("");
|
|
41
42
|
const [activeFilterName, setActiveFilterName] = useState([]);
|
|
42
43
|
const [allActiveFilters, setAllActiveFilters] = useState({});
|
|
43
44
|
const [hiddenFilters, setHiddenFilters] = useState({});
|
|
45
|
+
const [dateFilters, setDateFilters] = useState({});
|
|
46
|
+
const [toggleDateFilter, setToggleDateFilter] = useState(false);
|
|
47
|
+
|
|
44
48
|
const [filterParams, setFilterParams] = useState({});
|
|
45
49
|
const [sortColumn, setSortColumn] = useState(initialSortColumn);
|
|
46
50
|
const [sortDirection, setSortDirection] = useState(initialSortDirection);
|
|
@@ -104,6 +108,11 @@ export const SearchContextProvider = (props) => {
|
|
|
104
108
|
_.omitBy(_.flow(_.propOr([], "values"), (values) => _.size(values) < 2))
|
|
105
109
|
)(filtersPayload);
|
|
106
110
|
|
|
111
|
+
const allFilters = _.flow(
|
|
112
|
+
_.propOr({}, "data"),
|
|
113
|
+
_.omit(omitFilters)
|
|
114
|
+
)(filtersPayload);
|
|
115
|
+
|
|
107
116
|
const availableFilters = _.flow(_.keys, _.without(selectedFilters))(filters);
|
|
108
117
|
const filterTypes = _.mapValues("type")(filters);
|
|
109
118
|
|
|
@@ -129,9 +138,10 @@ export const SearchContextProvider = (props) => {
|
|
|
129
138
|
const searchMust = useMemo(
|
|
130
139
|
() => ({
|
|
131
140
|
...defaultFilters,
|
|
141
|
+
...dateFilters,
|
|
132
142
|
..._.pickBy(_.negate(_.isEmpty))(allActiveFilters),
|
|
133
143
|
}),
|
|
134
|
-
[allActiveFilters, defaultFilters]
|
|
144
|
+
[allActiveFilters, defaultFilters, dateFilters]
|
|
135
145
|
);
|
|
136
146
|
|
|
137
147
|
const filterMust = useMemo(
|
|
@@ -177,6 +187,7 @@ export const SearchContextProvider = (props) => {
|
|
|
177
187
|
const filterParam = {
|
|
178
188
|
...(!_.isEmpty(query) && { query }),
|
|
179
189
|
must: searchMust,
|
|
190
|
+
...enrichSearchPayload,
|
|
180
191
|
sort,
|
|
181
192
|
page: page - 1,
|
|
182
193
|
size,
|
|
@@ -188,7 +199,15 @@ export const SearchContextProvider = (props) => {
|
|
|
188
199
|
setCountData(headers);
|
|
189
200
|
setLoading(false);
|
|
190
201
|
});
|
|
191
|
-
}, [
|
|
202
|
+
}, [
|
|
203
|
+
query,
|
|
204
|
+
searchMust,
|
|
205
|
+
sort,
|
|
206
|
+
defaultFilters,
|
|
207
|
+
page,
|
|
208
|
+
size,
|
|
209
|
+
enrichSearchPayload,
|
|
210
|
+
]);
|
|
192
211
|
|
|
193
212
|
const makeFiltersGroup = (filters, groups) =>
|
|
194
213
|
_.groupBy((filter) =>
|
|
@@ -234,7 +253,6 @@ export const SearchContextProvider = (props) => {
|
|
|
234
253
|
setSortDirection(query == "" ? initialSortDirection : "descending");
|
|
235
254
|
setQuery(query);
|
|
236
255
|
};
|
|
237
|
-
|
|
238
256
|
const context = {
|
|
239
257
|
disabled: false,
|
|
240
258
|
loadingFilters,
|
|
@@ -264,6 +282,10 @@ export const SearchContextProvider = (props) => {
|
|
|
264
282
|
setAllActiveFilters,
|
|
265
283
|
allActiveFilters,
|
|
266
284
|
filters,
|
|
285
|
+
allFilters,
|
|
286
|
+
setDateFilters,
|
|
287
|
+
toggleDateFilter,
|
|
288
|
+
setToggleDateFilter,
|
|
267
289
|
|
|
268
290
|
searchData,
|
|
269
291
|
loading,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { isEmpty, omitBy } from "lodash/fp";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import { FormattedMessage } from "react-intl";
|
|
5
|
+
import DateFilter from "@truedat/core/components/DateFilter";
|
|
6
|
+
import { dateFilterFormatter } from "@truedat/core/services/dateFilterFormatter";
|
|
7
|
+
import { useSearchContext } from "@truedat/core/search/SearchContext";
|
|
8
|
+
|
|
9
|
+
const SearchDateFilter = ({ active, defaultValues }) => {
|
|
10
|
+
const dateFormatter = dateFilterFormatter();
|
|
11
|
+
const dateMust = (dateFilter) => omitBy(isEmpty)(dateFormatter(dateFilter));
|
|
12
|
+
|
|
13
|
+
const { setDateFilters } = useSearchContext();
|
|
14
|
+
|
|
15
|
+
const onChangeDateFilter = (e) => {
|
|
16
|
+
const dateFilterFormatted = dateMust({ active, ...e });
|
|
17
|
+
setDateFilters(dateFilterFormatted);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return active ? (
|
|
21
|
+
<DateFilter
|
|
22
|
+
label={<FormattedMessage id="filter.updated_at" />}
|
|
23
|
+
name="updated_at"
|
|
24
|
+
onChange={onChangeDateFilter}
|
|
25
|
+
defaultValues={defaultValues}
|
|
26
|
+
/>
|
|
27
|
+
) : null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
SearchDateFilter.propTypes = {
|
|
31
|
+
active: PropTypes.bool,
|
|
32
|
+
defaultValues: PropTypes.object,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default SearchDateFilter;
|
|
@@ -31,9 +31,14 @@ export default function SearchSelectedFilters() {
|
|
|
31
31
|
userFilterScope
|
|
32
32
|
);
|
|
33
33
|
|
|
34
|
-
const handleSubmit = ({ filterName, filters, isGlobal }) => {
|
|
34
|
+
const handleSubmit = ({ filterName, filters, isGlobal, scope }) => {
|
|
35
35
|
const requestData = {
|
|
36
|
-
user_search_filter: {
|
|
36
|
+
user_search_filter: {
|
|
37
|
+
name: filterName,
|
|
38
|
+
filters,
|
|
39
|
+
is_global: isGlobal,
|
|
40
|
+
scope: scope,
|
|
41
|
+
},
|
|
37
42
|
};
|
|
38
43
|
saveFilters(requestData).then(() => {
|
|
39
44
|
mutate();
|
|
@@ -1,29 +1,71 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Input } from "semantic-ui-react";
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Button, Icon, Input } from "semantic-ui-react";
|
|
3
3
|
import { useIntl } from "react-intl";
|
|
4
|
+
import PropTypes from "prop-types";
|
|
4
5
|
import SearchFilters from "./SearchFilters";
|
|
5
6
|
import SearchSelectedFilters from "./SearchSelectedFilters";
|
|
6
7
|
import { useSearchContext } from "./SearchContext";
|
|
8
|
+
import SearchDateFilter from "./SearchDateFilter";
|
|
7
9
|
|
|
8
|
-
export default function SearchWidget() {
|
|
10
|
+
export default function SearchWidget({ dateFilter = false }) {
|
|
9
11
|
const { formatMessage } = useIntl();
|
|
10
12
|
|
|
11
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
query,
|
|
15
|
+
setQuery,
|
|
16
|
+
loadingFilters: loading,
|
|
17
|
+
toggleDateFilter,
|
|
18
|
+
setToggleDateFilter,
|
|
19
|
+
setDateFilters,
|
|
20
|
+
} = useSearchContext();
|
|
21
|
+
|
|
22
|
+
const defaultValues = {
|
|
23
|
+
name: "updated_at",
|
|
24
|
+
type: "since",
|
|
25
|
+
unit: "d",
|
|
26
|
+
value: "1",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const onClickSearchDateFilter = () => {
|
|
30
|
+
setToggleDateFilter(!toggleDateFilter);
|
|
31
|
+
toggleDateFilter ? setDateFilters({}) : null;
|
|
32
|
+
};
|
|
12
33
|
|
|
13
34
|
return (
|
|
14
35
|
<>
|
|
15
36
|
<Input
|
|
16
37
|
value={query}
|
|
17
38
|
onChange={(_e, data) => setQuery(data.value)}
|
|
18
|
-
icon={{ name: "search", link: true }}
|
|
19
39
|
iconPosition="left"
|
|
20
|
-
action
|
|
40
|
+
action
|
|
21
41
|
placeholder={formatMessage({
|
|
22
42
|
id: "search.placeholder",
|
|
23
43
|
})}
|
|
24
44
|
loading={loading}
|
|
25
|
-
|
|
45
|
+
>
|
|
46
|
+
<Icon name="search" link />
|
|
47
|
+
<input />
|
|
48
|
+
{dateFilter ? (
|
|
49
|
+
<Button
|
|
50
|
+
icon="calendar alternate outline"
|
|
51
|
+
active={toggleDateFilter}
|
|
52
|
+
onClick={() => onClickSearchDateFilter()}
|
|
53
|
+
/>
|
|
54
|
+
) : null}
|
|
55
|
+
|
|
56
|
+
<SearchFilters />
|
|
57
|
+
</Input>
|
|
58
|
+
|
|
26
59
|
<SearchSelectedFilters />
|
|
60
|
+
|
|
61
|
+
<SearchDateFilter
|
|
62
|
+
active={toggleDateFilter}
|
|
63
|
+
defaultValues={defaultValues}
|
|
64
|
+
/>
|
|
27
65
|
</>
|
|
28
66
|
);
|
|
29
67
|
}
|
|
68
|
+
|
|
69
|
+
SearchWidget.propTypes = {
|
|
70
|
+
dateFilter: PropTypes.bool,
|
|
71
|
+
};
|
|
@@ -3,33 +3,52 @@ import React from "react";
|
|
|
3
3
|
import { render } from "@truedat/test/render";
|
|
4
4
|
import { waitFor } from "@testing-library/react";
|
|
5
5
|
import userEvent from "@testing-library/user-event";
|
|
6
|
-
import
|
|
6
|
+
import SearchContextWrapper from "@truedat/core/components/common/SearchContextWrapper";
|
|
7
7
|
import SearchWidget from "../SearchWidget";
|
|
8
8
|
|
|
9
9
|
const messages = {
|
|
10
10
|
en: {
|
|
11
11
|
"search.placeholder": "Search...",
|
|
12
|
+
"filter.updated_at": "Last changed",
|
|
13
|
+
"dateFilter.years": "Years",
|
|
14
|
+
"dateFilter.months": "Months",
|
|
15
|
+
"dateFilter.weeks": "Weeks",
|
|
16
|
+
"dateFilter.days": "Days",
|
|
17
|
+
"dateFilter.range": "Range",
|
|
18
|
+
"dateFilter.before": "Before",
|
|
19
|
+
"dateFilter.since": "Since",
|
|
12
20
|
},
|
|
13
21
|
};
|
|
14
22
|
|
|
15
23
|
const renderOpts = { messages };
|
|
16
24
|
|
|
17
25
|
const setQuery = jest.fn();
|
|
26
|
+
|
|
18
27
|
const searchProps = {
|
|
19
28
|
userFiltersType: "foo",
|
|
20
|
-
useFilters: jest.fn(),
|
|
21
|
-
useSearch: jest.fn(),
|
|
22
|
-
loadingFilters: false,
|
|
23
29
|
query: "",
|
|
24
30
|
setQuery,
|
|
31
|
+
toggleDateFilter: false,
|
|
32
|
+
setToggleDateFilter: jest.fn(),
|
|
33
|
+
setDateFilters: jest.fn(),
|
|
25
34
|
};
|
|
26
35
|
|
|
27
36
|
describe("<SearchWidget/>", () => {
|
|
28
37
|
it("matches the latest snapshot", () => {
|
|
29
38
|
const { container } = render(
|
|
30
|
-
<
|
|
39
|
+
<SearchContextWrapper props={searchProps}>
|
|
31
40
|
<SearchWidget />
|
|
32
|
-
</
|
|
41
|
+
</SearchContextWrapper>,
|
|
42
|
+
renderOpts
|
|
43
|
+
);
|
|
44
|
+
expect(container).toMatchSnapshot();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("matches the latest snapshot with SearchDateFilter active", () => {
|
|
48
|
+
const { container } = render(
|
|
49
|
+
<SearchContextWrapper props={searchProps}>
|
|
50
|
+
<SearchWidget dateFilter={true} />
|
|
51
|
+
</SearchContextWrapper>,
|
|
33
52
|
renderOpts
|
|
34
53
|
);
|
|
35
54
|
expect(container).toMatchSnapshot();
|
|
@@ -37,9 +56,9 @@ describe("<SearchWidget/>", () => {
|
|
|
37
56
|
|
|
38
57
|
it("type in filter input filter options", async () => {
|
|
39
58
|
const { getByRole } = render(
|
|
40
|
-
<
|
|
59
|
+
<SearchContextWrapper props={searchProps}>
|
|
41
60
|
<SearchWidget />
|
|
42
|
-
</
|
|
61
|
+
</SearchContextWrapper>,
|
|
43
62
|
renderOpts
|
|
44
63
|
);
|
|
45
64
|
|
|
@@ -49,4 +68,22 @@ describe("<SearchWidget/>", () => {
|
|
|
49
68
|
expect(setQuery).toBeCalledTimes(1);
|
|
50
69
|
});
|
|
51
70
|
});
|
|
71
|
+
|
|
72
|
+
it("show date filter", () => {
|
|
73
|
+
const props = {
|
|
74
|
+
dateFilter: true,
|
|
75
|
+
};
|
|
76
|
+
const newSearchProps = {
|
|
77
|
+
...searchProps,
|
|
78
|
+
toggleDateFilter: true,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const { queryByText } = render(
|
|
82
|
+
<SearchContextWrapper props={newSearchProps}>
|
|
83
|
+
<SearchWidget {...props} />
|
|
84
|
+
</SearchContextWrapper>,
|
|
85
|
+
renderOpts
|
|
86
|
+
);
|
|
87
|
+
expect(queryByText(/Weeks/)).toBeInTheDocument();
|
|
88
|
+
});
|
|
52
89
|
});
|
|
@@ -5,17 +5,76 @@ exports[`<SearchWidget/> matches the latest snapshot 1`] = `
|
|
|
5
5
|
<div
|
|
6
6
|
class="ui action left icon input"
|
|
7
7
|
>
|
|
8
|
+
<i
|
|
9
|
+
aria-hidden="true"
|
|
10
|
+
class="search link icon"
|
|
11
|
+
/>
|
|
8
12
|
<input
|
|
9
13
|
placeholder="Search..."
|
|
10
14
|
type="text"
|
|
11
15
|
value=""
|
|
12
16
|
/>
|
|
17
|
+
<div
|
|
18
|
+
aria-expanded="false"
|
|
19
|
+
class="ui button floating labeled scrolling dropdown icon"
|
|
20
|
+
role="listbox"
|
|
21
|
+
tabindex="0"
|
|
22
|
+
>
|
|
23
|
+
<div
|
|
24
|
+
aria-atomic="true"
|
|
25
|
+
aria-live="polite"
|
|
26
|
+
class="divider text"
|
|
27
|
+
role="alert"
|
|
28
|
+
>
|
|
29
|
+
Filters
|
|
30
|
+
</div>
|
|
31
|
+
<i
|
|
32
|
+
aria-hidden="true"
|
|
33
|
+
class="filter icon"
|
|
34
|
+
/>
|
|
35
|
+
<div
|
|
36
|
+
class="menu transition"
|
|
37
|
+
>
|
|
38
|
+
<div
|
|
39
|
+
class="item"
|
|
40
|
+
role="option"
|
|
41
|
+
>
|
|
42
|
+
<em>
|
|
43
|
+
(reset filters)
|
|
44
|
+
</em>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div
|
|
50
|
+
class="selectedFilters"
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
exports[`<SearchWidget/> matches the latest snapshot with SearchDateFilter active 1`] = `
|
|
56
|
+
<div>
|
|
57
|
+
<div
|
|
58
|
+
class="ui action left icon input"
|
|
59
|
+
>
|
|
13
60
|
<i
|
|
14
61
|
aria-hidden="true"
|
|
15
62
|
class="search link icon"
|
|
16
63
|
/>
|
|
64
|
+
<input
|
|
65
|
+
placeholder="Search..."
|
|
66
|
+
type="text"
|
|
67
|
+
value=""
|
|
68
|
+
/>
|
|
69
|
+
<button
|
|
70
|
+
class="ui icon button"
|
|
71
|
+
>
|
|
72
|
+
<i
|
|
73
|
+
aria-hidden="true"
|
|
74
|
+
class="calendar alternate outline icon"
|
|
75
|
+
/>
|
|
76
|
+
</button>
|
|
17
77
|
<div
|
|
18
|
-
aria-busy="false"
|
|
19
78
|
aria-expanded="false"
|
|
20
79
|
class="ui button floating labeled scrolling dropdown icon"
|
|
21
80
|
role="listbox"
|
|
@@ -11,7 +11,7 @@ describe("selectors: makeDateFiltersSelector", () => {
|
|
|
11
11
|
name,
|
|
12
12
|
type: "before",
|
|
13
13
|
value: "5",
|
|
14
|
-
unit: "d"
|
|
14
|
+
unit: "d",
|
|
15
15
|
};
|
|
16
16
|
const state = { foo };
|
|
17
17
|
expect(selector(state)).toEqual({ updated_at: { lt: "now-5d" } });
|
|
@@ -23,7 +23,7 @@ describe("selectors: makeDateFiltersSelector", () => {
|
|
|
23
23
|
name,
|
|
24
24
|
type: "since",
|
|
25
25
|
value: "1",
|
|
26
|
-
unit: "M"
|
|
26
|
+
unit: "M",
|
|
27
27
|
};
|
|
28
28
|
const state = { foo };
|
|
29
29
|
expect(selector(state)).toEqual({ updated_at: { gte: "now-1M" } });
|
|
@@ -34,11 +34,11 @@ describe("selectors: makeDateFiltersSelector", () => {
|
|
|
34
34
|
active: true,
|
|
35
35
|
name,
|
|
36
36
|
type: "range",
|
|
37
|
-
range: "2020-01-01 - 2020-02-02"
|
|
37
|
+
range: "2020-01-01 - 2020-02-02",
|
|
38
38
|
};
|
|
39
39
|
const state = { foo };
|
|
40
40
|
expect(selector(state)).toEqual({
|
|
41
|
-
updated_at: { gte: "2020-01-01", lt: "2020-02-02||+1d" }
|
|
41
|
+
updated_at: { gte: "2020-01-01", lt: "2020-02-02||+1d" },
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
44
|
|
|
@@ -1,53 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
cond,
|
|
3
|
-
constant,
|
|
4
|
-
has,
|
|
5
|
-
negate,
|
|
6
|
-
propEq,
|
|
7
|
-
propOr,
|
|
8
|
-
split,
|
|
9
|
-
stubTrue,
|
|
10
|
-
} from "lodash/fp";
|
|
1
|
+
import { propOr } from "lodash/fp";
|
|
11
2
|
import { createSelector } from "reselect";
|
|
12
|
-
|
|
13
|
-
const relativeDate = ({ unit, value }) => `now-${value}${unit}`;
|
|
14
|
-
const toDateRange = ([from, to]) => ({ gte: from, lt: `${to}||+1d` });
|
|
15
|
-
const sinceDate = ({ name, ...props }) => ({
|
|
16
|
-
[name]: { gte: relativeDate(props) },
|
|
17
|
-
});
|
|
18
|
-
const untilDate = ({ name, ...props }) => ({
|
|
19
|
-
[name]: { lt: relativeDate(props) },
|
|
20
|
-
});
|
|
21
|
-
const dateRange = ({ name, range }) => ({
|
|
22
|
-
[name]: toDateRange(split(" - ")(range)),
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const grantDateRange = ({ name, range }) => {
|
|
26
|
-
const [start_date, end_date] = split(",")(name);
|
|
27
|
-
const [from, to] = split(" - ")(range);
|
|
28
|
-
|
|
29
|
-
return from && to
|
|
30
|
-
? {
|
|
31
|
-
[start_date]: { gte: from },
|
|
32
|
-
[end_date]: { lt: to },
|
|
33
|
-
}
|
|
34
|
-
: constant(empty);
|
|
35
|
-
};
|
|
36
|
-
|
|
3
|
+
import { dateFilterFormatter } from "@truedat/core/services/dateFilterFormatter";
|
|
37
4
|
const empty = {};
|
|
38
|
-
|
|
39
5
|
export const makeDateFiltersSelector = (dateFilterProp) => {
|
|
40
|
-
const dateRangeFilter =
|
|
41
|
-
dateFilterProp === "grantDateFilter" ? grantDateRange : dateRange;
|
|
42
6
|
return createSelector(
|
|
43
7
|
propOr(empty, dateFilterProp),
|
|
44
|
-
|
|
45
|
-
[negate(propEq("active", true)), constant(empty)],
|
|
46
|
-
[negate(has("name")), constant(empty)],
|
|
47
|
-
[propEq("type", "since"), sinceDate],
|
|
48
|
-
[propEq("type", "before"), untilDate],
|
|
49
|
-
[propEq("type", "range"), dateRangeFilter],
|
|
50
|
-
[stubTrue, constant(empty)],
|
|
51
|
-
])
|
|
8
|
+
dateFilterFormatter(dateFilterProp)
|
|
52
9
|
);
|
|
53
10
|
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { dateFilterFormatter } from "../dateFilterFormatter";
|
|
2
|
+
|
|
3
|
+
describe("dateFilterFormatter", () => {
|
|
4
|
+
const dff = dateFilterFormatter();
|
|
5
|
+
const grant_dff = dateFilterFormatter("grantDateFilter");
|
|
6
|
+
|
|
7
|
+
describe("returns a selector", () => {
|
|
8
|
+
it("returns a 'since relative date' filter", () => {
|
|
9
|
+
const foo = {
|
|
10
|
+
name: "updated_at",
|
|
11
|
+
type: "since",
|
|
12
|
+
unit: "d",
|
|
13
|
+
value: "3",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const filter = dff(foo);
|
|
17
|
+
|
|
18
|
+
expect(filter == { updated_at: { gte: "now-3d" } });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns a 'until relative date' filter", () => {
|
|
22
|
+
const foo = {
|
|
23
|
+
name: "updated_at",
|
|
24
|
+
type: "until",
|
|
25
|
+
unit: "y",
|
|
26
|
+
value: "2",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const filter = dff(foo);
|
|
30
|
+
|
|
31
|
+
expect(filter == { updated_at: { lt: "now-2y" } });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns a 'date range filter ' filter", () => {
|
|
35
|
+
const foo = {
|
|
36
|
+
name: "updated_at",
|
|
37
|
+
type: "range",
|
|
38
|
+
range: "2024-04-10 - 2024-05-05",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const filter = dff(foo);
|
|
42
|
+
|
|
43
|
+
expect(
|
|
44
|
+
filter == { updated_at: { gte: "2024-04-17", lt: "2024-05-05||+1d" } }
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("returns a 'date range filter for grants' filter", () => {
|
|
49
|
+
const foo = {
|
|
50
|
+
name: "start_date,end_date",
|
|
51
|
+
type: "range",
|
|
52
|
+
range: "2024-04-10 - 2024-05-05",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const filter = grant_dff(foo);
|
|
56
|
+
|
|
57
|
+
expect(
|
|
58
|
+
filter ==
|
|
59
|
+
{
|
|
60
|
+
updated_at: {
|
|
61
|
+
start_date: { gte: "2024-04-17" },
|
|
62
|
+
end_date: { lt: "2024-05-05" },
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("returns an empty object if active is not true", () => {
|
|
69
|
+
const foo = {
|
|
70
|
+
name: "updated_at",
|
|
71
|
+
type: "since",
|
|
72
|
+
unit: "d",
|
|
73
|
+
value: "3",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const filter = dff(foo);
|
|
77
|
+
|
|
78
|
+
expect(filter == {});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("returns an empty object if name is not present", () => {
|
|
82
|
+
const foo = {
|
|
83
|
+
type: "since",
|
|
84
|
+
unit: "d",
|
|
85
|
+
value: "3",
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const filter = dff(foo);
|
|
89
|
+
|
|
90
|
+
expect(filter == {});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cond,
|
|
3
|
+
constant,
|
|
4
|
+
has,
|
|
5
|
+
negate,
|
|
6
|
+
propEq,
|
|
7
|
+
split,
|
|
8
|
+
stubTrue,
|
|
9
|
+
} from "lodash/fp";
|
|
10
|
+
|
|
11
|
+
const relativeDate = ({ unit, value }) => `now-${value}${unit}`;
|
|
12
|
+
const toDateRange = ([from, to]) => ({ gte: from, lt: `${to}||+1d` });
|
|
13
|
+
const sinceDate = ({ name, ...props }) => ({
|
|
14
|
+
[name]: { gte: relativeDate(props) },
|
|
15
|
+
});
|
|
16
|
+
const untilDate = ({ name, ...props }) => ({
|
|
17
|
+
[name]: { lt: relativeDate(props) },
|
|
18
|
+
});
|
|
19
|
+
const dateRange = ({ name, range }) => ({
|
|
20
|
+
[name]: toDateRange(split(" - ")(range)),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const grantDateRange = ({ name, range }) => {
|
|
24
|
+
const [start_date, end_date] = split(",")(name);
|
|
25
|
+
const [from, to] = split(" - ")(range);
|
|
26
|
+
|
|
27
|
+
return from && to
|
|
28
|
+
? {
|
|
29
|
+
[start_date]: { gte: from },
|
|
30
|
+
[end_date]: { lt: to },
|
|
31
|
+
}
|
|
32
|
+
: constant(empty);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const empty = {};
|
|
36
|
+
|
|
37
|
+
export const dateFilterFormatter = (dateRangeFilter) =>
|
|
38
|
+
cond([
|
|
39
|
+
[negate(propEq("active", true)), constant(empty)],
|
|
40
|
+
[negate(has("name")), constant(empty)],
|
|
41
|
+
[propEq("type", "since"), sinceDate],
|
|
42
|
+
[propEq("type", "before"), untilDate],
|
|
43
|
+
[
|
|
44
|
+
propEq("type", "range"),
|
|
45
|
+
dateRangeFilter === "grantDateFilter" ? grantDateRange : dateRange,
|
|
46
|
+
],
|
|
47
|
+
[stubTrue, constant(empty)],
|
|
48
|
+
]);
|