mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-23 11:36:09 +10:00
Merge branch 'issues-filters' into 'master'
Big refactoring of issues filters * No magic numbers for issues filtering * Squash project users selectbox and users selectbox into one class * Move from API autocomplete to GitLab internal one * Smarter filter for project/group/all issues * Use selectbox with searchbox for assignee/author/milestone/label * Switch to ajax filter for issue author/assignee Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/1035 See merge request !466
This commit is contained in:
@@ -43,6 +43,9 @@ v 7.10.0 (unreleased)
|
||||
- Link note avatar to user.
|
||||
- Make Git-over-SSH errors more descriptive.
|
||||
- Fix EmailsOnPush.
|
||||
- Refactor issue filtering
|
||||
- AJAX selectbox for issue assignee and author filters
|
||||
- Fix issue with missing options in issue filtering dropdown if selected one
|
||||
|
||||
v 7.9.0
|
||||
- Send EmailsOnPush email when branch or tag is created or deleted.
|
||||
|
||||
@@ -1,57 +1,7 @@
|
||||
@Api =
|
||||
groups_path: "/api/:version/groups.json"
|
||||
group_path: "/api/:version/groups/:id.json"
|
||||
users_path: "/api/:version/users.json"
|
||||
user_path: "/api/:version/users/:id.json"
|
||||
notes_path: "/api/:version/projects/:id/notes.json"
|
||||
namespaces_path: "/api/:version/namespaces.json"
|
||||
project_users_path: "/api/:version/projects/:id/users.json"
|
||||
|
||||
# Get 20 (depends on api) recent notes
|
||||
# and sort the ascending from oldest to newest
|
||||
notes: (project_id, callback) ->
|
||||
url = Api.buildUrl(Api.notes_path)
|
||||
url = url.replace(':id', project_id)
|
||||
|
||||
$.ajax(
|
||||
url: url,
|
||||
data:
|
||||
private_token: gon.api_token
|
||||
gfm: true
|
||||
recent: true
|
||||
dataType: "json"
|
||||
).done (notes) ->
|
||||
notes.sort (a, b) ->
|
||||
return a.id - b.id
|
||||
callback(notes)
|
||||
|
||||
user: (user_id, callback) ->
|
||||
url = Api.buildUrl(Api.user_path)
|
||||
url = url.replace(':id', user_id)
|
||||
|
||||
$.ajax(
|
||||
url: url
|
||||
data:
|
||||
private_token: gon.api_token
|
||||
dataType: "json"
|
||||
).done (user) ->
|
||||
callback(user)
|
||||
|
||||
# Return users list. Filtered by query
|
||||
# Only active users retrieved
|
||||
users: (query, callback) ->
|
||||
url = Api.buildUrl(Api.users_path)
|
||||
|
||||
$.ajax(
|
||||
url: url
|
||||
data:
|
||||
private_token: gon.api_token
|
||||
search: query
|
||||
per_page: 20
|
||||
active: true
|
||||
dataType: "json"
|
||||
).done (users) ->
|
||||
callback(users)
|
||||
|
||||
group: (group_id, callback) ->
|
||||
url = Api.buildUrl(Api.group_path)
|
||||
@@ -80,23 +30,6 @@
|
||||
).done (groups) ->
|
||||
callback(groups)
|
||||
|
||||
# Return project users list. Filtered by query
|
||||
# Only active users retrieved
|
||||
projectUsers: (project_id, query, callback) ->
|
||||
url = Api.buildUrl(Api.project_users_path)
|
||||
url = url.replace(':id', project_id)
|
||||
|
||||
$.ajax(
|
||||
url: url
|
||||
data:
|
||||
private_token: gon.api_token
|
||||
search: query
|
||||
per_page: 20
|
||||
active: true
|
||||
dataType: "json"
|
||||
).done (users) ->
|
||||
callback(users)
|
||||
|
||||
# Return namespaces list. Filtered by query
|
||||
namespaces: (query, callback) ->
|
||||
url = Api.buildUrl(Api.namespaces_path)
|
||||
|
||||
@@ -127,7 +127,7 @@ class Dispatcher
|
||||
when 'show'
|
||||
new ProjectShow()
|
||||
when 'issues', 'merge_requests'
|
||||
new ProjectUsersSelect()
|
||||
new UsersSelect()
|
||||
when 'wikis'
|
||||
new Wikis()
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
class @ProjectUsersSelect
|
||||
constructor: ->
|
||||
$('.ajax-project-users-select').each (i, select) =>
|
||||
project_id = $(select).data('project-id') || $('body').data('project-id')
|
||||
|
||||
$(select).select2
|
||||
placeholder: $(select).data('placeholder') || "Search for a user"
|
||||
multiple: $(select).hasClass('multiselect')
|
||||
minimumInputLength: 0
|
||||
query: (query) ->
|
||||
Api.projectUsers project_id, query.term, (users) ->
|
||||
data = { results: users }
|
||||
|
||||
if query.term.length == 0
|
||||
nullUser = {
|
||||
name: 'Unassigned',
|
||||
avatar: null,
|
||||
username: 'none',
|
||||
id: -1
|
||||
}
|
||||
|
||||
data.results.unshift(nullUser)
|
||||
|
||||
query.callback(data)
|
||||
|
||||
initSelection: (element, callback) ->
|
||||
id = $(element).val()
|
||||
if id != "" && id != "-1"
|
||||
Api.user(id, callback)
|
||||
|
||||
|
||||
formatResult: (args...) =>
|
||||
@formatResult(args...)
|
||||
formatSelection: (args...) =>
|
||||
@formatSelection(args...)
|
||||
dropdownCssClass: "ajax-project-users-dropdown"
|
||||
dropdownAutoWidth: true
|
||||
escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
|
||||
m
|
||||
|
||||
formatResult: (user) ->
|
||||
if user.avatar_url
|
||||
avatar = user.avatar_url
|
||||
else
|
||||
avatar = gon.default_avatar_url
|
||||
|
||||
avatarMarkup = "<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>"
|
||||
|
||||
"<div class='user-result'>
|
||||
#{avatarMarkup}
|
||||
<div class='user-name'>#{user.name}</div>
|
||||
<div class='user-username'>#{user.username}</div>
|
||||
</div>"
|
||||
|
||||
formatSelection: (user) ->
|
||||
user.name
|
||||
@@ -1,20 +1,48 @@
|
||||
class @UsersSelect
|
||||
constructor: ->
|
||||
@usersPath = "/autocomplete/users.json"
|
||||
@userPath = "/autocomplete/users/:id.json"
|
||||
|
||||
$('.ajax-users-select').each (i, select) =>
|
||||
@projectId = $(select).data('project-id')
|
||||
@groupId = $(select).data('group-id')
|
||||
showNullUser = $(select).data('null-user')
|
||||
showAnyUser = $(select).data('any-user')
|
||||
|
||||
$(select).select2
|
||||
placeholder: "Search for a user"
|
||||
multiple: $(select).hasClass('multiselect')
|
||||
minimumInputLength: 0
|
||||
query: (query) ->
|
||||
Api.users query.term, (users) ->
|
||||
query: (query) =>
|
||||
@users query.term, (users) =>
|
||||
data = { results: users }
|
||||
|
||||
if query.term.length == 0
|
||||
anyUser = {
|
||||
name: 'Any',
|
||||
avatar: null,
|
||||
username: 'none',
|
||||
id: null
|
||||
}
|
||||
|
||||
nullUser = {
|
||||
name: 'Unassigned',
|
||||
avatar: null,
|
||||
username: 'none',
|
||||
id: 0
|
||||
}
|
||||
|
||||
if showNullUser
|
||||
data.results.unshift(nullUser)
|
||||
if showAnyUser
|
||||
data.results.unshift(anyUser)
|
||||
|
||||
query.callback(data)
|
||||
|
||||
initSelection: (element, callback) ->
|
||||
initSelection: (element, callback) =>
|
||||
id = $(element).val()
|
||||
if id isnt ""
|
||||
Api.user(id, callback)
|
||||
|
||||
if id != "" && id != "0"
|
||||
@user(id, callback)
|
||||
|
||||
formatResult: (args...) =>
|
||||
@formatResult(args...)
|
||||
@@ -38,3 +66,34 @@ class @UsersSelect
|
||||
|
||||
formatSelection: (user) ->
|
||||
user.name
|
||||
|
||||
user: (user_id, callback) =>
|
||||
url = @buildUrl(@userPath)
|
||||
url = url.replace(':id', user_id)
|
||||
|
||||
$.ajax(
|
||||
url: url
|
||||
dataType: "json"
|
||||
).done (user) ->
|
||||
callback(user)
|
||||
|
||||
# Return users list. Filtered by query
|
||||
# Only active users retrieved
|
||||
users: (query, callback) =>
|
||||
url = @buildUrl(@usersPath)
|
||||
|
||||
$.ajax(
|
||||
url: url
|
||||
data:
|
||||
search: query
|
||||
per_page: 20
|
||||
active: true
|
||||
project_id: @projectId
|
||||
group_id: @groupId
|
||||
dataType: "json"
|
||||
).done (users) ->
|
||||
callback(users)
|
||||
|
||||
buildUrl: (url) ->
|
||||
url = gon.relative_url_root + url if gon.relative_url_root?
|
||||
return url
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
.select2-drop-active {
|
||||
border: 1px solid #BBB !important;
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
|
||||
&.select2-drop-above {
|
||||
margin-bottom: 8px;
|
||||
@@ -106,3 +107,7 @@
|
||||
font-weight: bolder;
|
||||
}
|
||||
}
|
||||
|
||||
.ajax-users-dropdown {
|
||||
min-width: 225px !important;
|
||||
}
|
||||
|
||||
@@ -45,3 +45,11 @@
|
||||
|
||||
.btn { font-size: 13px; }
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
margin-right: 15px;
|
||||
|
||||
> span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
.issues-filters,
|
||||
.issues_bulk_update {
|
||||
select, .select2-container {
|
||||
width: 120px !important;
|
||||
@@ -69,14 +70,16 @@
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.issues-filters,
|
||||
.issues_bulk_update {
|
||||
select, .select2-container {
|
||||
width: 160px !important;
|
||||
width: 140px !important;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.issues-filters,
|
||||
.issues_bulk_update {
|
||||
.select2-container .select2-choice {
|
||||
color: #444 !important;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
class AutocompleteController < ApplicationController
|
||||
def users
|
||||
@users =
|
||||
if params[:project_id].present?
|
||||
project = Project.find(params[:project_id])
|
||||
|
||||
if can?(current_user, :read_project, project)
|
||||
project.team.users
|
||||
end
|
||||
elsif params[:group_id]
|
||||
group = Group.find(params[:group_id])
|
||||
|
||||
if can?(current_user, :read_group, group)
|
||||
group.users
|
||||
end
|
||||
else
|
||||
User.all
|
||||
end
|
||||
|
||||
@users = @users.search(params[:search]) if params[:search].present?
|
||||
@users = @users.active
|
||||
@users = @users.page(params[:page]).per(PER_PAGE)
|
||||
render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
|
||||
end
|
||||
|
||||
def user
|
||||
@user = User.find(params[:id])
|
||||
render json: @user, only: [:name, :username, :id], methods: [:avatar_url]
|
||||
end
|
||||
end
|
||||
@@ -19,6 +19,8 @@
|
||||
require_relative 'projects_finder'
|
||||
|
||||
class IssuableFinder
|
||||
NONE = '0'
|
||||
|
||||
attr_accessor :current_user, :params
|
||||
|
||||
def execute(current_user, params)
|
||||
@@ -112,7 +114,7 @@ class IssuableFinder
|
||||
|
||||
def by_milestone(items)
|
||||
if params[:milestone_id].present?
|
||||
items = items.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id]))
|
||||
items = items.where(milestone_id: (params[:milestone_id] == NONE ? nil : params[:milestone_id]))
|
||||
end
|
||||
|
||||
items
|
||||
@@ -120,7 +122,7 @@ class IssuableFinder
|
||||
|
||||
def by_assignee(items)
|
||||
if params[:assignee_id].present?
|
||||
items = items.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id]))
|
||||
items = items.where(assignee_id: (params[:assignee_id] == NONE ? nil : params[:assignee_id]))
|
||||
end
|
||||
|
||||
items
|
||||
@@ -128,7 +130,7 @@ class IssuableFinder
|
||||
|
||||
def by_author(items)
|
||||
if params[:author_id].present?
|
||||
items = items.where(author_id: (params[:author_id] == '0' ? nil : params[:author_id]))
|
||||
items = items.where(author_id: (params[:author_id] == NONE ? nil : params[:author_id]))
|
||||
end
|
||||
|
||||
items
|
||||
|
||||
@@ -275,7 +275,9 @@ module ApplicationHelper
|
||||
'https://' + promo_host
|
||||
end
|
||||
|
||||
def page_filter_path(options={})
|
||||
def page_filter_path(options = {})
|
||||
without = options.delete(:without)
|
||||
|
||||
exist_opts = {
|
||||
state: params[:state],
|
||||
scope: params[:scope],
|
||||
@@ -288,6 +290,12 @@ module ApplicationHelper
|
||||
|
||||
options = exist_opts.merge(options)
|
||||
|
||||
if without.present?
|
||||
without.each do |key|
|
||||
options.delete(key)
|
||||
end
|
||||
end
|
||||
|
||||
path = request.path
|
||||
path << "?#{options.to_param}"
|
||||
path
|
||||
|
||||
@@ -47,4 +47,9 @@ module LabelsHelper
|
||||
"#FFF"
|
||||
end
|
||||
end
|
||||
|
||||
def project_labels_options(project)
|
||||
options_for_select([['Any', nil]]) +
|
||||
options_from_collection_for_select(project.labels, 'name', 'name', params[:label_name])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,4 +19,16 @@ module MilestonesHelper
|
||||
content_tag :div, nil, options
|
||||
end
|
||||
end
|
||||
|
||||
def projects_milestones_options
|
||||
milestones =
|
||||
if @project
|
||||
@project.milestones
|
||||
else
|
||||
Milestone.where(project_id: @projects)
|
||||
end.active
|
||||
|
||||
options_for_select([['Any', nil]]) +
|
||||
options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,18 +4,27 @@ module SelectsHelper
|
||||
css_class << "multiselect " if opts[:multiple]
|
||||
css_class << (opts[:class] || '')
|
||||
value = opts[:selected] || ''
|
||||
placeholder = opts[:placeholder] || 'Search for a user'
|
||||
|
||||
hidden_field_tag(id, value, class: css_class)
|
||||
end
|
||||
null_user = opts[:null_user] || false
|
||||
any_user = opts[:any_user] || false
|
||||
|
||||
def project_users_select_tag(id, opts = {})
|
||||
css_class = "ajax-project-users-select "
|
||||
css_class << "multiselect " if opts[:multiple]
|
||||
css_class << (opts[:class] || '')
|
||||
value = opts[:selected] || ''
|
||||
placeholder = opts[:placeholder] || 'Select user'
|
||||
project_id = opts[:project_id] || @project.id
|
||||
hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder, 'data-project-id' => project_id)
|
||||
html = {
|
||||
class: css_class,
|
||||
'data-placeholder' => placeholder,
|
||||
'data-null-user' => null_user,
|
||||
'data-any-user' => any_user,
|
||||
}
|
||||
|
||||
unless opts[:scope] == :all
|
||||
if @project
|
||||
html['data-project-id'] = @project.id
|
||||
elsif @group
|
||||
html['data-group-id'] = @group.id
|
||||
end
|
||||
end
|
||||
|
||||
hidden_field_tag(id, value, html)
|
||||
end
|
||||
|
||||
def groups_select_tag(id, opts = {})
|
||||
|
||||
@@ -14,8 +14,8 @@ module Issues
|
||||
issue.update_nth_task(params[:task_num].to_i, false)
|
||||
end
|
||||
|
||||
params[:assignee_id] = "" if params[:assignee_id] == "-1"
|
||||
params[:milestone_id] = "" if params[:milestone_id] == "-1"
|
||||
params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
|
||||
params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE
|
||||
|
||||
old_labels = issue.labels.to_a
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ module MergeRequests
|
||||
merge_request.update_nth_task(params[:task_num].to_i, false)
|
||||
end
|
||||
|
||||
params[:assignee_id] = "" if params[:assignee_id] == "-1"
|
||||
params[:milestone_id] = "" if params[:milestone_id] == "-1"
|
||||
params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
|
||||
params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE
|
||||
|
||||
old_labels = merge_request.labels.to_a
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
%i.fa.fa-user
|
||||
Assign to
|
||||
.col-sm-10
|
||||
= project_users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
|
||||
placeholder: 'Select a user', class: 'custom-form-control',
|
||||
= users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
|
||||
placeholder: 'Select a user', class: 'custom-form-control', null_user: true,
|
||||
selected: issuable.assignee_id)
|
||||
|
||||
= link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
- else
|
||||
none
|
||||
- if can?(current_user, :modify_issue, @issue)
|
||||
= project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @issue.assignee_id)
|
||||
= users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @issue.assignee_id, null_user: true)
|
||||
|
||||
%div.prepend-top-20.clearfix
|
||||
.issuable-context-title
|
||||
@@ -44,5 +44,3 @@
|
||||
|
||||
:coffeescript
|
||||
new Subscription("#{toggle_subscription_namespace_project_issue_path(@issue.project.namespace, @project, @issue)}")
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
.issues_bulk_update.hide
|
||||
= form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do
|
||||
= select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), prompt: "Status", class: 'form-control')
|
||||
= project_users_select_tag('update[assignee_id]', placeholder: 'Assignee')
|
||||
= users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true)
|
||||
= select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
|
||||
= hidden_field_tag 'update[issues_ids]', []
|
||||
= hidden_field_tag :state_event, params[:state_event]
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
|
||||
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
|
||||
$('.edit-issue.inline-update input[type="submit"]').hide();
|
||||
new ProjectUsersSelect();
|
||||
new UsersSelect()
|
||||
new Issue();
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
%i.fa.fa-user
|
||||
Assign to
|
||||
.col-sm-10
|
||||
= project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
|
||||
= users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
|
||||
|
||||
= link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
|
||||
.form-group
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
none
|
||||
.issuable-context-selectbox
|
||||
- if can?(current_user, :modify_merge_request, @merge_request)
|
||||
= project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id)
|
||||
= users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id, null_user: true)
|
||||
|
||||
%div.prepend-top-20.clearfix
|
||||
.issuable-context-title
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
$('.context').html("#{escape_javascript(render partial: 'projects/merge_requests/show/context', locals: { issue: @issue })}");
|
||||
$('.context').effect('highlight');
|
||||
|
||||
new ProjectUsersSelect();
|
||||
new UsersSelect()
|
||||
|
||||
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true});
|
||||
merge_request = new MergeRequest();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'form-horizontal users-project-form' } do |f|
|
||||
.form-group
|
||||
= f.label :user_ids, "People", class: 'control-label'
|
||||
.col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large')
|
||||
.col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all)
|
||||
|
||||
.form-group
|
||||
= f.label :access_level, "Project Access", class: 'control-label'
|
||||
|
||||
@@ -15,105 +15,50 @@
|
||||
All
|
||||
|
||||
%div
|
||||
- if controller.controller_name == 'issues'
|
||||
.check-all-holder
|
||||
= check_box_tag "check_all_issues", nil, false,
|
||||
class: "check_all_issues left",
|
||||
disabled: !can?(current_user, :modify_issue, @project)
|
||||
.issues-other-filters
|
||||
.dropdown.inline.assignee-filter
|
||||
%button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"}
|
||||
%i.fa.fa-user
|
||||
%span.light assignee:
|
||||
- if @assignee.present?
|
||||
%strong= @assignee.name
|
||||
- elsif params[:assignee_id] == "0"
|
||||
Unassigned
|
||||
- else
|
||||
Any
|
||||
%b.caret
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to page_filter_path(assignee_id: nil) do
|
||||
Any
|
||||
= link_to page_filter_path(assignee_id: 0) do
|
||||
Unassigned
|
||||
- @assignees.sort_by(&:name).each do |user|
|
||||
%li
|
||||
= link_to page_filter_path(assignee_id: user.id) do
|
||||
= image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
|
||||
= user.name
|
||||
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_id, :label_name]), method: :get, class: 'filter-form' do
|
||||
- if controller.controller_name == 'issues'
|
||||
.check-all-holder
|
||||
= check_box_tag "check_all_issues", nil, false,
|
||||
class: "check_all_issues left",
|
||||
disabled: !can?(current_user, :modify_issue, @project)
|
||||
.issues-other-filters
|
||||
.filter-item.inline
|
||||
%span.light
|
||||
%i.fa.fa-user
|
||||
Assignee
|
||||
%strong
|
||||
= users_select_tag(:assignee_id, selected: params[:assignee_id],
|
||||
placeholder: 'Any', class: 'trigger-submit', any_user: true, null_user: true)
|
||||
|
||||
.dropdown.inline.prepend-left-10.author-filter
|
||||
%button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"}
|
||||
%i.fa.fa-user
|
||||
%span.light author:
|
||||
- if @author.present?
|
||||
%strong= @author.name
|
||||
- elsif params[:author_id] == "0"
|
||||
Unassigned
|
||||
- else
|
||||
Any
|
||||
%b.caret
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to page_filter_path(author_id: nil) do
|
||||
Any
|
||||
= link_to page_filter_path(author_id: 0) do
|
||||
Unassigned
|
||||
- @authors.sort_by(&:name).each do |user|
|
||||
%li
|
||||
= link_to page_filter_path(author_id: user.id) do
|
||||
= image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
|
||||
= user.name
|
||||
.filter-item.inline
|
||||
%span.light
|
||||
%i.fa.fa-user
|
||||
Author
|
||||
%strong
|
||||
= users_select_tag(:author_id, selected: params[:author_id],
|
||||
placeholder: 'Any', class: 'trigger-submit', any_user: true)
|
||||
|
||||
.dropdown.inline.prepend-left-10.milestone-filter
|
||||
%button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"}
|
||||
%i.fa.fa-clock-o
|
||||
%span.light milestone:
|
||||
- if @milestone.present?
|
||||
%strong= @milestone.title
|
||||
- elsif params[:milestone_id] == "0"
|
||||
None (backlog)
|
||||
- else
|
||||
Any
|
||||
%b.caret
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to page_filter_path(milestone_id: nil) do
|
||||
Any
|
||||
= link_to page_filter_path(milestone_id: 0) do
|
||||
None (backlog)
|
||||
- @milestones.each do |milestone|
|
||||
%li
|
||||
= link_to page_filter_path(milestone_id: milestone.id) do
|
||||
%strong= milestone.title
|
||||
%small.light= milestone.expires_at
|
||||
.filter-item.inline
|
||||
%span.light
|
||||
%i.fa.fa-clock-o
|
||||
Milestone
|
||||
%strong
|
||||
= select_tag('milestone_id', projects_milestones_options, class: "select2 trigger-submit")
|
||||
|
||||
- if @project
|
||||
.dropdown.inline.prepend-left-10.labels-filter
|
||||
%button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"}
|
||||
%i.fa.fa-tags
|
||||
%span.light label:
|
||||
- if params[:label_name].present?
|
||||
%strong= params[:label_name]
|
||||
- else
|
||||
Any
|
||||
%b.caret
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to page_filter_path(label_name: nil) do
|
||||
Any
|
||||
- if @project.labels.any?
|
||||
- @project.labels.each do |label|
|
||||
%li
|
||||
= link_to page_filter_path(label_name: label.name) do
|
||||
= render_colored_label(label)
|
||||
- else
|
||||
%li
|
||||
= link_to generate_namespace_project_labels_path(@project.namespace, @project, redirect: request.original_url), method: :post do
|
||||
%i.fa.fa-plus-circle
|
||||
Create default labels
|
||||
- if @project
|
||||
.filter-item.inline
|
||||
%span.light
|
||||
%i.fa.fa-tag
|
||||
Label
|
||||
%strong
|
||||
= select_tag('label_name', project_labels_options(@project), class: "select2 trigger-submit")
|
||||
|
||||
.pull-right
|
||||
= render 'shared/sort_dropdown'
|
||||
.pull-right
|
||||
= render 'shared/sort_dropdown'
|
||||
|
||||
:coffeescript
|
||||
new UsersSelect()
|
||||
|
||||
$('form.filter-form').on 'submit', (event) ->
|
||||
event.preventDefault()
|
||||
Turbolinks.visit @.action + '&' + $(@).serialize()
|
||||
|
||||
@@ -8,6 +8,11 @@ Gitlab::Application.routes.draw do
|
||||
authorizations: 'oauth/authorizations'
|
||||
end
|
||||
|
||||
# Autocomplete
|
||||
get '/autocomplete/users' => 'autocomplete#users'
|
||||
get '/autocomplete/users/:id' => 'autocomplete#user'
|
||||
|
||||
|
||||
# Search
|
||||
get 'search' => 'search#show'
|
||||
get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete
|
||||
|
||||
@@ -10,10 +10,12 @@ Feature: Dashboard Issues
|
||||
Scenario: I should see assigned issues
|
||||
Then I should see issues assigned to me
|
||||
|
||||
@javascript
|
||||
Scenario: I should see authored issues
|
||||
When I click "Authored by me" link
|
||||
Then I should see issues authored by me
|
||||
|
||||
@javascript
|
||||
Scenario: I should see all issues
|
||||
When I click "All" link
|
||||
Then I should see all issues
|
||||
|
||||
@@ -10,10 +10,12 @@ Feature: Dashboard Merge Requests
|
||||
Scenario: I should see assigned merge_requests
|
||||
Then I should see merge requests assigned to me
|
||||
|
||||
@javascript
|
||||
Scenario: I should see authored merge_requests
|
||||
When I click "Authored by me" link
|
||||
Then I should see merge requests authored by me
|
||||
|
||||
@javascript
|
||||
Scenario: I should see all merge_requests
|
||||
When I click "All" link
|
||||
Then I should see all merge requests
|
||||
|
||||
@@ -8,11 +8,7 @@ Feature: Project Issues Filter Labels
|
||||
And project "Shop" has issue "Feature1" with labels: "feature"
|
||||
Given I visit project "Shop" issues page
|
||||
|
||||
Scenario: I should see project issues
|
||||
Then I should see "bug" in labels filter
|
||||
And I should see "feature" in labels filter
|
||||
And I should see "enhancement" in labels filter
|
||||
|
||||
@javascript
|
||||
Scenario: I filter by one label
|
||||
Given I click link "bug"
|
||||
Then I should see "Bugfix1" in issues list
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
|
||||
include SharedAuthentication
|
||||
include SharedPaths
|
||||
include Select2Helper
|
||||
|
||||
step 'I should see issues assigned to me' do
|
||||
should_see(assigned_issue)
|
||||
@@ -35,21 +36,13 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
|
||||
end
|
||||
|
||||
step 'I click "Authored by me" link' do
|
||||
within ".assignee-filter" do
|
||||
click_link "Any"
|
||||
end
|
||||
within ".author-filter" do
|
||||
click_link current_user.name
|
||||
end
|
||||
select2(current_user.id, from: "#author_id")
|
||||
select2(nil, from: "#assignee_id")
|
||||
end
|
||||
|
||||
step 'I click "All" link' do
|
||||
within ".author-filter" do
|
||||
click_link "Any"
|
||||
end
|
||||
within ".assignee-filter" do
|
||||
click_link "Any"
|
||||
end
|
||||
select2(nil, from: "#author_id")
|
||||
select2(nil, from: "#assignee_id")
|
||||
end
|
||||
|
||||
def should_see(issue)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
|
||||
include SharedAuthentication
|
||||
include SharedPaths
|
||||
include Select2Helper
|
||||
|
||||
step 'I should see merge requests assigned to me' do
|
||||
should_see(assigned_merge_request)
|
||||
@@ -39,21 +40,13 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
|
||||
end
|
||||
|
||||
step 'I click "Authored by me" link' do
|
||||
within ".assignee-filter" do
|
||||
click_link "Any"
|
||||
end
|
||||
within ".author-filter" do
|
||||
click_link current_user.name
|
||||
end
|
||||
select2(current_user.id, from: "#author_id")
|
||||
select2(nil, from: "#assignee_id")
|
||||
end
|
||||
|
||||
step 'I click "All" link' do
|
||||
within ".author-filter" do
|
||||
click_link "Any"
|
||||
end
|
||||
within ".assignee-filter" do
|
||||
click_link "Any"
|
||||
end
|
||||
select2(nil, from: "#author_id")
|
||||
select2(nil, from: "#assignee_id")
|
||||
end
|
||||
|
||||
def should_see(merge_request)
|
||||
|
||||
@@ -2,24 +2,7 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
|
||||
include SharedAuthentication
|
||||
include SharedProject
|
||||
include SharedPaths
|
||||
|
||||
step 'I should see "bug" in labels filter' do
|
||||
within ".labels-filter" do
|
||||
page.should have_content "bug"
|
||||
end
|
||||
end
|
||||
|
||||
step 'I should see "feature" in labels filter' do
|
||||
within ".labels-filter" do
|
||||
page.should have_content "feature"
|
||||
end
|
||||
end
|
||||
|
||||
step 'I should see "enhancement" in labels filter' do
|
||||
within ".labels-filter" do
|
||||
page.should have_content "enhancement"
|
||||
end
|
||||
end
|
||||
include Select2Helper
|
||||
|
||||
step 'I should see "Bugfix1" in issues list' do
|
||||
within ".issues-list" do
|
||||
@@ -46,9 +29,7 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
|
||||
end
|
||||
|
||||
step 'I click link "bug"' do
|
||||
within ".labels-filter" do
|
||||
click_link "bug"
|
||||
end
|
||||
select2('bug', from: "#label_name")
|
||||
end
|
||||
|
||||
step 'I click link "feature"' do
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe AutocompleteController do
|
||||
let!(:project) { create(:project) }
|
||||
let!(:user) { create(:user) }
|
||||
let!(:user2) { create(:user) }
|
||||
|
||||
context 'project members' do
|
||||
before do
|
||||
sign_in(user)
|
||||
project.team << [user, :master]
|
||||
|
||||
get(:users, project_id: project.id)
|
||||
end
|
||||
|
||||
let(:body) { JSON.parse(response.body) }
|
||||
|
||||
it { body.should be_kind_of(Array) }
|
||||
it { body.size.should eq(1) }
|
||||
it { body.first["username"].should == user.username }
|
||||
end
|
||||
|
||||
context 'group members' do
|
||||
let(:group) { create(:group) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
group.add_owner(user)
|
||||
|
||||
get(:users, group_id: group.id)
|
||||
end
|
||||
|
||||
let(:body) { JSON.parse(response.body) }
|
||||
|
||||
it { body.should be_kind_of(Array) }
|
||||
it { body.size.should eq(1) }
|
||||
it { body.first["username"].should == user.username }
|
||||
end
|
||||
|
||||
context 'all users' do
|
||||
before do
|
||||
sign_in(user)
|
||||
get(:users)
|
||||
end
|
||||
|
||||
let(:body) { JSON.parse(response.body) }
|
||||
|
||||
it { body.should be_kind_of(Array) }
|
||||
it { body.size.should eq(User.count) }
|
||||
end
|
||||
end
|
||||
@@ -95,7 +95,7 @@ describe 'Issues', feature: true do
|
||||
let(:issue) { @issue }
|
||||
|
||||
it 'should allow filtering by issues with no specified milestone' do
|
||||
visit namespace_project_issues_path(project.namespace, project, milestone_id: '0')
|
||||
visit namespace_project_issues_path(project.namespace, project, milestone_id: IssuableFinder::NONE)
|
||||
|
||||
expect(page).not_to have_content 'foobar'
|
||||
expect(page).to have_content 'barbaz'
|
||||
@@ -111,7 +111,7 @@ describe 'Issues', feature: true do
|
||||
end
|
||||
|
||||
it 'should allow filtering by issues with no specified assignee' do
|
||||
visit namespace_project_issues_path(project.namespace, project, assignee_id: '0')
|
||||
visit namespace_project_issues_path(project.namespace, project, assignee_id: IssuableFinder::NONE)
|
||||
|
||||
expect(page).to have_content 'foobar'
|
||||
expect(page).not_to have_content 'barbaz'
|
||||
|
||||
@@ -17,9 +17,9 @@ module Select2Helper
|
||||
selector = options[:from]
|
||||
|
||||
if options[:multiple]
|
||||
execute_script("$('#{selector}').select2('val', ['#{value}']);")
|
||||
execute_script("$('#{selector}').select2('val', ['#{value}'], true);")
|
||||
else
|
||||
execute_script("$('#{selector}').select2('val', '#{value}');")
|
||||
execute_script("$('#{selector}').select2('val', '#{value}', true);")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user