فهرست منبع

Explore: Adds URL support for select mode (#17755)

Adds URL support for mode state (Metrics/Logs). It's important 
to be able to share a link to logs when a data source supports 
both metrics and logs.

Closes #17101
kay delaney 6 سال پیش
والد
کامیت
5f3a71bc7a

+ 4 - 2
public/app/core/utils/explore.test.ts

@@ -10,7 +10,7 @@ import {
   getFirstQueryErrorWithoutRefId,
   getRefIds,
 } from './explore';
-import { ExploreUrlState } from 'app/types/explore';
+import { ExploreUrlState, ExploreMode } from 'app/types/explore';
 import store from 'app/core/store';
 import { DataQueryError, LogsDedupStrategy } from '@grafana/ui';
 
@@ -18,6 +18,7 @@ const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
   datasource: null,
   queries: [],
   range: DEFAULT_RANGE,
+  mode: ExploreMode.Metrics,
   ui: {
     showingGraph: true,
     showingTable: true,
@@ -84,6 +85,7 @@ describe('state functions', () => {
       expect(serializeStateToUrlParam(state)).toBe(
         '{"datasource":"foo","queries":[{"expr":"metric{test=\\"a/b\\"}"},' +
           '{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"},' +
+          '"mode":"Metrics",' +
           '"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true,"dedupStrategy":"none"}}'
       );
     });
@@ -106,7 +108,7 @@ describe('state functions', () => {
         },
       };
       expect(serializeStateToUrlParam(state, true)).toBe(
-        '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"ui":[true,true,true,"none"]}]'
+        '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"mode":"Metrics"},{"ui":[true,true,true,"none"]}]'
       );
     });
   });

+ 16 - 3
public/app/core/utils/explore.ts

@@ -28,7 +28,14 @@ import {
   DataQueryRequest,
   DataStreamObserver,
 } from '@grafana/ui';
