diff --git a/ee/app/models/approval_rules/approval_group_rule.rb b/ee/app/models/approval_rules/approval_group_rule.rb new file mode 100644 index 0000000000000000000000000000000000000000..c68d4c865736e0781eec6571341921dad06171df --- /dev/null +++ b/ee/app/models/approval_rules/approval_group_rule.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ApprovalRules + class ApprovalGroupRule < ApplicationRecord + include ApprovalRuleLike + + enum rule_type: { + regular: 1, + code_owner: 2, + report_approver: 3, + any_approver: 4 + } + + belongs_to :group, inverse_of: :approval_rules + has_and_belongs_to_many :protected_branches + + validates :name, uniqueness: { scope: [:group_id, :rule_type] } + validates :rule_type, uniqueness: { + scope: :group_id, + message: proc { _('any-approver for the group already exists') } + }, if: :any_approver? + end +end diff --git a/ee/app/models/ee/group.rb b/ee/app/models/ee/group.rb index 21a3f63c38f3ba02f424ee471e9daff31abeb7b3..b5367b48fd553677044e1ed4b70a4da5d364b80c 100644 --- a/ee/app/models/ee/group.rb +++ b/ee/app/models/ee/group.rb @@ -82,6 +82,7 @@ module Group belongs_to :file_template_project, class_name: "Project" belongs_to :push_rule, inverse_of: :group + has_many :approval_rules, class_name: 'ApprovalRules::ApprovalGroupRule', inverse_of: :group delegate :deleting_user, :marked_for_deletion_on, to: :deletion_schedule, allow_nil: true diff --git a/ee/spec/factories/approval_rules.rb b/ee/spec/factories/approval_rules.rb index 433e1f8211e26c8a0da04219c3f839347f890441..62c7121d096d2b242fd31a03fb2b6ffc07ccbaa0 100644 --- a/ee/spec/factories/approval_rules.rb +++ b/ee/spec/factories/approval_rules.rb @@ -102,4 +102,10 @@ approval_project_rule user end + + factory :approval_group_rule, class: 'ApprovalRules::ApprovalGroupRule' do + group + sequence(:name) { |n| "#{ApprovalRuleLike::DEFAULT_NAME}-#{n}" } + rule_type { :regular } + end end diff --git a/ee/spec/models/approval_rules/approval_group_rule_spec.rb b/ee/spec/models/approval_rules/approval_group_rule_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..1ac8f0b9c7c9878deec2ceec870845a314079783 --- /dev/null +++ b/ee/spec/models/approval_rules/approval_group_rule_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ApprovalRules::ApprovalGroupRule, feature_category: :source_code_management do + subject { build(:approval_group_rule) } + + describe 'validations' do + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_uniqueness_of(:name).scoped_to([:group_id, :rule_type]) } + it { is_expected.to validate_numericality_of(:approvals_required).is_less_than_or_equal_to(100) } + it { is_expected.to validate_numericality_of(:approvals_required).is_greater_than_or_equal_to(0) } + end + + describe 'associations' do + it { is_expected.to belong_to(:group).inverse_of(:approval_rules) } + it { is_expected.to belong_to(:security_orchestration_policy_configuration) } + it { is_expected.to belong_to(:scan_result_policy_read) } + it { is_expected.to have_and_belong_to_many(:users) } + it { is_expected.to have_and_belong_to_many(:groups) } + it { is_expected.to have_and_belong_to_many(:protected_branches) } + end + + describe 'any_approver rules' do + let_it_be(:group) { create(:group) } + + let(:rule) { build(:approval_group_rule, group: group, rule_type: :any_approver) } + + it 'allows to create only one any_approver rule', :aggregate_failures do + create(:approval_group_rule, group: group, rule_type: :any_approver) + + expect(rule).not_to be_valid + expect(rule.errors.messages).to eq(rule_type: ['any-approver for the group already exists']) + end + end +end diff --git a/ee/spec/models/ee/group_spec.rb b/ee/spec/models/ee/group_spec.rb index dd25558506a80f672b365b0b221ce6feb6f6b306..c533baf8148427cb2eb8eb7dc06af196120706d8 100644 --- a/ee/spec/models/ee/group_spec.rb +++ b/ee/spec/models/ee/group_spec.rb @@ -31,6 +31,7 @@ it { is_expected.to have_many(:repository_storage_moves) } it { is_expected.to have_many(:iterations) } it { is_expected.to have_many(:iterations_cadences) } + it { is_expected.to have_many(:approval_rules).class_name('ApprovalRules::ApprovalGroupRule').inverse_of(:group) } it { is_expected.to have_many(:epic_board_recent_visits).inverse_of(:group) } it { is_expected.to have_many(:external_audit_event_destinations) } it { is_expected.to have_many(:google_cloud_logging_configurations) } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 7fff677c22c4d4731749778cb5930c9c1bd29040..212c2552cf770a3fc643433a9ca813f06c35ef12 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -55641,6 +55641,9 @@ msgstr "" msgid "and" msgstr "" +msgid "any-approver for the group already exists" +msgstr "" + msgid "any-approver for the merge request already exists" msgstr ""