Browse Source

File organization, action naming, comments

- moved ActionTypes to `./state/actionTypes`
- renamed click-related actions
- added comments to actions and state types
- prefixed Explore actions with `explore/`
- fixed query override issue when row was added
David Kaltschmidt 7 năm trước cách đây
mục cha
commit
6ff15039a9

+ 17 - 17
public/app/features/explore/Explore.tsx

@@ -18,15 +18,15 @@ import {
   changeDatasource,
   changeSize,
   changeTime,
-  clickClear,
-  clickCloseSplit,
-  clickExample,
-  clickSplit,
+  clearQueries,
   initializeExplore,
   modifyQueries,
   runQueries,
   scanStart,
   scanStop,
+  setQueries,
+  splitClose,
+  splitOpen,
 } from './state/actions';
 
 import { Alert } from './Error';
@@ -42,10 +42,7 @@ interface ExploreProps {
   changeDatasource: typeof changeDatasource;
   changeSize: typeof changeSize;
   changeTime: typeof changeTime;
-  clickClear: typeof clickClear;
-  clickCloseSplit: typeof clickCloseSplit;
-  clickExample: typeof clickExample;
-  clickSplit: typeof clickSplit;
+  clearQueries: typeof clearQueries;
   datasourceError: string;
   datasourceInstance: any;
   datasourceLoading: boolean | null;
@@ -65,7 +62,10 @@ interface ExploreProps {
   scanRange?: RawTimeRange;
   scanStart: typeof scanStart;
   scanStop: typeof scanStop;
+  setQueries: typeof setQueries;
   split: boolean;
+  splitClose: typeof splitClose;
+  splitOpen: typeof splitOpen;
   showingStartPage?: boolean;
   supportsGraph: boolean | null;
   supportsLogs: boolean | null;
@@ -152,20 +152,20 @@ export class Explore extends React.PureComponent<ExploreProps> {
   };
 
   onClickClear = () => {
-    this.props.clickClear(this.props.exploreId);
+    this.props.clearQueries(this.props.exploreId);
   };
 
   onClickCloseSplit = () => {
-    this.props.clickCloseSplit();
+    this.props.splitClose();
   };
 
   // Use this in help pages to set page to a single query
   onClickExample = (query: DataQuery) => {
-    this.props.clickExample(this.props.exploreId, query);
+    this.props.setQueries(this.props.exploreId, [query]);
   };
 
   onClickSplit = () => {
-    this.props.clickSplit();
+    this.props.splitOpen();
   };
 
   onClickLabel = (key: string, value: string) => {
@@ -175,7 +175,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
   onModifyQueries = (action, index?: number) => {
     const { datasourceInstance } = this.props;
     if (datasourceInstance && datasourceInstance.modifyQuery) {
-      const modifier = (queries: DataQuery, action: any) => datasourceInstance.modifyQuery(queries, action);
+      const modifier = (queries: DataQuery, modification: any) => datasourceInstance.modifyQuery(queries, modification);
       this.props.modifyQueries(this.props.exploreId, action, index, modifier);
     }
   };
@@ -366,15 +366,15 @@ const mapDispatchToProps = {
   changeDatasource,
   changeSize,
   changeTime,
-  clickClear,
-  clickCloseSplit,
-  clickExample,
-  clickSplit,
+  clearQueries,
   initializeExplore,
   modifyQueries,
   runQueries,
   scanStart,
   scanStop,
+  setQueries,
+  splitClose,
+  splitOpen,
 };
 
 export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(Explore));

+ 4 - 4
public/app/features/explore/GraphContainer.tsx

@@ -6,13 +6,12 @@ import { RawTimeRange, TimeRange } from '@grafana/ui';
 import { ExploreId, ExploreItemState } from 'app/types/explore';
 import { StoreState } from 'app/types';
 
-import { clickGraphButton } from './state/actions';
+import { toggleGraph } from './state/actions';
 import Graph from './Graph';
 import Panel from './Panel';
 
 interface GraphContainerProps {
   onChangeTime: (range: TimeRange) => void;
-  clickGraphButton: typeof clickGraphButton;
   exploreId: ExploreId;
   graphResult?: any[];
   loading: boolean;
@@ -20,11 +19,12 @@ interface GraphContainerProps {
   showingGraph: boolean;
   showingTable: boolean;
   split: boolean;
+  toggleGraph: typeof toggleGraph;
 }
 
 export class GraphContainer extends PureComponent<GraphContainerProps> {
   onClickGraphButton = () => {
-    this.props.clickGraphButton(this.props.exploreId);
+    this.props.toggleGraph(this.props.exploreId);
   };
 
   render() {
@@ -55,7 +55,7 @@ function mapStateToProps(state: StoreState, { exploreId }) {
 }
 
 const mapDispatchToProps = {
-  clickGraphButton,
+  toggleGraph,
 };
 
 export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(GraphContainer));

+ 4 - 4
public/app/features/explore/LogsContainer.tsx

@@ -7,12 +7,11 @@ import { ExploreId, ExploreItemState } from 'app/types/explore';
 import { LogsModel } from 'app/core/logs_model';
 import { StoreState } from 'app/types';
 
-import { clickLogsButton } from './state/actions';
+import { toggleLogs } from './state/actions';
 import Logs from './Logs';
 import Panel from './Panel';
 
 interface LogsContainerProps {
-  clickLogsButton: typeof clickLogsButton;
   exploreId: ExploreId;
   loading: boolean;
   logsHighlighterExpressions?: string[];
@@ -25,11 +24,12 @@ interface LogsContainerProps {
   scanning?: boolean;
   scanRange?: RawTimeRange;
   showingLogs: boolean;
+  toggleLogs: typeof toggleLogs;
 }
 
 export class LogsContainer extends PureComponent<LogsContainerProps> {
   onClickLogsButton = () => {
-    this.props.clickLogsButton(this.props.exploreId);
+    this.props.toggleLogs(this.props.exploreId);
   };
 
   render() {
@@ -85,7 +85,7 @@ function mapStateToProps(state: StoreState, { exploreId }) {
 }
 
 const mapDispatchToProps = {
-  clickLogsButton,
+  toggleLogs,
 };
 
 export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(LogsContainer));

+ 7 - 7
public/app/features/explore/TableContainer.tsx

@@ -5,30 +5,30 @@ import { connect } from 'react-redux';
 import { ExploreId, ExploreItemState } from 'app/types/explore';
 import { StoreState } from 'app/types';
 
-import { clickTableButton } from './state/actions';
+import { toggleGraph } from './state/actions';
 import Table from './Table';
 import Panel from './Panel';
 import TableModel from 'app/core/table_model';
 
 interface TableContainerProps {
-  clickTableButton: typeof clickTableButton;
   exploreId: ExploreId;
   loading: boolean;
-  onClickLabel: (key: string, value: string) => void;
+  onClickCell: (key: string, value: string) => void;
   showingTable: boolean;
   tableResult?: TableModel;
+  toggleGraph: typeof toggleGraph;
 }
 
 export class TableContainer extends PureComponent<TableContainerProps> {
   onClickTableButton = () => {
-    this.props.clickTableButton(this.props.exploreId);
+    this.props.toggleGraph(this.props.exploreId);
   };
 
   render() {
-    const { loading, onClickLabel, showingTable, tableResult } = this.props;
+    const { loading, onClickCell, showingTable, tableResult } = this.props;
     return (
       <Panel label="Table" loading={loading} isOpen={showingTable} onToggle={this.onClickTableButton}>
-        <Table data={tableResult} loading={loading} onClickCell={onClickLabel} />
+        <Table data={tableResult} loading={loading} onClickCell={onClickCell} />
       </Panel>
     );
   }
@@ -43,7 +43,7 @@ function mapStateToProps(state: StoreState, { exploreId }) {
 }
 
 const mapDispatchToProps = {
-  clickTableButton,
+  toggleGraph,
 };
 
 export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(TableContainer));

+ 252 - 0
public/app/features/explore/state/actionTypes.ts

@@ -0,0 +1,252 @@
+import { RawTimeRange, TimeRange } from '@grafana/ui';
+
+import { Emitter } from 'app/core/core';
+import {
+  ExploreId,
+  ExploreItemState,
+  HistoryItem,
+  RangeScanner,
+  ResultType,
+  QueryTransaction,
+} from 'app/types/explore';
+import { DataSourceSelectItem } from 'app/types/datasources';
+import { DataQuery } from 'app/types';
+
+export enum ActionTypes {
+  AddQueryRow = 'explore/ADD_QUERY_ROW',
+  ChangeDatasource = 'explore/CHANGE_DATASOURCE',
+  ChangeQuery = 'explore/CHANGE_QUERY',
+  ChangeSize = 'explore/CHANGE_SIZE',
+  ChangeTime = 'explore/CHANGE_TIME',
+  ClearQueries = 'explore/CLEAR_QUERIES',
+  HighlightLogsExpression = 'explore/HIGHLIGHT_LOGS_EXPRESSION',
+  InitializeExplore = 'explore/INITIALIZE_EXPLORE',
+  InitializeExploreSplit = 'explore/INITIALIZE_EXPLORE_SPLIT',
+  LoadDatasourceFailure = 'explore/LOAD_DATASOURCE_FAILURE',
+  LoadDatasourceMissing = 'explore/LOAD_DATASOURCE_MISSING',
+  LoadDatasourcePending = 'explore/LOAD_DATASOURCE_PENDING',
+  LoadDatasourceSuccess = 'explore/LOAD_DATASOURCE_SUCCESS',
+  ModifyQueries = 'explore/MODIFY_QUERIES',
+  QueryTransactionFailure = 'explore/QUERY_TRANSACTION_FAILURE',
+  QueryTransactionStart = 'explore/QUERY_TRANSACTION_START',
+  QueryTransactionSuccess = 'explore/QUERY_TRANSACTION_SUCCESS',
+  RemoveQueryRow = 'explore/REMOVE_QUERY_ROW',
+  RunQueries = 'explore/RUN_QUERIES',
+  RunQueriesEmpty = 'explore/RUN_QUERIES_EMPTY',
+  ScanRange = 'explore/SCAN_RANGE',
+  ScanStart = 'explore/SCAN_START',
+  ScanStop = 'explore/SCAN_STOP',
+  SetQueries = 'explore/SET_QUERIES',
+  SplitClose = 'explore/SPLIT_CLOSE',
+  SplitOpen = 'explore/SPLIT_OPEN',
+  StateSave = 'explore/STATE_SAVE',
+  ToggleGraph = 'explore/TOGGLE_GRAPH',
+  ToggleLogs = 'explore/TOGGLE_LOGS',
+  ToggleTable = 'explore/TOGGLE_TABLE',
+}
+
+export interface AddQueryRowAction {
+  type: ActionTypes.AddQueryRow;
+  exploreId: ExploreId;
+  index: number;
+  query: DataQuery;
+}
+
+export interface ChangeQueryAction {
+  type: ActionTypes.ChangeQuery;
+  exploreId: ExploreId;
+  query: DataQuery;
+  index: number;
+  override: boolean;
+}
+
+export interface ChangeSizeAction {
+  type: ActionTypes.ChangeSize;
+  exploreId: ExploreId;
+  width: number;
+  height: number;
+}
+
+export interface ChangeTimeAction {
+  type: ActionTypes.ChangeTime;
+  exploreId: ExploreId;
+  range: TimeRange;
+}
+
+export interface ClearQueriesAction {
+  type: ActionTypes.ClearQueries;
+  exploreId: ExploreId;
+}
+
+export interface HighlightLogsExpressionAction {
+  type: ActionTypes.HighlightLogsExpression;
+  exploreId: ExploreId;
+  expressions: string[];
+}
+
+export interface InitializeExploreAction {
+  type: ActionTypes.InitializeExplore;
+  exploreId: ExploreId;
+  containerWidth: number;
+  datasource: string;
+  eventBridge: Emitter;
+  exploreDatasources: DataSourceSelectItem[];
+  queries: DataQuery[];
+  range: RawTimeRange;
+}
+
+export interface InitializeExploreSplitAction {
+  type: ActionTypes.InitializeExploreSplit;
+}
+
+export interface LoadDatasourceFailureAction {
+  type: ActionTypes.LoadDatasourceFailure;
+  exploreId: ExploreId;
+  error: string;
+}
+
+export interface LoadDatasourcePendingAction {
+  type: ActionTypes.LoadDatasourcePending;
+  exploreId: ExploreId;
+  datasourceId: number;
+}
+
+export interface LoadDatasourceMissingAction {
+  type: ActionTypes.LoadDatasourceMissing;
+  exploreId: ExploreId;
+}
+
+export interface LoadDatasourceSuccessAction {
+  type: ActionTypes.LoadDatasourceSuccess;
+  exploreId: ExploreId;
+  StartPage?: any;
+  datasourceInstance: any;
+  history: HistoryItem[];
+  initialDatasource: string;
+  initialQueries: DataQuery[];
+  logsHighlighterExpressions?: any[];
+  showingStartPage: boolean;
+  supportsGraph: boolean;
+  supportsLogs: boolean;
+  supportsTable: boolean;
+}
+
+export interface ModifyQueriesAction {
+  type: ActionTypes.ModifyQueries;
+  exploreId: ExploreId;
+  modification: any;
+  index: number;
+  modifier: (queries: DataQuery[], modification: any) => DataQuery[];
+}
+
+export interface QueryTransactionFailureAction {
+  type: ActionTypes.QueryTransactionFailure;
+  exploreId: ExploreId;
+  queryTransactions: QueryTransaction[];
+}
+
+export interface QueryTransactionStartAction {
+  type: ActionTypes.QueryTransactionStart;
+  exploreId: ExploreId;
+  resultType: ResultType;
+  rowIndex: number;
+  transaction: QueryTransaction;
+}
+
+export interface QueryTransactionSuccessAction {
+  type: ActionTypes.QueryTransactionSuccess;
+  exploreId: ExploreId;
+  history: HistoryItem[];
+  queryTransactions: QueryTransaction[];
+}
+
+export interface RemoveQueryRowAction {
+  type: ActionTypes.RemoveQueryRow;
+  exploreId: ExploreId;
+  index: number;
+}
+
+export interface RunQueriesEmptyAction {
+  type: ActionTypes.RunQueriesEmpty;
+  exploreId: ExploreId;
+}
+
+export interface ScanStartAction {
+  type: ActionTypes.ScanStart;
+  exploreId: ExploreId;
+  scanner: RangeScanner;
+}
+
+export interface ScanRangeAction {
+  type: ActionTypes.ScanRange;
+  exploreId: ExploreId;
+  range: RawTimeRange;
+}
+
+export interface ScanStopAction {
+  type: ActionTypes.ScanStop;
+  exploreId: ExploreId;
+}
+
+export interface SetQueriesAction {
+  type: ActionTypes.SetQueries;
+  exploreId: ExploreId;
+  queries: DataQuery[];
+}
+
+export interface SplitCloseAction {
+  type: ActionTypes.SplitClose;
+}
+
+export interface SplitOpenAction {
+  type: ActionTypes.SplitOpen;
+  itemState: ExploreItemState;
+}
+
+export interface StateSaveAction {
+  type: ActionTypes.StateSave;
+}
+
+export interface ToggleTableAction {
+  type: ActionTypes.ToggleTable;
+  exploreId: ExploreId;
+}
+
+export interface ToggleGraphAction {
+  type: ActionTypes.ToggleGraph;
+  exploreId: ExploreId;
+}
+
+export interface ToggleLogsAction {
+  type: ActionTypes.ToggleLogs;
+  exploreId: ExploreId;
+}
+
+export type Action =
+  | AddQueryRowAction
+  | ChangeQueryAction
+  | ChangeSizeAction
+  | ChangeTimeAction
+  | ClearQueriesAction
+  | HighlightLogsExpressionAction
+  | InitializeExploreAction
+  | InitializeExploreSplitAction
+  | LoadDatasourceFailureAction
+  | LoadDatasourceMissingAction
+  | LoadDatasourcePendingAction
+  | LoadDatasourceSuccessAction
+  | ModifyQueriesAction
+  | QueryTransactionFailureAction
+  | QueryTransactionStartAction
+  | QueryTransactionSuccessAction
+  | RemoveQueryRowAction
+  | RunQueriesEmptyAction
+  | ScanRangeAction
+  | ScanStartAction
+  | ScanStopAction
+  | SetQueriesAction
+  | SplitCloseAction
+  | SplitOpenAction
+  | ToggleGraphAction
+  | ToggleLogsAction
+  | ToggleTableAction;

+ 204 - 298
public/app/features/explore/state/actions.ts

@@ -21,9 +21,7 @@ import { DataQuery, StoreState } from 'app/types';
 import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
 import {
   ExploreId,
-  ExploreItemState,
   ExploreUrlState,
-  HistoryItem,
   RangeScanner,
   ResultType,
   QueryOptions,
@@ -33,245 +31,33 @@ import {
 } from 'app/types/explore';
 import { Emitter } from 'app/core/core';
 
-export enum ActionTypes {
-  AddQueryRow = 'ADD_QUERY_ROW',
-  ChangeDatasource = 'CHANGE_DATASOURCE',
-  ChangeQuery = 'CHANGE_QUERY',
-  ChangeSize = 'CHANGE_SIZE',
-  ChangeTime = 'CHANGE_TIME',
-  ClickClear = 'CLICK_CLEAR',
-  ClickCloseSplit = 'CLICK_CLOSE_SPLIT',
-  ClickExample = 'CLICK_EXAMPLE',
-  ClickGraphButton = 'CLICK_GRAPH_BUTTON',
-  ClickLogsButton = 'CLICK_LOGS_BUTTON',
-  ClickSplit = 'CLICK_SPLIT',
-  ClickTableButton = 'CLICK_TABLE_BUTTON',
-  HighlightLogsExpression = 'HIGHLIGHT_LOGS_EXPRESSION',
-  InitializeExplore = 'INITIALIZE_EXPLORE',
-  InitializeExploreSplit = 'INITIALIZE_EXPLORE_SPLIT',
-  LoadDatasourceFailure = 'LOAD_DATASOURCE_FAILURE',
-  LoadDatasourceMissing = 'LOAD_DATASOURCE_MISSING',
-  LoadDatasourcePending = 'LOAD_DATASOURCE_PENDING',
-  LoadDatasourceSuccess = 'LOAD_DATASOURCE_SUCCESS',
-  ModifyQueries = 'MODIFY_QUERIES',
-  QueryTransactionFailure = 'QUERY_TRANSACTION_FAILURE',
-  QueryTransactionStart = 'QUERY_TRANSACTION_START',
-  QueryTransactionSuccess = 'QUERY_TRANSACTION_SUCCESS',
-  RemoveQueryRow = 'REMOVE_QUERY_ROW',
-  RunQueries = 'RUN_QUERIES',
-  RunQueriesEmpty = 'RUN_QUERIES',
-  ScanRange = 'SCAN_RANGE',
-  ScanStart = 'SCAN_START',
-  ScanStop = 'SCAN_STOP',
-  StateSave = 'STATE_SAVE',
-}
-
-export interface AddQueryRowAction {
-  type: ActionTypes.AddQueryRow;
-  exploreId: ExploreId;
-  index: number;
-  query: DataQuery;
-}
-
-export interface ChangeQueryAction {
-  type: ActionTypes.ChangeQuery;
-  exploreId: ExploreId;
-  query: DataQuery;
-  index: number;
-  override: boolean;
-}
-
-export interface ChangeSizeAction {
-  type: ActionTypes.ChangeSize;
-  exploreId: ExploreId;
-  width: number;
-  height: number;
-}
-
-export interface ChangeTimeAction {
-  type: ActionTypes.ChangeTime;
-  exploreId: ExploreId;
-  range: TimeRange;
-}
-
-export interface ClickClearAction {
-  type: ActionTypes.ClickClear;
-  exploreId: ExploreId;
-}
-
-export interface ClickCloseSplitAction {
-  type: ActionTypes.ClickCloseSplit;
-}
-
-export interface ClickExampleAction {
-  type: ActionTypes.ClickExample;
-  exploreId: ExploreId;
-  query: DataQuery;
-}
-
-export interface ClickGraphButtonAction {
-  type: ActionTypes.ClickGraphButton;
-  exploreId: ExploreId;
-}
-
-export interface ClickLogsButtonAction {
-  type: ActionTypes.ClickLogsButton;
-  exploreId: ExploreId;
-}
-
-export interface ClickSplitAction {
-  type: ActionTypes.ClickSplit;
-  itemState: ExploreItemState;
-}
-
-export interface ClickTableButtonAction {
-  type: ActionTypes.ClickTableButton;
-  exploreId: ExploreId;
-}
-
-export interface HighlightLogsExpressionAction {
-  type: ActionTypes.HighlightLogsExpression;
-  exploreId: ExploreId;
-  expressions: string[];
-}
-
-export interface InitializeExploreAction {
-  type: ActionTypes.InitializeExplore;
-  exploreId: ExploreId;
-  containerWidth: number;
-  datasource: string;
-  eventBridge: Emitter;
-  exploreDatasources: DataSourceSelectItem[];
-  queries: DataQuery[];
-  range: RawTimeRange;
-}
-
-export interface InitializeExploreSplitAction {
-  type: ActionTypes.InitializeExploreSplit;
-}
-
-export interface LoadDatasourceFailureAction {
-  type: ActionTypes.LoadDatasourceFailure;
-  exploreId: ExploreId;
-  error: string;
-}
-
-export interface LoadDatasourcePendingAction {
-  type: ActionTypes.LoadDatasourcePending;
-  exploreId: ExploreId;
-  datasourceId: number;
-}
-
-export interface LoadDatasourceMissingAction {
-  type: ActionTypes.LoadDatasourceMissing;
-  exploreId: ExploreId;
-}
-
-export interface LoadDatasourceSuccessAction {
-  type: ActionTypes.LoadDatasourceSuccess;
-  exploreId: ExploreId;
-  StartPage?: any;
-  datasourceInstance: any;
-  history: HistoryItem[];
-  initialDatasource: string;
-  initialQueries: DataQuery[];
-  logsHighlighterExpressions?: any[];
-  showingStartPage: boolean;
-  supportsGraph: boolean;
-  supportsLogs: boolean;
-  supportsTable: boolean;
-}
-
-export interface ModifyQueriesAction {
-  type: ActionTypes.ModifyQueries;
-  exploreId: ExploreId;
-  modification: any;
-  index: number;
-  modifier: (queries: DataQuery[], modification: any) => DataQuery[];
-}
-
-export interface QueryTransactionFailureAction {
-  type: ActionTypes.QueryTransactionFailure;
-  exploreId: ExploreId;
-  queryTransactions: QueryTransaction[];
-}
-
-export interface QueryTransactionStartAction {
-  type: ActionTypes.QueryTransactionStart;
-  exploreId: ExploreId;
-  resultType: ResultType;
-  rowIndex: number;
-  transaction: QueryTransaction;
-}
-
-export interface QueryTransactionSuccessAction {
-  type: ActionTypes.QueryTransactionSuccess;
-  exploreId: ExploreId;
-  history: HistoryItem[];
-  queryTransactions: QueryTransaction[];
-}
-
-export interface RemoveQueryRowAction {
-  type: ActionTypes.RemoveQueryRow;
-  exploreId: ExploreId;
-  index: number;
-}
-
-export interface ScanStartAction {
-  type: ActionTypes.ScanStart;
-  exploreId: ExploreId;
-  scanner: RangeScanner;
-}
-
-export interface ScanRangeAction {
-  type: ActionTypes.ScanRange;
-  exploreId: ExploreId;
-  range: RawTimeRange;
-}
-
-export interface ScanStopAction {
-  type: ActionTypes.ScanStop;
-  exploreId: ExploreId;
-}
-
-export interface StateSaveAction {
-  type: ActionTypes.StateSave;
-}
-
-export type Action =
-  | AddQueryRowAction
-  | ChangeQueryAction
-  | ChangeSizeAction
-  | ChangeTimeAction
-  | ClickClearAction
-  | ClickCloseSplitAction
-  | ClickExampleAction
-  | ClickGraphButtonAction
-  | ClickLogsButtonAction
-  | ClickSplitAction
-  | ClickTableButtonAction
-  | HighlightLogsExpressionAction
-  | InitializeExploreAction
-  | InitializeExploreSplitAction
-  | LoadDatasourceFailureAction
-  | LoadDatasourceMissingAction
-  | LoadDatasourcePendingAction
-  | LoadDatasourceSuccessAction
-  | ModifyQueriesAction
-  | QueryTransactionFailureAction
-  | QueryTransactionStartAction
-  | QueryTransactionSuccessAction
-  | RemoveQueryRowAction
-  | ScanRangeAction
-  | ScanStartAction
-  | ScanStopAction;
-type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
-
+import {
+  Action as ThunkableAction,
+  ActionTypes,
+  AddQueryRowAction,
+  ChangeSizeAction,
+  HighlightLogsExpressionAction,
+  LoadDatasourceFailureAction,
+  LoadDatasourceMissingAction,
+  LoadDatasourcePendingAction,
+  LoadDatasourceSuccessAction,
+  QueryTransactionStartAction,
+  ScanStopAction,
+} from './actionTypes';
+
+type ThunkResult<R> = ThunkAction<R, StoreState, undefined, ThunkableAction>;
+
+/**
+ * Adds a query row after the row with the given index.
+ */
 export function addQueryRow(exploreId: ExploreId, index: number): AddQueryRowAction {
   const query = generateEmptyQuery(index + 1);
   return { type: ActionTypes.AddQueryRow, exploreId, index, query };
 }
 
+/**
+ * Loads a new datasource identified by the given name.
+ */
 export function changeDatasource(exploreId: ExploreId, datasource: string): ThunkResult<void> {
   return async dispatch => {
     const instance = await getDatasourceSrv().get(datasource);
@@ -279,6 +65,10 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun
   };
 }
 
+/**
+ * Query change handler for the query row with the given index.
+ * If `override` is reset the query modifications and run the queries. Use this to set queries via a link.
+ */
 export function changeQuery(
   exploreId: ExploreId,
   query: DataQuery,
@@ -298,6 +88,10 @@ export function changeQuery(
   };
 }
 
+/**
+ * Keep track of the Explore container size, in particular the width.
+ * The width will be used to calculate graph intervals (number of datapoints).
+ */
 export function changeSize(
   exploreId: ExploreId,
   { height, width }: { height: number; width: number }
@@ -305,6 +99,9 @@ export function changeSize(
   return { type: ActionTypes.ChangeSize, exploreId, height, width };
 }
 
+/**
+ * Change the time range of Explore. Usually called from the Timepicker or a graph interaction.
+ */
 export function changeTime(exploreId: ExploreId, range: TimeRange): ThunkResult<void> {
   return dispatch => {
     dispatch({ type: ActionTypes.ChangeTime, exploreId, range });
@@ -312,78 +109,28 @@ export function changeTime(exploreId: ExploreId, range: TimeRange): ThunkResult<
   };
 }
 
-export function clickClear(exploreId: ExploreId): ThunkResult<void> {
+/**
+ * Clear all queries and results.
+ */
+export function clearQueries(exploreId: ExploreId): ThunkResult<void> {
   return dispatch => {
     dispatch(scanStop(exploreId));
-    dispatch({ type: ActionTypes.ClickClear, exploreId });
-    dispatch(stateSave());
-  };
-}
-
-export function clickCloseSplit(): ThunkResult<void> {
-  return dispatch => {
-    dispatch({ type: ActionTypes.ClickCloseSplit });
+    dispatch({ type: ActionTypes.ClearQueries, exploreId });
     dispatch(stateSave());
   };
 }
 
-export function clickExample(exploreId: ExploreId, rawQuery: DataQuery): ThunkResult<void> {
-  return dispatch => {
-    const query = { ...rawQuery, ...generateEmptyQuery() };
-    dispatch({
-      type: ActionTypes.ClickExample,
-      exploreId,
-      query,
-    });
-    dispatch(runQueries(exploreId));
-  };
-}
-
-export function clickGraphButton(exploreId: ExploreId): ThunkResult<void> {
-  return (dispatch, getState) => {
-    dispatch({ type: ActionTypes.ClickGraphButton, exploreId });
-    if (getState().explore[exploreId].showingGraph) {
-      dispatch(runQueries(exploreId));
-    }
-  };
-}
-
-export function clickLogsButton(exploreId: ExploreId): ThunkResult<void> {
-  return (dispatch, getState) => {
-    dispatch({ type: ActionTypes.ClickLogsButton, exploreId });
-    if (getState().explore[exploreId].showingLogs) {
-      dispatch(runQueries(exploreId));
-    }
-  };
-}
-
-export function clickSplit(): ThunkResult<void> {
-  return (dispatch, getState) => {
-    // Clone left state to become the right state
-    const leftState = getState().explore.left;
-    const itemState = {
-      ...leftState,
-      queryTransactions: [],
-      initialQueries: leftState.modifiedQueries.slice(),
-    };
-    dispatch({ type: ActionTypes.ClickSplit, itemState });
-    dispatch(stateSave());
-  };
-}
-
-export function clickTableButton(exploreId: ExploreId): ThunkResult<void> {
-  return (dispatch, getState) => {
-    dispatch({ type: ActionTypes.ClickTableButton, exploreId });
-    if (getState().explore[exploreId].showingTable) {
-      dispatch(runQueries(exploreId));
-    }
-  };
-}
-
+/**
+ * Highlight expressions in the log results
+ */
 export function highlightLogsExpression(exploreId: ExploreId, expressions: string[]): HighlightLogsExpressionAction {
   return { type: ActionTypes.HighlightLogsExpression, exploreId, expressions };
 }
 
+/**
+ * Initialize Explore state with state from the URL and the React component.
+ * Call this only on components for with the Explore state has not been initialized.
+ */
 export function initializeExplore(
   exploreId: ExploreId,
   datasource: string,
@@ -426,29 +173,46 @@ export function initializeExplore(
   };
 }
 
+/**
+ * Initialize the wrapper split state
+ */
 export function initializeExploreSplit() {
   return async dispatch => {
     dispatch({ type: ActionTypes.InitializeExploreSplit });
   };
 }
 
+/**
+ * Display an error that happened during the selection of a datasource
+ */
 export const loadDatasourceFailure = (exploreId: ExploreId, error: string): LoadDatasourceFailureAction => ({
   type: ActionTypes.LoadDatasourceFailure,
   exploreId,
   error,
 });
 
+/**
+ * Display an error when no datasources have been configured
+ */
 export const loadDatasourceMissing = (exploreId: ExploreId): LoadDatasourceMissingAction => ({
   type: ActionTypes.LoadDatasourceMissing,
   exploreId,
 });
 
+/**
+ * Start the async process of loading a datasource to display a loading indicator
+ */
 export const loadDatasourcePending = (exploreId: ExploreId, datasourceId: number): LoadDatasourcePendingAction => ({
   type: ActionTypes.LoadDatasourcePending,
   exploreId,
   datasourceId,
 });
 
+/**
+ * Datasource loading was successfully completed. The instance is stored in the state as well in case we need to
+ * run datasource-specific code. Existing queries are imported to the new datasource if an importer exists,
+ * e.g., Prometheus -> Loki queries.
+ */
 export const loadDatasourceSuccess = (
   exploreId: ExploreId,
   instance: any,
@@ -481,6 +245,9 @@ export const loadDatasourceSuccess = (
   };
 };
 
+/**
+ * Main action to asynchronously load a datasource. Dispatches lots of smaller actions for feedback.
+ */
 export function loadDatasource(exploreId: ExploreId, instance: any): ThunkResult<void> {
   return async (dispatch, getState) => {
     const datasourceId = instance.meta.id;
@@ -542,6 +309,13 @@ export function loadDatasource(exploreId: ExploreId, instance: any): ThunkResult
   };
 }
 
+/**
+ * Action to modify a query given a datasource-specific modifier action.
+ * @param exploreId Explore area
+ * @param modification Action object with a type, e.g., ADD_FILTER
+ * @param index Optional query row index. If omitted, the modification is applied to all query rows.
+ * @param modifier Function that executes the modification, typically `datasourceInstance.modifyQueries`.
+ */
 export function modifyQueries(
   exploreId: ExploreId,
   modification: any,
@@ -556,6 +330,10 @@ export function modifyQueries(
   };
 }
 
+/**
+ * Mark a query transaction as failed with an error extracted from the query response.
+ * The transaction will be marked as `done`.
+ */
 export function queryTransactionFailure(
   exploreId: ExploreId,
   transactionId: string,
@@ -614,6 +392,13 @@ export function queryTransactionFailure(
   };
 }
 
+/**
+ * Start a query transaction for the given result type.
+ * @param exploreId Explore area
+ * @param transaction Query options and `done` status.
+ * @param resultType Associate the transaction with a result viewer, e.g., Graph
+ * @param rowIndex Index is used to associate latency for this transaction with a query row
+ */
 export function queryTransactionStart(
   exploreId: ExploreId,
   transaction: QueryTransaction,
@@ -623,6 +408,17 @@ export function queryTransactionStart(
   return { type: ActionTypes.QueryTransactionStart, exploreId, resultType, rowIndex, transaction };
 }
 
+/**
+ * Complete a query transaction, mark the transaction as `done` and store query state in URL.
+ * If the transaction was started by a scanner, it keeps on scanning for more results.
+ * Side-effect: the query is stored in localStorage.
+ * @param exploreId Explore area
+ * @param transactionId ID
+ * @param result Response from `datasourceInstance.query()`
+ * @param latency Duration between request and response
+ * @param queries Queries from all query rows
+ * @param datasourceId Origin datasource instance, used to discard results if current datasource is different
+ */
 export function queryTransactionSuccess(
   exploreId: ExploreId,
   transactionId: string,
@@ -691,6 +487,9 @@ export function queryTransactionSuccess(
   };
 }
 
+/**
+ * Remove query row of the given index, as well as associated query results.
+ */
 export function removeQueryRow(exploreId: ExploreId, index: number): ThunkResult<void> {
   return dispatch => {
     dispatch({ type: ActionTypes.RemoveQueryRow, exploreId, index });
@@ -698,6 +497,9 @@ export function removeQueryRow(exploreId: ExploreId, index: number): ThunkResult
   };
 }
 
+/**
+ * Main action to run queries and dispatches sub-actions based on which result viewers are active
+ */
 export function runQueries(exploreId: ExploreId) {
   return (dispatch, getState) => {
     const {
@@ -757,6 +559,13 @@ export function runQueries(exploreId: ExploreId) {
   };
 }
 
+/**
+ * Helper action to build a query transaction object and handing the query to the datasource.
+ * @param exploreId Explore area
+ * @param resultType Result viewer that will be associated with this query result
+ * @param queryOptions Query options as required by the datasource's `query()` function.
+ * @param resultGetter Optional result extractor, e.g., if the result is a list and you only need the first element.
+ */
 function runQueriesForType(
   exploreId: ExploreId,
   resultType: ResultType,
@@ -801,18 +610,79 @@ function runQueriesForType(
   };
 }
 
+/**
+ * Start a scan for more results using the given scanner.
+ * @param exploreId Explore area
+ * @param scanner Function that a) returns a new time range and b) triggers a query run for the new range
+ */
 export function scanStart(exploreId: ExploreId, scanner: RangeScanner): ThunkResult<void> {
   return dispatch => {
+    // Register the scanner
     dispatch({ type: ActionTypes.ScanStart, exploreId, scanner });
+    // Scanning must trigger query run, and return the new range
     const range = scanner();
+    // Set the new range to be displayed
     dispatch({ type: ActionTypes.ScanRange, exploreId, range });
   };
 }
 
+/**
+ * Stop any scanning for more results.
+ */
 export function scanStop(exploreId: ExploreId): ScanStopAction {
   return { type: ActionTypes.ScanStop, exploreId };
 }
 
+/**
+ * Reset queries to the given queries. Any modifications will be discarded.
+ * Use this action for clicks on query examples. Triggers a query run.
+ */
+export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): ThunkResult<void> {
+  return dispatch => {
+    // Inject react keys into query objects
+    const queries = rawQueries.map(q => ({ ...q, ...generateEmptyQuery() }));
+    dispatch({
+      type: ActionTypes.SetQueries,
+      exploreId,
+      queries,
+    });
+    dispatch(runQueries(exploreId));
+  };
+}
+
+/**
+ * Close the split view and save URL state.
+ */
+export function splitClose(): ThunkResult<void> {
+  return dispatch => {
+    dispatch({ type: ActionTypes.SplitClose });
+    dispatch(stateSave());
+  };
+}
+
+/**
+ * Open the split view and copy the left state to be the right state.
+ * The right state is automatically initialized.
+ * The copy keeps all query modifications but wipes the query results.
+ */
+export function splitOpen(): ThunkResult<void> {
+  return (dispatch, getState) => {
+    // Clone left state to become the right state
+    const leftState = getState().explore.left;
+    const itemState = {
+      ...leftState,
+      queryTransactions: [],
+      initialQueries: leftState.modifiedQueries.slice(),
+    };
+    dispatch({ type: ActionTypes.SplitOpen, itemState });
+    dispatch(stateSave());
+  };
+}
+
+/**
+ * Saves Explore state to URL using the `left` and `right` parameters.
+ * If split view is not active, `right` will not be set.
+ */
 export function stateSave() {
   return (dispatch, getState) => {
     const { left, right, split } = getState().explore;
@@ -834,3 +704,39 @@ export function stateSave() {
     dispatch(updateLocation({ query: urlStates }));
   };
 }
+
+/**
+ * Expand/collapse the graph result viewer. When collapsed, graph queries won't be run.
+ */
+export function toggleGraph(exploreId: ExploreId): ThunkResult<void> {
+  return (dispatch, getState) => {
+    dispatch({ type: ActionTypes.ToggleGraph, exploreId });
+    if (getState().explore[exploreId].showingGraph) {
+      dispatch(runQueries(exploreId));
+    }
+  };
+}
+
+/**
+ * Expand/collapse the logs result viewer. When collapsed, log queries won't be run.
+ */
+export function toggleLogs(exploreId: ExploreId): ThunkResult<void> {
+  return (dispatch, getState) => {
+    dispatch({ type: ActionTypes.ToggleLogs, exploreId });
+    if (getState().explore[exploreId].showingLogs) {
+      dispatch(runQueries(exploreId));
+    }
+  };
+}
+
+/**
+ * Expand/collapse the table result viewer. When collapsed, table queries won't be run.
+ */
+export function toggleTable(exploreId: ExploreId): ThunkResult<void> {
+  return (dispatch, getState) => {
+    dispatch({ type: ActionTypes.ToggleTable, exploreId });
+    if (getState().explore[exploreId].showingTable) {
+      dispatch(runQueries(exploreId));
+    }
+  };
+}

+ 111 - 99
public/app/features/explore/state/reducers.ts

@@ -7,7 +7,7 @@ import {
 import { ExploreItemState, ExploreState, QueryTransaction } from 'app/types/explore';
 import { DataQuery } from 'app/types/series';
 
-import { Action, ActionTypes } from './actions';
+import { Action, ActionTypes } from './actionTypes';
 
 export const DEFAULT_RANGE = {
   from: 'now-6h',
@@ -62,14 +62,17 @@ const itemReducer = (state, action: Action): ExploreItemState => {
     case ActionTypes.AddQueryRow: {
       const { initialQueries, modifiedQueries, queryTransactions } = state;
       const { index, query } = action;
-      modifiedQueries[index + 1] = query;
 
-      const nextQueries = [
-        ...initialQueries.slice(0, index + 1),
-        { ...modifiedQueries[index + 1] },
+      // Add new query row after given index, keep modifications of existing rows
+      const nextModifiedQueries = [
+        ...modifiedQueries.slice(0, index + 1),
+        { ...query },
         ...initialQueries.slice(index + 1),
       ];
 
+      // Add to initialQueries, which will cause a new row to be rendered
+      const nextQueries = [...initialQueries.slice(0, index + 1), { ...query }, ...initialQueries.slice(index + 1)];
+
       // Ongoing transactions need to update their row indices
       const nextQueryTransactions = queryTransactions.map(qt => {
         if (qt.rowIndex > index) {
@@ -83,9 +86,9 @@ const itemReducer = (state, action: Action): ExploreItemState => {
 
       return {
         ...state,
-        modifiedQueries,
         initialQueries: nextQueries,
         logsHighlighterExpressions: undefined,
+        modifiedQueries: nextModifiedQueries,
         queryTransactions: nextQueryTransactions,
       };
     }
@@ -94,29 +97,33 @@ const itemReducer = (state, action: Action): ExploreItemState => {
       const { initialQueries, queryTransactions } = state;
       let { modifiedQueries } = state;
       const { query, index, override } = action;
-      modifiedQueries[index] = query;
-      if (override) {
-        const nextQuery: DataQuery = {
-          ...query,
-          ...generateEmptyQuery(index),
-        };
-        const nextQueries = [...initialQueries];
-        nextQueries[index] = nextQuery;
-        modifiedQueries = [...nextQueries];
-
-        // Discard ongoing transaction related to row query
-        const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
 
+      // Fast path: only change modifiedQueries to not trigger an update
+      modifiedQueries[index] = query;
+      if (!override) {
         return {
           ...state,
-          initialQueries: nextQueries,
-          modifiedQueries: nextQueries.slice(),
-          queryTransactions: nextQueryTransactions,
+          modifiedQueries,
         };
       }
+
+      // Override path: queries are completely reset
+      const nextQuery: DataQuery = {
+        ...query,
+        ...generateEmptyQuery(index),
+      };
+      const nextQueries = [...initialQueries];
+      nextQueries[index] = nextQuery;
+      modifiedQueries = [...nextQueries];
+
+      // Discard ongoing transaction related to row query
+      const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
+
       return {
         ...state,
-        modifiedQueries,
+        initialQueries: nextQueries,
+        modifiedQueries: nextQueries.slice(),
+        queryTransactions: nextQueryTransactions,
       };
     }
 
@@ -138,58 +145,17 @@ const itemReducer = (state, action: Action): ExploreItemState => {
       };
     }
 
-    case ActionTypes.ClickClear: {
+    case ActionTypes.ClearQueries: {
       const queries = ensureQueries();
       return {
         ...state,
         initialQueries: queries.slice(),
         modifiedQueries: queries.slice(),
+        queryTransactions: [],
         showingStartPage: Boolean(state.StartPage),
       };
     }
 
-    case ActionTypes.ClickExample: {
-      const modifiedQueries = [action.query];
-      return { ...state, initialQueries: modifiedQueries.slice(), modifiedQueries };
-    }
-
-    case ActionTypes.ClickGraphButton: {
-      const showingGraph = !state.showingGraph;
-      let nextQueryTransactions = state.queryTransactions;
-      if (!showingGraph) {
-        // Discard transactions related to Graph query
-        nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph');
-      }
-      return { ...state, queryTransactions: nextQueryTransactions, showingGraph };
-    }
-
-    case ActionTypes.ClickLogsButton: {
-      const showingLogs = !state.showingLogs;
-      let nextQueryTransactions = state.queryTransactions;
-      if (!showingLogs) {
-        // Discard transactions related to Logs query
-        nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs');
-      }
-      return { ...state, queryTransactions: nextQueryTransactions, showingLogs };
-    }
-
-    case ActionTypes.ClickTableButton: {
-      const showingTable = !state.showingTable;
-      if (showingTable) {
-        return { ...state, showingTable, queryTransactions: state.queryTransactions };
-      }
-
-      // Toggle off needs discarding of table queries and results
-      const nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Table');
-      const results = calculateResultsFromQueryTransactions(
-        nextQueryTransactions,
-        state.datasourceInstance,
-        state.queryIntervals.intervalMs
-      );
-
-      return { ...state, ...results, queryTransactions: nextQueryTransactions, showingTable };
-    }
-
     case ActionTypes.HighlightLogsExpression: {
       const { expressions } = action;
       return { ...state, logsHighlighterExpressions: expressions };
@@ -248,7 +214,7 @@ const itemReducer = (state, action: Action): ExploreItemState => {
 
     case ActionTypes.ModifyQueries: {
       const { initialQueries, modifiedQueries, queryTransactions } = state;
-      const { action: modification, index, modifier } = action as any;
+      const { modification, index, modifier } = action as any;
       let nextQueries: DataQuery[];
       let nextQueryTransactions;
       if (index === undefined) {
@@ -290,37 +256,6 @@ const itemReducer = (state, action: Action): ExploreItemState => {
       };
     }
 
-    case ActionTypes.RemoveQueryRow: {
-      const { datasourceInstance, initialQueries, queryIntervals, queryTransactions } = state;
-      let { modifiedQueries } = state;
-      const { index } = action;
-
-      modifiedQueries = [...modifiedQueries.slice(0, index), ...modifiedQueries.slice(index + 1)];
-
-      if (initialQueries.length <= 1) {
-        return state;
-      }
-
-      const nextQueries = [...initialQueries.slice(0, index), ...initialQueries.slice(index + 1)];
-
-      // Discard transactions related to row query
-      const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
-      const results = calculateResultsFromQueryTransactions(
-        nextQueryTransactions,
-        datasourceInstance,
-        queryIntervals.intervalMs
-      );
-
-      return {
-        ...state,
-        ...results,
-        initialQueries: nextQueries,
-        logsHighlighterExpressions: undefined,
-        modifiedQueries: nextQueries.slice(),
-        queryTransactions: nextQueryTransactions,
-      };
-    }
-
     case ActionTypes.QueryTransactionFailure: {
       const { queryTransactions } = action;
       return {
@@ -373,6 +308,41 @@ const itemReducer = (state, action: Action): ExploreItemState => {
       };
     }
 
+    case ActionTypes.RemoveQueryRow: {
+      const { datasourceInstance, initialQueries, queryIntervals, queryTransactions } = state;
+      let { modifiedQueries } = state;
+      const { index } = action;
+
+      modifiedQueries = [...modifiedQueries.slice(0, index), ...modifiedQueries.slice(index + 1)];
+
+      if (initialQueries.length <= 1) {
+        return state;
+      }
+
+      const nextQueries = [...initialQueries.slice(0, index), ...initialQueries.slice(index + 1)];
+
+      // Discard transactions related to row query
+      const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
+      const results = calculateResultsFromQueryTransactions(
+        nextQueryTransactions,
+        datasourceInstance,
+        queryIntervals.intervalMs
+      );
+
+      return {
+        ...state,
+        ...results,
+        initialQueries: nextQueries,
+        logsHighlighterExpressions: undefined,
+        modifiedQueries: nextQueries.slice(),
+        queryTransactions: nextQueryTransactions,
+      };
+    }
+
+    case ActionTypes.RunQueriesEmpty: {
+      return { ...state, queryTransactions: [] };
+    }
+
     case ActionTypes.ScanRange: {
       return { ...state, scanRange: action.range };
     }
@@ -386,6 +356,48 @@ const itemReducer = (state, action: Action): ExploreItemState => {
       const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done);
       return { ...state, queryTransactions: nextQueryTransactions, scanning: false, scanRange: undefined };
     }
+
+    case ActionTypes.SetQueries: {
+      const { queries } = action;
+      return { ...state, initialQueries: queries.slice(), modifiedQueries: queries.slice() };
+    }
+
+    case ActionTypes.ToggleGraph: {
+      const showingGraph = !state.showingGraph;
+      let nextQueryTransactions = state.queryTransactions;
+      if (!showingGraph) {
+        // Discard transactions related to Graph query
+        nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph');
+      }
+      return { ...state, queryTransactions: nextQueryTransactions, showingGraph };
+    }
+
+    case ActionTypes.ToggleLogs: {
+      const showingLogs = !state.showingLogs;
+      let nextQueryTransactions = state.queryTransactions;
+      if (!showingLogs) {
+        // Discard transactions related to Logs query
+        nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs');
+      }
+      return { ...state, queryTransactions: nextQueryTransactions, showingLogs };
+    }
+
+    case ActionTypes.ToggleTable: {
+      const showingTable = !state.showingTable;
+      if (showingTable) {
+        return { ...state, showingTable, queryTransactions: state.queryTransactions };
+      }
+
+      // Toggle off needs discarding of table queries and results
+      const nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Table');
+      const results = calculateResultsFromQueryTransactions(
+        nextQueryTransactions,
+        state.datasourceInstance,
+        state.queryIntervals.intervalMs
+      );
+
+      return { ...state, ...results, queryTransactions: nextQueryTransactions, showingTable };
+    }
   }
 
   return state;
@@ -397,14 +409,14 @@ const itemReducer = (state, action: Action): ExploreItemState => {
  */
 export const exploreReducer = (state = initialExploreState, action: Action): ExploreState => {
   switch (action.type) {
-    case ActionTypes.ClickCloseSplit: {
+    case ActionTypes.SplitClose: {
       return {
         ...state,
         split: false,
       };
     }
 
-    case ActionTypes.ClickSplit: {
+    case ActionTypes.SplitOpen: {
       return {
         ...state,
         split: true,

+ 117 - 0
public/app/types/explore.ts

@@ -83,43 +83,160 @@ export enum ExploreId {
   right = 'right',
 }
 
+/**
+ * Global Explore state
+ */
 export interface ExploreState {
+  /**
+   * True if split view is active.
+   */
   split: boolean;
+  /**
+   * Explore state of the left split (left is default in non-split view).
+   */
   left: ExploreItemState;
+  /**
+   * Explore state of the right area in split view.
+   */
   right: ExploreItemState;
 }
 
 export interface ExploreItemState {
+  /**
+   * React component to be shown when no queries have been run yet, e.g., for a query language cheat sheet.
+   */
   StartPage?: any;
+  /**
+   * Width used for calculating the graph interval (can't have more datapoints than pixels)
+   */
   containerWidth: number;
+  /**
+   * Datasource instance that has been selected. Datasource-specific logic can be run on this object.
+   */
   datasourceInstance: any;
+  /**
+   * Error to be shown when datasource loading or testing failed.
+   */
   datasourceError: string;
+  /**
+   * True if the datasource is loading. `null` if the loading has not started yet.
+   */
   datasourceLoading: boolean | null;
+  /**
+   * True if there is no datasource to be selected.
+   */
   datasourceMissing: boolean;
+  /**
+   * Emitter to send events to the rest of Grafana.
+   */
   eventBridge?: Emitter;
+  /**
+   * List of datasources to be shown in the datasource selector.
+   */
   exploreDatasources: DataSourceSelectItem[];
+  /**
+   * List of timeseries to be shown in the Explore graph result viewer.
+   */
   graphResult?: any[];
+  /**
+   * History of recent queries. Datasource-specific and initialized via localStorage.
+   */
   history: HistoryItem[];
+  /**
+   * Initial datasource for this Explore, e.g., set via URL.
+   */
   initialDatasource?: string;
+  /**
+   * Initial queries for this Explore, e.g., set via URL. Each query will be
+   * converted to a query row. Query edits should be tracked in `modifiedQueries` though.
+   */
   initialQueries: DataQuery[];
+  /**
+   * True if this Explore area has been initialized.
+   * Used to distinguish URL state injection versus split view state injection.
+   */
   initialized: boolean;
+  /**
+   * Log line substrings to be highlighted as you type in a query field.
+   * Currently supports only the first query row.
+   */
   logsHighlighterExpressions?: string[];
+  /**
+   * Log query result to be displayed in the logs result viewer.
+   */
   logsResult?: LogsModel;
+  /**
+   * Copy of `initialQueries` that tracks user edits.
+   * Don't connect this property to a react component as it is updated on every query change.
+   * Used when running queries. Needs to be reset to `initialQueries` when those are reset as well.
+   */
   modifiedQueries: DataQuery[];
+  /**
+   * Query intervals for graph queries to determine how many datapoints to return.
+   * Needs to be updated when `datasourceInstance` or `containerWidth` is changed.
+   */
   queryIntervals: QueryIntervals;
+  /**
+   * List of query transaction to track query duration and query result.
+   * Graph/Logs/Table results are calculated on the fly from the transaction,
+   * based on the transaction's result types. Transaction also holds the row index
+   * so that results can be dropped and re-computed without running queries again
+   * when query rows are removed.
+   */
   queryTransactions: QueryTransaction[];
+  /**
+   * Tracks datasource when selected in the datasource selector.
+   * Allows the selection to be discarded if something went wrong during the asynchronous
+   * loading of the datasource.
+   */
   requestedDatasourceId?: number;
+  /**
+   * Time range for this Explore. Managed by the time picker and used by all query runs.
+   */
   range: TimeRange | RawTimeRange;
+  /**
+   * Scanner function that calculates a new range, triggers a query run, and returns the new range.
+   */
   scanner?: RangeScanner;
+  /**
+   * True if scanning for more results is active.
+   */
   scanning?: boolean;
+  /**
+   * Current scanning range to be shown to the user while scanning is active.
+   */
   scanRange?: RawTimeRange;
+  /**
+   * True if graph result viewer is expanded. Query runs will contain graph queries.
+   */
   showingGraph: boolean;
+  /**
+   * True if logs result viewer is expanded. Query runs will contain logs queries.
+   */
   showingLogs: boolean;
+  /**
+   * True StartPage needs to be shown. Typically set to `false` once queries have been run.
+   */
   showingStartPage?: boolean;
+  /**
+   * True if table result viewer is expanded. Query runs will contain table queries.
+   */
   showingTable: boolean;
+  /**
+   * True if `datasourceInstance` supports graph queries.
+   */
   supportsGraph: boolean | null;
+  /**
+   * True if `datasourceInstance` supports logs queries.
+   */
   supportsLogs: boolean | null;
+  /**
+   * True if `datasourceInstance` supports table queries.
+   */
   supportsTable: boolean | null;
+  /**
+   * Table model that combines all query table results into a single table.
+   */
   tableResult?: TableModel;
 }