TimePicker.tsx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import React, { PureComponent } from 'react';
  2. import moment from 'moment';
  3. import * as dateMath from 'app/core/utils/datemath';
  4. import * as rangeUtil from 'app/core/utils/rangeutil';
  5. import { RawTimeRange, TimeRange } from 'app/types/series';
  6. const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
  7. export const DEFAULT_RANGE = {
  8. from: 'now-6h',
  9. to: 'now',
  10. };
  11. /**
  12. * Return a human-editable string of either relative (inludes "now") or absolute local time (in the shape of DATE_FORMAT).
  13. * @param value Epoch or relative time
  14. */
  15. export function parseTime(value: string, isUtc = false): string {
  16. if (moment.isMoment(value)) {
  17. return value;
  18. }
  19. if (value.indexOf('now') !== -1) {
  20. return value;
  21. }
  22. let time: any = value;
  23. // Possible epoch
  24. if (!isNaN(time)) {
  25. time = parseInt(time, 10);
  26. }
  27. time = isUtc ? moment.utc(time) : moment(time);
  28. return time.format(DATE_FORMAT);
  29. }
  30. interface TimePickerProps {
  31. isOpen?: boolean;
  32. isUtc?: boolean;
  33. range?: RawTimeRange;
  34. onChangeTime?: (range: RawTimeRange, scanning?: boolean) => void;
  35. }
  36. interface TimePickerState {
  37. isOpen: boolean;
  38. isUtc: boolean;
  39. rangeString: string;
  40. refreshInterval?: string;
  41. initialRange?: RawTimeRange;
  42. // Input-controlled text, keep these in a shape that is human-editable
  43. fromRaw: string;
  44. toRaw: string;
  45. }
  46. export default class TimePicker extends PureComponent<TimePickerProps, TimePickerState> {
  47. dropdownEl: any;
  48. constructor(props) {
  49. super(props);
  50. this.state = {
  51. isOpen: props.isOpen,
  52. isUtc: props.isUtc,
  53. rangeString: '',
  54. fromRaw: '',
  55. toRaw: '',
  56. initialRange: DEFAULT_RANGE,
  57. refreshInterval: '',
  58. };
  59. } //Temp solution... How do detect if ds supports table format?
  60. static getDerivedStateFromProps(props, state) {
  61. if (state.initialRange && state.initialRange === props.range) {
  62. return state;
  63. }
  64. const from = props.range ? props.range.from : DEFAULT_RANGE.from;
  65. const to = props.range ? props.range.to : DEFAULT_RANGE.to;
  66. // Ensure internal format
  67. const fromRaw = parseTime(from, props.isUtc);
  68. const toRaw = parseTime(to, props.isUtc);
  69. const range = {
  70. from: fromRaw,
  71. to: toRaw,
  72. };
  73. return {
  74. ...state,
  75. fromRaw,
  76. toRaw,
  77. initialRange: props.range,
  78. rangeString: rangeUtil.describeTimeRange(range),
  79. };
  80. }
  81. move(direction: number, scanning?: boolean): RawTimeRange {
  82. const { onChangeTime } = this.props;
  83. const { fromRaw, toRaw } = this.state;
  84. const from = dateMath.parse(fromRaw, false);
  85. const to = dateMath.parse(toRaw, true);
  86. const step = scanning ? 1 : 2;
  87. const timespan = (to.valueOf() - from.valueOf()) / step;
  88. let nextTo, nextFrom;
  89. if (direction === -1) {
  90. nextTo = to.valueOf() - timespan;
  91. nextFrom = from.valueOf() - timespan;
  92. } else if (direction === 1) {
  93. nextTo = to.valueOf() + timespan;
  94. nextFrom = from.valueOf() + timespan;
  95. if (nextTo > Date.now() && to < Date.now()) {
  96. nextTo = Date.now();
  97. nextFrom = from.valueOf();
  98. }
  99. } else {
  100. nextTo = to.valueOf();
  101. nextFrom = from.valueOf();
  102. }
  103. const nextRange = {
  104. from: moment(nextFrom),
  105. to: moment(nextTo),
  106. };
  107. const nextTimeRange: TimeRange = {
  108. raw: nextRange,
  109. from: nextRange.from,
  110. to: nextRange.to,
  111. };
  112. this.setState(
  113. {
  114. rangeString: rangeUtil.describeTimeRange(nextRange),
  115. fromRaw: nextRange.from.format(DATE_FORMAT),
  116. toRaw: nextRange.to.format(DATE_FORMAT),
  117. },
  118. () => {
  119. onChangeTime(nextTimeRange, scanning);
  120. }
  121. );
  122. return nextRange;
  123. }
  124. handleChangeFrom = e => {
  125. this.setState({
  126. fromRaw: e.target.value,
  127. });
  128. };
  129. handleChangeTo = e => {
  130. this.setState({
  131. toRaw: e.target.value,
  132. });
  133. };
  134. handleClickApply = () => {
  135. const { onChangeTime } = this.props;
  136. let range;
  137. this.setState(
  138. state => {
  139. const { toRaw, fromRaw } = this.state;
  140. range = {
  141. from: dateMath.parse(fromRaw, false),
  142. to: dateMath.parse(toRaw, true),
  143. };
  144. const rangeString = rangeUtil.describeTimeRange(range);
  145. return {
  146. isOpen: false,
  147. rangeString,
  148. };
  149. },
  150. () => {
  151. if (onChangeTime) {
  152. onChangeTime(range);
  153. }
  154. }
  155. );
  156. };
  157. handleClickLeft = () => this.move(-1);
  158. handleClickPicker = () => {
  159. this.setState(state => ({
  160. isOpen: !state.isOpen,
  161. }));
  162. };
  163. handleClickRight = () => this.move(1);
  164. handleClickRefresh = () => {};
  165. handleClickRelativeOption = range => {
  166. const { onChangeTime } = this.props;
  167. const rangeString = rangeUtil.describeTimeRange(range);
  168. this.setState(
  169. {
  170. toRaw: range.to,
  171. fromRaw: range.from,
  172. isOpen: false,
  173. rangeString,
  174. },
  175. () => {
  176. if (onChangeTime) {
  177. onChangeTime(range);
  178. }
  179. }
  180. );
  181. };
  182. getTimeOptions() {
  183. return rangeUtil.getRelativeTimesList({}, this.state.rangeString);
  184. }
  185. dropdownRef = el => {
  186. this.dropdownEl = el;
  187. };
  188. renderDropdown() {
  189. const { fromRaw, isOpen, toRaw } = this.state;
  190. if (!isOpen) {
  191. return null;
  192. }
  193. const timeOptions = this.getTimeOptions();
  194. return (
  195. <div ref={this.dropdownRef} className="gf-timepicker-dropdown">
  196. <div className="gf-timepicker-absolute-section">
  197. <h3 className="section-heading">Custom range</h3>
  198. <label className="small">From:</label>
  199. <div className="gf-form-inline">
  200. <div className="gf-form max-width-28">
  201. <input
  202. type="text"
  203. className="gf-form-input input-large timepicker-from"
  204. value={fromRaw}
  205. onChange={this.handleChangeFrom}
  206. />
  207. </div>
  208. </div>
  209. <label className="small">To:</label>
  210. <div className="gf-form-inline">
  211. <div className="gf-form max-width-28">
  212. <input
  213. type="text"
  214. className="gf-form-input input-large timepicker-to"
  215. value={toRaw}
  216. onChange={this.handleChangeTo}
  217. />
  218. </div>
  219. </div>
  220. {/* <label className="small">Refreshing every:</label>
  221. <div className="gf-form-inline">
  222. <div className="gf-form max-width-28">
  223. <select className="gf-form-input input-medium" ng-options="f.value as f.text for f in ctrl.refresh.options"></select>
  224. </div>
  225. </div> */}
  226. <div className="gf-form">
  227. <button className="btn gf-form-btn btn-secondary" onClick={this.handleClickApply}>
  228. Apply
  229. </button>
  230. </div>
  231. </div>
  232. <div className="gf-timepicker-relative-section">
  233. <h3 className="section-heading">Quick ranges</h3>
  234. {Object.keys(timeOptions).map(section => {
  235. const group = timeOptions[section];
  236. return (
  237. <ul key={section}>
  238. {group.map(option => (
  239. <li className={option.active ? 'active' : ''} key={option.display}>
  240. <a onClick={() => this.handleClickRelativeOption(option)}>{option.display}</a>
  241. </li>
  242. ))}
  243. </ul>
  244. );
  245. })}
  246. </div>
  247. </div>
  248. );
  249. }
  250. render() {
  251. const { isUtc, rangeString, refreshInterval } = this.state;
  252. return (
  253. <div className="timepicker">
  254. <div className="navbar-buttons">
  255. <button className="btn navbar-button navbar-button--tight timepicker-left" onClick={this.handleClickLeft}>
  256. <i className="fa fa-chevron-left" />
  257. </button>
  258. <button className="btn navbar-button gf-timepicker-nav-btn" onClick={this.handleClickPicker}>
  259. <i className="fa fa-clock-o" />
  260. <span className="timepicker-rangestring">{rangeString}</span>
  261. {isUtc ? <span className="gf-timepicker-utc">UTC</span> : null}
  262. {refreshInterval ? <span className="text-warning">&nbsp; Refresh every {refreshInterval}</span> : null}
  263. </button>
  264. <button className="btn navbar-button navbar-button--tight timepicker-right" onClick={this.handleClickRight}>
  265. <i className="fa fa-chevron-right" />
  266. </button>
  267. </div>
  268. {this.renderDropdown()}
  269. </div>
  270. );
  271. }
  272. }