Commit 7090ff1c authored by Clement Ho's avatar Clement Ho

Merge branch '11412-fix-security-dashboard-history-bug' into 'master'

Fix routing bugs in security dashboards

See merge request gitlab-org/gitlab!16738
parents 422de1b2 12698d26
---
title: Fix routing bugs in security dashboards
merge_request: 16738
author:
type: fixed
import Vue from 'vue'; import Vue from 'vue';
import createStore from 'ee/security_dashboard/store'; import createStore from 'ee/security_dashboard/store';
import router from 'ee/security_dashboard/store/router';
import SecurityReportApp from 'ee/vue_shared/security_reports/card_security_reports_app.vue'; import SecurityReportApp from 'ee/vue_shared/security_reports/card_security_reports_app.vue';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
...@@ -75,6 +76,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -75,6 +76,7 @@ document.addEventListener('DOMContentLoaded', () => {
return new Vue({ return new Vue({
el: securityTab, el: securityTab,
store, store,
router,
components: { components: {
SecurityReportApp, SecurityReportApp,
}, },
......
import * as filtersMutationTypes from '../modules/filters/mutation_types'; import * as filtersMutationTypes from '../modules/filters/mutation_types';
export default store => { export default store => {
const refreshVulnerabilities = payload => {
store.dispatch('vulnerabilities/fetchVulnerabilities', payload);
store.dispatch('vulnerabilities/fetchVulnerabilitiesCount', payload);
store.dispatch('vulnerabilities/fetchVulnerabilitiesHistory', payload);
};
store.subscribe(({ type }) => { store.subscribe(({ type }) => {
switch (type) { switch (type) {
// SET_ALL_FILTERS mutations are triggered by navigation events, in such case we
// want to preserve the page number that was set in the sync_with_router plugin
case `filters/${filtersMutationTypes.SET_ALL_FILTERS}`: case `filters/${filtersMutationTypes.SET_ALL_FILTERS}`:
refreshVulnerabilities({
...store.getters['filters/activeFilters'],
page: store.state.vulnerabilities.pageInfo.page,
});
break;
// SET_FILTER and SET_TOGGLE_VALUE mutations happen when users interact with the UI,
// in that case we want to reset the page number
case `filters/${filtersMutationTypes.SET_FILTER}`: case `filters/${filtersMutationTypes.SET_FILTER}`:
case `filters/${filtersMutationTypes.SET_TOGGLE_VALUE}`: { case `filters/${filtersMutationTypes.SET_TOGGLE_VALUE}`: {
const activeFilters = store.getters['filters/activeFilters']; refreshVulnerabilities(store.getters['filters/activeFilters']);
store.dispatch('vulnerabilities/fetchVulnerabilities', activeFilters);
store.dispatch('vulnerabilities/fetchVulnerabilitiesCount', activeFilters);
store.dispatch('vulnerabilities/fetchVulnerabilitiesHistory', activeFilters);
break; break;
} }
default: default:
......
import {
SET_VULNERABILITIES_HISTORY_DAY_RANGE,
RECEIVE_VULNERABILITIES_SUCCESS,
} from '../modules/vulnerabilities/mutation_types';
/** /**
* Vuex store plugin to sync some Group Security Dashboard view settings with the URL. * Vuex store plugin to sync some Group Security Dashboard view settings with the URL.
*/ */
export default router => store => { export default router => store => {
let syncingRouter = false; let syncingRouter = false;
const MUTATION_TYPES = [
`vulnerabilities/${SET_VULNERABILITIES_HISTORY_DAY_RANGE}`,
`vulnerabilities/${RECEIVE_VULNERABILITIES_SUCCESS}`,
];
// Update store from routing events // Update store from routing events
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
...@@ -19,15 +10,13 @@ export default router => store => { ...@@ -19,15 +10,13 @@ export default router => store => {
if (to.name === 'dashboard' && !updatedFromState) { if (to.name === 'dashboard' && !updatedFromState) {
syncingRouter = true; syncingRouter = true;
store.dispatch(`filters/setAllFilters`, to.query); const page = parseInt(to.query.page, 10) || 1;
const page = parseInt(to.query.page, 10); store.dispatch(`vulnerabilities/setVulnerabilitiesPage`, page);
if (Number.isFinite(page)) {
store.dispatch(`vulnerabilities/setVulnerabilitiesPage`, page);
}
const dayRange = parseInt(to.query.days, 10); const dayRange = parseInt(to.query.days, 10);
if (Number.isFinite(dayRange)) { if (Number.isFinite(dayRange)) {
store.dispatch(`vulnerabilities/setVulnerabilitiesHistoryDayRange`, dayRange); store.dispatch(`vulnerabilities/setVulnerabilitiesHistoryDayRange`, dayRange);
} }
store.dispatch(`filters/setAllFilters`, to.query);
syncingRouter = false; syncingRouter = false;
} }
...@@ -35,16 +24,36 @@ export default router => store => { ...@@ -35,16 +24,36 @@ export default router => store => {
}); });
// Update router from store mutations // Update router from store mutations
store.subscribe(({ type }) => { const updateRouter = (queryParams = {}) => {
if (!syncingRouter && MUTATION_TYPES.includes(type)) { const activeFilters = store.getters['filters/activeFilters'];
const activeFilters = store.getters['filters/activeFilters']; const routePayload = {
const { page } = store.state.vulnerabilities.pageInfo; name: 'dashboard',
const days = store.state.vulnerabilities.vulnerabilitiesHistoryDayRange; query: {
store.$router.push({ ...activeFilters,
name: 'dashboard', page: store.state.vulnerabilities.pageInfo.page,
query: { ...activeFilters, page, days }, days: store.state.vulnerabilities.vulnerabilitiesHistoryDayRange,
params: { updatedFromState: true }, ...queryParams,
}); },
params: { updatedFromState: true },
};
const resolvedRoute = router.resolve(routePayload);
if (resolvedRoute.route.fullPath !== router.currentRoute.fullPath) {
router.push(routePayload);
}
};
store.subscribeAction(({ type, payload }) => {
if (syncingRouter) {
return;
}
switch (type) {
case `vulnerabilities/fetchVulnerabilities`:
updateRouter({ page: payload.page });
break;
case `vulnerabilities/setVulnerabilitiesHistoryDayRange`:
updateRouter({ days: payload });
break;
default:
} }
}); });
}; };
import createStore from 'ee/security_dashboard/store/index'; import createStore from 'ee/security_dashboard/store/index';
import * as vulnerabilitiesMutationTypes from 'ee/security_dashboard/store/modules/vulnerabilities/mutation_types';
import { DAYS } from 'ee/security_dashboard/store/modules/vulnerabilities/constants'; import { DAYS } from 'ee/security_dashboard/store/modules/vulnerabilities/constants';
describe('syncWithRouter', () => { describe('syncWithRouter', () => {
...@@ -32,6 +31,19 @@ describe('syncWithRouter', () => { ...@@ -32,6 +31,19 @@ describe('syncWithRouter', () => {
); );
}); });
it('sets page to 1 if query query string does not contain a page param after URL changes', () => {
const page = undefined;
const days = DAYS.SIXTY;
const query = { example: ['test'], page, days };
jest.spyOn(store, 'dispatch');
const routerPush = store.$router.push.bind(store.$router);
jest.spyOn(store.$router, 'push');
routerPush({ name: 'dashboard', query });
expect(store.dispatch).toHaveBeenCalledWith(`vulnerabilities/setVulnerabilitiesPage`, 1);
});
it("doesn't update the store if the URL update originated from the mediator", () => { it("doesn't update the store if the URL update originated from the mediator", () => {
const query = { example: ['test'] }; const query = { example: ['test'] };
...@@ -48,34 +60,41 @@ describe('syncWithRouter', () => { ...@@ -48,34 +60,41 @@ describe('syncWithRouter', () => {
jest.spyOn(store.$router, 'push').mockImplementation(noop); jest.spyOn(store.$router, 'push').mockImplementation(noop);
store.commit( store.dispatch(`vulnerabilities/fetchVulnerabilities`, { page });
`vulnerabilities/${vulnerabilitiesMutationTypes.RECEIVE_VULNERABILITIES_SUCCESS}`,
{ pageInfo: { page } },
);
expect(store.$router.push).toHaveBeenCalledTimes(1); expect(store.$router.push).toHaveBeenCalledTimes(1);
expect(store.$router.push).toHaveBeenCalledWith({ expect(store.$router.push).toHaveBeenCalledWith(
name: 'dashboard', expect.objectContaining({
query: expect.objectContaining({ ...activeFilters, page }), name: 'dashboard',
params: { updatedFromState: true }, query: expect.objectContaining({
}); ...activeFilters,
page,
days: store.state.vulnerabilities.vulnerabilitiesHistoryDayRange,
}),
params: { updatedFromState: true },
}),
);
}); });
it('it updates the route after changing the vulnerability history day range', () => { it('it updates the route after changing the vulnerability history day range', () => {
const activeFilters = store.getters['filters/activeFilters'];
const days = DAYS.SIXTY; const days = DAYS.SIXTY;
jest.spyOn(store.$router, 'push').mockImplementation(noop); jest.spyOn(store.$router, 'push').mockImplementation(noop);
store.commit( store.dispatch(`vulnerabilities/setVulnerabilitiesHistoryDayRange`, days);
`vulnerabilities/${vulnerabilitiesMutationTypes.SET_VULNERABILITIES_HISTORY_DAY_RANGE}`,
days,
);
expect(store.$router.push).toHaveBeenCalledTimes(1); expect(store.$router.push).toHaveBeenCalledTimes(1);
expect(store.$router.push).toHaveBeenCalledWith({ expect(store.$router.push).toHaveBeenCalledWith(
name: 'dashboard', expect.objectContaining({
query: expect.objectContaining({ days }), name: 'dashboard',
params: { updatedFromState: true }, query: expect.objectContaining({
}); ...activeFilters,
page: store.state.vulnerabilities.pageInfo.page,
days,
}),
params: { updatedFromState: true },
}),
);
}); });
}); });
...@@ -6,6 +6,7 @@ import { TEST_HOST } from 'helpers/test_constants'; ...@@ -6,6 +6,7 @@ import { TEST_HOST } from 'helpers/test_constants';
import CardSecurityDashboardApp from 'ee/vue_shared/security_reports/card_security_reports_app.vue'; import CardSecurityDashboardApp from 'ee/vue_shared/security_reports/card_security_reports_app.vue';
import createStore from 'ee/security_dashboard/store'; import createStore from 'ee/security_dashboard/store';
import router from 'ee/security_dashboard/store/router';
import { trimText } from 'helpers/text_helper'; import { trimText } from 'helpers/text_helper';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -24,6 +25,7 @@ describe('Card security reports app', () => { ...@@ -24,6 +25,7 @@ describe('Card security reports app', () => {
wrapper = mount(CardSecurityDashboardApp, { wrapper = mount(CardSecurityDashboardApp, {
localVue, localVue,
store: createStore(), store: createStore(),
router,
sync: false, sync: false,
stubs: ['security-dashboard-table'], stubs: ['security-dashboard-table'],
propsData: { propsData: {
......
...@@ -35,24 +35,24 @@ describe('mediator', () => { ...@@ -35,24 +35,24 @@ describe('mediator', () => {
it('triggers fetching vulnerabilities after filters change', () => { it('triggers fetching vulnerabilities after filters change', () => {
spyOn(store, 'dispatch'); spyOn(store, 'dispatch');
const activeFilters = store.getters['filters/activeFilters']; const payload = {
...store.getters['filters/activeFilters'],
page: store.state.vulnerabilities.pageInfo.page,
};
store.commit(`filters/${filtersMutationTypes.SET_ALL_FILTERS}`, {}); store.commit(`filters/${filtersMutationTypes.SET_ALL_FILTERS}`, {});
expect(store.dispatch).toHaveBeenCalledTimes(3); expect(store.dispatch).toHaveBeenCalledTimes(3);
expect(store.dispatch).toHaveBeenCalledWith( expect(store.dispatch).toHaveBeenCalledWith('vulnerabilities/fetchVulnerabilities', payload);
'vulnerabilities/fetchVulnerabilities',
activeFilters,
);
expect(store.dispatch).toHaveBeenCalledWith( expect(store.dispatch).toHaveBeenCalledWith(
'vulnerabilities/fetchVulnerabilitiesCount', 'vulnerabilities/fetchVulnerabilitiesCount',
activeFilters, payload,
); );
expect(store.dispatch).toHaveBeenCalledWith( expect(store.dispatch).toHaveBeenCalledWith(
'vulnerabilities/fetchVulnerabilitiesHistory', 'vulnerabilities/fetchVulnerabilitiesHistory',
activeFilters, payload,
); );
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment