Commit bc449d6c authored by Illya Klymov's avatar Illya Klymov

Merge branch '216873-add-mr-note-to-standalone-vulnerability-page' into 'master'

Display MR note on standalone vulnerability page

See merge request gitlab-org/gitlab!34146
parents ebe4a389 51f19d4d
......@@ -55,14 +55,19 @@ generates for you. GitLab supports the following scanners:
is only available for Node.js projects managed with `yarn`.
- [Container Scanning](../container_scanning/index.md).
When an automatic solution is available, the button in the header will show "Resolve with merge request":
![Resolve with Merge Request button](img/standalone_vulnerability_page_merge_request_button_v13_1.png)
Selecting the button will create a merge request with the automatic solution.
### Manually applying a suggested patch
To apply a patch automatically generated by GitLab to fix a vulnerability:
To manually apply the patch that was generated by GitLab for a vulnerability, select the dropdown arrow on the "Resolve
with merge request" button, then select the "Download patch to resolve" option:
![Resolve with Merge Request button dropdown](img/standalone_vulnerability_page_merge_request_button_dropdown_v13_1.png)
1. Open the issue created in [Create issue](#creating-an-issue-for-a-vulnerability).
1. In the **Issue description**, scroll to **Solution** and download the linked patch file.
1. Ensure your local project has the same commit checked out that was used to generate the patch.
1. Run `git apply remediation.patch` to apply the patch.
1. Verify and commit the changes to your branch.
This will change the button text to "Download patch to resolve". Click on it to download the patch:
![Apply patch for dependency scanning](../img/vulnerability_solution.png)
![Download patch button](img/standalone_vulnerability_page_download_patch_button_v13_1.png)
......@@ -49,28 +49,22 @@ function createFooterApp() {
const { vulnerabilityFeedbackHelpPath, hasMr, discussionsUrl, notesUrl } = el.dataset;
const vulnerability = JSON.parse(el.dataset.vulnerabilityJson);
const finding = JSON.parse(el.dataset.findingJson);
const { issue_feedback: feedback, remediation, solution } = finding;
const hasDownload = Boolean(
vulnerability.state !== 'resolved' && remediation?.diff?.length && !hasMr,
vulnerability.state !== 'resolved' && finding.remediation?.diff?.length && !hasMr,
);
const props = {
discussionsUrl,
notesUrl,
finding,
solutionInfo: {
solution,
remediation,
solution: finding.solution,
remediation: finding.remediation,
hasDownload,
hasMr,
hasRemediation: Boolean(remediation),
vulnerabilityFeedbackHelpPath,
isStandaloneVulnerability: true,
},
feedback,
project: {
url: finding.project.full_path,
value: finding.project.full_name,
},
};
return new Vue({
......
......@@ -22,7 +22,7 @@ export default {
},
computed: {
hasProjectUrl() {
return this.project?.value && this.project?.url;
return Boolean(this.project?.value && this.project?.url);
},
eventText() {
if (this.hasProjectUrl) {
......
......@@ -22,7 +22,7 @@ export default {
},
computed: {
hasProjectUrl() {
return this.project?.value && this.project?.url;
return Boolean(this.project?.value && this.project?.url);
},
eventText() {
if (this.hasProjectUrl) {
......
......@@ -6,27 +6,23 @@ import createFlash from '~/flash';
import { s__, __ } from '~/locale';
import IssueNote from 'ee/vue_shared/security_reports/components/issue_note.vue';
import SolutionCard from 'ee/vue_shared/security_reports/components/solution_card.vue';
import MergeRequestNote from 'ee/vue_shared/security_reports/components/merge_request_note.vue';
import HistoryEntry from './history_entry.vue';
import VulnerabilitiesEventBus from './vulnerabilities_event_bus';
export default {
name: 'VulnerabilityFooter',
components: { IssueNote, SolutionCard, HistoryEntry },
components: { IssueNote, SolutionCard, MergeRequestNote, HistoryEntry },
props: {
discussionsUrl: {
type: String,
required: true,
},
feedback: {
type: Object,
required: false,
default: null,
},
notesUrl: {
type: String,
required: true,
},
project: {
finding: {
type: Object,
required: true,
},
......@@ -53,11 +49,14 @@ export default {
return acc;
}, {});
},
hasIssue() {
return Boolean(this.feedback?.issue_iid);
},
hasSolution() {
return this.solutionInfo.solution || this.solutionInfo.hasRemediation;
return Boolean(this.solutionInfo.solution || this.solutionInfo.remediation);
},
project() {
return {
url: this.finding.project?.full_path,
value: this.finding.project?.full_name,
};
},
},
......@@ -156,8 +155,20 @@ export default {
<template>
<div>
<solution-card v-if="hasSolution" v-bind="solutionInfo" />
<div v-if="hasIssue" class="card">
<issue-note :feedback="feedback" :project="project" class="card-body" />
<div v-if="finding.issue_feedback || finding.merge_request_feedback" class="card">
<issue-note
v-if="finding.issue_feedback"
:feedback="finding.issue_feedback"
:project="project"
class="card-body"
/>
<merge-request-note
v-if="finding.merge_request_feedback"
:feedback="finding.merge_request_feedback"
:project="project"
class="card-body"
/>
</div>
<hr />
......
---
title: Add MR note to standalone vulnerability page
merge_request: 34146
author:
type: added
......@@ -4,7 +4,7 @@ import HistoryEntry from 'ee/vulnerabilities/components/history_entry.vue';
import VulnerabilitiesEventBus from 'ee/vulnerabilities/components/vulnerabilities_event_bus';
import SolutionCard from 'ee/vue_shared/security_reports/components/solution_card.vue';
import IssueNote from 'ee/vue_shared/security_reports/components/issue_note.vue';
import { TEST_HOST } from 'helpers/test_constants';
import MergeRequestNote from 'ee/vue_shared/security_reports/components/merge_request_note.vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
......@@ -27,13 +27,15 @@ describe('Vulnerability Footer', () => {
vulnerabilityFeedbackHelpPath:
'/help/user/application_security/index#interacting-with-the-vulnerabilities',
},
project: {
url: '/root/security-reports',
value: 'Administrator / Security Reports',
},
finding: {},
notesUrl: '/notes',
};
const project = {
full_path: '/root/security-reports',
full_name: 'Administrator / Security Reports',
};
const solutionInfoProp = {
hasDownload: true,
hasMr: false,
......@@ -44,19 +46,6 @@ describe('Vulnerability Footer', () => {
'/help/user/application_security/index#interacting-with-the-vulnerabilities',
};
const feedbackProps = {
author: {},
branch: null,
category: 'container_scanning',
created_at: '2020-03-18T00:10:49.527Z',
feedback_type: 'issue',
id: 36,
issue_iid: 22,
issue_url: `${TEST_HOST}/root/security-reports/-/issues/22`,
project_fingerprint: 'f7319ea35fc016e754e9549dd89b338aea4c72cc',
project_id: 19,
};
const createWrapper = (props = minimumProps) => {
wrapper = shallowMount(VulnerabilityFooter, {
propsData: props,
......@@ -91,19 +80,29 @@ describe('Vulnerability Footer', () => {
});
});
describe('issue history', () => {
it('does show issue history when there is one', () => {
createWrapper({ ...minimumProps, feedback: feedbackProps });
expect(wrapper.contains(IssueNote)).toBe(true);
expect(wrapper.find(IssueNote).props()).toMatchObject({
feedback: feedbackProps,
project: minimumProps.project,
describe.each`
type | prop | component
${'issue'} | ${'issue_feedback'} | ${IssueNote}
${'merge request'} | ${'merge_request_feedback'} | ${MergeRequestNote}
`('$type note', ({ prop, component }) => {
// The object itself does not matter, we just want to make sure it's passed to the issue note.
const feedback = {};
it('shows issue note when an issue exists for the vulnerability', () => {
createWrapper({ ...minimumProps, finding: { project, [prop]: feedback } });
expect(wrapper.contains(component)).toBe(true);
expect(wrapper.find(component).props()).toMatchObject({
feedback,
project: {
url: project.full_path,
value: project.full_name,
},
});
});
it('does not show issue history when there is not one', () => {
it('does not show issue note when there is no issue for the vulnerability', () => {
createWrapper();
expect(wrapper.contains(IssueNote)).toBe(false);
expect(wrapper.contains(component)).toBe(false);
});
});
......
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