mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Ensures queries aren't updated when returning to dashboard if browser back is used (#20897)
* Explore: Ensures queries aren't updated when returning to dashboard if browser back is used Closes #20873
This commit is contained in:
parent
43dbbe51f0
commit
cc1d468041
@ -16,6 +16,7 @@ import {
|
||||
PermissionLevel,
|
||||
ThunkResult,
|
||||
} from 'app/types';
|
||||
import { DataQuery } from '@grafana/data';
|
||||
|
||||
export const loadDashboardPermissions = createAction<DashboardAclDTO[]>('dashboard/loadDashboardPermissions');
|
||||
|
||||
@ -53,6 +54,21 @@ function toUpdateItem(item: DashboardAcl): DashboardAclUpdateDTO {
|
||||
};
|
||||
}
|
||||
|
||||
interface SetDashboardQueriesToUpdatePayload {
|
||||
panelId: number;
|
||||
queries: DataQuery[];
|
||||
}
|
||||
|
||||
export const clearDashboardQueriesToUpdate = createAction('dashboard/clearDashboardQueriesToUpdate');
|
||||
export const setDashboardQueriesToUpdate = createAction<SetDashboardQueriesToUpdatePayload>(
|
||||
'dashboard/setDashboardQueriesToUpdate'
|
||||
);
|
||||
export const setDashboardQueriesToUpdateOnLoad = (panelId: number, queries: DataQuery[]): ThunkResult<void> => {
|
||||
return async dispatch => {
|
||||
await dispatch(setDashboardQueriesToUpdate({ panelId, queries }));
|
||||
};
|
||||
};
|
||||
|
||||
export function updateDashboardPermission(
|
||||
dashboardId: number,
|
||||
itemToUpdate: DashboardAcl,
|
||||
|
@ -4,7 +4,6 @@ import { initDashboard, InitDashboardArgs } from './initDashboard';
|
||||
import { DashboardRouteInfo } from 'app/types';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { dashboardInitCompleted, dashboardInitFetching, dashboardInitServices } from './actions';
|
||||
import { resetExploreAction } from 'app/features/explore/state/actionTypes';
|
||||
import { updateLocation } from '../../../core/actions';
|
||||
|
||||
jest.mock('app/core/services/backend_srv');
|
||||
@ -109,6 +108,12 @@ function describeInitScenario(description: string, scenarioFn: ScenarioFn) {
|
||||
location: {
|
||||
query: {},
|
||||
},
|
||||
dashboard: {
|
||||
modifiedQueries: {
|
||||
panelId: undefined,
|
||||
queries: undefined,
|
||||
},
|
||||
},
|
||||
user: {},
|
||||
explore: {
|
||||
left: {
|
||||
@ -201,8 +206,6 @@ describeInitScenario('Initializing existing dashboard', ctx => {
|
||||
},
|
||||
];
|
||||
|
||||
const expectedQueries = mockQueries.map(query => ({ refId: query.refId, expr: query.expr }));
|
||||
|
||||
ctx.setup(() => {
|
||||
ctx.storeState.user.orgId = 12;
|
||||
ctx.storeState.explore.left.originPanelId = 2;
|
||||
@ -222,23 +225,9 @@ describeInitScenario('Initializing existing dashboard', ctx => {
|
||||
expect(ctx.actions[2].payload.query.orgId).toBe(12);
|
||||
});
|
||||
|
||||
it('Should send resetExploreAction when coming from explore', () => {
|
||||
expect(ctx.actions[3].type).toBe(resetExploreAction.type);
|
||||
expect(ctx.actions[3].payload.force).toBe(true);
|
||||
expect(ctx.dashboardSrv.setCurrent).lastCalledWith(
|
||||
expect.objectContaining({
|
||||
panels: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
targets: expectedQueries,
|
||||
}),
|
||||
]),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('Should send action dashboardInitCompleted', () => {
|
||||
expect(ctx.actions[4].type).toBe(dashboardInitCompleted.type);
|
||||
expect(ctx.actions[4].payload.title).toBe('My cool dashboard');
|
||||
expect(ctx.actions[3].type).toBe(dashboardInitCompleted.type);
|
||||
expect(ctx.actions[3].payload.title).toBe('My cool dashboard');
|
||||
});
|
||||
|
||||
it('Should initialize services', () => {
|
||||
|
@ -18,12 +18,12 @@ import {
|
||||
dashboardInitFailed,
|
||||
dashboardInitSlow,
|
||||
dashboardInitServices,
|
||||
clearDashboardQueriesToUpdate,
|
||||
} from './actions';
|
||||
|
||||
// Types
|
||||
import { DashboardRouteInfo, StoreState, ThunkDispatch, ThunkResult, DashboardDTO, ExploreItemState } from 'app/types';
|
||||
import { DashboardRouteInfo, StoreState, ThunkDispatch, ThunkResult, DashboardDTO } from 'app/types';
|
||||
import { DashboardModel } from './DashboardModel';
|
||||
import { resetExploreAction } from 'app/features/explore/state/actionTypes';
|
||||
import { DataQuery } from '@grafana/data';
|
||||
|
||||
export interface InitDashboardArgs {
|
||||
@ -173,8 +173,8 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult<void> {
|
||||
timeSrv.init(dashboard);
|
||||
annotationsSrv.init(dashboard);
|
||||
|
||||
const left = storeState.explore && storeState.explore.left;
|
||||
dashboard.meta.fromExplore = !!(left && left.originPanelId);
|
||||
const { panelId, queries } = storeState.dashboard.modifiedQueries;
|
||||
dashboard.meta.fromExplore = !!(panelId && queries);
|
||||
|
||||
// template values service needs to initialize completely before
|
||||
// the rest of the dashboard can load
|
||||
@ -204,7 +204,7 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult<void> {
|
||||
}
|
||||
|
||||
if (dashboard.meta.fromExplore) {
|
||||
updateQueriesWhenComingFromExplore(dispatch, dashboard, left);
|
||||
updateQueriesWhenComingFromExplore(dispatch, dashboard, panelId, queries);
|
||||
}
|
||||
|
||||
// legacy srv state
|
||||
@ -245,24 +245,15 @@ function getNewDashboardModelData(urlFolderId?: string): any {
|
||||
function updateQueriesWhenComingFromExplore(
|
||||
dispatch: ThunkDispatch,
|
||||
dashboard: DashboardModel,
|
||||
left: ExploreItemState
|
||||
originPanelId: number,
|
||||
queries: DataQuery[]
|
||||
) {
|
||||
// When returning to the origin panel from explore, if we're doing
|
||||
// so with changes all the explore state is reset _except_ the queries
|
||||
// and the origin panel ID.
|
||||
const panelArrId = dashboard.panels.findIndex(panel => panel.id === left.originPanelId);
|
||||
const panelArrId = dashboard.panels.findIndex(panel => panel.id === originPanelId);
|
||||
|
||||
if (panelArrId > -1) {
|
||||
dashboard.panels[panelArrId].targets = left.queries.map((query: DataQuery & { context?: string }) => {
|
||||
delete query.context;
|
||||
delete query.key;
|
||||
return query;
|
||||
});
|
||||
dashboard.panels[panelArrId].targets = queries;
|
||||
}
|
||||
|
||||
dashboard.startRefresh();
|
||||
|
||||
// Force-reset explore so that on subsequent dashboard loads we aren't
|
||||
// taking the modified queries from explore again.
|
||||
dispatch(resetExploreAction({ force: true }));
|
||||
// Clear update state now that we're done
|
||||
dispatch(clearDashboardQueriesToUpdate());
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ import {
|
||||
dashboardInitServices,
|
||||
dashboardInitSlow,
|
||||
loadDashboardPermissions,
|
||||
setDashboardQueriesToUpdate,
|
||||
clearDashboardQueriesToUpdate,
|
||||
} from './actions';
|
||||
import { processAclItems } from 'app/core/utils/acl';
|
||||
import { panelEditorReducer } from '../panel_editor/state/reducers';
|
||||
@ -18,6 +20,10 @@ export const initialState: DashboardState = {
|
||||
isInitSlow: false,
|
||||
model: null,
|
||||
permissions: [],
|
||||
modifiedQueries: {
|
||||
panelId: undefined,
|
||||
queries: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
// Redux Toolkit uses ImmerJs as part of their solution to ensure that state objects are not mutated.
|
||||
@ -87,6 +93,28 @@ export const dashboardReducer = (state: DashboardState = initialState, action: A
|
||||
};
|
||||
}
|
||||
|
||||
if (setDashboardQueriesToUpdate.match(action)) {
|
||||
const { panelId, queries } = action.payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
modifiedQueries: {
|
||||
panelId,
|
||||
queries,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (clearDashboardQueriesToUpdate.match(action)) {
|
||||
return {
|
||||
...state,
|
||||
modifiedQueries: {
|
||||
panelId: undefined,
|
||||
queries: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
|
@ -20,7 +20,6 @@ import {
|
||||
syncTimes,
|
||||
changeRefreshInterval,
|
||||
changeMode,
|
||||
clearOrigin,
|
||||
} from './state/actions';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
import { getTimeZone } from '../profile/state/selectors';
|
||||
@ -32,6 +31,7 @@ import { ResponsiveButton } from './ResponsiveButton';
|
||||
import { RunButton } from './RunButton';
|
||||
import { LiveTailControls } from './useLiveTailControls';
|
||||
import { getExploreDatasources } from './state/selectors';
|
||||
import { setDashboardQueriesToUpdateOnLoad } from '../dashboard/state/actions';
|
||||
|
||||
const getStyles = memoizeOne(() => {
|
||||
return {
|
||||
@ -78,8 +78,8 @@ interface DispatchProps {
|
||||
syncTimes: typeof syncTimes;
|
||||
changeRefreshInterval: typeof changeRefreshInterval;
|
||||
changeMode: typeof changeMode;
|
||||
clearOrigin: typeof clearOrigin;
|
||||
updateLocation: typeof updateLocation;
|
||||
setDashboardQueriesToUpdateOnLoad: typeof setDashboardQueriesToUpdateOnLoad;
|
||||
}
|
||||
|
||||
type Props = StateProps & DispatchProps & OwnProps;
|
||||
@ -113,14 +113,14 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
|
||||
};
|
||||
|
||||
returnToPanel = async ({ withChanges = false } = {}) => {
|
||||
const { originPanelId } = this.props;
|
||||
const { originPanelId, queries } = this.props;
|
||||
|
||||
const dashboardSrv = getDashboardSrv();
|
||||
const dash = dashboardSrv.getCurrent();
|
||||
const titleSlug = kbn.slugifyForUrl(dash.title);
|
||||
|
||||
if (!withChanges) {
|
||||
this.props.clearOrigin();
|
||||
if (withChanges) {
|
||||
this.props.setDashboardQueriesToUpdateOnLoad(originPanelId, this.cleanQueries(queries));
|
||||
}
|
||||
|
||||
const dashViewOptions = {
|
||||
@ -137,6 +137,15 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
|
||||
});
|
||||
};
|
||||
|
||||
// Remove explore specific parameters from queries
|
||||
private cleanQueries(queries: DataQuery[]) {
|
||||
return queries.map((query: DataQuery & { context?: string }) => {
|
||||
delete query.context;
|
||||
delete query.key;
|
||||
return query;
|
||||
});
|
||||
}
|
||||
|
||||
getSelectedDatasource = () => {
|
||||
const { datasourceName } = this.props;
|
||||
const exploreDatasources = getExploreDatasources();
|
||||
@ -381,7 +390,7 @@ const mapDispatchToProps: DispatchProps = {
|
||||
split: splitOpen,
|
||||
syncTimes,
|
||||
changeMode: changeMode,
|
||||
clearOrigin,
|
||||
setDashboardQueriesToUpdateOnLoad,
|
||||
};
|
||||
|
||||
export const ExploreToolbar = hot(module)(connect(mapStateToProps, mapDispatchToProps)(UnConnectedExploreToolbar));
|
||||
|
@ -49,10 +49,6 @@ export interface ClearQueriesPayload {
|
||||
exploreId: ExploreId;
|
||||
}
|
||||
|
||||
export interface ClearOriginPayload {
|
||||
exploreId: ExploreId;
|
||||
}
|
||||
|
||||
export interface HighlightLogsExpressionPayload {
|
||||
exploreId: ExploreId;
|
||||
expressions: string[];
|
||||
@ -220,11 +216,6 @@ export const changeRefreshIntervalAction = createAction<ChangeRefreshIntervalPay
|
||||
*/
|
||||
export const clearQueriesAction = createAction<ClearQueriesPayload>('explore/clearQueries');
|
||||
|
||||
/**
|
||||
* Clear origin panel id.
|
||||
*/
|
||||
export const clearOriginAction = createAction<ClearOriginPayload>('explore/clearOrigin');
|
||||
|
||||
/**
|
||||
* Highlight expressions in the log results
|
||||
*/
|
||||
|
@ -50,7 +50,6 @@ import {
|
||||
ChangeRefreshIntervalPayload,
|
||||
changeSizeAction,
|
||||
ChangeSizePayload,
|
||||
clearOriginAction,
|
||||
clearQueriesAction,
|
||||
historyUpdatedAction,
|
||||
initializeExploreAction,
|
||||
@ -232,12 +231,6 @@ export function clearQueries(exploreId: ExploreId): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
export function clearOrigin(): ThunkResult<void> {
|
||||
return dispatch => {
|
||||
dispatch(clearOriginAction({ exploreId: ExploreId.left }));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all explore data sources and sets the chosen datasource.
|
||||
* If there are no datasources a missing datasource action is dispatched.
|
||||
|
@ -34,7 +34,6 @@ import {
|
||||
changeRangeAction,
|
||||
changeRefreshIntervalAction,
|
||||
changeSizeAction,
|
||||
clearOriginAction,
|
||||
clearQueriesAction,
|
||||
highlightLogsExpressionAction,
|
||||
historyUpdatedAction,
|
||||
@ -234,13 +233,6 @@ export const itemReducer = (state: ExploreItemState = makeExploreItemState(), ac
|
||||
};
|
||||
}
|
||||
|
||||
if (clearOriginAction.match(action)) {
|
||||
return {
|
||||
...state,
|
||||
originPanelId: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (highlightLogsExpressionAction.match(action)) {
|
||||
const { expressions } = action.payload;
|
||||
return { ...state, logsHighlighterExpressions: expressions };
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { DashboardAcl } from './acl';
|
||||
import { DataQuery } from '@grafana/data';
|
||||
|
||||
export interface MutableDashboard {
|
||||
title: string;
|
||||
@ -71,4 +72,8 @@ export interface DashboardState {
|
||||
isInitSlow: boolean;
|
||||
initError?: DashboardInitError;
|
||||
permissions: DashboardAcl[] | null;
|
||||
modifiedQueries?: {
|
||||
panelId: number;
|
||||
queries: DataQuery[];
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user