123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- import { AnyAction, createAction, PayloadAction } from '@reduxjs/toolkit';
- import {
- AbsoluteTimeRange,
- dateTimeForTimeZone,
- LoadingState,
- RawTimeRange,
- sortLogsResult,
- TimeRange,
- } from '@grafana/data';
- import { getTemplateSrv } from '@grafana/runtime';
- import { RefreshPicker } from '@grafana/ui';
- import { getTimeRange, refreshIntervalToSortOrder, stopQueryState } from 'app/core/utils/explore';
- import { getFiscalYearStartMonth, getTimeZone } from 'app/features/profile/state/selectors';
- import { ExploreItemState, ThunkResult } from 'app/types';
- import { ExploreId } from 'app/types/explore';
- import { getTimeSrv } from '../../dashboard/services/TimeSrv';
- import { TimeModel } from '../../dashboard/state/TimeModel';
- import { syncTimesAction, stateSave } from './main';
- import { runQueries } from './query';
- //
- // Actions and Payloads
- //
- export interface ChangeRangePayload {
- exploreId: ExploreId;
- range: TimeRange;
- absoluteRange: AbsoluteTimeRange;
- }
- export const changeRangeAction = createAction<ChangeRangePayload>('explore/changeRange');
- /**
- * Change the time range of Explore. Usually called from the Timepicker or a graph interaction.
- */
- export interface ChangeRefreshIntervalPayload {
- exploreId: ExploreId;
- refreshInterval: string;
- }
- export const changeRefreshIntervalAction = createAction<ChangeRefreshIntervalPayload>('explore/changeRefreshInterval');
- export const updateTimeRange = (options: {
- exploreId: ExploreId;
- rawRange?: RawTimeRange;
- absoluteRange?: AbsoluteTimeRange;
- }): ThunkResult<void> => {
- return (dispatch, getState) => {
- const { syncedTimes } = getState().explore;
- if (syncedTimes) {
- dispatch(updateTime({ ...options, exploreId: ExploreId.left }));
- // When running query by updating time range, we want to preserve cache.
- // Cached results are currently used in Logs pagination.
- dispatch(runQueries(ExploreId.left, { preserveCache: true }));
- dispatch(updateTime({ ...options, exploreId: ExploreId.right }));
- dispatch(runQueries(ExploreId.right, { preserveCache: true }));
- } else {
- dispatch(updateTime({ ...options }));
- dispatch(runQueries(options.exploreId, { preserveCache: true }));
- }
- };
- };
- /**
- * Change the refresh interval of Explore. Called from the Refresh picker.
- */
- export function changeRefreshInterval(
- exploreId: ExploreId,
- refreshInterval: string
- ): PayloadAction<ChangeRefreshIntervalPayload> {
- return changeRefreshIntervalAction({ exploreId, refreshInterval });
- }
- export const updateTime = (config: {
- exploreId: ExploreId;
- rawRange?: RawTimeRange;
- absoluteRange?: AbsoluteTimeRange;
- }): ThunkResult<void> => {
- return (dispatch, getState) => {
- const { exploreId, absoluteRange: absRange, rawRange: actionRange } = config;
- const itemState = getState().explore[exploreId]!;
- const timeZone = getTimeZone(getState().user);
- const fiscalYearStartMonth = getFiscalYearStartMonth(getState().user);
- const { range: rangeInState } = itemState;
- let rawRange: RawTimeRange = rangeInState.raw;
- if (absRange) {
- rawRange = {
- from: dateTimeForTimeZone(timeZone, absRange.from),
- to: dateTimeForTimeZone(timeZone, absRange.to),
- };
- }
- if (actionRange) {
- rawRange = actionRange;
- }
- const range = getTimeRange(timeZone, rawRange, fiscalYearStartMonth);
- const absoluteRange: AbsoluteTimeRange = { from: range.from.valueOf(), to: range.to.valueOf() };
- const timeModel: TimeModel = {
- time: range.raw,
- refresh: false,
- timepicker: {},
- getTimezone: () => timeZone,
- timeRangeUpdated: (rawTimeRange: RawTimeRange) => {
- dispatch(updateTimeRange({ exploreId: exploreId, rawRange: rawTimeRange }));
- },
- };
- // We need to re-initialize TimeSrv because it might have been triggered by the other Explore pane (when split)
- getTimeSrv().init(timeModel);
- // After re-initializing TimeSrv we need to update the time range in Template service for interpolation
- // of __from and __to variables
- getTemplateSrv().updateTimeRange(getTimeSrv().timeRange());
- dispatch(changeRangeAction({ exploreId, range, absoluteRange }));
- };
- };
- /**
- * Syncs time interval, if they are not synced on both panels in a split mode.
- * Unsyncs time interval, if they are synced on both panels in a split mode.
- */
- export function syncTimes(exploreId: ExploreId): ThunkResult<void> {
- return (dispatch, getState) => {
- if (exploreId === ExploreId.left) {
- const leftState = getState().explore.left;
- dispatch(updateTimeRange({ exploreId: ExploreId.right, rawRange: leftState.range.raw }));
- } else {
- const rightState = getState().explore.right!;
- dispatch(updateTimeRange({ exploreId: ExploreId.left, rawRange: rightState.range.raw }));
- }
- const isTimeSynced = getState().explore.syncedTimes;
- dispatch(syncTimesAction({ syncedTimes: !isTimeSynced }));
- dispatch(stateSave());
- };
- }
- /**
- * Forces the timepicker's time into absolute time.
- * The conversion is applied to all Explore panes.
- * Useful to produce a bookmarkable URL that points to the same data.
- */
- export function makeAbsoluteTime(): ThunkResult<void> {
- return (dispatch, getState) => {
- const timeZone = getTimeZone(getState().user);
- const fiscalYearStartMonth = getFiscalYearStartMonth(getState().user);
- const leftState = getState().explore.left;
- const leftRange = getTimeRange(timeZone, leftState.range.raw, fiscalYearStartMonth);
- const leftAbsoluteRange: AbsoluteTimeRange = { from: leftRange.from.valueOf(), to: leftRange.to.valueOf() };
- dispatch(updateTime({ exploreId: ExploreId.left, absoluteRange: leftAbsoluteRange }));
- const rightState = getState().explore.right!;
- if (rightState) {
- const rightRange = getTimeRange(timeZone, rightState.range.raw, fiscalYearStartMonth);
- const rightAbsoluteRange: AbsoluteTimeRange = { from: rightRange.from.valueOf(), to: rightRange.to.valueOf() };
- dispatch(updateTime({ exploreId: ExploreId.right, absoluteRange: rightAbsoluteRange }));
- }
- dispatch(stateSave());
- };
- }
- /**
- * Reducer for an Explore area, to be used by the global Explore reducer.
- */
- // Redux Toolkit uses ImmerJs as part of their solution to ensure that state objects are not mutated.
- // ImmerJs has an autoFreeze option that freezes objects from change which means this reducer can't be migrated to createSlice
- // because the state would become frozen and during run time we would get errors because flot (Graph lib) would try to mutate
- // the frozen state.
- // https://github.com/reduxjs/redux-toolkit/issues/242
- export const timeReducer = (state: ExploreItemState, action: AnyAction): ExploreItemState => {
- if (changeRefreshIntervalAction.match(action)) {
- const { refreshInterval } = action.payload;
- const live = RefreshPicker.isLive(refreshInterval);
- const sortOrder = refreshIntervalToSortOrder(refreshInterval);
- const logsResult = sortLogsResult(state.logsResult, sortOrder);
- if (RefreshPicker.isLive(state.refreshInterval) && !live) {
- stopQueryState(state.querySubscription);
- }
- return {
- ...state,
- refreshInterval,
- queryResponse: {
- ...state.queryResponse,
- state: live ? LoadingState.Streaming : LoadingState.Done,
- },
- isLive: live,
- isPaused: live ? false : state.isPaused,
- loading: live,
- logsResult,
- };
- }
- if (changeRangeAction.match(action)) {
- const { range, absoluteRange } = action.payload;
- return {
- ...state,
- range,
- absoluteRange,
- };
- }
- return state;
- };
|