-import { ExploreUrlState, HistoryItem, QueryTransaction, QueryIntervals, QueryOptions } from 'app/types/explore';
+import {
+  ExploreUrlState,
+  HistoryItem,
+  QueryTransaction,
+  QueryIntervals,
+  QueryOptions,
+  ExploreMode,
+} from 'app/types/explore';
 import { config } from '../config';
 
 export const DEFAULT_RANGE = {
@@ -155,10 +162,11 @@ export function buildQueryTransaction(
 
 export const clearQueryKeys: (query: DataQuery) => object = ({ key, refId, ...rest }) => rest;
 
-const metricProperties = ['expr', 'target', 'datasource'];
+const metricProperties = ['expr', 'target', 'datasource', 'query'];
 const isMetricSegment = (segment: { [key: string]: string }) =>
   metricProperties.some(prop => segment.hasOwnProperty(prop));
 const isUISegment = (segment: { [key: string]: string }) => segment.hasOwnProperty('ui');
+const isModeSegment = (segment: { [key: string]: string }) => segment.hasOwnProperty('mode');
 
 enum ParseUrlStateIndex {
   RangeFrom = 0,
@@ -207,6 +215,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
     queries: [],
     range: DEFAULT_RANGE,
     ui: DEFAULT_UI_STATE,
+    mode: null,
   };
 
   if (!parsed) {
@@ -229,6 +238,9 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
   const datasource = parsed[ParseUrlStateIndex.Datasource];
   const parsedSegments = parsed.slice(ParseUrlStateIndex.SegmentsStart);
   const queries = parsedSegments.filter(segment => isMetricSegment(segment));
+  const modeObj = parsedSegments.filter(segment => isModeSegment(segment))[0];
+  const mode = modeObj ? modeObj.mode : ExploreMode.Metrics;
+
   const uiState = parsedSegments.filter(segment => isUISegment(segment))[0];
   const ui = uiState
     ? {
@@ -239,7 +251,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
       }
     : DEFAULT_UI_STATE;
 
-  return { datasource, queries, range, ui };
+  return { datasource, queries, range, ui, mode };
 }
 
 export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: boolean): string {
@@ -249,6 +261,7 @@ export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: bo
       urlState.range.to,
       urlState.datasource,
       ...urlState.queries,
+      { mode: urlState.mode },
       {
         ui: [
           !!urlState.ui.showingGraph,

+ 24 - 5
public/app/features/explore/Explore.tsx

@@ -83,9 +83,9 @@ interface ExploreProps {
   initialDatasource: string;
   initialQueries: DataQuery[];
   initialRange: RawTimeRange;
+  mode: ExploreMode;
   initialUI: ExploreUIState;
   queryErrors: DataQueryError[];
-  mode: ExploreMode;
   isLive: boolean;
   updateTimeRange: typeof updateTimeRange;
 }
@@ -129,7 +129,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
   }
 
   componentDidMount() {
-    const { initialized, exploreId, initialDatasource, initialQueries, initialRange, initialUI } = this.props;
+    const { initialized, exploreId, initialDatasource, initialQueries, initialRange, mode, initialUI } = this.props;
     const width = this.el ? this.el.offsetWidth : 0;
 
     // initialize the whole explore first time we mount and if browser history contains a change in datasource
@@ -139,6 +139,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
         initialDatasource,
         initialQueries,
         initialRange,
+        mode,
         width,
         this.exploreEvents,
         initialUI
@@ -316,14 +317,32 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) {
     urlState,
     update,
     queryErrors,
-    mode,
     isLive,
+    supportedModes,
+    mode,
   } = item;
 
-  const { datasource, queries, range: urlRange, ui } = (urlState || {}) as ExploreUrlState;
+  const { datasource, queries, range: urlRange, mode: urlMode, ui } = (urlState || {}) as ExploreUrlState;
   const initialDatasource = datasource || store.get(LAST_USED_DATASOURCE_KEY);
   const initialQueries: DataQuery[] = ensureQueries(queries);
   const initialRange = urlRange ? getTimeRangeFromUrl(urlRange, timeZone).raw : DEFAULT_RANGE;
+
+  let newMode: ExploreMode;
+  if (supportedModes.length) {
+    const urlModeIsValid = supportedModes.includes(urlMode);
+    const modeStateIsValid = supportedModes.includes(mode);
+
+    if (urlModeIsValid) {
+      newMode = urlMode;
+    } else if (modeStateIsValid) {
+      newMode = mode;
+    } else {
+      newMode = supportedModes[0];
+    }
+  } else {
+    newMode = [ExploreMode.Metrics, ExploreMode.Logs].includes(urlMode) ? urlMode : ExploreMode.Metrics;
+  }
+
   const initialUI = ui || DEFAULT_UI_STATE;
 
   return {
@@ -340,9 +359,9 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) {
     initialDatasource,
     initialQueries,
     initialRange,
+    mode: newMode,
     initialUI,
     queryErrors,
-    mode,
     isLive,
   };
 }

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

@@ -98,6 +98,7 @@ export interface InitializeExplorePayload {
   eventBridge: Emitter;
   queries: DataQuery[];
   range: TimeRange;
+  mode: ExploreMode;
   ui: ExploreUIState;
 }
 

+ 2 - 1
public/app/features/explore/state/actions.test.ts

@@ -1,5 +1,5 @@
 import { refreshExplore, testDatasource, loadDatasource } from './actions';
-import { ExploreId, ExploreUrlState, ExploreUpdateState } from 'app/types';
+import { ExploreId, ExploreUrlState, ExploreUpdateState, ExploreMode } from 'app/types';
 import { thunkTester } from 'test/core/thunk/thunkTester';
 import {
   initializeExploreAction,
@@ -55,6 +55,7 @@ const setup = (updateOverides?: Partial<ExploreUpdateState>) => {
     datasource: 'some-datasource',
     queries: [],
     range: range.raw,
+    mode: ExploreMode.Metrics,
     ui,
   };
   const updateDefaults = makeInitialUpdateState();

+ 12 - 6
public/app/features/explore/state/actions.ts

@@ -105,10 +105,10 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun
     const currentDataSourceInstance = getState().explore[exploreId].datasourceInstance;
     const queries = getState().explore[exploreId].queries;
 
-    await dispatch(importQueries(exploreId, queries, currentDataSourceInstance, newDataSourceInstance));
-
     dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: newDataSourceInstance }));
 
+    await dispatch(importQueries(exploreId, queries, currentDataSourceInstance, newDataSourceInstance));
+
     if (getState().explore[exploreId].isLive) {
       dispatch(changeRefreshInterval(exploreId, offOption.value));
     }
@@ -123,9 +123,8 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun
  */
 export function changeMode(exploreId: ExploreId, mode: ExploreMode): ThunkResult<void> {
   return dispatch => {
-    dispatch(clearQueries(exploreId));
+    dispatch(clearQueriesAction({ exploreId }));
     dispatch(changeModeAction({ exploreId, mode }));
-    dispatch(runQueries(exploreId));
   };
 }
 
@@ -236,6 +235,7 @@ export function initializeExplore(
   datasourceName: string,
   queries: DataQuery[],
   rawRange: RawTimeRange,
+  mode: ExploreMode,
   containerWidth: number,
   eventBridge: Emitter,
   ui: ExploreUIState
@@ -251,6 +251,7 @@ export function initializeExplore(
         eventBridge,
         queries,
         range,
+        mode,
         ui,
       })
     );
@@ -527,7 +528,7 @@ export function refreshExplore(exploreId: ExploreId): ThunkResult<void> {
     }
 
     const { urlState, update, containerWidth, eventBridge } = itemState;
-    const { datasource, queries, range: urlRange, ui } = urlState;
+    const { datasource, queries, range: urlRange, mode, ui } = urlState;
     const refreshQueries: DataQuery[] = [];
     for (let index = 0; index < queries.length; index++) {
       const query = queries[index];
@@ -539,7 +540,7 @@ export function refreshExplore(exploreId: ExploreId): ThunkResult<void> {
     // need to refresh datasource
     if (update.datasource) {
       const initialQueries = ensureQueries(queries);
-      dispatch(initializeExplore(exploreId, datasource, initialQueries, range, containerWidth, eventBridge, ui));
+      dispatch(initializeExplore(exploreId, datasource, initialQueries, range, mode, containerWidth, eventBridge, ui));
       return;
     }
 
@@ -557,6 +558,11 @@ export function refreshExplore(exploreId: ExploreId): ThunkResult<void> {
       dispatch(setQueriesAction({ exploreId, queries: refreshQueries }));
     }
 
+    // need to refresh mode
+    if (update.mode) {
+      dispatch(changeModeAction({ exploreId, mode }));
+    }
+
     // always run queries when refresh is needed
     if (update.queries || update.ui || update.range) {
       dispatch(runQueries(exploreId));

+ 4 - 4
public/app/features/explore/state/epics/stateSaveEpic.test.ts

@@ -15,7 +15,7 @@ describe('stateSaveEpic', () => {
             .whenActionIsDispatched(stateSaveAction())
             .thenResultingActionsEqual(
               updateLocation({
-                query: { left: '["now-6h","now","test",{"ui":[true,true,true,null]}]' },
+                query: { left: '["now-6h","now","test",{"mode":null},{"ui":[true,true,true,null]}]' },
                 replace: true,
               }),
               setUrlReplacedAction({ exploreId })
@@ -32,8 +32,8 @@ describe('stateSaveEpic', () => {
             .thenResultingActionsEqual(
               updateLocation({
                 query: {
-                  left: '["now-6h","now","test",{"ui":[true,true,true,null]}]',
-                  right: '["now-6h","now","test",{"ui":[true,true,true,null]}]',
+                  left: '["now-6h","now","test",{"mode":null},{"ui":[true,true,true,null]}]',
+                  right: '["now-6h","now","test",{"mode":null},{"ui":[true,true,true,null]}]',
                 },
                 replace: true,
               }),
@@ -51,7 +51,7 @@ describe('stateSaveEpic', () => {
           .whenActionIsDispatched(stateSaveAction())
           .thenResultingActionsEqual(
             updateLocation({
-              query: { left: '["now-6h","now","test",{"ui":[true,true,true,null]}]' },
+              query: { left: '["now-6h","now","test",{"mode":null},{"ui":[true,true,true,null]}]' },
               replace: false,
             })
           );

+ 2 - 0
public/app/features/explore/state/epics/stateSaveEpic.ts

@@ -37,6 +37,7 @@ export const stateSaveEpic: Epic<ActionOf<any>, ActionOf<any>, StoreState> = (ac
         datasource: left.datasourceInstance.name,
         queries: left.queries.map(clearQueryKeys),
         range: toRawTimeRange(left.range),
+        mode: left.mode,
         ui: {
           showingGraph: left.showingGraph,
           showingLogs: true,
@@ -50,6 +51,7 @@ export const stateSaveEpic: Epic<ActionOf<any>, ActionOf<any>, StoreState> = (ac
           datasource: right.datasourceInstance.name,
           queries: right.queries.map(clearQueryKeys),
           range: toRawTimeRange(right.range),
+          mode: right.mode,
           ui: {
             showingGraph: right.showingGraph,
             showingLogs: true,

+ 2 - 0
public/app/features/explore/state/reducers.test.ts

@@ -104,6 +104,7 @@ describe('Explore item reducer', () => {
             datasource: true,
             queries: true,
             range: true,
+            mode: true,
             ui: true,
           },
         };
@@ -213,6 +214,7 @@ export const setup = (urlStateOverrides?: any) => {
       from: '',
       to: '',
     },
+    mode: ExploreMode.Metrics,
     ui: {
       dedupStrategy: LogsDedupStrategy.none,
       showingGraph: false,

+ 6 - 2
public/app/features/explore/state/reducers.ts

@@ -70,6 +70,7 @@ export const makeInitialUpdateState = (): ExploreUpdateState => ({
   datasource: false,
   queries: false,
   range: false,
+  mode: false,
   ui: false,
 });
 
@@ -215,12 +216,13 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
   .addMapper({
     filter: initializeExploreAction,
     mapper: (state, action): ExploreItemState => {
-      const { containerWidth, eventBridge, queries, range, ui } = action.payload;
+      const { containerWidth, eventBridge, queries, range, mode, ui } = action.payload;
       return {
         ...state,
         containerWidth,
         eventBridge,
         range,
+        mode,
         queries,
         initialized: true,
         queryKeys: getQueryKeys(queries, state.datasourceInstance),
@@ -599,13 +601,14 @@ export const updateChildRefreshState = (
     return {
       ...state,
       urlState,
-      update: { datasource: false, queries: false, range: false, ui: false },
+      update: { datasource: false, queries: false, range: false, mode: false, ui: false },
     };
   }
 
   const datasource = _.isEqual(urlState ? urlState.datasource : '', state.urlState.datasource) === false;
   const queries = _.isEqual(urlState ? urlState.queries : [], state.urlState.queries) === false;
   const range = _.isEqual(urlState ? urlState.range : DEFAULT_RANGE, state.urlState.range) === false;
+  const mode = _.isEqual(urlState ? urlState.mode : ExploreMode.Metrics, state.urlState.mode) === false;
   const ui = _.isEqual(urlState ? urlState.ui : DEFAULT_UI_STATE, state.urlState.ui) === false;
 
   return {
@@ -616,6 +619,7 @@ export const updateChildRefreshState = (
       datasource,
       queries,
       range,
+      mode,
       ui,
     },
   };

+ 3 - 1
public/app/plugins/datasource/elasticsearch/components/ElasticsearchQueryField.tsx

@@ -36,7 +36,9 @@ class ElasticsearchQueryField extends React.PureComponent<Props, State> {
   }
 
   componentDidMount() {
-    this.onChangeQuery('', true);
+    if (!this.props.query.isLogsQuery) {
+      this.onChangeQuery('', true);
+    }
   }
 
   componentWillUnmount() {}

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

@@ -261,6 +261,7 @@ export interface ExploreUpdateState {
   datasource: boolean;
   queries: boolean;
   range: boolean;
+  mode: boolean;
   ui: boolean;
 }
 
@@ -274,6 +275,7 @@ export interface ExploreUIState {
 export interface ExploreUrlState {
   datasource: string;
   queries: any[]; // Should be a DataQuery, but we're going to strip refIds, so typing makes less sense
+  mode: ExploreMode;
   range: RawTimeRange;
   ui: ExploreUIState;
 }