Merge branch 'issue-filter-name-options' into 'master'

Issuable filtering improvements

This improves the filtering of issues and merge requests by creating a single file that encapsulates all the filtering. Previously this was done with a file for issues and a file for merge requests.

Created the ability for the text search to be done alongside other filterables. Previously because this was outside the filterable form, this wasn't possible and would instead do either filter dropdown or text filter - not both. Fixes #4252 

Fixed issue with not being able to filter and sort issues without refreshing the page. Fixes #15269 

See merge request !3699
This commit is contained in:
Jacob Schatz 2016-05-25 19:44:06 +00:00
commit 0cc060bc5f
11 changed files with 203 additions and 71 deletions

View file

@ -16,7 +16,6 @@ class Dispatcher
shortcut_handler = null
switch page
when 'projects:issues:index'
Issues.init()
Issuable.init()
shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show'

View file

@ -1,7 +1,11 @@
issuable_created = false
@Issuable =
init: ->
Issuable.initTemplates()
Issuable.initSearch()
unless issuable_created
issuable_created = true
Issuable.initTemplates()
Issuable.initSearch()
Issuable.initChecks()
initTemplates: ->
Issuable.labelRow = _.template(
@ -19,7 +23,16 @@
.on 'keyup', ->
clearTimeout(@timer)
@timer = setTimeout( ->
Issuable.filterResults $('#issue_search_form')
$search = $('#issue_search')
$form = $('.js-filter-form')
$input = $("input[name='#{$search.attr('name')}']", $form)
if $input.length is 0
$form.append "<input type='hidden' name='#{$search.attr('name')}' value='#{_.escape($search.val())}'/>"
else
$input.val $search.val()
Issuable.filterResults $form
, 500)
toggleLabelFilters: ->
@ -59,15 +72,22 @@
dataType: "json"
reload: ->
if Issues.created
Issues.initChecks()
if Issuable.created
Issuable.initChecks()
$('#filter_issue_search').val($('#issue_search').val())
initChecks: ->
$('.check_all_issues').on 'click', ->
$('.selected_issue').prop('checked', @checked)
Issuable.checkChanged()
$('.selected_issue').on 'change', Issuable.checkChanged
updateStateFilters: ->
stateFilters = $('.issues-state-filters')
stateFilters = $('.issues-state-filters, .dropdown-menu-sort')
newParams = {}
paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search']
paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search', 'issue_search']
for paramKey in paramKeys
newParams[paramKey] = gl.utils.getParameterValues(paramKey)[0] or ''
@ -82,3 +102,17 @@
else
newUrl = gl.utils.mergeUrlParams(newParams, initialUrl)
$(this).attr 'href', newUrl
checkChanged: ->
checked_issues = $('.selected_issue:checked')
if checked_issues.length > 0
ids = $.map checked_issues, (value) ->
$(value).data('id')
$('#update_issues_ids').val ids
$('.issues-other-filters').hide()
$('.issues_bulk_update').show()
else
$('#update_issues_ids').val []
$('.issues_bulk_update').hide()
$('.issues-other-filters').show()

View file

@ -1,38 +0,0 @@
@Issues =
init: ->
Issues.created = true
Issues.initChecks()
$("body").on "ajax:success", ".close_issue, .reopen_issue", ->
t = $(this)
totalIssues = undefined
reopen = t.hasClass("reopen_issue")
$(".issue_counter").each ->
issue = $(this)
totalIssues = parseInt($(this).html(), 10)
if reopen and issue.closest(".main_menu").length
$(this).html totalIssues + 1
else
$(this).html totalIssues - 1
initChecks: ->
$(".check_all_issues").click ->
$(".selected_issue").prop("checked", @checked)
Issues.checkChanged()
$(".selected_issue").bind "change", Issues.checkChanged
checkChanged: ->
checked_issues = $(".selected_issue:checked")
if checked_issues.length > 0
ids = []
$.each checked_issues, (index, value) ->
ids.push $(value).attr("data-id")
$("#update_issues_ids").val ids
$(".issues-other-filters").hide()
$(".issues_bulk_update").show()
else
$("#update_issues_ids").val []
$(".issues_bulk_update").hide()
$(".issues-other-filters").show()

View file

@ -26,10 +26,19 @@
newUrl = decodeURIComponent(url)
for paramName, paramValue of params
pattern = new RegExp "\\b(#{paramName}=).*?(&|$)"
if url.search(pattern) >= 0
if not paramValue?
newUrl = newUrl.replace pattern, ''
else if url.search(pattern) isnt -1
newUrl = newUrl.replace pattern, "$1#{paramValue}$2"
else
newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}"
# Remove a trailing ampersand
lastChar = newUrl[newUrl.length - 1]
if lastChar is '&'
newUrl = newUrl.slice 0, -1
newUrl
# removes parameter query string from url. returns the modified url

View file

