mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Refactor & centralize URL/state sync (#66286)
Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { ExploreUrlState, serializeStateToUrlParam, SplitOpenOptions, UrlQueryMap } from '@grafana/data';
|
||||
import { SplitOpenOptions } from '@grafana/data';
|
||||
import { DataSourceSrv, locationService } from '@grafana/runtime';
|
||||
import { GetExploreUrlArguments, stopQueryState } from 'app/core/utils/explore';
|
||||
import { GetExploreUrlArguments } from 'app/core/utils/explore';
|
||||
import { PanelModel } from 'app/features/dashboard/state';
|
||||
import { ExploreId, ExploreItemState, ExploreState } from 'app/types/explore';
|
||||
|
||||
@@ -12,9 +12,10 @@ import { RichHistorySearchFilters, RichHistorySettings } from '../../../core/uti
|
||||
import { createAsyncThunk, ThunkResult } from '../../../types';
|
||||
import { CorrelationData } from '../../correlations/useCorrelations';
|
||||
import { TimeSrv } from '../../dashboard/services/TimeSrv';
|
||||
import { withUniqueRefIds } from '../utils/queries';
|
||||
|
||||
import { initializeExplore, paneReducer } from './explorePane';
|
||||
import { getUrlStateFromPaneState, makeExplorePaneState } from './utils';
|
||||
import { DEFAULT_RANGE, makeExplorePaneState } from './utils';
|
||||
|
||||
//
|
||||
// Actions and Payloads
|
||||
@@ -50,90 +51,43 @@ export const maximizePaneAction = createAction<{
|
||||
export const evenPaneResizeAction = createAction('explore/evenPaneResizeAction');
|
||||
|
||||
/**
|
||||
* Resets state for explore.
|
||||
* Close the pane with the given id.
|
||||
*/
|
||||
export const resetExploreAction = createAction('explore/resetExplore');
|
||||
type SplitCloseActionPayload = ExploreId;
|
||||
export const splitClose = createAction<SplitCloseActionPayload>('explore/splitClose');
|
||||
|
||||
/**
|
||||
* Close the split view and save URL state.
|
||||
*/
|
||||
export interface SplitCloseActionPayload {
|
||||
itemId: ExploreId;
|
||||
export interface SetPaneStateActionPayload {
|
||||
[itemId: string]: Partial<ExploreItemState>;
|
||||
}
|
||||
export const splitCloseAction = createAction<SplitCloseActionPayload>('explore/splitClose');
|
||||
export const setPaneState = createAction<SetPaneStateActionPayload>('explore/setPaneState');
|
||||
|
||||
//
|
||||
// Action creators
|
||||
//
|
||||
export const clearPanes = createAction('explore/clearPanes');
|
||||
|
||||
/**
|
||||
* Save local redux state back to the URL. Should be called when there is some change that should affect the URL.
|
||||
* Not all of the redux state is reflected in URL though.
|
||||
*/
|
||||
export const stateSave = (options?: { replace?: boolean }): ThunkResult<void> => {
|
||||
return (_, getState) => {
|
||||
const { left, right } = getState().explore.panes;
|
||||
const orgId = getState().user.orgId.toString();
|
||||
const urlStates: { [index: string]: string | null } = { orgId };
|
||||
|
||||
urlStates.left = serializeStateToUrlParam(getUrlStateFromPaneState(left!));
|
||||
|
||||
if (right) {
|
||||
urlStates.right = serializeStateToUrlParam(getUrlStateFromPaneState(right));
|
||||
} else {
|
||||
urlStates.right = null;
|
||||
}
|
||||
|
||||
lastSavedUrl.right = urlStates.right;
|
||||
lastSavedUrl.left = urlStates.left;
|
||||
|
||||
locationService.partial({ ...urlStates }, options?.replace);
|
||||
};
|
||||
};
|
||||
|
||||
// Store the url we saved last se we are not trying to update local state based on that.
|
||||
export const lastSavedUrl: UrlQueryMap = {};
|
||||
|
||||
/**
|
||||
* Opens a new right split pane by navigating to appropriate URL. It either copies existing state of the left pane
|
||||
* or uses values from options arg. This does only navigation each pane is then responsible for initialization from
|
||||
* the URL.
|
||||
* Opens a new split pane. It either copies existing state of the left pane
|
||||
* or uses values from options arg.
|
||||
*
|
||||
* TODO: this can be improved by better inferring fallback values.
|
||||
*/
|
||||
export const splitOpen = createAsyncThunk(
|
||||
'explore/splitOpen',
|
||||
async (options: SplitOpenOptions | undefined, { getState }) => {
|
||||
const leftState: ExploreItemState = getState().explore.panes.left!;
|
||||
const leftUrlState = getUrlStateFromPaneState(leftState);
|
||||
let rightUrlState: ExploreUrlState = leftUrlState;
|
||||
async (options: SplitOpenOptions | undefined, { getState, dispatch }) => {
|
||||
const leftState = getState().explore.panes.left;
|
||||
|
||||
if (options) {
|
||||
const { query, queries } = options;
|
||||
const queries = options?.queries ?? (options?.query ? [options?.query] : leftState?.queries || []);
|
||||
|
||||
rightUrlState = {
|
||||
datasource: options.datasourceUid,
|
||||
queries: queries ?? (query ? [query] : []),
|
||||
range: options.range || leftState.range,
|
||||
panelsState: options.panelsState,
|
||||
};
|
||||
}
|
||||
|
||||
const urlState = serializeStateToUrlParam(rightUrlState);
|
||||
locationService.partial({ right: urlState }, true);
|
||||
await dispatch(
|
||||
initializeExplore({
|
||||
exploreId: ExploreId.right,
|
||||
datasource: options?.datasourceUid || leftState?.datasourceInstance?.getRef(),
|
||||
queries: withUniqueRefIds(queries),
|
||||
range: options?.range || leftState?.range.raw || DEFAULT_RANGE,
|
||||
panelsState: options?.panelsState || leftState?.panelsState,
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Close the split view and save URL state. We need to update the state here because when closing we cannot just
|
||||
* update the URL and let the components handle it because if we swap panes from right to left it is not easily apparent
|
||||
* from the URL.
|
||||
*/
|
||||
export function splitClose(itemId: ExploreId): ThunkResult<void> {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(splitCloseAction({ itemId }));
|
||||
dispatch(stateSave());
|
||||
};
|
||||
}
|
||||
|
||||
export interface NavigateToExploreDependencies {
|
||||
getDataSourceSrv: () => DataSourceSrv;
|
||||
getTimeSrv: () => TimeSrv;
|
||||
@@ -169,9 +123,7 @@ export const navigateToExplore = (
|
||||
const initialExploreItemState = makeExplorePaneState();
|
||||
export const initialExploreState: ExploreState = {
|
||||
syncedTimes: false,
|
||||
panes: {
|
||||
[ExploreId.left]: initialExploreItemState,
|
||||
},
|
||||
panes: {},
|
||||
correlations: undefined,
|
||||
richHistoryStorageFull: false,
|
||||
richHistoryLimitExceededWarningShown: false,
|
||||
@@ -185,10 +137,9 @@ export const initialExploreState: ExploreState = {
|
||||
* Actions that have an `exploreId` get routed to the ExploreItemReducer.
|
||||
*/
|
||||
export const exploreReducer = (state = initialExploreState, action: AnyAction): ExploreState => {
|
||||
if (splitCloseAction.match(action)) {
|
||||
const { itemId } = action.payload;
|
||||
if (splitClose.match(action)) {
|
||||
const panes = {
|
||||
left: itemId === ExploreId.left ? state.panes.right : state.panes.left,
|
||||
left: action.payload === ExploreId.left ? state.panes.right : state.panes.left,
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
@@ -254,23 +205,6 @@ export const exploreReducer = (state = initialExploreState, action: AnyAction):
|
||||
};
|
||||
}
|
||||
|
||||
if (resetExploreAction.match(action)) {
|
||||
// FIXME: reducers should REALLY not have side effects.
|
||||
for (const [, pane] of Object.entries(state.panes).filter(([exploreId]) => exploreId !== ExploreId.left)) {
|
||||
stopQueryState(pane!.querySubscription);
|
||||
}
|
||||
|
||||
return {
|
||||
...initialExploreState,
|
||||
panes: {
|
||||
left: {
|
||||
...initialExploreItemState,
|
||||
queries: state.panes.left!.queries,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (richHistorySettingsUpdatedAction.match(action)) {
|
||||
const richHistorySettings = action.payload;
|
||||
return {
|
||||
@@ -299,21 +233,26 @@ export const exploreReducer = (state = initialExploreState, action: AnyAction):
|
||||
};
|
||||
}
|
||||
|
||||
if (action.payload) {
|
||||
const { exploreId } = action.payload;
|
||||
if (exploreId !== undefined) {
|
||||
return {
|
||||
...state,
|
||||
panes: Object.entries(state.panes).reduce<ExploreState['panes']>((acc, [id, pane]) => {
|
||||
if (id === exploreId) {
|
||||
acc[id as ExploreId] = paneReducer(pane, action);
|
||||
} else {
|
||||
acc[id as ExploreId] = pane;
|
||||
}
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
}
|
||||
if (clearPanes.match(action)) {
|
||||
return {
|
||||
...state,
|
||||
panes: {},
|
||||
};
|
||||
}
|
||||
|
||||
const exploreId: ExploreId | undefined = action.payload?.exploreId;
|
||||
if (typeof exploreId === 'string') {
|
||||
return {
|
||||
...state,
|
||||
panes: Object.entries(state.panes).reduce<ExploreState['panes']>((acc, [id, pane]) => {
|
||||
if (id === exploreId) {
|
||||
acc[id] = paneReducer(pane, action);
|
||||
} else {
|
||||
acc[id as ExploreId] = pane;
|
||||
}
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
|
Reference in New Issue
Block a user