Commit 4dc71e3b authored by Kushal Pandya's avatar Kushal Pandya

Make smarter suggestion for assign commands

Uses Issuable assignees info to filter list of users suggested
in slash commands for assignees.
parent 5c2c6c72
import $ from 'jquery'; import $ from 'jquery';
import 'at.js'; import 'at.js';
import _ from 'underscore'; import _ from 'underscore';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import glRegexp from './lib/utils/regexp'; import glRegexp from './lib/utils/regexp';
import AjaxCache from './lib/utils/ajax_cache'; import AjaxCache from './lib/utils/ajax_cache';
import { spriteIcon } from './lib/utils/common_utils'; import { spriteIcon } from './lib/utils/common_utils';
...@@ -53,8 +54,8 @@ export const defaultAutocompleteConfig = { ...@@ -53,8 +54,8 @@ export const defaultAutocompleteConfig = {
}; };
class GfmAutoComplete { class GfmAutoComplete {
constructor(dataSources) { constructor(dataSources = {}) {
this.dataSources = dataSources || {}; this.dataSources = dataSources;
this.cachedData = {}; this.cachedData = {};
this.isLoadingData = {}; this.isLoadingData = {};
} }
...@@ -199,6 +200,16 @@ class GfmAutoComplete { ...@@ -199,6 +200,16 @@ class GfmAutoComplete {
} }
setupMembers($input) { setupMembers($input) {
const fetchData = this.fetchData.bind(this);
const MEMBER_COMMAND = {
ASSIGN: '/assign',
UNASSIGN: '/unassign',
REASSIGN: '/reassign',
CC: '/cc',
};
let assignees = [];
let command = '';
// Team Members // Team Members
$input.atwho({ $input.atwho({
at: '@', at: '@',
...@@ -225,6 +236,48 @@ class GfmAutoComplete { ...@@ -225,6 +236,48 @@ class GfmAutoComplete {
callbacks: { callbacks: {
...this.getDefaultCallbacks(), ...this.getDefaultCallbacks(),
beforeSave: membersBeforeSave, beforeSave: membersBeforeSave,
matcher(flag, subtext) {
const subtextNodes = subtext
.split(/\n+/g)
.pop()
.split(GfmAutoComplete.regexSubtext);
// Check if @ is followed by '/assign', '/reassign', '/unassign' or '/cc' commands.
command = subtextNodes.find(node => {
if (Object.values(MEMBER_COMMAND).includes(node)) {
return node;
}
return null;
});
// Cache assignees list for easier filtering later
assignees = SidebarMediator.singleton?.store?.assignees?.map(
assignee => `${assignee.username} ${assignee.name}`,
);
const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers);
return match && match.length ? match[1] : null;
},
filter(query, data, searchKey) {
if (GfmAutoComplete.isLoading(data)) {
fetchData(this.$inputor, this.at);
return data;
}
if (data === GfmAutoComplete.defaultLoadingData) {
return $.fn.atwho.default.callbacks.filter(query, data, searchKey);
}
if (command === MEMBER_COMMAND.ASSIGN) {
// Only include members which are not assigned to Issuable currently
return data.filter(member => !assignees.includes(member.search));
} else if (command === MEMBER_COMMAND.UNASSIGN) {
// Only include members which are assigned to Issuable currently
return data.filter(member => assignees.includes(member.search));
}
return data;
},
}, },
}); });
} }
......
---
title: Make smarter user suggestions for assign slash commands
merge_request: 24294
author:
type: added
# frozen_string_literal: true
require 'spec_helper'
describe 'GFM autocomplete EE', :js do
let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
let(:another_user) { create(:user, name: 'another user', username: 'another.user') }
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
before do
project.add_maintainer(user)
end
context 'assignees' do
let(:issue_assignee) { create(:issue, project: project) }
before do
issue_assignee.update(assignees: [user])
sign_in(user)
visit project_issue_path(project, issue_assignee)
wait_for_requests
end
it 'only lists users who are currently assigned to the issue when using /unassign' do
note = find('#note-body')
page.within '.timeline-content-form' do
note.native.send_keys('/una')
end
find('.atwho-view li', text: '/unassign')
note.native.send_keys(:tab)
wait_for_requests
users = find('#at-view-users .atwho-view-ul')
expect(users).to have_content(user.username)
expect(users).not_to have_content(another_user.username)
end
end
end
...@@ -282,6 +282,32 @@ describe 'GFM autocomplete', :js do ...@@ -282,6 +282,32 @@ describe 'GFM autocomplete', :js do
end end
end end
context 'assignees' do
let(:issue_assignee) { create(:issue, project: project) }
before do
issue_assignee.update(assignees: [user])
visit project_issue_path(project, issue_assignee)
wait_for_requests
end
it 'lists users who are currently not assigned to the issue when using /assign' do
note = find('#note-body')
page.within '.timeline-content-form' do
note.native.send_keys('/as')
end
find('.atwho-view li', text: '/assign')
note.native.send_keys(:tab)
wait_for_requests
expect(find('#at-view-users .atwho-view-ul')).not_to have_content(user.username)
end
end
context 'labels' do context 'labels' do
it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do
create(:label, project: project, title: label_xss_title) create(:label, project: project, title: label_xss_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