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,88 +24,23 @@ export default { ...@@ -24,88 +24,23 @@ 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';
}, },
hasIdentifiers(key) { computed: {
return key === 'identifiers'; url() {
}, return getFileLocation(this.vulnerability.location);
hasLinks(key) {
return key === 'links';
},
hasSeverity(key) {
return key === 'severity';
}, },
valuedFields(details) { file() {
const result = {}; const file = this.vulnerability?.location?.file;
Object.keys(details).forEach(key => { if (!file) {
if (details[key].value && details[key].value.length) { return null;
result[key] = details[key];
if (key === 'file' && this.vulnerability.blob_path) {
result[key].isLink = true;
result[key].url = this.vulnerability.blob_path;
}
} }
});
return result;
},
},
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 = ''; let lineSuffix = '';
const { start_line: startLine, end_line: endLine } = this.vulnerability.location;
if (startLine) { if (startLine) {
lineSuffix += `:${startLine}`; lineSuffix += `:${startLine}`;
...@@ -114,28 +49,28 @@ export default { ...@@ -114,28 +49,28 @@ export default {
} }
} }
details.className.value = className; return `${file}${lineSuffix}`;
details.methodName.value = methodName; },
details.file.value = file ? `${file}${lineSuffix}` : null; identifiers() {
details.image.value = image; return this.asNonEmptyListOrNull(this.vulnerability.identifiers);
details.namespace.value = namespace; },
details.url.value = fileLocation; className() {
details.url.url = fileLocation; return this.vulnerability.location?.class;
} },
methodName() {
if (vulnerability.instances && vulnerability.instances.length) { return this.vulnerability.location?.method;
details.instances.value = vulnerability.instances; },
} else { image() {
details.instances.value = null; return this.vulnerability.location?.image;
} },
namespace() {
if (vulnerability.links && vulnerability.links.length) { return this.vulnerability.location?.operating_system;
details.links.value = vulnerability.links; },
} else { links() {
details.links.value = null; return this.asNonEmptyListOrNull(this.vulnerability.links);
} },
instances() {
return this.valuedFields(details); return this.asNonEmptyListOrNull(this.vulnerability.instances);
}, },
}, },
}; };
...@@ -143,47 +78,61 @@ export default { ...@@ -143,47 +78,61 @@ 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"> <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">{{ field.text }}:</label> <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"> <div class="col-sm-10 pl-0 text-secondary">
<template v-if="hasValue(field)"> <gl-friendly-wrap :text="vulnerability.description" />
<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> </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>
<div class="report-block-list-issue-description-link">
<safe-link <div v-if="vulnerability.project" class="d-sm-flex my-sm-2 my-4">
:href="instance.uri" <label class="col-sm-2 text-sm-right font-weight-bold pl-0"
target="_blank" >{{ s__('Vulnerability|Project') }}:</label
rel="noopener noreferrer nofollow"
class="break-link"
> >
{{ instance.uri }} <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> </safe-link>
</div> </div>
<expand-button v-if="instance.evidence"> </div>
<pre
slot="expanded" <div v-if="url" class="d-sm-flex my-sm-2 my-4">
class="block report-block-dast-code prepend-top-10 report-block-issue-code" <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
> >
{{ instance.evidence }}</pre <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"
> >
</expand-button> <gl-friendly-wrap :text="file" />
</safe-link>
<gl-friendly-wrap v-else :text="file" />
</div> </div>
</li>
</ul>
</div> </div>
<template v-else-if="hasIdentifiers(key)">
<span v-for="(identifier, i) in field.value" :key="i"> <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 <safe-link
v-if="identifier.url" v-if="identifier.url"
:class="`js-link-${key}`" class="js-link-identifiers"
:href="identifier.url" :href="identifier.url"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
...@@ -191,37 +140,121 @@ export default { ...@@ -191,37 +140,121 @@ export default {
{{ identifier.name }} {{ identifier.name }}
</safe-link> </safe-link>
<span v-else> {{ identifier.name }} </span> <span v-else> {{ identifier.name }} </span>
<span v-if="hasMoreValues(i, field.value)">,&nbsp;</span> <span v-if="hasMoreValues(i, identifiers)">,&nbsp;</span>
</span> </span>
</template> </div>
<template v-else-if="hasLinks(key)"> </div>
<span v-for="(link, i) in field.value" :key="i">
<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 <safe-link
:class="`js-link-${key}`" class="js-link-links"
:href="link.url" :href="link.url"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
{{ link.value || link.url }} {{ link.value || link.url }}
</safe-link> </safe-link>
<span v-if="hasMoreValues(i, field.value)">,&nbsp;</span> <span v-if="hasMoreValues(i, links)">,&nbsp;</span>
</span> </span>
</template> </div>
<template v-else-if="hasSeverity(key)"> </div>
<severity-badge :severity="field.value" class="d-inline" />
</template> <div v-if="instances" class="d-sm-flex my-sm-2 my-4">
<template v-else> <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">
<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-link">
<safe-link <safe-link
v-if="field.isLink" :href="instance.uri"
:class="`js-link-${key}`"
:href="field.url"
target="_blank" target="_blank"
rel="noopener noreferrer nofollow"
class="break-link"
> >
<gl-friendly-wrap :text="field.value" /> {{ instance.uri }}
</safe-link> </safe-link>
<gl-friendly-wrap v-else :text="field.value" /> </div>
</template> <expand-button v-if="instance.evidence">
</template> <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> </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