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';
import HeaderApp from 'ee/vulnerabilities/components/header.vue';
import DetailsApp from 'ee/vulnerabilities/components/details.vue';
import FooterApp from 'ee/vulnerabilities/components/footer.vue';
import { VULNERABILITY_STATE_OBJECTS } from 'ee/vulnerabilities/constants';
function createHeaderApp() {
const el = document.getElementById('js-vulnerability-header');
const initialVulnerability = JSON.parse(el.dataset.vulnerabilityJson);
const pipeline = JSON.parse(el.dataset.pipelineJson);
const finding = JSON.parse(el.dataset.findingJson);
const { projectFingerprint, createIssueUrl, createMrUrl } = el.dataset;
const vulnerability = JSON.parse(el.dataset.vulnerability);
return new Vue({
el,
......@@ -17,12 +14,7 @@ function createHeaderApp() {
render: h =>
h(HeaderApp, {
props: {
createMrUrl,
initialVulnerability,
finding,
pipeline,
projectFingerprint,
createIssueUrl,
initialVulnerability: vulnerability,
},
}),
});
......@@ -46,12 +38,22 @@ function createFooterApp() {
return false;
}
const { vulnerabilityFeedbackHelpPath, hasMr, discussionsUrl, notesUrl } = el.dataset;
const vulnerability = JSON.parse(el.dataset.vulnerabilityJson);
const finding = JSON.parse(el.dataset.findingJson);
const {
vulnerability_feedback_help_path: vulnerabilityFeedbackHelpPath,
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(
vulnerability.state !== 'resolved' && finding.remediation?.diff?.length && !hasMr,
state !== VULNERABILITY_STATE_OBJECTS.resolved.state && remediation?.diff?.length && !hasMr,
);
const hasRemediation = Boolean(remediation);
const props = {
discussionsUrl,
......@@ -62,9 +64,15 @@ function createFooterApp() {
remediation: finding.remediation,
hasDownload,
hasMr,
hasRemediation,
vulnerabilityFeedbackHelpPath,
isStandaloneVulnerability: true,
},
feedback,
project: {
url: project.full_path,
value: project.full_name,
},
};
return new Vue({
......
......@@ -26,30 +26,10 @@ export default {
},
props: {
createMrUrl: {
type: String,
required: true,
},
initialVulnerability: {
type: Object,
required: true,
},
finding: {
type: Object,
required: true,
},
pipeline: {
type: Object,
required: true,
},
createIssueUrl: {
type: String,
required: true,
},
projectFingerprint: {
type: String,
required: true,
},
},
data() {
......@@ -88,16 +68,16 @@ export default {
);
},
hasIssue() {
return Boolean(this.finding.issue_feedback?.issue_iid);
return Boolean(this.vulnerability.issue_feedback?.issue_iid);
},
hasRemediation() {
const { remediations } = this.finding;
const { remediations } = this.vulnerability;
return Boolean(remediations && remediations[0]?.diff?.length > 0);
},
canCreateMergeRequest() {
return (
!this.finding.merge_request_feedback?.merge_request_path &&
Boolean(this.createMrUrl) &&
!this.vulnerability.merge_request_feedback?.merge_request_path &&
Boolean(this.vulnerability.create_mr_url) &&
this.hasRemediation
);
},
......@@ -163,17 +143,23 @@ export default {
},
createIssue() {
this.isProcessingAction = true;
const {
report_type: category,
project_fingerprint: projectFingerprint,
id,
} = this.vulnerability;
axios
.post(this.createIssueUrl, {
.post(this.vulnerability.create_issue_url, {
vulnerability_feedback: {
feedback_type: FEEDBACK_TYPES.ISSUE,
category: this.vulnerability.report_type,
project_fingerprint: this.projectFingerprint,
category,
project_fingerprint: projectFingerprint,
vulnerability_data: {
...this.vulnerability,
...this.finding,
category: this.vulnerability.report_type,
vulnerability_id: this.vulnerability.id,
category,
vulnerability_id: id,
},
},
})
......@@ -189,17 +175,23 @@ export default {
},
createMergeRequest() {
this.isProcessingAction = true;
const {
report_type: category,
pipeline: { sourceBranch },
project_fingerprint: projectFingerprint,
} = this.vulnerability;
axios
.post(this.createMrUrl, {
.post(this.vulnerability.create_mr_url, {
vulnerability_feedback: {
feedback_type: FEEDBACK_TYPES.MERGE_REQUEST,
category: this.vulnerability.report_type,
project_fingerprint: this.projectFingerprint,
category,
project_fingerprint: projectFingerprint,
vulnerability_data: {
...this.vulnerability,
...this.finding,
category: this.vulnerability.report_type,
target_branch: this.pipeline.sourceBranch,
category,
target_branch: sourceBranch,
},
},
})
......@@ -214,7 +206,10 @@ export default {
});
},
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 {
<status-description
class="issuable-meta"
:vulnerability="vulnerability"
:pipeline="pipeline"
:user="user"
:is-loading-vulnerability="isLoadingVulnerability"
:is-loading-user="isLoadingUser"
......
......@@ -19,10 +19,6 @@ export default {
type: Object,
required: true,
},
pipeline: {
type: Object,
required: true,
},
user: {
type: Object,
required: false,
......@@ -42,7 +38,7 @@ export default {
time() {
const { state } = this.vulnerability;
return state === 'detected'
? this.pipeline.created_at
? this.vulnerability.pipeline.created_at
: this.vulnerability[`${this.vulnerability.state}_at`];
},
......@@ -86,9 +82,9 @@ export default {
img-css-classes="avatar-inline"
/>
</template>
<template v-if="pipeline" #pipelineLink>
<gl-link :href="pipeline.url" target="_blank" class="link">
{{ pipeline.id }}
<template v-if="vulnerability.pipeline" #pipelineLink>
<gl-link :href="vulnerability.pipeline.url" target="_blank" class="link">
{{ vulnerability.pipeline.id }}
</gl-link>
</template>
</gl-sprintf>
......
......@@ -3,9 +3,7 @@
- breadcrumb_title @vulnerability.id
- page_title @vulnerability.title
- page_description @vulnerability.description
- finding = @vulnerability.finding
- location = finding.location
- vulnerability_init_details = vulnerability_details_json(@vulnerability, @pipeline)
- vulnerability_init_details = { vulnerability: vulnerability_details_json(@vulnerability, @pipeline)}
#js-vulnerability-header{ data: vulnerability_init_details }
#js-vulnerability-details{ data: vulnerability_init_details }
......
......@@ -27,32 +27,30 @@ describe('Vulnerability status description component', () => {
const createDate = value => (value ? new Date(value) : new Date()).toISOString();
const createWrapper = ({
vulnerability = {},
pipeline = {},
vulnerability = { pipeline: {} },
user,
isLoadingVulnerability = false,
isLoadingUser = false,
} = {}) => {
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
// it manually for the component to mount properly.
if (v.state === 'detected') {
p.created_at = p.created_at || createDate();
v.pipeline.created_at = v.pipeline.created_at || createDate();
} else {
const propertyName = `${v.state}_at`;
v[propertyName] = v[propertyName] || createDate();
}
wrapper = mount(StatusText, {
propsData: { vulnerability, pipeline, user, isLoadingVulnerability, isLoadingUser },
propsData: { vulnerability, user, isLoadingVulnerability, isLoadingUser },
});
};
describe('state text', () => {
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)}`));
});
......@@ -62,8 +60,7 @@ describe('Vulnerability status description component', () => {
it('uses the pipeline created date when the vulnerability state is "detected"', () => {
const pipelineDateString = createDate('2001');
createWrapper({
vulnerability: { state: 'detected' },
pipeline: { created_at: pipelineDateString },
vulnerability: { state: 'detected', pipeline: { created_at: pipelineDateString } },
});
expect(timeAgo().props('time')).toBe(pipelineDateString);
......@@ -75,8 +72,11 @@ describe('Vulnerability status description component', () => {
state => {
const expectedDate = createDate();
createWrapper({
vulnerability: { state, [`${state}_at`]: expectedDate },
pipeline: { created_at: 'pipeline_created_at' },
vulnerability: {
state,
pipeline: { created_at: 'pipeline_created_at' },
[`${state}_at`]: expectedDate,
},
});
expect(timeAgo().props('time')).toBe(expectedDate);
......@@ -87,8 +87,7 @@ describe('Vulnerability status description component', () => {
describe('pipeline link', () => {
it('shows the pipeline link when the vulnerability state is "detected"', () => {
createWrapper({
vulnerability: { state: 'detected' },
pipeline: { url: 'pipeline/url' },
vulnerability: { state: 'detected', pipeline: { url: 'pipeline/url' } },
});
expect(pipelineLink().attributes('href')).toBe('pipeline/url');
......@@ -98,8 +97,7 @@ describe('Vulnerability status description component', () => {
'does not show the pipeline link when the vulnerability state is "%s"',
state => {
createWrapper({
vulnerability: { state },
pipeline: { url: 'pipeline/url' },
vulnerability: { state, pipeline: { url: 'pipeline/url' } },
});
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', () => {
});
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(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