Fixed issue #1930 - bulk action is not executed for a pending-state.

This commit is contained in:
Martin Edenhofer 2018-05-22 00:57:26 +02:00
parent c0e02c3d89
commit 5b0b10ce59
6 changed files with 339 additions and 59 deletions

View file

@ -1162,9 +1162,10 @@ class Table extends App.Controller
# start organization popups
@organizationPopups()
@bulkForm = new BulkForm
@bulkForm = new BulkForm(
holder: @el
view: @view
)
# start bulk action observ
@el.append(@bulkForm.el)
@ -1226,12 +1227,16 @@ class BulkForm extends App.Controller
constructor: ->
super
@configure_attributes_ticket = [
{ name: 'state_id', display: 'State', tag: 'select', multiple: false, null: true, relation: 'TicketState', translate: true, nulloption: true, default: '' },
{ name: 'priority_id', display: 'Priority', tag: 'select', multiple: false, null: true, relation: 'TicketPriority', translate: true, nulloption: true, default: '' },
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: true, relation: 'Group', nulloption: true },
{ name: 'owner_id', display: 'Owner', tag: 'select', multiple: false, null: true, relation: 'User', nulloption: true }
]
@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
@holder = @options.holder
@visible = false
@ -1246,9 +1251,9 @@ class BulkForm extends App.Controller
App.TicketCreateCollection.unbind(@bindId)
render: ->
@el.css 'right', App.Utils.getScrollBarWidth()
@el.css('right', App.Utils.getScrollBarWidth())
@html App.view('agent_ticket_view/bulk')()
@html(App.view('agent_ticket_view/bulk')())
handlers = @Config.get('TicketZoomFormHandler')
@ -1304,12 +1309,15 @@ class BulkForm extends App.Controller
setTimeout ( => @$('.textarea.form-group textarea').focus() ), 0
reset: =>
@$('.js-action-step').removeClass('hide')
@$('.js-confirm-step').addClass('hide')
@cancel()
if @visible
@makeSpaceForTableRows()
cancel: =>
@$('.js-action-step').removeClass('hide')
@$('.js-confirm-step').addClass('hide')
show: =>
@el.removeClass('hide')
@visible = true
@ -1325,30 +1333,84 @@ class BulkForm extends App.Controller
scrollParent = @holder.scrollParent()
isScrolledToBottom = scrollParent.prop('scrollHeight') is scrollParent.scrollTop() + scrollParent.outerHeight()
@holder.css 'margin-bottom', height
@holder.css('margin-bottom', height)
if isScrolledToBottom
scrollParent.scrollTop scrollParent.prop('scrollHeight') - scrollParent.outerHeight()
removeSpaceForTableRows: =>
@holder.css 'margin-bottom', 0
@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()
@bulk_count = @holder.find('.table-overview').find('[name="bulk"]:checked').length
@bulk_count_index = 0
@holder.find('.table-overview').find('[name="bulk"]:checked').each( (index, element) =>
@log 'notice', '@bulk_count_index', @bulk_count, @bulk_count_index
@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)
params = @formParam(e.target)
# update ticket
ticket_update = {}
for item of params
if params[item] != ''
ticket_update[item] = params[item]
ticketUpdate = @ticketMergeParams(params)
# validate article
if params['body']
@ -1372,7 +1434,7 @@ class BulkForm extends App.Controller
@formEnable(e)
return
ticket.load(ticket_update)
ticket.load(ticketUpdate)
# if title is empty - ticket can't processed, set ?
if _.isEmpty(ticket.title)
@ -1380,7 +1442,7 @@ class BulkForm extends App.Controller
ticket.save(
done: (r) =>
@bulk_count_index++
@bulkCountIndex++
# reset form after save
if article
@ -1390,13 +1452,22 @@ class BulkForm extends App.Controller
)
# refresh view after all tickets are proceeded
if @bulk_count_index == @bulk_count
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)
}
)
)
@holder.find('.table-overview').find('[name="bulk"]:checked').prop('checked', false)
App.Event.trigger 'notify', {
type: 'success'

View file

@ -27,6 +27,7 @@
- fix that place method doesn't think that the container is the window, but rather the real window is the window
- added rerender method to show correct today if task is longer open the 24 hours
- scroll into view
- fix vertical auto position
*/
(function(factory){
@ -689,7 +690,9 @@
visualPadding = 10,
container = $(this.o.container),
windowWidth = $(window).width(),
scrollTop = container.scrollTop(),
scrollTop = container.scrollParent().scrollTop(),
bottomEdge = container.offset().top + container.height(),
scrollHeight = container.scrollParent().prop('scrollHeight'),
appendOffset = container.offset();
var parentsZindex = [];
@ -738,10 +741,10 @@
// auto y orientation is best-situation: top or bottom, no fudging,
// decision based on which shows more of the calendar
var yorient = this.o.orientation.y,
top_overflow;
space_below;
if (yorient === 'auto'){
top_overflow = -scrollTop + top - calendarHeight;
yorient = top_overflow < 0 ? 'bottom' : 'top';
space_below = scrollHeight - bottomEdge - scrollTop;
yorient = space_below > calendarHeight ? 'bottom' : 'top';
}
this.picker.addClass('datepicker-orient-' + yorient);

View file

@ -1,4 +1,4 @@
<div class="u-positionOrigin" data-name="<%= @attribute.nameRaw %>">
<div class="control controls--date" data-name="<%= @attribute.nameRaw %>">
<input type="hidden" value="<%= @attribute.value %>" name="<%= @attribute.name %>">
<input type="text" value="" class="form-control js-datepicker <%= @attribute.class %>" data-item="date">
</div>

View file

@ -1,4 +1,4 @@
<div class="horizontal u-positionOrigin" data-name="<%= @attribute.nameRaw %>">
<div class="controls controls--datetime" data-name="<%= @attribute.nameRaw %>">
<input type="hidden" value="<%= @attribute.value %>" name="<%= @attribute.name %>">
<input type="text" value="" class="form-control flex-shrink-horizontal js-datepicker <%= @attribute.class %>" data-item="date">
<div class="controls-label"><%- @T('at') %></div>

View file

@ -1634,6 +1634,15 @@ fieldset > .form-group {
position: relative;
}
.controls--datetime {
position: relative;
display: flex;
}
.controls--date {
position: relative;
}
.controls-label {
margin: 11px 10px 0;
flex-shrink: 0;
@ -2980,6 +2989,11 @@ footer {
align-items: center;
}
.bulkAction-firstStep .has-error {
border-color: red !important;
border: 1px solid;
}
.bulkAction-secondStep {
display: flex;
flex-direction: column;
@ -4570,6 +4584,11 @@ footer {
position: relative;
height: 60px;
flex: 1 1 auto;
&.datetime {
min-width: 140px;
overflow: visible; // datepicker popup needs to be visible
}
}
.form-group.is-changed {
@ -4638,15 +4657,44 @@ footer {
left: 0;
top: 0;
position: absolute;
padding: 28px 18px 12px;
padding: 28px 5px 12px 20px;
float: none;
display: block;
border-radius: 0;
background: none;
}
.form-inline .controls--select {
position: static;
.form-inline {
.controls--datetime,
.controls--date,
.controls--select {
position: static;
}
.controls--datetime {
position: absolute;
bottom: 12px;
left: 0;
padding: 0 5px 0 20px;
width: 100%;
.controls-label {
display: none;
}
.form-control {
width: 70px;
line-height: inherit;
position: static;
padding: 0;
height: auto;
&.time {
margin-left: 5px;
width: 38px;
}
}
}
}
.bulkAction-secondStep .form-group {

View file

@ -2,7 +2,7 @@
require 'browser_test_helper'
class AgentTicketOverviewLevel0Test < TestCase
def test_i
def test_bulk_close
@browser = browser_instance
login(
username: 'master@example.com',
@ -38,56 +38,63 @@ class AgentTicketOverviewLevel0Test < TestCase
)
click(text: 'Unassigned & Open')
sleep 8 # till overview is rendered
watch_for(
css: '.content.active',
value: 'overview count test #2',
)
# select both via bulk action
click(
css: '.active table tr td input[value="' + ticket1[:id] + '"] + .icon-checkbox.icon-unchecked',
css: '.content.active table tr td input[value="' + ticket1[:id] + '"] + .icon-checkbox.icon-unchecked',
fast: true,
)
# scroll to reply - needed for chrome
scroll_to(
position: 'top',
css: '.active table tr td input[value="' + ticket2[:id] + '"] + .icon-checkbox.icon-unchecked',
css: '.content.active table tr td input[value="' + ticket2[:id] + '"] + .icon-checkbox.icon-unchecked',
)
click(
css: '.active table tr td input[value="' + ticket2[:id] + '"] + .icon-checkbox.icon-unchecked',
css: '.content.active table tr td input[value="' + ticket2[:id] + '"] + .icon-checkbox.icon-unchecked',
fast: true,
)
exists(
css: '.active table tr td input[value="' + ticket1[:id] + '"][type="checkbox"]:checked',
css: '.content.active table tr td input[value="' + ticket1[:id] + '"][type="checkbox"]:checked',
)
exists(
css: '.active table tr td input[value="' + ticket2[:id] + '"][type="checkbox"]:checked',
css: '.content.active table tr td input[value="' + ticket2[:id] + '"][type="checkbox"]:checked',
)
# select close state & submit
select(
css: '.active .bulkAction [name="state_id"]',
css: '.content.active .bulkAction [name="state_id"]',
value: 'closed',
)
click(
css: '.active .bulkAction .js-confirm',
css: '.content.active .bulkAction .js-confirm',
)
click(
css: '.active .bulkAction .js-submit',
css: '.content.active .bulkAction .js-submit',
)
watch_for_disappear(
css: '.content.active table tr td input[value="' + ticket2[:id] + '"]',
timeout: 6,
)
sleep 6
exists_not(
css: '.active table tr td input[value="' + ticket1[:id] + '"]',
css: '.content.active table tr td input[value="' + ticket1[:id] + '"]',
)
exists_not(
css: '.active table tr td input[value="' + ticket2[:id] + '"]',
css: '.content.active table tr td input[value="' + ticket2[:id] + '"]',
)
# remember current overview count
overview_counter_before = overview_counter()
# click options and enable number and article count
click(css: '.active [data-type="settings"]')
click(css: '.content.active [data-type="settings"]')
watch_for(
css: '.modal h1',
@ -116,15 +123,15 @@ class AgentTicketOverviewLevel0Test < TestCase
# check if number and article count is shown
match(
css: '.active table th:nth-child(3)',
css: '.content.active table th:nth-child(3)',
value: '#',
)
match(
css: '.active table th:nth-child(4)',
css: '.content.active table th:nth-child(4)',
value: 'Title',
)
match(
css: '.active table th:nth-child(7)',
css: '.content.active table th:nth-child(7)',
value: 'Article#',
)
@ -134,20 +141,20 @@ class AgentTicketOverviewLevel0Test < TestCase
# check if number and article count is shown
match(
css: '.active table th:nth-child(3)',
css: '.content.active table th:nth-child(3)',
value: '#',
)
match(
css: '.active table th:nth-child(4)',
css: '.content.active table th:nth-child(4)',
value: 'Title',
)
match(
css: '.active table th:nth-child(7)',
css: '.content.active table th:nth-child(7)',
value: 'Article#',
)
# disable number and article count
click(css: '.active [data-type="settings"]')
click(css: '.content.active [data-type="settings"]')
watch_for(
css: '.modal h1',
@ -164,15 +171,15 @@ class AgentTicketOverviewLevel0Test < TestCase
# check if number and article count is gone
match_not(
css: '.active table th:nth-child(3)',
css: '.content.active table th:nth-child(3)',
value: '#',
)
match(
css: '.active table th:nth-child(3)',
css: '.content.active table th:nth-child(3)',
value: 'Title',
)
exists_not(
css: '.active table th:nth-child(8)',
css: '.content.active table th:nth-child(8)',
)
# create new ticket
@ -211,4 +218,155 @@ class AgentTicketOverviewLevel0Test < TestCase
# cleanup
tasks_close_all()
end
def test_bulk_pending
@browser = browser_instance
login(
username: 'master@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all()
# test bulk action
# create new ticket
ticket1 = ticket_create(
data: {
customer: 'nico',
group: 'Users',
title: 'overview count test #3',
body: 'overview count test #3',
}
)
ticket2 = ticket_create(
data: {
customer: 'nico',
group: 'Users',
title: 'overview count test #4',
body: 'overview count test #4',
}
)
click(text: 'Overviews')
# enable full overviews
execute(
js: '$(".content.active .sidebar").css("display", "block")',
)
click(text: 'Unassigned & Open')
watch_for(
css: '.content.active',
value: 'overview count test #4',
timeout: 8,
)
# remember current overview count
overview_counter_before = overview_counter()
# select both via bulk action
click(
css: '.content.active table tr td input[value="' + ticket1[:id] + '"] + .icon-checkbox.icon-unchecked',
fast: true,
)
# scroll to reply - needed for chrome
scroll_to(
position: 'top',
css: '.content.active table tr td input[value="' + ticket2[:id] + '"] + .icon-checkbox.icon-unchecked',
)
click(
css: '.content.active table tr td input[value="' + ticket2[:id] + '"] + .icon-checkbox.icon-unchecked',
fast: true,
)
exists(
css: '.content.active table tr td input[value="' + ticket1[:id] + '"][type="checkbox"]:checked',
)
exists(
css: '.content.active table tr td input[value="' + ticket2[:id] + '"][type="checkbox"]:checked',
)
exists(
displayed: false,
css: '.content.active .bulkAction [data-name="pending_time"]',
)
select(
css: '.content.active .bulkAction [name="state_id"]',
value: 'pending close',
)
exists(
displayed: true,
css: '.content.active .bulkAction [data-name="pending_time"]',
)
set(
css: '.content.active .bulkAction [data-item="date"]',
value: '05/23/2088',
)
select(
css: '.content.active .bulkAction [name="group_id"]',
value: 'Users',
)
select(
css: '.content.active .bulkAction [name="owner_id"]',
value: 'Test Master Agent',
)
click(
css: '.content.active .bulkAction .js-confirm',
)
click(
css: '.content.active .bulkAction .js-submit',
)
watch_for_disappear(
css: '.content.active table tr td input[value="' + ticket2[:id] + '"]',
timeout: 12,
)
exists_not(
css: '.content.active table tr td input[value="' + ticket1[:id] + '"]',
)
exists_not(
css: '.content.active table tr td input[value="' + ticket2[:id] + '"]',
)
# get new overview count
overview_counter_new = overview_counter()
assert_equal(overview_counter_before['#ticket/view/all_unassigned'] - 2, overview_counter_new['#ticket/view/all_unassigned'])
# open ticket by search
ticket_open_by_search(
number: ticket1[:number],
)
sleep 1
# close ticket
ticket_update(
data: {
state: 'closed',
}
)
# open ticket by search
ticket_open_by_search(
number: ticket2[:number],
)
sleep 1
# close ticket
ticket_update(
data: {
state: 'closed',
}
)
# cleanup
tasks_close_all()
end
end