Commit deb72ebb authored by Vitali Tatarintev's avatar Vitali Tatarintev

Merge branch '233810-add-arguments-to-vulnerabilities-severities-count-in-graphql' into 'master'

Add arguments to vulnerabilitySeveritiesCount in GraphQL API

See merge request gitlab-org/gitlab!41067
parents 0adc3cda 06349e6c
......@@ -6863,6 +6863,36 @@ type Group {
last: Int
): VulnerabilityScannerConnection
"""
Counts for each vulnerability severity in the group and its subgroups
"""
vulnerabilitySeveritiesCount(
"""
Filter vulnerabilities by project
"""
projectId: [ID!]
"""
Filter vulnerabilities by report type
"""
reportType: [VulnerabilityReportType!]
"""
Filter vulnerabilities by scanner
"""
scanner: [String!]
"""
Filter vulnerabilities by severity
"""
severity: [VulnerabilitySeverity!]
"""
Filter vulnerabilities by state
"""
state: [VulnerabilityState!]
): VulnerabilitySeveritiesCount
"""
Web URL of the group
"""
......@@ -7030,6 +7060,36 @@ type InstanceSecurityDashboard {
"""
last: Int
): VulnerabilityScannerConnection
"""
Counts for each vulnerability severity from projects selected in Instance Security Dashboard
"""
vulnerabilitySeveritiesCount(
"""
Filter vulnerabilities by project
"""
projectId: [ID!]
"""
Filter vulnerabilities by report type
"""
reportType: [VulnerabilityReportType!]
"""
Filter vulnerabilities by scanner
"""
scanner: [String!]
"""
Filter vulnerabilities by severity
"""
severity: [VulnerabilitySeverity!]
"""
Filter vulnerabilities by state
"""
state: [VulnerabilityState!]
): VulnerabilitySeveritiesCount
}
"""
......@@ -12428,9 +12488,34 @@ type Project {
): VulnerabilityScannerConnection
"""
Counts for each severity of vulnerability of the project
Counts for each vulnerability severity in the project
"""
vulnerabilitySeveritiesCount: VulnerabilitySeveritiesCount
vulnerabilitySeveritiesCount(
"""
Filter vulnerabilities by project
"""
projectId: [ID!]
"""
Filter vulnerabilities by report type
"""
reportType: [VulnerabilityReportType!]
"""
Filter vulnerabilities by scanner
"""
scanner: [String!]
"""
Filter vulnerabilities by severity
"""
severity: [VulnerabilitySeverity!]
"""
Filter vulnerabilities by state
"""
state: [VulnerabilityState!]
): VulnerabilitySeveritiesCount
"""
Web URL of the project
......
......@@ -1048,6 +1048,7 @@ Autogenerated return type of EpicTreeReorder
| `userPermissions` | GroupPermissions! | Permissions for the current user on the resource |
| `visibility` | String | Visibility of the namespace |
| `vulnerabilityGrades` | VulnerableProjectsByGrade! => Array | Represents vulnerable project counts for each grade |
| `vulnerabilitySeveritiesCount` | VulnerabilitySeveritiesCount | Counts for each vulnerability severity in the group and its subgroups |
| `webUrl` | String! | Web URL of the group |
## GroupMember
......@@ -1077,6 +1078,7 @@ Represents a Group Membership
| Name | Type | Description |
| --- | ---- | ---------- |
| `vulnerabilityGrades` | VulnerableProjectsByGrade! => Array | Represents vulnerable project counts for each grade |
| `vulnerabilitySeveritiesCount` | VulnerabilitySeveritiesCount | Counts for each vulnerability severity from projects selected in Instance Security Dashboard |
## Issue
......@@ -1770,7 +1772,7 @@ Autogenerated return type of PipelineRetry
| `tagList` | String | List of project topics (not Git tags) |
| `userPermissions` | ProjectPermissions! | Permissions for the current user on the resource |
| `visibility` | String | Visibility of the project |
| `vulnerabilitySeveritiesCount` | VulnerabilitySeveritiesCount | Counts for each severity of vulnerability of the project |
| `vulnerabilitySeveritiesCount` | VulnerabilitySeveritiesCount | Counts for each vulnerability severity in the project |
| `webUrl` | String | Web URL of the project |
| `wikiEnabled` | Boolean | Indicates if Wikis are enabled for the current user |
......
......@@ -42,6 +42,10 @@ module EE
description: 'Vulnerability scanners reported on the project vulnerabilties of the group and its subgroups',
resolver: ::Resolvers::Vulnerabilities::ScannersResolver
field :vulnerability_severities_count, ::Types::VulnerabilitySeveritiesCountType, null: true,
description: 'Counts for each vulnerability severity in the group and its subgroups',
resolver: ::Resolvers::VulnerabilitySeveritiesCountResolver
field :vulnerabilities_count_by_day,
::Types::VulnerabilitiesCountByDayType.connection_type,
null: true,
......
......@@ -42,8 +42,8 @@ module EE
resolver: ::Resolvers::Vulnerabilities::ScannersResolver
field :vulnerability_severities_count, ::Types::VulnerabilitySeveritiesCountType, null: true,
description: 'Counts for each severity of vulnerability of the project',
resolve: -> (obj, *) { obj.vulnerability_statistic || Hash.new(0) }
description: 'Counts for each vulnerability severity in the project',
resolver: ::Resolvers::VulnerabilitySeveritiesCountResolver
field :requirement, ::Types::RequirementsManagement::RequirementType, null: true,
description: 'Find a single requirement. Available only when feature flag `requirements_management` is enabled.',
......
# frozen_string_literal: true
module Resolvers
class VulnerabilitySeveritiesCountResolver < VulnerabilitiesBaseResolver
include Gitlab::Utils::StrongMemoize
type Types::VulnerabilitySeveritiesCountType, null: true
argument :project_id, [GraphQL::ID_TYPE],
required: false,
description: 'Filter vulnerabilities by project'
argument :report_type, [Types::VulnerabilityReportTypeEnum],
required: false,
description: 'Filter vulnerabilities by report type'
argument :severity, [Types::VulnerabilitySeverityEnum],
required: false,
description: 'Filter vulnerabilities by severity'
argument :state, [Types::VulnerabilityStateEnum],
required: false,
description: 'Filter vulnerabilities by state'
argument :scanner, [GraphQL::STRING_TYPE],
required: false,
description: 'Filter vulnerabilities by scanner'
def resolve(**args)
return Vulnerability.none unless vulnerable
Hash.new(0)
.merge(vulnerabilities(args).grouped_by_severity.count)
end
private
def vulnerabilities(filters)
Security::VulnerabilitiesFinder.new(vulnerable, filters).execute
end
end
end
......@@ -18,6 +18,10 @@ module Types
description: 'Vulnerability scanners reported on the vulnerabilties from projects selected in Instance Security Dashboard',
resolver: ::Resolvers::Vulnerabilities::ScannersResolver
field :vulnerability_severities_count, ::Types::VulnerabilitySeveritiesCountType, null: true,
description: 'Counts for each vulnerability severity from projects selected in Instance Security Dashboard',
resolver: ::Resolvers::VulnerabilitySeveritiesCountResolver
field :vulnerability_grades,
[Types::VulnerableProjectsByGradeType],
null: false,
......
......@@ -71,6 +71,7 @@ class Vulnerability < ApplicationRecord
scope :with_severities, -> (severities) { where(severity: severities) }
scope :with_states, -> (states) { where(state: states) }
scope :with_scanners, -> (scanners) { joins(findings: :scanner).merge(Vulnerabilities::Scanner.with_external_id(scanners)) }
scope :grouped_by_severity, -> { group(:severity) }
class << self
def parent_class
......
---
title: Add ability to filter vulnerabilitiesSeveritiesCount in GraphQL for Project,
Group and Instance Security Dashboard
merge_request: 41067
author:
type: added
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::VulnerabilitySeveritiesCountResolver do
include GraphqlHelpers
describe '#resolve' do
subject { resolve(described_class, obj: vulnerable, args: filters, ctx: { current_user: current_user }) }
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, security_dashboard_projects: [project]) }
let_it_be(:low_vulnerability) do
create(:vulnerability, :with_findings, :detected, :low, :dast, project: project)
end
let_it_be(:critical_vulnerability) do
create(:vulnerability, :with_findings, :detected, :critical, :sast, project: project)
end
let_it_be(:high_vulnerability) do
create(:vulnerability, :with_findings, :dismissed, :high, :container_scanning, project: project)
end
let(:current_user) { user }
let(:filters) { {} }
let(:vulnerable) { project }
context 'when given severities' do
let(:filters) { { severity: ['low'] } }
it 'only returns count for low severity vulnerability' do
is_expected.to eq('low' => 1)
end
end
context 'when given states' do
let(:filters) { { state: ['dismissed'] } }
it 'only returns count for high severity vulnerability' do
is_expected.to eq('high' => 1)
end
end
context 'when given scanner' do
let(:filters) { { scanner: [high_vulnerability.finding_scanner_external_id] } }
it 'only returns count for high severity vulnerability' do
is_expected.to eq('high' => 1)
end
end
context 'when given report types' do
let(:filters) { { report_type: %i[dast sast] } }
it 'only returns count for vulnerabilities of the given report types' do
is_expected.to eq('critical' => 1, 'low' => 1)
end
end
context 'when resolving vulnerabilities for a project' do
it "returns the project's vulnerabilities" do
is_expected.to eq('critical' => 1, 'high' => 1, 'low' => 1)
end
end
context 'when resolving vulnerabilities for an instance security dashboard' do
before do
project.add_developer(user)
end
let(:vulnerable) { nil }
context 'when there is a current user' do
it "returns vulnerabilities for all projects on the current user's instance security dashboard" do
is_expected.to eq('critical' => 1, 'high' => 1, 'low' => 1)
end
end
context 'and there is no current user' do
let(:current_user) { nil }
it 'returns no vulnerabilities' do
is_expected.to be_empty
end
end
end
end
end
......@@ -8,7 +8,7 @@ RSpec.describe GitlabSchema.types['InstanceSecurityDashboard'] do
let_it_be(:user) { create(:user, security_dashboard_projects: [project]) }
let(:fields) do
%i[projects vulnerability_scanners vulnerability_grades]
%i[projects vulnerability_scanners vulnerability_severities_count vulnerability_grades]
end
before do
......
......@@ -16,7 +16,7 @@ RSpec.describe GitlabSchema.types['Project'] do
it 'includes the ee specific fields' do
expected_fields = %w[
vulnerabilities sast_ci_configuration vulnerability_scanners requirement_states_count
vulnerability_severities_count packages compliance_frameworks
vulnerability_severities_count packages compliance_frameworks vulnerability_severities_count
security_dashboard_path iterations
]
......
......@@ -226,6 +226,21 @@ RSpec.describe Vulnerability do
it { is_expected.to match_array(expected_values) }
end
describe '.grouped_by_severity' do
before do
create_list(:vulnerability, 6, :critical)
create_list(:vulnerability, 4, :high)
create_list(:vulnerability, 2, :medium)
create_list(:vulnerability, 5, :low)
create_list(:vulnerability, 1, :info)
create_list(:vulnerability, 3, :unknown)
end
subject { described_class.grouped_by_severity.count }
it { is_expected.to eq('critical' => 6, 'high' => 4, 'info' => 1, 'low' => 5, 'medium' => 2, 'unknown' => 3) }
end
describe '#finding' do
let_it_be(:project) { create(:project, :with_vulnerability) }
let_it_be(:vulnerability) { project.vulnerabilities.first }
......
......@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Query.project(fullPath).vulnerabilitySeveritiesCount' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:statistic) { create(:vulnerability_statistic, :grade_d, project: project) }
let_it_be(:vulnerability) { create(:vulnerability, :high, project: project) }
let_it_be(:query) do
%(
......@@ -30,6 +30,6 @@ RSpec.describe 'Query.project(fullPath).vulnerabilitySeveritiesCount' do
it "returns counts for each severity of the project's detected or confirmed vulnerabilities" do
high_count = subject.dig('data', 'project', 'vulnerabilitySeveritiesCount', 'high')
expect(high_count).to be(statistic.high)
expect(high_count).to eq(1)
end
end
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