Commit f19d356d authored by Mark Florian's avatar Mark Florian

Replace VulnerabilityDetails component

This completely removes the old `vulnerability-details` component and
replaces it with the new one.

Now that the bulk of snapshots have served their purpose, they have been
removed, along with now-unsued mocks.
parent 233f466c
...@@ -122,22 +122,6 @@ export default { ...@@ -122,22 +122,6 @@ export default {
isEditingExistingFeedback() { isEditingExistingFeedback() {
return this.dismissalFeedback && this.modal.isCommentingOnDismissal; return this.dismissalFeedback && this.modal.isCommentingOnDismissal;
}, },
valuedFields() {
const { data } = this.modal;
const result = {};
Object.keys(data).forEach(key => {
if (data[key].value && data[key].value.length) {
result[key] = data[key];
if (key === 'file' && this.vulnerability.blob_path) {
result[key].isLink = true;
result[key].url = this.vulnerability.blob_path;
}
}
});
return result;
},
dismissalFeedbackObject() { dismissalFeedbackObject() {
if (this.dismissalFeedback) { if (this.dismissalFeedback) {
return this.dismissalFeedback; return this.dismissalFeedback;
...@@ -202,7 +186,7 @@ export default { ...@@ -202,7 +186,7 @@ export default {
class="modal-security-report-dast" class="modal-security-report-dast"
> >
<slot> <slot>
<vulnerability-details :details="valuedFields" class="js-vulnerability-details" /> <vulnerability-details :vulnerability="vulnerability" class="js-vulnerability-details" />
<solution-card <solution-card
:solution="solution" :solution="solution"
......
<script> <script>
import { GlFriendlyWrap } from '@gitlab/ui'; import { GlFriendlyWrap } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import SafeLink from 'ee/vue_shared/components/safe_link.vue'; import SafeLink from 'ee/vue_shared/components/safe_link.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import SeverityBadge from './severity_badge.vue'; import SeverityBadge from './severity_badge.vue';
import getFileLocation from '../store/utils/get_file_location';
import VulnerabilityDetail from './vulnerability_detail.vue';
export default { export default {
name: 'VulnerabilityDetails', name: 'VulnerabilityDetails',
...@@ -11,9 +14,10 @@ export default { ...@@ -11,9 +14,10 @@ export default {
Icon, Icon,
SafeLink, SafeLink,
SeverityBadge, SeverityBadge,
VulnerabilityDetail,
}, },
props: { props: {
details: { vulnerability: {
type: Object, type: Object,
required: true, required: true,
}, },
...@@ -22,20 +26,53 @@ export default { ...@@ -22,20 +26,53 @@ export default {
hasMoreValues(index, values) { hasMoreValues(index, values) {
return index < values.length - 1; return index < values.length - 1;
}, },
hasValue(field) { asNonEmptyListOrNull(list) {
return field.value && field.value.length > 0; return list?.length > 0 ? list : null;
}, },
hasInstances(key) { },
return key === 'instances'; computed: {
url() {
return getFileLocation(this.vulnerability.location);
},
file() {
const file = this.vulnerability?.location?.file;
if (!file) {
return null;
}
let lineSuffix = '';
const { start_line: startLine, end_line: endLine } = this.vulnerability.location;
if (startLine) {
lineSuffix += `:${startLine}`;
if (endLine && startLine !== endLine) {
lineSuffix += `-${endLine}`;
}
}
return `${file}${lineSuffix}`;
}, },
hasIdentifiers(key) { identifiers() {
return key === 'identifiers'; return this.asNonEmptyListOrNull(this.vulnerability.identifiers);
}, },
hasLinks(key) { className() {
return key === 'links'; return this.vulnerability.location?.class;
}, },
hasSeverity(key) { methodName() {
return key === 'severity'; return this.vulnerability.location?.method;
},
image() {
return this.vulnerability.location?.image;
},
namespace() {
return this.vulnerability.location?.operating_system;
},
links() {
return this.asNonEmptyListOrNull(this.vulnerability.links);
},
instances() {
return this.asNonEmptyListOrNull(this.vulnerability.instances);
}, },
}, },
}; };
...@@ -43,86 +80,122 @@ export default { ...@@ -43,86 +80,122 @@ export default {
<template> <template>
<div class="border-white mb-0 px-3"> <div class="border-white mb-0 px-3">
<div v-for="(field, key, index) in details" :key="index" class="d-sm-flex my-sm-2 my-4"> <vulnerability-detail
<label class="col-sm-2 text-sm-right font-weight-bold pl-0">{{ field.text }}:</label> v-if="vulnerability.description"
<div class="col-sm-10 pl-0 text-secondary"> :label="s__('Vulnerability|Description')"
<template v-if="hasValue(field)"> >
<div v-if="hasInstances(key)" class="info-well"> <gl-friendly-wrap :text="vulnerability.description" />
<ul class="report-block-list"> </vulnerability-detail>
<li v-for="(instance, i) in field.value" :key="i" class="report-block-list-issue">
<div class="report-block-list-icon append-right-5 failed"> <vulnerability-detail v-if="vulnerability.project" :label="s__('Vulnerability|Project')">
<icon :size="32" name="status_failed_borderless" /> <safe-link class="js-link-project" :href="vulnerability.project.full_path" target="_blank">
</div> <gl-friendly-wrap :text="vulnerability.project.full_name" />
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5"> </safe-link>
<div class="report-block-list-issue-description-text"> </vulnerability-detail>
{{ instance.method }}
</div> <vulnerability-detail v-if="url" :label="__('URL')">
<div class="report-block-list-issue-description-link"> <safe-link class="js-link-url" :href="url" target="_blank">
<safe-link <gl-friendly-wrap :text="url" />
:href="instance.uri" </safe-link>
target="_blank" </vulnerability-detail>
rel="noopener noreferrer nofollow"
class="break-link" <vulnerability-detail v-if="file" :label="s__('Vulnerability|File')">
> <safe-link
{{ instance.uri }} v-if="vulnerability.blob_path"
</safe-link> class="js-link-file"
</div> :href="vulnerability.blob_path"
<expand-button v-if="instance.evidence"> target="_blank"
<pre >
slot="expanded" <gl-friendly-wrap :text="file" />
class="block report-block-dast-code prepend-top-10 report-block-issue-code" </safe-link>
> <gl-friendly-wrap v-else :text="file" />
{{ instance.evidence }}</pre </vulnerability-detail>
>
</expand-button> <vulnerability-detail v-if="identifiers" :label="s__('Vulnerability|Identifiers')">
</div> <span v-for="(identifier, i) in identifiers" :key="i">
</li> <safe-link
</ul> v-if="identifier.url"
</div> class="js-link-identifiers"
<template v-else-if="hasIdentifiers(key)"> :href="identifier.url"
<span v-for="(identifier, i) in field.value" :key="i"> target="_blank"
<safe-link rel="noopener noreferrer"
v-if="identifier.url" >
:class="`js-link-${key}`" {{ identifier.name }}
:href="identifier.url" </safe-link>
target="_blank" <span v-else> {{ identifier.name }} </span>
rel="noopener noreferrer" <span v-if="hasMoreValues(i, identifiers)">,&nbsp;</span>
> </span>
{{ identifier.name }} </vulnerability-detail>
</safe-link>
<span v-else> {{ identifier.name }} </span> <vulnerability-detail v-if="vulnerability.severity" :label="s__('Vulnerability|Severity')">
<span v-if="hasMoreValues(i, field.value)">,&nbsp;</span> <severity-badge :severity="vulnerability.severity" class="d-inline" />
</span> </vulnerability-detail>
</template>
<template v-else-if="hasLinks(key)"> <vulnerability-detail
<span v-for="(link, i) in field.value" :key="i"> v-if="vulnerability.report_type"
<safe-link :label="s__('Vulnerability|Report Type')"
:class="`js-link-${key}`" >
:href="link.url" <gl-friendly-wrap :text="vulnerability.report_type" />
target="_blank" </vulnerability-detail>
rel="noopener noreferrer"
> <vulnerability-detail v-if="className" :label="s__('Vulnerability|Class')">
{{ link.value || link.url }} <gl-friendly-wrap :text="className" />
</safe-link> </vulnerability-detail>
<span v-if="hasMoreValues(i, field.value)">,&nbsp;</span>
</span> <vulnerability-detail v-if="methodName" :label="s__('Vulnerability|Method')">
</template> <gl-friendly-wrap :text="methodName" />
<template v-else-if="hasSeverity(key)"> </vulnerability-detail>
<severity-badge :severity="field.value" class="d-inline" />
</template> <vulnerability-detail v-if="image" :label="s__('Vulnerability|Image')">
<template v-else> <gl-friendly-wrap :text="image" />
<safe-link </vulnerability-detail>
v-if="field.isLink"
:class="`js-link-${key}`" <vulnerability-detail v-if="namespace" :label="s__('Vulnerability|Namespace')">
:href="field.url" <gl-friendly-wrap :text="namespace" />
target="_blank" </vulnerability-detail>
>
<gl-friendly-wrap :text="field.value" /> <vulnerability-detail v-if="links" :label="s__('Vulnerability|Links')">
</safe-link> <span v-for="(link, i) in links" :key="i">
<gl-friendly-wrap v-else :text="field.value" /> <safe-link class="js-link-links" :href="link.url" target="_blank" rel="noopener noreferrer">
</template> {{ link.value || link.url }}
</template> </safe-link>
<span v-if="hasMoreValues(i, links)">,&nbsp;</span>
</span>
</vulnerability-detail>
<vulnerability-detail v-if="instances" :label="s__('Vulnerability|Instances')">
<div class="info-well">
<ul class="report-block-list">
<li v-for="(instance, i) in instances" :key="i" class="report-block-list-issue">
<div class="report-block-list-icon append-right-5 failed">
<icon :size="32" name="status_failed_borderless" />
</div>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text">
{{ instance.method }}
</div>
<div class="report-block-list-issue-description-link">
<safe-link
:href="instance.uri"
target="_blank"
rel="noopener noreferrer nofollow"
class="break-link"
>
{{ instance.uri }}
</safe-link>
</div>
<expand-button v-if="instance.evidence">
<pre
slot="expanded"
class="block report-block-dast-code prepend-top-10 report-block-issue-code"
>
{{ instance.evidence }}</pre
>
</expand-button>
</div>
</li>
</ul>
</div> </div>
</div> </vulnerability-detail>
</div> </div>
</template> </template>
<script>
import { GlFriendlyWrap } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import SafeLink from 'ee/vue_shared/components/safe_link.vue';
import Icon from '~/vue_shared/components/icon.vue';
import SeverityBadge from './severity_badge.vue';
import getFileLocation from '../store/utils/get_file_location';
import VulnerabilityDetail from './vulnerability_detail.vue';
export default {
name: 'VulnerabilityDetails2',
components: {
GlFriendlyWrap,
Icon,
SafeLink,
SeverityBadge,
VulnerabilityDetail,
},
props: {
vulnerability: {
type: Object,
required: true,
},
},
methods: {
hasMoreValues(index, values) {
return index < values.length - 1;
},
asNonEmptyListOrNull(list) {
return list?.length > 0 ? list : null;
},
},
computed: {
url() {
return getFileLocation(this.vulnerability.location);
},
file() {
const file = this.vulnerability?.location?.file;
if (!file) {
return null;
}
let lineSuffix = '';
const { start_line: startLine, end_line: endLine } = this.vulnerability.location;
if (startLine) {
lineSuffix += `:${startLine}`;
if (endLine && startLine !== endLine) {
lineSuffix += `-${endLine}`;
}
}
return `${file}${lineSuffix}`;
},
identifiers() {
return this.asNonEmptyListOrNull(this.vulnerability.identifiers);
},
className() {
return this.vulnerability.location?.class;
},
methodName() {
return this.vulnerability.location?.method;
},
image() {
return this.vulnerability.location?.image;
},
namespace() {
return this.vulnerability.location?.operating_system;
},
links() {
return this.asNonEmptyListOrNull(this.vulnerability.links);
},
instances() {
return this.asNonEmptyListOrNull(this.vulnerability.instances);
},
},
};
</script>
<template>
<div class="border-white mb-0 px-3">
<vulnerability-detail
v-if="vulnerability.description"
:label="s__('Vulnerability|Description')"
>
<gl-friendly-wrap :text="vulnerability.description" />
</vulnerability-detail>
<vulnerability-detail v-if="vulnerability.project" :label="s__('Vulnerability|Project')">
<safe-link class="js-link-project" :href="vulnerability.project.full_path" target="_blank">
<gl-friendly-wrap :text="vulnerability.project.full_name" />
</safe-link>
</vulnerability-detail>
<vulnerability-detail v-if="url" :label="__('URL')">
<safe-link class="js-link-url" :href="url" target="_blank">
<gl-friendly-wrap :text="url" />
</safe-link>
</vulnerability-detail>
<vulnerability-detail v-if="file" :label="s__('Vulnerability|File')">
<safe-link
v-if="vulnerability.blob_path"
class="js-link-file"
:href="vulnerability.blob_path"
target="_blank"
>
<gl-friendly-wrap :text="file" />
</safe-link>
<gl-friendly-wrap v-else :text="file" />
</vulnerability-detail>
<vulnerability-detail v-if="identifiers" :label="s__('Vulnerability|Identifiers')">
<span v-for="(identifier, i) in identifiers" :key="i">
<safe-link
v-if="identifier.url"
class="js-link-identifiers"
:href="identifier.url"
target="_blank"
rel="noopener noreferrer"
>
{{ identifier.name }}
</safe-link>
<span v-else> {{ identifier.name }} </span>
<span v-if="hasMoreValues(i, identifiers)">,&nbsp;</span>
</span>
</vulnerability-detail>
<vulnerability-detail v-if="vulnerability.severity" :label="s__('Vulnerability|Severity')">
<severity-badge :severity="vulnerability.severity" class="d-inline" />
</vulnerability-detail>
<vulnerability-detail
v-if="vulnerability.report_type"
:label="s__('Vulnerability|Report Type')"
>
<gl-friendly-wrap :text="vulnerability.report_type" />
</vulnerability-detail>
<vulnerability-detail v-if="className" :label="s__('Vulnerability|Class')">
<gl-friendly-wrap :text="className" />
</vulnerability-detail>
<vulnerability-detail v-if="methodName" :label="s__('Vulnerability|Method')">
<gl-friendly-wrap :text="methodName" />
</vulnerability-detail>
<vulnerability-detail v-if="image" :label="s__('Vulnerability|Image')">
<gl-friendly-wrap :text="image" />
</vulnerability-detail>
<vulnerability-detail v-if="namespace" :label="s__('Vulnerability|Namespace')">
<gl-friendly-wrap :text="namespace" />
</vulnerability-detail>
<vulnerability-detail v-if="links" :label="s__('Vulnerability|Links')">
<span v-for="(link, i) in links" :key="i">
<safe-link class="js-link-links" :href="link.url" target="_blank" rel="noopener noreferrer">
{{ link.value || link.url }}
</safe-link>
<span v-if="hasMoreValues(i, links)">,&nbsp;</span>
</span>
</vulnerability-detail>
<vulnerability-detail v-if="instances" :label="s__('Vulnerability|Instances')">
<div class="info-well">
<ul class="report-block-list">
<li v-for="(instance, i) in instances" :key="i" class="report-block-list-issue">
<div class="report-block-list-icon append-right-5 failed">
<icon :size="32" name="status_failed_borderless" />
</div>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text">
{{ instance.method }}
</div>
<div class="report-block-list-issue-description-link">
<safe-link
:href="instance.uri"
target="_blank"
rel="noopener noreferrer nofollow"
class="break-link"
>
{{ instance.uri }}
</safe-link>
</div>
<expand-button v-if="instance.evidence">
<pre
slot="expanded"
class="block report-block-dast-code prepend-top-10 report-block-issue-code"
>
{{ instance.evidence }}</pre
>
</expand-button>
</div>
</li>
</ul>
</div>
</vulnerability-detail>
</div>
</template>
This source diff could not be displayed because it is too large. You can view the blob instead.
import { mount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
import createState from 'ee/vue_shared/security_reports/store/state'; import createState from 'ee/vue_shared/security_reports/store/state';
import VulnerabilityDetails from 'ee/vue_shared/security_reports/components/vulnerability_details.vue'; import VulnerabilityDetails from 'ee/vue_shared/security_reports/components/vulnerability_details.vue';
import VulnerabilityDetails2 from 'ee/vue_shared/security_reports/components/vulnerability_details_2.vue';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue'; import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import SafeLink from 'ee/vue_shared/components/safe_link.vue'; import SafeLink from 'ee/vue_shared/components/safe_link.vue';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { mockFindings } from '../mock_data';
function makeVulnerability(changes = {}) { function makeVulnerability(changes = {}) {
return Object.assign(cloneDeep(findings[0]), changes); return Object.assign(cloneDeep(mockFindings[0]), changes);
} }
describe('VulnerabilityDetails component', () => { describe('VulnerabilityDetails component', () => {
let wrapper; let wrapper;
const componentFactory = vulnerability => { const componentFactory = vulnerability => {
wrapper = mount(VulnerabilityDetails2, { wrapper = mount(VulnerabilityDetails, {
propsData: { vulnerability }, propsData: { vulnerability },
}); });
}; };
...@@ -26,6 +26,10 @@ describe('VulnerabilityDetails component', () => { ...@@ -26,6 +26,10 @@ describe('VulnerabilityDetails component', () => {
expect(link.text()).toBe(text); expect(link.text()).toBe(text);
}; };
afterEach(() => {
wrapper.destroy();
});
it('renders severity with a badge', () => { it('renders severity with a badge', () => {
const vulnerability = makeVulnerability({ severity: 'critical' }); const vulnerability = makeVulnerability({ severity: 'critical' });
componentFactory(vulnerability); componentFactory(vulnerability);
...@@ -151,66 +155,18 @@ describe('VulnerabilityDetails component', () => { ...@@ -151,66 +155,18 @@ describe('VulnerabilityDetails component', () => {
); );
}); });
}); });
});
import mutations from 'ee/vue_shared/security_reports/store/mutations';
import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue';
import { findings } from './mock_findings';
import createDashboardState from 'ee/security_dashboard/store/modules/vulnerabilities/state';
import dashboardMutations from 'ee/security_dashboard/store/modules/vulnerabilities/mutations';
describe('VulnerabilityDetails component pin tests', () => {
let wrapper;
const mrFactory = vulnFinding => { describe('pin test', () => {
const state = createState(); const factory = vulnFinding => {
mutations.SET_ISSUE_MODAL_DATA(state, { issue: vulnFinding }); wrapper = shallowMount(VulnerabilityDetails, {
const details = IssueModal.computed.valuedFields.call({ propsData: {
...state, vulnerability: vulnFinding,
vulnerability: vulnFinding, },
}); });
};
wrapper = mount(VulnerabilityDetails, {
propsData: {
details,
},
});
};
const dashboardFactory = vulnFinding => {
const state = createDashboardState();
dashboardMutations.SET_MODAL_DATA(state, { vulnerability: vulnFinding });
const details = IssueModal.computed.valuedFields.call({
...state,
vulnerability: vulnFinding,
});
wrapper = mount(VulnerabilityDetails, {
propsData: {
details,
},
});
};
const factory = vulnFinding => {
wrapper = mount(VulnerabilityDetails2, {
propsData: {
vulnerability: vulnFinding,
},
});
};
describe('in grouped_security_reports_app', () => {
it.each(findings.map(v => [v.name, v]))('works for %s', (_, finding) => {
factory(finding);
expect(wrapper.element).toMatchSnapshot();
});
});
describe('in Security Dashboards', () => { it('renders correctly', () => {
it.each(findings.map(v => [v.name, v]))('works for %s', (_, finding) => { factory(makeVulnerability());
factory(finding);
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
}); });
......
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