mirror of
https://github.com/apache/superset.git
synced 2026-04-18 23:55:00 +00:00
[explore] Improved time filters controls (#3371)
* Improved time filters controls * lint * Fix coverage * Allow empty dates
This commit is contained in:
committed by
GitHub
parent
aff7a82664
commit
a47a512808
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
|
||||
import BoundsControl from './controls/BoundsControl';
|
||||
import CheckboxControl from './controls/CheckboxControl';
|
||||
import DatasourceControl from './controls/DatasourceControl';
|
||||
import DateFilterControl from './controls/DateFilterControl';
|
||||
import FilterControl from './controls/FilterControl';
|
||||
import HiddenControl from './controls/HiddenControl';
|
||||
import SelectControl from './controls/SelectControl';
|
||||
@@ -16,6 +17,7 @@ const controlMap = {
|
||||
BoundsControl,
|
||||
CheckboxControl,
|
||||
DatasourceControl,
|
||||
DateFilterControl,
|
||||
FilterControl,
|
||||
HiddenControl,
|
||||
SelectControl,
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Button, ButtonGroup, FormControl, InputGroup,
|
||||
Label, OverlayTrigger, Popover, Glyphicon,
|
||||
} from 'react-bootstrap';
|
||||
import Select from 'react-select';
|
||||
import Datetime from 'react-datetime';
|
||||
import 'react-datetime/css/react-datetime.css';
|
||||
import moment from 'moment';
|
||||
|
||||
import ControlHeader from '../ControlHeader';
|
||||
import PopoverSection from '../../../components/PopoverSection';
|
||||
|
||||
const RELATIVE_TIME_OPTIONS = ['ago', 'from now'];
|
||||
const TIME_GRAIN_OPTIONS = ['seconds', 'minutes', 'days', 'weeks', 'months', 'years'];
|
||||
|
||||
const propTypes = {
|
||||
animation: PropTypes.bool,
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string.isRequired,
|
||||
height: PropTypes.number,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
animation: true,
|
||||
onChange: () => {},
|
||||
value: null,
|
||||
};
|
||||
|
||||
export default class DateFilterControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const words = props.value.split(' ');
|
||||
this.state = {
|
||||
num: '7',
|
||||
grain: 'days',
|
||||
rel: 'ago',
|
||||
dttm: '',
|
||||
type: 'free',
|
||||
free: '',
|
||||
};
|
||||
if (words.length >= 3 && RELATIVE_TIME_OPTIONS.indexOf(words[2]) >= 0) {
|
||||
this.state.num = words[0];
|
||||
this.state.grain = words[1];
|
||||
this.state.rel = words[2];
|
||||
this.state.type = 'rel';
|
||||
} else if (moment(props.value).isValid()) {
|
||||
this.state.dttm = props.value;
|
||||
this.state.type = 'fix';
|
||||
} else {
|
||||
this.state.free = props.value;
|
||||
this.state.type = 'free';
|
||||
}
|
||||
}
|
||||
onControlChange(target, opt) {
|
||||
this.setState({ [target]: opt.value }, this.onChange);
|
||||
}
|
||||
onNumberChange(event) {
|
||||
this.setState({ num: event.target.value }, this.onChange);
|
||||
}
|
||||
onChange() {
|
||||
let val;
|
||||
if (this.state.type === 'rel') {
|
||||
val = `${this.state.num} ${this.state.grain} ${this.state.rel}`;
|
||||
} else if (this.state.type === 'fix') {
|
||||
val = this.state.dttm;
|
||||
} else if (this.state.type === 'free') {
|
||||
val = this.state.free;
|
||||
}
|
||||
this.props.onChange(val);
|
||||
}
|
||||
onFreeChange(event) {
|
||||
this.setState({ free: event.target.value }, this.onChange);
|
||||
}
|
||||
setType(type) {
|
||||
this.setState({ type }, this.onChange);
|
||||
}
|
||||
setValue(val) {
|
||||
this.setState({ type: 'free', free: val }, this.onChange);
|
||||
this.close();
|
||||
}
|
||||
setDatetime(dttm) {
|
||||
this.setState({ dttm: dttm.format().substring(0, 19) }, this.onChange);
|
||||
}
|
||||
close() {
|
||||
this.refs.trigger.hide();
|
||||
}
|
||||
renderPopover() {
|
||||
return (
|
||||
<Popover id="filter-popover">
|
||||
<div style={{ width: '240px' }}>
|
||||
<PopoverSection
|
||||
title="Fixed"
|
||||
isSelected={this.state.type === 'fix'}
|
||||
onSelect={this.setType.bind(this, 'fix')}
|
||||
>
|
||||
<InputGroup bsSize="small">
|
||||
<InputGroup.Addon>
|
||||
<Glyphicon glyph="calendar" />
|
||||
</InputGroup.Addon>
|
||||
<Datetime
|
||||
inputProps={{ className: 'form-control input-sm' }}
|
||||
dateFormat="YYYY-MM-DD"
|
||||
defaultValue={this.state.dttm}
|
||||
onFocus={this.setType.bind(this, 'fix')}
|
||||
onChange={this.setDatetime.bind(this)}
|
||||
timeFormat="h:mm:ss"
|
||||
/>
|
||||
</InputGroup>
|
||||
</PopoverSection>
|
||||
<PopoverSection
|
||||
title="Relative"
|
||||
isSelected={this.state.type === 'rel'}
|
||||
onSelect={this.setType.bind(this, 'rel')}
|
||||
>
|
||||
<div className="clearfix">
|
||||
<div style={{ width: '50px' }} className="input-inline">
|
||||
<FormControl
|
||||
onFocus={this.setType.bind(this, 'rel')}
|
||||
value={this.state.num}
|
||||
onChange={this.onNumberChange.bind(this)}
|
||||
bsSize="small"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ width: '95px' }} className="input-inline">
|
||||
<Select
|
||||
onFocus={this.setType.bind(this, 'rel')}
|
||||
value={this.state.grain}
|
||||
clearable={false}
|
||||
options={TIME_GRAIN_OPTIONS.map(s => ({ label: s, value: s }))}
|
||||
onChange={this.onControlChange.bind(this, 'grain')}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ width: '95px' }} className="input-inline">
|
||||
<Select
|
||||
value={this.state.rel}
|
||||
onFocus={this.setType.bind(this, 'rel')}
|
||||
clearable={false}
|
||||
options={RELATIVE_TIME_OPTIONS.map(s => ({ label: s, value: s }))}
|
||||
onChange={this.onControlChange.bind(this, 'rel')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverSection>
|
||||
<PopoverSection
|
||||
title="Free form"
|
||||
isSelected={this.state.type === 'free'}
|
||||
onSelect={this.setType.bind(this, 'free')}
|
||||
info={
|
||||
'Superset supports smart date parsing. Strings like `last sunday` or ' +
|
||||
'`last october` can be used.'
|
||||
}
|
||||
>
|
||||
<FormControl
|
||||
onFocus={this.setType.bind(this, 'free')}
|
||||
value={this.state.free}
|
||||
onChange={this.onFreeChange.bind(this)}
|
||||
bsSize="small"
|
||||
/>
|
||||
</PopoverSection>
|
||||
<div className="clearfix">
|
||||
<Button
|
||||
bsSize="small"
|
||||
className="float-left ok"
|
||||
bsStyle="primary"
|
||||
onClick={this.close.bind(this)}
|
||||
>
|
||||
Ok
|
||||
</Button>
|
||||
<ButtonGroup
|
||||
className="float-right"
|
||||
>
|
||||
<Button
|
||||
bsSize="small"
|
||||
onClick={this.setValue.bind(this, 'now')}
|
||||
>
|
||||
now
|
||||
</Button>
|
||||
<Button
|
||||
bsSize="small"
|
||||
onClick={this.setValue.bind(this, '')}
|
||||
>
|
||||
clear
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<ControlHeader {...this.props} />
|
||||
<OverlayTrigger
|
||||
animation={this.props.animation}
|
||||
container={document.body}
|
||||
trigger="click"
|
||||
rootClose
|
||||
ref="trigger"
|
||||
placement="right"
|
||||
overlay={this.renderPopover()}
|
||||
>
|
||||
<Label style={{ cursor: 'pointer' }}>
|
||||
{this.props.value.replace('T00:00:00', '') || '∞'}
|
||||
</Label>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DateFilterControl.propTypes = propTypes;
|
||||
DateFilterControl.defaultProps = defaultProps;
|
||||
Reference in New Issue
Block a user