diff --git a/ee/app/assets/javascripts/security_dashboard/components/app.vue b/ee/app/assets/javascripts/security_dashboard/components/app.vue
index 60d3d10f0e79a411d54c2f7eff26edad9b6f9050..c2f23eb899dd4e991b9e37a32f2d18bda5f596b7 100644
--- a/ee/app/assets/javascripts/security_dashboard/components/app.vue
+++ b/ee/app/assets/javascripts/security_dashboard/components/app.vue
@@ -25,11 +25,6 @@ export default {
type: String,
required: true,
},
- projectsEndpoint: {
- type: String,
- required: false,
- default: null,
- },
vulnerabilitiesEndpoint: {
type: String,
required: true,
@@ -62,7 +57,6 @@ export default {
},
computed: {
...mapState('vulnerabilities', ['modal', 'pageInfo']),
- ...mapState('projects', ['projects']),
...mapGetters('filters', ['activeFilters']),
canCreateIssue() {
const path = this.vulnerability.create_vulnerability_feedback_issue_path;
@@ -106,14 +100,12 @@ export default {
if (this.showHideDismissedToggle) {
this.setHideDismissedToggleInitialState();
}
- this.setProjectsEndpoint(this.projectsEndpoint);
this.setVulnerabilitiesEndpoint(this.vulnerabilitiesEndpoint);
this.setVulnerabilitiesCountEndpoint(this.vulnerabilitiesCountEndpoint);
this.setVulnerabilitiesHistoryEndpoint(this.vulnerabilitiesHistoryEndpoint);
this.fetchVulnerabilities({ ...this.activeFilters, page: this.pageInfo.page });
this.fetchVulnerabilitiesCount(this.activeFilters);
this.fetchVulnerabilitiesHistory(this.activeFilters);
- this.fetchProjects();
},
methods: {
...mapActions('vulnerabilities', [
@@ -136,7 +128,6 @@ export default {
'undoDismiss',
'downloadPatch',
]),
- ...mapActions('projects', ['setProjectsEndpoint', 'fetchProjects']),
...mapActions('filters', ['lockFilter', 'setHideDismissedToggleInitialState']),
emitVulnerabilitiesCountChanged(count) {
this.$emit('vulnerabilitiesCountChanged', count);
diff --git a/ee/app/assets/javascripts/security_dashboard/components/group_security_dashboard.vue b/ee/app/assets/javascripts/security_dashboard/components/group_security_dashboard.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b37d58bd7bf8e72e62f872fadd1fd0bfff6aff66
--- /dev/null
+++ b/ee/app/assets/javascripts/security_dashboard/components/group_security_dashboard.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
diff --git a/ee/app/assets/javascripts/security_dashboard/index.js b/ee/app/assets/javascripts/security_dashboard/index.js
index c8f150d5b526fac5e1ef26d7b80768484e800f96..3e2a664c97d7449579756ba91fbca13e28fa29e9 100644
--- a/ee/app/assets/javascripts/security_dashboard/index.js
+++ b/ee/app/assets/javascripts/security_dashboard/index.js
@@ -1,8 +1,9 @@
import Vue from 'vue';
-import GroupSecurityDashboardApp from './components/app.vue';
+import GroupSecurityDashboardApp from './components/group_security_dashboard.vue';
import UnavailableState from './components/unavailable_state.vue';
import createStore from './store';
import router from './store/router';
+import projectsPlugin from './store/plugins/projects';
export default function() {
const el = document.getElementById('js-group-security-dashboard');
@@ -22,7 +23,7 @@ export default function() {
});
}
- const store = createStore();
+ const store = createStore({ plugins: [projectsPlugin] });
return new Vue({
el,
store,
diff --git a/ee/app/assets/javascripts/security_dashboard/store/index.js b/ee/app/assets/javascripts/security_dashboard/store/index.js
index 2d9e64e3a2e94481aebd6a473d73eacf16490daf..41f8125b50a9a0b0be7aff2ee58d3cef24ca6b73 100644
--- a/ee/app/assets/javascripts/security_dashboard/store/index.js
+++ b/ee/app/assets/javascripts/security_dashboard/store/index.js
@@ -1,22 +1,20 @@
import Vue from 'vue';
import Vuex from 'vuex';
import router from './router';
-import configureModerator from './moderator';
-import syncWithRouter from './sync_with_router';
+import mediator from './plugins/mediator';
+import syncWithRouter from './plugins/sync_with_router';
import filters from './modules/filters/index';
-import projects from './modules/projects/index';
import vulnerabilities from './modules/vulnerabilities/index';
Vue.use(Vuex);
-export default () => {
+export default ({ plugins = [] } = {}) => {
const store = new Vuex.Store({
modules: {
filters,
- projects,
vulnerabilities,
},
- plugins: [configureModerator, syncWithRouter(router)],
+ plugins: [mediator, syncWithRouter(router), ...plugins],
});
store.$router = router;
diff --git a/ee/app/assets/javascripts/security_dashboard/store/moderator.js b/ee/app/assets/javascripts/security_dashboard/store/moderator.js
deleted file mode 100644
index 0328a3907867df5a17f11d7cfe1f9498700c44f4..0000000000000000000000000000000000000000
--- a/ee/app/assets/javascripts/security_dashboard/store/moderator.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import * as filtersMutationTypes from './modules/filters/mutation_types';
-import * as projectsMutationTypes from './modules/projects/mutation_types';
-import { BASE_FILTERS } from './modules/filters/constants';
-
-export default function configureModerator(store) {
- store.subscribe(({ type, payload }) => {
- switch (type) {
- case `projects/${projectsMutationTypes.RECEIVE_PROJECTS_SUCCESS}`:
- store.dispatch('filters/setFilterOptions', {
- filterId: 'project_id',
- options: [
- BASE_FILTERS.project_id,
- ...payload.projects.map(project => ({
- name: project.name,
- id: project.id.toString(),
- })),
- ],
- });
- break;
- case `filters/${filtersMutationTypes.SET_ALL_FILTERS}`:
- case `filters/${filtersMutationTypes.SET_FILTER}`:
- case `filters/${filtersMutationTypes.SET_TOGGLE_VALUE}`: {
- const activeFilters = store.getters['filters/activeFilters'];
- store.dispatch('vulnerabilities/fetchVulnerabilities', activeFilters);
- store.dispatch('vulnerabilities/fetchVulnerabilitiesCount', activeFilters);
- store.dispatch('vulnerabilities/fetchVulnerabilitiesHistory', activeFilters);
- break;
- }
- default:
- }
- });
-}
diff --git a/ee/app/assets/javascripts/security_dashboard/store/plugins/mediator.js b/ee/app/assets/javascripts/security_dashboard/store/plugins/mediator.js
new file mode 100644
index 0000000000000000000000000000000000000000..5871e351e5f55a4595380ce286ecbd0778b17d0e
--- /dev/null
+++ b/ee/app/assets/javascripts/security_dashboard/store/plugins/mediator.js
@@ -0,0 +1,18 @@
+import * as filtersMutationTypes from '../modules/filters/mutation_types';
+
+export default store => {
+ store.subscribe(({ type }) => {
+ switch (type) {
+ case `filters/${filtersMutationTypes.SET_ALL_FILTERS}`:
+ case `filters/${filtersMutationTypes.SET_FILTER}`:
+ case `filters/${filtersMutationTypes.SET_TOGGLE_VALUE}`: {
+ const activeFilters = store.getters['filters/activeFilters'];
+ store.dispatch('vulnerabilities/fetchVulnerabilities', activeFilters);
+ store.dispatch('vulnerabilities/fetchVulnerabilitiesCount', activeFilters);
+ store.dispatch('vulnerabilities/fetchVulnerabilitiesHistory', activeFilters);
+ break;
+ }
+ default:
+ }
+ });
+};
diff --git a/ee/app/assets/javascripts/security_dashboard/store/plugins/projects.js b/ee/app/assets/javascripts/security_dashboard/store/plugins/projects.js
new file mode 100644
index 0000000000000000000000000000000000000000..ff3a2943974185bec8290692fd0a831021682ba7
--- /dev/null
+++ b/ee/app/assets/javascripts/security_dashboard/store/plugins/projects.js
@@ -0,0 +1,22 @@
+import projectsModule from '../modules/projects';
+import * as projectsMutationTypes from '../modules/projects/mutation_types';
+import { BASE_FILTERS } from '../modules/filters/constants';
+
+export default store => {
+ store.registerModule('projects', projectsModule);
+
+ store.subscribe(({ type, payload }) => {
+ if (type === `projects/${projectsMutationTypes.RECEIVE_PROJECTS_SUCCESS}`) {
+ store.dispatch('filters/setFilterOptions', {
+ filterId: 'project_id',
+ options: [
+ BASE_FILTERS.project_id,
+ ...payload.projects.map(({ name, id }) => ({
+ name,
+ id: id.toString(),
+ })),
+ ],
+ });
+ }
+ });
+};
diff --git a/ee/app/assets/javascripts/security_dashboard/store/sync_with_router.js b/ee/app/assets/javascripts/security_dashboard/store/plugins/sync_with_router.js
similarity index 96%
rename from ee/app/assets/javascripts/security_dashboard/store/sync_with_router.js
rename to ee/app/assets/javascripts/security_dashboard/store/plugins/sync_with_router.js
index db943fed712107c65bd4bddd24e70a66bd2475dd..09bc3d003ab32222851d92a1f691d30588f83f68 100644
--- a/ee/app/assets/javascripts/security_dashboard/store/sync_with_router.js
+++ b/ee/app/assets/javascripts/security_dashboard/store/plugins/sync_with_router.js
@@ -1,7 +1,7 @@
import {
SET_VULNERABILITIES_HISTORY_DAY_RANGE,
RECEIVE_VULNERABILITIES_SUCCESS,
-} from './modules/vulnerabilities/mutation_types';
+} from '../modules/vulnerabilities/mutation_types';
/**
* Vuex store plugin to sync some Group Security Dashboard view settings with the URL.
diff --git a/ee/spec/frontend/security_dashboard/components/app_spec.js b/ee/spec/frontend/security_dashboard/components/app_spec.js
index 98d8e323496c18626b0cae9e7bd1b09faaaea3fc..c0870204d20369f2b8a33285d8dd69b5b8ab7fb5 100644
--- a/ee/spec/frontend/security_dashboard/components/app_spec.js
+++ b/ee/spec/frontend/security_dashboard/components/app_spec.js
@@ -15,7 +15,6 @@ import createStore from 'ee/security_dashboard/store';
const localVue = createLocalVue();
const pipelineId = 123;
-const projectsEndpoint = `${TEST_HOST}/projects`;
const vulnerabilitiesEndpoint = `${TEST_HOST}/vulnerabilities`;
const vulnerabilitiesCountEndpoint = `${TEST_HOST}/vulnerabilities_summary`;
const vulnerabilitiesHistoryEndpoint = `${TEST_HOST}/vulnerabilities_history`;
@@ -27,14 +26,12 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('Security Dashboard app', () => {
let wrapper;
let mock;
- let fetchProjectsSpy;
let lockFilterSpy;
let setPipelineIdSpy;
let store;
const setup = () => {
mock = new MockAdapter(axios);
- fetchProjectsSpy = jest.fn();
lockFilterSpy = jest.fn();
setPipelineIdSpy = jest.fn();
};
@@ -47,13 +44,11 @@ describe('Security Dashboard app', () => {
sync: false,
methods: {
lockFilter: lockFilterSpy,
- fetchProjects: fetchProjectsSpy,
setPipelineId: setPipelineIdSpy,
},
propsData: {
dashboardDocumentation: '',
emptyStateSvgPath: '',
- projectsEndpoint,
vulnerabilitiesEndpoint,
vulnerabilitiesCountEndpoint,
vulnerabilitiesHistoryEndpoint,
@@ -95,10 +90,6 @@ describe('Security Dashboard app', () => {
expect(wrapper.vm.isLockedToProject).toBe(false);
});
- it('fetches projects', () => {
- expect(fetchProjectsSpy).toHaveBeenCalled();
- });
-
it('does not lock project filters', () => {
expect(lockFilterSpy).not.toHaveBeenCalled();
});
@@ -139,10 +130,6 @@ describe('Security Dashboard app', () => {
expect(wrapper.vm.isLockedToProject).toBe(true);
});
- it('fetches projects', () => {
- expect(fetchProjectsSpy).toHaveBeenCalled();
- });
-
it('locks the filters to a given project', () => {
expect(lockFilterSpy).toHaveBeenCalledWith({
filterId: 'project_id',
diff --git a/ee/spec/frontend/security_dashboard/components/group_security_dashboard_spec.js b/ee/spec/frontend/security_dashboard/components/group_security_dashboard_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..a6f9dba9c40bbec8c49cfca9f2b8b95d056e6526
--- /dev/null
+++ b/ee/spec/frontend/security_dashboard/components/group_security_dashboard_spec.js
@@ -0,0 +1,82 @@
+import Vuex from 'vuex';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import GroupSecurityDashboard from 'ee/security_dashboard/components/group_security_dashboard.vue';
+import SecurityDashboard from 'ee/security_dashboard/components/app.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+const dashboardDocumentation = '/help/docs';
+const emptyStateSvgPath = '/svgs/empty/svg';
+const projectsEndpoint = '/projects';
+const vulnerabilitiesEndpoint = '/vulnerabilities';
+const vulnerabilitiesCountEndpoint = '/vulnerabilities_summary';
+const vulnerabilitiesHistoryEndpoint = '/vulnerabilities_history';
+const vulnerabilityFeedbackHelpPath = '/vulnerabilities_feedback_help';
+
+describe('Group Security Dashboard component', () => {
+ let store;
+ let wrapper;
+
+ const factory = () => {
+ store = new Vuex.Store({
+ modules: {
+ projects: {
+ namespaced: true,
+ actions: {
+ fetchProjects() {},
+ setProjectsEndpoint() {},
+ },
+ },
+ },
+ });
+ jest.spyOn(store, 'dispatch').mockImplementation();
+
+ wrapper = shallowMount(GroupSecurityDashboard, {
+ localVue,
+ store,
+ sync: false,
+ propsData: {
+ dashboardDocumentation,
+ emptyStateSvgPath,
+ projectsEndpoint,
+ vulnerabilitiesEndpoint,
+ vulnerabilitiesCountEndpoint,
+ vulnerabilitiesHistoryEndpoint,
+ vulnerabilityFeedbackHelpPath,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('on creation', () => {
+ beforeEach(() => {
+ factory();
+ });
+
+ it('dispatches the expected actions', () => {
+ expect(store.dispatch.mock.calls).toEqual([
+ ['projects/setProjectsEndpoint', projectsEndpoint],
+ ['projects/fetchProjects', undefined],
+ ]);
+ });
+
+ it('renders the security dashboard', () => {
+ const dashboard = wrapper.find(SecurityDashboard);
+ expect(dashboard.exists()).toBe(true);
+ expect(dashboard.props()).toEqual(
+ expect.objectContaining({
+ dashboardDocumentation,
+ emptyStateSvgPath,
+ vulnerabilitiesEndpoint,
+ vulnerabilitiesCountEndpoint,
+ vulnerabilitiesHistoryEndpoint,
+ vulnerabilityFeedbackHelpPath,
+ }),
+ );
+ });
+ });
+});
diff --git a/ee/spec/frontend/security_dashboard/store/plugins/projects_spec.js b/ee/spec/frontend/security_dashboard/store/plugins/projects_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..0467a35b56409cfad6fb457f94849b328b5cb034
--- /dev/null
+++ b/ee/spec/frontend/security_dashboard/store/plugins/projects_spec.js
@@ -0,0 +1,38 @@
+import Vuex from 'vuex';
+import createStore from 'ee/security_dashboard/store';
+import projectsModule from 'ee/security_dashboard/store/modules/projects';
+import projectsPlugin from 'ee/security_dashboard/store/plugins/projects';
+import * as projectsMutationTypes from 'ee/security_dashboard/store/modules/projects/mutation_types';
+import { BASE_FILTERS } from 'ee/security_dashboard/store/modules/filters/constants';
+
+describe('projects plugin', () => {
+ let store;
+
+ beforeEach(() => {
+ jest.spyOn(Vuex.Store.prototype, 'registerModule');
+ store = createStore({ plugins: [projectsPlugin] });
+ });
+
+ it('registers the projects module on the store', () => {
+ expect(Vuex.Store.prototype.registerModule).toHaveBeenCalledTimes(1);
+ expect(Vuex.Store.prototype.registerModule).toHaveBeenCalledWith('projects', projectsModule);
+ });
+
+ it('sets project filter options after projects have been received', () => {
+ jest.spyOn(store, 'dispatch').mockImplementation();
+ const projectOption = { name: 'foo', id: '1' };
+
+ store.commit(`projects/${projectsMutationTypes.RECEIVE_PROJECTS_SUCCESS}`, {
+ projects: [{ ...projectOption, irrelevantProperty: 'foobar' }],
+ });
+
+ expect(store.dispatch).toHaveBeenCalledTimes(1);
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'filters/setFilterOptions',
+ Object({
+ filterId: 'project_id',
+ options: [BASE_FILTERS.project_id, projectOption],
+ }),
+ );
+ });
+});
diff --git a/ee/spec/frontend/security_dashboard/store/sync_with_router_spec.js b/ee/spec/frontend/security_dashboard/store/plugins/sync_with_router_spec.js
similarity index 99%
rename from ee/spec/frontend/security_dashboard/store/sync_with_router_spec.js
rename to ee/spec/frontend/security_dashboard/store/plugins/sync_with_router_spec.js
index ed87c00ee49551b875296a7313e9a7da97eaf999..6395ca282659aa4410f95910f48d580bb1a0c2c5 100644
--- a/ee/spec/frontend/security_dashboard/store/sync_with_router_spec.js
+++ b/ee/spec/frontend/security_dashboard/store/plugins/sync_with_router_spec.js
@@ -32,7 +32,7 @@ describe('syncWithRouter', () => {
);
});
- it("doesn't update the store if the URL update originated from the moderator", () => {
+ it("doesn't update the store if the URL update originated from the mediator", () => {
const query = { example: ['test'] };
jest.spyOn(store, 'commit').mockImplementation(noop);
diff --git a/ee/spec/javascripts/security_dashboard/store/moderator_spec.js b/ee/spec/javascripts/security_dashboard/store/plugins/mediator_spec.js
similarity index 75%
rename from ee/spec/javascripts/security_dashboard/store/moderator_spec.js
rename to ee/spec/javascripts/security_dashboard/store/plugins/mediator_spec.js
index 261a8bdd34e734ba75f02ba42d73aa5aab3fc55e..98ae5c21136480badf4678a7c7f0492556a09847 100644
--- a/ee/spec/javascripts/security_dashboard/store/moderator_spec.js
+++ b/ee/spec/javascripts/security_dashboard/store/plugins/mediator_spec.js
@@ -1,32 +1,13 @@
import createStore from 'ee/security_dashboard/store/index';
-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 { BASE_FILTERS } from 'ee/security_dashboard/store/modules/filters/constants';
-describe('moderator', () => {
+describe('mediator', () => {
let store;
beforeEach(() => {
store = createStore();
});
- it('sets project filter options after projects have been received', () => {
- spyOn(store, 'dispatch');
-
- store.commit(`projects/${projectsMutationTypes.RECEIVE_PROJECTS_SUCCESS}`, {
- projects: [{ name: 'foo', id: 1, otherProp: 'foobar' }],
- });
-
- expect(store.dispatch).toHaveBeenCalledTimes(1);
- expect(store.dispatch).toHaveBeenCalledWith(
- 'filters/setFilterOptions',
- Object({
- filterId: 'project_id',
- options: [BASE_FILTERS.project_id, { name: 'foo', id: '1' }],
- }),
- );
- });
-
it('triggers fetching vulnerabilities after one filter changes', () => {
spyOn(store, 'dispatch');