Skip to content

feat: Add relative date filter in data browser for date constraints relative to when the query is run #2736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 24, 2025
Merged
40 changes: 30 additions & 10 deletions src/components/BrowserFilter/BrowserFilter.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Label from 'components/Label/Label.react';
import Position from 'lib/Position';
import React from 'react';
import styles from 'components/BrowserFilter/BrowserFilter.scss';
import Checkbox from 'components/Checkbox/Checkbox.react';
import { List, Map } from 'immutable';

const POPOVER_CONTENT_ID = 'browserFilterPopover';
Expand All @@ -32,6 +33,7 @@ export default class BrowserFilter extends React.Component {
confirmName: false,
name: '',
blacklistedFilters: Filters.BLACKLISTED_FILTERS.concat(props.blacklistedFilters),
relativeDates: false,
};
this.toggle = this.toggle.bind(this);
this.wrapRef = React.createRef();
Expand Down Expand Up @@ -63,6 +65,7 @@ export default class BrowserFilter extends React.Component {
name: '',
confirmName: false,
editMode: this.props.filters.size === 0,
relativeDates: false, // Reset relative dates state when opening/closing
}));
this.props.setCurrent(null);
}
Expand Down Expand Up @@ -114,7 +117,7 @@ export default class BrowserFilter extends React.Component {
}
return filter;
});
this.props.onSaveFilter(formatted, this.state.name);
this.props.onSaveFilter(formatted, this.state.name, this.state.relativeDates);
this.toggle();
}

Expand All @@ -137,6 +140,8 @@ export default class BrowserFilter extends React.Component {
this.state.blacklistedFilters,
this.state.filters
);

const hasDateState = this.state.filters.some(filter => filter.get('compareTo')?.__type === 'Date');
popover = (
<Popover
fixed={true}
Expand Down Expand Up @@ -180,17 +185,32 @@ export default class BrowserFilter extends React.Component {
)}
/>
{this.state.confirmName && (
<Field
label={<Label text="Filter view name" />}
input={
<TextInput
placeholder="Give it a good name..."
value={this.state.name}
onChange={name => this.setState({ name })}
/>
<>
<Field
label={<Label text="Filter view name" />}
input={
<TextInput
placeholder="Give it a good name..."
value={this.state.name}
onChange={name => this.setState({ name })}
/>
}
/>
{hasDateState &&
<Field
label={<Label text="Relative dates" />}
input={
<Checkbox
checked={this.state.relativeDates}
onChange={checked => this.setState({ relativeDates: checked })}
className={styles.checkbox}
/>
}
/>
}
/>
</>
)}

{this.state.confirmName && (
<div className={styles.footer}>
<Button
Expand Down
7 changes: 4 additions & 3 deletions src/components/CategoryList/CategoryList.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ export default class CategoryList extends React.Component {
const query = new URLSearchParams(this.props.params);
if (query.has('filters')) {
const queryFilter = query.get('filters');
const filterId = query.get('filterId');
for (let i = 0; i < c.filters?.length; i++) {
const filter = c.filters[i];
if (queryFilter === filter.filter) {
if (queryFilter === filter.filter || filterId && filterId === filter.id) {
height += (i + 1) * 20;
break;
}
Expand Down Expand Up @@ -138,10 +139,10 @@ export default class CategoryList extends React.Component {
</div>
{this.state.openClasses.includes(id) &&
c.filters.map((filterData, index) => {
const { name, filter } = filterData;
const { name, filter,id } = filterData;
const url = `${this.props.linkPrefix}${c.name}?filters=${encodeURIComponent(
filter
)}`;
)}&filterId=${id}`;
return (
<div key={index} className={styles.childLink}>
<Link
Expand Down
24 changes: 22 additions & 2 deletions src/dashboard/Data/Browser/Browser.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -1071,15 +1071,34 @@ class Browser extends DashboardView {
});
}

saveFilters(filters, name) {
const _filters = JSON.stringify(filters.toJSON());
saveFilters(filters, name, relativeDate) {
const jsonFilters = filters.toJSON();
if (relativeDate && jsonFilters?.length) {
for (let i = 0; i < jsonFilters.length; i++) {
const filter = jsonFilters[i];
const compareTo = filter.get('compareTo');
if (compareTo?.__type === 'Date') {
compareTo.__type = 'RelativeDate';
const now = new Date();
const date = new Date(compareTo.iso);
const diff = date.getTime() - now.getTime();
compareTo.value = Math.floor(diff / 1000);
delete compareTo.iso;
filter.set('compareTo', compareTo);
jsonFilters[i] = filter;
}
}
}

const _filters = JSON.stringify(jsonFilters);
const preferences = ClassPreferences.getPreferences(
this.context.applicationId,
this.props.params.className
);
if (!preferences.filters.includes(_filters)) {
preferences.filters.push({
name,
id: crypto.randomUUID(),
filter: _filters,
});
}
Expand All @@ -1088,6 +1107,7 @@ class Browser extends DashboardView {
this.context.applicationId,
this.props.params.className
);

super.forceUpdate();
}

Expand Down
26 changes: 26 additions & 0 deletions src/lib/generatePath.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
const MOUNT_PATH = window.PARSE_DASHBOARD_PATH;

export default function generatePath(currentApp, path, prependMountPath = false) {

const urlObj = new URL(path, window.location.origin);
const params = new URLSearchParams(urlObj.search);

const filters = JSON.parse(params.get('filters'))

if (filters) {
for (let i = 0; i < filters.length; i++) {
const filter = filters[i];
if (filter.compareTo?.__type === 'RelativeDate') {
const date = new Date();
date.setTime(date.getTime() + filter.compareTo.value * 1000);
filter.compareTo = {
__type: 'Date',
iso: date.toISOString(),
}
filters[i] = filter;
}
}

params.set('filters', JSON.stringify(filters));
urlObj.search = params.toString();

path = urlObj.toString().split(window.location.origin)[1].substring(1);
}

if (prependMountPath && MOUNT_PATH) {
return `${MOUNT_PATH}apps/${currentApp.slug}/${path}`;
}
Expand Down
Loading