Commit 6318e161 authored by Dylan Griffith's avatar Dylan Griffith

Merge branch '347325-add-filtering-and-sorting-to-compliance-violations-graphql' into 'master'

Add filtering and sorting to compliance violations GraphQL type

See merge request gitlab-org/gitlab!80996
parents be70ef77 bf343e65
...@@ -11104,7 +11104,6 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -11104,7 +11104,6 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupistemporarystorageincreaseenabled"></a>`isTemporaryStorageIncreaseEnabled` | [`Boolean!`](#boolean) | Status of the temporary storage increase. | | <a id="groupistemporarystorageincreaseenabled"></a>`isTemporaryStorageIncreaseEnabled` | [`Boolean!`](#boolean) | Status of the temporary storage increase. |
| <a id="grouplfsenabled"></a>`lfsEnabled` | [`Boolean`](#boolean) | Indicates if Large File Storage (LFS) is enabled for namespace. | | <a id="grouplfsenabled"></a>`lfsEnabled` | [`Boolean`](#boolean) | Indicates if Large File Storage (LFS) is enabled for namespace. |
| <a id="groupmentionsdisabled"></a>`mentionsDisabled` | [`Boolean`](#boolean) | Indicates if a group is disabled from getting mentioned. | | <a id="groupmentionsdisabled"></a>`mentionsDisabled` | [`Boolean`](#boolean) | Indicates if a group is disabled from getting mentioned. |
| <a id="groupmergerequestviolations"></a>`mergeRequestViolations` | [`ComplianceViolationConnection`](#complianceviolationconnection) | Compliance violations reported on merge requests merged within the group. Available only when feature flag `compliance_violations_graphql_type` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
| <a id="groupname"></a>`name` | [`String!`](#string) | Name of the namespace. | | <a id="groupname"></a>`name` | [`String!`](#string) | Name of the namespace. |
| <a id="grouporganizations"></a>`organizations` | [`CustomerRelationsOrganizationConnection`](#customerrelationsorganizationconnection) | Find organizations of this group. (see [Connections](#connections)) | | <a id="grouporganizations"></a>`organizations` | [`CustomerRelationsOrganizationConnection`](#customerrelationsorganizationconnection) | Find organizations of this group. (see [Connections](#connections)) |
| <a id="grouppackagesettings"></a>`packageSettings` | [`PackageSettings`](#packagesettings) | Package settings for the namespace. | | <a id="grouppackagesettings"></a>`packageSettings` | [`PackageSettings`](#packagesettings) | Package settings for the namespace. |
...@@ -11449,6 +11448,23 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -11449,6 +11448,23 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="grouplabelsonlygrouplabels"></a>`onlyGroupLabels` | [`Boolean`](#boolean) | Include only group level labels. | | <a id="grouplabelsonlygrouplabels"></a>`onlyGroupLabels` | [`Boolean`](#boolean) | Include only group level labels. |
| <a id="grouplabelssearchterm"></a>`searchTerm` | [`String`](#string) | Search term to find labels with. | | <a id="grouplabelssearchterm"></a>`searchTerm` | [`String`](#string) | Search term to find labels with. |
##### `Group.mergeRequestViolations`
Compliance violations reported on merge requests merged within the group. Available only when feature flag `compliance_violations_graphql_type` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice.
Returns [`ComplianceViolationConnection`](#complianceviolationconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="groupmergerequestviolationsfilters"></a>`filters` | [`ComplianceViolationInput`](#complianceviolationinput) | Filters applied when retrieving compliance violations. |
| <a id="groupmergerequestviolationssort"></a>`sort` | [`ComplianceViolationSort`](#complianceviolationsort) | List compliance violations by sort order. |
##### `Group.mergeRequests` ##### `Group.mergeRequests`
Merge requests for projects in this group. Merge requests for projects in this group.
...@@ -17156,6 +17172,21 @@ Severity of the compliance violation. ...@@ -17156,6 +17172,21 @@ Severity of the compliance violation.
| <a id="complianceviolationseveritylow"></a>`LOW` | Low severity. | | <a id="complianceviolationseveritylow"></a>`LOW` | Low severity. |
| <a id="complianceviolationseveritymedium"></a>`MEDIUM` | Medium severity. | | <a id="complianceviolationseveritymedium"></a>`MEDIUM` | Medium severity. |
### `ComplianceViolationSort`
Compliance violation sort values.
| Value | Description |
| ----- | ----------- |
| <a id="complianceviolationsortmerged_at_asc"></a>`MERGED_AT_ASC` | Date merged in ascending order, further sorted by ID in ascending order. |
| <a id="complianceviolationsortmerged_at_desc"></a>`MERGED_AT_DESC` | Date merged in descending order, further sorted by ID in descending order. |
| <a id="complianceviolationsortmerge_request_title_asc"></a>`MERGE_REQUEST_TITLE_ASC` | Merge request title in ascending order, further sorted by ID in ascending order. |
| <a id="complianceviolationsortmerge_request_title_desc"></a>`MERGE_REQUEST_TITLE_DESC` | Merge request title in descending order, further sorted by ID in descending order. |
| <a id="complianceviolationsortseverity_level_asc"></a>`SEVERITY_LEVEL_ASC` | Severity in ascending order, further sorted by ID in ascending order. |
| <a id="complianceviolationsortseverity_level_desc"></a>`SEVERITY_LEVEL_DESC` | Severity in descending order, further sorted by ID in descending order. |
| <a id="complianceviolationsortviolation_reason_asc"></a>`VIOLATION_REASON_ASC` | Violation reason in ascending order, further sorted by ID in ascending order. |
| <a id="complianceviolationsortviolation_reason_desc"></a>`VIOLATION_REASON_DESC` | Violation reason in descending order, further sorted by ID in descending order. |
### `ConanMetadatumFileTypeEnum` ### `ConanMetadatumFileTypeEnum`
Conan file types. Conan file types.
...@@ -19744,6 +19775,16 @@ Field that are available while modifying the custom mapping attributes for an HT ...@@ -19744,6 +19775,16 @@ Field that are available while modifying the custom mapping attributes for an HT
| <a id="complianceframeworkinputname"></a>`name` | [`String`](#string) | New name for the compliance framework. | | <a id="complianceframeworkinputname"></a>`name` | [`String`](#string) | New name for the compliance framework. |
| <a id="complianceframeworkinputpipelineconfigurationfullpath"></a>`pipelineConfigurationFullPath` | [`String`](#string) | Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hipaa` **(ULTIMATE)**. | | <a id="complianceframeworkinputpipelineconfigurationfullpath"></a>`pipelineConfigurationFullPath` | [`String`](#string) | Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hipaa` **(ULTIMATE)**. |
### `ComplianceViolationInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="complianceviolationinputmergedafter"></a>`mergedAfter` | [`Date`](#date) | Merged date of merge requests merged after a compliance violation was created. |
| <a id="complianceviolationinputmergedbefore"></a>`mergedBefore` | [`Date`](#date) | Merged date of merge requests merged before a compliance violation was created. |
| <a id="complianceviolationinputprojectids"></a>`projectIds` | [`[ProjectID!]`](#projectid) | Filter compliance violations by project. |
### `DastProfileCadenceInput` ### `DastProfileCadenceInput`
Represents DAST Profile Cadence. Represents DAST Profile Cadence.
...@@ -7,6 +7,11 @@ ...@@ -7,6 +7,11 @@
# Arguments: # Arguments:
# current_user: the current user to validate they have the right permissions to access the compliance violations data # current_user: the current user to validate they have the right permissions to access the compliance violations data
# group_id: the ID of the group to search within # group_id: the ID of the group to search within
# params: optional! a hash with one or more of the following:
# project_ids: restrict the compliance violations returned to those in the group's projects that also match these IDs
# merged_before: only return compliance violations which were caused by a merge request merged on or before this date
# merged_after: only return compliance violations which were caused by a merge request merged on or after this date
# sort: return compliance violations ordered by severity level, violation reason, merge request title or date merged (asc/desc)
module ComplianceManagement module ComplianceManagement
module MergeRequests module MergeRequests
...@@ -14,25 +19,68 @@ module ComplianceManagement ...@@ -14,25 +19,68 @@ module ComplianceManagement
include FinderMethods include FinderMethods
include MergedAtFilter include MergedAtFilter
def initialize(current_user:, group:) def initialize(current_user:, group:, params: {})
@current_user = current_user @current_user = current_user
@group = group @group = group
@params = params
end end
def execute def execute
return ::MergeRequests::ComplianceViolation.none unless allowed? return ::MergeRequests::ComplianceViolation.none unless allowed?
::MergeRequests::ComplianceViolation.by_group(group).order_by_severity_level(:desc) items = init_collection
items = filter_by_projects(items)
items = filter_by_merged_before(items)
items = filter_by_merged_after(items)
sort(items)
end end
private private
attr_reader :current_user, :group attr_reader :current_user, :group, :params
def allowed? def allowed?
::Feature.enabled?(:compliance_violations_graphql_type, group, default_enabled: :yaml) && ::Feature.enabled?(:compliance_violations_graphql_type, group, default_enabled: :yaml) &&
Ability.allowed?(current_user, :read_group_compliance_dashboard, group) Ability.allowed?(current_user, :read_group_compliance_dashboard, group)
end end
def init_collection
::MergeRequests::ComplianceViolation.with_violating_user.by_group(group)
end
def filter_by_projects(items)
return items unless params[:project_ids].present?
items.by_projects(params[:project_ids])
end
def filter_by_merged_before(items)
return items unless params[:merged_before].present?
items.merged_before(params[:merged_before]) if params[:merged_before].present?
end
def filter_by_merged_after(items)
return items unless params[:merged_after].present?
items.merged_after(params[:merged_after]) if params[:merged_after].present?
end
def sort(items)
case params[:sort]
when 'SEVERITY_LEVEL_ASC' then items.order_by_severity_level(:asc)
when 'SEVERITY_LEVEL_DESC' then items.order_by_severity_level(:desc)
when 'VIOLATION_REASON_ASC' then items.order_by_reason(:asc)
when 'VIOLATION_REASON_DESC' then items.order_by_reason(:desc)
when 'MERGE_REQUEST_TITLE_ASC' then items.order_by_merge_request_title(:asc)
when 'MERGE_REQUEST_TITLE_DESC' then items.order_by_merge_request_title(:desc)
when 'MERGED_AT_ASC' then items.order_by_merged_at(:asc)
when 'MERGED_AT_DESC' then items.order_by_merged_at(:desc)
else items.order_by_severity_level(:desc)
end
end
end end
end end
end end
...@@ -9,8 +9,18 @@ module Resolvers ...@@ -9,8 +9,18 @@ module Resolvers
type ::Types::ComplianceManagement::MergeRequests::ComplianceViolationType.connection_type, null: true type ::Types::ComplianceManagement::MergeRequests::ComplianceViolationType.connection_type, null: true
description 'Compliance violations reported on a merged merge request.' description 'Compliance violations reported on a merged merge request.'
def resolve argument :filters, Types::ComplianceManagement::MergeRequests::ComplianceViolationInputType,
::ComplianceManagement::MergeRequests::ComplianceViolationsFinder.new(current_user: current_user, group: group).execute required: false,
default_value: {},
description: 'Filters applied when retrieving compliance violations.'
argument :sort, ::Types::ComplianceManagement::MergeRequests::ComplianceViolationSortEnum,
required: false,
default_value: 'SEVERITY_LEVEL_DESC',
description: 'List compliance violations by sort order.'
def resolve(filters: {}, sort: 'SEVERITY_LEVEL_DESC')
::ComplianceManagement::MergeRequests::ComplianceViolationsFinder.new(current_user: current_user, group: group, params: filters.to_h.merge(sort: sort)).execute
end end
end end
end end
......
# frozen_string_literal: true
module Types
module ComplianceManagement
module MergeRequests
class ComplianceViolationInputType < BaseInputObject
graphql_name 'ComplianceViolationInput'
argument :project_ids, [::Types::GlobalIDType[::Project]],
required: false,
description: 'Filter compliance violations by project.',
prepare: ->(ids, _ctx) { ids.map(&:model_id) }
argument :merged_before, ::Types::DateType,
required: false,
description: 'Merged date of merge requests merged before a compliance violation was created.'
argument :merged_after, ::Types::DateType,
required: false,
description: 'Merged date of merge requests merged after a compliance violation was created.'
end
end
end
end
# frozen_string_literal: true
module Types
module ComplianceManagement
module MergeRequests
class ComplianceViolationSortEnum < BaseEnum
graphql_name 'ComplianceViolationSort'
description 'Compliance violation sort values.'
value 'SEVERITY_LEVEL_DESC', 'Severity in descending order, further sorted by ID in descending order.'
value 'SEVERITY_LEVEL_ASC', 'Severity in ascending order, further sorted by ID in ascending order.'
value 'VIOLATION_REASON_DESC', 'Violation reason in descending order, further sorted by ID in descending order.'
value 'VIOLATION_REASON_ASC', 'Violation reason in ascending order, further sorted by ID in ascending order.'
value 'MERGE_REQUEST_TITLE_DESC', 'Merge request title in descending order, further sorted by ID in descending order.'
value 'MERGE_REQUEST_TITLE_ASC', 'Merge request title in ascending order, further sorted by ID in ascending order.'
value 'MERGED_AT_DESC', 'Date merged in descending order, further sorted by ID in descending order.'
value 'MERGED_AT_ASC', 'Date merged in ascending order, further sorted by ID in ascending order.'
end
end
end
end
...@@ -9,13 +9,22 @@ module MergeRequests ...@@ -9,13 +9,22 @@ module MergeRequests
enum reason: ::Enums::MergeRequests::ComplianceViolation.reasons enum reason: ::Enums::MergeRequests::ComplianceViolation.reasons
enum severity_level: ::Enums::MergeRequests::ComplianceViolation.severity_levels enum severity_level: ::Enums::MergeRequests::ComplianceViolation.severity_levels
scope :with_violating_user, -> { preload(:violating_user) }
scope :with_merge_requests, -> { preload(merge_request: [{ target_project: :namespace }, :metrics]) } scope :with_merge_requests, -> { preload(merge_request: [{ target_project: :namespace }, :metrics]) }
scope :join_merge_requests, -> { with_merge_requests.joins(:merge_request) }
scope :join_projects, -> { with_merge_requests.joins(merge_request: { target_project: :namespace }) } scope :join_projects, -> { with_merge_requests.joins(merge_request: { target_project: :namespace }) }
scope :join_metrics, -> { with_merge_requests.joins(merge_request: :metrics) }
scope :by_approved_by_committer, -> { where(reason: ::Gitlab::ComplianceManagement::Violations::ApprovedByCommitter::REASON) } scope :by_approved_by_committer, -> { where(reason: ::Gitlab::ComplianceManagement::Violations::ApprovedByCommitter::REASON) }
scope :by_group, -> (group) { join_projects.where(merge_requests: { projects: { namespace_id: group.self_and_descendants } }) } scope :by_group, -> (group) { join_projects.where(merge_requests: { projects: { namespace_id: group.self_and_descendants } }) }
scope :by_projects, -> (project_ids) { join_merge_requests.where(merge_requests: { target_project_id: project_ids }) }
scope :merged_before, -> (date) { join_metrics.where('"merge_request_metrics"."merged_at" <= ?', date).references(:merge_request_metrics) }
scope :merged_after, -> (date) { join_metrics.where('"merge_request_metrics"."merged_at" >= ?', date).references(:merge_request_metrics) }
scope :order_by_reason, -> (direction) { order(reason: direction, id: direction) }
scope :order_by_severity_level, -> (direction) { order(severity_level: direction, id: direction) } scope :order_by_severity_level, -> (direction) { order(severity_level: direction, id: direction) }
scope :order_by_merge_request_title, -> (direction) { join_merge_requests.order("\"merge_requests\".\"title\" #{direction.to_s.upcase}", id: direction).references(:merge_request) }
scope :order_by_merged_at, -> (direction) { join_metrics.order("\"merge_request_metrics\".\"merged_at\" #{direction.to_s.upcase}", id: direction).references(:merge_request_metrics) }
belongs_to :violating_user, class_name: 'User' belongs_to :violating_user, class_name: 'User'
belongs_to :merge_request belongs_to :merge_request
......
...@@ -3,19 +3,28 @@ ...@@ -3,19 +3,28 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe ComplianceManagement::MergeRequests::ComplianceViolationsFinder do RSpec.describe ComplianceManagement::MergeRequests::ComplianceViolationsFinder do
using RSpec::Parameterized::TableSyntax
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, group: group) } let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:project2) { create(:project, :repository, group: group) } let_it_be(:project2) { create(:project, :repository, group: group) }
let_it_be(:project_outside_group) { create(:project, :repository, group: create(:group)) } let_it_be(:project_outside_group) { create(:project, :repository, group: create(:group)) }
let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project, state: :merged) } let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project, state: :merged, title: 'abcd') }
let_it_be(:merge_request2) { create(:merge_request, source_project: project2, target_project: project2, state: :merged) } let_it_be(:merge_request2) { create(:merge_request, source_project: project2, target_project: project2, state: :merged, title: 'zyxw') }
let_it_be(:merge_request_outside_group) { create(:merge_request, source_project: project_outside_group, target_project: project_outside_group, state: :merged) } let_it_be(:merge_request_outside_group) { create(:merge_request, source_project: project_outside_group, target_project: project_outside_group, state: :merged) }
let_it_be(:compliance_violation) { create(:compliance_violation, :approved_by_committer, severity_level: :low, merge_request: merge_request) } let_it_be(:compliance_violation) { create(:compliance_violation, :approved_by_committer, severity_level: :low, merge_request: merge_request) }
let_it_be(:compliance_violation2) { create(:compliance_violation, :approved_by_merge_request_author, severity_level: :high, merge_request: merge_request2) } let_it_be(:compliance_violation2) { create(:compliance_violation, :approved_by_merge_request_author, severity_level: :high, merge_request: merge_request2) }
let_it_be(:compliance_violation_outside_group) { create(:compliance_violation, :approved_by_committer, merge_request: merge_request_outside_group) } let_it_be(:compliance_violation_outside_group) { create(:compliance_violation, :approved_by_committer, merge_request: merge_request_outside_group) }
subject(:finder) { described_class.new(current_user: current_user, group: group) } let(:params) { {} }
before do
merge_request.metrics.update!(merged_at: 3.days.ago)
merge_request2.metrics.update!(merged_at: 1.day.ago)
end
subject(:finder) { described_class.new(current_user: current_user, group: group, params: params) }
describe '#execute' do describe '#execute' do
subject { finder.execute } subject { finder.execute }
...@@ -51,6 +60,54 @@ RSpec.describe ComplianceManagement::MergeRequests::ComplianceViolationsFinder d ...@@ -51,6 +60,54 @@ RSpec.describe ComplianceManagement::MergeRequests::ComplianceViolationsFinder d
expect(subject).to contain_exactly(compliance_violation, compliance_violation2) expect(subject).to contain_exactly(compliance_violation, compliance_violation2)
end end
end end
context 'filtering the results' do
context 'when given an array of project IDs' do
let(:params) { { project_ids: [project.id] } }
it 'finds the filtered compliance violations' do
expect(subject).to contain_exactly(compliance_violation)
end
end
context 'when given merged at dates' do
where(:merged_params, :result) do
{ merged_before: 2.days.ago } | lazy { compliance_violation }
{ merged_after: 2.days.ago } | lazy { compliance_violation2 }
{ merged_before: Date.current, merged_after: 2.days.ago } | lazy { compliance_violation2 }
end
with_them do
let(:params) { merged_params }
it 'finds the filtered compliance violations' do
expect(subject).to contain_exactly(result)
end
end
end
end
context 'sorting the results' do
where(:direction, :result) do
'SEVERITY_LEVEL_ASC' | lazy { [compliance_violation, compliance_violation2] }
'SEVERITY_LEVEL_DESC' | lazy { [compliance_violation2, compliance_violation] }
'VIOLATION_REASON_ASC' | lazy { [compliance_violation, compliance_violation2] }
'VIOLATION_REASON_DESC' | lazy { [compliance_violation2, compliance_violation] }
'MERGE_REQUEST_TITLE_ASC' | lazy { [compliance_violation, compliance_violation2] }
'MERGE_REQUEST_TITLE_DESC' | lazy { [compliance_violation2, compliance_violation] }
'MERGED_AT_ASC' | lazy { [compliance_violation, compliance_violation2] }
'MERGED_AT_DESC' | lazy { [compliance_violation2, compliance_violation] }
'UNKNOWN_SORT' | lazy { [compliance_violation, compliance_violation2] }
end
with_them do
let(:params) { { sort: direction } }
it 'finds the filtered compliance violations' do
expect(subject).to match_array(result)
end
end
end
end end
end end
end end
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Resolvers::ComplianceManagement::MergeRequests::ComplianceViolationResolver do RSpec.describe Resolvers::ComplianceManagement::MergeRequests::ComplianceViolationResolver do
using RSpec::Parameterized::TableSyntax
include GraphqlHelpers include GraphqlHelpers
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
...@@ -10,15 +12,22 @@ RSpec.describe Resolvers::ComplianceManagement::MergeRequests::ComplianceViolati ...@@ -10,15 +12,22 @@ RSpec.describe Resolvers::ComplianceManagement::MergeRequests::ComplianceViolati
let_it_be(:project) { create(:project, :repository, group: group) } let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:project2) { create(:project, :repository, group: group) } let_it_be(:project2) { create(:project, :repository, group: group) }
let_it_be(:project_outside_group) { create(:project, :repository, group: create(:group)) } let_it_be(:project_outside_group) { create(:project, :repository, group: create(:group)) }
let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project, state: :merged) } let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project, state: :merged, title: 'abcd') }
let_it_be(:merge_request2) { create(:merge_request, source_project: project2, target_project: project2, state: :merged) } let_it_be(:merge_request2) { create(:merge_request, source_project: project2, target_project: project2, state: :merged, title: 'zyxw') }
let_it_be(:merge_request_outside_group) { create(:merge_request, source_project: project_outside_group, target_project: project_outside_group, state: :merged) } let_it_be(:merge_request_outside_group) { create(:merge_request, source_project: project_outside_group, target_project: project_outside_group, state: :merged) }
let_it_be(:compliance_violation) { create(:compliance_violation, :approved_by_committer, severity_level: :low, merge_request: merge_request) } let_it_be(:compliance_violation) { create(:compliance_violation, :approved_by_committer, severity_level: :low, merge_request: merge_request) }
let_it_be(:compliance_violation2) { create(:compliance_violation, :approved_by_merge_request_author, severity_level: :high, merge_request: merge_request2) } let_it_be(:compliance_violation2) { create(:compliance_violation, :approved_by_merge_request_author, severity_level: :high, merge_request: merge_request2) }
let_it_be(:compliance_violation_outside_group) { create(:compliance_violation, :approved_by_committer, merge_request: merge_request_outside_group) } let_it_be(:compliance_violation_outside_group) { create(:compliance_violation, :approved_by_committer, merge_request: merge_request_outside_group) }
before do
merge_request.metrics.update!(merged_at: 3.days.ago)
merge_request2.metrics.update!(merged_at: 1.day.ago)
end
describe '#resolve' do describe '#resolve' do
subject(:resolve_compliance_violations) { resolve(described_class, obj: group, ctx: { current_user: current_user }) } let(:args) { {} }
subject(:resolve_compliance_violations) { resolve(described_class, obj: group, args: args, ctx: { current_user: current_user }) }
context 'feature flag is disabled' do context 'feature flag is disabled' do
before do before do
...@@ -47,6 +56,54 @@ RSpec.describe Resolvers::ComplianceManagement::MergeRequests::ComplianceViolati ...@@ -47,6 +56,54 @@ RSpec.describe Resolvers::ComplianceManagement::MergeRequests::ComplianceViolati
expect(subject).to contain_exactly(compliance_violation, compliance_violation2) expect(subject).to contain_exactly(compliance_violation, compliance_violation2)
end end
end end
context 'filtering the results' do
context 'when given an array of project IDs' do
let(:args) { { filters: { project_ids: [::Gitlab::GlobalId.as_global_id(project.id, model_name: 'Project')] } } }
it 'finds the filtered compliance violations' do
expect(subject).to contain_exactly(compliance_violation)
end
end
context 'when given merged at dates' do
where(:merged_params, :result) do
{ merged_before: 2.days.ago } | lazy { compliance_violation }
{ merged_after: 2.days.ago } | lazy { compliance_violation2 }
{ merged_before: Date.current, merged_after: 2.days.ago } | lazy { compliance_violation2 }
end
with_them do
let(:args) { { filters: merged_params } }
it 'finds the filtered compliance violations' do
expect(subject).to contain_exactly(result)
end
end
end
end
context 'sorting the results' do
where(:direction, :result) do
'SEVERITY_LEVEL_ASC' | lazy { [compliance_violation, compliance_violation2] }
'SEVERITY_LEVEL_DESC' | lazy { [compliance_violation2, compliance_violation] }
'VIOLATION_REASON_ASC' | lazy { [compliance_violation, compliance_violation2] }
'VIOLATION_REASON_DESC' | lazy { [compliance_violation2, compliance_violation] }
'MERGE_REQUEST_TITLE_ASC' | lazy { [compliance_violation, compliance_violation2] }
'MERGE_REQUEST_TITLE_DESC' | lazy { [compliance_violation2, compliance_violation] }
'MERGED_AT_ASC' | lazy { [compliance_violation, compliance_violation2] }
'MERGED_AT_DESC' | lazy { [compliance_violation2, compliance_violation] }
'UNKNOWN_SORT' | lazy { [compliance_violation, compliance_violation2] }
end
with_them do
let(:args) { { sort: direction } }
it 'finds the filtered compliance violations' do
expect(subject).to match_array(result)
end
end
end
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['ComplianceViolationInput'] do
let(:arguments) do
%w[projectIds mergedBefore mergedAfter]
end
specify { expect(described_class.graphql_name).to eq('ComplianceViolationInput') }
specify { expect(described_class.arguments.keys).to match_array(arguments) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['ComplianceViolationSort'] do
let(:fields) do
%w[
SEVERITY_LEVEL_DESC SEVERITY_LEVEL_ASC VIOLATION_REASON_DESC VIOLATION_REASON_ASC
MERGE_REQUEST_TITLE_DESC MERGE_REQUEST_TITLE_ASC MERGED_AT_DESC MERGED_AT_ASC
]
end
specify { expect(described_class.graphql_name).to eq('ComplianceViolationSort') }
specify { expect(described_class.values.keys).to match_array(fields) }
end
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe MergeRequests::ComplianceViolation, type: :model do RSpec.describe MergeRequests::ComplianceViolation, type: :model do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
let_it_be(:merge_request) { create(:merge_request, state: :merged) } let_it_be(:merge_request) { create(:merge_request, state: :merged, title: 'zyxw') }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
describe "Associations" do describe "Associations" do
...@@ -80,6 +80,77 @@ RSpec.describe MergeRequests::ComplianceViolation, type: :model do ...@@ -80,6 +80,77 @@ RSpec.describe MergeRequests::ComplianceViolation, type: :model do
end end
end end
describe '#by_projects' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:alt_merge_request) { create(:merge_request, source_project: project, target_project: project, state: :merged) }
let_it_be(:violations) do
[
create(:compliance_violation, :approved_by_committer, merge_request: alt_merge_request, violating_user: create(:user)),
create(:compliance_violation, :approved_by_committer, merge_request: merge_request, violating_user: create(:user))
]
end
it 'returns the correct collection of violations' do
expect(described_class.by_projects([project.id])).to contain_exactly(violations[0])
end
end
describe '#merged_before' do
let_it_be(:alt_merge_request) { create(:merge_request, state: :merged) }
let_it_be(:violations) do
[
create(:compliance_violation, :approved_by_committer, merge_request: alt_merge_request, violating_user: create(:user)),
create(:compliance_violation, :approved_by_committer, merge_request: merge_request, violating_user: create(:user))
]
end
before do
alt_merge_request.metrics.update!(merged_at: 2.days.ago)
end
it 'returns the correct collection of violations' do
expect(described_class.merged_before(1.day.ago)).to contain_exactly(violations[0])
end
end
describe '#merged_after' do
let_it_be(:alt_merge_request) { create(:merge_request, state: :merged) }
let_it_be(:violations) do
[
create(:compliance_violation, :approved_by_committer, merge_request: alt_merge_request, violating_user: create(:user)),
create(:compliance_violation, :approved_by_committer, merge_request: merge_request, violating_user: create(:user))
]
end
before do
alt_merge_request.metrics.update!(merged_at: Date.current)
end
it 'returns the correct collection of violations' do
expect(described_class.merged_after(1.day.ago)).to contain_exactly(violations[0])
end
end
describe '#order_by_reason' do
let_it_be(:violations) do
[
create(:compliance_violation, :approved_by_merge_request_author, merge_request: merge_request, violating_user: create(:user)),
create(:compliance_violation, :approved_by_committer, merge_request: merge_request, violating_user: create(:user))
]
end
where(:direction, :result) do
'ASC' | lazy { [violations[0], violations[1]] }
'DESC' | lazy { [violations[1], violations[0]] }
end
with_them do
it 'returns the correct collection of violations' do
expect(described_class.order_by_reason(direction)).to match_array(result)
end
end
end
describe '#order_by_severity_level' do describe '#order_by_severity_level' do
let_it_be(:violations) do let_it_be(:violations) do
[ [
...@@ -100,6 +171,52 @@ RSpec.describe MergeRequests::ComplianceViolation, type: :model do ...@@ -100,6 +171,52 @@ RSpec.describe MergeRequests::ComplianceViolation, type: :model do
end end
end end
describe '#order_by_merge_request_title' do
let_it_be(:alt_merge_request) { create(:merge_request, state: :merged, title: 'abcd') }
let_it_be(:violations) do
[
create(:compliance_violation, :approved_by_committer, severity_level: :high, merge_request: alt_merge_request, violating_user: create(:user)),
create(:compliance_violation, :approved_by_committer, severity_level: :low, merge_request: merge_request, violating_user: create(:user))
]
end
where(:direction, :result) do
'ASC' | lazy { [violations[0], violations[1]] }
'DESC' | lazy { [violations[1], violations[0]] }
end
with_them do
it 'returns the correct collection of violations' do
expect(described_class.order_by_merge_request_title(direction)).to match_array(result)
end
end
end
describe '#order_by_merged_at' do
let_it_be(:alt_merge_request) { create(:merge_request, state: :merged) }
let_it_be(:violations) do
[
create(:compliance_violation, :approved_by_committer, merge_request: alt_merge_request, violating_user: create(:user)),
create(:compliance_violation, :approved_by_committer, merge_request: merge_request, violating_user: create(:user))
]
end
before do
alt_merge_request.metrics.update!(merged_at: 2.days.ago)
end
where(:direction, :result) do
'ASC' | lazy { [violations[0], violations[1]] }
'DESC' | lazy { [violations[1], violations[0]] }
end
with_them do
it 'returns the correct collection of violations' do
expect(described_class.order_by_merged_at(direction)).to match_array(result)
end
end
end
describe '.process_merge_request' do describe '.process_merge_request' do
it 'loops through each violation class', :aggregate_failures do it 'loops through each violation class', :aggregate_failures do
expect_next_instance_of(::Gitlab::ComplianceManagement::Violations::ApprovedByMergeRequestAuthor, merge_request) do |violation| expect_next_instance_of(::Gitlab::ComplianceManagement::Violations::ApprovedByMergeRequestAuthor, merge_request) do |violation|
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'getting the compliance violations for a group' do RSpec.describe 'getting the compliance violations for a group' do
using RSpec::Parameterized::TableSyntax
include GraphqlHelpers include GraphqlHelpers
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
...@@ -10,8 +12,8 @@ RSpec.describe 'getting the compliance violations for a group' do ...@@ -10,8 +12,8 @@ RSpec.describe 'getting the compliance violations for a group' do
let_it_be(:project) { create(:project, :repository, group: group) } let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:project2) { create(:project, :repository, group: group) } let_it_be(:project2) { create(:project, :repository, group: group) }
let_it_be(:project_outside_group) { create(:project, :repository, group: create(:group)) } let_it_be(:project_outside_group) { create(:project, :repository, group: create(:group)) }
let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project, state: :merged) } let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project, state: :merged, title: 'abcd') }
let_it_be(:merge_request2) { create(:merge_request, source_project: project2, target_project: project2, state: :merged) } let_it_be(:merge_request2) { create(:merge_request, source_project: project2, target_project: project2, state: :merged, title: 'zyxw') }
let_it_be(:merge_request_outside_group) { create(:merge_request, source_project: project_outside_group, target_project: project_outside_group, state: :merged) } let_it_be(:merge_request_outside_group) { create(:merge_request, source_project: project_outside_group, target_project: project_outside_group, state: :merged) }
let_it_be(:compliance_violation) { create(:compliance_violation, :approved_by_committer, severity_level: :low, merge_request: merge_request) } let_it_be(:compliance_violation) { create(:compliance_violation, :approved_by_committer, severity_level: :low, merge_request: merge_request) }
let_it_be(:compliance_violation2) { create(:compliance_violation, :approved_by_merge_request_author, severity_level: :high, merge_request: merge_request2) } let_it_be(:compliance_violation2) { create(:compliance_violation, :approved_by_merge_request_author, severity_level: :high, merge_request: merge_request2) }
...@@ -63,6 +65,11 @@ RSpec.describe 'getting the compliance violations for a group' do ...@@ -63,6 +65,11 @@ RSpec.describe 'getting the compliance violations for a group' do
let(:compliance_violations) { graphql_data_at(:group, :merge_request_violations, :nodes) } let(:compliance_violations) { graphql_data_at(:group, :merge_request_violations, :nodes) }
before do
merge_request.metrics.update!(merged_at: 3.days.ago)
merge_request2.metrics.update!(merged_at: 1.day.ago)
end
context 'when feature is disabled' do context 'when feature is disabled' do
before do before do
stub_feature_flags(compliance_violations_graphql_type: false) stub_feature_flags(compliance_violations_graphql_type: false)
...@@ -100,6 +107,53 @@ RSpec.describe 'getting the compliance violations for a group' do ...@@ -100,6 +107,53 @@ RSpec.describe 'getting the compliance violations for a group' do
expect(compliance_violations).to contain_exactly(violation_output, violation2_output) expect(compliance_violations).to contain_exactly(violation_output, violation2_output)
end end
end end
context 'filtering the results' do
context 'when given an array of project IDs' do
it 'finds all the compliance violations' do
post_graphql(query({ filters: { projectIds: [project.to_global_id.to_s] } }), current_user: current_user)
expect(compliance_violations).to contain_exactly(violation_output)
end
end
context 'when given merged at dates' do
where(:merged_params, :result) do
{ 'mergedBefore' => 2.days.ago.to_date.iso8601 } | lazy { violation_output }
{ 'mergedAfter' => 2.days.ago.to_date.iso8601 } | lazy { violation2_output }
{ 'mergedBefore' => Date.current.iso8601, 'mergedAfter' => 2.days.ago.to_date.iso8601 } | lazy { violation2_output }
end
with_them do
it 'finds all the compliance violations' do
post_graphql(query({ filters: merged_params }), current_user: current_user)
expect(compliance_violations).to contain_exactly(result)
end
end
end
end
context 'sorting the results' do
where(:direction, :result) do
:SEVERITY_LEVEL_ASC | lazy { [violation_output, violation2_output] }
:SEVERITY_LEVEL_DESC | lazy { [violation2_output, violation_output] }
:VIOLATION_REASON_ASC | lazy { [violation_output, violation2_output] }
:VIOLATION_REASON_DESC | lazy { [violation2_output, violation_output] }
:MERGE_REQUEST_TITLE_ASC | lazy { [violation_output, violation2_output] }
:MERGE_REQUEST_TITLE_DESC | lazy { [violation2_output, violation_output] }
:MERGED_AT_ASC | lazy { [violation_output, violation2_output] }
:MERGED_AT_DESC | lazy { [violation2_output, violation_output] }
end
with_them do
it 'finds all the compliance violations' do
post_graphql(query({ sort: direction }), current_user: current_user)
expect(compliance_violations).to match_array(result)
end
end
end
end end
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