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