Commit f5f3ca18 authored by Jonathan Schafer's avatar Jonathan Schafer Committed by Mayra Cabrera

Use issue creation for vulnerabilities

This MR changes the call that is used by the front end
to allow the user to edit an issue before creation from
a vulnerability.
parent 473ee64b
......@@ -47,9 +47,9 @@ and allows you to comment on a change.
You can create an issue for a vulnerability by selecting the **Create issue** button.
This creates a [confidential issue](../../project/issues/confidential_issues.md) in the
project the vulnerability came from and pre-populates it with useful information from
the vulnerability report. After the issue is created, GitLab redirects you to the
This allows the user to create a [confidential issue](../../project/issues/confidential_issues.md)
in the project the vulnerability came from. Fields are pre-populated with pertinent information
from the vulnerability report. After the issue is created, GitLab redirects you to the
issue page so you can edit, assign, or comment on the issue.
## Link issues to the vulnerability
......
<script>
import axios from 'axios';
import { GlButton, GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import { GlButton } from '@gitlab/ui';
import RelatedIssuesStore from '~/related_issues/stores/related_issues_store';
import RelatedIssuesBlock from '~/related_issues/components/related_issues_block.vue';
import { issuableTypesMap, PathIdSeparator } from '~/related_issues/constants';
......@@ -15,9 +15,6 @@ export default {
components: {
RelatedIssuesBlock,
GlButton,
GlAlert,
GlSprintf,
GlLink,
},
props: {
endpoint: {
......@@ -60,7 +57,7 @@ export default {
return Boolean(this.state.relatedIssues.find(i => i.lockIssueRemoval));
},
canCreateIssue() {
return !this.isIssueAlreadyCreated && !this.isFetching && Boolean(this.createIssueUrl);
return !this.isIssueAlreadyCreated && !this.isFetching && Boolean(this.newIssueUrl);
},
},
inject: {
......@@ -70,7 +67,7 @@ export default {
projectFingerprint: {
default: '',
},
createIssueUrl: {
newIssueUrl: {
default: '',
},
reportType: {
......@@ -89,17 +86,7 @@ export default {
methods: {
createIssue() {
this.isProcessingAction = true;
this.errorCreatingIssue = false;
return axios
.post(this.createIssueUrl)
.then(({ data: { web_url } }) => {
redirectTo(web_url);
})
.catch(() => {
this.isProcessingAction = false;
this.errorCreatingIssue = true;
});
redirectTo(this.newIssueUrl, { params: { vulnerability_id: this.vulnerabilityId } });
},
toggleFormVisibility() {
this.isFormVisible = !this.isFormVisible;
......@@ -218,28 +205,6 @@ export default {
<template>
<div>
<gl-alert
v-if="errorCreatingIssue"
variant="danger"
class="gl-mt-5"
@dismiss="errorCreatingIssue = false"
>
<p class="gl-font-weight-bold gl-mb-2">{{ $options.i18n.createIssueErrorTitle }}</p>
<p class="gl-mb-0">
<gl-sprintf :message="$options.i18n.createIssueErrorBody">
<template #tracking="{ content }">
<gl-link class="gl-display-inline-block" :href="issueTrackingHelpPath" target="_blank">
{{ content }}
</gl-link>
</template>
<template #permissions="{ content }">
<gl-link class="gl-display-inline-block" :href="permissionsHelpPath" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</p>
</gl-alert>
<related-issues-block
:help-path="helpPath"
:is-fetching="isFetching"
......
......@@ -15,7 +15,7 @@ export default el => {
provide: {
reportType: vulnerability.reportType,
createIssueUrl: vulnerability.createIssueUrl,
newIssueUrl: vulnerability.newIssueUrl,
projectFingerprint: vulnerability.projectFingerprint,
vulnerabilityId: vulnerability.id,
issueTrackingHelpPath: vulnerability.issueTrackingHelpPath,
......
......@@ -10,7 +10,7 @@ module VulnerabilitiesHelper
result = {
timestamp: Time.now.to_i,
create_issue_url: create_issue_url_for(vulnerability),
new_issue_url: new_issue_url_for(vulnerability),
create_jira_issue_url: create_jira_issue_url_for(vulnerability),
related_jira_issues_path: project_integrations_jira_issues_path(vulnerability.project, vulnerability_ids: [vulnerability.id]),
has_mr: !!vulnerability.finding.merge_request_feedback.try(:merge_request_iid),
......@@ -27,10 +27,10 @@ module VulnerabilitiesHelper
result.merge(vulnerability_data(vulnerability), vulnerability_finding_data(vulnerability))
end
def create_issue_url_for(vulnerability)
def new_issue_url_for(vulnerability)
return unless vulnerability.project.issues_enabled?
create_issue_project_security_vulnerability_path(vulnerability.project, vulnerability)
new_project_issue_path(vulnerability.project, { vulnerability_id: vulnerability.id })
end
def create_jira_issue_url_for(vulnerability)
......
---
title: Creating an issue from a vulnerability takes user to the new issue page
merge_request: 48926
author:
type: changed
......@@ -30,7 +30,7 @@ describe('Vulnerability Header', () => {
reportType: 'sast',
state: 'detected',
createMrUrl: '/create_mr_url',
createIssueUrl: '/create_issue_url',
newIssueUrl: '/new_issue_url',
projectFingerprint: 'abc123',
pipeline: {
id: 2,
......
import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import RelatedIssues from 'ee/vulnerabilities/components/related_issues.vue';
import waitForPromises from 'helpers/wait_for_promises';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
......@@ -25,7 +23,7 @@ describe('Vulnerability related issues component', () => {
};
const vulnerabilityId = 5131;
const createIssueUrl = '/create/issue';
const newIssueUrl = '/new/issue';
const projectFingerprint = 'project-fingerprint';
const issueTrackingHelpPath = '/help/issue/tracking';
const permissionsHelpPath = '/help/permissions';
......@@ -40,7 +38,7 @@ describe('Vulnerability related issues component', () => {
provide: {
vulnerabilityId,
projectFingerprint,
createIssueUrl,
newIssueUrl,
reportType,
issueTrackingHelpPath,
permissionsHelpPath,
......@@ -59,7 +57,6 @@ describe('Vulnerability related issues component', () => {
const blockProp = prop => relatedIssuesBlock().props(prop);
const blockEmit = (eventName, data) => relatedIssuesBlock().vm.$emit(eventName, data);
const findCreateIssueButton = () => wrapper.find({ ref: 'createIssue' });
const findAlert = () => wrapper.find(GlAlert);
afterEach(() => {
wrapper.destroy();
......@@ -283,14 +280,10 @@ describe('Vulnerability related issues component', () => {
});
describe('when linked issue is not yet created', () => {
const failCreateIssueAction = async () => {
mockAxios.onPost(createIssueUrl).reply(500);
expect(findAlert().exists()).toBe(false);
findCreateIssueButton().vm.$emit('click');
await waitForPromises();
};
let redirectToSpy;
beforeEach(async () => {
redirectToSpy = jest.spyOn(urlUtility, 'redirectTo').mockImplementation(() => {});
mockAxios.onGet(propsData.endpoint).replyOnce(httpStatusCodes.OK, [issue1, issue2]);
createWrapper({ stubs: { RelatedIssuesBlock } });
await axios.waitForAll();
......@@ -300,34 +293,11 @@ describe('Vulnerability related issues component', () => {
expect(findCreateIssueButton().exists()).toBe(true);
});
it('calls create issue endpoint on click and redirects to new issue', async () => {
const issueUrl = `/group/project/-/security/vulnerabilities/${vulnerabilityId}/create_issue`;
const spy = jest.spyOn(urlUtility, 'redirectTo');
mockAxios.onPost(propsData.createIssueUrl).reply(200, {
web_url: issueUrl,
});
it('calls new issue endpoint on click', () => {
findCreateIssueButton().vm.$emit('click');
await waitForPromises();
const [postRequest] = mockAxios.history.post;
expect(mockAxios.history.post).toHaveLength(1);
expect(postRequest.url).toBe(createIssueUrl);
expect(spy).toHaveBeenCalledWith(issueUrl);
});
it('shows an error message when issue creation fails', async () => {
await failCreateIssueAction();
expect(mockAxios.history.post).toHaveLength(1);
expect(findAlert().exists()).toBe(true);
});
it('dismisses the error message', async () => {
await failCreateIssueAction();
findAlert().vm.$emit('dismiss');
await wrapper.vm.$nextTick();
expect(findAlert().exists()).toBe(false);
expect(redirectToSpy).toHaveBeenCalledWith(newIssueUrl, {
params: { vulnerability_id: vulnerabilityId },
});
});
});
......@@ -335,7 +305,7 @@ describe('Vulnerability related issues component', () => {
it('hides the "Create Issue" button', () => {
createWrapper({
provide: {
createIssueUrl: undefined,
newIssueUrl: undefined,
},
});
......
......@@ -16,7 +16,7 @@ describe('Vulnerability', () => {
report_type: 'sast',
state: 'detected',
create_mr_url: '/create_mr_url',
create_issue_url: '/create_issue_url',
new_issue_url: '/new_issue_url',
project_fingerprint: 'abc123',
pipeline: {
id: 2,
......
......@@ -58,7 +58,7 @@ RSpec.describe VulnerabilitiesHelper do
it 'has expected vulnerability properties' do
expect(subject).to include(
timestamp: Time.now.to_i,
create_issue_url: "/#{project.full_path}/-/security/vulnerabilities/#{vulnerability.id}/create_issue",
new_issue_url: "/#{project.full_path}/-/issues/new?vulnerability_id=#{vulnerability.id}",
create_jira_issue_url: nil,
related_jira_issues_path: "/#{project.full_path}/-/integrations/jira/issues?vulnerability_ids%5B%5D=#{vulnerability.id}",
has_mr: anything,
......@@ -76,8 +76,8 @@ RSpec.describe VulnerabilitiesHelper do
allow(project).to receive(:issues_enabled?).and_return(false)
end
it 'has `create_issue_url` set as nil' do
expect(subject).to include(create_issue_url: nil)
it 'has `new_issue_url` set as nil' do
expect(subject).to include(new_issue_url: nil)
end
end
end
......
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