Fixes #445 - Add bulk option to extended search
This commit is contained in:
parent
3486390843
commit
db0229e629
8 changed files with 558 additions and 332 deletions
|
@ -167,15 +167,110 @@ class App.Search extends App.Controller
|
|||
object = App[model].fullLocal(item.id)
|
||||
list.push object
|
||||
if model is 'Ticket'
|
||||
|
||||
openTicket = (id,e) =>
|
||||
# open ticket via task manager to provide task with overview info
|
||||
ticket = App.Ticket.findNative(id)
|
||||
App.TaskManager.execute(
|
||||
key: "Ticket-#{ticket.id}"
|
||||
controller: 'TicketZoom'
|
||||
params:
|
||||
ticket_id: ticket.id
|
||||
overview_id: @overview.id
|
||||
show: true
|
||||
)
|
||||
@navigate ticket.uiUrl()
|
||||
|
||||
checkbox = @permissionCheck('ticket.agent') ? true : false
|
||||
|
||||
callbackCheckbox = (id, checked, e) =>
|
||||
if @shouldShowBulkForm()
|
||||
@bulkForm.render()
|
||||
@bulkForm.show()
|
||||
else
|
||||
@bulkForm.hide()
|
||||
|
||||
if @lastChecked && e.shiftKey
|
||||
# check items in a row
|
||||
currentItem = $(e.currentTarget).parents('.item')
|
||||
lastCheckedItem = $(@lastChecked).parents('.item')
|
||||
items = currentItem.parent().children()
|
||||
|
||||
if currentItem.index() > lastCheckedItem.index()
|
||||
# current item is below last checked item
|
||||
startId = lastCheckedItem.index()
|
||||
endId = currentItem.index()
|
||||
else
|
||||
# current item is above last checked item
|
||||
startId = currentItem.index()
|
||||
endId = lastCheckedItem.index()
|
||||
|
||||
items.slice(startId+1, endId).find('[name="bulk"]').prop('checked', (-> !@checked))
|
||||
|
||||
@lastChecked = e.currentTarget
|
||||
|
||||
ticket_ids = []
|
||||
for item in localList
|
||||
ticket_ids.push item.id
|
||||
localeEl = @$('.js-content')
|
||||
@table = new App.TicketList(
|
||||
tableId: "find_#{model}"
|
||||
el: @$('.js-content')
|
||||
tableId: "find_#{model}"
|
||||
el: localeEl
|
||||
columns: [ 'number', 'title', 'customer', 'group', 'owner', 'created_at' ]
|
||||
ticket_ids: ticket_ids
|
||||
radio: false
|
||||
checkbox: checkbox
|
||||
bindRow:
|
||||
events:
|
||||
'click': openTicket
|
||||
bindCheckbox:
|
||||
events:
|
||||
'click': callbackCheckbox
|
||||
select_all: callbackCheckbox
|
||||
)
|
||||
|
||||
updateSearch = =>
|
||||
callback = =>
|
||||
@search(true)
|
||||
@delay(callback, 100)
|
||||
|
||||
@bulkForm = new App.TicketBulkForm(
|
||||
holder: localeEl
|
||||
view: @view
|
||||
callback: updateSearch
|
||||
noSidebar: true
|
||||
)
|
||||
|
||||
# start bulk action observ
|
||||
@el.append(@bulkForm.el)
|
||||
localElement = @$('.js-content')
|
||||
if localElement.find('input[name="bulk"]:checked').length isnt 0
|
||||
@bulkForm.show()
|
||||
|
||||
# show/hide bulk action
|
||||
localElement.delegate('input[name="bulk"], input[name="bulk_all"]', 'change', (e) =>
|
||||
if @shouldShowBulkForm()
|
||||
@bulkForm.show()
|
||||
else
|
||||
@bulkForm.hide()
|
||||
@bulkForm.reset()
|
||||
)
|
||||
|
||||
# deselect bulk_all if one item is uncheck observ
|
||||
localElement.delegate('[name="bulk"]', 'change', (e) ->
|
||||
bulkAll = localElement.find('[name="bulk_all"]')
|
||||
checkedCount = localElement.find('input[name="bulk"]:checked').length
|
||||
checkboxCount = localElement.find('input[name="bulk"]').length
|
||||
if checkedCount is 0
|
||||
bulkAll.prop('indeterminate', false)
|
||||
bulkAll.prop('checked', false)
|
||||
else
|
||||
if checkedCount is checkboxCount
|
||||
bulkAll.prop('indeterminate', false)
|
||||
bulkAll.prop('checked', true)
|
||||
else
|
||||
bulkAll.prop('checked', false)
|
||||
bulkAll.prop('indeterminate', true)
|
||||
)
|
||||
else
|
||||
openObject = (id,e) =>
|
||||
|
@ -202,6 +297,18 @@ class App.Search extends App.Controller
|
|||
updateFilledClass: ->
|
||||
@searchInput.toggleClass 'is-empty', !@searchInput.val()
|
||||
|
||||
shouldShowBulkForm: =>
|
||||
items = @$('table').find('input[name="bulk"]:checked')
|
||||
return false if items.length == 0
|
||||
|
||||
ticket_ids = _.map(items, (el) -> $(el).val() )
|
||||
ticket_group_ids = _.map(App.Ticket.findAll(ticket_ids), (ticket) -> ticket.group_id)
|
||||
ticket_group_ids = _.uniq(ticket_group_ids)
|
||||
allowed_group_ids = App.User.find(@Session.get('id')).allGroupIds('change')
|
||||
allowed_group_ids = _.map(allowed_group_ids, (id_string) -> parseInt(id_string, 10) )
|
||||
_.every(ticket_group_ids, (id) -> id in allowed_group_ids)
|
||||
|
||||
|
||||
class Router extends App.ControllerPermanent
|
||||
constructor: (params) ->
|
||||
super
|
||||
|
|
|
@ -1,47 +1,3 @@
|
|||
ValidUsersForTicketSelectionMethods =
|
||||
validUsersForTicketSelection: ->
|
||||
items = $('.content.active .table-overview .table').find('[name="bulk"]:checked')
|
||||
|
||||
# we want to display all users for which we can assign the tickets directly
|
||||
# for this we need to get the groups of all selected tickets
|
||||
# after we got those we need to check which users are available in all groups
|
||||
# users that are not in all groups can't get the tickets assigned
|
||||
ticket_ids = _.map(items, (el) -> $(el).val() )
|
||||
ticket_group_ids = _.map(App.Ticket.findAll(ticket_ids), (ticket) -> ticket.group_id)
|
||||
users = @usersInGroups(ticket_group_ids)
|
||||
|
||||
# get the list of possible groups for the current user
|
||||
# from the TicketCreateCollection
|
||||
# (filled for e.g. the TicketCreation or TicketZoom assignment)
|
||||
# and order them by name
|
||||
group_ids = _.keys(@formMeta?.dependencies?.group_id)
|
||||
groups = App.Group.findAll(group_ids)
|
||||
groups_sorted = _.sortBy(groups, (group) -> group.name)
|
||||
|
||||
# get the number of visible users per group
|
||||
# from the TicketCreateCollection
|
||||
# (filled for e.g. the TicketCreation or TicketZoom assignment)
|
||||
for group in groups
|
||||
group.valid_users_count = @formMeta?.dependencies?.group_id?[group.id]?.owner_id.length || 0
|
||||
|
||||
{
|
||||
users: users
|
||||
groups: groups_sorted
|
||||
}
|
||||
|
||||
usersInGroups: (group_ids) ->
|
||||
ids_by_group = _.chain(@formMeta?.dependencies?.group_id)
|
||||
.pick(group_ids)
|
||||
.values()
|
||||
.map( (e) -> e.owner_id)
|
||||
.value()
|
||||
|
||||
# Underscore's intersection doesn't work when chained
|
||||
ids_in_all_groups = _.intersection(ids_by_group...)
|
||||
|
||||
users = App.User.findAll(ids_in_all_groups)
|
||||
_.sortBy(users, (user) -> user.firstname)
|
||||
|
||||
class App.TicketOverview extends App.Controller
|
||||
className: 'overviews'
|
||||
activeFocus: 'nav'
|
||||
|
@ -69,7 +25,7 @@ class App.TicketOverview extends App.Controller
|
|||
'mouseenter .js-batch-hover-target': 'highlightBatchEntry'
|
||||
'mouseleave .js-batch-hover-target': 'unhighlightBatchEntry'
|
||||
|
||||
@include ValidUsersForTicketSelectionMethods
|
||||
@include App.ValidUsersForTicketSelectionMethods
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
@ -1280,18 +1236,19 @@ class Table extends App.Controller
|
|||
|
||||
@renderPopovers()
|
||||
|
||||
@bulkForm = new BulkForm(
|
||||
@bulkForm = new App.TicketBulkForm(
|
||||
holder: @el
|
||||
view: @view
|
||||
)
|
||||
|
||||
# start bulk action observ
|
||||
@el.append(@bulkForm.el)
|
||||
if @$('.table-overview').find('input[name="bulk"]:checked').length isnt 0
|
||||
localElement = @$('.table-overview')
|
||||
if localElement.find('input[name="bulk"]:checked').length isnt 0
|
||||
@bulkForm.show()
|
||||
|
||||
# show/hide bulk action
|
||||
@$('.table-overview').delegate('input[name="bulk"], input[name="bulk_all"]', 'change', (e) =>
|
||||
localElement.delegate('input[name="bulk"], input[name="bulk_all"]', 'change', (e) =>
|
||||
if @shouldShowBulkForm()
|
||||
@bulkForm.show()
|
||||
else
|
||||
|
@ -1300,10 +1257,10 @@ class Table extends App.Controller
|
|||
)
|
||||
|
||||
# deselect bulk_all if one item is uncheck observ
|
||||
@$('.table-overview').delegate('[name="bulk"]', 'change', (e) =>
|
||||
bulkAll = @$('.table-overview').find('[name="bulk_all"]')
|
||||
checkedCount = @$('.table-overview').find('input[name="bulk"]:checked').length
|
||||
checkboxCount = @$('.table-overview').find('input[name="bulk"]').length
|
||||
localElement.delegate('[name="bulk"]', 'change', (e) ->
|
||||
bulkAll = localElement.find('[name="bulk_all"]')
|
||||
checkedCount = localElement.find('input[name="bulk"]:checked').length
|
||||
checkboxCount = localElement.find('input[name="bulk"]').length
|
||||
if checkedCount is 0
|
||||
bulkAll.prop('indeterminate', false)
|
||||
bulkAll.prop('checked', false)
|
||||
|
@ -1362,281 +1319,6 @@ class Table extends App.Controller
|
|||
onCloseCallback: @keyboardOn
|
||||
)
|
||||
|
||||
class BulkForm extends App.Controller
|
||||
className: 'bulkAction hide'
|
||||
|
||||
events:
|
||||
'submit form': 'submit'
|
||||
'click .js-submit': 'submit'
|
||||
'click .js-confirm': 'confirm'
|
||||
'click .js-cancel': 'reset'
|
||||
|
||||
@include ValidUsersForTicketSelectionMethods
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
@configure_attributes_ticket = []
|
||||
used_attributes = ['state_id', 'pending_time', 'priority_id', 'group_id', 'owner_id']
|
||||
attributesClean = App.Ticket.attributesGet('edit')
|
||||
for attributeName, attribute of attributesClean
|
||||
if _.contains(used_attributes, attributeName)
|
||||
localAttribute = clone(attribute)
|
||||
localAttribute.nulloption = true
|
||||
localAttribute.default = ''
|
||||
localAttribute.null = true
|
||||
@configure_attributes_ticket.push localAttribute
|
||||
|
||||
time_attribute = _.findWhere(@configure_attributes_ticket, {'name': 'pending_time'})
|
||||
if time_attribute
|
||||
time_attribute.orientation = 'top'
|
||||
time_attribute.disableScroll = true
|
||||
|
||||
@holder = @options.holder
|
||||
@visible = false
|
||||
|
||||
load = (data) =>
|
||||
App.Collection.loadAssets(data.assets)
|
||||
@formMeta = data.form_meta
|
||||
@render()
|
||||
@bindId = App.TicketCreateCollection.bind(load)
|
||||
|
||||
release: =>
|
||||
App.TicketCreateCollection.unbind(@bindId)
|
||||
|
||||
render: ->
|
||||
@el.css('right', App.Utils.getScrollBarWidth())
|
||||
|
||||
@html(App.view('agent_ticket_view/bulk')())
|
||||
|
||||
handlers = @Config.get('TicketZoomFormHandler')
|
||||
|
||||
for attribute in @configure_attributes_ticket
|
||||
continue if attribute.name != 'owner_id'
|
||||
{users, groups} = @validUsersForTicketSelection()
|
||||
options = _.map(users, (user) -> {value: user.id, name: user.displayName()} )
|
||||
attribute.possible_groups_owners = options
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @$('#form-ticket-bulk')
|
||||
model:
|
||||
configure_attributes: @configure_attributes_ticket
|
||||
className: 'create'
|
||||
labelClass: 'input-group-addon'
|
||||
handlersConfig: handlers
|
||||
params: {}
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
noFieldset: true
|
||||
)
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @$('#form-ticket-bulk-comment')
|
||||
model:
|
||||
configure_attributes: [{ name: 'body', display: 'Comment', tag: 'textarea', rows: 4, null: true, upload: false, item_class: 'flex' }]
|
||||
className: 'create'
|
||||
labelClass: 'input-group-addon'
|
||||
noFieldset: true
|
||||
)
|
||||
|
||||
@confirm_attributes = [
|
||||
{ name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', filter: @articleTypeFilter, default: '9', translate: true, class: 'medium' }
|
||||
{ name: 'internal', display: 'Visibility', tag: 'select', null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: '', default: false }
|
||||
]
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @$('#form-ticket-bulk-typeVisibility')
|
||||
model:
|
||||
configure_attributes: @confirm_attributes
|
||||
className: 'create'
|
||||
labelClass: 'input-group-addon'
|
||||
noFieldset: true
|
||||
)
|
||||
|
||||
articleTypeFilter: (items) ->
|
||||
for item in items
|
||||
if item.name is 'note'
|
||||
return [item]
|
||||
items
|
||||
|
||||
confirm: =>
|
||||
@$('.js-action-step').addClass('hide')
|
||||
@$('.js-confirm-step').removeClass('hide')
|
||||
|
||||
@makeSpaceForTableRows()
|
||||
|
||||
# need a delay because of the click event
|
||||
setTimeout ( => @$('.textarea.form-group textarea').focus() ), 0
|
||||
|
||||
reset: =>
|
||||
@cancel()
|
||||
|
||||
if @visible
|
||||
@makeSpaceForTableRows()
|
||||
|
||||
cancel: =>
|
||||
@$('.js-action-step').removeClass('hide')
|
||||
@$('.js-confirm-step').addClass('hide')
|
||||
|
||||
show: =>
|
||||
@el.removeClass('hide')
|
||||
@visible = true
|
||||
@makeSpaceForTableRows()
|
||||
|
||||
hide: =>
|
||||
@el.addClass('hide')
|
||||
@visible = false
|
||||
@removeSpaceForTableRows()
|
||||
|
||||
makeSpaceForTableRows: =>
|
||||
height = @el.height()
|
||||
scrollParent = @holder.scrollParent()
|
||||
isScrolledToBottom = scrollParent.prop('scrollHeight') is scrollParent.scrollTop() + scrollParent.outerHeight()
|
||||
|
||||
@holder.css('margin-bottom', height)
|
||||
|
||||
if isScrolledToBottom
|
||||
scrollParent.scrollTop scrollParent.prop('scrollHeight') - scrollParent.outerHeight()
|
||||
|
||||
removeSpaceForTableRows: =>
|
||||
@holder.css('margin-bottom', 0)
|
||||
|
||||
ticketMergeParams: (params) ->
|
||||
ticketUpdate = {}
|
||||
for item of params
|
||||
if params[item] != '' && params[item] != null
|
||||
ticketUpdate[item] = params[item]
|
||||
|
||||
# in case if a group is selected, set also the selected owner (maybe nobody)
|
||||
if params.group_id != '' && params.group_id != null
|
||||
ticketUpdate.owner_id = params.owner_id
|
||||
ticketUpdate
|
||||
|
||||
submit: (e) =>
|
||||
e.preventDefault()
|
||||
|
||||
@bulkCount = @holder.find('.table-overview').find('[name="bulk"]:checked').length
|
||||
|
||||
if @bulkCount is 0
|
||||
App.Event.trigger('notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('At least one object must be selected.')
|
||||
})
|
||||
return
|
||||
|
||||
ticket_ids = []
|
||||
@holder.find('.table-overview').find('[name="bulk"]:checked').each( (index, element) ->
|
||||
ticket_id = $(element).val()
|
||||
ticket_ids.push ticket_id
|
||||
)
|
||||
|
||||
params = @formParam(e.target)
|
||||
|
||||
for ticket_id in ticket_ids
|
||||
ticket = App.Ticket.find(ticket_id)
|
||||
|
||||
ticketUpdate = @ticketMergeParams(params)
|
||||
ticket.load(ticketUpdate)
|
||||
|
||||
# if title is empty - ticket can't processed, set ?
|
||||
if _.isEmpty(ticket.title)
|
||||
ticket.title = '-'
|
||||
|
||||
# validate ticket
|
||||
errors = ticket.validate(
|
||||
screen: 'edit'
|
||||
)
|
||||
if errors
|
||||
@log 'error', 'update', errors
|
||||
errorString = ''
|
||||
for key, error of errors
|
||||
errorString += "#{key}: #{error}"
|
||||
|
||||
@formValidate(
|
||||
form: e.target
|
||||
errors: errors
|
||||
screen: 'edit'
|
||||
)
|
||||
|
||||
App.Event.trigger('notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Bulk action stopped %s!', errorString)
|
||||
})
|
||||
@cancel()
|
||||
return
|
||||
|
||||
@bulkCountIndex = 0
|
||||
for ticket_id in ticket_ids
|
||||
ticket = App.Ticket.find(ticket_id)
|
||||
|
||||
# update ticket
|
||||
ticketUpdate = @ticketMergeParams(params)
|
||||
|
||||
# validate article
|
||||
if params['body']
|
||||
article = new App.TicketArticle
|
||||
params.from = @Session.get().displayName()
|
||||
params.ticket_id = ticket.id
|
||||
params.form_id = @form_id
|
||||
|
||||
sender = App.TicketArticleSender.findByAttribute('name', 'Agent')
|
||||
type = App.TicketArticleType.find(params['type_id'])
|
||||
params.sender_id = sender.id
|
||||
|
||||
if !params['internal']
|
||||
params['internal'] = false
|
||||
|
||||
@log 'notice', 'update article', params, sender
|
||||
article.load(params)
|
||||
errors = article.validate()
|
||||
if errors
|
||||
@log 'error', 'update article', errors
|
||||
@formEnable(e)
|
||||
return
|
||||
|
||||
ticket.load(ticketUpdate)
|
||||
|
||||
# if title is empty - ticket can't processed, set ?
|
||||
if _.isEmpty(ticket.title)
|
||||
ticket.title = '-'
|
||||
|
||||
@saveTicketArticle(ticket, article)
|
||||
|
||||
@holder.find('.table-overview').find('[name="bulk"]:checked').prop('checked', false)
|
||||
App.Event.trigger('notify', {
|
||||
type: 'success'
|
||||
msg: App.i18n.translateContent('Bulk action executed!')
|
||||
})
|
||||
|
||||
saveTicketArticle: (ticket, article) =>
|
||||
ticket.save(
|
||||
done: (r) =>
|
||||
@bulkCountIndex++
|
||||
|
||||
# reset form after save
|
||||
if article
|
||||
article.save(
|
||||
fail: (r) =>
|
||||
@log 'error', 'update article', r
|
||||
)
|
||||
|
||||
# refresh view after all tickets are proceeded
|
||||
if @bulkCountIndex == @bulkCount
|
||||
@render()
|
||||
@hide()
|
||||
|
||||
# fetch overview data again
|
||||
App.Event.trigger('overview:fetch')
|
||||
|
||||
fail: (r) =>
|
||||
@bulkCountIndex++
|
||||
@log 'error', 'update ticket', r
|
||||
App.Event.trigger 'notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Can\'t update Ticket %s!', ticket.number)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class App.OverviewSettings extends App.ControllerModal
|
||||
buttonClose: true
|
||||
|
|
|
@ -0,0 +1,278 @@
|
|||
class App.TicketBulkForm extends App.Controller
|
||||
className: 'bulkAction hide'
|
||||
|
||||
events:
|
||||
'submit form': 'submit'
|
||||
'click .js-submit': 'submit'
|
||||
'click .js-confirm': 'confirm'
|
||||
'click .js-cancel': 'reset'
|
||||
|
||||
@include App.ValidUsersForTicketSelectionMethods
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
@configure_attributes_ticket = []
|
||||
|
||||
used_attributes = ['state_id', 'pending_time', 'priority_id', 'group_id', 'owner_id']
|
||||
attributesClean = App.Ticket.attributesGet('edit')
|
||||
for attributeName, attribute of attributesClean
|
||||
if _.contains(used_attributes, attributeName)
|
||||
localAttribute = clone(attribute)
|
||||
localAttribute.nulloption = true
|
||||
localAttribute.default = ''
|
||||
localAttribute.null = true
|
||||
@configure_attributes_ticket.push localAttribute
|
||||
|
||||
|
||||
time_attribute = _.findWhere(@configure_attributes_ticket, {'name': 'pending_time'})
|
||||
if time_attribute
|
||||
time_attribute.orientation = 'top'
|
||||
time_attribute.disableScroll = true
|
||||
|
||||
@holder = @options.holder
|
||||
@visible = false
|
||||
|
||||
load = (data) =>
|
||||
App.Collection.loadAssets(data.assets)
|
||||
@formMeta = data.form_meta
|
||||
@render()
|
||||
@bindId = App.TicketCreateCollection.bind(load)
|
||||
|
||||
release: =>
|
||||
App.TicketCreateCollection.unbind(@bindId)
|
||||
|
||||
render: ->
|
||||
@el.css('right', App.Utils.getScrollBarWidth())
|
||||
@el.addClass('no-sidebar') if @noSidebar
|
||||
|
||||
@html(App.view('agent_ticket_view/bulk')())
|
||||
|
||||
handlers = @Config.get('TicketZoomFormHandler')
|
||||
|
||||
for attribute in @configure_attributes_ticket
|
||||
continue if attribute.name != 'owner_id'
|
||||
{users, groups} = @validUsersForTicketSelection()
|
||||
options = _.map(users, (user) -> {value: user.id, name: user.displayName()} )
|
||||
attribute.possible_groups_owners = options
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @$('#form-ticket-bulk')
|
||||
model:
|
||||
configure_attributes: @configure_attributes_ticket
|
||||
className: 'create'
|
||||
labelClass: 'input-group-addon'
|
||||
handlersConfig: handlers
|
||||
params: {}
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
noFieldset: true
|
||||
)
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @$('#form-ticket-bulk-comment')
|
||||
model:
|
||||
configure_attributes: [{ name: 'body', display: 'Comment', tag: 'textarea', rows: 4, null: true, upload: false, item_class: 'flex' }]
|
||||
className: 'create'
|
||||
labelClass: 'input-group-addon'
|
||||
noFieldset: true
|
||||
)
|
||||
|
||||
@confirm_attributes = [
|
||||
{ name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', filter: @articleTypeFilter, default: '9', translate: true, class: 'medium' }
|
||||
{ name: 'internal', display: 'Visibility', tag: 'select', null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: '', default: false }
|
||||
]
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @$('#form-ticket-bulk-typeVisibility')
|
||||
model:
|
||||
configure_attributes: @confirm_attributes
|
||||
className: 'create'
|
||||
labelClass: 'input-group-addon'
|
||||
noFieldset: true
|
||||
)
|
||||
|
||||
articleTypeFilter: (items) ->
|
||||
for item in items
|
||||
if item.name is 'note'
|
||||
return [item]
|
||||
items
|
||||
|
||||
confirm: =>
|
||||
@$('.js-action-step').addClass('hide')
|
||||
@$('.js-confirm-step').removeClass('hide')
|
||||
|
||||
@makeSpaceForTableRows()
|
||||
|
||||
# need a delay because of the click event
|
||||
setTimeout ( => @$('.textarea.form-group textarea').focus() ), 0
|
||||
|
||||
reset: =>
|
||||
@cancel()
|
||||
|
||||
if @visible
|
||||
@makeSpaceForTableRows()
|
||||
|
||||
cancel: =>
|
||||
@$('.js-action-step').removeClass('hide')
|
||||
@$('.js-confirm-step').addClass('hide')
|
||||
|
||||
show: =>
|
||||
@el.removeClass('hide')
|
||||
@visible = true
|
||||
@makeSpaceForTableRows()
|
||||
|
||||
hide: =>
|
||||
@el.addClass('hide')
|
||||
@visible = false
|
||||
@removeSpaceForTableRows()
|
||||
|
||||
makeSpaceForTableRows: =>
|
||||
height = @el.height()
|
||||
scrollParent = @holder.scrollParent()
|
||||
isScrolledToBottom = scrollParent.prop('scrollHeight') is scrollParent.scrollTop() + scrollParent.outerHeight()
|
||||
|
||||
@holder.css('margin-bottom', height)
|
||||
|
||||
if isScrolledToBottom
|
||||
scrollParent.scrollTop scrollParent.prop('scrollHeight') - scrollParent.outerHeight()
|
||||
|
||||
removeSpaceForTableRows: =>
|
||||
@holder.css('margin-bottom', 0)
|
||||
|
||||
ticketMergeParams: (params) ->
|
||||
ticketUpdate = {}
|
||||
for item of params
|
||||
if params[item] != '' && params[item] != null
|
||||
ticketUpdate[item] = params[item]
|
||||
|
||||
# in case if a group is selected, set also the selected owner (maybe nobody)
|
||||
if params.group_id != '' && params.group_id != null
|
||||
ticketUpdate.owner_id = params.owner_id
|
||||
ticketUpdate
|
||||
|
||||
submit: (e) =>
|
||||
e.preventDefault()
|
||||
|
||||
@bulkCount = @holder.find('.table').find('[name="bulk"]:checked').length
|
||||
|
||||
if @bulkCount is 0
|
||||
App.Event.trigger('notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('At least one object must be selected.')
|
||||
})
|
||||
return
|
||||
|
||||
ticket_ids = []
|
||||
@holder.find('.table').find('[name="bulk"]:checked').each( (index, element) ->
|
||||
ticket_id = $(element).val()
|
||||
ticket_ids.push ticket_id
|
||||
)
|
||||
|
||||
params = @formParam(e.target)
|
||||
|
||||
for ticket_id in ticket_ids
|
||||
ticket = App.Ticket.find(ticket_id)
|
||||
|
||||
ticketUpdate = @ticketMergeParams(params)
|
||||
ticket.load(ticketUpdate)
|
||||
|
||||
# if title is empty - ticket can't processed, set ?
|
||||
if _.isEmpty(ticket.title)
|
||||
ticket.title = '-'
|
||||
|
||||
# validate ticket
|
||||
errors = ticket.validate(
|
||||
screen: 'edit'
|
||||
)
|
||||
if errors
|
||||
@log 'error', 'update', errors
|
||||
errorString = ''
|
||||
for key, error of errors
|
||||
errorString += "#{key}: #{error}"
|
||||
|
||||
@formValidate(
|
||||
form: e.target
|
||||
errors: errors
|
||||
screen: 'edit'
|
||||
)
|
||||
|
||||
App.Event.trigger('notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Bulk action stopped %s!', errorString)
|
||||
})
|
||||
@cancel()
|
||||
return
|
||||
|
||||
@bulkCountIndex = 0
|
||||
for ticket_id in ticket_ids
|
||||
ticket = App.Ticket.find(ticket_id)
|
||||
|
||||
# update ticket
|
||||
ticketUpdate = @ticketMergeParams(params)
|
||||
|
||||
# validate article
|
||||
if params['body']
|
||||
article = new App.TicketArticle
|
||||
params.from = @Session.get().displayName()
|
||||
params.ticket_id = ticket.id
|
||||
params.form_id = @form_id
|
||||
|
||||
sender = App.TicketArticleSender.findByAttribute('name', 'Agent')
|
||||
type = App.TicketArticleType.find(params['type_id'])
|
||||
params.sender_id = sender.id
|
||||
|
||||
if !params['internal']
|
||||
params['internal'] = false
|
||||
|
||||
@log 'notice', 'update article', params, sender
|
||||
article.load(params)
|
||||
errors = article.validate()
|
||||
if errors
|
||||
@log 'error', 'update article', errors
|
||||
@formEnable(e)
|
||||
return
|
||||
|
||||
ticket.load(ticketUpdate)
|
||||
|
||||
# if title is empty - ticket can't processed, set ?
|
||||
if _.isEmpty(ticket.title)
|
||||
ticket.title = '-'
|
||||
|
||||
@saveTicketArticle(ticket, article)
|
||||
|
||||
@holder.find('.table').find('[name="bulk"]:checked').prop('checked', false)
|
||||
App.Event.trigger('notify', {
|
||||
type: 'success'
|
||||
msg: App.i18n.translateContent('Bulk action executed!')
|
||||
})
|
||||
|
||||
saveTicketArticle: (ticket, article) =>
|
||||
ticket.save(
|
||||
done: (r) =>
|
||||
@bulkCountIndex++
|
||||
|
||||
# reset form after save
|
||||
if article
|
||||
article.save(
|
||||
fail: (r) =>
|
||||
@log 'error', 'update article', r
|
||||
)
|
||||
|
||||
# refresh view after all tickets are proceeded
|
||||
if @bulkCountIndex == @bulkCount
|
||||
@render()
|
||||
@hide()
|
||||
|
||||
# fetch overview data again
|
||||
App.Event.trigger('overview:fetch')
|
||||
|
||||
fail: (r) =>
|
||||
@bulkCountIndex++
|
||||
@log 'error', 'update ticket', r
|
||||
App.Event.trigger 'notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Can\'t update Ticket %s!', ticket.number)
|
||||
}
|
||||
)
|
||||
|
|
@ -117,11 +117,13 @@ class App.TicketList extends App.Controller
|
|||
overview: @columns || [ 'number', 'title', 'customer', 'group', 'created_at' ]
|
||||
model: App.Ticket
|
||||
objects: list
|
||||
checkbox: @checkbox
|
||||
#bindRow:
|
||||
# events:
|
||||
# 'click': openTicket
|
||||
callbackHeader: callbackHeader
|
||||
callbackAttributes: callbackAttributes
|
||||
bindCheckbox: @bindCheckbox
|
||||
radio: @radio
|
||||
)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ class App.GlobalSearch extends App.Controller
|
|||
query = params.query
|
||||
# use cache for search result
|
||||
currentTime = new Date
|
||||
if @searchResultCache[query] && @searchResultCache[query].time > currentTime.setSeconds(currentTime.getSeconds() - 20)
|
||||
if !params.force && @searchResultCache[query] && @searchResultCache[query].time > currentTime.setSeconds(currentTime.getSeconds() - 20)
|
||||
if @ajaxRequestId
|
||||
App.Ajax.abort(@ajaxRequestId)
|
||||
@ajaxStart(params)
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
App.ValidUsersForTicketSelectionMethods =
|
||||
validUsersForTicketSelection: ->
|
||||
items = $('.content.active .table-overview .table').find('[name="bulk"]:checked')
|
||||
|
||||
# we want to display all users for which we can assign the tickets directly
|
||||
# for this we need to get the groups of all selected tickets
|
||||
# after we got those we need to check which users are available in all groups
|
||||
# users that are not in all groups can't get the tickets assigned
|
||||
ticket_ids = _.map(items, (el) -> $(el).val() )
|
||||
ticket_group_ids = _.map(App.Ticket.findAll(ticket_ids), (ticket) -> ticket.group_id)
|
||||
users = @usersInGroups(ticket_group_ids)
|
||||
|
||||
# get the list of possible groups for the current user
|
||||
# from the TicketCreateCollection
|
||||
# (filled for e.g. the TicketCreation or TicketZoom assignment)
|
||||
# and order them by name
|
||||
group_ids = _.keys(@formMeta?.dependencies?.group_id)
|
||||
groups = App.Group.findAll(group_ids)
|
||||
groups_sorted = _.sortBy(groups, (group) -> group.name)
|
||||
|
||||
# get the number of visible users per group
|
||||
# from the TicketCreateCollection
|
||||
# (filled for e.g. the TicketCreation or TicketZoom assignment)
|
||||
for group in groups
|
||||
group.valid_users_count = @formMeta?.dependencies?.group_id?[group.id]?.owner_id.length || 0
|
||||
|
||||
{
|
||||
users: users
|
||||
groups: groups_sorted
|
||||
}
|
||||
|
||||
usersInGroups: (group_ids) ->
|
||||
ids_by_group = _.chain(@formMeta?.dependencies?.group_id)
|
||||
.pick(group_ids)
|
||||
.values()
|
||||
.map( (e) -> e.owner_id)
|
||||
.value()
|
||||
|
||||
# Underscore's intersection doesn't work when chained
|
||||
ids_in_all_groups = _.intersection(ids_by_group...)
|
||||
|
||||
users = App.User.findAll(ids_in_all_groups)
|
||||
_.sortBy(users, (user) -> user.firstname)
|
|
@ -3835,6 +3835,15 @@ footer {
|
|||
min-width: $minWidth - $sidebarWidth;
|
||||
}
|
||||
|
||||
&.no-sidebar {
|
||||
@include bidi-style(left, $navigationWidth, right, 0);
|
||||
min-width: $minWidth - $navigationWidth;
|
||||
|
||||
@include small-desktop {
|
||||
min-width: $minWidth;
|
||||
}
|
||||
}
|
||||
|
||||
@include phone {
|
||||
@include bidi-style(left, $mobileNavigationWidth, right, 0);
|
||||
min-width: 0;
|
||||
|
|
|
@ -2,7 +2,12 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Search', type: :system, searchindex: true do
|
||||
RSpec.describe 'Search', type: :system, authenticated: true, searchindex: true do
|
||||
let(:users_group) { Group.find_by(name: 'Users') }
|
||||
let(:ticket_1) { create(:ticket, title: 'Testing Ticket 1', group: users_group) }
|
||||
let(:ticket_2) { create(:ticket, title: 'Testing Ticket 2', group: users_group) }
|
||||
let(:note) { 'Test note' }
|
||||
|
||||
before do
|
||||
configure_elasticsearch(required: true, rebuild: true)
|
||||
end
|
||||
|
@ -13,7 +18,107 @@ RSpec.describe 'Search', type: :system, searchindex: true do
|
|||
click_on 'Show Search Details'
|
||||
|
||||
within '#navigation .tasks a[data-key=Search]' do
|
||||
expect(page).to have_text '"Welcome"'
|
||||
expect(page).to have_content '"Welcome"'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with ticket search result' do
|
||||
before do
|
||||
ticket_1 && ticket_2 && rebuild_searchindex
|
||||
|
||||
fill_in id: 'global-search', with: 'Testing'
|
||||
click_on 'Show Search Details'
|
||||
|
||||
find('[data-tab-content=Ticket]').click
|
||||
end
|
||||
|
||||
context 'checkbox' do
|
||||
it 'has checkbox for each ticket records' do
|
||||
within '.detail-search table.table' do
|
||||
expect(page).to have_xpath(".//td[contains(@class, 'js-checkbox-field')]//input[@type='checkbox']", visible: :all, minimum: 2)
|
||||
end
|
||||
end
|
||||
|
||||
it 'has select all checkbox' do
|
||||
within '.detail-search table.table' do
|
||||
expect(page).to have_xpath(".//th//input[@type='checkbox' and @name='bulk_all']", visible: :all, count: 1)
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows bulkform when checkbox is checked' do
|
||||
within '.detail-search table.table' do
|
||||
find("tr[data-id='#{ticket_1.id}']").check('bulk', allow_label_click: true)
|
||||
end
|
||||
|
||||
expect(page).to have_selector('.bulkAction.no-sidebar')
|
||||
.and have_no_selector('.bulkAction.no-sidebar.hide', visible: :all)
|
||||
end
|
||||
|
||||
it 'shows bulkform when all checkbox is checked' do
|
||||
within '.detail-search table.table' do
|
||||
find('th.table-checkbox').check('bulk_all', allow_label_click: true)
|
||||
end
|
||||
|
||||
expect(page).to have_selector('.bulkAction.no-sidebar')
|
||||
.and have_no_selector('.bulkAction.no-sidebar.hide', visible: :all)
|
||||
end
|
||||
|
||||
it 'hides bulkform when checkbox is unchecked' do
|
||||
within '.detail-search table.table' do
|
||||
find('th.table-checkbox').check('bulk_all', allow_label_click: true)
|
||||
|
||||
all('.js-tableBody tr.item').each { |row| row.uncheck('bulk', allow_label_click: true) }
|
||||
end
|
||||
|
||||
expect(page).to have_selector('.bulkAction.no-sidebar.hide', visible: :hide)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with bulkform activated' do
|
||||
before do
|
||||
find('th.table-checkbox').check('bulk_all', allow_label_click: true)
|
||||
end
|
||||
|
||||
it 'has group label' do
|
||||
within '.bulkAction .bulkAction-form' do
|
||||
expect(page).to have_content 'GROUP'
|
||||
end
|
||||
end
|
||||
|
||||
it 'has owner label' do
|
||||
within '.bulkAction .bulkAction-form' do
|
||||
expect(page).to have_content 'OWNER'
|
||||
end
|
||||
end
|
||||
|
||||
it 'has state label' do
|
||||
within '.bulkAction .bulkAction-form' do
|
||||
expect(page).to have_content 'STATE'
|
||||
end
|
||||
end
|
||||
|
||||
it 'has priority label' do
|
||||
within '.bulkAction .bulkAction-form' do
|
||||
expect(page).to have_content 'PRIORITY'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'bulk note' do
|
||||
before { current_window.resize_to(1300, 1040) }
|
||||
|
||||
it 'adds note to selected ticket' do
|
||||
within :active_content do
|
||||
find("tr[data-id='#{ticket_1.id}']").check('bulk', allow_label_click: true)
|
||||
click '.js-confirm'
|
||||
find('.js-confirm-step textarea').fill_in with: note
|
||||
click '.js-submit'
|
||||
end
|
||||
|
||||
expect do
|
||||
wait(10, interval: 0.1).until { ticket_1.articles.last&.body == note }
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue