Commit 23cb2667 authored by Patrick Bajao's avatar Patrick Bajao

Return overridden property in API response

In order to determine whether a MR-level rule based on a project
level rule is overridden (e.g. name, approvals required, users or
groups have changed), a `overridden` property needs to be returned.

Overridden rules will also be unscoped even if their source rule
is scoped to protected branches.
parent 643567db
...@@ -676,7 +676,8 @@ This includes additional information about the users who have already approved ...@@ -676,7 +676,8 @@ This includes additional information about the users who have already approved
} }
], ],
"source_rule": null, "source_rule": null,
"approved": true "approved": true,
"overridden": false
} }
] ]
} }
...@@ -753,7 +754,8 @@ GET /projects/:id/merge_requests/:merge_request_iid/approval_rules ...@@ -753,7 +754,8 @@ GET /projects/:id/merge_requests/:merge_request_iid/approval_rules
"ldap_access": null "ldap_access": null
} }
], ],
"contains_hidden_groups": false "contains_hidden_groups": false,
"overridden": false
} }
] ]
``` ```
...@@ -837,7 +839,8 @@ will be used. ...@@ -837,7 +839,8 @@ will be used.
"ldap_access": null "ldap_access": null
} }
], ],
"contains_hidden_groups": false "contains_hidden_groups": false,
"overridden": false
} }
``` ```
...@@ -921,7 +924,8 @@ These are system generated rules. ...@@ -921,7 +924,8 @@ These are system generated rules.
"ldap_access": null "ldap_access": null
} }
], ],
"contains_hidden_groups": false "contains_hidden_groups": false,
"overridden": false
} }
``` ```
......
...@@ -75,8 +75,9 @@ class ApprovalMergeRequestRule < ApplicationRecord ...@@ -75,8 +75,9 @@ class ApprovalMergeRequestRule < ApplicationRecord
end end
def self.applicable_to_branch(branch) def self.applicable_to_branch(branch)
includes(approval_project_rule: :protected_branches).select do |rule| includes(:users, :groups, approval_project_rule: [:users, :groups, :protected_branches]).select do |rule|
next true unless rule.approval_project_rule.present? next true unless rule.approval_project_rule.present?
next true if rule.overridden?
rule.approval_project_rule.applies_to_branch?(branch) rule.approval_project_rule.applies_to_branch?(branch)
end end
......
...@@ -10,8 +10,8 @@ class ApprovalWrappedRule ...@@ -10,8 +10,8 @@ class ApprovalWrappedRule
def_delegators(:@approval_rule, def_delegators(:@approval_rule,
:regular?, :any_approver?, :code_owner?, :report_approver?, :regular?, :any_approver?, :code_owner?, :report_approver?,
:id, :name, :users, :groups, :code_owner, :source_rule, :overridden?, :id, :name, :users, :groups, :code_owner,
:rule_type, :approvals_required) :source_rule, :rule_type, :approvals_required)
def self.wrap(merge_request, rule) def self.wrap(merge_request, rule)
if rule.any_approver? if rule.any_approver?
......
...@@ -54,4 +54,13 @@ module ApprovalRuleLike ...@@ -54,4 +54,13 @@ module ApprovalRuleLike
def user_defined? def user_defined?
regular? || any_approver? regular? || any_approver?
end end
def overridden?
return false unless source_rule.present?
source_rule.name != name ||
source_rule.approvals_required != approvals_required ||
source_rule.user_ids.to_set != user_ids.to_set ||
source_rule.group_ids.to_set != group_ids.to_set
end
end end
---
title: Return overridden property in API response
merge_request: 28521
author:
type: fixed
...@@ -9,6 +9,7 @@ module EE ...@@ -9,6 +9,7 @@ module EE
end end
expose :source_rule, using: MergeRequestApprovalRule::SourceRule expose :source_rule, using: MergeRequestApprovalRule::SourceRule
expose :overridden?, as: :overridden
end end
end end
end end
......
...@@ -30,7 +30,8 @@ ...@@ -30,7 +30,8 @@
"type": "object", "type": "object",
"properties": {} "properties": {}
} }
} },
"overridden": { "type": "boolean" }
}, },
"additionalProperties": false "additionalProperties": false
} }
...@@ -189,10 +189,14 @@ describe ApprovalMergeRequestRule do ...@@ -189,10 +189,14 @@ describe ApprovalMergeRequestRule do
subject { described_class.applicable_to_branch(branch) } subject { described_class.applicable_to_branch(branch) }
context 'when there are no associated source rules' do shared_examples_for 'with applicable rules to specified branch' do
it { is_expected.to eq([rule]) } it { is_expected.to eq([rule]) }
end end
context 'when there are no associated source rules' do
it_behaves_like 'with applicable rules to specified branch'
end
context 'when there are associated source rules' do context 'when there are associated source rules' do
let(:source_rule) { create(:approval_project_rule, project: merge_request.target_project) } let(:source_rule) { create(:approval_project_rule, project: merge_request.target_project) }
...@@ -200,27 +204,46 @@ describe ApprovalMergeRequestRule do ...@@ -200,27 +204,46 @@ describe ApprovalMergeRequestRule do
rule.update!(approval_project_rule: source_rule) rule.update!(approval_project_rule: source_rule)
end end
context 'and there are no associated protected branches to source rule' do context 'and rule is not overridden' do
it { is_expected.to eq([rule]) }
end
context 'and there are associated protected branches to source rule' do
before do before do
source_rule.update!(protected_branches: protected_branches) rule.update!(
name: source_rule.name,
approvals_required: source_rule.approvals_required,
users: source_rule.users,
groups: source_rule.groups
)
end end
context 'and branch matches' do context 'and there are no associated protected branches to source rule' do
let(:protected_branches) { [create(:protected_branch, name: branch)] } it_behaves_like 'with applicable rules to specified branch'
it { is_expected.to eq([rule]) }
end end
context 'but branch does not match anything' do context 'and there are associated protected branches to source rule' do
let(:protected_branches) { [create(:protected_branch, name: branch.reverse)] } before do
source_rule.update!(protected_branches: protected_branches)
end
context 'and branch matches' do
let(:protected_branches) { [create(:protected_branch, name: branch)] }
it { is_expected.to be_empty } it_behaves_like 'with applicable rules to specified branch'
end
context 'but branch does not match anything' do
let(:protected_branches) { [create(:protected_branch, name: branch.reverse)] }
it { is_expected.to be_empty }
end
end end
end end
context 'but rule is overridden' do
before do
rule.update!(name: 'Overridden Rule')
end
it_behaves_like 'with applicable rules to specified branch'
end
end end
end end
......
...@@ -1585,8 +1585,22 @@ describe ApprovalState do ...@@ -1585,8 +1585,22 @@ describe ApprovalState do
merge_request.update!(target_branch: 'stable-1') merge_request.update!(target_branch: 'stable-1')
source_rule.update!(protected_branches: [protected_branch]) source_rule.update!(protected_branches: [protected_branch])
another_source_rule.update!(protected_branches: [another_protected_branch]) another_source_rule.update!(protected_branches: [another_protected_branch])
mr_rule.update!(approval_project_rule: another_source_rule)
another_mr_rule.update!(approval_project_rule: source_rule) mr_rule.update!(
approval_project_rule: another_source_rule,
name: another_source_rule.name,
approvals_required: another_source_rule.approvals_required,
users: another_source_rule.users,
groups: another_source_rule.groups
)
another_mr_rule.update!(
approval_project_rule: source_rule,
name: source_rule.name,
approvals_required: source_rule.approvals_required,
users: source_rule.users,
groups: source_rule.groups
)
end end
context 'and scoped_approval_rules feature is enabled' do context 'and scoped_approval_rules feature is enabled' do
......
...@@ -84,12 +84,96 @@ describe ApprovalRuleLike do ...@@ -84,12 +84,96 @@ describe ApprovalRuleLike do
subject { create(:approval_merge_request_rule, merge_request: merge_request) } subject { create(:approval_merge_request_rule, merge_request: merge_request) }
it_behaves_like 'approval rule like' it_behaves_like 'approval rule like'
describe '#overridden?' do
it 'returns false' do
expect(subject.overridden?).to be_falsy
end
context 'when rule has source rule' do
let(:source_rule) do
create(
:approval_project_rule,
project: merge_request.target_project,
name: 'Source Rule',
approvals_required: 2,
users: [user1, user2],
groups: [group1, group2]
)
end
before do
subject.update!(approval_project_rule: source_rule)
end
context 'and any attributes differ from source rule' do
shared_examples_for 'overridden rule' do
it 'returns true' do
expect(subject.overridden?).to be_truthy
end
end
context 'name' do
before do
subject.update!(name: 'Overridden Rule')
end
it_behaves_like 'overridden rule'
end
context 'approvals_required' do
before do
subject.update!(approvals_required: 1)
end
it_behaves_like 'overridden rule'
end
context 'users' do
before do
subject.update!(users: [user1])
end
it_behaves_like 'overridden rule'
end
context 'groups' do
before do
subject.update!(groups: [group1])
end
it_behaves_like 'overridden rule'
end
end
context 'and no changes made to attributes' do
before do
subject.update!(
name: source_rule.name,
approvals_required: source_rule.approvals_required,
users: source_rule.users,
groups: source_rule.groups
)
end
it 'returns false' do
expect(subject.overridden?).to be_falsy
end
end
end
end
end end
context 'Project' do context 'Project' do
subject { create(:approval_project_rule) } subject { create(:approval_project_rule) }
it_behaves_like 'approval rule like' it_behaves_like 'approval rule like'
describe '#overridden?' do
it 'returns false' do
expect(subject.overridden?).to be_falsy
end
end
end end
describe '.group_users' do describe '.group_users' do
......
...@@ -28,8 +28,8 @@ describe Projects::MergeRequestsController do ...@@ -28,8 +28,8 @@ describe Projects::MergeRequestsController do
create(:code_owner_rule, merge_request: merge_request) create(:code_owner_rule, merge_request: merge_request)
# Threshold of 2 because we load the users & group users for all rules # Threshold of 3 because we load the source_rule, users & group users for all rules
expect { get_edit }.not_to exceed_query_limit(control).with_threshold(2) expect { get_edit }.not_to exceed_query_limit(control).with_threshold(3)
end end
it 'does not cause extra queries when multiple code owner rules are present' do it 'does not cause extra queries when multiple code owner rules are present' do
......
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