Commit bb5467cd authored by Mark Florian's avatar Mark Florian

Explicitly render vulnerability details

This refactors the new `vulnerability-details` component to explicitly
render the markup for the various details of the vulnerability, without
going through an unnecessary transformation stage.

This is preferable (though more verbose) because different details are
rendered in different ways. Before, the template resembled the
[loop-switch sequence][2] anti-pattern, and adding special cases was
awkward.

The snapshot changes in this commit are simply whitespace changes and
added/missing HTML comments, resulting from the different conditional
rendering logic.

This addresses [issue 32039][1].

[1]: https://gitlab.com/gitlab-org/gitlab/issues/32039
[2]: https://en.wikipedia.org/wiki/Loop-switch_sequence
parent 27f0d546
......@@ -24,118 +24,53 @@ export default {
hasMoreValues(index, values) {
return index < values.length - 1;
},
hasValue(field) {
return field.value && field.value.length > 0;
},
hasInstances(key) {
return key === 'instances';
},
hasIdentifiers(key) {
return key === 'identifiers';
},
hasLinks(key) {
return key === 'links';
},
hasSeverity(key) {
return key === 'severity';
},
valuedFields(details) {
const result = {};
Object.keys(details).forEach(key => {
if (details[key].value && details[key].value.length) {
result[key] = details[key];
if (key === 'file' && this.vulnerability.blob_path) {
result[key].isLink = true;
result[key].url = this.vulnerability.blob_path;
}
}
});
return result;
asNonEmptyListOrNull(list) {
return list?.length > 0 ? list : null;
},
},
computed: {
details() {
const { vulnerability } = this;
const { location } = vulnerability;
const details = {
description: {
text: s__('Vulnerability|Description'),
value: vulnerability.description,
},
project: {
text: s__('Vulnerability|Project'),
isLink: true,
value: vulnerability.project?.full_name,
url: vulnerability.project?.full_path,
},
url: { text: __('URL'), isLink: true },
file: { text: s__('Vulnerability|File') },
identifiers: {
text: s__('Vulnerability|Identifiers'),
value: vulnerability.identifiers.length && vulnerability.identifiers,
},
severity: {
text: s__('Vulnerability|Severity'),
value: vulnerability.severity,
},
reportType: {
text: s__('Vulnerability|Report Type'),
value: vulnerability.report_type,
},
className: { text: s__('Vulnerability|Class') },
methodName: { text: s__('Vulnerability|Method') },
image: { text: s__('Vulnerability|Image') },
namespace: { text: s__('Vulnerability|Namespace') },
links: { text: s__('Vulnerability|Links') },
instances: { text: s__('Vulnerability|Instances') },
};
if (location) {
const {
file,
start_line: startLine,
end_line: endLine,
image,
operating_system: namespace,
class: className,
method: methodName,
} = location;
const fileLocation = getFileLocation(location);
let lineSuffix = '';
if (startLine) {
lineSuffix += `:${startLine}`;
if (endLine && startLine !== endLine) {
lineSuffix += `-${endLine}`;
}
}
url() {
return getFileLocation(this.vulnerability.location);
},
file() {
const file = this.vulnerability?.location?.file;
details.className.value = className;
details.methodName.value = methodName;
details.file.value = file ? `${file}${lineSuffix}` : null;
details.image.value = image;
details.namespace.value = namespace;
details.url.value = fileLocation;
details.url.url = fileLocation;
if (!file) {
return null;
}
if (vulnerability.instances && vulnerability.instances.length) {
details.instances.value = vulnerability.instances;
} else {
details.instances.value = null;
}
let lineSuffix = '';
const { start_line: startLine, end_line: endLine } = this.vulnerability.location;
if (vulnerability.links && vulnerability.links.length) {
details.links.value = vulnerability.links;
} else {
details.links.value = null;
if (startLine) {
lineSuffix += `:${startLine}`;
if (endLine && startLine !== endLine) {
lineSuffix += `-${endLine}`;
}
}
return this.valuedFields(details);
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);
},
},
};
......@@ -143,85 +78,183 @@ export default {
<template>
<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">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0">{{ field.text }}:</label>
<div v-if="vulnerability.description" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0"
>{{ s__('Vulnerability|Description') }}:</label
>
<div class="col-sm-10 pl-0 text-secondary">
<gl-friendly-wrap :text="vulnerability.description" />
</div>
</div>
<div v-if="vulnerability.project" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0"
>{{ s__('Vulnerability|Project') }}:</label
>
<div class="col-sm-10 pl-0 text-secondary">
<safe-link class="js-link-project" :href="vulnerability.project.full_path" target="_blank">
<gl-friendly-wrap :text="vulnerability.project.full_name" />
</safe-link>
</div>
</div>
<div v-if="url" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0">{{ __('URL') }}:</label>
<div class="col-sm-10 pl-0 text-secondary">
<safe-link class="js-link-url" :href="url" target="_blank">
<gl-friendly-wrap :text="url" />
</safe-link>
</div>
</div>
<div v-if="file" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0"
>{{ s__('Vulnerability|File') }}:</label
>
<div class="col-sm-10 pl-0 text-secondary">
<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" />
</div>
</div>
<div v-if="identifiers" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0"
>{{ s__('Vulnerability|Identifiers') }}:</label
>
<div class="col-sm-10 pl-0 text-secondary">
<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>
</div>
</div>
<div v-if="vulnerability.severity" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0"
>{{ s__('Vulnerability|Severity') }}:</label
>
<div class="col-sm-10 pl-0 text-secondary">
<severity-badge :severity="vulnerability.severity" class="d-inline" />
</div>
</div>
<div v-if="vulnerability.report_type" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0"
>{{ s__('Vulnerability|Report Type') }}:</label
>
<div class="col-sm-10 pl-0 text-secondary">
<gl-friendly-wrap :text="vulnerability.report_type" />
</div>
</div>
<div v-if="className" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0"
>{{ s__('Vulnerability|Class') }}:</label
>
<div class="col-sm-10 pl-0 text-secondary">
<gl-friendly-wrap :text="className" />
</div>
</div>
<div v-if="methodName" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0"
>{{ s__('Vulnerability|Method') }}:</label
>
<div class="col-sm-10 pl-0 text-secondary">
<gl-friendly-wrap :text="methodName" />
</div>
</div>
<div v-if="image" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0"
>{{ s__('Vulnerability|Image') }}:</label
>
<div class="col-sm-10 pl-0 text-secondary">
<gl-friendly-wrap :text="image" />
</div>
</div>
<div v-if="namespace" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0"
>{{ s__('Vulnerability|Namespace') }}:</label
>
<div class="col-sm-10 pl-0 text-secondary">
<gl-friendly-wrap :text="namespace" />
</div>
</div>
<div v-if="links" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0"
>{{ s__('Vulnerability|Links') }}:</label
>
<div class="col-sm-10 pl-0 text-secondary">
<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>
</div>
</div>
<div v-if="instances" class="d-sm-flex my-sm-2 my-4">
<label class="col-sm-2 text-sm-right font-weight-bold pl-0"
>{{ s__('Vulnerability|Instances') }}:</label
>
<div class="col-sm-10 pl-0 text-secondary">
<template v-if="hasValue(field)">
<div v-if="hasInstances(key)" class="info-well">
<ul class="report-block-list">
<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">
<icon :size="32" name="status_failed_borderless" />
<div v-if="instances" 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 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 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>
</li>
</ul>
</div>
<template v-else-if="hasIdentifiers(key)">
<span v-for="(identifier, i) in field.value" :key="i">
<safe-link
v-if="identifier.url"
:class="`js-link-${key}`"
:href="identifier.url"
target="_blank"
rel="noopener noreferrer"
>
{{ identifier.name }}
</safe-link>
<span v-else> {{ identifier.name }} </span>
<span v-if="hasMoreValues(i, field.value)">,&nbsp;</span>
</span>
</template>
<template v-else-if="hasLinks(key)">
<span v-for="(link, i) in field.value" :key="i">
<safe-link
:class="`js-link-${key}`"
:href="link.url"
target="_blank"
rel="noopener noreferrer"
>
{{ link.value || link.url }}
</safe-link>
<span v-if="hasMoreValues(i, field.value)">,&nbsp;</span>
</span>
</template>
<template v-else-if="hasSeverity(key)">
<severity-badge :severity="field.value" class="d-inline" />
</template>
<template v-else>
<safe-link
v-if="field.isLink"
:class="`js-link-${key}`"
:href="field.url"
target="_blank"
>
<gl-friendly-wrap :text="field.value" />
</safe-link>
<gl-friendly-wrap v-else :text="field.value" />
</template>
</template>
<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>
</div>
......
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