Commit 54a319fe authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '233736-add-reviewer-approval-rules' into 'master'

Display which approval rules match a given reviewer (FE)

See merge request gitlab-org/gitlab!46738
parents 71074380 ef89ff91
...@@ -49,6 +49,7 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -49,6 +49,7 @@ function UsersSelect(currentUser, els, options = {}) {
options.todoStateFilter = $dropdown.data('todoStateFilter'); options.todoStateFilter = $dropdown.data('todoStateFilter');
options.iid = $dropdown.data('iid'); options.iid = $dropdown.data('iid');
options.issuableType = $dropdown.data('issuableType'); options.issuableType = $dropdown.data('issuableType');
options.targetBranch = $dropdown.data('targetBranch');
const showNullUser = $dropdown.data('nullUser'); const showNullUser = $dropdown.data('nullUser');
const defaultNullUser = $dropdown.data('nullUserDefault'); const defaultNullUser = $dropdown.data('nullUserDefault');
const showMenuAbove = $dropdown.data('showMenuAbove'); const showMenuAbove = $dropdown.data('showMenuAbove');
...@@ -582,7 +583,14 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -582,7 +583,14 @@ function UsersSelect(currentUser, els, options = {}) {
img = `<img src='${avatar}' class='avatar avatar-inline m-0' width='32' />`; img = `<img src='${avatar}' class='avatar avatar-inline m-0' width='32' />`;
} }
return userSelect.renderRow(options.issuableType, user, selected, username, img); return userSelect.renderRow(
options.issuableType,
user,
selected,
username,
img,
elsClassName,
);
}, },
}); });
}); });
...@@ -746,8 +754,17 @@ UsersSelect.prototype.users = function(query, options, callback) { ...@@ -746,8 +754,17 @@ UsersSelect.prototype.users = function(query, options, callback) {
...getAjaxUsersSelectParams(options, AJAX_USERS_SELECT_PARAMS_MAP), ...getAjaxUsersSelectParams(options, AJAX_USERS_SELECT_PARAMS_MAP),
}; };
if (options.issuableType === 'merge_request') { const isMergeRequest = options.issuableType === 'merge_request';
const isEditMergeRequest = !options.issuableType && (options.iid && options.targetBranch);
const isNewMergeRequest = !options.issuableType && (!options.iid && options.targetBranch);
if (isMergeRequest || isEditMergeRequest || isNewMergeRequest) {
params.merge_request_iid = options.iid || null; params.merge_request_iid = options.iid || null;
params.approval_rules = true;
}
if (isNewMergeRequest) {
params.target_branch = options.targetBranch || null;
} }
return axios.get(url, { params }).then(({ data }) => { return axios.get(url, { params }).then(({ data }) => {
...@@ -762,7 +779,14 @@ UsersSelect.prototype.buildUrl = function(url) { ...@@ -762,7 +779,14 @@ UsersSelect.prototype.buildUrl = function(url) {
return url; return url;
}; };
UsersSelect.prototype.renderRow = function(issuableType, user, selected, username, img) { UsersSelect.prototype.renderRow = function(
issuableType,
user,
selected,
username,
img,
elsClassName,
) {
const tooltip = issuableType === 'merge_request' && !user.can_merge ? __('Cannot merge') : ''; const tooltip = issuableType === 'merge_request' && !user.can_merge ? __('Cannot merge') : '';
const tooltipClass = tooltip ? `has-tooltip` : ''; const tooltipClass = tooltip ? `has-tooltip` : '';
const selectedClass = selected === true ? 'is-active' : ''; const selectedClass = selected === true ? 'is-active' : '';
...@@ -776,10 +800,15 @@ UsersSelect.prototype.renderRow = function(issuableType, user, selected, usernam ...@@ -776,10 +800,15 @@ UsersSelect.prototype.renderRow = function(issuableType, user, selected, usernam
<a href="#" class="dropdown-menu-user-link d-flex align-items-center ${linkClasses}" ${tooltipAttributes}> <a href="#" class="dropdown-menu-user-link d-flex align-items-center ${linkClasses}" ${tooltipAttributes}>
${this.renderRowAvatar(issuableType, user, img)} ${this.renderRowAvatar(issuableType, user, img)}
<span class="d-flex flex-column overflow-hidden"> <span class="d-flex flex-column overflow-hidden">
<strong class="dropdown-menu-user-full-name"> <strong class="dropdown-menu-user-full-name gl-font-weight-bold">
${escape(user.name)} ${escape(user.name)}
</strong> </strong>
${username ? `<span class="dropdown-menu-user-username">${username}</span>` : ''} ${
username
? `<span class="dropdown-menu-user-username gl-text-gray-400">${username}</span>`
: ''
}
${this.renderApprovalRules(elsClassName, user.applicable_approval_rules)}
</span> </span>
</a> </a>
</li> </li>
...@@ -802,4 +831,22 @@ UsersSelect.prototype.renderRowAvatar = function(issuableType, user, img) { ...@@ -802,4 +831,22 @@ UsersSelect.prototype.renderRowAvatar = function(issuableType, user, img) {
</span>`; </span>`;
}; };
UsersSelect.prototype.renderApprovalRules = function(elsClassName, approvalRules = []) {
if (!gon.features?.reviewerApprovalRules || !elsClassName?.includes('reviewer')) {
return '';
}
const count = approvalRules.length;
const [rule] = approvalRules;
const countText = sprintf(__('(+%{count}&nbsp;rules)'), { count });
const renderApprovalRulesCount = count > 1 ? `<span class="ml-1">${countText}</span>` : '';
return count
? `<div class="gl-display-flex gl-font-sm">
<span class="gl-text-truncate" title="${rule.name}">${rule.name}</span>
${renderApprovalRulesCount}
</div>`
: '';
};
export default UsersSelect; export default UsersSelect;
...@@ -14,6 +14,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -14,6 +14,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
before_action do before_action do
push_frontend_feature_flag(:merge_request_reviewers, @project, default_enabled: true) push_frontend_feature_flag(:merge_request_reviewers, @project, default_enabled: true)
push_frontend_feature_flag(:mr_collapsed_approval_rules, @project) push_frontend_feature_flag(:mr_collapsed_approval_rules, @project)
push_frontend_feature_flag(:reviewer_approval_rules, @project)
end end
def new def new
......
...@@ -53,6 +53,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -53,6 +53,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:vue_issuable_sidebar, @project.group) push_frontend_feature_flag(:vue_issuable_sidebar, @project.group)
push_frontend_feature_flag(:merge_request_reviewers, @project, default_enabled: true) push_frontend_feature_flag(:merge_request_reviewers, @project, default_enabled: true)
push_frontend_feature_flag(:mr_collapsed_approval_rules, @project) push_frontend_feature_flag(:mr_collapsed_approval_rules, @project)
push_frontend_feature_flag(:reviewer_approval_rules, @project)
end end
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions] around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions]
......
...@@ -55,7 +55,7 @@ module FormHelper ...@@ -55,7 +55,7 @@ module FormHelper
dropdown_data dropdown_data
end end
def reviewers_dropdown_options(issuable_type) def reviewers_dropdown_options(issuable_type, iid = nil, target_branch = nil)
dropdown_data = { dropdown_data = {
toggle_class: 'js-reviewer-search js-multiselect js-save-user-data', toggle_class: 'js-reviewer-search js-multiselect js-save-user-data',
title: 'Request review from', title: 'Request review from',
...@@ -78,6 +78,14 @@ module FormHelper ...@@ -78,6 +78,14 @@ module FormHelper
} }
} }
if iid
dropdown_data[:data][:iid] = iid
end
if target_branch
dropdown_data[:data][:target_branch] = target_branch
end
if merge_request_supports_multiple_reviewers? if merge_request_supports_multiple_reviewers?
dropdown_data = multiple_reviewers_dropdown_options(dropdown_data) dropdown_data = multiple_reviewers_dropdown_options(dropdown_data)
end end
......
...@@ -7,6 +7,6 @@ ...@@ -7,6 +7,6 @@
- if issuable.reviewers.empty? - if issuable.reviewers.empty?
= hidden_field_tag "#{issuable.to_ability_name}[reviewer_ids][]", 0, id: nil, data: { meta: '' } = hidden_field_tag "#{issuable.to_ability_name}[reviewer_ids][]", 0, id: nil, data: { meta: '' }
= dropdown_tag(users_dropdown_label(issuable.reviewers), options: reviewers_dropdown_options(issuable.to_ability_name)) = dropdown_tag(users_dropdown_label(issuable.reviewers), options: reviewers_dropdown_options(issuable.to_ability_name, issuable.iid, issuable.target_branch))
- if Feature.enabled?(:mr_collapsed_approval_rules, @project) - if Feature.enabled?(:mr_collapsed_approval_rules, @project)
= render_if_exists 'shared/issuable/approver_suggestion', issuable: issuable, presenter: presenter = render_if_exists 'shared/issuable/approver_suggestion', issuable: issuable, presenter: presenter
---
name: reviewer_approval_rules
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46738
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/293742
milestone: '13.7'
type: development
group: group::code review
default_enabled: false
...@@ -10,4 +10,45 @@ RSpec.describe 'Merge request > User edits MR with multiple reviewers' do ...@@ -10,4 +10,45 @@ RSpec.describe 'Merge request > User edits MR with multiple reviewers' do
end end
it_behaves_like 'multiple reviewers merge request', 'updates', 'Save changes' it_behaves_like 'multiple reviewers merge request', 'updates', 'Save changes'
context 'user approval rules', :js do
let(:rule_name) { 'some-custom-rule' }
let!(:mr_rule) { create(:approval_merge_request_rule, merge_request: merge_request, users: [user], name: rule_name, approvals_required: 1 )}
it 'is not shown in assignee dropdown' do
find('.js-assignee-search').click
wait_for_requests
page.within '.dropdown-menu-assignee' do
expect(page).not_to have_content(rule_name)
end
end
it 'is shown in reviewer dropdown' do
find('.js-reviewer-search').click
wait_for_requests
page.within '.dropdown-menu-reviewer' do
expect(page).to have_content(rule_name)
end
end
end
context 'when reviewer_approval_rules feature flag off' do
let(:rule_name) { 'some-custom-rule' }
let!(:mr_rule) { create(:approval_merge_request_rule, merge_request: merge_request, users: [user], name: rule_name, approvals_required: 1 )}
before do
stub_feature_flags(reviewer_approval_rules: false)
end
it 'is not shown in reviewer dropdown' do
find('.js-reviewer-search').click
wait_for_requests
page.within '.dropdown-menu-reviewer' do
expect(page).not_to have_content(rule_name)
end
end
end
end end
...@@ -962,6 +962,9 @@ msgstr "" ...@@ -962,6 +962,9 @@ msgstr ""
msgid "(%{value}) has already been taken" msgid "(%{value}) has already been taken"
msgstr "" msgstr ""
msgid "(+%{count}&nbsp;rules)"
msgstr ""
msgid "(No changes)" msgid "(No changes)"
msgstr "" msgstr ""
......
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