Commit 6d02ea2c authored by Phil Hughes's avatar Phil Hughes

Merge branch 'add-multiple-assignees-to-create-issue-page' into 'multiple_assignees_review'

Add multiple assignees to create issue page

See merge request !1766
parents 8bf5936d 97622a82
......@@ -732,6 +732,10 @@ GitLabDropdown = (function() {
$input.attr('id', this.options.inputId);
}
if (this.options.inputMeta) {
$input.attr('data-meta', selectedObject[this.options.inputMeta]);
}
return this.dropdown.before($input);
};
......
......@@ -52,40 +52,86 @@ import eventHub from './sidebar/event_hub';
$collapsedSidebar = $block.find('.sidebar-collapsed-user');
$loading = $block.find('.block-loading').fadeOut();
if ($block[0]) {
$block[0].addEventListener('assignYourself', () => {
// Remove unassigned selected from the DOM
const unassignedSelected = $dropdown.closest('.selectbox')
.find("input[name='" + ($dropdown.data('field-name')) + "'][value=0]");
if (unassignedSelected) {
unassignedSelected.remove();
}
const assignYourself = function () {
const unassignedSelected = $dropdown.closest('.selectbox')
.find(`input[name='${$dropdown.data('field-name')}'][value=0]`);
if (unassignedSelected) {
unassignedSelected.remove();
}
// Save current selected user to the DOM
const input = document.createElement('input');
input.type = 'hidden';
input.name = $dropdown.data('field-name');
// Save current selected user to the DOM
const input = document.createElement('input');
input.type = 'hidden';
input.name = $dropdown.data('field-name');
const currentUserInfo = $dropdown.data('currentUserInfo');
if (currentUserInfo) {
input.value = currentUserInfo.id;
input.dataset.meta = currentUserInfo.name;
} else if (_this.currentUser) {
input.value = _this.currentUser.id;
}
$dropdown.before(input);
});
$dropdown.before(input);
}
if ($block[0]) {
$block[0].addEventListener('assignYourself', assignYourself);
}
var getSelected = function() {
const getSelectedUserInputs = function() {
return $selectbox
.find(`input[name="${$dropdown.data('field-name')}"]`)
.find(`input[name="${$dropdown.data('field-name')}"]`);
}
const getSelected = function() {
return getSelectedUserInputs()
.map((index, input) => parseInt(input.value, 10))
.get();
};
const getMultiSelectDropdownTitle = function(selectedUser, isSelected) {
const selectedUsers = getSelected()
.filter(u => u !== 0);
const firstUser = getSelectedUserInputs()
.map((index, input) => ({
name: input.dataset.meta,
value: parseInt(input.value, 10),
}))
.filter(u => u.id !== 0)
.get(0);
if (selectedUsers.length === 0) {
return 'Unassigned';
} else if (selectedUsers.length === 1) {
return firstUser.name;
} else if (isSelected) {
let otherSelected = selectedUsers.filter(s => s !== selectedUser.id);
return `${selectedUser.name} + ${otherSelected.length} more`;
} else {
return `${firstUser.name} + ${selectedUsers.length - 1} more`;
}
}
$('.assign-to-me-link').on('click', (e) => {
e.preventDefault();
$(e.currentTarget).hide();
const $input = $(`input[name="${$dropdown.data('field-name')}"]`);
$input.val(gon.current_user_id);
selectedId = $input.val();
$dropdown.find('.dropdown-toggle-text').text(gon.current_user_fullname).removeClass('is-default');
if ($dropdown.data('multiSelect')) {
assignYourself();
const currentUserInfo = $dropdown.data('currentUserInfo');
$dropdown.find('.dropdown-toggle-text').text(getMultiSelectDropdownTitle(currentUserInfo)).removeClass('is-default');
} else {
const $input = $(`input[name="${$dropdown.data('field-name')}"]`);
$input.val(gon.current_user_id);
selectedId = $input.val();
$dropdown.find('.dropdown-toggle-text').text(gon.current_user_fullname).removeClass('is-default');
}
});
$block.on('click', '.js-assign-yourself', (e) => {
......@@ -243,6 +289,10 @@ import eventHub from './sidebar/event_hub';
this.processData(inputValue, users, callback);
}
if (this.multiSelect) {
return getMultiSelectDropdownTitle(selected, $(el).hasClass('is-active'));
}
if (selected && 'id' in selected && $(el).hasClass('is-active')) {
if (selected.text) {
return selected.text;
......@@ -259,15 +309,19 @@ import eventHub from './sidebar/event_hub';
eventHub.$emit('sidebar.saveAssignees');
}
$selectbox.hide();
// Recalculate where .value is because vue might have changed it
$block = $selectbox.closest('.block');
$value = $block.find('.value');
// display:block overrides the hide-collapse rule
return $value.css('display', '');
if (!$dropdown.data('always-show-selectbox')) {
$selectbox.hide();
// Recalculate where .value is because vue might have changed it
$block = $selectbox.closest('.block');
$value = $block.find('.value');
// display:block overrides the hide-collapse rule
$value.css('display', '');
}
},
multiSelect: $dropdown.hasClass('js-multiselect'),
inputMeta: $dropdown.data('input-meta'),
clicked: function(options) {
const { $el, e, isMarking } = options;
const user = options.selectedObj;
......@@ -322,6 +376,12 @@ import eventHub from './sidebar/event_hub';
// User unselected
eventHub.$emit('sidebar.removeAssignee', user);
}
if (getSelected().find(u => u === gon.current_user_id)) {
$('.assign-to-me-link').hide();
} else {
$('.assign-to-me-link').show();
}
}
var isIssueIndex, isMRIndex, page, selected;
......
......@@ -15,4 +15,37 @@ module FormHelper
end
end
end
def issue_dropdown_options(issuable, has_multiple_assignees = true)
options = {
toggle_class: 'js-user-search js-assignee-search',
title: 'Select assignee',
filter: true,
dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee',
placeholder: 'Search users',
data: {
first_user: (current_user.username if current_user),
null_user: true,
current_user: true,
project_id: issuable.project.try(:id),
field_name: "#{issuable.class.model_name.param_key}[assignee_ids][]",
default_label: 'Assignee',
'max-select': 1,
'dropdown-header': 'Assignee',
}
}
if has_multiple_assignees
options[:toggle_class] += ' js-multiselect js-save-user-data'
options[:title] = 'Select assignee(s)'
options[:data][:multi_select] = true
options[:data][:'input-meta'] = 'name'
options[:data][:'always-show-selectbox'] = true
options[:data][:current_user_info] = current_user.to_json(only: [:id, :name])
options[:data][:'dropdown-header'] = 'Assignee(s)'
options[:data].delete(:'max-select')
end
options
end
end
......@@ -63,6 +63,16 @@ module IssuablesHelper
end
end
def users_dropdown_label(selected_users)
if selected_users.length == 0
"Unassigned"
elsif selected_users.length == 1
selected_users[0].name
else
"#{selected_users[0].name} + #{selected_users.length - 1} more"
end
end
def user_dropdown_label(user_id, default_label)
return default_label if user_id.nil?
return "Unassigned" if user_id == "0"
......
......@@ -13,12 +13,12 @@
- if issuable.is_a?(Issue)
= form.label :assignee_ids, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
.issuable-form-select-holder
= form.hidden_field :assignee_ids
.issuable-form-select-holder.selectbox
- issuable.assignees.each do |assignee|
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { meta: assignee.name }
= dropdown_tag(user_dropdown_label(issuable.assignee_ids, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_ids, field_name: "#{issuable.class.model_name.param_key}[assignee_ids]", default_label: "Assignee"} })
= link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignee_ids.split(', ').include?(current_user.id)}"
= dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable, true))
= link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
- else
= form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
......
......@@ -22,25 +22,65 @@ describe 'New/edit issue', feature: true, js: true do
visit new_namespace_project_issue_path(project.namespace, project)
end
describe 'multiple assignees' do
before do
click_button 'Unassigned'
end
it 'unselects other assignees when unassigned is selected' do
page.within '.dropdown-menu-user' do
click_link user2.name
end
page.within '.dropdown-menu-user' do
click_link 'Unassigned'
end
page.within '.js-assignee-search' do
expect(page).to have_content 'Unassigned'
end
expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match('0')
end
it 'toggles assign to me when current user is selected and unselected' do
page.within '.dropdown-menu-user' do
click_link user.name
end
expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible
page.within '.dropdown-menu-user' do
click_link user.name
end
expect(find('a', text: 'Assign to me')).to be_visible
end
end
it 'allows user to create new issue' do
fill_in 'issue_title', with: 'title'
fill_in 'issue_description', with: 'title'
expect(find('a', text: 'Assign to me')).to be_visible
click_button 'Assignee'
click_button 'Unassigned'
page.within '.dropdown-menu-user' do
click_link user2.name
end
expect(find('input[name="issue[assignee_ids]"]', visible: false).value).to match(user2.id.to_s)
expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s)
page.within '.js-assignee-search' do
expect(page).to have_content user2.name
end
expect(find('a', text: 'Assign to me')).to be_visible
click_link 'Assign to me'
expect(find('input[name="issue[assignee_ids]"]', visible: false).value).to match(user.id.to_s)
assignee_ids = page.all('input[name="issue[assignee_ids][]"]', visible: false)
expect(assignee_ids[0].value).to match(user2.id.to_s)
expect(assignee_ids[1].value).to match(user.id.to_s)
page.within '.js-assignee-search' do
expect(page).to have_content user.name
expect(page).to have_content "#{user2.name} + 1 more"
end
expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible
......@@ -76,7 +116,7 @@ describe 'New/edit issue', feature: true, js: true do
page.within '.issuable-sidebar' do
page.within '.assignee' do
expect(page).to have_content user.name
expect(page).to have_content "2 Assignees"
end
page.within '.milestone' do
......@@ -125,7 +165,7 @@ describe 'New/edit issue', feature: true, js: true do
end
it 'allows user to update issue' do
expect(find('input[name="issue[assignee_ids]"]', visible: false).value).to match(user.id.to_s)
expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user.id.to_s)
expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible
......
......@@ -4,6 +4,24 @@ describe IssuablesHelper do
let(:label) { build_stubbed(:label) }
let(:label2) { build_stubbed(:label) }
describe '#users_dropdown_label' do
let(:user) { build_stubbed(:user) }
let(:user2) { build_stubbed(:user) }
it 'returns unassigned' do
expect(users_dropdown_label([])).to eq('Unassigned')
end
it 'returns selected user\'s name' do
expect(users_dropdown_label([user])).to eq(user.name)
end
it 'returns selected user\'s name and counter' do
expect(users_dropdown_label([user, user2])).to eq("#{user.name} + 1 more")
end
end
describe '#issuable_labels_tooltip' do
it 'returns label text' do
expect(issuable_labels_tooltip([label])).to eq(label.title)
......
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