Skip to content
Snippets Groups Projects
Commit 6ee0436c authored by Douwe Maan's avatar Douwe Maan
Browse files

Merge branch 'tc-namespace-license-checks--multiple-assignees' into 'master'

CE counterpart of: Namespace license checks for multiple assignees

See merge request !11825
parents 52862754 2194856f
No related branches found
No related tags found
No related merge requests found
Showing
with 145 additions and 49 deletions
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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,
Loading
Loading
@@ -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',
Loading
Loading
@@ -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
Loading
Loading
@@ -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)
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
@@ -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]
@updates[:assignee_ids] =
if issuable.allows_multiple_assignees?
issuable.assignees.pluck(:id) + users.map(&:id)
else
[users.last.id]
end
end
desc do
if issuable.allows_multiple_assignees?
'Remove all or specific assignee(s)'
else
@updates[:assignee_id] = users.last.id
'Remove assignee'
end
end
desc 'Remove assignee'
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'
Loading
Loading
Loading
Loading
@@ -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")
Loading
Loading
Loading
Loading
@@ -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 } }
Loading
Loading
Loading
Loading
@@ -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)
Loading
Loading
@@ -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)}"
Loading
Loading
@@ -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
 
Loading
Loading
Loading
Loading
@@ -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) }
 
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
@@ -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])
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment