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:
kay delaney 2020-01-16 11:44:05 +00:00 committed by GitHub
parent 43dbbe51f0
commit cc1d468041
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 83 additions and 69 deletions

View File

@ -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,

View File

@ -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', () => {

View File

@ -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());
}

View File

@ -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;
};

View File

@ -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));

View File

@ -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
*/

View File

@ -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.

View File

@ -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 };

View File

@ -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[];
};
}