Commit a4a5fdcc authored by Alexander Turinske's avatar Alexander Turinske Committed by Michał Zając

Update vulnerability page for new data structure

- In an effort to cut down on the amount of data coming from the
  backend, the data sent up has been updated to be include
  more targeted information in a new format
- update the frontend for the new data structure
- update status_description component for new data
- pipeline is now a part of the vulnerability object
  so an extra prop is no longer needed
- fix error for undefined remediations
- update tests
parent 7cd933ad
...@@ -2,14 +2,11 @@ import Vue from 'vue'; ...@@ -2,14 +2,11 @@ import Vue from 'vue';
import HeaderApp from 'ee/vulnerabilities/components/header.vue'; import HeaderApp from 'ee/vulnerabilities/components/header.vue';
import DetailsApp from 'ee/vulnerabilities/components/details.vue'; import DetailsApp from 'ee/vulnerabilities/components/details.vue';
import FooterApp from 'ee/vulnerabilities/components/footer.vue'; import FooterApp from 'ee/vulnerabilities/components/footer.vue';
import { VULNERABILITY_STATE_OBJECTS } from 'ee/vulnerabilities/constants';
function createHeaderApp() { function createHeaderApp() {
const el = document.getElementById('js-vulnerability-header'); const el = document.getElementById('js-vulnerability-header');
const initialVulnerability = JSON.parse(el.dataset.vulnerabilityJson); const vulnerability = JSON.parse(el.dataset.vulnerability);
const pipeline = JSON.parse(el.dataset.pipelineJson);
const finding = JSON.parse(el.dataset.findingJson);
const { projectFingerprint, createIssueUrl, createMrUrl } = el.dataset;
return new Vue({ return new Vue({
el, el,
...@@ -17,12 +14,7 @@ function createHeaderApp() { ...@@ -17,12 +14,7 @@ function createHeaderApp() {
render: h => render: h =>
h(HeaderApp, { h(HeaderApp, {
props: { props: {
createMrUrl, initialVulnerability: vulnerability,
initialVulnerability,
finding,
pipeline,
projectFingerprint,
createIssueUrl,
}, },
}), }),
}); });
...@@ -46,12 +38,22 @@ function createFooterApp() { ...@@ -46,12 +38,22 @@ function createFooterApp() {
return false; return false;
} }
const { vulnerabilityFeedbackHelpPath, hasMr, discussionsUrl, notesUrl } = el.dataset; const {
const vulnerability = JSON.parse(el.dataset.vulnerabilityJson); vulnerability_feedback_help_path: vulnerabilityFeedbackHelpPath,
const finding = JSON.parse(el.dataset.findingJson); has_mr: hasMr,
discussions_url: discussionsUrl,
state,
issue_feedback: feedback,
project,
remediations,
solution,
} = JSON.parse(el.dataset.vulnerability);
const remediation = remediations?.length ? remediations[0] : null;
const hasDownload = Boolean( const hasDownload = Boolean(
vulnerability.state !== 'resolved' && finding.remediation?.diff?.length && !hasMr, state !== VULNERABILITY_STATE_OBJECTS.resolved.state && remediation?.diff?.length && !hasMr,
); );
const hasRemediation = Boolean(remediation);
const props = { const props = {
discussionsUrl, discussionsUrl,
...@@ -62,9 +64,15 @@ function createFooterApp() { ...@@ -62,9 +64,15 @@ function createFooterApp() {
remediation: finding.remediation, remediation: finding.remediation,
hasDownload, hasDownload,
hasMr, hasMr,
hasRemediation,
vulnerabilityFeedbackHelpPath, vulnerabilityFeedbackHelpPath,
isStandaloneVulnerability: true, isStandaloneVulnerability: true,
}, },
feedback,
project: {
url: project.full_path,
value: project.full_name,
},
}; };
return new Vue({ return new Vue({
......
...@@ -26,30 +26,10 @@ export default { ...@@ -26,30 +26,10 @@ export default {
}, },
props: { props: {
createMrUrl: {
type: String,
required: true,
},
initialVulnerability: { initialVulnerability: {
type: Object, type: Object,
required: true, required: true,
}, },
finding: {
type: Object,
required: true,
},
pipeline: {
type: Object,
required: true,
},
createIssueUrl: {
type: String,
required: true,
},
projectFingerprint: {
type: String,
required: true,
},
}, },
data() { data() {
...@@ -88,16 +68,16 @@ export default { ...@@ -88,16 +68,16 @@ export default {
); );
}, },
hasIssue() { hasIssue() {
return Boolean(this.finding.issue_feedback?.issue_iid); return Boolean(this.vulnerability.issue_feedback?.issue_iid);
}, },
hasRemediation() { hasRemediation() {
const { remediations } = this.finding; const { remediations } = this.vulnerability;
return Boolean(remediations && remediations[0]?.diff?.length > 0); return Boolean(remediations && remediations[0]?.diff?.length > 0);
}, },
canCreateMergeRequest() { canCreateMergeRequest() {
return ( return (
!this.finding.merge_request_feedback?.merge_request_path && !this.vulnerability.merge_request_feedback?.merge_request_path &&
Boolean(this.createMrUrl) && Boolean(this.vulnerability.create_mr_url) &&
this.hasRemediation this.hasRemediation
); );
}, },
...@@ -163,17 +143,23 @@ export default { ...@@ -163,17 +143,23 @@ export default {
}, },
createIssue() { createIssue() {
this.isProcessingAction = true; this.isProcessingAction = true;
const {
report_type: category,
project_fingerprint: projectFingerprint,
id,
} = this.vulnerability;
axios axios
.post(this.createIssueUrl, { .post(this.vulnerability.create_issue_url, {
vulnerability_feedback: { vulnerability_feedback: {
feedback_type: FEEDBACK_TYPES.ISSUE, feedback_type: FEEDBACK_TYPES.ISSUE,
category: this.vulnerability.report_type, category,
project_fingerprint: this.projectFingerprint, project_fingerprint: projectFingerprint,
vulnerability_data: { vulnerability_data: {
...this.vulnerability, ...this.vulnerability,
...this.finding, category,
category: this.vulnerability.report_type, vulnerability_id: id,
vulnerability_id: this.vulnerability.id,
}, },
}, },
}) })
...@@ -189,17 +175,23 @@ export default { ...@@ -189,17 +175,23 @@ export default {
}, },
createMergeRequest() { createMergeRequest() {
this.isProcessingAction = true; this.isProcessingAction = true;
const {
report_type: category,
pipeline: { sourceBranch },
project_fingerprint: projectFingerprint,
} = this.vulnerability;
axios axios
.post(this.createMrUrl, { .post(this.vulnerability.create_mr_url, {
vulnerability_feedback: { vulnerability_feedback: {
feedback_type: FEEDBACK_TYPES.MERGE_REQUEST, feedback_type: FEEDBACK_TYPES.MERGE_REQUEST,
category: this.vulnerability.report_type, category,
project_fingerprint: this.projectFingerprint, project_fingerprint: projectFingerprint,
vulnerability_data: { vulnerability_data: {
...this.vulnerability, ...this.vulnerability,
...this.finding, category,
category: this.vulnerability.report_type, target_branch: sourceBranch,
target_branch: this.pipeline.sourceBranch,
}, },
}, },
}) })
...@@ -214,7 +206,10 @@ export default { ...@@ -214,7 +206,10 @@ export default {
}); });
}, },
downloadPatch() { downloadPatch() {
download({ fileData: this.finding.remediations[0].diff, fileName: `remediation.patch` }); download({
fileData: this.vulnerability.remediations[0].diff,
fileName: `remediation.patch`,
});
}, },
}, },
}; };
...@@ -243,7 +238,6 @@ export default { ...@@ -243,7 +238,6 @@ export default {
<status-description <status-description
class="issuable-meta" class="issuable-meta"
:vulnerability="vulnerability" :vulnerability="vulnerability"
:pipeline="pipeline"
:user="user" :user="user"
:is-loading-vulnerability="isLoadingVulnerability" :is-loading-vulnerability="isLoadingVulnerability"
:is-loading-user="isLoadingUser" :is-loading-user="isLoadingUser"
......
...@@ -19,10 +19,6 @@ export default { ...@@ -19,10 +19,6 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
pipeline: {
type: Object,
required: true,
},
user: { user: {
type: Object, type: Object,
required: false, required: false,
...@@ -42,7 +38,7 @@ export default { ...@@ -42,7 +38,7 @@ export default {
time() { time() {
const { state } = this.vulnerability; const { state } = this.vulnerability;
return state === 'detected' return state === 'detected'
? this.pipeline.created_at ? this.vulnerability.pipeline.created_at
: this.vulnerability[`${this.vulnerability.state}_at`]; : this.vulnerability[`${this.vulnerability.state}_at`];
}, },
...@@ -86,9 +82,9 @@ export default { ...@@ -86,9 +82,9 @@ export default {
img-css-classes="avatar-inline" img-css-classes="avatar-inline"
/> />
</template> </template>
<template v-if="pipeline" #pipelineLink> <template v-if="vulnerability.pipeline" #pipelineLink>
<gl-link :href="pipeline.url" target="_blank" class="link"> <gl-link :href="vulnerability.pipeline.url" target="_blank" class="link">
{{ pipeline.id }} {{ vulnerability.pipeline.id }}
</gl-link> </gl-link>
</template> </template>
</gl-sprintf> </gl-sprintf>
......
...@@ -3,9 +3,7 @@ ...@@ -3,9 +3,7 @@
- breadcrumb_title @vulnerability.id - breadcrumb_title @vulnerability.id
- page_title @vulnerability.title - page_title @vulnerability.title
- page_description @vulnerability.description - page_description @vulnerability.description
- finding = @vulnerability.finding - vulnerability_init_details = { vulnerability: vulnerability_details_json(@vulnerability, @pipeline)}
- location = finding.location
- vulnerability_init_details = vulnerability_details_json(@vulnerability, @pipeline)
#js-vulnerability-header{ data: vulnerability_init_details } #js-vulnerability-header{ data: vulnerability_init_details }
#js-vulnerability-details{ data: vulnerability_init_details } #js-vulnerability-details{ data: vulnerability_init_details }
......
...@@ -27,32 +27,30 @@ describe('Vulnerability status description component', () => { ...@@ -27,32 +27,30 @@ describe('Vulnerability status description component', () => {
const createDate = value => (value ? new Date(value) : new Date()).toISOString(); const createDate = value => (value ? new Date(value) : new Date()).toISOString();
const createWrapper = ({ const createWrapper = ({
vulnerability = {}, vulnerability = { pipeline: {} },
pipeline = {},
user, user,
isLoadingVulnerability = false, isLoadingVulnerability = false,
isLoadingUser = false, isLoadingUser = false,
} = {}) => { } = {}) => {
const v = vulnerability; const v = vulnerability;
const p = pipeline;
// Automatically create the ${v.state}_at property if it doesn't exist. Otherwise, every test would need to create // Automatically create the ${v.state}_at property if it doesn't exist. Otherwise, every test would need to create
// it manually for the component to mount properly. // it manually for the component to mount properly.
if (v.state === 'detected') { if (v.state === 'detected') {
p.created_at = p.created_at || createDate(); v.pipeline.created_at = v.pipeline.created_at || createDate();
} else { } else {
const propertyName = `${v.state}_at`; const propertyName = `${v.state}_at`;
v[propertyName] = v[propertyName] || createDate(); v[propertyName] = v[propertyName] || createDate();
} }
wrapper = mount(StatusText, { wrapper = mount(StatusText, {
propsData: { vulnerability, pipeline, user, isLoadingVulnerability, isLoadingUser }, propsData: { vulnerability, user, isLoadingVulnerability, isLoadingUser },
}); });
}; };
describe('state text', () => { describe('state text', () => {
it.each(ALL_STATES)('shows the correct string for the vulnerability state "%s"', state => { it.each(ALL_STATES)('shows the correct string for the vulnerability state "%s"', state => {
createWrapper({ vulnerability: { state } }); createWrapper({ vulnerability: { state, pipeline: {} } });
expect(wrapper.text()).toMatch(new RegExp(`^${capitalize(state)}`)); expect(wrapper.text()).toMatch(new RegExp(`^${capitalize(state)}`));
}); });
...@@ -62,8 +60,7 @@ describe('Vulnerability status description component', () => { ...@@ -62,8 +60,7 @@ describe('Vulnerability status description component', () => {
it('uses the pipeline created date when the vulnerability state is "detected"', () => { it('uses the pipeline created date when the vulnerability state is "detected"', () => {
const pipelineDateString = createDate('2001'); const pipelineDateString = createDate('2001');
createWrapper({ createWrapper({
vulnerability: { state: 'detected' }, vulnerability: { state: 'detected', pipeline: { created_at: pipelineDateString } },
pipeline: { created_at: pipelineDateString },
}); });
expect(timeAgo().props('time')).toBe(pipelineDateString); expect(timeAgo().props('time')).toBe(pipelineDateString);
...@@ -75,8 +72,11 @@ describe('Vulnerability status description component', () => { ...@@ -75,8 +72,11 @@ describe('Vulnerability status description component', () => {
state => { state => {
const expectedDate = createDate(); const expectedDate = createDate();
createWrapper({ createWrapper({
vulnerability: { state, [`${state}_at`]: expectedDate }, vulnerability: {
state,
pipeline: { created_at: 'pipeline_created_at' }, pipeline: { created_at: 'pipeline_created_at' },
[`${state}_at`]: expectedDate,
},
}); });
expect(timeAgo().props('time')).toBe(expectedDate); expect(timeAgo().props('time')).toBe(expectedDate);
...@@ -87,8 +87,7 @@ describe('Vulnerability status description component', () => { ...@@ -87,8 +87,7 @@ describe('Vulnerability status description component', () => {
describe('pipeline link', () => { describe('pipeline link', () => {
it('shows the pipeline link when the vulnerability state is "detected"', () => { it('shows the pipeline link when the vulnerability state is "detected"', () => {
createWrapper({ createWrapper({
vulnerability: { state: 'detected' }, vulnerability: { state: 'detected', pipeline: { url: 'pipeline/url' } },
pipeline: { url: 'pipeline/url' },
}); });
expect(pipelineLink().attributes('href')).toBe('pipeline/url'); expect(pipelineLink().attributes('href')).toBe('pipeline/url');
...@@ -98,8 +97,7 @@ describe('Vulnerability status description component', () => { ...@@ -98,8 +97,7 @@ describe('Vulnerability status description component', () => {
'does not show the pipeline link when the vulnerability state is "%s"', 'does not show the pipeline link when the vulnerability state is "%s"',
state => { state => {
createWrapper({ createWrapper({
vulnerability: { state }, vulnerability: { state, pipeline: { url: 'pipeline/url' } },
pipeline: { url: 'pipeline/url' },
}); });
expect(pipelineLink().exists()).toBe(false); // The user avatar should be shown instead, those tests are handled separately. expect(pipelineLink().exists()).toBe(false); // The user avatar should be shown instead, those tests are handled separately.
...@@ -152,7 +150,7 @@ describe('Vulnerability status description component', () => { ...@@ -152,7 +150,7 @@ describe('Vulnerability status description component', () => {
}); });
it('hides the skeleton loader and shows everything else when the vulnerability is not loading', () => { it('hides the skeleton loader and shows everything else when the vulnerability is not loading', () => {
createWrapper({ vulnerability: { state: 'detected' } }); createWrapper({ vulnerability: { state: 'detected', pipeline: {} } });
expect(skeletonLoader().exists()).toBe(false); expect(skeletonLoader().exists()).toBe(false);
expect(timeAgo().exists()).toBe(true); expect(timeAgo().exists()).toBe(true);
......
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