Commit d6c8eefb authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Big refactoring of issues filters

* 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
parent 1c2711f7
@Api = @Api =
groups_path: "/api/:version/groups.json" groups_path: "/api/:version/groups.json"
group_path: "/api/:version/groups/:id.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" 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) -> group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path) url = Api.buildUrl(Api.group_path)
...@@ -80,23 +30,6 @@ ...@@ -80,23 +30,6 @@
).done (groups) -> ).done (groups) ->
callback(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 # Return namespaces list. Filtered by query
namespaces: (query, callback) -> namespaces: (query, callback) ->
url = Api.buildUrl(Api.namespaces_path) url = Api.buildUrl(Api.namespaces_path)
......
...@@ -127,7 +127,7 @@ class Dispatcher ...@@ -127,7 +127,7 @@ class Dispatcher
when 'show' when 'show'
new ProjectShow() new ProjectShow()
when 'issues', 'merge_requests' when 'issues', 'merge_requests'
new ProjectUsersSelect() new UsersSelect()
when 'wikis' when 'wikis'
new Wikis() new Wikis()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
......
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
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
.select2-drop-active { .select2-drop-active {
border: 1px solid #BBB !important; border: 1px solid #BBB !important;
margin-top: 4px; margin-top: 4px;
font-size: 13px;
&.select2-drop-above { &.select2-drop-above {
margin-bottom: 8px; margin-bottom: 8px;
...@@ -106,3 +107,7 @@ ...@@ -106,3 +107,7 @@
font-weight: bolder; font-weight: bolder;
} }
} }
.ajax-users-dropdown {
min-width: 225px !important;
}
...@@ -45,3 +45,11 @@ ...@@ -45,3 +45,11 @@
.btn { font-size: 13px; } .btn { font-size: 13px; }
} }
.filter-item {
margin-right: 15px;
> span {
margin-right: 4px;
}
}
...@@ -60,6 +60,7 @@ ...@@ -60,6 +60,7 @@
} }
@media (min-width: 800px) { @media (min-width: 800px) {
.issues-filters,
.issues_bulk_update { .issues_bulk_update {
select, .select2-container { select, .select2-container {
width: 120px !important; width: 120px !important;
...@@ -69,14 +70,16 @@ ...@@ -69,14 +70,16 @@
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
.issues-filters,
.issues_bulk_update { .issues_bulk_update {
select, .select2-container { select, .select2-container {
width: 160px !important; width: 140px !important;
display: inline-block; display: inline-block;
} }
} }
} }
.issues-filters,
.issues_bulk_update { .issues_bulk_update {
.select2-container .select2-choice { .select2-container .select2-choice {
color: #444 !important; color: #444 !important;
......
...@@ -275,7 +275,9 @@ module ApplicationHelper ...@@ -275,7 +275,9 @@ module ApplicationHelper
'https://' + promo_host 'https://' + promo_host
end end
def page_filter_path(options={}) def page_filter_path(options = {})
without = options.delete(:without)
exist_opts = { exist_opts = {
state: params[:state], state: params[:state],
scope: params[:scope], scope: params[:scope],
...@@ -288,6 +290,12 @@ module ApplicationHelper ...@@ -288,6 +290,12 @@ module ApplicationHelper
options = exist_opts.merge(options) options = exist_opts.merge(options)
if without.present?
without.each do |key|
options.delete(key)
end
end
path = request.path path = request.path
path << "?#{options.to_param}" path << "?#{options.to_param}"
path path
......
...@@ -47,4 +47,9 @@ module LabelsHelper ...@@ -47,4 +47,9 @@ module LabelsHelper
"#FFF" "#FFF"
end end
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 end
...@@ -19,4 +19,16 @@ module MilestonesHelper ...@@ -19,4 +19,16 @@ module MilestonesHelper
content_tag :div, nil, options content_tag :div, nil, options
end end
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 end
...@@ -4,18 +4,27 @@ module SelectsHelper ...@@ -4,18 +4,27 @@ module SelectsHelper
css_class << "multiselect " if opts[:multiple] css_class << "multiselect " if opts[:multiple]
css_class << (opts[:class] || '') css_class << (opts[:class] || '')
value = opts[:selected] || '' value = opts[:selected] || ''
placeholder = opts[:placeholder] || 'Search for a user'
hidden_field_tag(id, value, class: css_class) null_user = opts[:null_user] || false
any_user = opts[:any_user] || false
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 end
def project_users_select_tag(id, opts = {}) hidden_field_tag(id, value, html)
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)
end end
def groups_select_tag(id, opts = {}) def groups_select_tag(id, opts = {})
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
%i.fa.fa-user %i.fa.fa-user
Assign to Assign to
.col-sm-10 .col-sm-10
= project_users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
placeholder: 'Select a user', class: 'custom-form-control', placeholder: 'Select a user', class: 'custom-form-control',
selected: issuable.assignee_id) selected: issuable.assignee_id)
&nbsp; &nbsp;
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
- else - else
none none
- if can?(current_user, :modify_issue, @issue) - 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 %div.prepend-top-20.clearfix
.issuable-context-title .issuable-context-title
...@@ -44,5 +44,3 @@ ...@@ -44,5 +44,3 @@
:coffeescript :coffeescript
new Subscription("#{toggle_subscription_namespace_project_issue_path(@issue.project.namespace, @project, @issue)}") new Subscription("#{toggle_subscription_namespace_project_issue_path(@issue.project.namespace, @project, @issue)}")
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
.issues_bulk_update.hide .issues_bulk_update.hide
= form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do = 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') = 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") = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
= hidden_field_tag 'update[issues_ids]', [] = hidden_field_tag 'update[issues_ids]', []
= hidden_field_tag :state_event, params[:state_event] = hidden_field_tag :state_event, params[:state_event]
......
...@@ -13,5 +13,5 @@ ...@@ -13,5 +13,5 @@
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true}) $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
$('.edit-issue.inline-update input[type="submit"]').hide(); $('.edit-issue.inline-update input[type="submit"]').hide();
new ProjectUsersSelect(); new UsersSelect()
new Issue(); new Issue();
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
%i.fa.fa-user %i.fa.fa-user
Assign to Assign to
.col-sm-10 .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)
&nbsp; &nbsp;
= link_to 'Assign to me', '#', class: 'btn assign-to-me-link' = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
.form-group .form-group
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
none none
.issuable-context-selectbox .issuable-context-selectbox
- if can?(current_user, :modify_merge_request, @merge_request) - 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 %div.prepend-top-20.clearfix
.issuable-context-title .issuable-context-title
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
$('.context').html("#{escape_javascript(render partial: 'projects/merge_requests/show/context', locals: { issue: @issue })}"); $('.context').html("#{escape_javascript(render partial: 'projects/merge_requests/show/context', locals: { issue: @issue })}");
$('.context').effect('highlight'); $('.context').effect('highlight');
new ProjectUsersSelect(); new UsersSelect()
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true}); $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true});
merge_request = new MergeRequest(); merge_request = new MergeRequest();
= 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_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 .form-group
= f.label :user_ids, "People", class: 'control-label' = 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 .form-group
= f.label :access_level, "Project Access", class: 'control-label' = f.label :access_level, "Project Access", class: 'control-label'
......
...@@ -15,105 +15,50 @@ ...@@ -15,105 +15,50 @@
All All
%div %div
= 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' - if controller.controller_name == 'issues'
.check-all-holder .check-all-holder
= check_box_tag "check_all_issues", nil, false, = check_box_tag "check_all_issues", nil, false,
class: "check_all_issues left", class: "check_all_issues left",
disabled: !can?(current_user, :modify_issue, @project) disabled: !can?(current_user, :modify_issue, @project)
.issues-other-filters .issues-other-filters
.dropdown.inline.assignee-filter .filter-item.inline
%button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"} %span.light
%i.fa.fa-user %i.fa.fa-user
%span.light assignee: Assignee
- if @assignee.present? %strong
%strong= @assignee.name = users_select_tag(:assignee_id, selected: params[:assignee_id],
- elsif params[:assignee_id] == "0" placeholder: 'Any', class: 'trigger-submit', any_user: true, null_user: true)
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
.dropdown.inline.prepend-left-10.author-filter .filter-item.inline
%button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"} %span.light
%i.fa.fa-user %i.fa.fa-user
%span.light author: Author
- if @author.present? %strong
%strong= @author.name = users_select_tag(:author_id, selected: params[:author_id],
- elsif params[:author_id] == "0" placeholder: 'Any', class: 'trigger-submit', any_user: true)
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
.dropdown.inline.prepend-left-10.milestone-filter .filter-item.inline
%button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"} %span.light
%i.fa.fa-clock-o %i.fa.fa-clock-o
%span.light milestone: Milestone
- if @milestone.present? %strong
%strong= @milestone.title = select_tag('milestone_id', projects_milestones_options, class: "select2 trigger-submit")
- 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
- if @project - if @project
.dropdown.inline.prepend-left-10.labels-filter .filter-item.inline
%button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"} %span.light
%i.fa.fa-tags %i.fa.fa-tag
%span.light label: Label
- if params[:label_name].present? %strong
%strong= params[:label_name] = select_tag('label_name', project_labels_options(@project), class: "select2 trigger-submit")
- 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
.pull-right .pull-right
= render 'shared/sort_dropdown' = render 'shared/sort_dropdown'
:coffeescript
new UsersSelect()
$('form.filter-form').on 'submit', (event) ->
event.preventDefault()
Turbolinks.visit @.action + '&' + $(@).serialize()
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