diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 2dcf5e6a0d66a5bf272b5745a3e82f66266a8362..ed34e2f5623595800675bcdf7eaa00119936b1ef 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -66,6 +66,7 @@ export default class IssuableForm { gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources, ).setup(); this.usersSelect = new UsersSelect(); + this.reviewersSelect = new UsersSelect(undefined, '.js-reviewer-search'); this.zenMode = new ZenMode(); this.titleField = this.form.find('input[name*="[title]"]'); diff --git a/app/assets/javascripts/users_select/index.js b/app/assets/javascripts/users_select/index.js index ad86929ec0ad6679c34d964f684ff14676dba5fa..e73b226772f31617b5bc912bb4c48772e1c5ddd1 100644 --- a/app/assets/javascripts/users_select/index.js +++ b/app/assets/javascripts/users_select/index.js @@ -55,6 +55,7 @@ function UsersSelect(currentUser, els, options = {}) { const defaultLabel = $dropdown.data('defaultLabel'); const issueURL = $dropdown.data('issueUpdate'); const $selectbox = $dropdown.closest('.selectbox'); + const $assignToMeLink = $selectbox.next('.assign-to-me-link'); let $block = $selectbox.closest('.block'); const abilityName = $dropdown.data('abilityName'); let $value = $block.find('.value'); @@ -161,7 +162,7 @@ function UsersSelect(currentUser, els, options = {}) { }); }; - $('.assign-to-me-link').on('click', e => { + $assignToMeLink.on('click', e => { e.preventDefault(); $(e.currentTarget).hide(); @@ -451,9 +452,9 @@ function UsersSelect(currentUser, els, options = {}) { } if (getSelected().find(u => u === gon.current_user_id)) { - $('.assign-to-me-link').hide(); + $assignToMeLink.hide(); } else { - $('.assign-to-me-link').show(); + $assignToMeLink.show(); } } diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb index 67bfeb22d92d6ad694bbcedf5a5a298650e8f439..3dde5afcb923544e3b1175ecafe0dcb2dcfaedf2 100644 --- a/app/helpers/form_helper.rb +++ b/app/helpers/form_helper.rb @@ -55,6 +55,29 @@ def assignees_dropdown_options(issuable_type) dropdown_data end + def reviewers_dropdown_options(issuable_type) + { + toggle_class: 'js-reviewer-search js-multiselect js-save-user-data', + title: 'Request review from', + filter: true, + dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-reviewer', + placeholder: _('Search users'), + data: { + first_user: current_user&.username, + null_user: true, + current_user: true, + project_id: (@target_project || @project)&.id, + field_name: "#{issuable_type}[reviewer_ids][]", + default_label: 'Unassigned', + 'dropdown-header': 'Reviewer(s)', + multi_select: true, + 'input-meta': 'name', + 'always-show-selectbox': true, + current_user_info: UserSerializer.new.represent(current_user) + } + } + end + # Overwritten def issue_supports_multiple_assignees? false diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml index 79e6f043b6404a03d9b94a7ea5674547dcea9793..380521af5bfbb4b41398d494eb95574830365dd7 100644 --- a/app/views/shared/issuable/form/_metadata.html.haml +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -12,6 +12,10 @@ .form-group.row.merge-request-assignee = render "shared/issuable/form/metadata_issuable_assignee", issuable: issuable, form: form, has_due_date: has_due_date + - if issuable.allows_reviewers? + .form-group.row.merge-request-reviewer + = render "shared/issuable/form/metadata_issuable_reviewer", issuable: issuable, form: form, has_due_date: has_due_date + = render_if_exists "shared/issuable/form/epic", issuable: issuable, form: form, project: project - if issuable.supports_milestone? diff --git a/app/views/shared/issuable/form/_metadata_issuable_reviewer.html.haml b/app/views/shared/issuable/form/_metadata_issuable_reviewer.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..a8b033bba3607cc9358deb196c0a12c1d2a6ff96 --- /dev/null +++ b/app/views/shared/issuable/form/_metadata_issuable_reviewer.html.haml @@ -0,0 +1,10 @@ += form.label :reviewer_id, "Reviewer", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}" +.col-sm-10{ class: ("col-md-8" if has_due_date) } + .issuable-form-select-holder.selectbox + - issuable.reviewers.each do |reviewer| + = hidden_field_tag "#{issuable.to_ability_name}[reviewer_ids][]", reviewer.id, id: nil, data: { meta: reviewer.name, avatar_url: reviewer.avatar_url, name: reviewer.name, username: reviewer.username } + + - if issuable.reviewers.empty? + = 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)) diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb index 2c949ed84f4e6e82694a489028a6f49ee6e20c1b..397ca70f4a103172360cc05e2d6a79528de2ee68 100644 --- a/spec/features/merge_request/user_edits_mr_spec.rb +++ b/spec/features/merge_request/user_edits_mr_spec.rb @@ -20,4 +20,32 @@ include_context 'merge request edit context' it_behaves_like 'an editable merge request' end + + context 'when merge_request_reviewers is turned on' do + before do + stub_feature_flags(merge_request_reviewers: true) + end + + context 'non-fork merge request' do + include_context 'merge request edit context' + it_behaves_like 'an editable merge request with reviewers' + end + + context 'for a forked project' do + let(:source_project) { fork_project(target_project, nil, repository: true) } + + include_context 'merge request edit context' + it_behaves_like 'an editable merge request with reviewers' + end + end + + context 'when merge_request_reviewers is turned off' do + before do + stub_feature_flags(merge_request_reviewers: false) + end + + it 'does not render reviewers dropdown' do + expect(page).not_to have_selector('.js-reviewer-search') + end + end end diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb index 487c38da7da7ce67957a36d12a303f65e8c07c28..c9910487798de65eaa68c5dbfa073ece816bbff6 100644 --- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb +++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb @@ -124,3 +124,16 @@ def get_textarea_height page.evaluate_script('document.getElementById("merge_request_description").offsetHeight') end + +RSpec.shared_examples 'an editable merge request with reviewers' do + it 'updates merge request', :js do + find('.js-reviewer-search').click + page.within '.dropdown-menu-user' do + click_link user.name + end + expect(find('input[name="merge_request[reviewer_ids][]"]', visible: false).value).to match(user.id.to_s) + page.within '.js-reviewer-search' do + expect(page).to have_content user.name + end + end +end diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb index 55a74dc822950efb1df24a17eae6f108636171df..215d404e395fc266bc20bcd2bb127514b5935178 100644 --- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb @@ -20,6 +20,7 @@ target_project: project, author: user, assignees: [user], + reviewers: [user], milestone: milestone) end