From 22fe0a2a977b7e91fc2ecb2b8c49b4307d6633bb Mon Sep 17 00:00:00 2001 From: Mantas Masalskis Date: Mon, 6 Jul 2020 17:03:28 +0200 Subject: [PATCH] =?UTF-8?q?Fixes=20#864=20-=20Added=20relative=20pending?= =?UTF-8?q?=5Ftime=20to=20Macro/Trigger/Scheduler=20functionality.=20Based?= =?UTF-8?q?=20on=20the=20Pull=20Request=20#2862=20by=20@fleverest=20?= =?UTF-8?q?=E2=80=93=20huge=20thanks=20=E2=9D=A4=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_ui_element/ticket_perform_action.coffee | 22 ++- .../javascripts/app/models/ticket.coffee | 19 +++ .../attribute_selector.jst.eco | 4 +- app/models/observer/ticket/pending_time.rb | 22 +++ app/models/ticket.rb | 32 ++++ app/views/tests/ticket_macro.html.erb | 14 ++ config/application.rb | 1 + config/routes/test.rb | 1 + .../tests/form_ticket_perform_action.js | 141 ++++++++++++++++++ public/assets/tests/ticket_macro.js | 43 ++++++ spec/models/ticket_spec.rb | 66 ++++++++ spec/system/js/q_unit_spec.rb | 4 + 12 files changed, 363 insertions(+), 6 deletions(-) create mode 100644 app/models/observer/ticket/pending_time.rb create mode 100644 app/views/tests/ticket_macro.html.erb create mode 100644 public/assets/tests/ticket_macro.js diff --git a/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee b/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee index b1ee3689c..98b20b51d 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee @@ -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) -> diff --git a/app/assets/javascripts/app/models/ticket.coffee b/app/assets/javascripts/app/models/ticket.coffee index f16fcf114..1564f9cd6 100644 --- a/app/assets/javascripts/app/models/ticket.coffee +++ b/app/assets/javascripts/app/models/ticket.coffee @@ -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' diff --git a/app/assets/javascripts/app/views/generic/ticket_perform_action/attribute_selector.jst.eco b/app/assets/javascripts/app/views/generic/ticket_perform_action/attribute_selector.jst.eco index 2dc95049a..35e9a8ed1 100644 --- a/app/assets/javascripts/app/views/generic/ticket_perform_action/attribute_selector.jst.eco +++ b/app/assets/javascripts/app/views/generic/ticket_perform_action/attribute_selector.jst.eco @@ -11,5 +11,5 @@ <%- @Icon('arrow-down', 'dropdown-arrow') %> -
- \ No newline at end of file +
+ diff --git a/app/models/observer/ticket/pending_time.rb b/app/models/observer/ticket/pending_time.rb new file mode 100644 index 000000000..23b52ab27 --- /dev/null +++ b/app/models/observer/ticket/pending_time.rb @@ -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 diff --git a/app/models/ticket.rb b/app/models/ticket.rb index 5af2e467e..4bb8ec1e9 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -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? diff --git a/app/views/tests/ticket_macro.html.erb b/app/views/tests/ticket_macro.html.erb new file mode 100644 index 000000000..99a7630b7 --- /dev/null +++ b/app/views/tests/ticket_macro.html.erb @@ -0,0 +1,14 @@ + + +<%= javascript_include_tag "/assets/tests/qunit-1.21.0.js", "/assets/tests/ticket_macro.js", nonce: true %> + + + + + +
diff --git a/config/application.rb b/config/application.rb index cd446f493..08cde287e 100644 --- a/config/application.rb +++ b/config/application.rb @@ -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', diff --git a/config/routes/test.rb b/config/routes/test.rb index 1a12eb804..b89c662a4 100644 --- a/config/routes/test.rb +++ b/config/routes/test.rb @@ -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 diff --git a/public/assets/tests/form_ticket_perform_action.js b/public/assets/tests/form_ticket_perform_action.js index 5983b5728..979298629 100644 --- a/public/assets/tests/form_ticket_perform_action.js +++ b/public/assets/tests/form_ticket_perform_action.js @@ -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('

ticket_perform_action check

') + + 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') +}); diff --git a/public/assets/tests/ticket_macro.js b/public/assets/tests/ticket_macro.js new file mode 100644 index 000000000..ee6356884 --- /dev/null +++ b/public/assets/tests/ticket_macro.js @@ -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') +}) diff --git a/spec/models/ticket_spec.rb b/spec/models/ticket_spec.rb index cdda2a371..954ae9ba3 100644 --- a/spec/models/ticket_spec.rb +++ b/spec/models/ticket_spec.rb @@ -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 { diff --git a/spec/system/js/q_unit_spec.rb b/spec/system/js/q_unit_spec.rb index 25ea10d28..38ddfefab 100644 --- a/spec/system/js/q_unit_spec.rb +++ b/spec/system/js/q_unit_spec.rb @@ -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