diff --git a/ee/app/models/gitlab_subscriptions/features.rb b/ee/app/models/gitlab_subscriptions/features.rb index 4f7dc3c63c46cfa32a5eb25209b13477baf5439b..fcf7efb9dc0de964d5bb6f865d55a33d12b5b9ac 100644 --- a/ee/app/models/gitlab_subscriptions/features.rb +++ b/ee/app/models/gitlab_subscriptions/features.rb @@ -159,6 +159,7 @@ class Features scoped_labels smartcard_auth swimlanes + target_branch_rules type_of_work_analytics minimal_access_role unprotection_restrictions diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index 26ed4b6f19fb9cd825cf55c341cfa017e79a6ece..0e440b99d7f7737ea93a3f563c9c77a4ec4780b1 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -264,6 +264,14 @@ module ProjectPolicy ::Gitlab::Llm::StageCheck.available?(subject, :generate_description) end + with_scope :subject + condition(:target_branch_rules_available) { subject.licensed_feature_available?(:target_branch_rules) } + + with_scope :subject + condition(:target_branch_rules_enabled) do + ::Feature.enabled?(:target_branch_rules_flag, subject) + end + rule { visual_review_bot }.policy do prevent :read_note enable :create_note @@ -640,6 +648,10 @@ module ProjectPolicy rule do ai_features_enabled & generate_description_enabled & can?(:create_issue) end.enable :generate_description + + rule do + target_branch_rules_enabled & target_branch_rules_available & maintainer + end.enable :create_target_branch_rule end override :lookup_access_level! diff --git a/ee/app/services/target_branch_rules/base_service.rb b/ee/app/services/target_branch_rules/base_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..738c5ae52603cfcf4bd6aa8cbdb64962ea63ea72 --- /dev/null +++ b/ee/app/services/target_branch_rules/base_service.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module TargetBranchRules + class BaseService < ::BaseService + attr_reader :project, :current_user, :params + + # project - The project the target branch rule is for + # current_user - The user that performs the action + # params - A hash of parameters + def initialize(project, current_user, params = {}) + @project = project + @current_user = current_user + @params = params + end + end +end diff --git a/ee/app/services/target_branch_rules/create_service.rb b/ee/app/services/target_branch_rules/create_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..f333b4f9997881898355d575c0e2eede7e2f82e5 --- /dev/null +++ b/ee/app/services/target_branch_rules/create_service.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module TargetBranchRules + class CreateService < TargetBranchRules::BaseService + def execute + return error_no_permissions unless authorized? + + if target_branch_rule.save + success(payload: { target_branch_rule: target_branch_rule }) + else + error(target_branch_rule&.errors&.full_messages || _('Failed to create target branch rule')) + end + end + + private + + def authorized? + can?(current_user, :create_target_branch_rule, project) + end + + def error_no_permissions + error(_('You have insufficient permissions to create a target branch rule')) + end + + def target_branch_rule + @_target_branch_rule ||= project.target_branch_rules.new(params) + end + end +end diff --git a/ee/config/feature_flags/development/target_branch_rules_flag.yml b/ee/config/feature_flags/development/target_branch_rules_flag.yml new file mode 100644 index 0000000000000000000000000000000000000000..3a4bb2694f0fc500d6bf41e91618672fd49571a1 --- /dev/null +++ b/ee/config/feature_flags/development/target_branch_rules_flag.yml @@ -0,0 +1,8 @@ +--- +name: target_branch_rules_flag +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127115 +rollout_issue_url: +milestone: '16.3' +type: development +group: group::code review +default_enabled: false diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index 92b7fe1bfeb144e2921aa1b49f36be0c6da47f05..be71817fbff0ab9ce84e22a1ed5e93c6a512ead1 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -2890,4 +2890,38 @@ def create_member_role(member, abilities = member_role_abilities) it { is_expected.to be_disallowed(:fill_in_merge_request_template) } end end + + describe 'create_target_branch_rule policy' do + let(:current_user) { owner } + + describe 'when the target_branch_rules_flag flag is disabled' do + before do + stub_feature_flags(target_branch_rules_flag: false) + end + + it { is_expected.to be_disallowed(:create_target_branch_rule) } + end + + describe 'when the project does not have the correct license' do + before do + stub_licensed_features(target_branch_rules: false) + end + + it { is_expected.to be_disallowed(:create_target_branch_rule) } + end + + describe 'when the user does not have permissions' do + let(:current_user) { auditor } + + it { is_expected.to be_disallowed(:create_target_branch_rule) } + end + + describe 'when the user has permission' do + before do + stub_licensed_features(target_branch_rules: true) + end + + it { is_expected.to be_allowed(:create_target_branch_rule) } + end + end end diff --git a/ee/spec/services/target_branch_rules/create_service_spec.rb b/ee/spec/services/target_branch_rules/create_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0729732bf6465b23dae9068598e9e3a19f65869c --- /dev/null +++ b/ee/spec/services/target_branch_rules/create_service_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe TargetBranchRules::CreateService, feature_category: :code_review_workflow do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let(:params) { { name: 'dev/*', target_branch: 'main' } } + + subject(:service) { described_class.new(project, user, params) } + + describe 'when the target_branch_rules_flag flag is disabled' do + before do + stub_feature_flags(target_branch_rules_flag: false) + end + + it 'returns an error' do + response = service.execute + + expect(response[:status]).to eq(:error) + expect(response[:message]).to eq(_('You have insufficient permissions to create a target branch rule')) + end + end + + describe 'when the project does not have the correct license' do + before do + stub_licensed_features(target_branch_rules: false) + end + + it 'returns an error' do + response = service.execute + + expect(response[:status]).to eq(:error) + expect(response[:message]).to eq(_('You have insufficient permissions to create a target branch rule')) + end + end + + describe 'when the user does not have permissions' do + it 'returns an error' do + response = service.execute + + expect(response[:status]).to eq(:error) + expect(response[:message]).to eq(_('You have insufficient permissions to create a target branch rule')) + end + end + + context 'when user has permission' do + before_all do + project.add_owner(user) + end + + before do + stub_licensed_features(target_branch_rules: true) + end + + describe 'when the target branch rule already exists' do + before do + create(:target_branch_rule, project: project, name: 'dev/*') + end + + it 'returns an error' do + response = service.execute + + expect(response[:status]).to eq(:error) + expect(response[:message]).to contain_exactly('Name has already been taken') + end + end + + describe 'when the target branch gets created' do + it 'returns success' do + expect { expect(service.execute[:status]).to eq(:success) } + .to change { project.target_branch_rules.count }.by(1) + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d82166233e81bc292c8174d27fc6990ae0a460d3..a353575ee06b4a140d54b7c2fc02adc0c01b87de 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -19409,6 +19409,9 @@ msgstr "" msgid "Failed to create resources" msgstr "" +msgid "Failed to create target branch rule" +msgstr "" + msgid "Failed to create wiki" msgstr "" @@ -53927,6 +53930,9 @@ msgstr "" msgid "You have insufficient permissions to create a Todo for this alert" msgstr "" +msgid "You have insufficient permissions to create a target branch rule" +msgstr "" + msgid "You have insufficient permissions to create an HTTP integration for this project" msgstr ""