diff --git a/ee/app/assets/javascripts/security_dashboard/components/app.vue b/ee/app/assets/javascripts/security_dashboard/components/app.vue index cd1804fe1312e70daa2dfba759e69a9d21bcc89b..748e462b53ebf089ec9e8a412c134760e3404cf9 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/app.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/app.vue @@ -24,6 +24,10 @@ export default { type: String, required: true, }, + projectsEndpoint: { + type: String, + required: true, + }, vulnerabilitiesEndpoint: { type: String, required: true, @@ -43,13 +47,16 @@ export default { }, computed: { ...mapState('vulnerabilities', ['modal']), + ...mapState('projects', ['projects']), ...mapGetters('filters', ['activeFilters']), }, created() { + this.setProjectsEndpoint(this.projectsEndpoint); this.setVulnerabilitiesEndpoint(this.vulnerabilitiesEndpoint); this.setVulnerabilitiesCountEndpoint(this.vulnerabilitiesCountEndpoint); this.setVulnerabilitiesHistoryEndpoint(this.vulnerabilitiesHistoryEndpoint); this.fetchVulnerabilitiesCount(); + this.fetchProjects(); }, methods: { ...mapActions('vulnerabilities', [ @@ -63,6 +70,7 @@ export default { 'setVulnerabilitiesEndpoint', 'setVulnerabilitiesHistoryEndpoint', ]), + ...mapActions('projects', ['setProjectsEndpoint', 'fetchProjects']), filterChange() { this.fetchVulnerabilities(this.activeFilters); this.fetchVulnerabilitiesCount(this.activeFilters); diff --git a/ee/app/assets/javascripts/security_dashboard/components/report_type_popover.vue b/ee/app/assets/javascripts/security_dashboard/components/report_type_popover.vue index d415376598a1bb47da6a406da087e026bca2430a..820b34fe44d865ca698bc16366850d134940c42f 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/report_type_popover.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/report_type_popover.vue @@ -1,5 +1,6 @@ @@ -33,12 +35,10 @@ export default { v-if="dashboardDocumentation" target="_blank" rel="noopener noreferrer" - :title="s__('Security Reports|Security dashboard documentation')" + :title="$options.linkTitle" :href="dashboardDocumentation" > - {{ - s__('Security Reports|Security dashboard documentation') - }} + {{ $options.linkTitle }} diff --git a/ee/app/assets/javascripts/security_dashboard/index.js b/ee/app/assets/javascripts/security_dashboard/index.js index d421fc1569a174d965ad314e2ccb8846f53e2c74..4a555d5d31f7c4c9eca611a8fc02450e0e517aae 100644 --- a/ee/app/assets/javascripts/security_dashboard/index.js +++ b/ee/app/assets/javascripts/security_dashboard/index.js @@ -16,6 +16,7 @@ export default () => { props: { dashboardDocumentation: el.dataset.dashboardDocumentation, emptyStateSvgPath: el.dataset.emptyStateSvgPath, + projectsEndpoint: el.dataset.projectsEndpoint, vulnerabilityFeedbackHelpPath: el.dataset.vulnerabilityFeedbackHelpPath, vulnerabilitiesEndpoint: el.dataset.vulnerabilitiesEndpoint, vulnerabilitiesCountEndpoint: el.dataset.vulnerabilitiesSummaryEndpoint, diff --git a/ee/app/assets/javascripts/security_dashboard/store/index.js b/ee/app/assets/javascripts/security_dashboard/store/index.js index 6f51dfde52bce155e4564e230ec985214c9093da..2fc3fc755cc1acc68c2ce0152d162acf41bba8a7 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/index.js +++ b/ee/app/assets/javascripts/security_dashboard/store/index.js @@ -1,14 +1,20 @@ import Vue from 'vue'; import Vuex from 'vuex'; -import vulnerabilities from './modules/vulnerabilities/index'; +import configureModerator from './moderator'; import filters from './modules/filters/index'; +import projects from './modules/projects/index'; +import vulnerabilities from './modules/vulnerabilities/index'; Vue.use(Vuex); -export default () => - new Vuex.Store({ - modules: { - vulnerabilities, - filters, - }, - }); +const store = new Vuex.Store({ + modules: { + filters, + projects, + vulnerabilities, + }, +}); + +configureModerator(store); + +export default () => store; diff --git a/ee/app/assets/javascripts/security_dashboard/store/moderator.js b/ee/app/assets/javascripts/security_dashboard/store/moderator.js new file mode 100644 index 0000000000000000000000000000000000000000..909e5ec6ee4592f7c54f71889ee8b453e686343a --- /dev/null +++ b/ee/app/assets/javascripts/security_dashboard/store/moderator.js @@ -0,0 +1,26 @@ +import * as projectsMutationTypes from './modules/projects/mutation_types'; + +export default function configureModerator(store) { + store.subscribe(({ type, payload }) => { + switch (type) { + case `projects/${projectsMutationTypes.RECEIVE_PROJECTS_SUCCESS}`: + return store.dispatch('filters/setFilterOptions', { + filterId: 'project', + options: [ + { + name: 'All', + id: 'all', + selected: true, + }, + ...payload.projects.map(project => ({ + name: project.name, + id: project.id.toString(), + selected: false, + })), + ], + }); + default: + return null; + } + }); +} diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/filters/actions.js b/ee/app/assets/javascripts/security_dashboard/store/modules/filters/actions.js index 2b06257ba68e968de41db06f3057552ec815392a..21153d0e380cf2736559e481e3079a893af81601 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/filters/actions.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/filters/actions.js @@ -4,6 +4,10 @@ export const setFilter = ({ commit }, payload) => { commit(types.SET_FILTER, payload); }; +export const setFilterOptions = ({ commit }, payload) => { + commit(types.SET_FILTER_OPTIONS, payload); +}; + // prevent babel-plugin-rewire from generating an invalid default during karma tests // This is no longer needed after gitlab-ce#52179 is merged export default () => {}; diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/filters/mutation_types.js b/ee/app/assets/javascripts/security_dashboard/store/modules/filters/mutation_types.js index a92b48b9e2aacfaf9ea57ed72019f74ed0fe23a5..4a880d1a6bb62998f356f66f03835bd5870b8d43 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/filters/mutation_types.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/filters/mutation_types.js @@ -1,4 +1,2 @@ export const SET_FILTER = 'SET_FILTER'; - -// This is here because es-lint requires a default export when there are less than two named exports -export default SET_FILTER; +export const SET_FILTER_OPTIONS = 'SET_FILTER_OPTIONS'; diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/filters/mutations.js b/ee/app/assets/javascripts/security_dashboard/store/modules/filters/mutations.js index 634628b856f1e7742850aa44b2354c9df431da1c..b02c3010dba9f04138d2ecdd2e6c6198f3333784 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/filters/mutations.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/filters/mutations.js @@ -41,4 +41,8 @@ export default { activeFilter.options = activeOptions; } }, + [types.SET_FILTER_OPTIONS](state, payload) { + const { filterId, options } = payload; + state.filters.find(filter => filter.id === filterId).options = options; + }, }; diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/filters/state.js b/ee/app/assets/javascripts/security_dashboard/store/modules/filters/state.js index 38068d044bbf0f9935350888ca6196a3101a549f..ce266cd769443c1ef9a67a39c482491ca68a83fc 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/filters/state.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/filters/state.js @@ -32,5 +32,16 @@ export default () => ({ }), ], }, + { + name: 'Project', + id: 'project', + options: [ + { + name: 'All', + id: 'all', + selected: true, + }, + ], + }, ], }); diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/projects/actions.js b/ee/app/assets/javascripts/security_dashboard/store/modules/projects/actions.js new file mode 100644 index 0000000000000000000000000000000000000000..73b1d574de84f581c1537f700827dd42819b7598 --- /dev/null +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/projects/actions.js @@ -0,0 +1,37 @@ +import axios from '~/lib/utils/axios_utils'; +import * as types from './mutation_types'; + +export const setProjectsEndpoint = ({ commit }, endpoint) => { + commit(types.SET_PROJECTS_ENDPOINT, endpoint); +}; + +export const fetchProjects = ({ state, dispatch }) => { + dispatch('requestProjects'); + + axios({ + method: 'GET', + url: state.projectsEndpoint, + }) + .then(({ data }) => { + dispatch('receiveProjectsSuccess', { projects: data }); + }) + .catch(() => { + dispatch('receiveProjectsError'); + }); +}; + +export const requestProjects = ({ commit }) => { + commit(types.REQUEST_PROJECTS); +}; + +export const receiveProjectsSuccess = ({ commit }, { projects }) => { + commit(types.RECEIVE_PROJECTS_SUCCESS, { projects }); +}; + +export const receiveProjectsError = ({ commit }) => { + commit(types.RECEIVE_PROJECTS_ERROR); +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +// This is no longer needed after gitlab-ce#52179 is merged +export default () => {}; diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/projects/index.js b/ee/app/assets/javascripts/security_dashboard/store/modules/projects/index.js new file mode 100644 index 0000000000000000000000000000000000000000..68c81bb45096f154036c07678c400afaec9bb260 --- /dev/null +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/projects/index.js @@ -0,0 +1,10 @@ +import state from './state'; +import mutations from './mutations'; +import * as actions from './actions'; + +export default { + namespaced: true, + state, + mutations, + actions, +}; diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/projects/mutation_types.js b/ee/app/assets/javascripts/security_dashboard/store/modules/projects/mutation_types.js new file mode 100644 index 0000000000000000000000000000000000000000..7fcf0ea6a49bdec96e91189f820c6be3f246bf96 --- /dev/null +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/projects/mutation_types.js @@ -0,0 +1,4 @@ +export const SET_PROJECTS_ENDPOINT = 'SET_PROJECTS_ENDPOINT'; +export const REQUEST_PROJECTS = 'REQUEST_PROJECTS'; +export const RECEIVE_PROJECTS_SUCCESS = 'RECEIVE_PROJECTS_SUCCESS'; +export const RECEIVE_PROJECTS_ERROR = 'RECEIVE_PROJECTS_ERROR'; diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/projects/mutations.js b/ee/app/assets/javascripts/security_dashboard/store/modules/projects/mutations.js new file mode 100644 index 0000000000000000000000000000000000000000..d176e5700f6db6a60b5a63cd64ac789ea98ba0d8 --- /dev/null +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/projects/mutations.js @@ -0,0 +1,19 @@ +import * as types from './mutation_types'; + +export default { + [types.SET_PROJECTS_ENDPOINT](state, payload) { + state.projectsEndpoint = payload; + }, + [types.REQUEST_PROJECTS](state) { + state.isLoadingProjects = true; + state.errorLoadingProjects = false; + }, + [types.RECEIVE_PROJECTS_SUCCESS](state, payload) { + state.projects = payload.projects; + state.isLoadingProjects = false; + }, + [types.RECEIVE_PROJECTS_ERROR](state) { + state.isLoadingProjects = false; + state.errorLoadingProjects = true; + }, +}; diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/projects/state.js b/ee/app/assets/javascripts/security_dashboard/store/modules/projects/state.js new file mode 100644 index 0000000000000000000000000000000000000000..f31b49f35f65acab8049d7635d759fc0db8b7337 --- /dev/null +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/projects/state.js @@ -0,0 +1,6 @@ +export default () => ({ + endpoint: '', + projects: [], + isLoadingProjects: false, + errorLoadingProjects: false, +}); diff --git a/ee/app/views/groups/security/dashboard/show.html.haml b/ee/app/views/groups/security/dashboard/show.html.haml index b5a5f07855757464efac589d4020f5e5b10c9d8a..300a3e4783744a18cd3f344a2f78ab9539c12533 100644 --- a/ee/app/views/groups/security/dashboard/show.html.haml +++ b/ee/app/views/groups/security/dashboard/show.html.haml @@ -4,6 +4,7 @@ #js-group-security-dashboard{ data: { vulnerabilities_endpoint: group_security_vulnerabilities_path(@group), vulnerabilities_summary_endpoint: summary_group_security_vulnerabilities_path(@group), vulnerabilities_history_endpoint: history_group_security_vulnerabilities_path(@group), + projects_endpoint: expose_url(api_v4_groups_projects_path(id: @group.id)), vulnerability_feedback_help_path: help_page_path("user/project/merge_requests/index", anchor: "interacting-with-security-reports-ultimate"), empty_state_svg_path: image_path('illustrations/security-dashboard-empty-state.svg'), dashboard_documentation: help_page_path('user/group/security_dashboard/index') } } diff --git a/ee/changelogs/unreleased/6240-project-filter-for-gsd.yml b/ee/changelogs/unreleased/6240-project-filter-for-gsd.yml new file mode 100644 index 0000000000000000000000000000000000000000..50bec364860623b67abccd13076d15d535738bc4 --- /dev/null +++ b/ee/changelogs/unreleased/6240-project-filter-for-gsd.yml @@ -0,0 +1,5 @@ +--- +title: Adds project filtering to the GSD +merge_request: 8944 +author: +type: added diff --git a/ee/spec/javascripts/security_dashboard/components/filters_spec.js b/ee/spec/javascripts/security_dashboard/components/filters_spec.js index ea00a30a102c8837695b37b3c047dde3abcd0326..8fd344d7659f3139e5f0d4d0642f1067cbea6c31 100644 --- a/ee/spec/javascripts/security_dashboard/components/filters_spec.js +++ b/ee/spec/javascripts/security_dashboard/components/filters_spec.js @@ -18,8 +18,8 @@ describe('Filter component', () => { vm.$destroy(); }); - it('should display both filters', () => { - expect(vm.$el.querySelectorAll('.js-filter').length).toEqual(2); + it('should display all filters', () => { + expect(vm.$el.querySelectorAll('.js-filter').length).toEqual(3); }); }); }); diff --git a/ee/spec/javascripts/security_dashboard/store/filters/actions_spec.js b/ee/spec/javascripts/security_dashboard/store/filters/actions_spec.js index 03d00185d2307dc51758faa5956ebb138538fa59..a4d74acc68183afd193cea27e2b6a686ce4ff038 100644 --- a/ee/spec/javascripts/security_dashboard/store/filters/actions_spec.js +++ b/ee/spec/javascripts/security_dashboard/store/filters/actions_spec.js @@ -25,4 +25,25 @@ describe('filters actions', () => { ); }); }); + + describe('setFilterOptions', () => { + it('should commit the SET_FILTER_OPTIONS mutuation', done => { + const state = createState(); + const payload = { filterId: 'project', options: [] }; + + testAction( + actions.setFilterOptions, + payload, + state, + [ + { + type: types.SET_FILTER_OPTIONS, + payload, + }, + ], + [], + done, + ); + }); + }); }); diff --git a/ee/spec/javascripts/security_dashboard/store/filters/getters_spec.js b/ee/spec/javascripts/security_dashboard/store/filters/getters_spec.js index 76fcfb8f00486b576f3c8cbc0bf063c33c235af9..a1f6c2bc15fa5e90ecf1c8c8e0eea8b7835a27ec 100644 --- a/ee/spec/javascripts/security_dashboard/store/filters/getters_spec.js +++ b/ee/spec/javascripts/security_dashboard/store/filters/getters_spec.js @@ -17,10 +17,14 @@ describe('filters module getters', () => { getFilterIds, }; }; + let state; + + beforeEach(() => { + state = createState(); + }); describe('getFilter', () => { it('should return the type filter information', () => { - const state = createState(); const typeFilter = getters.getFilter(state)('report_type'); expect(typeFilter.name).toEqual('Report type'); @@ -30,7 +34,6 @@ describe('filters module getters', () => { describe('getSelectedOptions', () => { describe('with one selected option', () => { it('should return "All" as the selected option', () => { - const state = createState(); const selectedOptions = getters.getSelectedOptions(state, mockedGetters(state))( 'report_type', ); @@ -42,7 +45,7 @@ describe('filters module getters', () => { describe('with multiple selected options', () => { it('should return both "High" and "Critical" ', () => { - const state = { + state = { filters: [ { id: 'severity', @@ -58,16 +61,13 @@ describe('filters module getters', () => { }); describe('getSelectedOptionIds', () => { - it('should return "one" as the selcted project ID', () => { - const state = createState(); - const projectFilter = { - id: 'project', + it('should return "one" as the selcted dummy ID', () => { + const dummyFilter = { + id: 'dummy', options: [{ id: 'one', selected: true }, { id: 'anotherone', selected: false }], }; - state.filters.push(projectFilter); - const selectedOptionIds = getters.getSelectedOptionIds(state, mockedGetters(state))( - 'project', - ); + state.filters.push(dummyFilter); + const selectedOptionIds = getters.getSelectedOptionIds(state, mockedGetters(state))('dummy'); expect(selectedOptionIds).toHaveLength(1); expect(selectedOptionIds[0]).toEqual('one'); @@ -76,7 +76,6 @@ describe('filters module getters', () => { describe('getSelectedOptionNames', () => { it('should return "All" as the selected option', () => { - const state = createState(); const selectedOptionNames = getters.getSelectedOptionNames(state, mockedGetters(state))( 'severity', ); @@ -85,7 +84,7 @@ describe('filters module getters', () => { }); it('should return the correct message when multiple filters are selected', () => { - const state = { + state = { filters: [ { id: 'severity', @@ -103,22 +102,20 @@ describe('filters module getters', () => { describe('activeFilters', () => { it('should return no severity filters', () => { - const state = createState(); const activeFilters = getters.activeFilters(state, mockedGetters(state)); expect(activeFilters.severity).toHaveLength(0); }); - it('should return multiple project filters"', () => { - const state = createState(); - const projectFilter = { - id: 'project', + it('should return multiple dummy filters"', () => { + const dummyFilter = { + id: 'dummy', options: [{ id: 'one', selected: true }, { id: 'anotherone', selected: true }], }; - state.filters.push(projectFilter); + state.filters.push(dummyFilter); const activeFilters = getters.activeFilters(state, mockedGetters(state)); - expect(activeFilters.project).toHaveLength(2); + expect(activeFilters.dummy).toHaveLength(2); }); }); }); diff --git a/ee/spec/javascripts/security_dashboard/store/filters/mutations_spec.js b/ee/spec/javascripts/security_dashboard/store/filters/mutations_spec.js index 6bca9e13c61c172bb4bceaf7d636a7e8fb0b4c5c..aff84fdaf8b49e80f3a8fac80e8ef95a545a8888 100644 --- a/ee/spec/javascripts/security_dashboard/store/filters/mutations_spec.js +++ b/ee/spec/javascripts/security_dashboard/store/filters/mutations_spec.js @@ -40,4 +40,22 @@ describe('filters module mutations', () => { }); }); }); + + describe('SET_FILTER_OPTIONS', () => { + let state; + let firstFilter; + const options = [{ id: 0, name: 'c' }, { id: 3, name: 'c' }]; + + beforeEach(() => { + state = createState(); + [firstFilter] = state.filters; + const filterId = firstFilter.id; + + mutations[types.SET_FILTER_OPTIONS](state, { filterId, options }); + }); + + it('should add all the options to the type filter', () => { + expect(firstFilter.options).toEqual(options); + }); + }); }); diff --git a/ee/spec/javascripts/security_dashboard/store/projects/actions_spec.js b/ee/spec/javascripts/security_dashboard/store/projects/actions_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..d3ee398ffc46c8ed03d4f8f5e361cef30ed2ae21 --- /dev/null +++ b/ee/spec/javascripts/security_dashboard/store/projects/actions_spec.js @@ -0,0 +1,132 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import testAction from 'spec/helpers/vuex_action_helper'; +import { TEST_HOST } from 'spec/test_constants'; + +import createState from 'ee/security_dashboard/store/modules/projects/state'; +import * as types from 'ee/security_dashboard/store/modules/projects/mutation_types'; +import * as actions from 'ee/security_dashboard/store/modules/projects/actions'; + +import mockData from './data/mock_data.json'; + +describe('projects actions', () => { + const data = mockData; + const endpoint = `${TEST_HOST}/projects.json`; + + describe('fetchProjects', () => { + let mock; + const state = createState(); + + beforeEach(() => { + state.projectsEndpoint = endpoint; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('on success', () => { + beforeEach(() => { + mock.onGet(state.projectsEndpoint).replyOnce(200, data); + }); + + it('should dispatch the request and success actions', done => { + testAction( + actions.fetchProjects, + {}, + state, + [], + [ + { type: 'requestProjects' }, + { + type: 'receiveProjectsSuccess', + payload: { projects: data }, + }, + ], + done, + ); + }); + }); + + describe('on error', () => { + beforeEach(() => { + mock.onGet(state.projectsEndpoint).replyOnce(404, {}); + }); + + it('should dispatch the request and error actions', done => { + testAction( + actions.fetchProjects, + {}, + state, + [], + [{ type: 'requestProjects' }, { type: 'receiveProjectsError' }], + done, + ); + }); + }); + }); + + describe('receiveProjectsSuccess', () => { + it('should commit the success mutation', done => { + const state = createState(); + + testAction( + actions.receiveProjectsSuccess, + { projects: data }, + state, + [ + { + type: types.RECEIVE_PROJECTS_SUCCESS, + payload: { projects: data }, + }, + ], + [], + done, + ); + }); + }); + + describe('receiveProjectsError', () => { + it('should commit the error mutation', done => { + const state = createState(); + + testAction( + actions.receiveProjectsError, + {}, + state, + [{ type: types.RECEIVE_PROJECTS_ERROR }], + [], + done, + ); + }); + }); + + describe('requestProjects', () => { + it('should commit the request mutation', done => { + const state = createState(); + + testAction(actions.requestProjects, {}, state, [{ type: types.REQUEST_PROJECTS }], [], done); + }); + }); + + describe('setProjectsEndpoint', () => { + it('should commit the correct mutuation', done => { + const state = createState(); + + testAction( + actions.setProjectsEndpoint, + endpoint, + state, + [ + { + type: types.SET_PROJECTS_ENDPOINT, + payload: endpoint, + }, + ], + [], + done, + ); + }); + }); +}); diff --git a/ee/spec/javascripts/security_dashboard/store/projects/data/mock_data.json b/ee/spec/javascripts/security_dashboard/store/projects/data/mock_data.json new file mode 100644 index 0000000000000000000000000000000000000000..e834a0c58f1c6158e88b9d993e170846a7b43463 --- /dev/null +++ b/ee/spec/javascripts/security_dashboard/store/projects/data/mock_data.json @@ -0,0 +1,39 @@ +[ + { + "id": 9, + "description": "foo", + "default_branch": "master", + "tag_list": [], + "archived": false, + "visibility": "internal", + "ssh_url_to_repo": "git@gitlab.example.com/html5-boilerplate.git", + "http_url_to_repo": "http://gitlab.example.com/h5bp/html5-boilerplate.git", + "web_url": "http://gitlab.example.com/h5bp/html5-boilerplate", + "name": "Html5 Boilerplate", + "name_with_namespace": "Experimental / Html5 Boilerplate", + "path": "html5-boilerplate", + "path_with_namespace": "h5bp/html5-boilerplate", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "jobs_enabled": true, + "snippets_enabled": true, + "created_at": "2016-04-05T21:40:50.169Z", + "last_activity_at": "2016-04-06T16:52:08.432Z", + "shared_runners_enabled": true, + "creator_id": 1, + "namespace": { + "id": 5, + "name": "Experimental", + "path": "h5bp", + "kind": "group" + }, + "avatar_url": null, + "star_count": 1, + "forks_count": 0, + "open_issues_count": 3, + "public_jobs": true, + "shared_with_groups": [], + "request_access_enabled": false + } +] diff --git a/ee/spec/javascripts/security_dashboard/store/projects/mutations_spec.js b/ee/spec/javascripts/security_dashboard/store/projects/mutations_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..df2b21dea2166ec551e70eb4411cf1a517f5ec00 --- /dev/null +++ b/ee/spec/javascripts/security_dashboard/store/projects/mutations_spec.js @@ -0,0 +1,72 @@ +import createState from 'ee/security_dashboard/store/modules/projects/state'; +import * as types from 'ee/security_dashboard/store/modules/projects/mutation_types'; +import mutations from 'ee/security_dashboard/store/modules/projects/mutations'; +import mockData from './data/mock_data.json'; + +describe('projects module mutations', () => { + describe('SET_PROJECTS_ENDPOINT', () => { + it('should set `projectsEndpoint` to `fakepath.json`', () => { + const state = createState(); + const endpoint = 'fakepath.json'; + + mutations[types.SET_PROJECTS_ENDPOINT](state, endpoint); + + expect(state.projectsEndpoint).toEqual(endpoint); + }); + }); + + describe('REQUEST_PROJECTS', () => { + let state; + + beforeEach(() => { + state = { + ...createState(), + errorLoadingProjects: true, + }; + mutations[types.REQUEST_PROJECTS](state); + }); + + it('should set `isLoadingProjects` to `true`', () => { + expect(state.isLoadingProjects).toBe(true); + }); + + it('should set `errorLoadingProjects` to `false`', () => { + expect(state.errorLoadingProjects).toBe(false); + }); + }); + + describe('RECEIVE_PROJECTS_SUCCESS', () => { + let payload; + let state; + + beforeEach(() => { + payload = { + projects: mockData, + }; + state = createState(); + mutations[types.RECEIVE_PROJECTS_SUCCESS](state, payload); + }); + + it('should set `isLoadingProjects` to `false`', () => { + expect(state.isLoadingProjects).toBe(false); + }); + + it('should set `pageInfo`', () => { + expect(state.pageInfo).toBe(payload.pageInfo); + }); + + it('should set `projects`', () => { + expect(state.projects).toBe(payload.projects); + }); + }); + + describe('RECEIVE_PROJECTS_ERROR', () => { + it('should set `isLoadingProjects` to `false`', () => { + const state = createState(); + + mutations[types.RECEIVE_PROJECTS_ERROR](state); + + expect(state.isLoadingProjects).toBe(false); + }); + }); +});