Merge branch 'tc-namespace-license-checks--multiple-assignees' into 'master'
CE counterpart of: Namespace license checks for multiple assignees See merge request !11825
This commit is contained in:
commit
6ee0436c89
13 changed files with 146 additions and 50 deletions
|
@ -206,8 +206,6 @@ function UsersSelect(currentUser, els) {
|
|||
return $dropdown.glDropdown({
|
||||
showMenuAbove: showMenuAbove,
|
||||
data: function(term, callback) {
|
||||
var isAuthorFilter;
|
||||
isAuthorFilter = $('.js-author-search');
|
||||
return _this.users(term, options, function(users) {
|
||||
// GitLabDropdownFilter returns this.instance
|
||||
// GitLabDropdownRemote returns this.options.instance
|
||||
|
|
|
@ -16,8 +16,8 @@ module FormHelper
|
|||
end
|
||||
end
|
||||
|
||||
def issue_dropdown_options(issuable, has_multiple_assignees = true)
|
||||
options = {
|
||||
def issue_assignees_dropdown_options
|
||||
{
|
||||
toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data',
|
||||
title: 'Select assignee',
|
||||
filter: true,
|
||||
|
@ -27,8 +27,8 @@ module FormHelper
|
|||
first_user: current_user&.username,
|
||||
null_user: true,
|
||||
current_user: true,
|
||||
project_id: issuable.project.try(:id),
|
||||
field_name: "#{issuable.class.model_name.param_key}[assignee_ids][]",
|
||||
project_id: @project.id,
|
||||
field_name: 'issue[assignee_ids][]',
|
||||
default_label: 'Unassigned',
|
||||
'max-select': 1,
|
||||
'dropdown-header': 'Assignee',
|
||||
|
@ -38,13 +38,5 @@ module FormHelper
|
|||
current_user_info: current_user.to_json(only: [:id, :name])
|
||||
}
|
||||
}
|
||||
|
||||
if has_multiple_assignees
|
||||
options[:title] = 'Select assignee(s)'
|
||||
options[:data][:'dropdown-header'] = 'Assignee(s)'
|
||||
options[:data].delete(:'max-select')
|
||||
end
|
||||
|
||||
options
|
||||
end
|
||||
end
|
||||
|
|
|
@ -126,6 +126,18 @@ module SearchHelper
|
|||
search_path(options)
|
||||
end
|
||||
|
||||
def search_filter_input_options(type)
|
||||
{
|
||||
id: "filtered-search-#{type}",
|
||||
placeholder: 'Search or filter results...',
|
||||
data: {
|
||||
'project-id' => @project.id,
|
||||
'username-params' => @users.to_json(only: [:id, :username]),
|
||||
'base-endpoint' => namespace_project_path(@project.namespace, @project)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# Sanitize a HTML field for search display. Most tags are stripped out and the
|
||||
# maximum length is set to 200 characters.
|
||||
def search_md_sanitize(object, field)
|
||||
|
|
|
@ -102,6 +102,14 @@ module Issuable
|
|||
def locking_enabled?
|
||||
title_changed? || description_changed?
|
||||
end
|
||||
|
||||
def allows_multiple_assignees?
|
||||
false
|
||||
end
|
||||
|
||||
def has_multiple_assignees?
|
||||
assignees.count > 1
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
|
|
@ -197,11 +197,19 @@ class MergeRequest < ActiveRecord::Base
|
|||
}
|
||||
end
|
||||
|
||||
# This method is needed for compatibility with issues to not mess view and other code
|
||||
# These method are needed for compatibility with issues to not mess view and other code
|
||||
def assignees
|
||||
Array(assignee)
|
||||
end
|
||||
|
||||
def assignee_ids
|
||||
Array(assignee_id)
|
||||
end
|
||||
|
||||
def assignee_ids=(ids)
|
||||
write_attribute(:assignee_id, ids.last)
|
||||
end
|
||||
|
||||
def assignee_or_author?(user)
|
||||
author_id == user.id || assignee_id == user.id
|
||||
end
|
||||
|
|
|
@ -92,9 +92,12 @@ module QuickActions
|
|||
|
||||
desc 'Assign'
|
||||
explanation do |users|
|
||||
"Assigns #{users.first.to_reference}." if users.any?
|
||||
users = issuable.allows_multiple_assignees? ? users : users.take(1)
|
||||
"Assigns #{users.map(&:to_reference).to_sentence}."
|
||||
end
|
||||
params do
|
||||
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
|
||||
end
|
||||
params '@user'
|
||||
condition do
|
||||
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
|
||||
end
|
||||
|
@ -104,28 +107,69 @@ module QuickActions
|
|||
command :assign do |users|
|
||||
next if users.empty?
|
||||
|
||||
if issuable.is_a?(Issue)
|
||||
@updates[:assignee_ids] = [users.last.id]
|
||||
else
|
||||
@updates[:assignee_id] = users.last.id
|
||||
end
|
||||
@updates[:assignee_ids] =
|
||||
if issuable.allows_multiple_assignees?
|
||||
issuable.assignees.pluck(:id) + users.map(&:id)
|
||||
else
|
||||
[users.last.id]
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Remove assignee'
|
||||
desc do
|
||||
if issuable.allows_multiple_assignees?
|
||||
'Remove all or specific assignee(s)'
|
||||
else
|
||||
'Remove assignee'
|
||||
end
|
||||
end
|
||||
explanation do
|
||||
"Removes assignee #{issuable.assignees.first.to_reference}."
|
||||
"Removes #{'assignee'.pluralize(issuable.assignees.size)} #{issuable.assignees.map(&:to_reference).to_sentence}."
|
||||
end
|
||||
params do
|
||||
issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
|
||||
end
|
||||
condition do
|
||||
issuable.persisted? &&
|
||||
issuable.assignees.any? &&
|
||||
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
|
||||
end
|
||||
command :unassign do
|
||||
if issuable.is_a?(Issue)
|
||||
@updates[:assignee_ids] = []
|
||||
else
|
||||
@updates[:assignee_id] = nil
|
||||
end
|
||||
parse_params do |unassign_param|
|
||||
# When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
|
||||
extract_users(unassign_param) if issuable.allows_multiple_assignees?
|
||||
end
|
||||
command :unassign do |users = nil|
|
||||
@updates[:assignee_ids] =
|
||||
if users&.any?
|
||||
issuable.assignees.pluck(:id) - users.map(&:id)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
desc do
|
||||
"Change assignee#{'(s)' if issuable.allows_multiple_assignees?}"
|
||||
end
|
||||
explanation do |users|
|
||||
users = issuable.allows_multiple_assignees? ? users : users.take(1)
|
||||
"Change #{'assignee'.pluralize(users.size)} to #{users.map(&:to_reference).to_sentence}."
|
||||
end
|
||||
params do
|
||||
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
|
||||
end
|
||||
condition do
|
||||
issuable.persisted? &&
|
||||
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
|
||||
end
|
||||
parse_params do |assignee_param|
|
||||
extract_users(assignee_param)
|
||||
end
|
||||
command :reassign do |users|
|
||||
@updates[:assignee_ids] =
|
||||
if issuable.allows_multiple_assignees?
|
||||
users.map(&:id)
|
||||
else
|
||||
[users.last.id]
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Set milestone'
|
||||
|
|
|
@ -19,10 +19,11 @@
|
|||
":data-name" => "assignee.name",
|
||||
":data-username" => "assignee.username" }
|
||||
.dropdown
|
||||
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: "button", ref: "assigneeDropdown", data: { toggle: "dropdown", field_name: "issue[assignee_ids][]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true", multi_select: "true", 'max-select' => 1, dropdown: { header: 'Assignee' } },
|
||||
- dropdown_options = issue_assignees_dropdown_options
|
||||
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: { toggle: 'dropdown', field_name: 'issue[assignee_ids][]', first_user: current_user&.username, current_user: 'true', project_id: @project.id, null_user: 'true', multi_select: 'true', 'dropdown-header': dropdown_options[:data][:'dropdown-header'], 'max-select': dropdown_options[:data][:'max-select'] },
|
||||
":data-issuable-id" => "issue.id",
|
||||
":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
|
||||
Select assignee
|
||||
= dropdown_options[:title]
|
||||
= icon("chevron-down")
|
||||
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
|
||||
= dropdown_title("Assign to")
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
.scroll-container
|
||||
%ul.tokens-container.list-unstyled
|
||||
%li.input-token
|
||||
%input.form-control.filtered-search{ id: "filtered-search-#{type.to_s}", placeholder: 'Search or filter results...', data: { 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } }
|
||||
%input.form-control.filtered-search{ search_filter_input_options(type) }
|
||||
= icon('filter')
|
||||
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
|
||||
%ul{ data: { dropdown: true } }
|
||||
|
|
|
@ -37,19 +37,20 @@
|
|||
- issuable.assignees.each do |assignee|
|
||||
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
|
||||
|
||||
- options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
|
||||
|
||||
- options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
|
||||
- title = 'Select assignee'
|
||||
|
||||
- if issuable.is_a?(Issue)
|
||||
- unless issuable.assignees.any?
|
||||
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil
|
||||
- dropdown_options = issue_assignees_dropdown_options
|
||||
- title = dropdown_options[:title]
|
||||
- options[:toggle_class] += ' js-multiselect js-save-user-data'
|
||||
- data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" }
|
||||
- data[:multi_select] = true
|
||||
- data['dropdown-title'] = title
|
||||
- data['dropdown-header'] = 'Assignee'
|
||||
- data['max-select'] = 1
|
||||
- data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
|
||||
- data['max-select'] = dropdown_options[:data][:'max-select']
|
||||
- options[:data].merge!(data)
|
||||
|
||||
= dropdown_tag(title, options: options)
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
- if issuable.assignees.length === 0
|
||||
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
|
||||
|
||||
= dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable,false))
|
||||
= dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_assignees_dropdown_options)
|
||||
= link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
|
||||
|
|
|
@ -31,8 +31,8 @@ describe 'New/edit issue', :feature, :js do
|
|||
# the original method, resulting in infinite recurison when called.
|
||||
# This is likely a bug with helper modules included into dynamically generated view classes.
|
||||
# To work around this, we have to hold on to and call to the original implementation manually.
|
||||
original_issue_dropdown_options = FormHelper.instance_method(:issue_dropdown_options)
|
||||
allow_any_instance_of(FormHelper).to receive(:issue_dropdown_options).and_wrap_original do |original, *args|
|
||||
original_issue_dropdown_options = FormHelper.instance_method(:issue_assignees_dropdown_options)
|
||||
allow_any_instance_of(FormHelper).to receive(:issue_assignees_dropdown_options).and_wrap_original do |original, *args|
|
||||
options = original_issue_dropdown_options.bind(original.receiver).call(*args)
|
||||
options[:data][:per_page] = 2
|
||||
|
||||
|
|
|
@ -105,6 +105,22 @@ describe MergeRequest, models: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#assignee_ids' do
|
||||
it 'returns an array of the assigned user id' do
|
||||
subject.assignee_id = 123
|
||||
|
||||
expect(subject.assignee_ids).to eq([123])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#assignee_ids=' do
|
||||
it 'sets assignee_id to the last id in the array' do
|
||||
subject.assignee_ids = [123, 456]
|
||||
|
||||
expect(subject.assignee_id).to eq(456)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#assignee_or_author?' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
|
|
|
@ -359,18 +359,18 @@ describe QuickActions::InterpretService, services: true do
|
|||
let(:content) { "/assign @#{developer.username}" }
|
||||
|
||||
context 'Issue' do
|
||||
it 'fetches assignee and populates assignee_id if content contains /assign' do
|
||||
it 'fetches assignee and populates assignee_ids if content contains /assign' do
|
||||
_, updates = service.execute(content, issue)
|
||||
|
||||
expect(updates).to eq(assignee_ids: [developer.id])
|
||||
expect(updates[:assignee_ids]).to match_array([developer.id])
|
||||
end
|
||||
end
|
||||
|
||||
context 'Merge Request' do
|
||||
it 'fetches assignee and populates assignee_id if content contains /assign' do
|
||||
it 'fetches assignee and populates assignee_ids if content contains /assign' do
|
||||
_, updates = service.execute(content, merge_request)
|
||||
|
||||
expect(updates).to eq(assignee_id: developer.id)
|
||||
expect(updates).to eq(assignee_ids: [developer.id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -383,7 +383,7 @@ describe QuickActions::InterpretService, services: true do
|
|||
end
|
||||
|
||||
context 'Issue' do
|
||||
it 'fetches assignee and populates assignee_id if content contains /assign' do
|
||||
it 'fetches assignee and populates assignee_ids if content contains /assign' do
|
||||
_, updates = service.execute(content, issue)
|
||||
|
||||
expect(updates[:assignee_ids]).to match_array([developer.id])
|
||||
|
@ -391,10 +391,10 @@ describe QuickActions::InterpretService, services: true do
|
|||
end
|
||||
|
||||
context 'Merge Request' do
|
||||
it 'fetches assignee and populates assignee_id if content contains /assign' do
|
||||
it 'fetches assignee and populates assignee_ids if content contains /assign' do
|
||||
_, updates = service.execute(content, merge_request)
|
||||
|
||||
expect(updates).to eq(assignee_id: developer.id)
|
||||
expect(updates).to eq(assignee_ids: [developer.id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -422,11 +422,27 @@ describe QuickActions::InterpretService, services: true do
|
|||
end
|
||||
|
||||
context 'Merge Request' do
|
||||
it 'populates assignee_id: nil if content contains /unassign' do
|
||||
merge_request.update(assignee_id: developer.id)
|
||||
it 'populates assignee_ids: [] if content contains /unassign' do
|
||||
merge_request.update(assignee_ids: [developer.id])
|
||||
_, updates = service.execute(content, merge_request)
|
||||
|
||||
expect(updates).to eq(assignee_id: nil)
|
||||
expect(updates).to eq(assignee_ids: [])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'reassign command' do
|
||||
let(:content) { '/reassign' }
|
||||
|
||||
context 'Issue' do
|
||||
it 'reassigns user if content contains /reassign @user' do
|
||||
user = create(:user)
|
||||
|
||||
issue.update(assignee_ids: [developer.id])
|
||||
|
||||
_, updates = service.execute("/reassign @#{user.username}", issue)
|
||||
|
||||
expect(updates).to eq(assignee_ids: [user.id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue