Commit 2c5400c6 by Hugo Häggmark Committed by kay delaney

Explore: Parses and updates TimeSrv in one place in Explore (#17677)

* Wip: Adds timeEpic

* Refactor: Introduces absoluteRange in Explore state

* Refactor: Removes changeTime action

* Tests: Adds tests for timeEpic

* Refactor: Spells AbsoluteRange correctly
parent 6be1606b
...@@ -112,6 +112,9 @@ export class TimeSrv { ...@@ -112,6 +112,9 @@ export class TimeSrv {
private routeUpdated() { private routeUpdated() {
const params = this.$location.search(); const params = this.$location.search();
if (params.left) {
return; // explore handles this;
}
const urlRange = this.timeRangeForUrl(); const urlRange = this.timeRangeForUrl();
// check if url has time range // check if url has time range
if (params.from && params.to) { if (params.from && params.to) {
......
...@@ -21,13 +21,13 @@ import TimePicker from './TimePicker'; ...@@ -21,13 +21,13 @@ import TimePicker from './TimePicker';
// Actions // Actions
import { import {
changeSize, changeSize,
changeTime,
initializeExplore, initializeExplore,
modifyQueries, modifyQueries,
scanStart, scanStart,
setQueries, setQueries,
refreshExplore, refreshExplore,
reconnectDatasource, reconnectDatasource,
updateTimeRange,
} from './state/actions'; } from './state/actions';
// Types // Types
...@@ -60,7 +60,6 @@ import { scanStopAction } from './state/actionTypes'; ...@@ -60,7 +60,6 @@ import { scanStopAction } from './state/actionTypes';
interface ExploreProps { interface ExploreProps {
StartPage?: ComponentClass<ExploreStartPageProps>; StartPage?: ComponentClass<ExploreStartPageProps>;
changeSize: typeof changeSize; changeSize: typeof changeSize;
changeTime: typeof changeTime;
datasourceError: string; datasourceError: string;
datasourceInstance: DataSourceApi; datasourceInstance: DataSourceApi;
datasourceLoading: boolean | null; datasourceLoading: boolean | null;
...@@ -88,6 +87,7 @@ interface ExploreProps { ...@@ -88,6 +87,7 @@ interface ExploreProps {
queryErrors: DataQueryError[]; queryErrors: DataQueryError[];
mode: ExploreMode; mode: ExploreMode;
isLive: boolean; isLive: boolean;
updateTimeRange: typeof updateTimeRange;
} }
/** /**
...@@ -158,11 +158,12 @@ export class Explore extends React.PureComponent<ExploreProps> { ...@@ -158,11 +158,12 @@ export class Explore extends React.PureComponent<ExploreProps> {
this.el = el; this.el = el;
}; };
onChangeTime = (range: RawTimeRange, changedByScanner?: boolean) => { onChangeTime = (rawRange: RawTimeRange, changedByScanner?: boolean) => {
if (this.props.scanning && !changedByScanner) { const { updateTimeRange, exploreId, scanning } = this.props;
if (scanning && !changedByScanner) {
this.onStopScanning(); this.onStopScanning();
} }
this.props.changeTime(this.props.exploreId, range); updateTimeRange({ exploreId, rawRange });
}; };
// Use this in help pages to set page to a single query // Use this in help pages to set page to a single query
...@@ -348,7 +349,6 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) { ...@@ -348,7 +349,6 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) {
const mapDispatchToProps = { const mapDispatchToProps = {
changeSize, changeSize,
changeTime,
initializeExplore, initializeExplore,
modifyQueries, modifyQueries,
reconnectDatasource, reconnectDatasource,
...@@ -356,6 +356,7 @@ const mapDispatchToProps = { ...@@ -356,6 +356,7 @@ const mapDispatchToProps = {
scanStart, scanStart,
scanStopAction, scanStopAction,
setQueries, setQueries,
updateTimeRange,
}; };
export default hot(module)( export default hot(module)(
......
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { TimeRange, TimeZone, AbsoluteTimeRange, LoadingState } from '@grafana/ui'; import { TimeZone, AbsoluteTimeRange, LoadingState } from '@grafana/ui';
import { ExploreId, ExploreItemState } from 'app/types/explore'; import { ExploreId, ExploreItemState } from 'app/types/explore';
import { StoreState } from 'app/types'; import { StoreState } from 'app/types';
import { toggleGraph, changeTime } from './state/actions'; import { toggleGraph, updateTimeRange } from './state/actions';
import Graph from './Graph'; import Graph from './Graph';
import Panel from './Panel'; import Panel from './Panel';
import { getTimeZone } from '../profile/state/selectors'; import { getTimeZone } from '../profile/state/selectors';
import { toUtc, dateTime } from '@grafana/ui/src/utils/moment_wrapper';
interface GraphContainerProps { interface GraphContainerProps {
exploreId: ExploreId; exploreId: ExploreId;
graphResult?: any[]; graphResult?: any[];
loading: boolean; loading: boolean;
range: TimeRange; absoluteRange: AbsoluteTimeRange;
timeZone: TimeZone; timeZone: TimeZone;
showingGraph: boolean; showingGraph: boolean;
showingTable: boolean; showingTable: boolean;
split: boolean; split: boolean;
toggleGraph: typeof toggleGraph; toggleGraph: typeof toggleGraph;
changeTime: typeof changeTime; updateTimeRange: typeof updateTimeRange;
width: number; width: number;
} }
...@@ -31,20 +30,25 @@ export class GraphContainer extends PureComponent<GraphContainerProps> { ...@@ -31,20 +30,25 @@ export class GraphContainer extends PureComponent<GraphContainerProps> {
this.props.toggleGraph(this.props.exploreId, this.props.showingGraph); this.props.toggleGraph(this.props.exploreId, this.props.showingGraph);
}; };
onChangeTime = (absRange: AbsoluteTimeRange) => { onChangeTime = (absoluteRange: AbsoluteTimeRange) => {
const { exploreId, timeZone, changeTime } = this.props; const { exploreId, updateTimeRange } = this.props;
const range = {
from: timeZone === 'utc' ? toUtc(absRange.from) : dateTime(absRange.from),
to: timeZone === 'utc' ? toUtc(absRange.to) : dateTime(absRange.to),
};
changeTime(exploreId, range); updateTimeRange({ exploreId, absoluteRange });
}; };
render() { render() {
const { exploreId, graphResult, loading, showingGraph, showingTable, range, split, width, timeZone } = this.props; const {
exploreId,
graphResult,
loading,
showingGraph,
showingTable,
absoluteRange,
split,
width,
timeZone,
} = this.props;
const graphHeight = showingGraph && showingTable ? 200 : 400; const graphHeight = showingGraph && showingTable ? 200 : 400;
const timeRange = { from: range.from.valueOf(), to: range.to.valueOf() };
return ( return (
<Panel label="Graph" collapsible isOpen={showingGraph} loading={loading} onToggle={this.onClickGraphButton}> <Panel label="Graph" collapsible isOpen={showingGraph} loading={loading} onToggle={this.onClickGraphButton}>
...@@ -54,7 +58,7 @@ export class GraphContainer extends PureComponent<GraphContainerProps> { ...@@ -54,7 +58,7 @@ export class GraphContainer extends PureComponent<GraphContainerProps> {
height={graphHeight} height={graphHeight}
id={`explore-graph-${exploreId}`} id={`explore-graph-${exploreId}`}
onChangeTime={this.onChangeTime} onChangeTime={this.onChangeTime}
range={timeRange} range={absoluteRange}
timeZone={timeZone} timeZone={timeZone}
split={split} split={split}
width={width} width={width}
...@@ -69,14 +73,22 @@ function mapStateToProps(state: StoreState, { exploreId }) { ...@@ -69,14 +73,22 @@ function mapStateToProps(state: StoreState, { exploreId }) {
const explore = state.explore; const explore = state.explore;
const { split } = explore; const { split } = explore;
const item: ExploreItemState = explore[exploreId]; const item: ExploreItemState = explore[exploreId];
const { graphResult, loadingState, range, showingGraph, showingTable } = item; const { graphResult, loadingState, showingGraph, showingTable, absoluteRange } = item;
const loading = loadingState === LoadingState.Loading || loadingState === LoadingState.Streaming; const loading = loadingState === LoadingState.Loading || loadingState === LoadingState.Streaming;
return { graphResult, loading, range, showingGraph, showingTable, split, timeZone: getTimeZone(state.user) }; return {
graphResult,
loading,
showingGraph,
showingTable,
split,
timeZone: getTimeZone(state.user),
absoluteRange,
};
} }
const mapDispatchToProps = { const mapDispatchToProps = {
toggleGraph, toggleGraph,
changeTime, updateTimeRange,
}; };
export default hot(module)( export default hot(module)(
......
...@@ -7,7 +7,6 @@ import { ...@@ -7,7 +7,6 @@ import {
Switch, Switch,
LogLevel, LogLevel,
TimeZone, TimeZone,
TimeRange,
AbsoluteTimeRange, AbsoluteTimeRange,
LogsMetaKind, LogsMetaKind,
LogsModel, LogsModel,
...@@ -58,7 +57,7 @@ interface Props { ...@@ -58,7 +57,7 @@ interface Props {
exploreId: string; exploreId: string;
highlighterExpressions: string[]; highlighterExpressions: string[];
loading: boolean; loading: boolean;
range: TimeRange; absoluteRange: AbsoluteTimeRange;
timeZone: TimeZone; timeZone: TimeZone;
scanning?: boolean; scanning?: boolean;
scanRange?: RawTimeRange; scanRange?: RawTimeRange;
...@@ -167,7 +166,7 @@ export default class Logs extends PureComponent<Props, State> { ...@@ -167,7 +166,7 @@ export default class Logs extends PureComponent<Props, State> {
highlighterExpressions, highlighterExpressions,
loading = false, loading = false,
onClickLabel, onClickLabel,
range, absoluteRange,
timeZone, timeZone,
scanning, scanning,
scanRange, scanRange,
...@@ -206,10 +205,6 @@ export default class Logs extends PureComponent<Props, State> { ...@@ -206,10 +205,6 @@ export default class Logs extends PureComponent<Props, State> {
const timeSeries = data.series const timeSeries = data.series
? data.series.map(series => new TimeSeries(series)) ? data.series.map(series => new TimeSeries(series))
: [new TimeSeries({ datapoints: [] })]; : [new TimeSeries({ datapoints: [] })];
const absRange = {
from: range.from.valueOf(),
to: range.to.valueOf(),
};
return ( return (
<div className="logs-panel"> <div className="logs-panel">
...@@ -218,7 +213,7 @@ export default class Logs extends PureComponent<Props, State> { ...@@ -218,7 +213,7 @@ export default class Logs extends PureComponent<Props, State> {
data={timeSeries} data={timeSeries}
height={100} height={100}
width={width} width={width}
range={absRange} range={absoluteRange}
timeZone={timeZone} timeZone={timeZone}
id={`explore-logs-graph-${exploreId}`} id={`explore-logs-graph-${exploreId}`}
onChangeTime={this.props.onChangeTime} onChangeTime={this.props.onChangeTime}
......
...@@ -3,12 +3,9 @@ import { hot } from 'react-hot-loader'; ...@@ -3,12 +3,9 @@ import { hot } from 'react-hot-loader';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
RawTimeRange, RawTimeRange,
TimeRange,
LogLevel, LogLevel,
TimeZone, TimeZone,
AbsoluteTimeRange, AbsoluteTimeRange,
toUtc,
dateTime,
DataSourceApi, DataSourceApi,
LogsModel, LogsModel,
LogRowModel, LogRowModel,
...@@ -19,7 +16,7 @@ import { ...@@ -19,7 +16,7 @@ import {
import { ExploreId, ExploreItemState } from 'app/types/explore'; import { ExploreId, ExploreItemState } from 'app/types/explore';
import { StoreState } from 'app/types'; import { StoreState } from 'app/types';
import { changeDedupStrategy, changeTime } from './state/actions'; import { changeDedupStrategy, updateTimeRange } from './state/actions';
import Logs from './Logs'; import Logs from './Logs';
import Panel from './Panel'; import Panel from './Panel';
import { toggleLogLevelAction, changeRefreshIntervalAction } from 'app/features/explore/state/actionTypes'; import { toggleLogLevelAction, changeRefreshIntervalAction } from 'app/features/explore/state/actionTypes';
...@@ -39,7 +36,6 @@ interface LogsContainerProps { ...@@ -39,7 +36,6 @@ interface LogsContainerProps {
onClickLabel: (key: string, value: string) => void; onClickLabel: (key: string, value: string) => void;
onStartScanning: () => void; onStartScanning: () => void;
onStopScanning: () => void; onStopScanning: () => void;
range: TimeRange;
timeZone: TimeZone; timeZone: TimeZone;
scanning?: boolean; scanning?: boolean;
scanRange?: RawTimeRange; scanRange?: RawTimeRange;
...@@ -48,20 +44,17 @@ interface LogsContainerProps { ...@@ -48,20 +44,17 @@ interface LogsContainerProps {
dedupStrategy: LogsDedupStrategy; dedupStrategy: LogsDedupStrategy;
hiddenLogLevels: Set<LogLevel>; hiddenLogLevels: Set<LogLevel>;
width: number; width: number;
changeTime: typeof changeTime;
isLive: boolean; isLive: boolean;
stopLive: typeof changeRefreshIntervalAction; stopLive: typeof changeRefreshIntervalAction;
updateTimeRange: typeof updateTimeRange;
absoluteRange: AbsoluteTimeRange;
} }
export class LogsContainer extends Component<LogsContainerProps> { export class LogsContainer extends Component<LogsContainerProps> {
onChangeTime = (absRange: AbsoluteTimeRange) => { onChangeTime = (absoluteRange: AbsoluteTimeRange) => {
const { exploreId, timeZone, changeTime } = this.props; const { exploreId, updateTimeRange } = this.props;
const range = {
from: timeZone === 'utc' ? toUtc(absRange.from) : dateTime(absRange.from),
to: timeZone === 'utc' ? toUtc(absRange.to) : dateTime(absRange.to),
};
changeTime(exploreId, range); updateTimeRange({ exploreId, absoluteRange });
}; };
onStopLive = () => { onStopLive = () => {
...@@ -111,7 +104,7 @@ export class LogsContainer extends Component<LogsContainerProps> { ...@@ -111,7 +104,7 @@ export class LogsContainer extends Component<LogsContainerProps> {
onClickLabel, onClickLabel,
onStartScanning, onStartScanning,
onStopScanning, onStopScanning,
range, absoluteRange,
timeZone, timeZone,
scanning, scanning,
scanRange, scanRange,
...@@ -143,7 +136,7 @@ export class LogsContainer extends Component<LogsContainerProps> { ...@@ -143,7 +136,7 @@ export class LogsContainer extends Component<LogsContainerProps> {
onStopScanning={onStopScanning} onStopScanning={onStopScanning}
onDedupStrategyChange={this.handleDedupStrategyChange} onDedupStrategyChange={this.handleDedupStrategyChange}
onToggleLogLevel={this.hangleToggleLogLevel} onToggleLogLevel={this.hangleToggleLogLevel}
range={range} absoluteRange={absoluteRange}
timeZone={timeZone} timeZone={timeZone}
scanning={scanning} scanning={scanning}
scanRange={scanRange} scanRange={scanRange}
...@@ -165,9 +158,9 @@ function mapStateToProps(state: StoreState, { exploreId }) { ...@@ -165,9 +158,9 @@ function mapStateToProps(state: StoreState, { exploreId }) {
loadingState, loadingState,
scanning, scanning,
scanRange, scanRange,
range,
datasourceInstance, datasourceInstance,
isLive, isLive,
absoluteRange,
} = item; } = item;
const loading = loadingState === LoadingState.Loading || loadingState === LoadingState.Streaming; const loading = loadingState === LoadingState.Loading || loadingState === LoadingState.Streaming;
const { dedupStrategy } = exploreItemUIStateSelector(item); const { dedupStrategy } = exploreItemUIStateSelector(item);
...@@ -181,21 +174,21 @@ function mapStateToProps(state: StoreState, { exploreId }) { ...@@ -181,21 +174,21 @@ function mapStateToProps(state: StoreState, { exploreId }) {
logsResult, logsResult,
scanning, scanning,
scanRange, scanRange,
range,
timeZone, timeZone,
dedupStrategy, dedupStrategy,
hiddenLogLevels, hiddenLogLevels,
dedupedResult, dedupedResult,
datasourceInstance, datasourceInstance,
isLive, isLive,
absoluteRange,
}; };
} }
const mapDispatchToProps = { const mapDispatchToProps = {
changeDedupStrategy, changeDedupStrategy,
toggleLogLevelAction, toggleLogLevelAction,
changeTime,
stopLive: changeRefreshIntervalAction, stopLive: changeRefreshIntervalAction,
updateTimeRange,
}; };
export default hot(module)( export default hot(module)(
......
...@@ -14,6 +14,7 @@ import { ...@@ -14,6 +14,7 @@ import {
TimeSeries, TimeSeries,
DataQueryResponseData, DataQueryResponseData,
LoadingState, LoadingState,
AbsoluteTimeRange,
} from '@grafana/ui/src/types'; } from '@grafana/ui/src/types';
import { import {
ExploreId, ExploreId,
...@@ -73,11 +74,6 @@ export interface ChangeSizePayload { ...@@ -73,11 +74,6 @@ export interface ChangeSizePayload {
height: number; height: number;
} }
export interface ChangeTimePayload {
exploreId: ExploreId;
range: TimeRange;
}
export interface ChangeRefreshIntervalPayload { export interface ChangeRefreshIntervalPayload {
exploreId: ExploreId; exploreId: ExploreId;
refreshInterval: string; refreshInterval: string;
...@@ -233,7 +229,6 @@ export interface LoadExploreDataSourcesPayload { ...@@ -233,7 +229,6 @@ export interface LoadExploreDataSourcesPayload {
export interface RunQueriesPayload { export interface RunQueriesPayload {
exploreId: ExploreId; exploreId: ExploreId;
range: TimeRange;
} }
export interface ResetQueryErrorPayload { export interface ResetQueryErrorPayload {
...@@ -274,6 +269,13 @@ export interface LimitMessageRatePayload { ...@@ -274,6 +269,13 @@ export interface LimitMessageRatePayload {
export interface ChangeRangePayload { export interface ChangeRangePayload {
exploreId: ExploreId; exploreId: ExploreId;
range: TimeRange; range: TimeRange;
absoluteRange: AbsoluteTimeRange;
}
export interface UpdateTimeRangePayload {
exploreId: ExploreId;
rawRange?: RawTimeRange;
absoluteRange?: AbsoluteTimeRange;
} }
/** /**
...@@ -306,11 +308,6 @@ export const changeSizeAction = actionCreatorFactory<ChangeSizePayload>('explore ...@@ -306,11 +308,6 @@ export const changeSizeAction = actionCreatorFactory<ChangeSizePayload>('explore
/** /**
* Change the time range of Explore. Usually called from the Timepicker or a graph interaction. * Change the time range of Explore. Usually called from the Timepicker or a graph interaction.
*/ */
export const changeTimeAction = actionCreatorFactory<ChangeTimePayload>('explore/CHANGE_TIME').create();
/**
* Change the time range of Explore. Usually called from the Timepicker or a graph interaction.
*/
export const changeRefreshIntervalAction = actionCreatorFactory<ChangeRefreshIntervalPayload>( export const changeRefreshIntervalAction = actionCreatorFactory<ChangeRefreshIntervalPayload>(
'explore/CHANGE_REFRESH_INTERVAL' 'explore/CHANGE_REFRESH_INTERVAL'
).create(); ).create();
...@@ -490,6 +487,8 @@ export const limitMessageRatePayloadAction = actionCreatorFactory<LimitMessageRa ...@@ -490,6 +487,8 @@ export const limitMessageRatePayloadAction = actionCreatorFactory<LimitMessageRa
export const changeRangeAction = actionCreatorFactory<ChangeRangePayload>('explore/CHANGE_RANGE').create(); export const changeRangeAction = actionCreatorFactory<ChangeRangePayload>('explore/CHANGE_RANGE').create();
export const updateTimeRangeAction = actionCreatorFactory<UpdateTimeRangePayload>('explore/UPDATE_TIMERANGE').create();
export type HigherOrderAction = export type HigherOrderAction =
| ActionOf<SplitCloseActionPayload> | ActionOf<SplitCloseActionPayload>
| SplitOpenAction | SplitOpenAction
......
...@@ -4,7 +4,6 @@ import { thunkTester } from 'test/core/thunk/thunkTester'; ...@@ -4,7 +4,6 @@ import { thunkTester } from 'test/core/thunk/thunkTester';
import { import {
initializeExploreAction, initializeExploreAction,
InitializeExplorePayload, InitializeExplorePayload,
changeTimeAction,
updateUIStateAction, updateUIStateAction,
setQueriesAction, setQueriesAction,
testDataSourcePendingAction, testDataSourcePendingAction,
...@@ -12,6 +11,7 @@ import { ...@@ -12,6 +11,7 @@ import {
testDataSourceFailureAction, testDataSourceFailureAction,
loadDatasourcePendingAction, loadDatasourcePendingAction,
loadDatasourceReadyAction, loadDatasourceReadyAction,
updateTimeRangeAction,
} from './actionTypes'; } from './actionTypes';
import { Emitter } from 'app/core/core'; import { Emitter } from 'app/core/core';
import { ActionOf } from 'app/core/redux/actionCreatorFactory'; import { ActionOf } from 'app/core/redux/actionCreatorFactory';
...@@ -118,15 +118,15 @@ describe('refreshExplore', () => { ...@@ -118,15 +118,15 @@ describe('refreshExplore', () => {
}); });
describe('and update range is set', () => { describe('and update range is set', () => {
it('then it should dispatch changeTimeAction', async () => { it('then it should dispatch updateTimeRangeAction', async () => {
const { exploreId, range, initialState } = setup({ range: true }); const { exploreId, range, initialState } = setup({ range: true });
const dispatchedActions = await thunkTester(initialState) const dispatchedActions = await thunkTester(initialState)
.givenThunk(refreshExplore) .givenThunk(refreshExplore)
.whenThunkIsDispatched(exploreId); .whenThunkIsDispatched(exploreId);
expect(dispatchedActions[0].type).toEqual(changeTimeAction.type); expect(dispatchedActions[0].type).toEqual(updateTimeRangeAction.type);
expect(dispatchedActions[0].payload).toEqual({ exploreId, range }); expect(dispatchedActions[0].payload).toEqual({ exploreId, rawRange: range.raw });
}); });
}); });
......
...@@ -24,6 +24,7 @@ import { ...@@ -24,6 +24,7 @@ import {
DataSourceSelectItem, DataSourceSelectItem,
QueryFixAction, QueryFixAction,
LogsDedupStrategy, LogsDedupStrategy,
AbsoluteTimeRange,
} from '@grafana/ui'; } from '@grafana/ui';
import { ExploreId, RangeScanner, ExploreUIState, QueryTransaction, ExploreMode } from 'app/types/explore'; import { ExploreId, RangeScanner, ExploreUIState, QueryTransaction, ExploreMode } from 'app/types/explore';
import { import {
...@@ -33,7 +34,6 @@ import { ...@@ -33,7 +34,6 @@ import {
ChangeRefreshIntervalPayload, ChangeRefreshIntervalPayload,
changeSizeAction, changeSizeAction,
ChangeSizePayload, ChangeSizePayload,
changeTimeAction,
clearQueriesAction, clearQueriesAction,
initializeExploreAction, initializeExploreAction,
loadDatasourceMissingAction, loadDatasourceMissingAction,
...@@ -61,6 +61,7 @@ import { ...@@ -61,6 +61,7 @@ import {
scanRangeAction, scanRangeAction,
runQueriesAction, runQueriesAction,
stateSaveAction, stateSaveAction,
updateTimeRangeAction,
} from './actionTypes'; } from './actionTypes';
import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory'; import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory';
import { getTimeZone } from 'app/features/profile/state/selectors'; import { getTimeZone } from 'app/features/profile/state/selectors';
...@@ -164,17 +165,16 @@ export function changeSize( ...@@ -164,17 +165,16 @@ export function changeSize(
return changeSizeAction({ exploreId, height, width }); return changeSizeAction({ exploreId, height, width });
} }
/** export const updateTimeRange = (options: {
* Change the time range of Explore. Usually called from the Time picker or a graph interaction. exploreId: ExploreId;
*/ rawRange?: RawTimeRange;
export function changeTime(exploreId: ExploreId, rawRange: RawTimeRange): ThunkResult<void> { absoluteRange?: AbsoluteTimeRange;
return (dispatch, getState) => { }): ThunkResult<void> => {
const timeZone = getTimeZone(getState().user); return dispatch => {
const range = getTimeRange(timeZone, rawRange); dispatch(updateTimeRangeAction({ ...options }));
dispatch(changeTimeAction({ exploreId, range })); dispatch(runQueries(options.exploreId));
dispatch(runQueries(exploreId));
}; };
} };
/** /**
* Change the refresh interval of Explore. Called from the Refresh picker. * Change the refresh interval of Explore. Called from the Refresh picker.
...@@ -402,12 +402,8 @@ export function modifyQueries( ...@@ -402,12 +402,8 @@ export function modifyQueries(
*/ */
export function runQueries(exploreId: ExploreId): ThunkResult<void> { export function runQueries(exploreId: ExploreId): ThunkResult<void> {
return (dispatch, getState) => { return (dispatch, getState) => {
const { range } = getState().explore[exploreId]; dispatch(updateTimeRangeAction({ exploreId }));
dispatch(runQueriesAction({ exploreId }));
const timeZone = getTimeZone(getState().user);
const updatedRange = getTimeRange(timeZone, range.raw);
dispatch(runQueriesAction({ exploreId, range: updatedRange }));
}; };
} }
...@@ -548,7 +544,7 @@ export function refreshExplore(exploreId: ExploreId): ThunkResult<void> { ...@@ -548,7 +544,7 @@ export function refreshExplore(exploreId: ExploreId): ThunkResult<void> {
} }
if (update.range) { if (update.range) {
dispatch(changeTimeAction({ exploreId, range })); dispatch(updateTimeRangeAction({ exploreId, rawRange: range.raw }));
} }
// need to refresh ui state // need to refresh ui state
......
...@@ -3,7 +3,14 @@ import { Observable, Subject } from 'rxjs'; ...@@ -3,7 +3,14 @@ import { Observable, Subject } from 'rxjs';
import { mergeMap, catchError, takeUntil, filter } from 'rxjs/operators'; import { mergeMap, catchError, takeUntil, filter } from 'rxjs/operators';
import _, { isString } from 'lodash'; import _, { isString } from 'lodash';
import { isLive } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker'; import { isLive } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker';
import { DataStreamState, LoadingState, DataQueryResponse, SeriesData, DataQueryResponseData } from '@grafana/ui'; import {
DataStreamState,
LoadingState,
DataQueryResponse,
SeriesData,
DataQueryResponseData,
AbsoluteTimeRange,
} from '@grafana/ui';
import * as dateMath from '@grafana/ui/src/utils/datemath'; import * as dateMath from '@grafana/ui/src/utils/datemath';
import { ActionOf } from 'app/core/redux/actionCreatorFactory'; import { ActionOf } from 'app/core/redux/actionCreatorFactory';
...@@ -115,15 +122,24 @@ export const runQueriesBatchEpic: Epic<ActionOf<any>, ActionOf<any>, StoreState> ...@@ -115,15 +122,24 @@ export const runQueriesBatchEpic: Epic<ActionOf<any>, ActionOf<any>, StoreState>
if (state === LoadingState.Streaming) { if (state === LoadingState.Streaming) {
if (event.request && event.request.range) { if (event.request && event.request.range) {
let newRange = event.request.range; let newRange = event.request.range;
let absoluteRange: AbsoluteTimeRange = {
from: newRange.from.valueOf(),
to: newRange.to.valueOf(),
};
if (isString(newRange.raw.from)) { if (isString(newRange.raw.from)) {
newRange = { newRange = {
from: dateMath.parse(newRange.raw.from, false), from: dateMath.parse(newRange.raw.from, false),
to: dateMath.parse(newRange.raw.to, true), to: dateMath.parse(newRange.raw.to, true),
raw: newRange.raw, raw: newRange.raw,
}; };
absoluteRange = {
from: newRange.from.valueOf(),
to: newRange.to.valueOf(),
};
} }
outerObservable.next(changeRangeAction({ exploreId, range: newRange })); outerObservable.next(changeRangeAction({ exploreId, range: newRange, absoluteRange }));
} }
outerObservable.next( outerObservable.next(
limitMessageRatePayloadAction({ limitMessageRatePayloadAction({
exploreId, exploreId,
......
...@@ -13,7 +13,7 @@ describe('runQueriesEpic', () => { ...@@ -13,7 +13,7 @@ describe('runQueriesEpic', () => {
const { exploreId, state, datasourceInterval, containerWidth } = mockExploreState({ queries }); const { exploreId, state, datasourceInterval, containerWidth } = mockExploreState({ queries });
epicTester(runQueriesEpic, state) epicTester(runQueriesEpic, state)
.whenActionIsDispatched(runQueriesAction({ exploreId, range: null })) .whenActionIsDispatched(runQueriesAction({ exploreId }))
.thenResultingActionsEqual( .thenResultingActionsEqual(
runQueriesBatchAction({ runQueriesBatchAction({
exploreId, exploreId,
...@@ -33,7 +33,7 @@ describe('runQueriesEpic', () => { ...@@ -33,7 +33,7 @@ describe('runQueriesEpic', () => {
}); });
epicTester(runQueriesEpic, state) epicTester(runQueriesEpic, state)
.whenActionIsDispatched(runQueriesAction({ exploreId, range: null })) .whenActionIsDispatched(runQueriesAction({ exploreId }))
.thenResultingActionsEqual( .thenResultingActionsEqual(
runQueriesBatchAction({ runQueriesBatchAction({
exploreId, exploreId,
...@@ -50,7 +50,7 @@ describe('runQueriesEpic', () => { ...@@ -50,7 +50,7 @@ describe('runQueriesEpic', () => {
const { exploreId, state } = mockExploreState({ queries }); const { exploreId, state } = mockExploreState({ queries });
epicTester(runQueriesEpic, state) epicTester(runQueriesEpic, state)
.whenActionIsDispatched(runQueriesAction({ exploreId, range: null })) .whenActionIsDispatched(runQueriesAction({ exploreId }))
.thenResultingActionsEqual(clearQueriesAction({ exploreId }), stateSaveAction()); .thenResultingActionsEqual(clearQueriesAction({ exploreId }), stateSaveAction());
}); });
}); });
...@@ -63,7 +63,7 @@ describe('runQueriesEpic', () => { ...@@ -63,7 +63,7 @@ describe('runQueriesEpic', () => {
}); });
epicTester(runQueriesEpic, state) epicTester(runQueriesEpic, state)
.whenActionIsDispatched(runQueriesAction({ exploreId, range: null })) .whenActionIsDispatched(runQueriesAction({ exploreId }))
.thenNoActionsWhereDispatched(); .thenNoActionsWhereDispatched();
}); });
}); });
......
import { dateTime, DefaultTimeZone } from '@grafana/ui';
import { epicTester } from 'test/core/redux/epicTester';
import { mockExploreState } from 'test/mocks/mockExploreState';
import { timeEpic } from './timeEpic';
import { updateTimeRangeAction, changeRangeAction } from '../actionTypes';
import { EpicDependencies } from 'app/store/configureStore';
const from = dateTime('2019-01-01 10:00:00.000Z');
const to = dateTime('2019-01-01 16:00:00.000Z');
const rawFrom = 'now-6h';
const rawTo = 'now';
const rangeMock = {
from,
to,
raw: {
from: rawFrom,
to: rawTo,
},
};
describe('timeEpic', () => {
describe('when updateTimeRangeAction is dispatched', () => {
describe('and no rawRange is supplied', () => {
describe('and no absoluteRange is supplied', () => {
it('then the correct actions are dispatched', () => {
const { exploreId, state, range } = mockExploreState({ range: rangeMock });
const absoluteRange = { from: range.from.valueOf(), to: range.to.valueOf() };
const stateToTest = { ...state, user: { timeZone: 'browser', orgId: -1 } };
const getTimeRange = jest.fn().mockReturnValue(rangeMock);
const dependencies: Partial<EpicDependencies> = {
getTimeRange,
};
epicTester(timeEpic, stateToTest, dependencies)
.whenActionIsDispatched(updateTimeRangeAction({ exploreId }))
.thenDependencyWasCalledTimes(1, 'getTimeSrv', 'init')
.thenDependencyWasCalledTimes(1, 'getTimeRange')
.thenDependencyWasCalledWith([DefaultTimeZone, rangeMock.raw], 'getTimeRange')
.thenResultingActionsEqual(
changeRangeAction({
exploreId,
range,
absoluteRange,
})
);
});
});
describe('and absoluteRange is supplied', () => {
it('then the correct actions are dispatched', () => {
const { exploreId, state, range } = mockExploreState({ range: rangeMock });
const absoluteRange = { from: range.from.valueOf(), to: range.to.valueOf() };
const stateToTest = { ...state, user: { timeZone: 'browser', orgId: -1 } };
const getTimeRange = jest.fn().mockReturnValue(rangeMock);
const dependencies: Partial<EpicDependencies> = {
getTimeRange,
};
epicTester(timeEpic, stateToTest, dependencies)
.whenActionIsDispatched(updateTimeRangeAction({ exploreId, absoluteRange }))
.thenDependencyWasCalledTimes(1, 'getTimeSrv', 'init')
.thenDependencyWasCalledTimes(1, 'getTimeRange')
.thenDependencyWasCalledWith([DefaultTimeZone, { from: null, to: null }], 'getTimeRange')
.thenDependencyWasCalledTimes(2, 'dateTime')
.thenResultingActionsEqual(
changeRangeAction({
exploreId,
range,
absoluteRange,
})
);
});
});
});
describe('and rawRange is supplied', () => {
describe('and no absoluteRange is supplied', () => {
it('then the correct actions are dispatched', () => {
const { exploreId, state, range } = mockExploreState({ range: rangeMock });
const rawRange = { from: 'now-5m', to: 'now' };
const absoluteRange = { from: range.from.valueOf(), to: range.to.valueOf() };
const stateToTest = { ...state, user: { timeZone: 'browser', orgId: -1 } };
const getTimeRange = jest.fn().mockReturnValue(rangeMock);
const dependencies: Partial<EpicDependencies> = {
getTimeRange,
};
epicTester(timeEpic, stateToTest, dependencies)
.whenActionIsDispatched(updateTimeRangeAction({ exploreId, rawRange }))
.thenDependencyWasCalledTimes(1, 'getTimeSrv', 'init')
.thenDependencyWasCalledTimes(1, 'getTimeRange')
.thenDependencyWasCalledWith([DefaultTimeZone, rawRange], 'getTimeRange')
.thenResultingActionsEqual(
changeRangeAction({
exploreId,
range,
absoluteRange,
})
);
});
});
});
});
});
import { Epic } from 'redux-observable';
import { map } from 'rxjs/operators';
import { AbsoluteTimeRange, RawTimeRange } from '@grafana/ui';
import { ActionOf } from 'app/core/redux/actionCreatorFactory';
import { StoreState } from 'app/types/store';
import { updateTimeRangeAction, UpdateTimeRangePayload, changeRangeAction } from '../actionTypes';
export const timeEpic: Epic<ActionOf<any>, ActionOf<any>, StoreState> = (
action$,
state$,
{ getTimeSrv, getTimeRange, getTimeZone, toUtc, dateTime }
) => {
return action$.ofType(updateTimeRangeAction.type).pipe(
map((action: ActionOf<UpdateTimeRangePayload>) => {
const { exploreId, absoluteRange: absRange, rawRange: actionRange } = action.payload;
const itemState = state$.value.explore[exploreId];
const timeZone = getTimeZone(state$.value.user);
const { range: rangeInState } = itemState;
let rawRange: RawTimeRange = rangeInState.raw;
if (absRange) {
rawRange = {
from: timeZone.isUtc ? toUtc(absRange.from) : dateTime(absRange.from),
to: timeZone.isUtc ? toUtc(absRange.to) : dateTime(absRange.to),
};
}
if (actionRange) {
rawRange = actionRange;
}
const range = getTimeRange(timeZone, rawRange);
const absoluteRange: AbsoluteTimeRange = { from: range.from.valueOf(), to: range.to.valueOf() };
getTimeSrv().init({
time: range.raw,
refresh: false,
getTimezone: () => timeZone.raw,
timeRangeUpdated: () => undefined,
});
return changeRangeAction({ exploreId, range, absoluteRange });
})
);
};
...@@ -4,7 +4,6 @@ import { ...@@ -4,7 +4,6 @@ import {
exploreReducer, exploreReducer,
makeInitialUpdateState, makeInitialUpdateState,
initialExploreState, initialExploreState,
DEFAULT_RANGE,
} from './reducers'; } from './reducers';
import { import {
ExploreId, ExploreId,
...@@ -32,7 +31,7 @@ import { ActionOf } from 'app/core/redux/actionCreatorFactory'; ...@@ -32,7 +31,7 @@ import { ActionOf } from 'app/core/redux/actionCreatorFactory';
import { updateLocation } from 'app/core/actions/location'; import { updateLocation } from 'app/core/actions/location';
import { serializeStateToUrlParam } from 'app/core/utils/explore'; import { serializeStateToUrlParam } from 'app/core/utils/explore';
import TableModel from 'app/core/table_model'; import TableModel from 'app/core/table_model';
import { DataSourceApi, DataQuery, LogsModel, LogsDedupStrategy, LoadingState, dateTime } from '@grafana/ui'; import { DataSourceApi, DataQuery, LogsModel, LogsDedupStrategy, LoadingState } from '@grafana/ui';
describe('Explore item reducer', () => { describe('Explore item reducer', () => {
describe('scanning', () => { describe('scanning', () => {
...@@ -193,16 +192,12 @@ describe('Explore item reducer', () => { ...@@ -193,16 +192,12 @@ describe('Explore item reducer', () => {
intervalMs: 1000, intervalMs: 1000,
}, },
showingStartPage: false, showingStartPage: false,
range: { range: null,
from: dateTime(),
to: dateTime(),
raw: DEFAULT_RANGE,
},
}; };
reducerTester() reducerTester()
.givenReducer(itemReducer, initalState) .givenReducer(itemReducer, initalState)
.whenActionIsDispatched(runQueriesAction({ exploreId: ExploreId.left, range: expectedState.range })) .whenActionIsDispatched(runQueriesAction({ exploreId: ExploreId.left }))
.thenStateShouldEqual(expectedState); .thenStateShouldEqual(expectedState);
}); });
}); });
......
...@@ -36,7 +36,6 @@ import { ...@@ -36,7 +36,6 @@ import {
addQueryRowAction, addQueryRowAction,
changeQueryAction, changeQueryAction,
changeSizeAction, changeSizeAction,
changeTimeAction,
changeRefreshIntervalAction, changeRefreshIntervalAction,
clearQueriesAction, clearQueriesAction,
highlightLogsExpressionAction, highlightLogsExpressionAction,
...@@ -95,6 +94,10 @@ export const makeExploreItemState = (): ExploreItemState => ({ ...@@ -95,6 +94,10 @@ export const makeExploreItemState = (): ExploreItemState => ({
to: null, to: null,
raw: DEFAULT_RANGE, raw: DEFAULT_RANGE,
}, },
absoluteRange: {
from: null,
to: null,
},
scanning: false, scanning: false,
scanRange: null, scanRange: null,
showingGraph: true, showingGraph: true,
...@@ -175,12 +178,6 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta ...@@ -175,12 +178,6 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
}, },
}) })
.addMapper({ .addMapper({
filter: changeTimeAction,
mapper: (state, action): ExploreItemState => {
return { ...state, range: action.payload.range };
},
})
.addMapper({
filter: changeRefreshIntervalAction, filter: changeRefreshIntervalAction,
mapper: (state, action): ExploreItemState => { mapper: (state, action): ExploreItemState => {
const { refreshInterval } = action.payload; const { refreshInterval } = action.payload;
...@@ -520,8 +517,8 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta ...@@ -520,8 +517,8 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
}) })
.addMapper({ .addMapper({
filter: runQueriesAction, filter: runQueriesAction,
mapper: (state, action): ExploreItemState => { mapper: (state): ExploreItemState => {
const { range } = action.payload; const { range } = state;
const { datasourceInstance, containerWidth } = state; const { datasourceInstance, containerWidth } = state;
let interval = '1s'; let interval = '1s';
if (datasourceInstance && datasourceInstance.interval) { if (datasourceInstance && datasourceInstance.interval) {
...@@ -575,9 +572,11 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta ...@@ -575,9 +572,11 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
.addMapper({ .addMapper({
filter: changeRangeAction, filter: changeRangeAction,
mapper: (state, action): ExploreItemState => { mapper: (state, action): ExploreItemState => {
const { range, absoluteRange } = action.payload;
return { return {
...state, ...state,
range: action.payload.range, range,
absoluteRange,
}; };
}, },
}) })
......
...@@ -28,11 +28,24 @@ import { ...@@ -28,11 +28,24 @@ import {
DataSourceJsonData, DataSourceJsonData,
DataQueryRequest, DataQueryRequest,
DataStreamObserver, DataStreamObserver,
TimeZone,
RawTimeRange,
TimeRange,
DateTimeInput,
FormatInput,
DateTime,
toUtc,
dateTime,
} from '@grafana/ui'; } from '@grafana/ui';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { getQueryResponse } from 'app/core/utils/explore'; import { getQueryResponse } from 'app/core/utils/explore';
import { StoreState } from 'app/types/store'; import { StoreState } from 'app/types/store';
import { toggleLogActionsMiddleware } from 'app/core/middlewares/application'; import { toggleLogActionsMiddleware } from 'app/core/middlewares/application';
import { timeEpic } from 'app/features/explore/state/epics/timeEpic';
import { TimeSrv, getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { UserState } from 'app/types/user';
import { getTimeRange } from 'app/core/utils/explore';
import { getTimeZone } from 'app/features/profile/state/selectors';
const rootReducers = { const rootReducers = {
...sharedReducers, ...sharedReducers,
...@@ -59,7 +72,8 @@ export const rootEpic: any = combineEpics( ...@@ -59,7 +72,8 @@ export const rootEpic: any = combineEpics(
runQueriesEpic, runQueriesEpic,
runQueriesBatchEpic, runQueriesBatchEpic,
processQueryResultsEpic, processQueryResultsEpic,
processQueryErrorsEpic processQueryErrorsEpic,
timeEpic
); );
export interface EpicDependencies { export interface EpicDependencies {
...@@ -68,10 +82,20 @@ export interface EpicDependencies { ...@@ -68,10 +82,20 @@ export interface EpicDependencies {
options: DataQueryRequest<DataQuery>, options: DataQueryRequest<DataQuery>,
observer?: DataStreamObserver observer?: DataStreamObserver
) => Observable<DataQueryResponse>; ) => Observable<DataQueryResponse>;
getTimeSrv: () => TimeSrv;
getTimeRange: (timeZone: TimeZone, rawRange: RawTimeRange) => TimeRange;
getTimeZone: (state: UserState) => TimeZone;
toUtc: (input?: DateTimeInput, formatInput?: FormatInput) => DateTime;
dateTime: (input?: DateTimeInput, formatInput?: FormatInput) => DateTime;
} }
const dependencies: EpicDependencies = { const dependencies: EpicDependencies = {
getQueryResponse, getQueryResponse,
getTimeSrv,
getTimeRange,
getTimeZone,
toUtc,
dateTime,
}; };
const epicMiddleware = createEpicMiddleware({ dependencies }); const epicMiddleware = createEpicMiddleware({ dependencies });
......
...@@ -12,6 +12,7 @@ import { ...@@ -12,6 +12,7 @@ import {
LogsModel, LogsModel,
LogsDedupStrategy, LogsDedupStrategy,
LoadingState, LoadingState,
AbsoluteTimeRange,
} from '@grafana/ui'; } from '@grafana/ui';
import { Emitter } from 'app/core/core'; import { Emitter } from 'app/core/core';
...@@ -189,6 +190,8 @@ export interface ExploreItemState { ...@@ -189,6 +190,8 @@ export interface ExploreItemState {
* Time range for this Explore. Managed by the time picker and used by all query runs. * Time range for this Explore. Managed by the time picker and used by all query runs.
*/ */
range: TimeRange; range: TimeRange;
absoluteRange: AbsoluteTimeRange;
/** /**
* Scanner function that calculates a new range, triggers a query run, and returns the new range. * Scanner function that calculates a new range, triggers a query run, and returns the new range.
*/ */
......
...@@ -8,15 +8,19 @@ import { ...@@ -8,15 +8,19 @@ import {
DataStreamObserver, DataStreamObserver,
DataQueryResponse, DataQueryResponse,
DataStreamState, DataStreamState,
DefaultTimeZone,
} from '@grafana/ui'; } from '@grafana/ui';
import { ActionOf } from 'app/core/redux/actionCreatorFactory'; import { ActionOf } from 'app/core/redux/actionCreatorFactory';
import { StoreState } from 'app/types/store'; import { StoreState } from 'app/types/store';
import { EpicDependencies } from 'app/store/configureStore'; import { EpicDependencies } from 'app/store/configureStore';
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { DEFAULT_RANGE } from 'app/core/utils/explore';
export const epicTester = ( export const epicTester = (
epic: Epic<ActionOf<any>, ActionOf<any>, StoreState, EpicDependencies>, epic: Epic<ActionOf<any>, ActionOf<any>, StoreState, EpicDependencies>,
state?: Partial<StoreState> state?: Partial<StoreState>,
dependencies?: Partial<EpicDependencies>
) => { ) => {
const resultingActions: Array<ActionOf<any>> = []; const resultingActions: Array<ActionOf<any>> = [];
const action$ = new Subject<ActionOf<any>>(); const action$ = new Subject<ActionOf<any>>();
...@@ -35,12 +39,35 @@ export const epicTester = ( ...@@ -35,12 +39,35 @@ export const epicTester = (
} }
return queryResponse$; return queryResponse$;
}; };
const init = jest.fn();
const getTimeSrv = (): TimeSrv => {
const timeSrvMock: TimeSrv = {} as TimeSrv;
const dependencies: EpicDependencies = { return Object.assign(timeSrvMock, { init });
};
const getTimeRange = jest.fn().mockReturnValue(DEFAULT_RANGE);
const getTimeZone = jest.fn().mockReturnValue(DefaultTimeZone);
const toUtc = jest.fn().mockReturnValue(null);
const dateTime = jest.fn().mockReturnValue(null);
const defaultDependencies: EpicDependencies = {
getQueryResponse, getQueryResponse,
getTimeSrv,
getTimeRange,
getTimeZone,
toUtc,
dateTime,
}; };
epic(actionObservable$, stateObservable$, dependencies).subscribe({ next: action => resultingActions.push(action) }); const theDependencies: EpicDependencies = { ...defaultDependencies, ...dependencies };
epic(actionObservable$, stateObservable$, theDependencies).subscribe({
next: action => resultingActions.push(action),
});
const whenActionIsDispatched = (action: ActionOf<any>) => { const whenActionIsDispatched = (action: ActionOf<any>) => {
action$.next(action); action$.next(action);
...@@ -78,6 +105,32 @@ export const epicTester = ( ...@@ -78,6 +105,32 @@ export const epicTester = (
return instance; return instance;
}; };
const getDependencyMock = (dependency: string, method?: string) => {
const dep = theDependencies[dependency];
let mock = null;
if (dep instanceof Function) {
mock = method ? dep()[method] : dep();
} else {
mock = method ? dep[method] : dep;
}
return mock;
};
const thenDependencyWasCalledTimes = (times: number, dependency: string, method?: string) => {
const mock = getDependencyMock(dependency, method);
expect(mock).toBeCalledTimes(times);
return instance;
};
const thenDependencyWasCalledWith = (args: any[], dependency: string, method?: string) => {
const mock = getDependencyMock(dependency, method);
expect(mock).toBeCalledWith(...args);
return instance;
};
const instance = { const instance = {
whenActionIsDispatched, whenActionIsDispatched,
whenQueryReceivesResponse, whenQueryReceivesResponse,
...@@ -85,6 +138,8 @@ export const epicTester = ( ...@@ -85,6 +138,8 @@ export const epicTester = (
whenQueryObserverReceivesEvent, whenQueryObserverReceivesEvent,
thenResultingActionsEqual, thenResultingActionsEqual,
thenNoActionsWhereDispatched, thenNoActionsWhereDispatched,
thenDependencyWasCalledTimes,
thenDependencyWasCalledWith,
}; };
return instance; return instance;
......
...@@ -3,6 +3,7 @@ import { DataSourceApi } from '@grafana/ui/src/types/datasource'; ...@@ -3,6 +3,7 @@ import { DataSourceApi } from '@grafana/ui/src/types/datasource';
import { ExploreId, ExploreItemState, ExploreState } from 'app/types/explore'; import { ExploreId, ExploreItemState, ExploreState } from 'app/types/explore';
import { makeExploreItemState } from 'app/features/explore/state/reducers'; import { makeExploreItemState } from 'app/features/explore/state/reducers';
import { StoreState } from 'app/types'; import { StoreState } from 'app/types';
import { TimeRange, dateTime } from '@grafana/ui';
export const mockExploreState = (options: any = {}) => { export const mockExploreState = (options: any = {}) => {
const isLive = options.isLive || false; const isLive = options.isLive || false;
...@@ -31,6 +32,14 @@ export const mockExploreState = (options: any = {}) => { ...@@ -31,6 +32,14 @@ export const mockExploreState = (options: any = {}) => {
}, },
interval: datasourceInterval, interval: datasourceInterval,
}; };
const range: TimeRange = options.range || {
from: dateTime('2019-01-01 10:00:00.000Z'),
to: dateTime('2019-01-01 16:00:00.000Z'),
raw: {
from: 'now-6h',
to: 'now',
},
};
const urlReplaced = options.urlReplaced || false; const urlReplaced = options.urlReplaced || false;
const left: ExploreItemState = options.left || { const left: ExploreItemState = options.left || {
...makeExploreItemState(), ...makeExploreItemState(),
...@@ -45,6 +54,7 @@ export const mockExploreState = (options: any = {}) => { ...@@ -45,6 +54,7 @@ export const mockExploreState = (options: any = {}) => {
scanner, scanner,
scanning, scanning,
urlReplaced, urlReplaced,
range,
}; };
const right: ExploreItemState = options.right || { const right: ExploreItemState = options.right || {
...makeExploreItemState(), ...makeExploreItemState(),
...@@ -59,6 +69,7 @@ export const mockExploreState = (options: any = {}) => { ...@@ -59,6 +69,7 @@ export const mockExploreState = (options: any = {}) => {
scanner, scanner,
scanning, scanning,
urlReplaced, urlReplaced,
range,
}; };
const split: boolean = options.split || false; const split: boolean = options.split || false;
const explore: ExploreState = { const explore: ExploreState = {
...@@ -82,5 +93,6 @@ export const mockExploreState = (options: any = {}) => { ...@@ -82,5 +93,6 @@ export const mockExploreState = (options: any = {}) => {
refreshInterval, refreshInterval,
state, state,
scanner, scanner,
range,
}; };
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment