Commit 2e077f28 authored by Mark Florian's avatar Mark Florian Committed by Phil Hughes

Persist vulnerabilities page number in GSD URL

This follows straight-forwardly from [previous work][1] to persist some
state of the Group Security Dashboard to the URL, and vice versa.

[1]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9108
parent 9b7b1493
...@@ -47,7 +47,7 @@ export default { ...@@ -47,7 +47,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapState('vulnerabilities', ['modal']), ...mapState('vulnerabilities', ['modal', 'pageInfo']),
...mapState('projects', ['projects']), ...mapState('projects', ['projects']),
...mapGetters('filters', ['activeFilters']), ...mapGetters('filters', ['activeFilters']),
canCreateIssuePermission() { canCreateIssuePermission() {
...@@ -67,7 +67,7 @@ export default { ...@@ -67,7 +67,7 @@ export default {
this.setVulnerabilitiesEndpoint(this.vulnerabilitiesEndpoint); this.setVulnerabilitiesEndpoint(this.vulnerabilitiesEndpoint);
this.setVulnerabilitiesCountEndpoint(this.vulnerabilitiesCountEndpoint); this.setVulnerabilitiesCountEndpoint(this.vulnerabilitiesCountEndpoint);
this.setVulnerabilitiesHistoryEndpoint(this.vulnerabilitiesHistoryEndpoint); this.setVulnerabilitiesHistoryEndpoint(this.vulnerabilitiesHistoryEndpoint);
this.fetchVulnerabilities(this.activeFilters); this.fetchVulnerabilities({ ...this.activeFilters, page: this.pageInfo.page });
this.fetchVulnerabilitiesCount(this.activeFilters); this.fetchVulnerabilitiesCount(this.activeFilters);
this.fetchVulnerabilitiesHistory(this.activeFilters); this.fetchVulnerabilitiesHistory(this.activeFilters);
this.fetchProjects(); this.fetchProjects();
......
...@@ -2,6 +2,7 @@ import Vue from 'vue'; ...@@ -2,6 +2,7 @@ import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import router from './router'; import router from './router';
import configureModerator from './moderator'; import configureModerator from './moderator';
import syncWithRouter from './sync_with_router';
import filters from './modules/filters/index'; import filters from './modules/filters/index';
import projects from './modules/projects/index'; import projects from './modules/projects/index';
import vulnerabilities from './modules/vulnerabilities/index'; import vulnerabilities from './modules/vulnerabilities/index';
...@@ -15,11 +16,10 @@ export default () => { ...@@ -15,11 +16,10 @@ export default () => {
projects, projects,
vulnerabilities, vulnerabilities,
}, },
plugins: [configureModerator, syncWithRouter(router)],
}); });
store.$router = router; store.$router = router;
configureModerator(store);
return store; return store;
}; };
import * as vulnerabilitiesMutationTypes from './modules/vulnerabilities/mutation_types';
import * as filtersMutationTypes from './modules/filters/mutation_types'; import * as filtersMutationTypes from './modules/filters/mutation_types';
import * as projectsMutationTypes from './modules/projects/mutation_types'; import * as projectsMutationTypes from './modules/projects/mutation_types';
import { BASE_FILTERS } from './modules/filters/constants'; import { BASE_FILTERS } from './modules/filters/constants';
export default function configureModerator(store) { export default function configureModerator(store) {
store.$router.beforeEach((to, from, next) => {
const updatedFromState = (to.params && to.params.updatedFromState) || false;
if (to.name === 'dashboard' && !updatedFromState) {
store.dispatch(`filters/setAllFilters`, to.query);
}
next();
});
store.subscribe(({ type, payload }) => { store.subscribe(({ type, payload }) => {
switch (type) { switch (type) {
case `projects/${projectsMutationTypes.RECEIVE_PROJECTS_SUCCESS}`: case `projects/${projectsMutationTypes.RECEIVE_PROJECTS_SUCCESS}`:
...@@ -36,15 +25,6 @@ export default function configureModerator(store) { ...@@ -36,15 +25,6 @@ export default function configureModerator(store) {
store.dispatch('vulnerabilities/fetchVulnerabilitiesHistory', activeFilters); store.dispatch('vulnerabilities/fetchVulnerabilitiesHistory', activeFilters);
break; break;
} }
case `vulnerabilities/${vulnerabilitiesMutationTypes.RECEIVE_VULNERABILITIES_SUCCESS}`: {
const activeFilters = store.getters['filters/activeFilters'];
store.$router.push({
name: 'dashboard',
query: activeFilters,
params: { updatedFromState: true },
});
break;
}
default: default:
} }
}); });
......
...@@ -45,6 +45,10 @@ export const receiveVulnerabilitiesCountError = ({ commit }) => { ...@@ -45,6 +45,10 @@ export const receiveVulnerabilitiesCountError = ({ commit }) => {
commit(types.RECEIVE_VULNERABILITIES_COUNT_ERROR); commit(types.RECEIVE_VULNERABILITIES_COUNT_ERROR);
}; };
export const setVulnerabilitiesPage = ({ commit }, page) => {
commit(types.SET_VULNERABILITIES_PAGE, page);
};
export const fetchVulnerabilities = ({ state, dispatch }, params = {}) => { export const fetchVulnerabilities = ({ state, dispatch }, params = {}) => {
if (!state.vulnerabilitiesEndpoint) { if (!state.vulnerabilitiesEndpoint) {
return; return;
......
export const SET_VULNERABILITIES_ENDPOINT = 'SET_VULNERABILITIES_ENDPOINT'; export const SET_VULNERABILITIES_ENDPOINT = 'SET_VULNERABILITIES_ENDPOINT';
export const SET_VULNERABILITIES_PAGE = 'SET_VULNERABILITIES_PAGE';
export const REQUEST_VULNERABILITIES = 'REQUEST_VULNERABILITIES'; export const REQUEST_VULNERABILITIES = 'REQUEST_VULNERABILITIES';
export const RECEIVE_VULNERABILITIES_SUCCESS = 'RECEIVE_VULNERABILITIES_SUCCESS'; export const RECEIVE_VULNERABILITIES_SUCCESS = 'RECEIVE_VULNERABILITIES_SUCCESS';
export const RECEIVE_VULNERABILITIES_ERROR = 'RECEIVE_VULNERABILITIES_ERROR'; export const RECEIVE_VULNERABILITIES_ERROR = 'RECEIVE_VULNERABILITIES_ERROR';
......
...@@ -24,6 +24,9 @@ export default { ...@@ -24,6 +24,9 @@ export default {
[types.SET_VULNERABILITIES_COUNT_ENDPOINT](state, payload) { [types.SET_VULNERABILITIES_COUNT_ENDPOINT](state, payload) {
state.vulnerabilitiesCountEndpoint = payload; state.vulnerabilitiesCountEndpoint = payload;
}, },
[types.SET_VULNERABILITIES_PAGE](state, payload) {
state.pageInfo = { ...state.pageInfo, page: payload };
},
[types.REQUEST_VULNERABILITIES_COUNT](state) { [types.REQUEST_VULNERABILITIES_COUNT](state) {
state.isLoadingVulnerabilitiesCount = true; state.isLoadingVulnerabilitiesCount = true;
state.errorLoadingVulnerabilitiesCount = false; state.errorLoadingVulnerabilitiesCount = false;
......
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.
*/
export default router => store => {
let syncingRouter = false;
const MUTATION_TYPES = [
`vulnerabilities/${SET_VULNERABILITIES_HISTORY_DAY_RANGE}`,
`vulnerabilities/${RECEIVE_VULNERABILITIES_SUCCESS}`,
];
// Update store from routing events
router.beforeEach((to, from, next) => {
const updatedFromState = (to.params && to.params.updatedFromState) || false;
if (to.name === 'dashboard' && !updatedFromState) {
syncingRouter = true;
store.dispatch(`filters/setAllFilters`, to.query);
const page = parseInt(to.query.page, 10);
if (Number.isFinite(page)) {
store.dispatch(`vulnerabilities/setVulnerabilitiesPage`, page);
}
const dayRange = parseInt(to.query.days, 10);
if (Number.isFinite(dayRange)) {
store.dispatch(`vulnerabilities/setVulnerabilitiesHistoryDayRange`, dayRange);
}
syncingRouter = false;
}
next();
});
// Update router from store mutations
store.subscribe(({ type }) => {
if (!syncingRouter && MUTATION_TYPES.includes(type)) {
const activeFilters = store.getters['filters/activeFilters'];
const { page } = store.state.vulnerabilities.pageInfo;
const days = store.state.vulnerabilities.vulnerabilitiesHistoryDayRange;
store.$router.push({
name: 'dashboard',
query: { ...activeFilters, page, days },
params: { updatedFromState: true },
});
}
});
};
---
title: Persist in the URL the page and day range of vulnerabilities viewed in the
Group Security Dashboard.
merge_request: 10402
author:
type: added
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';
describe('syncWithRouter', () => {
let store;
const noop = () => {};
beforeEach(() => {
store = createStore();
});
it('updates store after URL changes', () => {
const page = 3;
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 });
// Assert no implicit synchronous recursive calls occurred
expect(store.$router.push).not.toHaveBeenCalled();
expect(store.dispatch).toHaveBeenCalledWith(`filters/setAllFilters`, query);
expect(store.dispatch).toHaveBeenCalledWith(`vulnerabilities/setVulnerabilitiesPage`, page);
expect(store.dispatch).toHaveBeenCalledWith(
`vulnerabilities/setVulnerabilitiesHistoryDayRange`,
days,
);
});
it("doesn't update the store if the URL update originated from the moderator", () => {
const query = { example: ['test'] };
jest.spyOn(store, 'commit').mockImplementation(noop);
store.$router.push({ name: 'dashboard', query, params: { updatedFromState: true } });
expect(store.commit).toHaveBeenCalledTimes(0);
});
it('it updates the route after a successful vulnerability retrieval', () => {
const activeFilters = store.getters['filters/activeFilters'];
const page = 2;
jest.spyOn(store.$router, 'push').mockImplementation(noop);
store.commit(
`vulnerabilities/${vulnerabilitiesMutationTypes.RECEIVE_VULNERABILITIES_SUCCESS}`,
{ pageInfo: { page } },
);
expect(store.$router.push).toHaveBeenCalledTimes(1);
expect(store.$router.push).toHaveBeenCalledWith({
name: 'dashboard',
query: expect.objectContaining({ ...activeFilters, page }),
params: { updatedFromState: true },
});
});
it('it updates the route after changing the vulnerability history day range', () => {
const days = DAYS.SIXTY;
jest.spyOn(store.$router, 'push').mockImplementation(noop);
store.commit(
`vulnerabilities/${vulnerabilitiesMutationTypes.SET_VULNERABILITIES_HISTORY_DAY_RANGE}`,
days,
);
expect(store.$router.push).toHaveBeenCalledTimes(1);
expect(store.$router.push).toHaveBeenCalledWith({
name: 'dashboard',
query: expect.objectContaining({ days }),
params: { updatedFromState: true },
});
});
});
...@@ -49,6 +49,7 @@ describe('Security Dashboard Table', () => { ...@@ -49,6 +49,7 @@ describe('Security Dashboard Table', () => {
beforeEach(() => { beforeEach(() => {
store.commit(`vulnerabilities/${RECEIVE_VULNERABILITIES_SUCCESS}`, { store.commit(`vulnerabilities/${RECEIVE_VULNERABILITIES_SUCCESS}`, {
vulnerabilities: mockDataVulnerabilities, vulnerabilities: mockDataVulnerabilities,
pageInfo: {},
}); });
vm = mountComponentWithStore(Component, { store, props }); vm = mountComponentWithStore(Component, { store, props });
}); });
...@@ -62,7 +63,10 @@ describe('Security Dashboard Table', () => { ...@@ -62,7 +63,10 @@ describe('Security Dashboard Table', () => {
describe('with no vulnerabilties', () => { describe('with no vulnerabilties', () => {
beforeEach(() => { beforeEach(() => {
store.commit(`vulnerabilities/${RECEIVE_VULNERABILITIES_SUCCESS}`, { vulnerabilities: [] }); store.commit(`vulnerabilities/${RECEIVE_VULNERABILITIES_SUCCESS}`, {
vulnerabilities: [],
pageInfo: {},
});
vm = mountComponentWithStore(Component, { store, props }); vm = mountComponentWithStore(Component, { store, props });
}); });
......
import createStore from 'ee/security_dashboard/store/index'; import createStore from 'ee/security_dashboard/store/index';
import * as projectsMutationTypes from 'ee/security_dashboard/store/modules/projects/mutation_types'; import * as projectsMutationTypes from 'ee/security_dashboard/store/modules/projects/mutation_types';
import * as filtersMutationTypes from 'ee/security_dashboard/store/modules/filters/mutation_types'; import * as filtersMutationTypes from 'ee/security_dashboard/store/modules/filters/mutation_types';
import * as vulnerabilitiesMutationTypes from 'ee/security_dashboard/store/modules/vulnerabilities/mutation_types';
import { BASE_FILTERS } from 'ee/security_dashboard/store/modules/filters/constants'; import { BASE_FILTERS } from 'ee/security_dashboard/store/modules/filters/constants';
describe('moderator', () => { describe('moderator', () => {
...@@ -75,45 +74,4 @@ describe('moderator', () => { ...@@ -75,45 +74,4 @@ describe('moderator', () => {
activeFilters, activeFilters,
); );
}); });
describe('routing', () => {
it('updates store after URL changes', () => {
const query = { example: ['test'] };
spyOn(store, 'dispatch');
store.$router.push({ name: 'dashboard', query });
expect(store.dispatch).toHaveBeenCalledTimes(1);
expect(store.dispatch).toHaveBeenCalledWith(`filters/setAllFilters`, query);
});
it("doesn't update the store if the URL update originated from the moderator", () => {
const query = { example: ['test'] };
spyOn(store, 'commit');
store.$router.push({ name: 'dashboard', query, params: { updatedFromState: true } });
expect(store.commit).toHaveBeenCalledTimes(0);
});
it('it updates the route after a successful vulnerability retrieval', () => {
const activeFilters = store.getters['filters/activeFilters'];
spyOn(store.$router, 'push');
store.commit(
`vulnerabilities/${vulnerabilitiesMutationTypes.RECEIVE_VULNERABILITIES_SUCCESS}`,
{},
);
expect(store.$router.push).toHaveBeenCalledTimes(1);
expect(store.$router.push).toHaveBeenCalledWith({
name: 'dashboard',
query: activeFilters,
params: { updatedFromState: true },
});
});
});
}); });
...@@ -325,6 +325,27 @@ describe('vulnerabilities actions', () => { ...@@ -325,6 +325,27 @@ describe('vulnerabilities actions', () => {
); );
}); });
}); });
describe('setVulnerabilitiesPage', () => {
it('should commit the correct mutuation', done => {
const state = initialState;
const page = 3;
testAction(
actions.setVulnerabilitiesPage,
page,
state,
[
{
type: types.SET_VULNERABILITIES_PAGE,
payload: page,
},
],
[],
done,
);
});
});
}); });
describe('openModal', () => { describe('openModal', () => {
......
...@@ -16,6 +16,17 @@ describe('vulnerabilities module mutations', () => { ...@@ -16,6 +16,17 @@ describe('vulnerabilities module mutations', () => {
}); });
}); });
describe('SET_VULNERABILITIES_PAGE', () => {
const page = 3;
it(`should set pageInfo.page to ${page}`, () => {
const state = createState();
mutations[types.SET_VULNERABILITIES_PAGE](state, page);
expect(state.pageInfo.page).toEqual(page);
});
});
describe('REQUEST_VULNERABILITIES', () => { describe('REQUEST_VULNERABILITIES', () => {
let state; let state;
......
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