Commit 20061f40 authored by Jannik Lehmann's avatar Jannik Lehmann Committed by Mark Florian

Add AutoFix Indicator on the Vulnerability Page

This commit introduces an AutoFix Indicator
Badge whenever AutoFix is available for the
given Vulnerability.
parent e11ae35f
<script> <script>
import produce from 'immer';
import { GlAlert, GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui'; import { GlAlert, GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui';
import produce from 'immer';
import { __ } from '~/locale'; import { __ } from '~/locale';
import VulnerabilityList from './vulnerability_list.vue';
import vulnerabilitiesQuery from '../graphql/project_vulnerabilities.graphql';
import securityScannersQuery from '../graphql/project_security_scanners.graphql'; import securityScannersQuery from '../graphql/project_security_scanners.graphql';
import { VULNERABILITIES_PER_PAGE } from '../store/constants'; import vulnerabilitiesQuery from '../graphql/project_vulnerabilities.query.graphql';
import vulnerabilitiesQueryAutoFix from '../graphql/project_vulnerabilities_autofix.query.graphql';
import { preparePageInfo } from '../helpers'; import { preparePageInfo } from '../helpers';
import { VULNERABILITIES_PER_PAGE } from '../store/constants';
import VulnerabilityList from './vulnerability_list.vue';
const query = gon?.features?.secureVulnerabilityAutofixIndicator
? vulnerabilitiesQueryAutoFix
: vulnerabilitiesQuery;
export default { export default {
name: 'ProjectVulnerabilitiesApp', name: 'ProjectVulnerabilitiesApp',
...@@ -36,7 +41,7 @@ export default { ...@@ -36,7 +41,7 @@ export default {
}, },
apollo: { apollo: {
vulnerabilities: { vulnerabilities: {
query: vulnerabilitiesQuery, query,
variables() { variables() {
return { return {
fullPath: this.projectFullPath, fullPath: this.projectFullPath,
......
...@@ -8,6 +8,7 @@ import { ...@@ -8,6 +8,7 @@ import {
GlSkeletonLoading, GlSkeletonLoading,
GlTooltipDirective, GlTooltipDirective,
GlTable, GlTable,
GlBadge,
} from '@gitlab/ui'; } from '@gitlab/ui';
import RemediatedBadge from 'ee/vulnerabilities/components/remediated_badge.vue'; import RemediatedBadge from 'ee/vulnerabilities/components/remediated_badge.vue';
import FiltersProducedNoResults from 'ee/security_dashboard/components/empty_states/filters_produced_no_results.vue'; import FiltersProducedNoResults from 'ee/security_dashboard/components/empty_states/filters_produced_no_results.vue';
...@@ -36,6 +37,7 @@ export default { ...@@ -36,6 +37,7 @@ export default {
GlSkeletonLoading, GlSkeletonLoading,
GlSprintf, GlSprintf,
GlTable, GlTable,
GlBadge,
GlTruncate, GlTruncate,
IssuesBadge, IssuesBadge,
LocalStorageSync, LocalStorageSync,
...@@ -433,6 +435,14 @@ export default { ...@@ -433,6 +435,14 @@ export default {
<template #cell(activity)="{ item }"> <template #cell(activity)="{ item }">
<div class="gl-display-flex gl-justify-content-end"> <div class="gl-display-flex gl-justify-content-end">
<gl-badge
v-if="item.solutions"
v-gl-tooltip
data-testid="vulnerability-solutions-bulb"
variant="neutral"
icon="bulb"
:title="s__('AutoRemediation|Auto-fix solution available')"
/>
<issues-badge v-if="issues(item).length > 0" :issues="issues(item)" /> <issues-badge v-if="issues(item).length > 0" :issues="issues(item)" />
<remediated-badge v-if="item.resolvedOnDefaultBranch" class="gl-ml-3" /> <remediated-badge v-if="item.resolvedOnDefaultBranch" class="gl-ml-3" />
</div> </div>
......
#import "~/graphql_shared/fragments/pageInfoCursorsOnly.fragment.graphql"
#import "./vulnerability.fragment.graphql"
query project(
$fullPath: ID!
$after: String
$first: Int
$severity: [VulnerabilitySeverity!]
$reportType: [VulnerabilityReportType!]
$scanner: [String!]
$state: [VulnerabilityState!]
$sort: VulnerabilitySort
) {
project(fullPath: $fullPath) {
vulnerabilities(
after: $after
first: $first
severity: $severity
reportType: $reportType
scanner: $scanner
state: $state
sort: $sort
) {
nodes {
...Vulnerability
solutions
}
pageInfo {
...PageInfo
}
}
}
}
...@@ -5,6 +5,10 @@ module Projects ...@@ -5,6 +5,10 @@ module Projects
class VulnerabilityReportController < Projects::ApplicationController class VulnerabilityReportController < Projects::ApplicationController
include SecurityDashboardsPermissions include SecurityDashboardsPermissions
before_action do
push_frontend_feature_flag(:secure_vulnerability_autofix_indicator, @project)
end
feature_category :vulnerability_management feature_category :vulnerability_management
alias_method :vulnerable, :project alias_method :vulnerable, :project
......
---
name: secure_vulnerability_autofix_indicator
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48251/
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/288347
milestone: '13.7'
type: development
group: group::composition analysis
default_enabled: false
...@@ -2,6 +2,7 @@ export const generateVulnerabilities = () => [ ...@@ -2,6 +2,7 @@ export const generateVulnerabilities = () => [
{ {
id: 'id_0', id: 'id_0',
detectedAt: '2020-07-29T15:36:54Z', detectedAt: '2020-07-29T15:36:54Z',
solutions: true,
identifiers: [ identifiers: [
{ {
externalType: 'cve', externalType: 'cve',
...@@ -34,6 +35,7 @@ export const generateVulnerabilities = () => [ ...@@ -34,6 +35,7 @@ export const generateVulnerabilities = () => [
{ {
id: 'id_1', id: 'id_1',
detectedAt: '2020-07-22T19:31:24Z', detectedAt: '2020-07-22T19:31:24Z',
solutions: false,
identifiers: [ identifiers: [
{ {
externalType: 'gemnasium', externalType: 'gemnasium',
......
...@@ -47,6 +47,7 @@ describe('Vulnerability list component', () => { ...@@ -47,6 +47,7 @@ describe('Vulnerability list component', () => {
const findRows = () => wrapper.findAll('tbody tr'); const findRows = () => wrapper.findAll('tbody tr');
const findRow = (index = 0) => findRows().at(index); const findRow = (index = 0) => findRows().at(index);
const findRowById = id => wrapper.find(`tbody tr[data-pk="${id}"`); const findRowById = id => wrapper.find(`tbody tr[data-pk="${id}"`);
const findAutoFixBulbInRow = row => row.find('[data-testid="vulnerability-solutions-bulb"]');
const findIssuesBadge = (index = 0) => wrapper.findAll(IssuesBadge).at(index); const findIssuesBadge = (index = 0) => wrapper.findAll(IssuesBadge).at(index);
const findRemediatedBadge = () => wrapper.find(RemediatedBadge); const findRemediatedBadge = () => wrapper.find(RemediatedBadge);
const findSecurityScannerAlert = () => wrapper.find(SecurityScannerAlert); const findSecurityScannerAlert = () => wrapper.find(SecurityScannerAlert);
...@@ -105,6 +106,14 @@ describe('Vulnerability list component', () => { ...@@ -105,6 +106,14 @@ describe('Vulnerability list component', () => {
expect(findRemediatedBadge().exists()).toBe(true); expect(findRemediatedBadge().exists()).toBe(true);
}); });
it('should display autoFixIcon for first Item', () => {
expect(findAutoFixBulbInRow(findRow(0)).exists()).toBe(true);
});
it('should not display autoFixIcon for second Item', () => {
expect(findAutoFixBulbInRow(findRow(1)).exists()).toBe(false);
});
it('should correctly render the identifier cell', () => { it('should correctly render the identifier cell', () => {
const identifiers = findDataCells('vulnerability-identifier'); const identifiers = findDataCells('vulnerability-identifier');
const extraIdentifierCounts = findDataCells('vulnerability-more-identifiers'); const extraIdentifierCounts = findDataCells('vulnerability-more-identifiers');
......
...@@ -4099,6 +4099,9 @@ msgstr "" ...@@ -4099,6 +4099,9 @@ msgstr ""
msgid "AutoRemediation|%{mrsCount} ready for review" msgid "AutoRemediation|%{mrsCount} ready for review"
msgstr "" msgstr ""
msgid "AutoRemediation|Auto-fix solution available"
msgstr ""
msgid "AutoRemediation|Auto-fix solutions" msgid "AutoRemediation|Auto-fix solutions"
msgstr "" 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