Commit a134363a authored by Alexander Turinske's avatar Alexander Turinske

Add split button for new vulnerability actions

- add split button that allows
  for the creation of an issue and creation of a merge
  request
- add logic to conditionally render split button with single button
- add tests
- abstract out feedback types strings to the constants
  file to be more adjustable
- add changelog
parent bd056f74
---
title: Add ability to create merge request from vulnerability page
merge_request: 31620
author:
type: added
...@@ -8,7 +8,7 @@ function createHeaderApp() { ...@@ -8,7 +8,7 @@ function createHeaderApp() {
const pipeline = JSON.parse(el.dataset.pipelineJson); const pipeline = JSON.parse(el.dataset.pipelineJson);
const finding = JSON.parse(el.dataset.findingJson); const finding = JSON.parse(el.dataset.findingJson);
const { projectFingerprint, createIssueUrl } = el.dataset; const { projectFingerprint, createIssueUrl, createMrUrl } = el.dataset;
return new Vue({ return new Vue({
el, el,
...@@ -16,6 +16,7 @@ function createHeaderApp() { ...@@ -16,6 +16,7 @@ function createHeaderApp() {
render: h => render: h =>
h(HeaderApp, { h(HeaderApp, {
props: { props: {
createMrUrl,
initialVulnerability, initialVulnerability,
finding, finding,
pipeline, pipeline,
......
...@@ -9,8 +9,9 @@ import UsersCache from '~/lib/utils/users_cache'; ...@@ -9,8 +9,9 @@ import UsersCache from '~/lib/utils/users_cache';
import ResolutionAlert from './resolution_alert.vue'; import ResolutionAlert from './resolution_alert.vue';
import VulnerabilityStateDropdown from './vulnerability_state_dropdown.vue'; import VulnerabilityStateDropdown from './vulnerability_state_dropdown.vue';
import StatusDescription from './status_description.vue'; import StatusDescription from './status_description.vue';
import { VULNERABILITY_STATE_OBJECTS, HEADER_ACTION_BUTTONS } from '../constants'; import { VULNERABILITY_STATE_OBJECTS, FEEDBACK_TYPES, HEADER_ACTION_BUTTONS } from '../constants';
import VulnerabilitiesEventBus from './vulnerabilities_event_bus'; import VulnerabilitiesEventBus from './vulnerabilities_event_bus';
import SplitButton from 'ee/vue_shared/security_reports/components/split_button.vue';
export default { export default {
name: 'VulnerabilityHeader', name: 'VulnerabilityHeader',
...@@ -19,10 +20,15 @@ export default { ...@@ -19,10 +20,15 @@ export default {
GlLoadingIcon, GlLoadingIcon,
ResolutionAlert, ResolutionAlert,
VulnerabilityStateDropdown, VulnerabilityStateDropdown,
SplitButton,
StatusDescription, StatusDescription,
}, },
props: { props: {
createMrUrl: {
type: String,
required: true,
},
initialVulnerability: { initialVulnerability: {
type: Object, type: Object,
required: true, required: true,
...@@ -59,6 +65,10 @@ export default { ...@@ -59,6 +65,10 @@ export default {
actionButtons() { actionButtons() {
const buttons = []; const buttons = [];
if (this.canCreateMergeRequest) {
buttons.push(HEADER_ACTION_BUTTONS.mergeRequestCreation);
}
if (!this.hasIssue) { if (!this.hasIssue) {
buttons.push(HEADER_ACTION_BUTTONS.issueCreation); buttons.push(HEADER_ACTION_BUTTONS.issueCreation);
} }
...@@ -68,6 +78,17 @@ export default { ...@@ -68,6 +78,17 @@ export default {
hasIssue() { hasIssue() {
return Boolean(this.finding.issue_feedback?.issue_iid); return Boolean(this.finding.issue_feedback?.issue_iid);
}, },
hasRemediation() {
const { remediations } = this.finding;
return Boolean(remediations && remediations[0]?.diff?.length > 0);
},
canCreateMergeRequest() {
return (
!this.finding.merge_request_feedback?.merge_request_path &&
Boolean(this.createMrUrl) &&
this.hasRemediation
);
},
statusBoxStyle() { statusBoxStyle() {
// Get the badge variant based on the vulnerability state, defaulting to 'expired'. // Get the badge variant based on the vulnerability state, defaulting to 'expired'.
return VULNERABILITY_STATE_OBJECTS[this.vulnerability.state]?.statusBoxStyle || 'expired'; return VULNERABILITY_STATE_OBJECTS[this.vulnerability.state]?.statusBoxStyle || 'expired';
...@@ -132,7 +153,7 @@ export default { ...@@ -132,7 +153,7 @@ export default {
axios axios
.post(this.createIssueUrl, { .post(this.createIssueUrl, {
vulnerability_feedback: { vulnerability_feedback: {
feedback_type: 'issue', feedback_type: FEEDBACK_TYPES.ISSUE,
category: this.vulnerability.report_type, category: this.vulnerability.report_type,
project_fingerprint: this.projectFingerprint, project_fingerprint: this.projectFingerprint,
vulnerability_data: { vulnerability_data: {
...@@ -153,6 +174,32 @@ export default { ...@@ -153,6 +174,32 @@ export default {
); );
}); });
}, },
createMergeRequest() {
this.isProcessingAction = true;
axios
.post(this.createMrUrl, {
vulnerability_feedback: {
feedback_type: FEEDBACK_TYPES.MERGE_REQUEST,
category: this.vulnerability.report_type,
project_fingerprint: this.projectFingerprint,
vulnerability_data: {
...this.vulnerability,
...this.finding,
category: this.vulnerability.report_type,
target_branch: this.pipeline.sourceBranch,
},
},
})
.then(({ data: { merge_request_path } }) => {
redirectTo(merge_request_path);
})
.catch(() => {
this.isProcessingAction = false;
createFlash(
s__('ciReport|There was an error creating the merge request. Please try again.'),
);
});
},
}, },
}; };
</script> </script>
...@@ -194,8 +241,16 @@ export default { ...@@ -194,8 +241,16 @@ export default {
:initial-state="vulnerability.state" :initial-state="vulnerability.state"
@change="changeVulnerabilityState" @change="changeVulnerabilityState"
/> />
<split-button
v-if="actionButtons.length > 1"
:buttons="actionButtons"
:disabled="isProcessingAction"
class="js-split-button"
@createMergeRequest="createMergeRequest"
@createIssue="createIssue"
/>
<gl-deprecated-button <gl-deprecated-button
v-if="actionButtons.length > 0" v-else-if="actionButtons.length > 0"
class="ml-2" class="ml-2"
variant="success" variant="success"
category="secondary" category="secondary"
......
...@@ -33,6 +33,17 @@ export const VULNERABILITIES_PER_PAGE = 20; ...@@ -33,6 +33,17 @@ export const VULNERABILITIES_PER_PAGE = 20;
export const HEADER_ACTION_BUTTONS = { export const HEADER_ACTION_BUTTONS = {
issueCreation: { issueCreation: {
name: s__('ciReport|Create issue'), name: s__('ciReport|Create issue'),
tagline: s__('ciReport|Investigate this vulnerability by creating an issue'),
action: 'createIssue', action: 'createIssue',
}, },
mergeRequestCreation: {
name: s__('ciReport|Resolve with merge request'),
tagline: s__('ciReport|Automatically apply the patch in a new branch'),
action: 'createMergeRequest',
},
};
export const FEEDBACK_TYPES = {
ISSUE: 'issue',
MERGE_REQUEST: 'merge_request',
}; };
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