mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
committed by
Marcus Efraimsson
parent
021f5351fa
commit
5f3a71bc7a
@@ -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"]}]'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ export interface InitializeExplorePayload {
|
||||
eventBridge: Emitter;
|
||||
queries: DataQuery[];
|
||||
range: TimeRange;
|
||||
mode: ExploreMode;
|
||||
ui: ExploreUIState;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -36,7 +36,9 @@ class ElasticsearchQueryField extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.onChangeQuery('', true);
|
||||
if (!this.props.query.isLogsQuery) {
|
||||
this.onChangeQuery('', true);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user