@ -119,7 +119,7 @@
}
input {
height: 34px;
height: 35px;
display: inline-block;
position: relative;
top: 2px;

View file

@ -40,11 +40,6 @@
}
}
.issue-search-form {
margin: 0;
height: 24px;
}
form.edit-issue {
margin: 0;
}

View file

@ -262,6 +262,8 @@ module ApplicationHelper
assignee_id: params[:assignee_id],
author_id: params[:author_id],
sort: params[:sort],
issue_search: params[:issue_search],
label_name: params[:label_name]
}
options = exist_opts.merge(options)
@ -272,16 +274,11 @@ module ApplicationHelper
end
end
path = request.path
path << "?#{options.to_param}"
if add_label
if params[:label_name].present? and params[:label_name].respond_to?('any?')
params[:label_name].each do |label|
path << "&label_name[]=#{label}"
end
end
end
path
params = options.compact
params.delete(:label_name) unless add_label
"#{request.path}?#{params.to_param}"
end
def outdated_browser?

View file

@ -6,7 +6,7 @@
- else
= sort_title_recently_created
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
%li
= link_to page_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created

View file

@ -1,6 +1,8 @@
.issues-filters
.issues-details-filters.row-content-block.second-block
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search]), method: :get, class: 'filter-form js-filter-form' do
- if params[:issue_search].present?
= hidden_field_tag :issue_search, params[:issue_search]
- if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
.check-all-holder
= check_box_tag "check_all_issues", nil, false,

View file

@ -1,8 +1,2 @@
= form_tag(path, method: :get, id: "issue_search_form", class: 'issue-search-form') do
= search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by name ...', class: 'form-control issue_search search-text-input input-short', spellcheck: false }
= hidden_field_tag :state, params['state']
= hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id']
= hidden_field_tag :author_id, params['author_id']
= hidden_field_tag :milestone_id, params['milestone_id']
= hidden_field_tag :label_id, params['label_id']

View file

@ -154,4 +154,144 @@ describe 'Filter issues', feature: true do
end
end
end
describe 'filter issues by text' do
before do
create(:issue, title: "Bug", project: project)
bug_label = create(:label, project: project, title: 'bug')
milestone = create(:milestone, title: "8", project: project)
issue = create(:issue,
title: "Bug 2",
project: project,
milestone: milestone,
author: user,
assignee: user)
issue.labels << bug_label
visit namespace_project_issues_path(project.namespace, project)
end
context 'only text', js: true do
it 'should filter issues by searched text' do
fill_in 'issue_search', with: 'Bug'
page.within '.issues-list' do
expect(page).to have_selector('.issue', count: 2)
end
end
it 'should not show any issues' do
fill_in 'issue_search', with: 'testing'
page.within '.issues-list' do
expect(page).to_not have_selector('.issue')
end
end
end
context 'text and dropdown options', js: true do
it 'should filter by text and label' do
fill_in 'issue_search', with: 'Bug'
page.within '.issues-list' do
expect(page).to have_selector('.issue', count: 2)
end
click_button 'Label'
page.within '.labels-filter' do
click_link 'bug'
end
page.within '.issues-list' do
expect(page).to have_selector('.issue', count: 1)
end
end
it 'should filter by text and milestone' do
fill_in 'issue_search', with: 'Bug'
page.within '.issues-list' do
expect(page).to have_selector('.issue', count: 2)
end
click_button 'Milestone'
page.within '.milestone-filter' do
click_link '8'
end
page.within '.issues-list' do
expect(page).to have_selector('.issue', count: 1)
end
end
it 'should filter by text and assignee' do
fill_in 'issue_search', with: 'Bug'
page.within '.issues-list' do
expect(page).to have_selector('.issue', count: 2)
end
click_button 'Assignee'
page.within '.dropdown-menu-assignee' do
click_link user.name
end
page.within '.issues-list' do
expect(page).to have_selector('.issue', count: 1)
end
end
it 'should filter by text and author' do
fill_in 'issue_search', with: 'Bug'
page.within '.issues-list' do
expect(page).to have_selector('.issue', count: 2)
end
click_button 'Author'
page.within '.dropdown-menu-author' do
click_link user.name
end
page.within '.issues-list' do
expect(page).to have_selector('.issue', count: 1)
end
end
end
end
describe 'filter issues and sort', js: true do
before do
bug_label = create(:label, project: project, title: 'bug')
bug_one = create(:issue, title: "Frontend", project: project)
bug_two = create(:issue, title: "Bug 2", project: project)
bug_one.labels << bug_label
bug_two.labels << bug_label
visit namespace_project_issues_path(project.namespace, project)
end
it 'should be able to filter and sort issues' do
click_button 'Label'
page.within '.labels-filter' do
click_link 'bug'
end
page.within '.issues-list' do
expect(page).to have_selector('.issue', count: 2)
end
click_button 'Last created'
page.within '.dropdown-menu-sort' do
click_link 'Oldest created'
end
page.within '.issues-list' do
expect(first('.issue')).to have_content('Frontend')
end
end
end
end