Commit c1f7d648 authored by Nick Thomas's avatar Nick Thomas

Introduce namespace license checks for merge request approvers (EES)

parent c5aabfb7
......@@ -22,6 +22,12 @@ module Approvable
approvals_before_merge || target_project.approvals_before_merge
end
def approvals_before_merge
return 0 unless project&.feature_available?(:merge_request_approvers)
super
end
# An MR can potentially be approved by:
# - anyone in the approvers list
# - any other project member with developer access or higher (if there are no approvers
......
module EE
module MergeRequest
include ::Approvable
def ff_merge_possible?
project.repository.is_ancestor?(target_branch_sha, diff_head_sha)
end
......
......@@ -246,6 +246,17 @@ module EE
default_issues_tracker? || jira_tracker_active?
end
def approvals_before_merge
return 0 unless feature_available?(:merge_request_approvers)
super
end
def reset_approvals_on_push
super && feature_available?(:merge_request_approvers)
end
alias_method :reset_approvals_on_push?, :reset_approvals_on_push
def approver_ids=(value)
value.split(",").map(&:strip).each do |user_id|
approvers.find_or_create_by(user_id: user_id, target_id: id)
......
......@@ -10,6 +10,7 @@ class License < ActiveRecord::Base
FILE_LOCK_FEATURE = 'GitLab_FileLocks'.freeze
GEO_FEATURE = 'GitLab_Geo'.freeze
ISSUE_WEIGHTS_FEATURE = 'GitLab_IssueWeights'.freeze
MERGE_REQUEST_APPROVERS_FEATURE = 'GitLab_MergeRequestApprovers'.freeze
MERGE_REQUEST_REBASE_FEATURE = 'GitLab_MergeRequestRebase'.freeze
MERGE_REQUEST_SQUASH_FEATURE = 'GitLab_MergeRequestSquash'.freeze
OBJECT_STORAGE_FEATURE = 'GitLab_ObjectStorage'.freeze
......@@ -31,6 +32,7 @@ class License < ActiveRecord::Base
fast_forward_merge: FAST_FORWARD_MERGE_FEATURE,
file_lock: FILE_LOCK_FEATURE,
issue_weights: ISSUE_WEIGHTS_FEATURE,
merge_request_approvers: MERGE_REQUEST_APPROVERS_FEATURE,
merge_request_rebase: MERGE_REQUEST_REBASE_FEATURE,
merge_request_squash: MERGE_REQUEST_SQUASH_FEATURE
}.freeze
......@@ -46,6 +48,7 @@ class License < ActiveRecord::Base
{ EXPORT_ISSUES_FEATURE => 1 },
{ FAST_FORWARD_MERGE_FEATURE => 1 },
{ ISSUE_WEIGHTS_FEATURE => 1 },
{ MERGE_REQUEST_APPROVERS_FEATURE => 1 },
{ MERGE_REQUEST_REBASE_FEATURE => 1 },
{ MERGE_REQUEST_SQUASH_FEATURE => 1 },
{ RELATED_ISSUES_FEATURE => 1 }
......@@ -83,6 +86,7 @@ class License < ActiveRecord::Base
{ FILE_LOCK_FEATURE => 1 },
{ GEO_FEATURE => 1 },
{ ISSUE_WEIGHTS_FEATURE => 1 },
{ MERGE_REQUEST_APPROVERS_FEATURE => 1 },
{ MERGE_REQUEST_REBASE_FEATURE => 1 },
{ MERGE_REQUEST_SQUASH_FEATURE => 1 },
{ OBJECT_STORAGE_FEATURE => 1 },
......
......@@ -5,7 +5,6 @@ class MergeRequest < ActiveRecord::Base
include Referable
include Sortable
include Elastic::MergeRequestsSearch
include Approvable
include IgnorableColumn
ignore_column :position
......
- return unless project.feature_available?(:merge_request_approvers)
.form-group.reset-approvals-on-push
.checkbox
= label_tag :require_approvals do
= check_box_tag :require_approvals, nil, project.approvals_before_merge.nonzero?, class: 'js-require-approvals-toggle'
%strong Activate merge request approvals
= link_to icon('question-circle'), help_page_path("user/project/merge_requests/merge_request_approvals"), target: '_blank'
.descr Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that you will need to approve every merge request in a project.
.nested-settings{ class: project.approvals_before_merge.nonzero? ? '' : 'hidden' }
.form-group
= form.label :approver_ids, class: 'label-light' do
Approvers
= hidden_field_tag "project[approver_ids]"
= hidden_field_tag "project[approver_group_ids]"
.input-group.input-btn-group
= hidden_field_tag :approver_user_and_group_ids, '', { class: 'js-select-user-and-group input-large', tabindex: 1, 'data-name': 'project' }
%button.btn.btn-success.js-add-approvers{ type: 'button', title: 'Add approvers(s)' }
Add
.help-block
Add an approver or group suggestion for each merge request
.panel.panel-default.prepend-top-10.js-current-approvers
.panel-heading
Approvers
%span.badge
- ids = []
- project.approvers.each do |user|
- ids << user.user_id
- project.approver_groups.each do |group|
- group.users.each do |user|
- unless ids.include?(user.id)
- ids << user.id
= ids.count
%ul.well-list.approver-list
.load-wrapper.hidden
= icon('spinner spin', class: 'approver-list-loader')
- project.approvers.each do |approver|
%li.approver.settings-flex-row.js-approver{ data: { id: approver.user_id } }
= link_to approver.user.name, approver.user
.pull-right
%button{ href: namespace_project_approver_path(project.namespace, project, approver), data: { confirm: "Are you sure you want to remove approver #{approver.user.name}"}, class: "btn btn-remove js-approver-remove", title: 'Remove approver' }
= icon("trash")
- project.approver_groups.each do |approver_group|
%li.approver-group.settings-flex-row.js-approver-group{ data: { id: approver_group.group.id } }
.span
%span.light
Group:
= link_to approver_group.group.name, approver_group.group
%span.badge
= approver_group.group.members.count
.pull-right
%button{ href: namespace_project_approver_group_path(project.namespace, project, approver_group), data: { confirm: "Are you sure you want to remove group #{approver_group.group.name}" }, class: "btn btn-remove js-approver-remove", title: 'Remove group' }
= icon("trash")
- if project.approvers.empty? && project.approver_groups.empty?
%li There are no approvers
.form-group
= form.label :approvals_before_merge, class: 'label-light' do
Approvals required
= form.number_field :approvals_before_merge, class: "form-control", min: 0
.help-block
.form-group.reset-approvals-on-push
.checkbox
= form.label :reset_approvals_on_push do
= form.check_box :reset_approvals_on_push
%strong Reset approvals on push
.descr Approvals are reset when new data is pushed to the merge request
......@@ -47,74 +47,7 @@
.hint
Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('user/markdown'), target: '_blank'}.
.form-group.reset-approvals-on-push
.checkbox
= label_tag :require_approvals do
= check_box_tag :require_approvals, nil, project.approvals_before_merge.nonzero?, class: 'js-require-approvals-toggle'
%strong Activate merge request approvals
= link_to icon('question-circle'), help_page_path("user/project/merge_requests/merge_request_approvals"), target: '_blank'
.descr Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that you will need to approve every merge request in a project.
.nested-settings{ class: project.approvals_before_merge.nonzero? ? '' : 'hidden' }
.form-group
= form.label :approver_ids, class: 'label-light' do
Approvers
= hidden_field_tag "project[approver_ids]"
= hidden_field_tag "project[approver_group_ids]"
.input-group.input-btn-group
= hidden_field_tag :approver_user_and_group_ids, '', { class: 'js-select-user-and-group input-large', tabindex: 1, 'data-name': 'project' }
%button.btn.btn-success.js-add-approvers{ type: 'button', title: 'Add approvers(s)' }
Add
.help-block
Add an approver or group suggestion for each merge request
.panel.panel-default.prepend-top-10.js-current-approvers
.panel-heading
Approvers
%span.badge
- ids = []
- project.approvers.each do |user|
- ids << user.user_id
- project.approver_groups.each do |group|
- group.users.each do |user|
- unless ids.include?(user.id)
- ids << user.id
= ids.count
%ul.well-list.approver-list
.load-wrapper.hidden
= icon('spinner spin', class: 'approver-list-loader')
- project.approvers.each do |approver|
%li.approver.settings-flex-row.js-approver{ data: { id: approver.user_id } }
= link_to approver.user.name, approver.user
.pull-right
%button{ href: namespace_project_approver_path(project.namespace, project, approver), data: { confirm: "Are you sure you want to remove approver #{approver.user.name}"}, class: "btn btn-remove js-approver-remove", title: 'Remove approver' }
= icon("trash")
- project.approver_groups.each do |approver_group|
%li.approver-group.settings-flex-row.js-approver-group{ data: { id: approver_group.group.id } }
.span
%span.light
Group:
= link_to approver_group.group.name, approver_group.group
%span.badge
= approver_group.group.members.count
.pull-right
%button{ href: namespace_project_approver_group_path(project.namespace, project, approver_group), data: { confirm: "Are you sure you want to remove group #{approver_group.group.name}" }, class: "btn btn-remove js-approver-remove", title: 'Remove group' }
= icon("trash")
- if project.approvers.empty? && project.approver_groups.empty?
%li There are no approvers
.form-group
= form.label :approvals_before_merge, class: 'label-light' do
Approvals required
= form.number_field :approvals_before_merge, class: "form-control", min: 0
.help-block
.form-group.reset-approvals-on-push
.checkbox
= form.label :reset_approvals_on_push do
= form.check_box :reset_approvals_on_push
%strong Reset approvals on push
.descr Approvals are reset when new data is pushed to the merge request
= render 'projects/ee/merge_request_approvals_settings', project: project, form: form
:javascript
new GroupsSelect();
---
title: Introduce namespace license checks for merge request approvers (EES)
merge_request: 2324
author:
......@@ -132,7 +132,7 @@ module API
expose :printing_merge_request_link_enabled
# EE only
expose :approvals_before_merge
expose :approvals_before_merge, if: ->(project, _) { project.feature_available?(:merge_request_approvers) }
expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
end
......
......@@ -116,7 +116,7 @@ module API
expose :repository_storage, if: lambda { |_project, options| options[:current_user].try(:admin?) }
expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved
expose :approvals_before_merge
expose :approvals_before_merge, if: ->(project, _) { project.feature_available?(:merge_request_approvers) }
expose :statistics, using: '::API::V3::Entities::ProjectStatistics', if: :statistics
end
......
......@@ -127,4 +127,26 @@ describe MergeRequest, models: true do
end
end
end
describe '#approvals_before_merge' do
[
{ license: true, database: 5, expected: 5 },
{ license: true, database: 0, expected: 0 },
{ license: false, database: 5, expected: 0 },
{ license: false, database: 0, expected: 0 }
].each do |spec|
context spec.inspect do
let(:spec) { spec }
let(:merge_request) { build(:merge_request, approvals_before_merge: spec[:database]) }
subject { merge_request.approvals_before_merge }
before do
stub_licensed_features(merge_request_approvers: spec[:license])
end
it { is_expected.to eq(spec[:expected]) }
end
end
end
end
......@@ -295,6 +295,72 @@ describe Project, models: true do
end
end
describe '#approvals_before_merge' do
[
{ license: true, database: 5, expected: 5 },
{ license: true, database: 0, expected: 0 },
{ license: false, database: 5, expected: 0 },
{ license: false, database: 0, expected: 0 }
].each do |spec|
context spec.inspect do
let(:spec) { spec }
let(:project) { build(:project, approvals_before_merge: spec[:database]) }
subject { project.approvals_before_merge }
before do
stub_licensed_features(merge_request_approvers: spec[:license])
end
it { is_expected.to eq(spec[:expected]) }
end
end
end
describe "#reset_approvals_on_push?" do
[
{ license: true, database: true, expected: true },
{ license: true, database: false, expected: false },
{ license: false, database: true, expected: false },
{ license: false, database: false, expected: false }
].each do |spec|
context spec.inspect do
let(:spec) { spec }
let(:project) { build(:project, reset_approvals_on_push: spec[:database]) }
subject { project.reset_approvals_on_push? }
before do
stub_licensed_features(merge_request_approvers: spec[:license])
end
it { is_expected.to eq(spec[:expected]) }
end
end
end
describe '#approvals_before_merge' do
[
{ license: true, database: 5, expected: 5 },
{ license: true, database: 0, expected: 0 },
{ license: false, database: 5, expected: 0 },
{ license: false, database: 0, expected: 0 }
].each do |spec|
context spec.inspect do
let(:spec) { spec }
let(:project) { build(:project, approvals_before_merge: spec[:database]) }
subject { project.approvals_before_merge }
before do
stub_licensed_features(merge_request_approvers: spec[:license])
end
it { is_expected.to eq(spec[:expected]) }
end
end
end
describe '#merge_method' do
[
{ ff: true, rebase: true, ff_licensed: true, rebase_licensed: true, method: :ff },
......
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