diff --git a/public/app/containers/Explore/TimePicker.jest.tsx b/public/app/containers/Explore/TimePicker.jest.tsx new file mode 100644 index 00000000000..afe6b092901 --- /dev/null +++ b/public/app/containers/Explore/TimePicker.jest.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import sinon from 'sinon'; + +import * as rangeUtil from 'app/core/utils/rangeutil'; +import TimePicker, { DEFAULT_RANGE, parseTime } from './TimePicker'; + +describe('', () => { + it('renders closed with default values', () => { + const rangeString = rangeUtil.describeTimeRange(DEFAULT_RANGE); + const wrapper = shallow(); + expect(wrapper.find('.timepicker-rangestring').text()).toBe(rangeString); + expect(wrapper.find('.gf-timepicker-dropdown').exists()).toBe(false); + }); + + it('renders with relative range', () => { + const range = { + from: 'now-7h', + to: 'now', + }; + const rangeString = rangeUtil.describeTimeRange(range); + const wrapper = shallow(); + expect(wrapper.find('.timepicker-rangestring').text()).toBe(rangeString); + expect(wrapper.state('fromRaw')).toBe(range.from); + expect(wrapper.state('toRaw')).toBe(range.to); + expect(wrapper.find('.timepicker-from').props().value).toBe(range.from); + expect(wrapper.find('.timepicker-to').props().value).toBe(range.to); + }); + + it('renders with epoch (millies) range converted to ISO-ish', () => { + const range = { + from: '1', + to: '1000', + }; + const rangeString = rangeUtil.describeTimeRange({ + from: parseTime(range.from), + to: parseTime(range.to), + }); + const wrapper = shallow(); + expect(wrapper.state('fromRaw')).toBe('1970-01-01 00:00:00'); + expect(wrapper.state('toRaw')).toBe('1970-01-01 00:00:01'); + expect(wrapper.find('.timepicker-rangestring').text()).toBe(rangeString); + expect(wrapper.find('.timepicker-from').props().value).toBe('1970-01-01 00:00:00'); + expect(wrapper.find('.timepicker-to').props().value).toBe('1970-01-01 00:00:01'); + }); + + it('moves ranges forward and backward by half the range on arrow click', () => { + const range = { + from: '2000', + to: '4000', + }; + const rangeString = rangeUtil.describeTimeRange({ + from: parseTime(range.from), + to: parseTime(range.to), + }); + + const onChangeTime = sinon.spy(); + const wrapper = shallow(); + expect(wrapper.state('fromRaw')).toBe('1970-01-01 00:00:02'); + expect(wrapper.state('toRaw')).toBe('1970-01-01 00:00:04'); + expect(wrapper.find('.timepicker-rangestring').text()).toBe(rangeString); + expect(wrapper.find('.timepicker-from').props().value).toBe('1970-01-01 00:00:02'); + expect(wrapper.find('.timepicker-to').props().value).toBe('1970-01-01 00:00:04'); + + wrapper.find('.timepicker-left').simulate('click'); + expect(onChangeTime.calledOnce).toBe(true); + expect(wrapper.state('fromRaw')).toBe('1970-01-01 00:00:01'); + expect(wrapper.state('toRaw')).toBe('1970-01-01 00:00:03'); + + wrapper.find('.timepicker-right').simulate('click'); + expect(wrapper.state('fromRaw')).toBe('1970-01-01 00:00:02'); + expect(wrapper.state('toRaw')).toBe('1970-01-01 00:00:04'); + }); +}); diff --git a/public/app/containers/Explore/TimePicker.tsx b/public/app/containers/Explore/TimePicker.tsx index b67cd532019..3ae4ea4a83c 100644 --- a/public/app/containers/Explore/TimePicker.tsx +++ b/public/app/containers/Explore/TimePicker.tsx @@ -4,22 +4,43 @@ import moment from 'moment'; import * as dateMath from 'app/core/utils/datemath'; import * as rangeUtil from 'app/core/utils/rangeutil'; +const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'; + export const DEFAULT_RANGE = { from: 'now-6h', to: 'now', }; +export function parseTime(value, isUtc = false, asString = false) { + if (value.indexOf('now') !== -1) { + return value; + } + if (!isNaN(value)) { + const epoch = parseInt(value); + const m = isUtc ? moment.utc(epoch) : moment(epoch); + return asString ? m.format(DATE_FORMAT) : m; + } + return undefined; +} + export default class TimePicker extends PureComponent { dropdownEl: any; constructor(props) { super(props); + + const fromRaw = props.range ? props.range.from : DEFAULT_RANGE.from; + const toRaw = props.range ? props.range.to : DEFAULT_RANGE.to; + const range = { + from: parseTime(fromRaw), + to: parseTime(toRaw), + }; this.state = { - fromRaw: props.range ? props.range.from : DEFAULT_RANGE.from, - isOpen: false, - isUtc: false, - rangeString: rangeUtil.describeTimeRange(props.range || DEFAULT_RANGE), + fromRaw: parseTime(fromRaw, props.isUtc, true), + isOpen: props.isOpen, + isUtc: props.isUtc, + rangeString: rangeUtil.describeTimeRange(range), refreshInterval: '', - toRaw: props.range ? props.range.to : DEFAULT_RANGE.to, + toRaw: parseTime(toRaw, props.isUtc, true), }; } @@ -49,14 +70,15 @@ export default class TimePicker extends PureComponent { } const rangeString = rangeUtil.describeTimeRange(range); - to = moment.utc(to); - from = moment.utc(from); + // No need to convert to UTC again + to = moment(to); + from = moment(from); this.setState( { rangeString, - fromRaw: from, - toRaw: to, + fromRaw: from.format(DATE_FORMAT), + toRaw: to.format(DATE_FORMAT), }, () => { onChangeTime({ to, from }); @@ -76,6 +98,27 @@ export default class TimePicker extends PureComponent { }); }; + handleClickApply = () => { + const { onChangeTime } = this.props; + const { toRaw, fromRaw } = this.state; + const range = { + from: dateMath.parse(fromRaw, false), + to: dateMath.parse(toRaw, true), + }; + const rangeString = rangeUtil.describeTimeRange(range); + this.setState( + { + isOpen: false, + rangeString, + }, + () => { + if (onChangeTime) { + onChangeTime(range); + } + } + ); + }; + handleClickLeft = () => this.move(-1); handleClickPicker = () => { this.setState(state => ({ @@ -118,7 +161,7 @@ export default class TimePicker extends PureComponent { const timeOptions = this.getTimeOptions(); return (
-
+

Custom range

@@ -126,7 +169,7 @@ export default class TimePicker extends PureComponent {
@@ -136,7 +179,12 @@ export default class TimePicker extends PureComponent {
- +
@@ -146,7 +194,12 @@ export default class TimePicker extends PureComponent {
*/} -
+
+ +
+

Quick ranges

@@ -172,16 +225,16 @@ export default class TimePicker extends PureComponent { return (
- -
diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index 541477877bc..47fb3857225 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -36,6 +36,10 @@ .timepicker { display: flex; + + &-rangestring { + margin-left: 0.5em; + } } .run-icon {