debian-mirror-gitlab/app/assets/javascripts/users_select/index.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

885 lines
31 KiB
JavaScript
Raw Normal View History

2022-06-21 17:19:12 +05:30
/* eslint-disable func-names, prefer-rest-params, consistent-return, no-shadow, no-self-compare, no-unused-expressions, yoda, prefer-spread, camelcase, no-param-reassign */
2017-08-17 22:00:37 +05:30
/* global Issuable */
/* global emitSidebarEvent */
2018-05-09 12:01:36 +05:30
import $ from 'jquery';
2020-05-24 23:13:21 +05:30
import { escape, template, uniqBy } from 'lodash';
import {
AJAX_USERS_SELECT_OPTIONS_MAP,
AJAX_USERS_SELECT_PARAMS_MAP,
} from 'ee_else_ce/users_select/constants';
2020-11-24 15:15:51 +05:30
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
2021-03-11 19:13:27 +05:30
import { isUserBusy } from '~/set_status_modal/utils';
2021-01-29 00:20:46 +05:30
import { fixTitle, dispose } from '~/tooltips';
2022-06-21 17:19:12 +05:30
import axios from '~/lib/utils/axios_utils';
import { parseBoolean, spriteIcon } from '~/lib/utils/common_utils';
import { loadCSSFile } from '~/lib/utils/css_utils';
import { s__, __, sprintf } from '~/locale';
2021-03-11 19:13:27 +05:30
import { getAjaxUsersSelectOptions, getAjaxUsersSelectParams } from './utils';
2017-08-17 22:00:37 +05:30
// TODO: remove eventHub hack after code splitting refactor
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
2018-03-17 18:26:18 +05:30
function UsersSelect(currentUser, els, options = {}) {
2021-01-03 14:25:43 +05:30
const elsClassName = els?.toString().match('.(.+$)')[1];
2020-01-01 13:55:28 +05:30
const $els = $(els || '.js-user-search');
2017-09-10 17:25:29 +05:30
this.users = this.users.bind(this);
this.user = this.user.bind(this);
2020-07-28 23:09:34 +05:30
this.usersPath = '/-/autocomplete/users.json';
this.userPath = '/-/autocomplete/users/:id.json';
2017-09-10 17:25:29 +05:30
if (currentUser != null) {
if (typeof currentUser === 'object') {
this.currentUser = currentUser;
} else {
this.currentUser = JSON.parse(currentUser);
}
}
2022-08-13 15:12:31 +05:30
const { handleClick } = options;
2020-03-13 15:44:24 +05:30
const userSelect = this;
$els.each((i, dropdown) => {
const userSelect = this;
const options = {};
const $dropdown = $(dropdown);
options.projectId = $dropdown.data('projectId');
options.groupId = $dropdown.data('groupId');
options.showCurrentUser = $dropdown.data('currentUser');
options.todoFilter = $dropdown.data('todoFilter');
options.todoStateFilter = $dropdown.data('todoStateFilter');
options.iid = $dropdown.data('iid');
options.issuableType = $dropdown.data('issuableType');
2021-02-22 17:27:13 +05:30
options.targetBranch = $dropdown.data('targetBranch');
2022-11-25 23:54:43 +05:30
options.showSuggested = $dropdown.data('showSuggested');
2020-03-13 15:44:24 +05:30
const showNullUser = $dropdown.data('nullUser');
const defaultNullUser = $dropdown.data('nullUserDefault');
const showMenuAbove = $dropdown.data('showMenuAbove');
const showAnyUser = $dropdown.data('anyUser');
const firstUser = $dropdown.data('firstUser');
options.authorId = $dropdown.data('authorId');
const defaultLabel = $dropdown.data('defaultLabel');
const issueURL = $dropdown.data('issueUpdate');
const $selectbox = $dropdown.closest('.selectbox');
2020-11-24 15:15:51 +05:30
const $assignToMeLink = $selectbox.next('.assign-to-me-link');
2020-03-13 15:44:24 +05:30
let $block = $selectbox.closest('.block');
const abilityName = $dropdown.data('abilityName');
let $value = $block.find('.value');
const $collapsedSidebar = $block.find('.sidebar-collapsed-user');
2021-02-22 17:27:13 +05:30
const $loading = $block.find('.block-loading').addClass('gl-display-none');
2020-03-13 15:44:24 +05:30
const selectedIdDefault = defaultNullUser && showNullUser ? 0 : null;
let selectedId = $dropdown.data('selected');
let assignTo;
let assigneeTemplate;
let collapsedAssigneeTemplate;
if (selectedId === undefined) {
selectedId = selectedIdDefault;
}
2018-03-17 18:26:18 +05:30
2021-03-08 18:12:59 +05:30
const assignYourself = function () {
2020-03-13 15:44:24 +05:30
const unassignedSelected = $dropdown
.closest('.selectbox')
.find(`input[name='${$dropdown.data('fieldName')}'][value=0]`);
if (unassignedSelected) {
unassignedSelected.remove();
}
// Save current selected user to the DOM
const currentUserInfo = $dropdown.data('currentUserInfo') || {};
const currentUser = userSelect.currentUser || {};
const fieldName = $dropdown.data('fieldName');
const userName = currentUserInfo.name;
const userId = currentUserInfo.id || currentUser.id;
2020-05-24 23:13:21 +05:30
const inputHtmlString = template(`
2020-03-13 15:44:24 +05:30
<input type="hidden" name="<%- fieldName %>"
data-meta="<%- userName %>"
value="<%- userId %>" />
`)({ fieldName, userName, userId });
if ($selectbox) {
$dropdown.parent().before(inputHtmlString);
} else {
$dropdown.after(inputHtmlString);
}
};
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
if ($block[0]) {
$block[0].addEventListener('assignYourself', assignYourself);
}
2017-09-10 17:25:29 +05:30
2021-03-08 18:12:59 +05:30
const getSelectedUserInputs = function () {
2020-03-13 15:44:24 +05:30
return $selectbox.find(`input[name="${$dropdown.data('fieldName')}"]`);
};
2017-09-10 17:25:29 +05:30
2021-03-08 18:12:59 +05:30
const getSelected = function () {
2020-03-13 15:44:24 +05:30
return getSelectedUserInputs()
.map((index, input) => parseInt(input.value, 10))
.get();
};
2017-08-17 22:00:37 +05:30
2021-03-08 18:12:59 +05:30
const checkMaxSelect = function () {
2020-03-13 15:44:24 +05:30
const maxSelect = $dropdown.data('maxSelect');
if (maxSelect) {
const selected = getSelected();
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
if (selected.length > maxSelect) {
const firstSelectedId = selected[0];
const firstSelected = $dropdown
.closest('.selectbox')
.find(`input[name='${$dropdown.data('fieldName')}'][value=${firstSelectedId}]`);
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
firstSelected.remove();
2021-01-03 14:25:43 +05:30
if ($dropdown.hasClass(elsClassName)) {
emitSidebarEvent('sidebar.removeReviewer', {
id: firstSelectedId,
});
} else {
emitSidebarEvent('sidebar.removeAssignee', {
id: firstSelectedId,
});
}
2020-03-13 15:44:24 +05:30
}
}
};
2018-12-13 13:39:08 +05:30
2021-03-08 18:12:59 +05:30
const getMultiSelectDropdownTitle = function (selectedUser, isSelected) {
const selectedUsers = getSelected().filter((u) => u !== 0);
2020-03-13 15:44:24 +05:30
const firstUser = getSelectedUserInputs()
.map((index, input) => ({
name: input.dataset.meta,
value: parseInt(input.value, 10),
}))
2021-03-08 18:12:59 +05:30
.filter((u) => u.id !== 0)
2020-03-13 15:44:24 +05:30
.get(0);
if (selectedUsers.length === 0) {
return s__('UsersSelect|Unassigned');
} else if (selectedUsers.length === 1) {
return firstUser.name;
} else if (isSelected) {
2021-03-08 18:12:59 +05:30
const otherSelected = selectedUsers.filter((s) => s !== selectedUser.id);
2020-03-13 15:44:24 +05:30
return sprintf(s__('UsersSelect|%{name} + %{length} more'), {
name: selectedUser.name,
length: otherSelected.length,
2018-12-13 13:39:08 +05:30
});
2020-03-13 15:44:24 +05:30
}
2020-05-24 23:13:21 +05:30
return sprintf(s__('UsersSelect|%{name} + %{length} more'), {
name: firstUser.name,
length: selectedUsers.length - 1,
});
2020-03-13 15:44:24 +05:30
};
2018-12-13 13:39:08 +05:30
2022-08-13 15:12:31 +05:30
$assignToMeLink.on('click', (e) => {
e.preventDefault();
$(e.currentTarget).hide();
2020-03-13 15:44:24 +05:30
if ($dropdown.data('multiSelect')) {
assignYourself();
checkMaxSelect();
const currentUserInfo = $dropdown.data('currentUserInfo');
$dropdown
.find('.dropdown-toggle-text')
.text(getMultiSelectDropdownTitle(currentUserInfo))
.removeClass('is-default');
} else {
const $input = $(`input[name="${$dropdown.data('fieldName')}"]`);
$input.val(gon.current_user_id);
selectedId = $input.val();
$dropdown
.find('.dropdown-toggle-text')
.text(gon.current_user_fullname)
.removeClass('is-default');
}
});
2021-03-08 18:12:59 +05:30
$block.on('click', '.js-assign-yourself', (e) => {
2020-03-13 15:44:24 +05:30
e.preventDefault();
return assignTo(userSelect.currentUser.id);
});
2021-03-08 18:12:59 +05:30
assignTo = function (selected) {
2020-03-13 15:44:24 +05:30
const data = {};
data[abilityName] = {};
data[abilityName].assignee_id = selected != null ? selected : null;
2021-02-22 17:27:13 +05:30
$loading.removeClass('gl-display-none');
2020-03-13 15:44:24 +05:30
$dropdown.trigger('loading.gl.dropdown');
return axios.put(issueURL, data).then(({ data }) => {
let user = {};
2022-05-07 20:08:51 +05:30
let tooltipTitle;
2020-03-13 15:44:24 +05:30
$dropdown.trigger('loaded.gl.dropdown');
2021-02-22 17:27:13 +05:30
$loading.addClass('gl-display-none');
2020-03-13 15:44:24 +05:30
if (data.assignee) {
user = {
name: data.assignee.name,
username: data.assignee.username,
avatar: data.assignee.avatar_url,
};
2020-05-24 23:13:21 +05:30
tooltipTitle = escape(user.name);
2020-03-13 15:44:24 +05:30
} else {
user = {
name: s__('UsersSelect|Unassigned'),
username: '',
avatar: '',
};
tooltipTitle = s__('UsersSelect|Assignee');
}
$value.html(assigneeTemplate(user));
2021-01-29 00:20:46 +05:30
$collapsedSidebar.attr('title', tooltipTitle);
fixTitle($collapsedSidebar);
2020-03-13 15:44:24 +05:30
return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
});
};
2020-05-24 23:13:21 +05:30
collapsedAssigneeTemplate = template(
2020-11-24 15:15:51 +05:30
`<% if( avatar ) { %> <a class="author-link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> ${spriteIcon(
'user',
)} <% } %>`,
2020-03-13 15:44:24 +05:30
);
2020-05-24 23:13:21 +05:30
assigneeTemplate = template(
2022-08-13 15:12:31 +05:30
`<% if (username) { %> <a class="author-link gl-font-weight-bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself">
2020-03-13 15:44:24 +05:30
${sprintf(s__('UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}'), {
openingTag: '<a href="#" class="js-assign-yourself">',
closingTag: '</a>',
})}</span> <% } %>`,
);
2020-11-24 15:15:51 +05:30
return initDeprecatedJQueryDropdown($dropdown, {
2020-03-13 15:44:24 +05:30
showMenuAbove,
data(term, callback) {
2021-03-08 18:12:59 +05:30
return userSelect.users(term, options, (users) => {
2020-03-13 15:44:24 +05:30
// GitLabDropdownFilter returns this.instance
// GitLabDropdownRemote returns this.options.instance
2020-11-24 15:15:51 +05:30
const deprecatedJQueryDropdown = this.instance || this.options.instance;
deprecatedJQueryDropdown.options.processData(term, users, callback);
2020-03-13 15:44:24 +05:30
});
},
2021-04-29 21:17:54 +05:30
processData(term, dataArg, callback) {
// Sometimes the `dataArg` can contain special dropdown items like
// dividers which we don't want to consider here.
const data = dataArg.filter((x) => !x.type);
2020-03-13 15:44:24 +05:30
let users = data;
// Only show assigned user list when there is no search term
if ($dropdown.hasClass('js-multiselect') && term.length === 0) {
const selectedInputs = getSelectedUserInputs();
// Potential duplicate entries when dealing with issue board
// because issue board is also managed by vue
2021-03-08 18:12:59 +05:30
const selectedUsers = uniqBy(selectedInputs, (a) => a.value)
.filter((input) => {
2020-03-13 15:44:24 +05:30
const userId = parseInt(input.value, 10);
2021-03-08 18:12:59 +05:30
const inUsersArray = users.find((u) => u.id === userId);
2020-03-13 15:44:24 +05:30
return !inUsersArray && userId !== 0;
})
2021-03-08 18:12:59 +05:30
.map((input) => {
2020-03-13 15:44:24 +05:30
const userId = parseInt(input.value, 10);
const { avatarUrl, avatar_url, name, username, canMerge } = input.dataset;
return {
2020-07-28 23:09:34 +05:30
avatar_url: avatarUrl || avatar_url || gon.default_avatar_url,
2020-03-13 15:44:24 +05:30
id: userId,
name,
username,
can_merge: parseBoolean(canMerge),
2018-03-17 18:26:18 +05:30
};
2019-12-21 20:55:43 +05:30
});
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
users = data.concat(selectedUsers);
}
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
let anyUser;
let index;
let len;
let name;
let obj;
let showDivider;
if (term.length === 0) {
showDivider = 0;
if (firstUser) {
// Move current user to the front of the list
for (index = 0, len = users.length; index < len; index += 1) {
obj = users[index];
if (obj.username === firstUser) {
users.splice(index, 1);
users.unshift(obj);
break;
2017-08-17 22:00:37 +05:30
}
2020-03-13 15:44:24 +05:30
}
}
if (showNullUser) {
showDivider += 1;
users.unshift({
beforeDivider: true,
name: s__('UsersSelect|Unassigned'),
id: 0,
});
}
if (showAnyUser) {
showDivider += 1;
name = showAnyUser;
if (name === true) {
name = s__('UsersSelect|Any User');
}
anyUser = {
beforeDivider: true,
name,
id: null,
};
users.unshift(anyUser);
}
if (showDivider) {
users.splice(showDivider, 0, { type: 'divider' });
}
if ($dropdown.hasClass('js-multiselect')) {
2021-03-08 18:12:59 +05:30
const selected = getSelected().filter((i) => i !== 0);
2020-03-13 15:44:24 +05:30
2022-11-25 23:54:43 +05:30
if ($dropdown.data('showSuggested')) {
const suggested = this.suggestedUsers(users);
if (suggested.length) {
users = users.filter(
(u) => !u.suggested || (u.suggested && selected.indexOf(u.id) !== -1),
);
users.splice(showDivider + 1, 0, ...suggested);
}
}
2020-03-13 15:44:24 +05:30
if (selected.length > 0) {
if ($dropdown.data('dropdownHeader')) {
2018-12-13 13:39:08 +05:30
showDivider += 1;
2020-03-13 15:44:24 +05:30
users.splice(showDivider, 0, {
type: 'header',
content: $dropdown.data('dropdownHeader'),
2018-12-13 13:39:08 +05:30
});
}
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
const selectedUsers = users
2021-03-08 18:12:59 +05:30
.filter((u) => selected.indexOf(u.id) !== -1)
2020-03-13 15:44:24 +05:30
.sort((a, b) => a.name > b.name);
2017-08-17 22:00:37 +05:30
2021-03-08 18:12:59 +05:30
users = users.filter((u) => selected.indexOf(u.id) === -1);
2017-08-17 22:00:37 +05:30
2021-03-08 18:12:59 +05:30
selectedUsers.forEach((selectedUser) => {
2020-03-13 15:44:24 +05:30
showDivider += 1;
users.splice(showDivider, 0, selectedUser);
});
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
users.splice(showDivider + 1, 0, { type: 'divider' });
}
}
}
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
callback(users);
if (showMenuAbove) {
2020-11-24 15:15:51 +05:30
$dropdown.data('deprecatedJQueryDropdown').positionMenuAbove();
2020-03-13 15:44:24 +05:30
}
},
2022-11-25 23:54:43 +05:30
suggestedUsers(users) {
const selected = getSelected().filter((i) => i !== 0);
const suggestedUsers = users.filter((u) => u.suggested && selected.indexOf(u.id) === -1);
if (!suggestedUsers.length) return [];
const items = [
{ type: 'header', content: $dropdown.data('suggestedReviewersHeader') },
...suggestedUsers,
{ type: 'header', content: $dropdown.data('allMembersHeader') },
];
return items;
},
2020-03-13 15:44:24 +05:30
filterable: true,
filterRemote: true,
search: {
fields: ['name', 'username'],
},
selectable: true,
fieldName: $dropdown.data('fieldName'),
2020-11-24 15:15:51 +05:30
toggleLabel(selected, el, deprecatedJQueryDropdown) {
const inputValue = deprecatedJQueryDropdown.filterInput.val();
2020-03-13 15:44:24 +05:30
if (this.multiSelect && inputValue === '') {
// Remove non-users from the fullData array
2020-11-24 15:15:51 +05:30
const users = deprecatedJQueryDropdown.filteredFullData();
const callback = deprecatedJQueryDropdown.parseData.bind(deprecatedJQueryDropdown);
2020-03-13 15:44:24 +05:30
// Update the data model
this.processData(inputValue, users, callback);
}
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
if (this.multiSelect) {
return getMultiSelectDropdownTitle(selected, $(el).hasClass('is-active'));
}
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
if (selected && 'id' in selected && $(el).hasClass('is-active')) {
$dropdown.find('.dropdown-toggle-text').removeClass('is-default');
if (selected.text) {
return selected.text;
}
2020-05-24 23:13:21 +05:30
return selected.name;
2020-03-13 15:44:24 +05:30
}
2020-05-24 23:13:21 +05:30
$dropdown.find('.dropdown-toggle-text').addClass('is-default');
return defaultLabel;
2020-03-13 15:44:24 +05:30
},
defaultLabel,
hidden() {
if ($dropdown.hasClass('js-multiselect')) {
2021-01-03 14:25:43 +05:30
if ($dropdown.hasClass(elsClassName)) {
emitSidebarEvent('sidebar.saveReviewers');
} else {
emitSidebarEvent('sidebar.saveAssignees');
}
2020-03-13 15:44:24 +05:30
}
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
if (!$dropdown.data('alwaysShowSelectbox')) {
$selectbox.hide();
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
// 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('inputMeta'),
clicked(options) {
const { $el, e, isMarking } = options;
const user = options.selectedObj;
2021-01-29 00:20:46 +05:30
dispose($el);
2020-03-13 15:44:24 +05:30
if ($dropdown.hasClass('js-multiselect')) {
const isActive = $el.hasClass('is-active');
const previouslySelected = $dropdown
.closest('.selectbox')
.find(`input[name='${$dropdown.data('fieldName')}'][value!=0]`);
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
// Enables support for limiting the number of users selected
// Automatically removes the first on the list if more users are selected
checkMaxSelect();
if (user.beforeDivider && user.name.toLowerCase() === 'unassigned') {
// Unassigned selected
previouslySelected.each((index, element) => {
element.remove();
});
2021-01-03 14:25:43 +05:30
if ($dropdown.hasClass(elsClassName)) {
emitSidebarEvent('sidebar.removeAllReviewers');
} else {
emitSidebarEvent('sidebar.removeAllAssignees');
}
2020-03-13 15:44:24 +05:30
} else if (isActive) {
// user selected
2021-01-03 14:25:43 +05:30
if ($dropdown.hasClass(elsClassName)) {
emitSidebarEvent('sidebar.addReviewer', user);
} else {
emitSidebarEvent('sidebar.addAssignee', user);
}
2020-03-13 15:44:24 +05:30
// Remove unassigned selection (if it was previously selected)
const unassignedSelected = $dropdown
.closest('.selectbox')
.find(`input[name='${$dropdown.data('fieldName')}'][value=0]`);
if (unassignedSelected) {
unassignedSelected.remove();
2018-12-13 13:39:08 +05:30
}
2020-03-13 15:44:24 +05:30
} else {
if (previouslySelected.length === 0) {
// Select unassigned because there is no more selected users
this.addInput($dropdown.data('fieldName'), 0, {});
2017-09-10 17:25:29 +05:30
}
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
// User unselected
2021-01-03 14:25:43 +05:30
if ($dropdown.hasClass(elsClassName)) {
emitSidebarEvent('sidebar.removeReviewer', user);
} else {
emitSidebarEvent('sidebar.removeAssignee', user);
}
2020-03-13 15:44:24 +05:30
}
2017-09-10 17:25:29 +05:30
2021-03-08 18:12:59 +05:30
if (getSelected().find((u) => u === gon.current_user_id)) {
2020-11-24 15:15:51 +05:30
$assignToMeLink.hide();
2020-03-13 15:44:24 +05:30
} else {
2020-11-24 15:15:51 +05:30
$assignToMeLink.show();
2020-03-13 15:44:24 +05:30
}
}
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
const page = $('body').attr('data-page');
const isIssueIndex = page === 'projects:issues:index';
const isMRIndex = page === page && page === 'projects:merge_requests:index';
if (
$dropdown.hasClass('js-filter-bulk-update') ||
$dropdown.hasClass('js-issuable-form-dropdown')
) {
e.preventDefault();
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
const isSelecting = user.id !== selectedId;
selectedId = isSelecting ? user.id : selectedIdDefault;
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
if (selectedId === gon.current_user_id) {
$('.assign-to-me-link').hide();
} else {
$('.assign-to-me-link').show();
}
return;
}
2021-04-29 21:17:54 +05:30
if (handleClick) {
2020-03-13 15:44:24 +05:30
e.preventDefault();
handleClick(user, isMarking);
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
return Issuable.filterResults($dropdown.closest('form'));
} else if ($dropdown.hasClass('js-filter-submit')) {
return $dropdown.closest('form').submit();
} else if (!$dropdown.hasClass('js-multiselect')) {
const selected = $dropdown
.closest('.selectbox')
.find(`input[name='${$dropdown.data('fieldName')}']`)
.val();
return assignTo(selected);
}
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
// Automatically close dropdown after assignee is selected
// since CE has no multiple assignees
// EE does not have a max-select
if ($dropdown.data('maxSelect') && getSelected().length === $dropdown.data('maxSelect')) {
// Close the dropdown
$dropdown.dropdown('toggle');
}
},
id(user) {
return user.id;
},
opened(e) {
const $el = $(e.currentTarget);
const selected = getSelected();
$el.find('.is-active').removeClass('is-active');
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
function highlightSelected(id) {
$el.find(`li[data-user-id="${id}"] .dropdown-menu-user-link`).addClass('is-active');
}
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
if (selected.length > 0) {
2021-03-08 18:12:59 +05:30
getSelected().forEach((selectedId) => highlightSelected(selectedId));
2020-03-13 15:44:24 +05:30
} else {
highlightSelected(selectedId);
}
},
updateLabel: $dropdown.data('dropdownTitle'),
renderRow(user) {
const username = user.username ? `@${user.username}` : '';
const avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
let selected = false;
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
if (this.multiSelect) {
2021-03-08 18:12:59 +05:30
selected = getSelected().find((u) => user.id === u);
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
const { fieldName } = this;
const field = $dropdown
.closest('.selectbox')
.find(`input[name='${fieldName}'][value='${user.id}']`);
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
if (field.length) {
selected = true;
}
} else {
selected = user.id === selectedId;
}
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
let img = '';
if (user.beforeDivider != null) {
2020-05-24 23:13:21 +05:30
`<li><a href='#' class='${selected === true ? 'is-active' : ''}'>${escape(
2020-03-13 15:44:24 +05:30
user.name,
)}</a></li>`;
} else {
// 0 margin, because it's now handled by a wrapper
2022-08-13 15:12:31 +05:30
img = `<img src='${avatar}' class='avatar avatar-inline gl-m-0!' width='32' />`;
2020-03-13 15:44:24 +05:30
}
2017-08-17 22:00:37 +05:30
2021-02-22 17:27:13 +05:30
return userSelect.renderRow(
options.issuableType,
user,
selected,
username,
img,
elsClassName,
);
2020-03-13 15:44:24 +05:30
},
});
});
2020-11-24 15:15:51 +05:30
if ($('.ajax-users-select').length) {
import(/* webpackChunkName: 'select2' */ 'select2/select2')
.then(() => {
2021-02-22 17:27:13 +05:30
// eslint-disable-next-line promise/no-nesting
loadCSSFile(gon.select2_css_path)
.then(() => {
$('.ajax-users-select').each((i, select) => {
const options = getAjaxUsersSelectOptions($(select), AJAX_USERS_SELECT_OPTIONS_MAP);
options.skipLdap = $(select).hasClass('skip_ldap');
const showNullUser = $(select).data('nullUser');
const showAnyUser = $(select).data('anyUser');
const showEmailUser = $(select).data('emailUser');
const firstUser = $(select).data('firstUser');
return $(select).select2({
placeholder: __('Search for a user'),
multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0,
query(query) {
2021-03-08 18:12:59 +05:30
return userSelect.users(query.term, options, (users) => {
2021-02-22 17:27:13 +05:30
let name;
const data = {
results: users,
};
if (query.term.length === 0) {
if (firstUser) {
// Move current user to the front of the list
const ref = data.results;
for (let index = 0, len = ref.length; index < len; index += 1) {
const obj = ref[index];
if (obj.username === firstUser) {
data.results.splice(index, 1);
data.results.unshift(obj);
break;
}
}
}
if (showNullUser) {
const nullUser = {
name: s__('UsersSelect|Unassigned'),
id: 0,
};
data.results.unshift(nullUser);
}
if (showAnyUser) {
name = showAnyUser;
if (name === true) {
name = s__('UsersSelect|Any User');
}
const anyUser = {
name,
id: null,
};
data.results.unshift(anyUser);
2020-11-24 15:15:51 +05:30
}
2018-12-13 13:39:08 +05:30
}
2021-02-22 17:27:13 +05:30
if (
showEmailUser &&
data.results.length === 0 &&
query.term.match(/^[^@]+@[^@]+$/)
) {
const trimmed = query.term.trim();
const emailUser = {
name: sprintf(__('Invite "%{trimmed}" by email'), { trimmed }),
username: trimmed,
id: trimmed,
invite: true,
};
data.results.unshift(emailUser);
2020-11-24 15:15:51 +05:30
}
2021-02-22 17:27:13 +05:30
return query.callback(data);
});
},
initSelection() {
const args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
return userSelect.initSelection.apply(userSelect, args);
},
formatResult() {
const args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
return userSelect.formatResult.apply(userSelect, args);
},
formatSelection() {
const args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
return userSelect.formatSelection.apply(userSelect, args);
},
dropdownCssClass: 'ajax-users-dropdown',
// we do not want to escape markup since we are displaying html in results
escapeMarkup(m) {
return m;
},
2020-11-24 15:15:51 +05:30
});
2021-02-22 17:27:13 +05:30
});
})
.catch(() => {});
2020-11-24 15:15:51 +05:30
})
.catch(() => {});
}
2017-09-10 17:25:29 +05:30
}
2021-03-08 18:12:59 +05:30
UsersSelect.prototype.initSelection = function (element, callback) {
2020-01-01 13:55:28 +05:30
const id = $(element).val();
2018-12-13 13:39:08 +05:30
if (id === '0') {
2020-01-01 13:55:28 +05:30
const nullUser = {
2019-09-04 21:01:54 +05:30
name: s__('UsersSelect|Unassigned'),
2016-09-13 17:45:13 +05:30
};
2017-09-10 17:25:29 +05:30
return callback(nullUser);
2018-12-13 13:39:08 +05:30
} else if (id !== '') {
2017-09-10 17:25:29 +05:30
return this.user(id, callback);
}
};
2021-03-08 18:12:59 +05:30
UsersSelect.prototype.formatResult = function (user) {
2020-01-01 13:55:28 +05:30
let avatar = gon.default_avatar_url;
2017-09-10 17:25:29 +05:30
if (user.avatar_url) {
avatar = user.avatar_url;
}
2018-12-13 13:39:08 +05:30
return `
<div class='user-result'>
<div class='user-image'>
<img class='avatar avatar-inline s32' src='${avatar}'>
</div>
<div class='user-info'>
<div class='user-name dropdown-menu-user-full-name'>
2020-05-24 23:13:21 +05:30
${escape(user.name)}
2018-12-13 13:39:08 +05:30
</div>
<div class='user-username dropdown-menu-user-username text-secondary'>
2020-05-24 23:13:21 +05:30
${!user.invite ? `@${escape(user.username)}` : ''}
2018-12-13 13:39:08 +05:30
</div>
</div>
</div>
`;
2017-09-10 17:25:29 +05:30
};
2021-03-08 18:12:59 +05:30
UsersSelect.prototype.formatSelection = function (user) {
2020-05-24 23:13:21 +05:30
return escape(user.name);
2017-09-10 17:25:29 +05:30
};
2021-03-08 18:12:59 +05:30
UsersSelect.prototype.user = function (user_id, callback) {
2017-09-10 17:25:29 +05:30
if (!/^\d+$/.test(user_id)) {
return false;
}
2020-01-01 13:55:28 +05:30
let url = this.buildUrl(this.userPath);
2017-09-10 17:25:29 +05:30
url = url.replace(':id', user_id);
2018-12-13 13:39:08 +05:30
return axios.get(url).then(({ data }) => {
callback(data);
});
2017-09-10 17:25:29 +05:30
};
// Return users list. Filtered by query
// Only active users retrieved
2021-03-08 18:12:59 +05:30
UsersSelect.prototype.users = function (query, options, callback) {
2018-03-17 18:26:18 +05:30
const url = this.buildUrl(this.usersPath);
const params = {
search: query,
active: true,
2020-05-24 23:13:21 +05:30
...getAjaxUsersSelectParams(options, AJAX_USERS_SELECT_PARAMS_MAP),
2018-03-17 18:26:18 +05:30
};
2019-12-04 20:38:33 +05:30
2021-02-22 17:27:13 +05:30
const isMergeRequest = options.issuableType === 'merge_request';
2021-03-08 18:12:59 +05:30
const isEditMergeRequest = !options.issuableType && options.iid && options.targetBranch;
const isNewMergeRequest = !options.issuableType && !options.iid && options.targetBranch;
2021-02-22 17:27:13 +05:30
if (isMergeRequest || isEditMergeRequest || isNewMergeRequest) {
2019-12-04 20:38:33 +05:30
params.merge_request_iid = options.iid || null;
2021-02-22 17:27:13 +05:30
params.approval_rules = true;
}
2022-11-25 23:54:43 +05:30
if (isMergeRequest && options.showSuggested) {
params.show_suggested = true;
}
2021-02-22 17:27:13 +05:30
if (isNewMergeRequest) {
params.target_branch = options.targetBranch || null;
2019-12-04 20:38:33 +05:30
}
2018-12-13 13:39:08 +05:30
return axios.get(url, { params }).then(({ data }) => {
callback(data);
});
2017-09-10 17:25:29 +05:30
};
2021-03-08 18:12:59 +05:30
UsersSelect.prototype.buildUrl = function (url) {
2017-09-10 17:25:29 +05:30
if (gon.relative_url_root != null) {
url = gon.relative_url_root.replace(/\/$/, '') + url;
}
return url;
};
2021-03-08 18:12:59 +05:30
UsersSelect.prototype.renderRow = function (
2021-02-22 17:27:13 +05:30
issuableType,
user,
selected,
username,
img,
elsClassName,
) {
2019-12-04 20:38:33 +05:30
const tooltip = issuableType === 'merge_request' && !user.can_merge ? __('Cannot merge') : '';
const tooltipClass = tooltip ? `has-tooltip` : '';
const selectedClass = selected === true ? 'is-active' : '';
const linkClasses = `${selectedClass} ${tooltipClass}`;
const tooltipAttributes = tooltip
? `data-container="body" data-placement="left" data-title="${tooltip}"`
: '';
2022-11-25 23:54:43 +05:30
const dataUserSuggested = user.suggested ? `data-user-suggested=${user.suggested}` : '';
2019-12-04 20:38:33 +05:30
2021-03-11 19:13:27 +05:30
const name =
user?.availability && isUserBusy(user.availability)
? sprintf(__('%{name} (Busy)'), { name: user.name })
: user.name;
2019-12-04 20:38:33 +05:30
return `
2022-11-25 23:54:43 +05:30
<li data-user-id=${user.id} ${dataUserSuggested}>
2022-08-13 15:12:31 +05:30
<a href="#" class="dropdown-menu-user-link gl-display-flex! gl-align-items-center ${linkClasses}" ${tooltipAttributes}>
2019-12-04 20:38:33 +05:30
${this.renderRowAvatar(issuableType, user, img)}
2022-08-13 15:12:31 +05:30
<span class="gl-display-flex gl-flex-direction-column gl-overflow-hidden">
2021-02-22 17:27:13 +05:30
<strong class="dropdown-menu-user-full-name gl-font-weight-bold">
2021-03-11 19:13:27 +05:30
${escape(name)}
2019-12-04 20:38:33 +05:30
</strong>
2021-02-22 17:27:13 +05:30
${
username
2022-05-07 20:08:51 +05:30
? `<span class="dropdown-menu-user-username gl-text-gray-400">${escape(
username,
)}</span>`
2021-02-22 17:27:13 +05:30
: ''
}
${this.renderApprovalRules(elsClassName, user.applicable_approval_rules)}
2019-12-04 20:38:33 +05:30
</span>
</a>
</li>
`;
};
2021-03-08 18:12:59 +05:30
UsersSelect.prototype.renderRowAvatar = function (issuableType, user, img) {
2019-12-04 20:38:33 +05:30
if (user.beforeDivider) {
return img;
}
const mergeIcon =
issuableType === 'merge_request' && !user.can_merge
2021-02-22 17:27:13 +05:30
? spriteIcon('warning-solid', 's12 merge-icon')
2019-12-04 20:38:33 +05:30
: '';
2022-08-13 15:12:31 +05:30
return `<span class="gl-relative gl-mr-3">
2019-12-04 20:38:33 +05:30
${img}
${mergeIcon}
</span>`;
};
2021-03-08 18:12:59 +05:30
UsersSelect.prototype.renderApprovalRules = function (elsClassName, approvalRules = []) {
const count = approvalRules.length;
2021-03-11 19:13:27 +05:30
if (!elsClassName?.includes('reviewer') || !count) {
2021-02-22 17:27:13 +05:30
return '';
}
const [rule] = approvalRules;
const countText = sprintf(__('(+%{count}&nbsp;rules)'), { count });
2022-08-13 15:12:31 +05:30
const renderApprovalRulesCount = count > 1 ? `<span class="gl-ml-2">${countText}</span>` : '';
2021-09-30 23:02:18 +05:30
const ruleName = rule.rule_type === 'code_owner' ? __('Code Owner') : escape(rule.name);
2021-02-22 17:27:13 +05:30
2021-03-08 18:12:59 +05:30
return `<div class="gl-display-flex gl-font-sm">
<span class="gl-text-truncate" title="${ruleName}">${ruleName}</span>
${renderApprovalRulesCount}
</div>`;
2021-02-22 17:27:13 +05:30
};
2017-09-10 17:25:29 +05:30
export default UsersSelect;