Sfoglia il codice sorgente

Save state in URL and fix tests

David Kaltschmidt 7 anni fa
parent
commit
be172d3e4a

+ 22 - 50
public/app/core/utils/explore.test.ts

@@ -6,26 +6,13 @@ import {
   clearHistory,
   hasNonEmptyQuery,
 } from './explore';
-import { ExploreState } from 'app/types/explore';
+import { ExploreUrlState } from 'app/types/explore';
 import store from 'app/core/store';
 
-const DEFAULT_EXPLORE_STATE: ExploreState = {
+const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
   datasource: null,
-  datasourceError: null,
-  datasourceLoading: null,
-  datasourceMissing: false,
-  exploreDatasources: [],
-  graphInterval: 1000,
-  history: [],
-  initialQueries: [],
-  queryTransactions: [],
+  queries: [],
   range: DEFAULT_RANGE,
-  showingGraph: true,
-  showingLogs: true,
-  showingTable: true,
-  supportsGraph: null,
-  supportsLogs: null,
-  supportsTable: null,
 };
 
 describe('state functions', () => {
@@ -68,21 +55,19 @@ describe('state functions', () => {
     it('returns url parameter value for a state object', () => {
       const state = {
         ...DEFAULT_EXPLORE_STATE,
-        initialDatasource: 'foo',
-        range: {
-          from: 'now-5h',
-          to: 'now',
-        },
-        initialQueries: [
+        datasource: 'foo',
+        queries: [
           {
-            refId: '1',
             expr: 'metric{test="a/b"}',
           },
           {
-            refId: '2',
             expr: 'super{foo="x/z"}',
           },
         ],
+        range: {
+          from: 'now-5h',
+          to: 'now',
+        },
       };
       expect(serializeStateToUrlParam(state)).toBe(
         '{"datasource":"foo","queries":[{"expr":"metric{test=\\"a/b\\"}"},' +
@@ -93,21 +78,19 @@ describe('state functions', () => {
     it('returns url parameter value for a state object', () => {
       const state = {
         ...DEFAULT_EXPLORE_STATE,
-        initialDatasource: 'foo',
-        range: {
-          from: 'now-5h',
-          to: 'now',
-        },
-        initialQueries: [
+        datasource: 'foo',
+        queries: [
           {
-            refId: '1',
             expr: 'metric{test="a/b"}',
           },
           {
-            refId: '2',
             expr: 'super{foo="x/z"}',
           },
         ],
+        range: {
+          from: 'now-5h',
+          to: 'now',
+        },
       };
       expect(serializeStateToUrlParam(state, true)).toBe(
         '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"}]'
@@ -119,35 +102,24 @@ describe('state functions', () => {
     it('can parse the serialized state into the original state', () => {
       const state = {
         ...DEFAULT_EXPLORE_STATE,
-        initialDatasource: 'foo',
-        range: {
-          from: 'now - 5h',
-          to: 'now',
-        },
-        initialQueries: [
+        datasource: 'foo',
+        queries: [
           {
-            refId: '1',
             expr: 'metric{test="a/b"}',
           },
           {
-            refId: '2',
             expr: 'super{foo="x/z"}',
           },
         ],
+        range: {
+          from: 'now - 5h',
+          to: 'now',
+        },
       };
       const serialized = serializeStateToUrlParam(state);
       const parsed = parseUrlState(serialized);
 
-      // Account for datasource vs datasourceName
-      const { datasource, queries, ...rest } = parsed;
-      const resultState = {
-        ...rest,
-        datasource: DEFAULT_EXPLORE_STATE.datasource,
-        initialDatasource: datasource,
-        initialQueries: queries,
-      };
-
-      expect(state).toMatchObject(resultState);
+      expect(state).toMatchObject(parsed);
     });
   });
 });

+ 1 - 6
public/app/core/utils/explore.ts

@@ -142,7 +142,7 @@ export function buildQueryTransaction(
   };
 }
 
-const clearQueryKeys: ((query: DataQuery) => object) = ({ key, refId, ...rest }) => rest;
+export const clearQueryKeys: ((query: DataQuery) => object) = ({ key, refId, ...rest }) => rest;
 
 export function parseUrlState(initial: string | undefined): ExploreUrlState {
   if (initial) {
@@ -169,11 +169,6 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
 }
 
 export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: boolean): string {
-  // const urlState: ExploreUrlState = {
-  //   datasource: state.initialDatasource,
-  //   queries: state.initialQueries.map(clearQueryKeys),
-  //   range: state.range,
-  // };
   if (compact) {
     return JSON.stringify([urlState.range.from, urlState.range.to, urlState.datasource, ...urlState.queries]);
   }

+ 8 - 9
public/app/features/explore/Explore.tsx

@@ -13,6 +13,8 @@ import store from 'app/core/store';
 import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE } from 'app/core/utils/explore';
 import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
 import { Emitter } from 'app/core/utils/emitter';
+import { LogsModel } from 'app/core/logs_model';
+import TableModel from 'app/core/table_model';
 
 import {
   addQueryRow,
@@ -45,8 +47,6 @@ import Table from './Table';
 import ErrorBoundary from './ErrorBoundary';
 import { Alert } from './Error';
 import TimePicker, { parseTime } from './TimePicker';
-import { LogsModel } from 'app/core/logs_model';
-import TableModel from 'app/core/table_model';
 
 interface ExploreProps {
   StartPage?: any;
@@ -74,6 +74,7 @@ interface ExploreProps {
   initialDatasource?: string;
   initialQueries: DataQuery[];
   initializeExplore: typeof initializeExplore;
+  initialized: boolean;
   logsHighlighterExpressions?: string[];
   logsResult?: LogsModel;
   modifyQueries: typeof modifyQueries;
@@ -149,8 +150,9 @@ export class Explore extends React.PureComponent<ExploreProps> {
   }
 
   async componentDidMount() {
-    const { exploreId, split, urlState } = this.props;
-    if (!split) {
+    const { exploreId, initialized, urlState } = this.props;
+    // Don't initialize on split, but need to initialize urlparameters when present
+    if (!initialized) {
       // Load URL state and parse range
       const { datasource, queries, range = DEFAULT_RANGE } = (urlState || {}) as ExploreUrlState;
       const initialDatasource = datasource || store.get(LAST_USED_DATASOURCE_KEY);
@@ -277,11 +279,6 @@ export class Explore extends React.PureComponent<ExploreProps> {
     }
   }, 500);
 
-  // saveState = () => {
-  //   const { stateKey, onSaveState } = this.props;
-  //   onSaveState(stateKey, this.cloneState());
-  // };
-
   render() {
     const {
       StartPage,
@@ -478,6 +475,7 @@ function mapStateToProps(state: StoreState, { exploreId }) {
     graphResult,
     initialDatasource,
     initialQueries,
+    initialized,
     history,
     logsHighlighterExpressions,
     logsResult,
@@ -504,6 +502,7 @@ function mapStateToProps(state: StoreState, { exploreId }) {
     graphResult,
     initialDatasource,
     initialQueries,
+    initialized,
     history,
     logsHighlighterExpressions,
     logsResult,

+ 27 - 21
public/app/features/explore/Wrapper.tsx

@@ -3,51 +3,56 @@ import { hot } from 'react-hot-loader';
 import { connect } from 'react-redux';
 
 import { updateLocation } from 'app/core/actions';
-// import { serializeStateToUrlParam, parseUrlState } from 'app/core/utils/explore';
 import { StoreState } from 'app/types';
-import { ExploreId } from 'app/types/explore';
+import { ExploreId, ExploreUrlState } from 'app/types/explore';
+import { parseUrlState } from 'app/core/utils/explore';
 
+import { initializeExploreSplit } from './state/actions';
 import ErrorBoundary from './ErrorBoundary';
 import Explore from './Explore';
 
 interface WrapperProps {
-  backendSrv?: any;
-  datasourceSrv?: any;
+  initializeExploreSplit: typeof initializeExploreSplit;
   split: boolean;
   updateLocation: typeof updateLocation;
-  // urlStates: { [key: string]: string };
+  urlStates: { [key: string]: string };
 }
 
 export class Wrapper extends Component<WrapperProps> {
-  // urlStates: { [key: string]: string };
+  initialSplit: boolean;
+  urlStates: { [key: string]: ExploreUrlState };
 
   constructor(props: WrapperProps) {
     super(props);
-    // this.urlStates = props.urlStates;
+    this.urlStates = {};
+    const { left, right } = props.urlStates;
+    if (props.urlStates.left) {
+      this.urlStates.leftState = parseUrlState(left);
+    }
+    if (props.urlStates.right) {
+      this.urlStates.rightState = parseUrlState(right);
+      this.initialSplit = true;
+    }
   }
 
-  // onSaveState = (key: string, state: ExploreState) => {
-  //   const urlState = serializeStateToUrlParam(state, true);
-  //   this.urlStates[key] = urlState;
-  //   this.props.updateLocation({
-  //     query: this.urlStates,
-  //   });
-  // };
+  componentDidMount() {
+    if (this.initialSplit) {
+      this.props.initializeExploreSplit();
+    }
+  }
 
   render() {
     const { split } = this.props;
-    // State overrides for props from first Explore
-    // const urlStateLeft = parseUrlState(this.urlStates[STATE_KEY_LEFT]);
-    // const urlStateRight = parseUrlState(this.urlStates[STATE_KEY_RIGHT]);
+    const { leftState, rightState } = this.urlStates;
 
     return (
       <div className="explore-wrapper">
         <ErrorBoundary>
-          <Explore exploreId={ExploreId.left} />
+          <Explore exploreId={ExploreId.left} urlState={leftState} />
         </ErrorBoundary>
         {split && (
           <ErrorBoundary>
-            <Explore exploreId={ExploreId.right} />
+            <Explore exploreId={ExploreId.right} urlState={rightState} />
           </ErrorBoundary>
         )}
       </div>
@@ -56,12 +61,13 @@ export class Wrapper extends Component<WrapperProps> {
 }
 
 const mapStateToProps = (state: StoreState) => {
-  // urlStates: state.location.query,
+  const urlStates = state.location.query;
   const { split } = state.explore;
-  return { split };
+  return { split, urlStates };
 };
 
 const mapDispatchToProps = {
+  initializeExploreSplit,
   updateLocation,
 };
 

+ 51 - 9
public/app/features/explore/state/actions.ts

@@ -4,14 +4,17 @@ import { RawTimeRange, TimeRange } from '@grafana/ui';
 
 import {
   LAST_USED_DATASOURCE_KEY,
+  clearQueryKeys,
   ensureQueries,
   generateEmptyQuery,
   hasNonEmptyQuery,
   makeTimeSeriesList,
   updateHistory,
   buildQueryTransaction,
+  serializeStateToUrlParam,
 } from 'app/core/utils/explore';
 
+import { updateLocation } from 'app/core/actions';
 import store from 'app/core/store';
 import { DataSourceSelectItem } from 'app/types/datasources';
 import { DataQuery, StoreState } from 'app/types';
@@ -25,6 +28,7 @@ import {
   QueryTransaction,
   QueryHint,
   QueryHintGetter,
+  ExploreUrlState,
 } from 'app/types/explore';
 import { Emitter } from 'app/core/core';
 import { ExploreItemState } from './reducers';
@@ -44,6 +48,7 @@ export enum ActionTypes {
   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',
@@ -58,6 +63,7 @@ export enum ActionTypes {
   ScanRange = 'SCAN_RANGE',
   ScanStart = 'SCAN_START',
   ScanStop = 'SCAN_STOP',
+  StateSave = 'STATE_SAVE',
 }
 
 export interface AddQueryRowAction {
@@ -123,6 +129,12 @@ export interface ClickTableButtonAction {
   exploreId: ExploreId;
 }
 
+export interface HighlightLogsExpressionAction {
+  type: ActionTypes.HighlightLogsExpression;
+  exploreId: ExploreId;
+  expressions: string[];
+}
+
 export interface InitializeExploreAction {
   type: ActionTypes.InitializeExplore;
   exploreId: ExploreId;
@@ -134,10 +146,8 @@ export interface InitializeExploreAction {
   range: RawTimeRange;
 }
 
-export interface HighlightLogsExpressionAction {
-  type: ActionTypes.HighlightLogsExpression;
-  exploreId: ExploreId;
-  expressions: string[];
+export interface InitializeExploreSplitAction {
+  type: ActionTypes.InitializeExploreSplit;
 }
 
 export interface LoadDatasourceFailureAction {
@@ -224,6 +234,10 @@ export interface ScanStopAction {
   exploreId: ExploreId;
 }
 
+export interface StateSaveAction {
+  type: ActionTypes.StateSave;
+}
+
 export type Action =
   | AddQueryRowAction
   | ChangeQueryAction
@@ -238,6 +252,7 @@ export type Action =
   | ClickTableButtonAction
   | HighlightLogsExpressionAction
   | InitializeExploreAction
+  | InitializeExploreSplitAction
   | LoadDatasourceFailureAction
   | LoadDatasourceMissingAction
   | LoadDatasourcePendingAction
@@ -301,15 +316,14 @@ export function clickClear(exploreId: ExploreId): ThunkResult<void> {
   return dispatch => {
     dispatch(scanStop(exploreId));
     dispatch({ type: ActionTypes.ClickClear, exploreId });
-    // TODO save state
+    dispatch(stateSave());
   };
 }
 
 export function clickCloseSplit(): ThunkResult<void> {
   return dispatch => {
     dispatch({ type: ActionTypes.ClickCloseSplit });
-    // When closing split, remove URL state for split part
-    // TODO save state
+    dispatch(stateSave());
   };
 }
 
@@ -353,7 +367,7 @@ export function clickSplit(): ThunkResult<void> {
       initialQueries: leftState.modifiedQueries.slice(),
     };
     dispatch({ type: ActionTypes.ClickSplit, itemState });
-    // TODO save state
+    dispatch(stateSave());
   };
 }
 
@@ -412,6 +426,12 @@ export function initializeExplore(
   };
 }
 
+export function initializeExploreSplit() {
+  return async dispatch => {
+    dispatch({ type: ActionTypes.InitializeExploreSplit });
+  };
+}
+
 export const loadDatasourceFailure = (exploreId: ExploreId, error: string): LoadDatasourceFailureAction => ({
   type: ActionTypes.LoadDatasourceFailure,
   exploreId,
@@ -733,7 +753,7 @@ export function runQueries(exploreId: ExploreId) {
     if (showingLogs && supportsLogs) {
       dispatch(runQueriesForType(exploreId, 'Logs', { interval, format: 'logs' }));
     }
-    // TODO save state
+    dispatch(stateSave());
   };
 }
 
@@ -792,3 +812,25 @@ export function scanStart(exploreId: ExploreId, scanner: RangeScanner): ThunkRes
 export function scanStop(exploreId: ExploreId): ScanStopAction {
   return { type: ActionTypes.ScanStop, exploreId };
 }
+
+export function stateSave() {
+  return (dispatch, getState) => {
+    const { left, right, split } = getState().explore;
+    const urlStates: { [index: string]: string } = {};
+    const leftUrlState: ExploreUrlState = {
+      datasource: left.datasourceInstance.name,
+      queries: left.modifiedQueries.map(clearQueryKeys),
+      range: left.range,
+    };
+    urlStates.left = serializeStateToUrlParam(leftUrlState, true);
+    if (split) {
+      const rightUrlState: ExploreUrlState = {
+        datasource: right.datasourceInstance.name,
+        queries: right.modifiedQueries.map(clearQueryKeys),
+        range: right.range,
+      };
+      urlStates.right = serializeStateToUrlParam(rightUrlState, true);
+    }
+    dispatch(updateLocation({ query: urlStates }));
+  };
+}

+ 13 - 1
public/app/features/explore/state/reducers.ts

@@ -36,6 +36,7 @@ export interface ExploreItemState {
   history: HistoryItem[];
   initialDatasource?: string;
   initialQueries: DataQuery[];
+  initialized: boolean;
   logsHighlighterExpressions?: string[];
   logsResult?: LogsModel;
   modifiedQueries: DataQuery[];
@@ -74,6 +75,7 @@ const makeExploreItemState = (): ExploreItemState => ({
   exploreDatasources: [],
   history: [],
   initialQueries: [],
+  initialized: false,
   modifiedQueries: [],
   queryTransactions: [],
   queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL },
@@ -89,7 +91,7 @@ const makeExploreItemState = (): ExploreItemState => ({
 });
 
 const initialExploreState: ExploreState = {
-  split: false,
+  split: null,
   left: makeExploreItemState(),
   right: makeExploreItemState(),
 };
@@ -236,6 +238,7 @@ const itemReducer = (state, action: Action): ExploreItemState => {
         range,
         initialDatasource: action.datasource,
         initialQueries: action.queries,
+        initialized: true,
         modifiedQueries: action.queries.slice(),
       };
     }
@@ -436,6 +439,13 @@ export const exploreReducer = (state = initialExploreState, action: Action): Exp
         right: action.itemState,
       };
     }
+
+    case ActionTypes.InitializeExploreSplit: {
+      return {
+        ...state,
+        split: true,
+      };
+    }
   }
 
   const { exploreId } = action as any;
@@ -447,6 +457,8 @@ export const exploreReducer = (state = initialExploreState, action: Action): Exp
     };
   }
 
+  console.error('Unhandled action', action.type);
+
   return state;
 };