Commit 66649862 authored by Mark Florian's avatar Mark Florian

Merge branch 'auto-fix-light-bulb' into 'master'

Add AutoFix Indicator to the Vulnerability Report Page

See merge request gitlab-org/gitlab!48251
parents 55b92a6b 20061f40
<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