Merge branch 'multi-filter-labels' into 'master'
Mutliple label filter Fixes #989 See merge request !3438
This commit is contained in:
commit
692c35e6f4
27 changed files with 447 additions and 137 deletions
|
@ -17,6 +17,7 @@ class Dispatcher
|
|||
switch page
|
||||
when 'projects:issues:index'
|
||||
Issues.init()
|
||||
Issuable.init()
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'projects:issues:show'
|
||||
new Issue()
|
||||
|
@ -57,7 +58,7 @@ class Dispatcher
|
|||
new ZenMode()
|
||||
when 'projects:merge_requests:index'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
MergeRequests.init()
|
||||
Issuable.init()
|
||||
when 'dashboard:activity'
|
||||
new Activities()
|
||||
when 'dashboard:projects:starred'
|
||||
|
|
|
@ -386,13 +386,13 @@ class GitLabDropdown
|
|||
else
|
||||
selectedObject
|
||||
else
|
||||
if !value?
|
||||
field.remove()
|
||||
|
||||
if not @options.multiSelect
|
||||
if not @options.multiSelect or el.hasClass('dropdown-clear-active')
|
||||
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
|
||||
@dropdown.parent().find("input[name='#{fieldName}']").remove()
|
||||
|
||||
if !value?
|
||||
field.remove()
|
||||
|
||||
# Toggle active class for the tick mark
|
||||
el.addClass ACTIVE_CLASS
|
||||
|
||||
|
|
84
app/assets/javascripts/issuable.js.coffee
Normal file
84
app/assets/javascripts/issuable.js.coffee
Normal file
|
@ -0,0 +1,84 @@
|
|||
@Issuable =
|
||||
init: ->
|
||||
Issuable.initTemplates()
|
||||
Issuable.initSearch()
|
||||
|
||||
initTemplates: ->
|
||||
Issuable.labelRow = _.template(
|
||||
'<% _.each(labels, function(label){ %>
|
||||
<span class="label-row">
|
||||
<a href="#"><span class="label color-label has-tooltip" style="background-color: <%= label.color %>; color: <%= label.text_color %>" title="<%= _.escape(label.description) %>" data-container="body"><%= _.escape(label.title) %></span></a>
|
||||
</span>
|
||||
<% }); %>'
|
||||
)
|
||||
|
||||
initSearch: ->
|
||||
@timer = null
|
||||
$('#issue_search')
|
||||
.off 'keyup'
|
||||
.on 'keyup', ->
|
||||
clearTimeout(@timer)
|
||||
@timer = setTimeout( ->
|
||||
Issuable.filterResults $('#issue_search_form')
|
||||
, 500)
|
||||
|
||||
toggleLabelFilters: ->
|
||||
$filteredLabels = $('.filtered-labels')
|
||||
if $filteredLabels.find('.label-row').length > 0
|
||||
$filteredLabels.removeClass('hidden')
|
||||
else
|
||||
$filteredLabels.addClass('hidden')
|
||||
|
||||
filterResults: (form) =>
|
||||
formData = form.serialize()
|
||||
|
||||
$('.issues-holder, .merge-requests-holder').css('opacity', '0.5')
|
||||
formAction = form.attr('action')
|
||||
issuesUrl = formAction
|
||||
issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}")
|
||||
issuesUrl += formData
|
||||
$.ajax
|
||||
type: 'GET'
|
||||
url: formAction
|
||||
data: formData
|
||||
complete: ->
|
||||
$('.issues-holder, .merge-requests-holder').css('opacity', '1.0')
|
||||
success: (data) ->
|
||||
$('.issues-holder, .merge-requests-holder').html(data.html)
|
||||
# Change url so if user reload a page - search results are saved
|
||||
history.replaceState {page: issuesUrl}, document.title, issuesUrl
|
||||
Issuable.reload()
|
||||
Issuable.updateStateFilters()
|
||||
$filteredLabels = $('.filtered-labels')
|
||||
|
||||
if typeof Issuable.labelRow is 'function'
|
||||
$filteredLabels.html(Issuable.labelRow(data))
|
||||
|
||||
Issuable.toggleLabelFilters()
|
||||
|
||||
dataType: "json"
|
||||
|
||||
reload: ->
|
||||
if Issues.created
|
||||
Issues.initChecks()
|
||||
|
||||
$('#filter_issue_search').val($('#issue_search').val())
|
||||
|
||||
updateStateFilters: ->
|
||||
stateFilters = $('.issues-state-filters')
|
||||
newParams = {}
|
||||
paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search']
|
||||
|
||||
for paramKey in paramKeys
|
||||
newParams[paramKey] = gl.utils.getParameterValues(paramKey)[0] or ''
|
||||
|
||||
if stateFilters.length
|
||||
stateFilters.find('a').each ->
|
||||
initialUrl = gl.utils.removeParamQueryString($(this).attr('href'), 'label_name[]')
|
||||
labelNameValues = gl.utils.getParameterValues('label_name[]')
|
||||
if labelNameValues
|
||||
labelNameQueryString = ("label_name[]=#{value}" for value in labelNameValues).join('&')
|
||||
newUrl = "#{gl.utils.mergeUrlParams(newParams, initialUrl)}&#{labelNameQueryString}"
|
||||
else
|
||||
newUrl = gl.utils.mergeUrlParams(newParams, initialUrl)
|
||||
$(this).attr 'href', newUrl
|
|
@ -1,6 +1,6 @@
|
|||
@Issues =
|
||||
init: ->
|
||||
Issues.initSearch()
|
||||
Issues.created = true
|
||||
Issues.initChecks()
|
||||
|
||||
$("body").on "ajax:success", ".close_issue, .reopen_issue", ->
|
||||
|
@ -15,10 +15,6 @@
|
|||
else
|
||||
$(this).html totalIssues - 1
|
||||
|
||||
reload: ->
|
||||
Issues.initChecks()
|
||||
$('#filter_issue_search').val($('#issue_search').val())
|
||||
|
||||
initChecks: ->
|
||||
$(".check_all_issues").click ->
|
||||
$(".selected_issue").prop("checked", @checked)
|
||||
|
@ -26,51 +22,6 @@
|
|||
|
||||
$(".selected_issue").bind "change", Issues.checkChanged
|
||||
|
||||
# Update state filters if present in page
|
||||
updateStateFilters: ->
|
||||
stateFilters = $('.issues-state-filters')
|
||||
newParams = {}
|
||||
paramKeys = ['author_id', 'label_name', 'milestone_title', 'assignee_id', 'issue_search']
|
||||
|
||||
for paramKey in paramKeys
|
||||
newParams[paramKey] = gl.utils.getUrlParameter(paramKey) or ''
|
||||
|
||||
if stateFilters.length
|
||||
stateFilters.find('a').each ->
|
||||
initialUrl = $(this).attr 'href'
|
||||
$(this).attr 'href', gl.utils.mergeUrlParams(newParams, initialUrl)
|
||||
|
||||
# Make sure we trigger ajax request only after user stop typing
|
||||
initSearch: ->
|
||||
@timer = null
|
||||
$("#issue_search").keyup ->
|
||||
clearTimeout(@timer)
|
||||
@timer = setTimeout( ->
|
||||
Issues.filterResults $("#issue_search_form")
|
||||
, 500)
|
||||
|
||||
filterResults: (form) =>
|
||||
$('.issues-holder, .merge-requests-holder').css("opacity", '0.5')
|
||||
formAction = form.attr('action')
|
||||
formData = form.serialize()
|
||||
issuesUrl = formAction
|
||||
issuesUrl += ("#{if formAction.indexOf("?") < 0 then '?' else '&'}")
|
||||
issuesUrl += formData
|
||||
|
||||
$.ajax
|
||||
type: "GET"
|
||||
url: formAction
|
||||
data: formData
|
||||
complete: ->
|
||||
$('.issues-holder, .merge-requests-holder').css("opacity", '1.0')
|
||||
success: (data) ->
|
||||
$('.issues-holder, .merge-requests-holder').html(data.html)
|
||||
# Change url so if user reload a page - search results are saved
|
||||
history.replaceState {page: issuesUrl}, document.title, issuesUrl
|
||||
Issues.reload()
|
||||
Issues.updateStateFilters()
|
||||
dataType: "json"
|
||||
|
||||
checkChanged: ->
|
||||
checked_issues = $(".selected_issue:checked")
|
||||
if checked_issues.length > 0
|
||||
|
|
|
@ -6,7 +6,7 @@ class @LabelsSelect
|
|||
labelUrl = $dropdown.data('labels')
|
||||
issueUpdateURL = $dropdown.data('issueUpdate')
|
||||
selectedLabel = $dropdown.data('selected')
|
||||
if selectedLabel?
|
||||
if selectedLabel? and not $dropdown.hasClass 'js-multiselect'
|
||||
selectedLabel = selectedLabel.split(',')
|
||||
newLabelField = $('#new_label_name')
|
||||
newColorField = $('#new_label_color')
|
||||
|
@ -16,6 +16,7 @@ class @LabelsSelect
|
|||
abilityName = $dropdown.data('ability-name')
|
||||
$selectbox = $dropdown.closest('.selectbox')
|
||||
$block = $selectbox.closest('.block')
|
||||
$form = $dropdown.closest('form')
|
||||
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
|
||||
$value = $block.find('.value')
|
||||
$loading = $block.find('.block-loading').fadeOut()
|
||||
|
@ -171,7 +172,7 @@ class @LabelsSelect
|
|||
.find('a')
|
||||
.each((i) ->
|
||||
setTimeout(=>
|
||||
glAnimate($(@), 'pulse')
|
||||
gl.animate.animate($(@), 'pulse')
|
||||
,200 * i
|
||||
)
|
||||
)
|
||||
|
@ -200,16 +201,21 @@ class @LabelsSelect
|
|||
callback data
|
||||
|
||||
renderRow: (label) ->
|
||||
selectedClass = ''
|
||||
if $selectbox.find("input[type='hidden']\
|
||||
[name='#{$dropdown.data('field-name')}']\
|
||||
[value='#{label.id}']").length
|
||||
selectedClass = 'is-active'
|
||||
removesAll = label.id is 0 or not label.id?
|
||||
|
||||
selectedClass = []
|
||||
if $form.find("input[type='hidden']\
|
||||
[name='#{$dropdown.data('fieldName')}']\
|
||||
[value='#{this.id(label)}']").length
|
||||
selectedClass.push 'is-active'
|
||||
|
||||
if $dropdown.hasClass('js-multiselect') and removesAll
|
||||
selectedClass.push 'dropdown-clear-active'
|
||||
|
||||
color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
|
||||
|
||||
"<li>
|
||||
<a href='#' class='#{selectedClass}'>
|
||||
<a href='#' class='#{selectedClass.join(' ')}'>
|
||||
#{color}
|
||||
#{_.escape(label.title)}
|
||||
</a>
|
||||
|
@ -219,37 +225,56 @@ class @LabelsSelect
|
|||
fields: ['title']
|
||||
selectable: true
|
||||
|
||||
toggleLabel: (selected) ->
|
||||
toggleLabel: (selected, el) ->
|
||||
selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active')
|
||||
|
||||
if selected and selected.title?
|
||||
selected.title
|
||||
if selected_labels.length > 1
|
||||
"#{selected.title} +#{selected_labels.length - 1} more"
|
||||
else
|
||||
selected.title
|
||||
else if not selected and selected_labels.length isnt 0
|
||||
if selected_labels.length > 1
|
||||
"#{$(selected_labels[0]).text()} +#{selected_labels.length - 1} more"
|
||||
else if selected_labels.length is 1
|
||||
$(selected_labels).text()
|
||||
else
|
||||
defaultLabel
|
||||
fieldName: $dropdown.data('field-name')
|
||||
id: (label) ->
|
||||
if label.isAny?
|
||||
''
|
||||
else if $dropdown.hasClass "js-filter-submit"
|
||||
if $dropdown.hasClass("js-filter-submit") and not label.isAny?
|
||||
label.title
|
||||
else
|
||||
label.id
|
||||
|
||||
hidden: ->
|
||||
page = $('body').data 'page'
|
||||
isIssueIndex = page is 'projects:issues:index'
|
||||
isMRIndex = page is 'projects:merge_requests:index'
|
||||
|
||||
$selectbox.hide()
|
||||
# display:block overrides the hide-collapse rule
|
||||
$value.removeAttr('style')
|
||||
if $dropdown.hasClass 'js-multiselect'
|
||||
saveLabelData()
|
||||
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
|
||||
selectedLabels = $dropdown
|
||||
.closest('form')
|
||||
.find("input:hidden[name='#{$dropdown.data('fieldName')}']")
|
||||
Issuable.filterResults $dropdown.closest('form')
|
||||
else if $dropdown.hasClass('js-filter-submit')
|
||||
$dropdown.closest('form').submit()
|
||||
else
|
||||
saveLabelData()
|
||||
|
||||
multiSelect: $dropdown.hasClass 'js-multiselect'
|
||||
clicked: (label) ->
|
||||
page = $('body').data 'page'
|
||||
isIssueIndex = page is 'projects:issues:index'
|
||||
isMRIndex = page is page is 'projects:merge_requests:index'
|
||||
|
||||
isMRIndex = page is 'projects:merge_requests:index'
|
||||
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
|
||||
selectedLabel = label.title
|
||||
|
||||
Issues.filterResults $dropdown.closest('form')
|
||||
if not $dropdown.hasClass 'js-multiselect'
|
||||
selectedLabel = label.title
|
||||
Issuable.filterResults $dropdown.closest('form')
|
||||
else if $dropdown.hasClass 'js-filter-submit'
|
||||
$dropdown.closest('form').submit()
|
||||
else
|
||||
|
|
|
@ -1,13 +1,39 @@
|
|||
((w) ->
|
||||
if not w.gl? then w.gl = {}
|
||||
if not gl.animate? then gl.animate = {}
|
||||
|
||||
w.glAnimate = ($el, animation, done) ->
|
||||
gl.animate.animate = ($el, animation, options, done) ->
|
||||
if options?.cssStart?
|
||||
$el.css(options.cssStart)
|
||||
$el
|
||||
.removeClass()
|
||||
.removeClass(animation + ' animated')
|
||||
.addClass(animation + ' animated')
|
||||
.one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', ->
|
||||
$(this).removeClass()
|
||||
$(this).removeClass(animation + ' animated')
|
||||
if done?
|
||||
done()
|
||||
if options?.cssEnd?
|
||||
$el.css(options.cssEnd)
|
||||
return
|
||||
return
|
||||
return
|
||||
|
||||
gl.animate.animateEach = ($els, animation, time, options, done) ->
|
||||
dfd = $.Deferred()
|
||||
if not $els.length
|
||||
dfd.resolve()
|
||||
$els.each((i) ->
|
||||
setTimeout(=>
|
||||
$this = $(@)
|
||||
gl.animate.animate($this, animation, options, =>
|
||||
if i is $els.length - 1
|
||||
dfd.resolve()
|
||||
if done?
|
||||
done()
|
||||
)
|
||||
,time * i
|
||||
)
|
||||
return
|
||||
)
|
||||
return dfd.promise()
|
||||
return
|
||||
) window
|
|
@ -3,16 +3,20 @@
|
|||
w.gl ?= {}
|
||||
w.gl.utils ?= {}
|
||||
|
||||
w.gl.utils.getUrlParameter = (sParam) ->
|
||||
# Returns an array containing the value(s) of the
|
||||
# of the key passed as an argument
|
||||
w.gl.utils.getParameterValues = (sParam) ->
|
||||
sPageURL = decodeURIComponent(window.location.search.substring(1))
|
||||
sURLVariables = sPageURL.split('&')
|
||||
sParameterName = undefined
|
||||
values = []
|
||||
i = 0
|
||||
while i < sURLVariables.length
|
||||
sParameterName = sURLVariables[i].split('=')
|
||||
if sParameterName[0] is sParam
|
||||
return if sParameterName[1] is undefined then true else sParameterName[1]
|
||||
values.push(sParameterName[1])
|
||||
i++
|
||||
values
|
||||
|
||||
# #
|
||||
# @param {Object} params - url keys and value to merge
|
||||
|
@ -28,4 +32,12 @@
|
|||
newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}"
|
||||
newUrl
|
||||
|
||||
# removes parameter query string from url. returns the modified url
|
||||
w.gl.utils.removeParamQueryString = (url, param) ->
|
||||
url = decodeURIComponent(url)
|
||||
urlVariables = url.split('&')
|
||||
(
|
||||
variables for variables in urlVariables when variables.indexOf(param) is -1
|
||||
).join('&')
|
||||
|
||||
) window
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
#
|
||||
# * Filter merge requests
|
||||
#
|
||||
@MergeRequests =
|
||||
init: ->
|
||||
MergeRequests.initSearch()
|
||||
|
||||
# Make sure we trigger ajax request only after user stop typing
|
||||
initSearch: ->
|
||||
@timer = null
|
||||
$("#issue_search").keyup ->
|
||||
clearTimeout(@timer)
|
||||
@timer = setTimeout(MergeRequests.filterResults, 500)
|
||||
|
||||
filterResults: =>
|
||||
form = $("#issue_search_form")
|
||||
search = $("#issue_search").val()
|
||||
$('.merge-requests-holder').css("opacity", '0.5')
|
||||
issues_url = form.attr('action') + '?' + form.serialize()
|
||||
|
||||
$.ajax
|
||||
type: "GET"
|
||||
url: form.attr('action')
|
||||
data: form.serialize()
|
||||
complete: ->
|
||||
$('.merge-requests-holder').css("opacity", '1.0')
|
||||
success: (data) ->
|
||||
$('.merge-requests-holder').html(data.html)
|
||||
# Change url so if user reload a page - search results are saved
|
||||
history.replaceState {page: issues_url}, document.title, issues_url
|
||||
MergeRequests.reload()
|
||||
dataType: "json"
|
||||
|
||||
reload: ->
|
||||
$('#filter_issue_search').val($('#issue_search').val())
|
|
@ -97,7 +97,7 @@ class @MilestoneSelect
|
|||
selectedMilestone = selected.name
|
||||
else
|
||||
selectedMilestone = ''
|
||||
Issues.filterResults $dropdown.closest('form')
|
||||
Issuable.filterResults $dropdown.closest('form')
|
||||
else if $dropdown.hasClass('js-filter-submit')
|
||||
$dropdown.closest('form').submit()
|
||||
else
|
||||
|
|
|
@ -158,7 +158,7 @@ class @UsersSelect
|
|||
|
||||
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
|
||||
selectedId = user.id
|
||||
Issues.filterResults $dropdown.closest('form')
|
||||
Issuable.filterResults $dropdown.closest('form')
|
||||
else if $dropdown.hasClass 'js-filter-submit'
|
||||
$dropdown.closest('form').submit()
|
||||
else
|
||||
|
|
|
@ -33,14 +33,15 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
@issues = @issues.page(params[:page])
|
||||
@label = @project.labels.find_by(title: params[:label_name])
|
||||
@labels = @project.labels.where(title: params[:label_name])
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.atom { render layout: false }
|
||||
format.json do
|
||||
render json: {
|
||||
html: view_to_html_string("projects/issues/_issues")
|
||||
html: view_to_html_string("projects/issues/_issues"),
|
||||
labels: @labels.as_json(methods: :text_color)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,13 +38,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
@merge_requests = @merge_requests.page(params[:page])
|
||||
@merge_requests = @merge_requests.preload(:target_project)
|
||||
|
||||
@label = @project.labels.find_by(title: params[:label_name])
|
||||
@labels = @project.labels.where(title: params[:label_name])
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: {
|
||||
html: view_to_html_string("projects/merge_requests/_merge_requests")
|
||||
html: view_to_html_string("projects/merge_requests/_merge_requests"),
|
||||
labels: @labels.as_json(methods: :text_color)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -117,7 +117,7 @@ class IssuableFinder
|
|||
end
|
||||
|
||||
def filter_by_no_label?
|
||||
labels? && params[:label_name] == Label::None.title
|
||||
labels? && params[:label_name].include?(Label::None.title)
|
||||
end
|
||||
|
||||
def labels
|
||||
|
@ -278,7 +278,9 @@ class IssuableFinder
|
|||
end
|
||||
end
|
||||
|
||||
items
|
||||
# When filtering by multiple labels we may end up duplicating issues (if one
|
||||
# has multiple labels). This ensures we only return unique issues.
|
||||
items.distinct
|
||||
end
|
||||
|
||||
def label_names
|
||||
|
|
|
@ -254,11 +254,11 @@ module ApplicationHelper
|
|||
|
||||
def page_filter_path(options = {})
|
||||
without = options.delete(:without)
|
||||
add_label = options.delete(:label)
|
||||
|
||||
exist_opts = {
|
||||
state: params[:state],
|
||||
scope: params[:scope],
|
||||
label_name: params[:label_name],
|
||||
milestone_title: params[:milestone_title],
|
||||
assignee_id: params[:assignee_id],
|
||||
author_id: params[:author_id],
|
||||
|
@ -275,6 +275,13 @@ module ApplicationHelper
|
|||
|
||||
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
|
||||
end
|
||||
|
||||
|
|
|
@ -16,6 +16,25 @@ module IssuablesHelper
|
|||
base_issuable_scope(issuable).where('iid > ?', issuable.iid).last
|
||||
end
|
||||
|
||||
def multi_label_name(current_labels, default_label)
|
||||
# current_labels may be a string from before
|
||||
if current_labels.is_a?(Array)
|
||||
if current_labels.count > 1
|
||||
"#{current_labels[0]} +#{current_labels.count - 1} more"
|
||||
else
|
||||
current_labels[0]
|
||||
end
|
||||
elsif current_labels.is_a?(String)
|
||||
if current_labels.nil? || current_labels.empty?
|
||||
default_label
|
||||
else
|
||||
current_labels
|
||||
end
|
||||
else
|
||||
default_label
|
||||
end
|
||||
end
|
||||
|
||||
def issuable_json_path(issuable)
|
||||
project = issuable.project
|
||||
|
||||
|
|
|
@ -113,6 +113,10 @@ class Label < ActiveRecord::Base
|
|||
template
|
||||
end
|
||||
|
||||
def text_color
|
||||
LabelsHelper::text_color_for_bg(self.color)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def label_format_reference(format = :id)
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
%span.label-name
|
||||
= link_to_label(label, tooltip: false)
|
||||
%span.prepend-left-10
|
||||
= markdown(label.description, pipeline: :single_line)
|
||||
= markdown(label.description, pipeline: :single_line)
|
3
app/views/shared/_labels_row.html.haml
Normal file
3
app/views/shared/_labels_row.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
|||
- labels.each do |label|
|
||||
%span.label-row
|
||||
= link_to_label(label, tooltip: false)
|
|
@ -46,9 +46,10 @@
|
|||
.filter-item.inline
|
||||
= button_tag "Update issues", class: "btn update_selected_issues btn-save"
|
||||
|
||||
- if @label
|
||||
.gray-content-block.second-block
|
||||
= render "shared/label_row", label: @label
|
||||
- if !@labels.nil?
|
||||
.gray-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) }
|
||||
- if @labels.any?
|
||||
= render "shared/labels_row", labels: @labels
|
||||
|
||||
:javascript
|
||||
new UsersSelect();
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
- if params[:label_name].present?
|
||||
= hidden_field_tag(:label_name, params[:label_name])
|
||||
- if params[:label_name].respond_to?('any?')
|
||||
- params[:label_name].each do |label|
|
||||
= hidden_field_tag "label_name[]", label, id: nil
|
||||
.dropdown
|
||||
%button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
|
||||
%button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-multiselect.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name[]", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
|
||||
%span.dropdown-toggle-text
|
||||
= h(params[:label_name].presence || "Label")
|
||||
= h(multi_label_name(params[:label_name], "Label"))
|
||||
= icon('chevron-down')
|
||||
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
|
||||
.dropdown-page-one
|
||||
|
|
|
@ -4,22 +4,22 @@
|
|||
- else
|
||||
- page_context_word = 'issues'
|
||||
%li{class: ("active" if params[:state] == 'opened')}
|
||||
= link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do
|
||||
= link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do
|
||||
#{state_filters_text_for(:opened, @project)}
|
||||
|
||||
- if defined?(type) && type == :merge_requests
|
||||
%li{class: ("active" if params[:state] == 'merged')}
|
||||
= link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do
|
||||
= link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do
|
||||
#{state_filters_text_for(:merged, @project)}
|
||||
|
||||
%li{class: ("active" if params[:state] == 'closed')}
|
||||
= link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do
|
||||
= link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do
|
||||
#{state_filters_text_for(:closed, @project)}
|
||||
- else
|
||||
%li{class: ("active" if params[:state] == 'closed')}
|
||||
= link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do
|
||||
= link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do
|
||||
#{state_filters_text_for(:closed, @project)}
|
||||
|
||||
%li{class: ("active" if params[:state] == 'all')}
|
||||
= link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do
|
||||
= link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do
|
||||
#{state_filters_text_for(:all, @project)}
|
||||
|
|
|
@ -12,6 +12,7 @@ Feature: Project Issues Filter Labels
|
|||
@javascript
|
||||
Scenario: I filter by one label
|
||||
Given I click link "bug"
|
||||
And I click "dropdown close button"
|
||||
Then I should see "Bugfix1" in issues list
|
||||
And I should see "Bugfix2" in issues list
|
||||
And I should not see "Feature1" in issues list
|
||||
|
|
|
@ -32,6 +32,10 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
|
|||
page.find('.js-label-select').click
|
||||
sleep 0.5
|
||||
execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
|
||||
end
|
||||
|
||||
step 'I click "dropdown close button"' do
|
||||
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
|
||||
sleep 2
|
||||
end
|
||||
|
||||
|
|
172
spec/features/issues/filter_by_labels_spec.rb
Normal file
172
spec/features/issues/filter_by_labels_spec.rb
Normal file
|
@ -0,0 +1,172 @@
|
|||
require 'rails_helper'
|
||||
|
||||
feature 'Issue filtering by Labels', feature: true do
|
||||
let(:project) { create(:project, :public) }
|
||||
let!(:user) { create(:user)}
|
||||
let!(:label) { create(:label, project: project) }
|
||||
|
||||
before do
|
||||
['bug', 'feature', 'enhancement'].each do |title|
|
||||
create(:label,
|
||||
project: project,
|
||||
title: title)
|
||||
end
|
||||
|
||||
issue1 = create(:issue, title: "Bugfix1", project: project)
|
||||
issue1.labels << project.labels.find_by(title: 'bug')
|
||||
|
||||
issue2 = create(:issue, title: "Bugfix2", project: project)
|
||||
issue2.labels << project.labels.find_by(title: 'bug')
|
||||
issue2.labels << project.labels.find_by(title: 'enhancement')
|
||||
|
||||
issue3 = create(:issue, title: "Feature1", project: project)
|
||||
issue3.labels << project.labels.find_by(title: 'feature')
|
||||
|
||||
project.team << [user, :master]
|
||||
login_as(user)
|
||||
|
||||
visit namespace_project_issues_path(project.namespace, project)
|
||||
end
|
||||
|
||||
context 'filter by label bug', js: true do
|
||||
before do
|
||||
page.find('.js-label-select').click
|
||||
sleep 0.5
|
||||
execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
|
||||
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
|
||||
sleep 2
|
||||
end
|
||||
|
||||
it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do
|
||||
expect(page).to have_content "Bugfix1"
|
||||
expect(page).to have_content "Bugfix2"
|
||||
end
|
||||
|
||||
it 'should not show "Feature1" in issues list' do
|
||||
expect(page).not_to have_content "Feature1"
|
||||
end
|
||||
|
||||
it 'should show label "bug" in filtered-labels' do
|
||||
expect(find('.filtered-labels')).to have_content "bug"
|
||||
end
|
||||
|
||||
it 'should not show label "feature" and "enhancement" in filtered-labels' do
|
||||
expect(find('.filtered-labels')).not_to have_content "feature"
|
||||
expect(find('.filtered-labels')).not_to have_content "enhancement"
|
||||
end
|
||||
end
|
||||
|
||||
context 'filter by label feature', js: true do
|
||||
before do
|
||||
page.find('.js-label-select').click
|
||||
sleep 0.5
|
||||
execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()")
|
||||
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
|
||||
sleep 2
|
||||
end
|
||||
|
||||
it 'should show issue "Feature1" in issues list' do
|
||||
expect(page).to have_content "Feature1"
|
||||
end
|
||||
|
||||
it 'should not show "Bugfix1" and "Bugfix2" in issues list' do
|
||||
expect(page).not_to have_content "Bugfix2"
|
||||
expect(page).not_to have_content "Bugfix1"
|
||||
end
|
||||
|
||||
it 'should show label "feature" in filtered-labels' do
|
||||
expect(find('.filtered-labels')).to have_content "feature"
|
||||
end
|
||||
|
||||
it 'should not show label "bug" and "enhancement" in filtered-labels' do
|
||||
expect(find('.filtered-labels')).not_to have_content "bug"
|
||||
expect(find('.filtered-labels')).not_to have_content "enhancement"
|
||||
end
|
||||
end
|
||||
|
||||
context 'filter by label enhancement', js: true do
|
||||
before do
|
||||
page.find('.js-label-select').click
|
||||
sleep 0.5
|
||||
execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()")
|
||||
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
|
||||
sleep 2
|
||||
end
|
||||
|
||||
it 'should show issue "Bugfix2" in issues list' do
|
||||
expect(page).to have_content "Bugfix2"
|
||||
end
|
||||
|
||||
it 'should not show "Feature1" and "Bugfix1" in issues list' do
|
||||
expect(page).not_to have_content "Feature1"
|
||||
expect(page).not_to have_content "Bugfix1"
|
||||
end
|
||||
|
||||
it 'should show label "enhancement" in filtered-labels' do
|
||||
expect(find('.filtered-labels')).to have_content "enhancement"
|
||||
end
|
||||
|
||||
it 'should not show label "feature" and "bug" in filtered-labels' do
|
||||
expect(find('.filtered-labels')).not_to have_content "bug"
|
||||
expect(find('.filtered-labels')).not_to have_content "feature"
|
||||
end
|
||||
end
|
||||
|
||||
context 'filter by label enhancement or feature', js: true do
|
||||
before do
|
||||
page.find('.js-label-select').click
|
||||
sleep 0.5
|
||||
execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()")
|
||||
execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()")
|
||||
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
|
||||
sleep 2
|
||||
end
|
||||
|
||||
it 'should show issue "Bugfix2" or "Feature1" in issues list' do
|
||||
expect(page).to have_content "Bugfix2"
|
||||
expect(page).to have_content "Feature1"
|
||||
end
|
||||
|
||||
it 'should not show "Bugfix1" in issues list' do
|
||||
expect(page).not_to have_content "Bugfix1"
|
||||
end
|
||||
|
||||
it 'should show label "enhancement" and "feature" in filtered-labels' do
|
||||
expect(find('.filtered-labels')).to have_content "enhancement"
|
||||
expect(find('.filtered-labels')).to have_content "feature"
|
||||
end
|
||||
|
||||
it 'should not show label "bug" in filtered-labels' do
|
||||
expect(find('.filtered-labels')).not_to have_content "bug"
|
||||
end
|
||||
end
|
||||
|
||||
context 'filter by label enhancement or bug in issues list', js: true do
|
||||
before do
|
||||
page.find('.js-label-select').click
|
||||
sleep 0.5
|
||||
execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()")
|
||||
execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
|
||||
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
|
||||
sleep 2
|
||||
end
|
||||
|
||||
it 'should show issue "Bugfix2" or "Bugfix1" in issues list' do
|
||||
expect(page).to have_content "Bugfix2"
|
||||
expect(page).to have_content "Bugfix1"
|
||||
end
|
||||
|
||||
it 'should not show "Feature1"' do
|
||||
expect(page).not_to have_content "Feature1"
|
||||
end
|
||||
|
||||
it 'should show label "bug" and "enhancement" in filtered-labels' do
|
||||
expect(find('.filtered-labels')).to have_content "bug"
|
||||
expect(find('.filtered-labels')).to have_content "enhancement"
|
||||
end
|
||||
|
||||
it 'should not show label "feature" in filtered-labels' do
|
||||
expect(find('.filtered-labels')).not_to have_content "feature"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -84,14 +84,20 @@ describe 'Filter issues', feature: true do
|
|||
|
||||
it 'should filter by any label' do
|
||||
find('.dropdown-menu-labels a', text: 'Any Label').click
|
||||
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
|
||||
sleep 2
|
||||
|
||||
page.within '.labels-filter' do
|
||||
expect(page).to have_content 'Any Label'
|
||||
end
|
||||
expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Label')
|
||||
expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Any Label')
|
||||
end
|
||||
|
||||
it 'should filter by no label' do
|
||||
find('.dropdown-menu-labels a', text: 'No Label').click
|
||||
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
|
||||
sleep 2
|
||||
|
||||
page.within '.labels-filter' do
|
||||
expect(page).to have_content 'No Label'
|
||||
end
|
||||
|
@ -121,6 +127,7 @@ describe 'Filter issues', feature: true do
|
|||
find('.js-label-select').click
|
||||
|
||||
find('.dropdown-menu-labels .dropdown-content a', text: label.title).click
|
||||
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
|
||||
|
||||
sleep 2
|
||||
end
|
||||
|
|
|
@ -2,8 +2,14 @@ require 'rails_helper'
|
|||
|
||||
feature 'Merge Request filtering by Milestone', feature: true do
|
||||
let(:project) { create(:project, :public) }
|
||||
let!(:user) { create(:user)}
|
||||
let(:milestone) { create(:milestone, project: project) }
|
||||
|
||||
before do
|
||||
project.team << [user, :master]
|
||||
login_as(user)
|
||||
end
|
||||
|
||||
scenario 'filters by no Milestone', js: true do
|
||||
create(:merge_request, :with_diffs, source_project: project)
|
||||
create(:merge_request, :simple, source_project: project, milestone: milestone)
|
||||
|
|
|
@ -62,6 +62,22 @@ describe IssuesFinder do
|
|||
expect(issues).to eq([issue2])
|
||||
end
|
||||
|
||||
it 'returns unique issues when filtering by multiple labels' do
|
||||
label2 = create(:label, project: project2)
|
||||
|
||||
create(:label_link, label: label2, target: issue2)
|
||||
|
||||
params = {
|
||||
scope: 'all',
|
||||
label_name: [label.title, label2.title].join(','),
|
||||
state: 'opened'
|
||||
}
|
||||
|
||||
issues = IssuesFinder.new(user, params).execute
|
||||
|
||||
expect(issues).to eq([issue2])
|
||||
end
|
||||
|
||||
it 'should filter by no label name' do
|
||||
params = { scope: "all", label_name: Label::None.title, state: 'opened' }
|
||||
issues = IssuesFinder.new(user, params).execute
|
||||
|
|
Loading…
Reference in a new issue