Skip to content

Commit d9dfd69

Browse files
authored
feat: Add relative date filter in data browser for date constraints relative to when the query is run (#2736)
1 parent 30fe665 commit d9dfd69

File tree

4 files changed

+84
-16
lines changed

4 files changed

+84
-16
lines changed

src/components/BrowserFilter/BrowserFilter.react.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import Label from 'components/Label/Label.react';
1717
import Position from 'lib/Position';
1818
import React from 'react';
1919
import styles from 'components/BrowserFilter/BrowserFilter.scss';
20+
import Checkbox from 'components/Checkbox/Checkbox.react';
2021
import { List, Map } from 'immutable';
2122

2223
const POPOVER_CONTENT_ID = 'browserFilterPopover';
@@ -32,6 +33,7 @@ export default class BrowserFilter extends React.Component {
3233
confirmName: false,
3334
name: '',
3435
blacklistedFilters: Filters.BLACKLISTED_FILTERS.concat(props.blacklistedFilters),
36+
relativeDates: false,
3537
};
3638
this.toggle = this.toggle.bind(this);
3739
this.wrapRef = React.createRef();
@@ -63,6 +65,7 @@ export default class BrowserFilter extends React.Component {
6365
name: '',
6466
confirmName: false,
6567
editMode: this.props.filters.size === 0,
68+
relativeDates: false, // Reset relative dates state when opening/closing
6669
}));
6770
this.props.setCurrent(null);
6871
}
@@ -114,7 +117,7 @@ export default class BrowserFilter extends React.Component {
114117
}
115118
return filter;
116119
});
117-
this.props.onSaveFilter(formatted, this.state.name);
120+
this.props.onSaveFilter(formatted, this.state.name, this.state.relativeDates);
118121
this.toggle();
119122
}
120123

@@ -137,6 +140,8 @@ export default class BrowserFilter extends React.Component {
137140
this.state.blacklistedFilters,
138141
this.state.filters
139142
);
143+
144+
const hasDateState = this.state.filters.some(filter => filter.get('compareTo')?.__type === 'Date');
140145
popover = (
141146
<Popover
142147
fixed={true}
@@ -180,17 +185,32 @@ export default class BrowserFilter extends React.Component {
180185
)}
181186
/>
182187
{this.state.confirmName && (
183-
<Field
184-
label={<Label text="Filter view name" />}
185-
input={
186-
<TextInput
187-
placeholder="Give it a good name..."
188-
value={this.state.name}
189-
onChange={name => this.setState({ name })}
190-
/>
188+
<>
189+
<Field
190+
label={<Label text="Filter view name" />}
191+
input={
192+
<TextInput
193+
placeholder="Give it a good name..."
194+
value={this.state.name}
195+
onChange={name => this.setState({ name })}
196+
/>
197+
}
198+
/>
199+
{hasDateState &&
200+
<Field
201+
label={<Label text="Relative dates" />}
202+
input={
203+
<Checkbox
204+
checked={this.state.relativeDates}
205+
onChange={checked => this.setState({ relativeDates: checked })}
206+
className={styles.checkbox}
207+
/>
208+
}
209+
/>
191210
}
192-
/>
211+
</>
193212
)}
213+
194214
{this.state.confirmName && (
195215
<div className={styles.footer}>
196216
<Button

src/components/CategoryList/CategoryList.react.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ export default class CategoryList extends React.Component {
5353
const query = new URLSearchParams(this.props.params);
5454
if (query.has('filters')) {
5555
const queryFilter = query.get('filters');
56+
const filterId = query.get('filterId');
5657
for (let i = 0; i < c.filters?.length; i++) {
5758
const filter = c.filters[i];
58-
if (queryFilter === filter.filter) {
59+
if (queryFilter === filter.filter || filterId && filterId === filter.id) {
5960
height += (i + 1) * 20;
6061
break;
6162
}
@@ -108,9 +109,10 @@ export default class CategoryList extends React.Component {
108109
const query = new URLSearchParams(this.props.params);
109110
if (query.has('filters')) {
110111
const queryFilter = query.get('filters');
112+
const queryFilterId = query.get('filterId');
111113
for (let i = 0; i < c.filters?.length; i++) {
112114
const filter = c.filters[i];
113-
if (queryFilter === filter.filter) {
115+
if (queryFilter === filter.filter || queryFilterId && queryFilterId === filter.id) {
114116
selectedFilter = i;
115117
className = '';
116118
break;
@@ -138,10 +140,10 @@ export default class CategoryList extends React.Component {
138140
</div>
139141
{this.state.openClasses.includes(id) &&
140142
c.filters.map((filterData, index) => {
141-
const { name, filter } = filterData;
143+
const { name, filter, id } = filterData;
142144
const url = `${this.props.linkPrefix}${c.name}?filters=${encodeURIComponent(
143145
filter
144-
)}`;
146+
)}&filterId=${id}`;
145147
return (
146148
<div key={index} className={styles.childLink}>
147149
<Link

src/dashboard/Data/Browser/Browser.react.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,15 +1071,34 @@ class Browser extends DashboardView {
10711071
});
10721072
}
10731073

1074-
saveFilters(filters, name) {
1075-
const _filters = JSON.stringify(filters.toJSON());
1074+
saveFilters(filters, name, relativeDate) {
1075+
const jsonFilters = filters.toJSON();
1076+
if (relativeDate && jsonFilters?.length) {
1077+
for (let i = 0; i < jsonFilters.length; i++) {
1078+
const filter = jsonFilters[i];
1079+
const compareTo = filter.get('compareTo');
1080+
if (compareTo?.__type === 'Date') {
1081+
compareTo.__type = 'RelativeDate';
1082+
const now = new Date();
1083+
const date = new Date(compareTo.iso);
1084+
const diff = date.getTime() - now.getTime();
1085+
compareTo.value = Math.floor(diff / 1000);
1086+
delete compareTo.iso;
1087+
filter.set('compareTo', compareTo);
1088+
jsonFilters[i] = filter;
1089+
}
1090+
}
1091+
}
1092+
1093+
const _filters = JSON.stringify(jsonFilters);
10761094
const preferences = ClassPreferences.getPreferences(
10771095
this.context.applicationId,
10781096
this.props.params.className
10791097
);
10801098
if (!preferences.filters.includes(_filters)) {
10811099
preferences.filters.push({
10821100
name,
1101+
id: crypto.randomUUID(),
10831102
filter: _filters,
10841103
});
10851104
}
@@ -1088,6 +1107,7 @@ class Browser extends DashboardView {
10881107
this.context.applicationId,
10891108
this.props.params.className
10901109
);
1110+
10911111
super.forceUpdate();
10921112
}
10931113

src/lib/generatePath.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
const MOUNT_PATH = window.PARSE_DASHBOARD_PATH;
22

33
export default function generatePath(currentApp, path, prependMountPath = false) {
4+
5+
const urlObj = new URL(path, window.location.origin);
6+
const params = new URLSearchParams(urlObj.search);
7+
8+
const filters = JSON.parse(params.get('filters'))
9+
10+
if (filters) {
11+
for (let i = 0; i < filters.length; i++) {
12+
const filter = filters[i];
13+
if (filter.compareTo?.__type === 'RelativeDate') {
14+
const date = new Date();
15+
date.setTime(date.getTime() + filter.compareTo.value * 1000);
16+
filter.compareTo = {
17+
__type: 'Date',
18+
iso: date.toISOString(),
19+
}
20+
filters[i] = filter;
21+
}
22+
}
23+
24+
params.set('filters', JSON.stringify(filters));
25+
urlObj.search = params.toString();
26+
27+
path = urlObj.toString().split(window.location.origin)[1].substring(1);
28+
}
29+
430
if (prependMountPath && MOUNT_PATH) {
531
return `${MOUNT_PATH}apps/${currentApp.slug}/${path}`;
632
}

0 commit comments

Comments
 (0)