Commit 3a7b83f7 authored by Sam Beckham's avatar Sam Beckham Committed by Mark Florian

Fixes a bug that prevented autoremediation

- Moves the pipeline dashboard into it's own component
- Passes a source_branch to that component
- Adds the nessesary VueX to attach that source branch to the
`createMergeRequest` action
parent a917e70c
import Vue from 'vue'; import Vue from 'vue';
import { GlEmptyState } from '@gitlab/ui';
import createDashboardStore from 'ee/security_dashboard/store'; import createDashboardStore from 'ee/security_dashboard/store';
import SecurityDashboardApp from 'ee/security_dashboard/components/app.vue'; import PipelineSecurityDashboard from 'ee/security_dashboard/components/pipeline_security_dashboard.vue';
import { s__ } from '~/locale'; import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
import Translate from '~/vue_shared/translate';
Vue.use(Translate);
const initSecurityDashboardApp = el => { const initSecurityDashboardApp = el => {
const { const {
...@@ -13,38 +9,26 @@ const initSecurityDashboardApp = el => { ...@@ -13,38 +9,26 @@ const initSecurityDashboardApp = el => {
emptyStateSvgPath, emptyStateSvgPath,
pipelineId, pipelineId,
projectId, projectId,
sourceBranch,
vulnerabilitiesEndpoint, vulnerabilitiesEndpoint,
vulnerabilityFeedbackHelpPath, vulnerabilityFeedbackHelpPath,
} = el.dataset; } = el.dataset;
return new Vue({ return new Vue({
el, el,
store: createDashboardStore(), store: createDashboardStore({
dashboardType: DASHBOARD_TYPES.PIPELINE,
}),
render(createElement) { render(createElement) {
return createElement(SecurityDashboardApp, { return createElement(PipelineSecurityDashboard, {
props: { props: {
lockToProject: { projectId: parseInt(projectId, 10),
id: parseInt(projectId, 10),
},
pipelineId: parseInt(pipelineId, 10), pipelineId: parseInt(pipelineId, 10),
vulnerabilitiesEndpoint, vulnerabilitiesEndpoint,
vulnerabilityFeedbackHelpPath, vulnerabilityFeedbackHelpPath,
}, sourceBranch,
scopedSlots: { dashboardDocumentation,
emptyState: () => emptyStateSvgPath,
createElement(GlEmptyState, {
props: {
title: s__(`No vulnerabilities found for this pipeline`),
svgPath: emptyStateSvgPath,
description: s__(
`While it's rare to have no vulnerabilities for your pipeline, it can happen. In any event, we ask that you double check your settings to make sure all security scanning jobs have passed successfully.`,
),
primaryButtonLink: dashboardDocumentation,
primaryButtonText: s__(
'Security Reports|Learn more about setting up your dashboard',
),
},
}),
}, },
}); });
}, },
......
<script>
import { mapActions } from 'vuex';
import { GlEmptyState } from '@gitlab/ui';
import SecurityDashboard from './app.vue';
export default {
name: 'PipelineSecurityDashboard',
components: {
GlEmptyState,
SecurityDashboard,
},
props: {
dashboardDocumentation: {
type: String,
required: true,
},
emptyStateSvgPath: {
type: String,
required: true,
},
pipelineId: {
type: Number,
required: true,
},
projectId: {
type: Number,
required: true,
},
sourceBranch: {
type: String,
required: true,
},
vulnerabilitiesEndpoint: {
type: String,
required: true,
},
vulnerabilityFeedbackHelpPath: {
type: String,
required: true,
},
},
created() {
this.setSourceBranch(this.sourceBranch);
},
methods: {
...mapActions('vulnerabilities', ['setSourceBranch']),
},
};
</script>
<template>
<security-dashboard
:vulnerabilities-endpoint="vulnerabilitiesEndpoint"
:vulnerability-feedback-help-path="vulnerabilityFeedbackHelpPath"
:lock-to-project="{ id: projectId }"
:pipeline-id="pipelineId"
>
<template #emptyState>
<gl-empty-state
:title="s__('No vulnerabilities found for this pipeline')"
:svg-path="emptyStateSvgPath"
:description="
s__(
`While it's rare to have no vulnerabilities for your pipeline, it can happen. In any event, we ask that you double check your settings to make sure all security scanning jobs have passed successfully.`,
)
"
:primary-button-link="dashboardDocumentation"
:primary-button-text="s__('Security Reports|Learn more about setting up your dashboard')"
/>
</template>
</security-dashboard>
</template>
...@@ -20,6 +20,8 @@ const hideModal = () => $('#modal-mrwidget-security-issue').modal('hide'); ...@@ -20,6 +20,8 @@ const hideModal = () => $('#modal-mrwidget-security-issue').modal('hide');
export const setPipelineId = ({ commit }, id) => commit(types.SET_PIPELINE_ID, id); export const setPipelineId = ({ commit }, id) => commit(types.SET_PIPELINE_ID, id);
export const setSourceBranch = ({ commit }, ref) => commit(types.SET_SOURCE_BRANCH, ref);
export const setVulnerabilitiesEndpoint = ({ commit }, endpoint) => { export const setVulnerabilitiesEndpoint = ({ commit }, endpoint) => {
commit(types.SET_VULNERABILITIES_ENDPOINT, endpoint); commit(types.SET_VULNERABILITIES_ENDPOINT, endpoint);
}; };
...@@ -375,13 +377,17 @@ export const downloadPatch = ({ state }) => { ...@@ -375,13 +377,17 @@ export const downloadPatch = ({ state }) => {
$('#modal-mrwidget-security-issue').modal('hide'); $('#modal-mrwidget-security-issue').modal('hide');
}; };
export const createMergeRequest = ({ dispatch }, { vulnerability, flashError }) => { export const createMergeRequest = ({ state, dispatch }, { vulnerability, flashError }) => {
const { const {
report_type, report_type,
project_fingerprint, project_fingerprint,
create_vulnerability_feedback_merge_request_path, create_vulnerability_feedback_merge_request_path,
} = vulnerability; } = vulnerability;
// The target branch for the MR is the source branch of the pipeline.
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23677#note_278221556
const targetBranch = state.sourceBranch;
dispatch('requestCreateMergeRequest'); dispatch('requestCreateMergeRequest');
axios axios
...@@ -392,6 +398,7 @@ export const createMergeRequest = ({ dispatch }, { vulnerability, flashError }) ...@@ -392,6 +398,7 @@ export const createMergeRequest = ({ dispatch }, { vulnerability, flashError })
project_fingerprint, project_fingerprint,
vulnerability_data: { vulnerability_data: {
...vulnerability, ...vulnerability,
target_branch: targetBranch,
category: report_type, category: report_type,
}, },
}, },
......
export const SET_PIPELINE_ID = 'SET_PIPELINE_ID'; export const SET_PIPELINE_ID = 'SET_PIPELINE_ID';
export const SET_SOURCE_BRANCH = 'SET_SOURCE_BRANCH';
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 SET_VULNERABILITIES_PAGE = 'SET_VULNERABILITIES_PAGE';
export const REQUEST_VULNERABILITIES = 'REQUEST_VULNERABILITIES'; export const REQUEST_VULNERABILITIES = 'REQUEST_VULNERABILITIES';
......
...@@ -10,6 +10,9 @@ export default { ...@@ -10,6 +10,9 @@ export default {
[types.SET_PIPELINE_ID](state, payload) { [types.SET_PIPELINE_ID](state, payload) {
state.pipelineId = payload; state.pipelineId = payload;
}, },
[types.SET_SOURCE_BRANCH](state, payload) {
state.sourceBranch = payload;
},
[types.SET_VULNERABILITIES_ENDPOINT](state, payload) { [types.SET_VULNERABILITIES_ENDPOINT](state, payload) {
state.vulnerabilitiesEndpoint = payload; state.vulnerabilitiesEndpoint = payload;
}, },
......
...@@ -18,6 +18,7 @@ export default () => ({ ...@@ -18,6 +18,7 @@ export default () => ({
vulnerabilitiesHistoryEndpoint: null, vulnerabilitiesHistoryEndpoint: null,
vulnerabilitiesEndpoint: null, vulnerabilitiesEndpoint: null,
activeVulnerability: null, activeVulnerability: null,
sourceBranch: null,
modal: { modal: {
data: { data: {
description: { text: s__('Vulnerability|Description') }, description: { text: s__('Vulnerability|Description') },
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
empty_state_svg_path: image_path('illustrations/security-dashboard-empty-state.svg'), empty_state_svg_path: image_path('illustrations/security-dashboard-empty-state.svg'),
pipeline_id: pipeline.id, pipeline_id: pipeline.id,
project_id: project.id, project_id: project.id,
source_branch: pipeline.source_ref,
vulnerabilities_endpoint: vulnerabilities_endpoint_path, vulnerabilities_endpoint: vulnerabilities_endpoint_path,
vulnerability_feedback_help_path: help_page_path('user/application_security/index') } } vulnerability_feedback_help_path: help_page_path('user/application_security/index') } }
......
---
title: Fixes a bug that prevented auto-remediation on the pipeline security dashboard
merge_request: 23677
author:
type: fixed
import Vuex from 'vuex';
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import PipelineSecurityDashboard from 'ee/security_dashboard/components/pipeline_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 pipelineId = 1234;
const projectId = 5678;
const sourceBranch = 'feature-branch-1';
const vulnerabilitiesEndpoint = '/vulnerabilities';
const vulnerabilityFeedbackHelpPath = '/vulnerabilities_feedback_help';
describe('Pipeline Security Dashboard component', () => {
let store;
let wrapper;
const factory = options => {
store = new Vuex.Store({
modules: {
vulnerabilities: {
namespaced: true,
actions: {
setSourceBranch() {},
},
},
},
});
jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = shallowMount(PipelineSecurityDashboard, {
localVue,
store,
propsData: {
dashboardDocumentation,
emptyStateSvgPath,
pipelineId,
projectId,
sourceBranch,
vulnerabilitiesEndpoint,
vulnerabilityFeedbackHelpPath,
},
...options,
});
};
afterEach(() => {
wrapper.destroy();
});
describe('on creation', () => {
beforeEach(() => {
factory();
});
it('dispatches the expected actions', () => {
expect(store.dispatch.mock.calls).toEqual([
['vulnerabilities/setSourceBranch', sourceBranch],
]);
});
it('renders the security dashboard', () => {
const dashboard = wrapper.find(SecurityDashboard);
expect(dashboard.exists()).toBe(true);
expect(dashboard.props()).toMatchObject({
lockToProject: { id: projectId },
pipelineId,
vulnerabilitiesEndpoint,
vulnerabilityFeedbackHelpPath,
});
});
});
describe('with a stubbed dashboard for slot testing', () => {
beforeEach(() => {
factory({
stubs: {
'security-dashboard': { template: '<div><slot name="emptyState"></slot></div>' },
},
});
});
it('renders empty state component with correct props', () => {
const emptyState = wrapper.find(GlEmptyState);
expect(emptyState.attributes('title')).toBe('No vulnerabilities found for this pipeline');
});
});
});
...@@ -13,6 +13,8 @@ import mockDataVulnerabilities from './data/mock_data_vulnerabilities.json'; ...@@ -13,6 +13,8 @@ import mockDataVulnerabilities from './data/mock_data_vulnerabilities.json';
import mockDataVulnerabilitiesCount from './data/mock_data_vulnerabilities_count.json'; import mockDataVulnerabilitiesCount from './data/mock_data_vulnerabilities_count.json';
import mockDataVulnerabilitiesHistory from './data/mock_data_vulnerabilities_history.json'; import mockDataVulnerabilitiesHistory from './data/mock_data_vulnerabilities_history.json';
const sourceBranch = 'feature-branch-1';
describe('vulnerabilities count actions', () => { describe('vulnerabilities count actions', () => {
const data = mockDataVulnerabilitiesCount; const data = mockDataVulnerabilitiesCount;
const params = { filters: { type: ['sast'] } }; const params = { filters: { type: ['sast'] } };
...@@ -43,6 +45,24 @@ describe('vulnerabilities count actions', () => { ...@@ -43,6 +45,24 @@ describe('vulnerabilities count actions', () => {
}); });
}); });
describe('setSourceBranch', () => {
it('should commit the correct mutation', done => {
testAction(
actions.setSourceBranch,
sourceBranch,
state,
[
{
type: types.SET_SOURCE_BRANCH,
payload: sourceBranch,
},
],
[],
done,
);
});
});
describe('setVulnerabilitiesCountEndpoint', () => { describe('setVulnerabilitiesCountEndpoint', () => {
it('should commit the correct mutuation', done => { it('should commit the correct mutuation', done => {
const endpoint = 'fakepath.json'; const endpoint = 'fakepath.json';
......
...@@ -21,6 +21,16 @@ describe('vulnerabilities module mutations', () => { ...@@ -21,6 +21,16 @@ describe('vulnerabilities module mutations', () => {
}); });
}); });
describe('SET_SOURCE_BRANCH', () => {
const sourceBranch = 'feature-branch-1';
it(`should set the sourceBranch to ${sourceBranch}`, () => {
mutations[types.SET_SOURCE_BRANCH](state, sourceBranch);
expect(state.sourceBranch).toBe(sourceBranch);
});
});
describe('SET_VULNERABILITIES_ENDPOINT', () => { describe('SET_VULNERABILITIES_ENDPOINT', () => {
it('should set `vulnerabilitiesEndpoint` to `fakepath.json`', () => { it('should set `vulnerabilitiesEndpoint` to `fakepath.json`', () => {
const endpoint = 'fakepath.json'; const endpoint = 'fakepath.json';
......
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