Commit cb21ab78 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch '6240-filtering-for-history-chart' into 'master'

Add filtering for history and summary on security dashboard

See merge request gitlab-org/gitlab-ee!8972
parents 8a68e857 3a06c929
...@@ -4,41 +4,46 @@ class Groups::Security::VulnerabilitiesController < Groups::Security::Applicatio ...@@ -4,41 +4,46 @@ class Groups::Security::VulnerabilitiesController < Groups::Security::Applicatio
HISTORY_RANGE = 3.months HISTORY_RANGE = 3.months
def index def index
@vulnerabilities = ::Security::VulnerabilitiesFinder.new(group: group, params: finder_params) vulnerabilities = found_vulnerabilities.ordered.page(params[:page])
.execute
.ordered
.page(params[:page])
respond_to do |format| respond_to do |format|
format.json do format.json do
render json: Vulnerabilities::OccurrenceSerializer render json: Vulnerabilities::OccurrenceSerializer
.new(current_user: @current_user) .new(current_user: @current_user)
.with_pagination(request, response) .with_pagination(request, response)
.represent(@vulnerabilities, preload: true) .represent(vulnerabilities, preload: true)
end end
end end
end end
def summary def summary
vulnerabilities_summary = found_vulnerabilities.counted_by_severity
respond_to do |format| respond_to do |format|
format.json do format.json do
render json: VulnerabilitySummarySerializer.new.represent(group) render json: VulnerabilitySummarySerializer.new.represent(vulnerabilities_summary)
end end
end end
end end
def history def history
vulnerabilities_counter = found_vulnerabilities(:all).count_by_day_and_severity(HISTORY_RANGE)
respond_to do |format| respond_to do |format|
format.json do format.json do
render json: Vulnerabilities::HistorySerializer.new.represent(group.all_vulnerabilities.count_by_day_and_severity(HISTORY_RANGE)) render json: Vulnerabilities::HistorySerializer.new.represent(vulnerabilities_counter)
end end
end end
end end
private private
def finder_params def filter_params
params.permit(report_type: [], project_id: [], severity: []) params.permit(report_type: [], project_id: [], severity: [])
.merge(hide_dismissed: Gitlab::Utils.to_boolean(params[:hide_dismissed])) .merge(hide_dismissed: Gitlab::Utils.to_boolean(params[:hide_dismissed]))
end end
def found_vulnerabilities(collection = :latest)
::Security::VulnerabilitiesFinder.new(group: group, params: filter_params).execute(collection)
end
end end
...@@ -21,8 +21,8 @@ module Security ...@@ -21,8 +21,8 @@ module Security
@params = params @params = params
end end
def execute def execute(scope = :latest)
collection = group.latest_vulnerabilities collection = init_collection(scope)
collection = by_report_type(collection) collection = by_report_type(collection)
collection = by_project(collection) collection = by_project(collection)
collection = by_severity(collection) collection = by_severity(collection)
...@@ -52,5 +52,9 @@ module Security ...@@ -52,5 +52,9 @@ module Security
Vulnerabilities::Occurrence::LEVELS.values_at( Vulnerabilities::Occurrence::LEVELS.values_at(
*params[:severity]).compact) *params[:severity]).compact)
end end
def init_collection(scope)
scope == :all ? group.all_vulnerabilities : group.latest_vulnerabilities
end
end end
end end
...@@ -62,7 +62,6 @@ module Vulnerabilities ...@@ -62,7 +62,6 @@ module Vulnerabilities
scope :report_type, -> (type) { where(report_type: self.report_types[type]) } scope :report_type, -> (type) { where(report_type: self.report_types[type]) }
scope :ordered, -> { order("severity desc", :id) } scope :ordered, -> { order("severity desc", :id) }
scope :counted_by_report_and_severity, -> { group(:report_type, :severity).count }
scope :by_report_types, -> (values) { where(report_type: values) } scope :by_report_types, -> (values) { where(report_type: values) }
scope :by_projects, -> (values) { where(project_id: values) } scope :by_projects, -> (values) { where(project_id: values) }
...@@ -85,6 +84,10 @@ module Vulnerabilities ...@@ -85,6 +84,10 @@ module Vulnerabilities
.order('day') .order('day')
end end
def self.counted_by_severity
group(:severity).count
end
def feedback(feedback_type:) def feedback(feedback_type:)
params = { params = {
project_id: project_id, project_id: project_id,
......
# frozen_string_literal: true # frozen_string_literal: true
class VulnerabilitySummaryEntity < Grape::Entity class VulnerabilitySummaryEntity < Grape::Entity
Vulnerabilities::Occurrence::REPORT_TYPES.each do |report_type_name, report_type| Vulnerabilities::Occurrence::LEVELS.each do |severity_name, severity|
expose report_type_name do expose severity_name do |param|
Vulnerabilities::Occurrence::LEVELS.each do |severity_name, severity| object[severity] || 0
expose severity_name do |group|
grouped_vulnerabilities[[report_type_name, severity]] || 0
end
end
end end
end end
private
def grouped_vulnerabilities
@grouped_by_report_and_severity ||= object.latest_vulnerabilities.counted_by_report_and_severity
end
end end
---
title: Add filtering for summary and history on security dashboard
merge_request: 8972
author:
type: added
...@@ -223,11 +223,24 @@ describe Groups::Security::VulnerabilitiesController do ...@@ -223,11 +223,24 @@ describe Groups::Security::VulnerabilitiesController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an(Hash) expect(json_response).to be_an(Hash)
expect(json_response.dig('sast', 'high')).to eq(3) expect(json_response['high']).to eq(3)
expect(json_response.dig('dependency_scanning', 'low')).to eq(3) expect(json_response['low']).to eq(4)
expect(json_response.dig('dast', 'medium')).to eq(1) expect(json_response['medium']).to eq(1)
expect(response).to match_response_schema('vulnerabilities/summary', dir: 'ee') expect(response).to match_response_schema('vulnerabilities/summary', dir: 'ee')
end end
context 'with enabled filters' do
it 'returns counts for filtered vulnerabilities' do
get :summary, group_id: group, report_type: %w[sast dast], severity: %[high low], format: :json
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an(Hash)
expect(json_response['high']).to eq(3)
expect(json_response['low']).to eq(1)
expect(json_response['medium']).to eq(1)
expect(response).to match_response_schema('vulnerabilities/summary', dir: 'ee')
end
end
end end
end end
end end
...@@ -334,6 +347,21 @@ describe Groups::Security::VulnerabilitiesController do ...@@ -334,6 +347,21 @@ describe Groups::Security::VulnerabilitiesController do
}) })
expect(response).to match_response_schema('vulnerabilities/history', dir: 'ee') expect(response).to match_response_schema('vulnerabilities/history', dir: 'ee')
end end
it 'returns filtered history if filters are enabled' do
travel_to(Time.zone.parse('2019-02-10')) do
get :history, params: { group_id: group, report_type: %w[dependency_scanning sast] }, format: :json
end
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an(Hash)
expect(json_response['total']).to eq({ '2018-11-10' => 5, '2018-11-12' => 2 })
expect(json_response['critical']).to eq({ '2018-11-10' => 1 })
expect(json_response['high']).to eq({ '2018-11-10' => 2 })
expect(json_response['medium']).to eq({})
expect(json_response['low']).to eq({ '2018-11-10' => 2, '2018-11-12' => 2 })
expect(response).to match_response_schema('vulnerabilities/history', dir: 'ee')
end
end end
end end
end end
......
{ {
"type" : "object", "type" : "object",
"required" : [
"dast",
"sast",
"container_scanning",
"dependency_scanning"
],
"properties" : { "properties" : {
"dast" : { "$ref": "summary_for_report_type.json" }, "undefined" : { "type": "integer" },
"sast" : { "$ref": "summary_for_report_type.json" }, "ignore" : { "type": "integer" },
"container_scanning" : { "$ref": "summary_for_report_type.json" }, "unknown" : { "type": "integer" },
"dependency_scanning" : { "$ref": "summary_for_report_type.json" } "experimental" : { "type": "integer" },
"low" : { "type": "integer" },
"medium" : { "type": "integer" },
"high" : { "type": "integer" },
"critical" : { "type": "integer" }
}, },
"additional_properties" : false "additional_properties" : false
} }
{
"type" : "object",
"properties" : {
"undefined" : { "type": "integer" },
"ignore" : { "type": "integer" },
"unknown" : { "type": "integer" },
"experimental" : { "type": "integer" },
"low" : { "type": "integer" },
"medium" : { "type": "integer" },
"high" : { "type": "integer" },
"critical" : { "type": "integer" }
},
"additional_properties" : false
}
...@@ -195,4 +195,16 @@ describe Vulnerabilities::Occurrence do ...@@ -195,4 +195,16 @@ describe Vulnerabilities::Occurrence do
end end
end end
end end
describe '.count_by_severity' do
let!(:high_vulnerabilities) { create_list(:vulnerabilities_occurrence, 3, severity: :high) }
let!(:medium_vulnerabilities) { create_list(:vulnerabilities_occurrence, 2, severity: :medium) }
let!(:low_vulnerabilities) { create_list(:vulnerabilities_occurrence, 1, severity: :low) }
subject { described_class.counted_by_severity }
it 'returns counts' do
is_expected.to eq({ 4 => 1, 5 => 2, 6 => 3 })
end
end
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