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:
parent
e48f49a1a9
commit
22fe0a2a97
12 changed files with 363 additions and 6 deletions
|
@ -35,8 +35,13 @@ class App.UiElement.ticket_perform_action
|
||||||
# ignore readonly attributes
|
# ignore readonly attributes
|
||||||
if !row.readonly
|
if !row.readonly
|
||||||
config = _.clone(row)
|
config = _.clone(row)
|
||||||
if config.tag is 'tag'
|
|
||||||
|
switch config.tag
|
||||||
|
when 'datetime'
|
||||||
|
config.operator = ['static', 'relative']
|
||||||
|
when 'tag'
|
||||||
config.operator = ['add', 'remove']
|
config.operator = ['add', 'remove']
|
||||||
|
|
||||||
elements["#{groupKey}.#{config.name}"] = config
|
elements["#{groupKey}.#{config.name}"] = config
|
||||||
|
|
||||||
# add ticket deletion action
|
# add ticket deletion action
|
||||||
|
@ -318,13 +323,22 @@ class App.UiElement.ticket_perform_action
|
||||||
item = App.UiElement[tagSearch].render(config, {})
|
item = App.UiElement[tagSearch].render(config, {})
|
||||||
else
|
else
|
||||||
item = App.UiElement[config.tag].render(config, {})
|
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}"
|
config['name'] = "#{attribute.name}::#{groupAndAttribute}"
|
||||||
if attribute.value && attribute.value[groupAndAttribute]
|
if attribute.value && attribute.value[groupAndAttribute]
|
||||||
config['value'] = _.clone(attribute.value[groupAndAttribute])
|
config['value'] = _.clone(attribute.value[groupAndAttribute])
|
||||||
item = App.UiElement['time_range'].render(config, {})
|
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) ->
|
@buildNotificationArea: (notificationType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||||
|
|
||||||
|
|
|
@ -133,6 +133,25 @@ class App.Ticket extends App.Model
|
||||||
else
|
else
|
||||||
@tagAdd(params.ticket.id, tag)
|
@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
|
# apply user changes
|
||||||
else if attributes[1] is 'owner_id'
|
else if attributes[1] is 'owner_id'
|
||||||
if content.pre_condition is 'current_user.id'
|
if content.pre_condition is 'current_user.id'
|
||||||
|
|
|
@ -11,5 +11,5 @@
|
||||||
<%- @Icon('arrow-down', 'dropdown-arrow') %>
|
<%- @Icon('arrow-down', 'dropdown-arrow') %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls js-value"></div>
|
<div class="controls js-value horizontal"></div>
|
||||||
</div>
|
</div>
|
22
app/models/observer/ticket/pending_time.rb
Normal file
22
app/models/observer/ticket/pending_time.rb
Normal 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
|
|
@ -907,6 +907,38 @@ perform changes on ticket
|
||||||
next
|
next
|
||||||
end
|
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
|
# update tags
|
||||||
if key == 'ticket.tags'
|
if key == 'ticket.tags'
|
||||||
next if value['value'].blank?
|
next if value['value'].blank?
|
||||||
|
|
14
app/views/tests/ticket_macro.html.erb
Normal file
14
app/views/tests/ticket_macro.html.erb
Normal 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>
|
|
@ -29,6 +29,7 @@ module Zammad
|
||||||
'observer::_session',
|
'observer::_session',
|
||||||
'observer::_ticket::_close_time',
|
'observer::_ticket::_close_time',
|
||||||
'observer::_ticket::_last_owner_update',
|
'observer::_ticket::_last_owner_update',
|
||||||
|
'observer::_ticket::_pending_time',
|
||||||
'observer::_ticket::_user_ticket_counter',
|
'observer::_ticket::_user_ticket_counter',
|
||||||
'observer::_ticket::_article_changes',
|
'observer::_ticket::_article_changes',
|
||||||
'observer::_ticket::_article::_fillup_from_origin_by_id',
|
'observer::_ticket::_article::_fillup_from_origin_by_id',
|
||||||
|
|
|
@ -25,6 +25,7 @@ Zammad::Application.routes.draw do
|
||||||
match '/tests_table', to: 'tests#table', via: :get
|
match '/tests_table', to: 'tests#table', via: :get
|
||||||
match '/tests_table_extended', to: 'tests#table_extended', via: :get
|
match '/tests_table_extended', to: 'tests#table_extended', via: :get
|
||||||
match '/tests_html_utils', to: 'tests#html_utils', 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_ticket_selector', to: 'tests#ticket_selector', via: :get
|
||||||
match '/tests_taskbar', to: 'tests#taskbar', via: :get
|
match '/tests_taskbar', to: 'tests#taskbar', via: :get
|
||||||
match '/tests_text_module', to: 'tests#text_module', via: :get
|
match '/tests_text_module', to: 'tests#text_module', via: :get
|
||||||
|
|
|
@ -237,6 +237,104 @@ test( "ticket_perform_action check", function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deepEqual(params, test_params, 'form param check')
|
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
|
// 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')
|
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')
|
||||||
|
});
|
||||||
|
|
43
public/assets/tests/ticket_macro.js
Normal file
43
public/assets/tests/ticket_macro.js
Normal 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')
|
||||||
|
})
|
|
@ -153,6 +153,72 @@ RSpec.describe Ticket, type: :model do
|
||||||
end
|
end
|
||||||
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
|
context 'with "ticket.action" => { "value" => "delete" } in "perform" hash' do
|
||||||
let(:perform) do
|
let(:perform) do
|
||||||
{
|
{
|
||||||
|
|
|
@ -106,6 +106,10 @@ RSpec.describe 'QUnit', type: :system, authenticated_as: false, set_up: true, we
|
||||||
q_unit_tests('form_ticket_perform_action')
|
q_unit_tests('form_ticket_perform_action')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'Ticket macro' do
|
||||||
|
q_unit_tests('ticket_macro')
|
||||||
|
end
|
||||||
|
|
||||||
it 'Validation' do
|
it 'Validation' do
|
||||||
q_unit_tests('form_validation')
|
q_unit_tests('form_validation')
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue