Commit f2b34fb2 authored by Winnie Hellmann's avatar Winnie Hellmann Committed by Phil Hughes

Show current user immediately in issuable filters

parent 5cb8ad6c
...@@ -8,7 +8,7 @@ const Keyboard = function () { ...@@ -8,7 +8,7 @@ const Keyboard = function () {
var isUpArrow = false; var isUpArrow = false;
var isDownArrow = false; var isDownArrow = false;
var removeHighlight = function removeHighlight(list) { var removeHighlight = function removeHighlight(list) {
var itemElements = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0); var itemElements = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider):not(.hidden)'), 0);
var listItems = []; var listItems = [];
for(var i = 0; i < itemElements.length; i++) { for(var i = 0; i < itemElements.length; i++) {
var listItem = itemElements[i]; var listItem = itemElements[i];
......
...@@ -63,6 +63,9 @@ const AjaxFilter = { ...@@ -63,6 +63,9 @@ const AjaxFilter = {
return AjaxCache.retrieve(url) return AjaxCache.retrieve(url)
.then((data) => { .then((data) => {
this._loadData(data, config); this._loadData(data, config);
if (config.onLoadingFinished) {
config.onLoadingFinished(data);
}
}) })
.catch(config.onError); .catch(config.onError);
}, },
......
...@@ -18,6 +18,9 @@ class DropdownUser extends gl.FilteredSearchDropdown { ...@@ -18,6 +18,9 @@ class DropdownUser extends gl.FilteredSearchDropdown {
}, },
searchValueFunction: this.getSearchInput.bind(this), searchValueFunction: this.getSearchInput.bind(this),
loadingTemplate: this.loadingTemplate, loadingTemplate: this.loadingTemplate,
onLoadingFinished: () => {
this.hideCurrentUser();
},
onError() { onError() {
/* eslint-disable no-new */ /* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.'); new Flash('An error occured fetching the dropdown data.');
...@@ -28,6 +31,11 @@ class DropdownUser extends gl.FilteredSearchDropdown { ...@@ -28,6 +31,11 @@ class DropdownUser extends gl.FilteredSearchDropdown {
this.tokenKeys = tokenKeys; this.tokenKeys = tokenKeys;
} }
hideCurrentUser() {
const currenUserItem = this.dropdown.querySelector('.js-current-user');
currenUserItem.classList.add('hidden');
}
itemClicked(e) { itemClicked(e) {
super.itemClicked(e, super.itemClicked(e,
selected => selected.querySelector('.dropdown-light-content').innerText.trim()); selected => selected.querySelector('.dropdown-light-content').innerText.trim());
......
...@@ -8,18 +8,28 @@ module AvatarsHelper ...@@ -8,18 +8,28 @@ module AvatarsHelper
})) }))
end end
def user_avatar(options = {}) def user_avatar_without_link(options = {})
avatar_size = options[:size] || 16 avatar_size = options[:size] || 16
user_name = options[:user].try(:name) || options[:user_name] user_name = options[:user].try(:name) || options[:user_name]
css_class = options[:css_class] || '' css_class = options[:css_class] || ''
avatar_url = options[:url] || avatar_icon(options[:user] || options[:user_email], avatar_size)
avatar = image_tag( data_attributes = { container: 'body' }
avatar_icon(options[:user] || options[:user_email], avatar_size),
if options[:lazy]
data_attributes[:src] = avatar_url
end
image_tag(
options[:lazy] ? '' : avatar_url,
class: "avatar has-tooltip s#{avatar_size} #{css_class}", class: "avatar has-tooltip s#{avatar_size} #{css_class}",
alt: "#{user_name}'s avatar", alt: "#{user_name}'s avatar",
title: user_name, title: user_name,
data: { container: 'body' } data: data_attributes
) )
end
def user_avatar(options = {})
avatar = user_avatar_without_link(options)
if options[:user] if options[:user]
link_to(avatar, user_path(options[:user])) link_to(avatar, user_path(options[:user]))
......
...@@ -46,30 +46,27 @@ ...@@ -46,30 +46,27 @@
%span.js-filter-tag.dropdown-light-content %span.js-filter-tag.dropdown-light-content
{{tag}} {{tag}}
#js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu #js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
- if current_user
%ul{ data: { dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
user: current_user
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item = render 'shared/issuable/user_dropdown_item',
%button.btn.btn-link.dropdown-user user: User.new(username: '{{username}}', name: '{{name}}'),
%img.avatar{ alt: '{{name}}\'s avatar', width: '30', data: { src: '{{avatar_url}}' } } avatar: { lazy: true, url: '{{avatar_url}}' }
.dropdown-user-details
%span
{{name}}
%span.dropdown-light-content
@{{username}}
#js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu #js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } } %ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } } %li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link %button.btn.btn-link
No Assignee No Assignee
%li.divider %li.divider
- if current_user
= render 'shared/issuable/user_dropdown_item',
user: current_user
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item = render 'shared/issuable/user_dropdown_item',
%button.btn.btn-link.dropdown-user user: User.new(username: '{{username}}', name: '{{name}}'),
%img.avatar{ alt: '{{name}}\'s avatar', width: '30', data: { src: '{{avatar_url}}' } } avatar: { lazy: true, url: '{{avatar_url}}' }
.dropdown-user-details
%span
{{name}}
%span.dropdown-light-content
@{{username}}
#js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu #js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } } %ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } } %li.filter-dropdown-item{ data: { value: 'none' } }
......
- user = local_assigns.fetch(:user)
- avatar = local_assigns.fetch(:avatar, { })
%li.filter-dropdown-item{ class: ('js-current-user' if user == current_user) }
%button.btn.btn-link.dropdown-user{ type: :button }
= user_avatar_without_link(user: user, lazy: avatar[:lazy], url: avatar[:url], size: 30)
.dropdown-user-details
%span
= user.name
%span.dropdown-light-content
= user.to_reference
---
title: Show current user immediately in issuable filters
merge_request: 11630
author:
...@@ -157,6 +157,25 @@ describe 'Dropdown assignee', :feature, :js do ...@@ -157,6 +157,25 @@ describe 'Dropdown assignee', :feature, :js do
end end
end end
describe 'selecting from dropdown without Ajax call' do
before do
Gitlab::Testing::RequestBlockerMiddleware.block_requests!
filtered_search.set('assignee:')
end
after do
Gitlab::Testing::RequestBlockerMiddleware.allow_requests!
end
it 'selects current user' do
find('#js-dropdown-assignee .filter-dropdown-item', text: user.username).click
expect(page).to have_css(js_dropdown_assignee, visible: false)
expect_tokens([{ name: 'assignee', value: user.username }])
expect_filtered_search_input_empty
end
end
describe 'input has existing content' do describe 'input has existing content' do
it 'opens assignee dropdown with existing search term' do it 'opens assignee dropdown with existing search term' do
filtered_search.set('searchTerm assignee:') filtered_search.set('searchTerm assignee:')
......
...@@ -135,6 +135,25 @@ describe 'Dropdown author', js: true, feature: true do ...@@ -135,6 +135,25 @@ describe 'Dropdown author', js: true, feature: true do
end end
end end
describe 'selecting from dropdown without Ajax call' do
before do
Gitlab::Testing::RequestBlockerMiddleware.block_requests!
filtered_search.set('author:')
end
after do
Gitlab::Testing::RequestBlockerMiddleware.allow_requests!
end
it 'selects current user' do
find('#js-dropdown-author .filter-dropdown-item', text: user.username).click
expect(page).to have_css(js_dropdown_author, visible: false)
expect_tokens([{ name: 'author', value: user.username }])
expect_filtered_search_input_empty
end
end
describe 'input has existing content' do describe 'input has existing content' do
it 'opens author dropdown with existing search term' do it 'opens author dropdown with existing search term' do
filtered_search.set('searchTerm author:') filtered_search.set('searchTerm author:')
......
require 'rails_helper' require 'rails_helper'
describe AvatarsHelper do describe AvatarsHelper do
include ApplicationHelper
let(:user) { create(:user) } let(:user) { create(:user) }
describe '#user_avatar' do describe '#user_avatar' do
...@@ -18,4 +20,103 @@ describe AvatarsHelper do ...@@ -18,4 +20,103 @@ describe AvatarsHelper do
is_expected.to include(CGI.escapeHTML(user.avatar_url(size: 16))) is_expected.to include(CGI.escapeHTML(user.avatar_url(size: 16)))
end end
end end
describe '#user_avatar_without_link' do
let(:options) { { user: user } }
subject { helper.user_avatar_without_link(options) }
it 'displays user avatar' do
is_expected.to eq image_tag(
avatar_icon(user, 16),
class: 'avatar has-tooltip s16 ',
alt: "#{user.name}'s avatar",
title: user.name,
data: { container: 'body' }
)
end
context 'with css_class parameter' do
let(:options) { { user: user, css_class: '.cat-pics' } }
it 'uses provided css_class' do
is_expected.to eq image_tag(
avatar_icon(user, 16),
class: "avatar has-tooltip s16 #{options[:css_class]}",
alt: "#{user.name}'s avatar",
title: user.name,
data: { container: 'body' }
)
end
end
context 'with lazy parameter' do
let(:options) { { user: user, lazy: true } }
it 'uses data-src instead of src' do
is_expected.to eq image_tag(
'',
class: 'avatar has-tooltip s16 ',
alt: "#{user.name}'s avatar",
title: user.name,
data: { container: 'body', src: avatar_icon(user, 16) }
)
end
end
context 'with size parameter' do
let(:options) { { user: user, size: 99 } }
it 'uses provided size' do
is_expected.to eq image_tag(
avatar_icon(user, options[:size]),
class: "avatar has-tooltip s#{options[:size]} ",
alt: "#{user.name}'s avatar",
title: user.name,
data: { container: 'body' }
)
end
end
context 'with url parameter' do
let(:options) { { user: user, url: '/over/the/rainbow.png' } }
it 'uses provided url' do
is_expected.to eq image_tag(
options[:url],
class: 'avatar has-tooltip s16 ',
alt: "#{user.name}'s avatar",
title: user.name,
data: { container: 'body' }
)
end
end
context 'with user_name parameter' do
let(:options) { { user_name: 'Tinky Winky', user_email: 'no@f.un' } }
context 'with user parameter' do
let(:options) { { user: user, user_name: 'Tinky Winky' } }
it 'prefers user parameter' do
is_expected.to eq image_tag(
avatar_icon(user, 16),
class: 'avatar has-tooltip s16 ',
alt: "#{user.name}'s avatar",
title: user.name,
data: { container: 'body' }
)
end
end
it 'uses user_name and user_email parameter if user is not present' do
is_expected.to eq image_tag(
avatar_icon(options[:user_email], 16),
class: 'avatar has-tooltip s16 ',
alt: "#{options[:user_name]}'s avatar",
title: options[:user_name],
data: { container: 'body' }
)
end
end
end
end end
import AjaxCache from '~/lib/utils/ajax_cache';
import AjaxFilter from '~/droplab/plugins/ajax_filter';
describe('AjaxFilter', () => {
let dummyConfig;
const dummyData = 'dummy data';
let dummyList;
beforeEach(() => {
dummyConfig = {
endpoint: 'dummy endpoint',
searchKey: 'dummy search key',
};
dummyList = {
data: [],
list: document.createElement('div'),
};
AjaxFilter.hook = {
config: {
AjaxFilter: dummyConfig,
},
list: dummyList,
};
});
describe('trigger', () => {
let ajaxSpy;
beforeEach(() => {
spyOn(AjaxCache, 'retrieve').and.callFake(url => ajaxSpy(url));
spyOn(AjaxFilter, '_loadData');
dummyConfig.onLoadingFinished = jasmine.createSpy('spy');
const dynamicList = document.createElement('div');
dynamicList.dataset.dynamic = true;
dummyList.list.appendChild(dynamicList);
});
it('calls onLoadingFinished after loading data', (done) => {
ajaxSpy = (url) => {
expect(url).toBe('dummy endpoint?dummy search key=');
return Promise.resolve(dummyData);
};
AjaxFilter.trigger()
.then(() => {
expect(dummyConfig.onLoadingFinished.calls.count()).toBe(1);
})
.then(done)
.catch(done.fail);
});
it('does not call onLoadingFinished if Ajax call fails', (done) => {
const dummyError = new Error('My dummy is sick! :-(');
ajaxSpy = (url) => {
expect(url).toBe('dummy endpoint?dummy search key=');
return Promise.reject(dummyError);
};
AjaxFilter.trigger()
.then(done.fail)
.catch((error) => {
expect(error).toBe(dummyError);
expect(dummyConfig.onLoadingFinished.calls.count()).toBe(0);
})
.then(done)
.catch(done.fail);
});
});
});
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