Commit 4ef34125 authored by Pavel Shutsin's avatar Pavel Shutsin

Add Vulnerability Management metric for Devops Adoption API

Shows number of projects which haveused vulnerability
management feature

Changelog: added
parent 3bacba23
# frozen_string_literal: true
class AddDevopsAdoptionVulnerabilityManagementUsedCount < ActiveRecord::Migration[6.1]
def change
add_column :analytics_devops_adoption_snapshots, :vulnerability_management_used_count, :integer
end
end
# frozen_string_literal: true
class AddVulnerabilitiesCreatedAtIndex < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
INDEX_NAME = 'idx_vulnerabilities_partial_devops_adoption'
def up
add_concurrent_index :vulnerabilities, [:project_id, :created_at], where: 'state != 1', name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :vulnerabilities, INDEX_NAME
end
end
d7f8f7f5d8a6cf03d500825ef43234c69f7ad36908c0bade337591b05985c2fe
\ No newline at end of file
699ac7f8b9253920271686c497b57521bf4b0d26c802ca2a57447e4929cd147f
\ No newline at end of file
......@@ -9139,6 +9139,7 @@ CREATE TABLE analytics_devops_adoption_snapshots (
dast_enabled_count integer,
dependency_scanning_enabled_count integer,
coverage_fuzzing_enabled_count integer,
vulnerability_management_used_count integer,
CONSTRAINT check_3f472de131 CHECK ((namespace_id IS NOT NULL))
);
......@@ -22717,6 +22718,8 @@ CREATE UNIQUE INDEX idx_vuln_signatures_on_occurrences_id_and_signature_sha ON v
CREATE UNIQUE INDEX idx_vuln_signatures_uniqueness_signature_sha ON vulnerability_finding_signatures USING btree (finding_id, algorithm_type, signature_sha);
CREATE INDEX idx_vulnerabilities_partial_devops_adoption ON vulnerabilities USING btree (project_id, created_at) WHERE (state <> 1);
CREATE UNIQUE INDEX idx_vulnerability_ext_issue_links_on_vulne_id_and_ext_issue ON vulnerability_external_issue_links USING btree (vulnerability_id, external_type, external_project_key, external_issue_key);
CREATE UNIQUE INDEX idx_vulnerability_ext_issue_links_on_vulne_id_and_link_type ON vulnerability_external_issue_links USING btree (vulnerability_id, link_type) WHERE (link_type = 1);
......@@ -8555,6 +8555,7 @@ Snapshot.
| <a id="devopsadoptionsnapshotsecurityscansucceeded"></a>`securityScanSucceeded` | [`Boolean!`](#boolean) | At least one security scan succeeded. |
| <a id="devopsadoptionsnapshotstarttime"></a>`startTime` | [`Time!`](#time) | The start time for the snapshot where the data points were collected. |
| <a id="devopsadoptionsnapshottotalprojectscount"></a>`totalProjectsCount` | [`Int`](#int) | Total number of projects. |
| <a id="devopsadoptionsnapshotvulnerabilitymanagementusedcount"></a>`vulnerabilityManagementUsedCount` | [`Int`](#int) | Total number of projects with vulnerability management used at least once. |
### `DiffPosition`
......
......@@ -32,6 +32,8 @@ module Types
description: 'Total number of projects with enabled dependency scanning.'
field :coverage_fuzzing_enabled_count, GraphQL::INT_TYPE, null: true,
description: 'Total number of projects with enabled coverage fuzzing.'
field :vulnerability_management_used_count, GraphQL::INT_TYPE, null: true,
description: 'Total number of projects with vulnerability management used at least once.'
field :total_projects_count, GraphQL::INT_TYPE, null: true,
description: 'Total number of projects.'
field :recorded_at, Types::TimeType, null: false,
......
......@@ -19,6 +19,7 @@ class Analytics::DevopsAdoption::Snapshot < ApplicationRecord
:dast_enabled_count,
:dependency_scanning_enabled_count,
:coverage_fuzzing_enabled_count,
:vulnerability_management_used_count,
:total_projects_count
].freeze
......
......@@ -86,6 +86,7 @@ module EE
scope :grouped_by_severity, -> { reorder(severity: :desc).group(:severity) }
scope :by_project_fingerprints, -> (project_fingerprints) { joins(:findings).merge(Vulnerabilities::Finding.by_project_fingerprints(project_fingerprints)) }
scope :by_scanner_ids, -> (scanner_ids) { joins(:findings).merge(::Vulnerabilities::Finding.by_scanners(scanner_ids)) }
scope :created_in_time_range, ->(from: nil, to: nil) { where(created_at: from..to) }
scope :with_resolution, -> (has_resolution = true) { where(resolved_on_default_branch: has_resolution) }
scope :with_issues, -> (has_issues = true) do
......
......@@ -111,6 +111,18 @@ module Analytics
projects_count_with_artifact(Ci::JobArtifact.coverage_fuzzing_reports)
end
# rubocop: disable CodeReuse/ActiveRecord
def vulnerability_management_used_count
subquery = Vulnerability.not_detected
.created_in_time_range(from: range_start, to: range_end)
.where(Vulnerability.arel_table[:project_id].eq(Project.arel_table[:id])).arel.exists
snapshot_project_ids.each_slice(1000).sum do |project_ids|
Project.where(id: project_ids).where(subquery).count
end
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def projects_count_with_artifact(artifacts_scope)
subquery = artifacts_scope.created_in_time_range(from: range_start, to: range_end)
......
......@@ -203,6 +203,19 @@ RSpec.describe Analytics::DevopsAdoption::SnapshotCalculator do
include_examples 'calculates artifact type count', :coverage_fuzzing
end
describe 'vulnerability_management_used_count' do
subject { data[:vulnerability_management_used_count] }
it 'returns number of projects with at least 1 vulnerability acted upon' do
create :vulnerability, :resolved, project: project, created_at: 1.week.before(range_end)
create :vulnerability, :resolved, project: subproject, created_at: 1.year.before(range_end)
create :vulnerability, :detected, project: subproject, created_at: 1.week.before(range_end)
create :vulnerability, :resolved, created_at: 1.week.before(range_end)
expect(subject).to eq 1
end
end
context 'when snapshot already exists' do
subject(:data) { described_class.new(enabled_namespace: enabled_namespace, range_end: range_end, snapshot: snapshot).calculate }
......
......@@ -576,6 +576,18 @@ RSpec.describe Vulnerability do
it { is_expected.not_to match("gitlab-org/gitlab-foss/milestones/123") }
end
describe 'created_in_time_range' do
it 'returns vulnerabilities created in given time range', :aggregate_failures do
record1 = create(:vulnerability, created_at: 1.day.ago)
record2 = create(:vulnerability, created_at: 1.month.ago)
record3 = create(:vulnerability, created_at: 1.year.ago)
expect(described_class.created_in_time_range(from: 1.week.ago)).to match_array([record1, vulnerability])
expect(described_class.created_in_time_range(to: 1.week.ago)).to match_array([record2, record3])
expect(described_class.created_in_time_range(from: 2.months.ago, to: 1.week.ago)).to match_array([record2])
end
end
describe '#to_reference' do
let(:namespace) { build(:namespace, path: 'sample-namespace') }
let(:project) { build(:project, name: 'sample-project', namespace: namespace) }
......
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