@truedat/core 6.6.0 → 6.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/core",
3
- "version": "6.6.0",
3
+ "version": "6.6.1",
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.0",
39
+ "@truedat/test": "6.6.1",
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": "84d52d5067ff09b8d9c9b8a9bc8018c53a8e700c"
121
+ "gitHead": "2fa84eee80136fa3bd394c04153e6f16d2e5a1e0"
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/user/me";
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, onChange]);
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
- _.concat(_.isUndefined(scope) ? "" : `?scope=${scope}`),
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;
@@ -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",
@@ -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":
@@ -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(20, "pageSize")(props);
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
- }, [query, searchMust, sort, defaultFilters, page, size]);
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: { name: filterName, filters, is_global: isGlobal },
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 { query, setQuery, loadingFilters: loading } = useSearchContext();
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={<SearchFilters />}
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 SearchContext from "@truedat/core/search/SearchContext";
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
- <SearchContext.Provider value={searchProps}>
39
+ <SearchContextWrapper props={searchProps}>
31
40
  <SearchWidget />
32
- </SearchContext.Provider>,
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
- <SearchContext.Provider value={searchProps}>
59
+ <SearchContextWrapper props={searchProps}>
41
60
  <SearchWidget />
42
- </SearchContext.Provider>,
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
- cond([
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
+ ]);