diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/constants.js b/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/constants.js index 94d631b66eb1175541da751ae10849ebf5bc2551..a628619a9f8813d5c14e9d57ee0d32b5ada6353e 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/constants.js +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/constants.js @@ -54,6 +54,11 @@ export const FIELDS = { thClass: 'gl-text-right', class: 'activity', }, + IMAGE: { + key: 'imageAndTag', + label: s__('Vulnerability|Image and tag'), + class: 'gl-max-w-0 gl-break-all', + }, }; // These are used to identify which component should be rendered in vulnerability_filters.vue. @@ -87,7 +92,7 @@ export const FIELD_PRESETS = { FIELDS.TOOL, FIELDS.ACTIVITY, ], - CONTAINER_REGISTRY: [...BASE_FIELDS.START, FIELDS.IDENTIFIER, ...BASE_FIELDS.END], + CONTAINER_REGISTRY: [...BASE_FIELDS.START, FIELDS.IDENTIFIER, FIELDS.IMAGE, ...BASE_FIELDS.END], }; const BASE_FILTERS = { START: [FILTERS.STATUS, FILTERS.SEVERITY], END: [FILTERS.ACTIVITY] }; diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue index 3f2bd8f17550d371ae22ead233ac5cdf4611a205..baef5915cafc6939186aec6c92bcbf2f878ff6bc 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_list.vue @@ -6,6 +6,7 @@ import { GlSkeletonLoader, GlTooltipDirective, GlTable, + GlTruncate, } from '@gitlab/ui'; import { Portal } from 'portal-vue'; import DashboardHasNoVulnerabilities from 'ee/security_dashboard/components/shared/empty_states/dashboard_has_no_vulnerabilities.vue'; @@ -33,6 +34,7 @@ export default { GlSkeletonLoader, GlSprintf, GlTable, + GlTruncate, IssuesBadge, MergeRequestBadge, ResolutionBadge, @@ -241,6 +243,9 @@ export default { handleSortChange({ sortBy, sortDesc }) { this.$emit('update:sort', { sortBy, sortDesc }); }, + containerImageAndTag(item) { + return item.location?.image; + }, }, }; </script> @@ -409,6 +414,16 @@ export default { </div> </template> + <template #cell(imageAndTag)="{ item }"> + <gl-truncate + v-if="containerImageAndTag(item)" + :data-testid="`image-and-tag-${item.id}`" + :text="containerImageAndTag(item)" + position="middle" + with-tooltip + /> + </template> + <template #table-busy> <gl-skeleton-loader v-for="n in pageSize" :key="n" :lines="2" /> </template> diff --git a/ee/spec/frontend/security_dashboard/components/mock_data.js b/ee/spec/frontend/security_dashboard/components/mock_data.js index fbe825724abd953fe2b047566687b367430ea0c3..77f77818b8312ee0c325d035b090c1563330f69f 100644 --- a/ee/spec/frontend/security_dashboard/components/mock_data.js +++ b/ee/spec/frontend/security_dashboard/components/mock_data.js @@ -111,6 +111,73 @@ export const clusterImageScanningVulnerability = { }, }; +export const containerScanningForRegistryVulnerability = { + id: 'id_0', + detectedAt: '2020-07-29T15:36:54Z', + mergeRequest: { + id: 'mr-1', + webUrl: 'www.testmr.com/1', + state: 'status_warning', + iid: 1, + }, + identifiers: [ + { + externalType: 'cve', + name: 'CVE-2018-1234', + }, + { + externalType: 'gemnasium', + name: 'Gemnasium-2018-1234', + }, + ], + dismissalReason: 'USED_IN_TESTS', + title: 'Vulnerability 0', + severity: 'critical', + state: 'DISMISSED', + reportType: 'SAST', + resolvedOnDefaultBranch: false, + location: { + image: + 'registry.gitlab.com/groulot/container-scanning-test/main:5f21de6956aee99ddb68ae49498662d9872f50ff', + }, + project: { + id: 'project-1', + nameWithNamespace: 'Administrator / Security reports', + }, + scanner: { + id: 'scanner-1', + vendor: 'GitLab', + }, + issueLinks: { + nodes: [ + { + id: 'issue-1', + issue: { + id: 'issue-1', + iid: 15, + webUrl: 'url', + webPath: 'path', + title: 'title', + state: 'state', + resolvedOnDefaultBranch: true, + }, + }, + ], + }, + externalIssueLinks: { + nodes: [ + { + id: 'issue-1', + issue: { iid: 15, externalTracker: 'jira', resolvedOnDefaultBranch: true }, + }, + ], + }, + vulnerabilityPath: 'path', + userNotesCount: 1, + hasRemediations: true, + __typename: 'Vulnerability', +}; + export const generateVulnerabilities = () => [ { id: 'id_0', diff --git a/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js b/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js index cfc3931ba8b5044bbb70553bd0795e7f83b48304..334647b72108bf67045a037e880461f4a218222b 100644 --- a/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js +++ b/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_list_spec.js @@ -22,6 +22,7 @@ import { import { stubComponent } from 'helpers/stub_component'; import { clusterImageScanningVulnerability, + containerScanningForRegistryVulnerability, generateVulnerabilities, vulnerabilities, findings, @@ -75,6 +76,7 @@ describe('Vulnerability list component', () => { const findDataCell = (label) => wrapper.findByTestId(label); const findDataCells = (label) => wrapper.findAll(`[data-testid="${label}"]`); const findClusterCell = (id) => wrapper.findByTestId(`cluster-${id}`); + const findImageAndTagCell = (id) => wrapper.findByTestId(`image-and-tag-${id}`); const findLocationCell = (id) => wrapper.findByTestId(`location-${id}`); const findTitleCell = (id) => wrapper.findByTestId(`title-${id}`); const findAllStatusCells = () => wrapper.findAll(`[data-testid="vulnerability-status-content"]`); @@ -631,6 +633,35 @@ describe('Vulnerability list component', () => { }); }); + describe('container registry vulnerabilities', () => { + it('shows the image and tag column', () => { + createWrapper({ + props: { + fields: FIELD_PRESETS.CONTAINER_REGISTRY, + vulnerabilities: [containerScanningForRegistryVulnerability], + }, + }); + + expect(findImageAndTagCell(containerScanningForRegistryVulnerability.id).exists()).toBe(true); + expect(findImageAndTagCell(containerScanningForRegistryVulnerability.id).props('text')).toBe( + containerScanningForRegistryVulnerability.location.image, + ); + }); + + it('shows the empty image and tag column', () => { + createWrapper({ + props: { + fields: FIELD_PRESETS.CONTAINER_REGISTRY, + vulnerabilities: [{ ...containerScanningForRegistryVulnerability, location: {} }], + }, + }); + + expect(findImageAndTagCell(containerScanningForRegistryVulnerability.id).exists()).toBe( + false, + ); + }); + }); + describe('badges', () => { const findComponentBadgeInRow = (component) => { return (index = 0) => findRow(index).findComponent(component); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 83ca6ecf30676750676418eb2554fdbfece6e180..41f207dea191939e14b9d17ca01297a0a97f71c7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -60058,6 +60058,9 @@ msgstr "" msgid "Vulnerability|Identifiers" msgstr "" +msgid "Vulnerability|Image and tag" +msgstr "" + msgid "Vulnerability|Image:" msgstr ""