Fixes #864 - Added relative pending_time to Macro/Trigger/Scheduler functionality. Based on the Pull Request #2862 by @fleverest – huge thanks ❤️

This commit is contained in:
Mantas Masalskis 2020-07-06 17:03:28 +02:00 committed by Thorsten Eckel
parent e48f49a1a9
commit 22fe0a2a97
12 changed files with 363 additions and 6 deletions

View file

@ -35,8 +35,13 @@ class App.UiElement.ticket_perform_action
# ignore readonly attributes
if !row.readonly
config = _.clone(row)
if config.tag is 'tag'
config.operator = ['add', 'remove']
switch config.tag
when 'datetime'
config.operator = ['static', 'relative']
when 'tag'
config.operator = ['add', 'remove']
elements["#{groupKey}.#{config.name}"] = config
# add ticket deletion action
@ -318,13 +323,22 @@ class App.UiElement.ticket_perform_action
item = App.UiElement[tagSearch].render(config, {})
else
item = App.UiElement[config.tag].render(config, {})
if meta.operator is 'before (relative)' || meta.operator is 'within next (relative)' || meta.operator is 'within last (relative)' || meta.operator is 'after (relative)'
relative_operators = [
'before (relative)',
'within next (relative)',
'within last (relative)',
'after (relative)',
'relative'
]
if _.include(relative_operators, meta.operator)
config['name'] = "#{attribute.name}::#{groupAndAttribute}"
if attribute.value && attribute.value[groupAndAttribute]
config['value'] = _.clone(attribute.value[groupAndAttribute])
item = App.UiElement['time_range'].render(config, {})
elementRow.find('.js-value').removeClass('hide').html(item)
elementRow.find('.js-setAttribute > .flex > .js-value').removeClass('hide').html(item)
@buildNotificationArea: (notificationType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->

View file

@ -133,6 +133,25 @@ class App.Ticket extends App.Model
else
@tagAdd(params.ticket.id, tag)
# apply pending date changes
else if attributes[1] is 'pending_time' && content.operator is 'relative'
pendtil = new Date
diff = parseInt(content.value, 10)
switch content.range
when 'day'
pendtil.setDate(pendtil.getDate() + diff)
when 'minute'
pendtil.setMinutes(pendtil.getMinutes() + diff)
when 'hour'
pendtil.setHours(pendtil.getHours() + diff)
when 'month'
pendtil.setMonth(pendtil.getMonth() + diff)
when 'year'
pendtil.setYear(pendtil.getYear() + diff)
params.ticket[attributes[1]] = pendtil.toISOString()
# apply user changes
else if attributes[1] is 'owner_id'
if content.pre_condition is 'current_user.id'

View file

@ -11,5 +11,5 @@
<%- @Icon('arrow-down', 'dropdown-arrow') %>
</div>
</div>
<div class="controls js-value"></div>
</div>
<div class="controls js-value horizontal"></div>
</div>

View file

@ -0,0 +1,22 @@
# Ensures pending time is always zero-seconds
class Observer::Ticket::PendingTime < ActiveRecord::Observer
observe 'ticket'
def before_create(record)
_check(record)
end
def before_update(record)
_check(record)
end
private
def _check(record)
return true if record.pending_time.blank?
return true if !record.pending_time_changed?
return true if record.pending_time.sec.zero?
record.pending_time = record.pending_time.change sec: 0
end
end

View file

@ -907,6 +907,38 @@ perform changes on ticket
next
end
# Apply pending_time changes
if key == 'ticket.pending_time'
new_value = case value['operator']
when 'static'
value['value']
when 'relative'
pendtil = Time.zone.now
val = value['value'].to_i
case value['range']
when 'day'
pendtil += val.days
when 'minute'
pendtil += val.minutes
when 'hour'
pendtil += val.hours
when 'month'
pendtil += val.months
when 'year'
pendtil += val.years
end
pendtil
end
if new_value
self[attribute] = new_value
changed = true
next
end
end
# update tags
if key == 'ticket.tags'
next if value['value'].blank?

View file

@ -0,0 +1,14 @@
<link rel="stylesheet" href="/assets/tests/qunit-1.21.0.css">
<%= javascript_include_tag "/assets/tests/qunit-1.21.0.js", "/assets/tests/ticket_macro.js", nonce: true %>
<style type="text/css">
body {
padding-top: 0px;
}
</style>
<script type="text/javascript">
</script>
<div id="qunit" class="u-dontfold"></div>

View file

@ -29,6 +29,7 @@ module Zammad
'observer::_session',
'observer::_ticket::_close_time',
'observer::_ticket::_last_owner_update',
'observer::_ticket::_pending_time',
'observer::_ticket::_user_ticket_counter',
'observer::_ticket::_article_changes',
'observer::_ticket::_article::_fillup_from_origin_by_id',

View file

@ -25,6 +25,7 @@ Zammad::Application.routes.draw do
match '/tests_table', to: 'tests#table', via: :get
match '/tests_table_extended', to: 'tests#table_extended', via: :get
match '/tests_html_utils', to: 'tests#html_utils', via: :get
match '/tests_ticket_macro', to: 'tests#ticket_macro', via: :get
match '/tests_ticket_selector', to: 'tests#ticket_selector', via: :get
match '/tests_taskbar', to: 'tests#taskbar', via: :get
match '/tests_text_module', to: 'tests#text_module', via: :get

View file

@ -237,6 +237,104 @@ test( "ticket_perform_action check", function() {
}
}
deepEqual(params, test_params, 'form param check')
// add pending time
$('[data-attribute-name="ticket_perform_action3"] .js-add').last().click()
var row = $('[data-attribute-name="ticket_perform_action3"] .js-filterElement').last()
var date_string = '2010-07-15T12:00:00.000Z'
var date_parsed = new Date(date_string) // make sure it works regardless of browser locale
row.find('.js-attributeSelector .form-control').last().val('ticket.pending_time').trigger('change')
row.find('.js-datepicker').val(date_parsed.toLocaleDateString()).trigger('blur')
row.find('.js-datepicker').datepicker('setDate')
row.find('.js-timepicker').val(date_parsed.getHours() + ':' + date_parsed.getMinutes()).trigger('blur')
params = App.ControllerForm.params(el)
test_params = {
ticket_perform_action1: {
'ticket.state_id': {
value: '2'
}
},
ticket_perform_action2: {
'notification.email': {
body: 'some body',
internal: 'true',
recipient: 'ticket_customer',
subject: 'some subject'
},
'ticket.priority_id': {
value: '2'
},
'ticket.state_id': {
value: '1'
},
},
ticket_perform_action3: {
'notification.email': {
body: 'some body',
internal: 'false',
recipient: 'ticket_owner',
subject: 'some subject'
},
'ticket.pending_time': {
operator: 'static',
value: date_string
},
'ticket.state_id': {
value: '3'
}
}
}
deepEqual(params, test_params, 'form param check')
// switch pending time to relative
row.find('.js-operator select').val('relative').trigger('change')
row.find('.js-range').val('day').trigger('change')
row.find('.js-value').val('10').trigger('change')
params = App.ControllerForm.params(el)
test_params = {
ticket_perform_action1: {
'ticket.state_id': {
value: '2'
}
},
ticket_perform_action2: {
'notification.email': {
body: 'some body',
internal: 'true',
recipient: 'ticket_customer',
subject: 'some subject'
},
'ticket.priority_id': {
value: '2'
},
'ticket.state_id': {
value: '1'
},
},
ticket_perform_action3: {
'notification.email': {
body: 'some body',
internal: 'false',
recipient: 'ticket_owner',
subject: 'some subject'
},
'ticket.pending_time': {
operator: 'relative',
range: 'day',
value: '10'
},
'ticket.state_id': {
value: '3'
}
}
}
deepEqual(params, test_params, 'form param check')
});
// Test for backwards compatibility after issue is fixed https://github.com/zammad/zammad/issues/2782
@ -363,3 +461,46 @@ test( "ticket_perform_action rows manipulation", function() {
equal($(selector + ' .js-filterElement').length, 1, 'prevents removing last row')
});
// Test for backwards compatibility after PR https://github.com/zammad/zammad/pull/2862
test( "ticket_perform_action backwards check after PR#2862", function() {
$('#forms').append('<hr><h1>ticket_perform_action check</h1><form id="form3"></form>')
var el = $('#form3')
var defaults = {
ticket_perform_action4: {
'ticket.pending_time': {
value: '2010-07-15T05:00:00.000Z'
}
}
}
new App.ControllerForm({
el: el,
model: {
configure_attributes: [
{
name: 'ticket_perform_action4',
display: 'TicketPerformAction4',
tag: 'ticket_perform_action',
null: true,
},
]
},
params: defaults,
autofocus: true
})
var params = App.ControllerForm.params(el)
var test_params = {
ticket_perform_action4: {
'ticket.pending_time': {
operator: 'static',
value: '2010-07-15T05:00:00.000Z'
}
}
}
deepEqual(params, test_params, 'form param check')
});

View file

@ -0,0 +1,43 @@
test( "ticket macro pending time check", function() {
var test_relative = function(rules, target, description){
var ticket = new App.Ticket()
App.Ticket.macro({
ticket: ticket,
macro: {
"ticket.pending_time": rules
}
})
var compare_against = new Date()
var travel = Math.abs( new Date(ticket.pending_time) - compare_against)
var diff = Math.abs(target - travel)
ok(diff < 1000, description)
}
var rules = {
operator: "relative",
range: "day",
value: 5
}
test_relative(rules, 60 * 60 * 24 * 5 * 1000, '5 days')
var rules = {
operator: "relative",
range: "minute",
value: 3
}
test_relative(rules, 60 * 3 * 1000, '5 minutes')
var rules = {
operator: "relative",
range: "hour",
value: 10
}
test_relative(rules, 60 * 60 * 10 * 1000, '10 hours')
})

View file

@ -153,6 +153,72 @@ RSpec.describe Ticket, type: :model do
end
end
# Test for backwards compatibility after PR https://github.com/zammad/zammad/pull/2862
context 'with "pending_time" => { "value": DATE } in "perform" hash' do
let(:perform) do
{
'ticket.state_id' => {
'value' => Ticket::State.lookup(name: 'pending reminder').id.to_s
},
'ticket.pending_time' => {
'value' => timestamp,
},
}
end
let(:timestamp) { Time.zone.now }
it 'changes pending date to given date' do
freeze_time do
expect { ticket.perform_changes(perform, 'trigger', ticket, User.first) }
.to change(ticket, :pending_time).to(be_within(1.minute).of(timestamp))
end
end
end
# Test for PR https://github.com/zammad/zammad/pull/2862
context 'with "pending_time" => { "operator": "relative" } in "perform" hash' do
shared_examples 'verify' do
it 'verify relative pending time rule' do
freeze_time do
interval = relative_value.send(relative_range).from_now
expect { ticket.perform_changes(perform, 'trigger', ticket, User.first) }
.to change(ticket, :pending_time).to(be_within(1.minute).of(interval))
end
end
end
let(:perform) do
{
'ticket.state_id' => {
'value' => Ticket::State.lookup(name: 'pending reminder').id.to_s
},
'ticket.pending_time' => {
'operator' => 'relative',
'value' => relative_value,
'range' => relative_range_config
},
}
end
let(:relative_range_config) { relative_range.to_s.singularize }
context 'and value in days' do
let(:relative_value) { 2 }
let(:relative_range) { :days }
include_examples 'verify'
end
context 'and value in minutes' do
let(:relative_value) { 60 }
let(:relative_range) { :minutes }
include_examples 'verify'
end
end
context 'with "ticket.action" => { "value" => "delete" } in "perform" hash' do
let(:perform) do
{

View file

@ -106,6 +106,10 @@ RSpec.describe 'QUnit', type: :system, authenticated_as: false, set_up: true, we
q_unit_tests('form_ticket_perform_action')
end
it 'Ticket macro' do
q_unit_tests('ticket_macro')
end
it 'Validation' do
q_unit_tests('form_validation')
end