time.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import { AnyAction, createAction, PayloadAction } from '@reduxjs/toolkit';
  2. import {
  3. AbsoluteTimeRange,
  4. dateTimeForTimeZone,
  5. LoadingState,
  6. RawTimeRange,
  7. sortLogsResult,
  8. TimeRange,
  9. } from '@grafana/data';
  10. import { getTemplateSrv } from '@grafana/runtime';
  11. import { RefreshPicker } from '@grafana/ui';
  12. import { getTimeRange, refreshIntervalToSortOrder, stopQueryState } from 'app/core/utils/explore';
  13. import { getFiscalYearStartMonth, getTimeZone } from 'app/features/profile/state/selectors';
  14. import { ExploreItemState, ThunkResult } from 'app/types';
  15. import { ExploreId } from 'app/types/explore';
  16. import { getTimeSrv } from '../../dashboard/services/TimeSrv';
  17. import { TimeModel } from '../../dashboard/state/TimeModel';
  18. import { syncTimesAction, stateSave } from './main';
  19. import { runQueries } from './query';
  20. //
  21. // Actions and Payloads
  22. //
  23. export interface ChangeRangePayload {
  24. exploreId: ExploreId;
  25. range: TimeRange;
  26. absoluteRange: AbsoluteTimeRange;
  27. }
  28. export const changeRangeAction = createAction<ChangeRangePayload>('explore/changeRange');
  29. /**
  30. * Change the time range of Explore. Usually called from the Timepicker or a graph interaction.
  31. */
  32. export interface ChangeRefreshIntervalPayload {
  33. exploreId: ExploreId;
  34. refreshInterval: string;
  35. }
  36. export const changeRefreshIntervalAction = createAction<ChangeRefreshIntervalPayload>('explore/changeRefreshInterval');
  37. export const updateTimeRange = (options: {
  38. exploreId: ExploreId;
  39. rawRange?: RawTimeRange;
  40. absoluteRange?: AbsoluteTimeRange;
  41. }): ThunkResult<void> => {
  42. return (dispatch, getState) => {
  43. const { syncedTimes } = getState().explore;
  44. if (syncedTimes) {
  45. dispatch(updateTime({ ...options, exploreId: ExploreId.left }));
  46. // When running query by updating time range, we want to preserve cache.
  47. // Cached results are currently used in Logs pagination.
  48. dispatch(runQueries(ExploreId.left, { preserveCache: true }));
  49. dispatch(updateTime({ ...options, exploreId: ExploreId.right }));
  50. dispatch(runQueries(ExploreId.right, { preserveCache: true }));
  51. } else {
  52. dispatch(updateTime({ ...options }));
  53. dispatch(runQueries(options.exploreId, { preserveCache: true }));
  54. }
  55. };
  56. };
  57. /**
  58. * Change the refresh interval of Explore. Called from the Refresh picker.
  59. */
  60. export function changeRefreshInterval(
  61. exploreId: ExploreId,
  62. refreshInterval: string
  63. ): PayloadAction<ChangeRefreshIntervalPayload> {
  64. return changeRefreshIntervalAction({ exploreId, refreshInterval });
  65. }
  66. export const updateTime = (config: {
  67. exploreId: ExploreId;
  68. rawRange?: RawTimeRange;
  69. absoluteRange?: AbsoluteTimeRange;
  70. }): ThunkResult<void> => {
  71. return (dispatch, getState) => {
  72. const { exploreId, absoluteRange: absRange, rawRange: actionRange } = config;
  73. const itemState = getState().explore[exploreId]!;
  74. const timeZone = getTimeZone(getState().user);
  75. const fiscalYearStartMonth = getFiscalYearStartMonth(getState().user);
  76. const { range: rangeInState } = itemState;
  77. let rawRange: RawTimeRange = rangeInState.raw;
  78. if (absRange) {
  79. rawRange = {
  80. from: dateTimeForTimeZone(timeZone, absRange.from),
  81. to: dateTimeForTimeZone(timeZone, absRange.to),
  82. };
  83. }
  84. if (actionRange) {
  85. rawRange = actionRange;
  86. }
  87. const range = getTimeRange(timeZone, rawRange, fiscalYearStartMonth);
  88. const absoluteRange: AbsoluteTimeRange = { from: range.from.valueOf(), to: range.to.valueOf() };
  89. const timeModel: TimeModel = {
  90. time: range.raw,
  91. refresh: false,
  92. timepicker: {},
  93. getTimezone: () => timeZone,
  94. timeRangeUpdated: (rawTimeRange: RawTimeRange) => {
  95. dispatch(updateTimeRange({ exploreId: exploreId, rawRange: rawTimeRange }));
  96. },
  97. };
  98. // We need to re-initialize TimeSrv because it might have been triggered by the other Explore pane (when split)
  99. getTimeSrv().init(timeModel);
  100. // After re-initializing TimeSrv we need to update the time range in Template service for interpolation
  101. // of __from and __to variables
  102. getTemplateSrv().updateTimeRange(getTimeSrv().timeRange());
  103. dispatch(changeRangeAction({ exploreId, range, absoluteRange }));
  104. };
  105. };
  106. /**
  107. * Syncs time interval, if they are not synced on both panels in a split mode.
  108. * Unsyncs time interval, if they are synced on both panels in a split mode.
  109. */
  110. export function syncTimes(exploreId: ExploreId): ThunkResult<void> {
  111. return (dispatch, getState) => {
  112. if (exploreId === ExploreId.left) {
  113. const leftState = getState().explore.left;
  114. dispatch(updateTimeRange({ exploreId: ExploreId.right, rawRange: leftState.range.raw }));
  115. } else {
  116. const rightState = getState().explore.right!;
  117. dispatch(updateTimeRange({ exploreId: ExploreId.left, rawRange: rightState.range.raw }));
  118. }
  119. const isTimeSynced = getState().explore.syncedTimes;
  120. dispatch(syncTimesAction({ syncedTimes: !isTimeSynced }));
  121. dispatch(stateSave());
  122. };
  123. }
  124. /**
  125. * Forces the timepicker's time into absolute time.
  126. * The conversion is applied to all Explore panes.
  127. * Useful to produce a bookmarkable URL that points to the same data.
  128. */
  129. export function makeAbsoluteTime(): ThunkResult<void> {
  130. return (dispatch, getState) => {
  131. const timeZone = getTimeZone(getState().user);
  132. const fiscalYearStartMonth = getFiscalYearStartMonth(getState().user);
  133. const leftState = getState().explore.left;
  134. const leftRange = getTimeRange(timeZone, leftState.range.raw, fiscalYearStartMonth);
  135. const leftAbsoluteRange: AbsoluteTimeRange = { from: leftRange.from.valueOf(), to: leftRange.to.valueOf() };
  136. dispatch(updateTime({ exploreId: ExploreId.left, absoluteRange: leftAbsoluteRange }));
  137. const rightState = getState().explore.right!;
  138. if (rightState) {
  139. const rightRange = getTimeRange(timeZone, rightState.range.raw, fiscalYearStartMonth);
  140. const rightAbsoluteRange: AbsoluteTimeRange = { from: rightRange.from.valueOf(), to: rightRange.to.valueOf() };
  141. dispatch(updateTime({ exploreId: ExploreId.right, absoluteRange: rightAbsoluteRange }));
  142. }
  143. dispatch(stateSave());
  144. };
  145. }
  146. /**
  147. * Reducer for an Explore area, to be used by the global Explore reducer.
  148. */
  149. // Redux Toolkit uses ImmerJs as part of their solution to ensure that state objects are not mutated.
  150. // ImmerJs has an autoFreeze option that freezes objects from change which means this reducer can't be migrated to createSlice
  151. // because the state would become frozen and during run time we would get errors because flot (Graph lib) would try to mutate
  152. // the frozen state.
  153. // https://github.com/reduxjs/redux-toolkit/issues/242
  154. export const timeReducer = (state: ExploreItemState, action: AnyAction): ExploreItemState => {
  155. if (changeRefreshIntervalAction.match(action)) {
  156. const { refreshInterval } = action.payload;
  157. const live = RefreshPicker.isLive(refreshInterval);
  158. const sortOrder = refreshIntervalToSortOrder(refreshInterval);
  159. const logsResult = sortLogsResult(state.logsResult, sortOrder);
  160. if (RefreshPicker.isLive(state.refreshInterval) && !live) {
  161. stopQueryState(state.querySubscription);
  162. }
  163. return {
  164. ...state,
  165. refreshInterval,
  166. queryResponse: {
  167. ...state.queryResponse,
  168. state: live ? LoadingState.Streaming : LoadingState.Done,
  169. },
  170. isLive: live,
  171. isPaused: live ? false : state.isPaused,
  172. loading: live,
  173. logsResult,
  174. };
  175. }
  176. if (changeRangeAction.match(action)) {
  177. const { range, absoluteRange } = action.payload;
  178. return {
  179. ...state,
  180. range,
  181. absoluteRange,
  182. };
  183. }
  184. return state;
  185. };