| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- import React, { PureComponent } from 'react';
- import moment from 'moment';
- import * as dateMath from 'app/core/utils/datemath';
- import * as rangeUtil from 'app/core/utils/rangeutil';
- import { RawTimeRange, TimeRange } from '@grafana/ui';
- const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
- export const DEFAULT_RANGE = {
- from: 'now-6h',
- to: 'now',
- };
- /**
- * Return a human-editable string of either relative (inludes "now") or absolute local time (in the shape of DATE_FORMAT).
- * @param value Epoch or relative time
- */
- export function parseTime(value: string | moment.Moment, isUtc = false, ensureString = false): string | moment.Moment {
- if (moment.isMoment(value)) {
- if (ensureString) {
- return value.format(DATE_FORMAT);
- }
- return value;
- }
- if ((value as string).indexOf('now') !== -1) {
- return value;
- }
- let time: any = value;
- // Possible epoch
- if (!isNaN(time)) {
- time = parseInt(time, 10);
- }
- time = isUtc ? moment.utc(time) : moment(time);
- return time.format(DATE_FORMAT);
- }
- interface TimePickerProps {
- isOpen?: boolean;
- isUtc?: boolean;
- range?: RawTimeRange;
- onChangeTime?: (range: RawTimeRange, scanning?: boolean) => void;
- }
- interface TimePickerState {
- isOpen: boolean;
- isUtc: boolean;
- rangeString: string;
- refreshInterval?: string;
- initialRange?: RawTimeRange;
- // Input-controlled text, keep these in a shape that is human-editable
- fromRaw: string;
- toRaw: string;
- }
- /**
- * TimePicker with dropdown menu for relative dates.
- *
- * Initialize with a range that is either based on relative time strings,
- * or on Moment objects.
- * Internally the component needs to keep a string representation in `fromRaw`
- * and `toRaw` for the controlled inputs.
- * When a time is picked, `onChangeTime` is called with the new range that
- * is again based on relative time strings or Moment objects.
- */
- export default class TimePicker extends PureComponent<TimePickerProps, TimePickerState> {
- dropdownEl: any;
- constructor(props) {
- super(props);
- this.state = {
- isOpen: props.isOpen,
- isUtc: props.isUtc,
- rangeString: '',
- fromRaw: '',
- toRaw: '',
- initialRange: DEFAULT_RANGE,
- refreshInterval: '',
- };
- } //Temp solution... How do detect if ds supports table format?
- static getDerivedStateFromProps(props, state) {
- if (state.initialRange && state.initialRange === props.range) {
- return state;
- }
- const from = props.range ? props.range.from : DEFAULT_RANGE.from;
- const to = props.range ? props.range.to : DEFAULT_RANGE.to;
- // Ensure internal string format
- const fromRaw = parseTime(from, props.isUtc, true);
- const toRaw = parseTime(to, props.isUtc, true);
- const range = {
- from: fromRaw,
- to: toRaw,
- };
- return {
- ...state,
- fromRaw,
- toRaw,
- initialRange: props.range,
- rangeString: rangeUtil.describeTimeRange(range),
- };
- }
- move(direction: number, scanning?: boolean): RawTimeRange {
- const { onChangeTime } = this.props;
- const { fromRaw, toRaw } = this.state;
- const from = dateMath.parse(fromRaw, false);
- const to = dateMath.parse(toRaw, true);
- const step = scanning ? 1 : 2;
- const timespan = (to.valueOf() - from.valueOf()) / step;
- let nextTo, nextFrom;
- if (direction === -1) {
- nextTo = to.valueOf() - timespan;
- nextFrom = from.valueOf() - timespan;
- } else if (direction === 1) {
- nextTo = to.valueOf() + timespan;
- nextFrom = from.valueOf() + timespan;
- if (nextTo > Date.now() && to < Date.now()) {
- nextTo = Date.now();
- nextFrom = from.valueOf();
- }
- } else {
- nextTo = to.valueOf();
- nextFrom = from.valueOf();
- }
- const nextRange = {
- from: moment(nextFrom),
- to: moment(nextTo),
- };
- const nextTimeRange: TimeRange = {
- raw: nextRange,
- from: nextRange.from,
- to: nextRange.to,
- };
- this.setState(
- {
- rangeString: rangeUtil.describeTimeRange(nextRange),
- fromRaw: nextRange.from.format(DATE_FORMAT),
- toRaw: nextRange.to.format(DATE_FORMAT),
- },
- () => {
- onChangeTime(nextTimeRange, scanning);
- }
- );
- return nextRange;
- }
- handleChangeFrom = e => {
- this.setState({
- fromRaw: e.target.value,
- });
- };
- handleChangeTo = e => {
- this.setState({
- toRaw: e.target.value,
- });
- };
- handleClickApply = () => {
- const { onChangeTime } = this.props;
- let range;
- this.setState(
- state => {
- const { toRaw, fromRaw } = this.state;
- range = {
- from: dateMath.parse(fromRaw, false),
- to: dateMath.parse(toRaw, true),
- };
- const rangeString = rangeUtil.describeTimeRange(range);
- return {
- isOpen: false,
- rangeString,
- };
- },
- () => {
- if (onChangeTime) {
- onChangeTime(range);
- }
- }
- );
- };
- handleClickLeft = () => this.move(-1);
- handleClickPicker = () => {
- this.setState(state => ({
- isOpen: !state.isOpen,
- }));
- };
- handleClickRight = () => this.move(1);
- handleClickRefresh = () => {};
- handleClickRelativeOption = range => {
- const { onChangeTime } = this.props;
- const rangeString = rangeUtil.describeTimeRange(range);
- this.setState(
- {
- toRaw: range.to,
- fromRaw: range.from,
- isOpen: false,
- rangeString,
- },
- () => {
- if (onChangeTime) {
- onChangeTime(range);
- }
- }
- );
- };
- getTimeOptions() {
- return rangeUtil.getRelativeTimesList({}, this.state.rangeString);
- }
- dropdownRef = el => {
- this.dropdownEl = el;
- };
- renderDropdown() {
- const { fromRaw, isOpen, toRaw } = this.state;
- if (!isOpen) {
- return null;
- }
- const timeOptions = this.getTimeOptions();
- return (
- <div ref={this.dropdownRef} className="gf-timepicker-dropdown">
- <div className="popover-box">
- <div className="popover-box__header">
- <span className="popover-box__title">Quick ranges</span>
- </div>
- <div className="popover-box__body gf-timepicker-relative-section">
- {Object.keys(timeOptions).map(section => {
- const group = timeOptions[section];
- return (
- <ul key={section}>
- {group.map(option => (
- <li className={option.active ? 'active' : ''} key={option.display}>
- <a onClick={() => this.handleClickRelativeOption(option)}>{option.display}</a>
- </li>
- ))}
- </ul>
- );
- })}
- </div>
- </div>
- <div className="popover-box">
- <div className="popover-box__header">
- <span className="popover-box__title">Custom range</span>
- </div>
- <div className="popover-box__body gf-timepicker-absolute-section">
- <label className="small">From:</label>
- <div className="gf-form-inline">
- <div className="gf-form max-width-28">
- <input
- type="text"
- className="gf-form-input input-large timepicker-from"
- value={fromRaw}
- onChange={this.handleChangeFrom}
- />
- </div>
- </div>
- <label className="small">To:</label>
- <div className="gf-form-inline">
- <div className="gf-form max-width-28">
- <input
- type="text"
- className="gf-form-input input-large timepicker-to"
- value={toRaw}
- onChange={this.handleChangeTo}
- />
- </div>
- </div>
- <div className="gf-form">
- <button className="btn gf-form-btn btn-secondary" onClick={this.handleClickApply}>
- Apply
- </button>
- </div>
- </div>
- </div>
- </div>
- );
- }
- render() {
- const { isUtc, rangeString, refreshInterval } = this.state;
- return (
- <div className="timepicker">
- <div className="navbar-buttons">
- <button className="btn navbar-button navbar-button--tight timepicker-left" onClick={this.handleClickLeft}>
- <i className="fa fa-chevron-left" />
- </button>
- <button className="btn navbar-button gf-timepicker-nav-btn" onClick={this.handleClickPicker}>
- <i className="fa fa-clock-o" />
- <span className="timepicker-rangestring">{rangeString}</span>
- {isUtc ? <span className="gf-timepicker-utc">UTC</span> : null}
- {refreshInterval ? <span className="text-warning"> Refresh every {refreshInterval}</span> : null}
- </button>
- <button className="btn navbar-button navbar-button--tight timepicker-right" onClick={this.handleClickRight}>
- <i className="fa fa-chevron-right" />
- </button>
- </div>
- {this.renderDropdown()}
- </div>
- );
- }
- }
|