TimePicker.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. // Libraries
  2. import React, { PureComponent, createRef } from 'react';
  3. // Components
  4. import { ButtonSelect } from '../Select/ButtonSelect';
  5. import { Tooltip } from '../Tooltip/Tooltip';
  6. import { TimePickerPopover } from './TimePickerPopover';
  7. import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper';
  8. // Utils & Services
  9. import { isDateTime } from '@grafana/data';
  10. import { rangeUtil } from '@grafana/data';
  11. import { rawToTimeRange } from './time';
  12. // Types
  13. import { TimeRange, TimeOption, TimeZone, TIME_FORMAT } from '@grafana/data';
  14. import { SelectOptionItem } from '../Select/Select';
  15. export interface Props {
  16. value: TimeRange;
  17. selectOptions: TimeOption[];
  18. timeZone?: TimeZone;
  19. onChange: (timeRange: TimeRange) => void;
  20. onMoveBackward: () => void;
  21. onMoveForward: () => void;
  22. onZoom: () => void;
  23. }
  24. export const defaultSelectOptions: TimeOption[] = [
  25. { from: 'now-5m', to: 'now', display: 'Last 5 minutes', section: 3 },
  26. { from: 'now-15m', to: 'now', display: 'Last 15 minutes', section: 3 },
  27. { from: 'now-30m', to: 'now', display: 'Last 30 minutes', section: 3 },
  28. { from: 'now-1h', to: 'now', display: 'Last 1 hour', section: 3 },
  29. { from: 'now-3h', to: 'now', display: 'Last 3 hours', section: 3 },
  30. { from: 'now-6h', to: 'now', display: 'Last 6 hours', section: 3 },
  31. { from: 'now-12h', to: 'now', display: 'Last 12 hours', section: 3 },
  32. { from: 'now-24h', to: 'now', display: 'Last 24 hours', section: 3 },
  33. { from: 'now-2d', to: 'now', display: 'Last 2 days', section: 3 },
  34. { from: 'now-7d', to: 'now', display: 'Last 7 days', section: 3 },
  35. { from: 'now-30d', to: 'now', display: 'Last 30 days', section: 3 },
  36. { from: 'now-90d', to: 'now', display: 'Last 90 days', section: 3 },
  37. { from: 'now-6M', to: 'now', display: 'Last 6 months', section: 3 },
  38. { from: 'now-1y', to: 'now', display: 'Last 1 year', section: 3 },
  39. { from: 'now-2y', to: 'now', display: 'Last 2 years', section: 3 },
  40. { from: 'now-5y', to: 'now', display: 'Last 5 years', section: 3 },
  41. { from: 'now-1d/d', to: 'now-1d/d', display: 'Yesterday', section: 3 },
  42. { from: 'now-2d/d', to: 'now-2d/d', display: 'Day before yesterday', section: 3 },
  43. { from: 'now-7d/d', to: 'now-7d/d', display: 'This day last week', section: 3 },
  44. { from: 'now-1w/w', to: 'now-1w/w', display: 'Previous week', section: 3 },
  45. { from: 'now-1M/M', to: 'now-1M/M', display: 'Previous month', section: 3 },
  46. { from: 'now-1y/y', to: 'now-1y/y', display: 'Previous year', section: 3 },
  47. { from: 'now/d', to: 'now/d', display: 'Today', section: 3 },
  48. { from: 'now/d', to: 'now', display: 'Today so far', section: 3 },
  49. { from: 'now/w', to: 'now/w', display: 'This week', section: 3 },
  50. { from: 'now/w', to: 'now', display: 'This week so far', section: 3 },
  51. { from: 'now/M', to: 'now/M', display: 'This month', section: 3 },
  52. { from: 'now/M', to: 'now', display: 'This month so far', section: 3 },
  53. { from: 'now/y', to: 'now/y', display: 'This year', section: 3 },
  54. { from: 'now/y', to: 'now', display: 'This year so far', section: 3 },
  55. ];
  56. const defaultZoomOutTooltip = () => {
  57. return (
  58. <>
  59. Time range zoom out <br /> CTRL+Z
  60. </>
  61. );
  62. };
  63. export interface State {
  64. isCustomOpen: boolean;
  65. }
  66. export class TimePicker extends PureComponent<Props, State> {
  67. pickerTriggerRef = createRef<HTMLDivElement>();
  68. state: State = {
  69. isCustomOpen: false,
  70. };
  71. mapTimeOptionsToSelectOptionItems = (selectOptions: TimeOption[]) => {
  72. const options = selectOptions.map(timeOption => {
  73. return {
  74. label: timeOption.display,
  75. value: timeOption,
  76. };
  77. });
  78. options.unshift({
  79. label: 'Custom time range',
  80. value: { from: 'custom', to: 'custom', display: 'Custom', section: 1 },
  81. });
  82. return options;
  83. };
  84. onSelectChanged = (item: SelectOptionItem<TimeOption>) => {
  85. const { onChange, timeZone } = this.props;
  86. if (item.value && item.value.from === 'custom') {
  87. // this is to prevent the ClickOutsideWrapper from directly closing the popover
  88. setTimeout(() => {
  89. this.setState({ isCustomOpen: true });
  90. }, 1);
  91. return;
  92. }
  93. if (item.value) {
  94. onChange(rawToTimeRange({ from: item.value.from, to: item.value.to }, timeZone));
  95. }
  96. };
  97. onCustomChange = (timeRange: TimeRange) => {
  98. const { onChange } = this.props;
  99. onChange(timeRange);
  100. this.setState({ isCustomOpen: false });
  101. };
  102. onCloseCustom = () => {
  103. this.setState({ isCustomOpen: false });
  104. };
  105. render() {
  106. const { selectOptions: selectTimeOptions, value, onMoveBackward, onMoveForward, onZoom, timeZone } = this.props;
  107. const { isCustomOpen } = this.state;
  108. const options = this.mapTimeOptionsToSelectOptionItems(selectTimeOptions);
  109. const currentOption = options.find(item => isTimeOptionEqualToTimeRange(item.value, value));
  110. const rangeString = rangeUtil.describeTimeRange(value.raw);
  111. const label = (
  112. <>
  113. {isCustomOpen && <span>Custom time range</span>}
  114. {!isCustomOpen && <span>{rangeString}</span>}
  115. {timeZone === 'utc' && <span className="time-picker-utc">UTC</span>}
  116. </>
  117. );
  118. const isAbsolute = isDateTime(value.raw.to);
  119. return (
  120. <div className="time-picker" ref={this.pickerTriggerRef}>
  121. <div className="time-picker-buttons">
  122. {isAbsolute && (
  123. <button className="btn navbar-button navbar-button--tight" onClick={onMoveBackward}>
  124. <i className="fa fa-chevron-left" />
  125. </button>
  126. )}
  127. <ButtonSelect
  128. className="time-picker-button-select"
  129. value={currentOption}
  130. label={label}
  131. options={options}
  132. onChange={this.onSelectChanged}
  133. iconClass={'fa fa-clock-o fa-fw'}
  134. tooltipContent={<TimePickerTooltipContent timeRange={value} />}
  135. />
  136. {isAbsolute && (
  137. <button className="btn navbar-button navbar-button--tight" onClick={onMoveForward}>
  138. <i className="fa fa-chevron-right" />
  139. </button>
  140. )}
  141. <Tooltip content={defaultZoomOutTooltip} placement="bottom">
  142. <button className="btn navbar-button navbar-button--zoom" onClick={onZoom}>
  143. <i className="fa fa-search-minus" />
  144. </button>
  145. </Tooltip>
  146. {isCustomOpen && (
  147. <ClickOutsideWrapper onClick={this.onCloseCustom}>
  148. <TimePickerPopover value={value} timeZone={timeZone} onChange={this.onCustomChange} />
  149. </ClickOutsideWrapper>
  150. )}
  151. </div>
  152. </div>
  153. );
  154. }
  155. }
  156. const TimePickerTooltipContent = ({ timeRange }: { timeRange: TimeRange }) => (
  157. <>
  158. {timeRange.from.format(TIME_FORMAT)}
  159. <div className="text-center">to</div>
  160. {timeRange.to.format(TIME_FORMAT)}
  161. </>
  162. );
  163. function isTimeOptionEqualToTimeRange(option: TimeOption, range: TimeRange): boolean {
  164. return range.raw.from === option.from && range.raw.to === option.to;
  165. }