Commit 9c32bef2 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '267509-make-file-path-link' into 'master'

Make vulnerability file path linkable

See merge request gitlab-org/gitlab!55356
parents d0a94468 03b4f3cb
......@@ -20,7 +20,7 @@ import RemediatedBadge from 'ee/vulnerabilities/components/remediated_badge.vue'
import { VULNERABILITY_STATES } from 'ee/vulnerabilities/constants';
import { formatDate } from '~/lib/utils/datetime_utility';
import { convertToSnakeCase } from '~/lib/utils/text_utility';
import { s__, __, sprintf } from '~/locale';
import { s__, __ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { VULNERABILITIES_PER_PAGE } from '../store/constants';
import IssuesBadge from './issues_badge.vue';
......@@ -230,7 +230,7 @@ export default {
}
if (file && startLine) {
return `${file} ${sprintf(__('(line: %{startLine})'), { startLine })}`;
return `${file}:${startLine}`;
}
if (path) {
......@@ -248,6 +248,16 @@ export default {
extraIdentifierCount(identifiers) {
return identifiers?.length - 1;
},
fileUrl(vulnerability) {
const { startLine: start, endLine: end, blobPath } = vulnerability.location;
const lineNumber = end > start ? `${start}-${end}` : start;
if (!blobPath) {
return '';
}
return `${blobPath}${lineNumber ? `#L${lineNumber}` : ''}`;
},
primaryIdentifier(identifiers) {
return getPrimaryIdentifier(identifiers, 'externalType');
},
......@@ -421,8 +431,11 @@ export default {
<div v-if="shouldShowProjectNamespace">
{{ item.project.nameWithNamespace }}
</div>
<div v-if="shouldShowVulnerabilityPath(item)" class="monospace">
<gl-truncate :text="createLocationString(item.location)" position="middle" />
<div v-if="shouldShowVulnerabilityPath(item)">
<gl-link v-if="item.location.blobPath" :href="fileUrl(item)">
<gl-truncate :text="createLocationString(item.location)" position="middle" />
</gl-link>
<gl-truncate v-else :text="createLocationString(item.location)" position="middle" />
</div>
</div>
</template>
......
......@@ -27,13 +27,16 @@ fragment Vulnerability on Vulnerability {
image
}
... on VulnerabilityLocationDependencyScanning {
blobPath
file
}
... on VulnerabilityLocationSast {
blobPath
file
startLine
}
... on VulnerabilityLocationSecretDetection {
blobPath
file
startLine
}
......
---
title: Make vulnerability file path linkable in the vulnerability list
merge_request: 55356
author:
type: changed
......@@ -58,6 +58,8 @@ export const generateVulnerabilities = () => [
location: {
file: 'src/main/java/com/gitlab/security_products/tests/App.java',
startLine: '1337',
blobPath:
'/gitlab-org/security-reports2/-/blob/e5c61e4d5d0b8418011171def04ca0aa36532621/src/main/java/com/gitlab/security_products/tests/App.java',
},
project: {
nameWithNamespace: 'Administrator / Vulnerability reports',
......
......@@ -46,6 +46,7 @@ describe('Vulnerability list component', () => {
);
};
const locationText = ({ file, startLine }) => `${file}:${startLine}`;
const findTable = () => wrapper.findComponent(GlTable);
const findSortableColumn = () => wrapper.find('[aria-sort="descending"]');
const findCell = (label) => wrapper.find(`.js-${label}`);
......@@ -62,6 +63,7 @@ describe('Vulnerability list component', () => {
findRow(row).findComponent(VulnerabilityCommentIcon);
const findDataCell = (label) => wrapper.findByTestId(label);
const findDataCells = (label) => wrapper.findAll(`[data-testid="${label}"]`);
const findLocationCell = (id) => wrapper.findByTestId(`location-${id}`);
const findLocationTextWrapper = (cell) => cell.find(GlTruncate);
const findFiltersProducedNoResults = () => wrapper.findComponent(FiltersProducedNoResults);
const findDashboardHasNoVulnerabilities = () =>
......@@ -232,7 +234,7 @@ describe('Vulnerability list component', () => {
it('should display the vulnerability locations for images', () => {
const { id, project, location } = newVulnerabilities[0];
const cell = findDataCell(`location-${id}`);
const cell = findLocationCell(id);
expect(cell.text()).toContain(project.nameWithNamespace);
expect(findLocationTextWrapper(cell).props()).toEqual({
text: location.image,
......@@ -242,17 +244,17 @@ describe('Vulnerability list component', () => {
it('should display the vulnerability locations for code', () => {
const { id, project, location } = newVulnerabilities[1];
const cell = findDataCell(`location-${id}`);
const cell = findLocationCell(id);
expect(cell.text()).toContain(project.nameWithNamespace);
expect(findLocationTextWrapper(cell).props()).toEqual({
text: `${location.file} (line: ${location.startLine})`,
text: locationText(location),
position: 'middle',
});
});
it('should display the vulnerability locations for code with no line data', () => {
const { id, project, location } = newVulnerabilities[2];
const cell = findDataCell(`location-${id}`);
const cell = findLocationCell(id);
expect(cell.text()).toContain(project.nameWithNamespace);
expect(findLocationTextWrapper(cell).props()).toEqual({
text: location.file,
......@@ -262,14 +264,14 @@ describe('Vulnerability list component', () => {
it('should not display the vulnerability locations for vulnerabilities without a location', () => {
const { id, project } = newVulnerabilities[4];
const cellText = findDataCell(`location-${id}`).text();
const cellText = findLocationCell(id).text();
expect(cellText).toEqual(project.nameWithNamespace);
expect(cellText).not.toContain('(line: ');
expect(cellText).not.toContain(':');
});
it('should display the vulnerability locations for path', () => {
const { id, project, location } = newVulnerabilities[5];
const cell = findDataCell(`location-${id}`);
const cell = findLocationCell(id);
expect(cell.text()).toContain(project.nameWithNamespace);
expect(findLocationTextWrapper(cell).props()).toEqual({
text: location.path,
......@@ -293,7 +295,7 @@ describe('Vulnerability list component', () => {
it('should not display the vulnerability group/project locations for images', () => {
const { id, project, location } = newVulnerabilities[0];
const cell = findDataCell(`location-${id}`);
const cell = findLocationCell(id);
expect(cell.text()).not.toContain(project.nameWithNamespace);
expect(findLocationTextWrapper(cell).props()).toEqual({
text: location.image,
......@@ -310,17 +312,29 @@ describe('Vulnerability list component', () => {
it('should display the vulnerability locations for code', () => {
const { id, project, location } = newVulnerabilities[1];
const cell = findDataCell(`location-${id}`);
const cell = findLocationCell(id);
expect(cell.text()).not.toContain(project.nameWithNamespace);
expect(findLocationTextWrapper(cell).props()).toEqual({
text: `${location.file} (line: ${location.startLine})`,
text: locationText(location),
position: 'middle',
});
});
it('should make the file path linkable', () => {
const { id, location } = newVulnerabilities[1];
const cell = findLocationCell(id);
expect(cell.find('a').attributes('href')).toBe(`${location.blobPath}#L${location.startLine}`);
});
it('should not make the file path linkable if blobPath is missing', () => {
const { id } = newVulnerabilities[0];
const cell = findLocationCell(id);
expect(cell.find('a').exists()).toBe(false);
});
it('should not display the vulnerability group/project locations for code with no line data', () => {
const { id, project, location } = newVulnerabilities[2];
const cell = findDataCell(`location-${id}`);
const cell = findLocationCell(id);
expect(cell.text()).not.toContain(project.nameWithNamespace);
expect(findLocationTextWrapper(cell).props()).toEqual({
text: location.file,
......
......@@ -1031,9 +1031,6 @@ msgstr ""
msgid "(deleted)"
msgstr ""
msgid "(line: %{startLine})"
msgstr ""
msgid "(max size 15 MB)"
msgstr ""
......
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