From cb2286fae490a35ffdd19813e4fae396066a1360 Mon Sep 17 00:00:00 2001 From: Thorsten Eckel Date: Mon, 1 Mar 2021 08:18:40 +0000 Subject: [PATCH] Refactoring: Migrate Rails Observers to Concerns. --- .rubocop/todo.yml | 66 +++++++++---------- .../concerns/tag/writes_to_ticket_history.rb | 41 ++++++++++++ .../ticket/article/adds_metadata_email.rb} | 49 ++++++++------ .../ticket/article/adds_metadata_general.rb} | 38 ++++++----- .../article/adds_metadata_origin_by_id.rb | 34 ++++++++++ .../article/enqueue_communicate_email_job.rb} | 23 ++++--- .../enqueue_communicate_facebook_job.rb} | 24 ++++--- .../article/enqueue_communicate_sms_job.rb | 34 ++++++++++ .../enqueue_communicate_telegram_job.rb | 34 ++++++++++ .../enqueue_communicate_twitter_job.rb} | 25 ++++--- .../ticket/article/resets_ticket_state.rb} | 21 ++++-- .../ticket/calls_stats_ticket_reopen_log.rb | 23 +++++++ .../enqueues_user_ticket_counter_job.rb | 30 +++++++++ .../ticket/resets_pending_time_seconds.rb | 21 ++++++ .../ticket/sets_close_time.rb} | 24 ++++--- .../ticket/sets_last_owner_update_time.rb | 49 ++++++++++++++ .../ticket/sets_online_notification_seen.rb | 30 +++++++++ .../concerns/ticket/touches_associations.rb | 37 +++++++++++ .../user/performs_geo_lookup.rb} | 41 ++++++------ .../concerns/user/touches_organization.rb | 36 ++++++++++ .../user/updates_ticket_organization.rb | 28 ++++++++ app/models/observer/tag/ticket_history.rb | 36 ---------- .../ticket/article/communicate_sms.rb | 25 ------- .../ticket/article/communicate_telegram.rb | 27 -------- .../article/fillup_from_origin_by_id.rb | 27 -------- .../observer/ticket/last_owner_update.rb | 52 --------------- .../ticket/online_notification_seen.rb | 32 --------- app/models/observer/ticket/pending_time.rb | 22 ------- .../observer/ticket/ref_object_touch.rb | 43 ------------ app/models/observer/ticket/stats_reopen.rb | 26 -------- .../observer/ticket/user_ticket_counter.rb | 28 -------- app/models/observer/user/ref_object_touch.rb | 41 ------------ .../observer/user/ticket_organization.rb | 30 --------- app/models/tag.rb | 1 + app/models/ticket.rb | 9 +++ app/models/ticket/article.rb | 11 ++++ app/models/user.rb | 3 + config/application.rb | 20 ------ spec/jobs/communicate_twitter_job_spec.rb | 7 -- .../tag/writes_to_ticket_history_examples.rb | 33 ++++++++++ .../article/adds_metadata_general_spec.rb} | 2 +- .../enqueue_communicate_email_job_spec.rb | 41 ++++++++++++ .../enqueue_communicate_facebook_job_spec.rb | 41 ++++++++++++ .../enqueue_communicate_sms_job_spec.rb | 41 ++++++++++++ .../enqueue_communicate_telegram_job_spec.rb | 41 ++++++++++++ .../enqueue_communicate_twitter_job_spec.rb} | 6 +- .../calls_stats_ticket_reopen_log_examples.rb | 12 ++++ ...queues_user_ticket_counter_job_examples.rb | 10 +++ .../resets_pending_time_seconds_examples.rb | 12 ++++ .../ticket/sets_close_time_examples.rb | 18 +++++ .../sets_last_owner_update_time_examples.rb | 24 +++++++ .../user/performs_geo_lookup_examples.rb | 16 +++++ spec/models/ticket_spec.rb | 12 ++++ spec/models/user_spec.rb | 2 + 54 files changed, 903 insertions(+), 556 deletions(-) create mode 100644 app/models/concerns/tag/writes_to_ticket_history.rb rename app/models/{observer/ticket/article/fillup_from_email.rb => concerns/ticket/article/adds_metadata_email.rb} (52%) rename app/models/{observer/ticket/article/fillup_from_general.rb => concerns/ticket/article/adds_metadata_general.rb} (58%) create mode 100644 app/models/concerns/ticket/article/adds_metadata_origin_by_id.rb rename app/models/{observer/ticket/article/communicate_email.rb => concerns/ticket/article/enqueue_communicate_email_job.rb} (54%) rename app/models/{observer/ticket/article/communicate_facebook.rb => concerns/ticket/article/enqueue_communicate_facebook_job.rb} (53%) create mode 100644 app/models/concerns/ticket/article/enqueue_communicate_sms_job.rb create mode 100644 app/models/concerns/ticket/article/enqueue_communicate_telegram_job.rb rename app/models/{observer/ticket/article/communicate_twitter.rb => concerns/ticket/article/enqueue_communicate_twitter_job.rb} (53%) rename app/models/{observer/ticket/reset_new_state.rb => concerns/ticket/article/resets_ticket_state.rb} (60%) create mode 100644 app/models/concerns/ticket/calls_stats_ticket_reopen_log.rb create mode 100644 app/models/concerns/ticket/enqueues_user_ticket_counter_job.rb create mode 100644 app/models/concerns/ticket/resets_pending_time_seconds.rb rename app/models/{observer/ticket/close_time.rb => concerns/ticket/sets_close_time.rb} (51%) create mode 100644 app/models/concerns/ticket/sets_last_owner_update_time.rb create mode 100644 app/models/concerns/ticket/sets_online_notification_seen.rb create mode 100644 app/models/concerns/ticket/touches_associations.rb rename app/models/{observer/user/geo.rb => concerns/user/performs_geo_lookup.rb} (56%) create mode 100644 app/models/concerns/user/touches_organization.rb create mode 100644 app/models/concerns/user/updates_ticket_organization.rb delete mode 100644 app/models/observer/tag/ticket_history.rb delete mode 100644 app/models/observer/ticket/article/communicate_sms.rb delete mode 100644 app/models/observer/ticket/article/communicate_telegram.rb delete mode 100644 app/models/observer/ticket/article/fillup_from_origin_by_id.rb delete mode 100644 app/models/observer/ticket/last_owner_update.rb delete mode 100644 app/models/observer/ticket/online_notification_seen.rb delete mode 100644 app/models/observer/ticket/pending_time.rb delete mode 100644 app/models/observer/ticket/ref_object_touch.rb delete mode 100644 app/models/observer/ticket/stats_reopen.rb delete mode 100644 app/models/observer/ticket/user_ticket_counter.rb delete mode 100644 app/models/observer/user/ref_object_touch.rb delete mode 100644 app/models/observer/user/ticket_organization.rb create mode 100644 spec/models/concerns/tag/writes_to_ticket_history_examples.rb rename spec/models/{observer/ticket/article/fillup_from_general_spec.rb => concerns/ticket/article/adds_metadata_general_spec.rb} (80%) create mode 100644 spec/models/concerns/ticket/article/enqueue_communicate_email_job_spec.rb create mode 100644 spec/models/concerns/ticket/article/enqueue_communicate_facebook_job_spec.rb create mode 100644 spec/models/concerns/ticket/article/enqueue_communicate_sms_job_spec.rb create mode 100644 spec/models/concerns/ticket/article/enqueue_communicate_telegram_job_spec.rb rename spec/models/{observer/ticket/article/communicate_twitter_spec.rb => concerns/ticket/article/enqueue_communicate_twitter_job_spec.rb} (91%) create mode 100644 spec/models/concerns/ticket/calls_stats_ticket_reopen_log_examples.rb create mode 100644 spec/models/concerns/ticket/enqueues_user_ticket_counter_job_examples.rb create mode 100644 spec/models/concerns/ticket/resets_pending_time_seconds_examples.rb create mode 100644 spec/models/concerns/ticket/sets_close_time_examples.rb create mode 100644 spec/models/concerns/ticket/sets_last_owner_update_time_examples.rb create mode 100644 spec/models/concerns/user/performs_geo_lookup_examples.rb diff --git a/.rubocop/todo.yml b/.rubocop/todo.yml index 9bb013794..47a2aef41 100644 --- a/.rubocop/todo.yml +++ b/.rubocop/todo.yml @@ -138,6 +138,18 @@ Metrics/AbcSize: - 'app/models/concerns/has_rich_text.rb' - 'app/models/concerns/has_search_index_backend.rb' - 'app/models/concerns/has_search_sortable.rb' + - 'app/models/concerns/ticket/article/adds_metadata_email.rb' + - 'app/models/concerns/ticket/article/adds_metadata_general.rb' + - 'app/models/concerns/ticket/article/adds_metadata_origin_by_id.rb' + - 'app/models/concerns/ticket/article/enqueue_communicate_email_job.rb' + - 'app/models/concerns/ticket/article/enqueue_communicate_facebook_job.rb' + - 'app/models/concerns/ticket/article/enqueue_communicate_sms_job.rb' + - 'app/models/concerns/ticket/article/enqueue_communicate_telegram_job.rb' + - 'app/models/concerns/ticket/article/enqueue_communicate_twitter_job.rb' + - 'app/models/concerns/ticket/article/resets_ticket_state.rb' + - 'app/models/concerns/ticket/sets_last_owner_update_time.rb' + - 'app/models/concerns/ticket/touches_associations.rb' + - 'app/models/concerns/user/performs_geo_lookup.rb' - 'app/models/cti/caller_id.rb' - 'app/models/cti/driver/base.rb' - 'app/models/cti/driver/placetel.rb' @@ -156,19 +168,7 @@ Metrics/AbcSize: - 'app/models/link.rb' - 'app/models/object_manager/attribute.rb' - 'app/models/observer/chat/leave/background_job.rb' - - 'app/models/observer/ticket/article/communicate_email.rb' - - 'app/models/observer/ticket/article/communicate_facebook.rb' - - 'app/models/observer/ticket/article/communicate_sms.rb' - - 'app/models/observer/ticket/article/communicate_telegram.rb' - - 'app/models/observer/ticket/article/communicate_twitter.rb' - - 'app/models/observer/ticket/article/fillup_from_email.rb' - - 'app/models/observer/ticket/article/fillup_from_general.rb' - - 'app/models/observer/ticket/article/fillup_from_origin_by_id.rb' - - 'app/models/observer/ticket/last_owner_update.rb' - - 'app/models/observer/ticket/ref_object_touch.rb' - - 'app/models/observer/ticket/reset_new_state.rb' - 'app/models/observer/transaction.rb' - - 'app/models/observer/user/geo.rb' - 'app/models/online_notification.rb' - 'app/models/online_notification/assets.rb' - 'app/models/organization/assets.rb' @@ -533,6 +533,17 @@ Metrics/CyclomaticComplexity: - 'app/models/concerns/has_rich_text.rb' - 'app/models/concerns/has_search_index_backend.rb' - 'app/models/concerns/has_search_sortable.rb' + - 'app/models/concerns/ticket/article/adds_metadata_email.rb' + - 'app/models/concerns/ticket/article/adds_metadata_general.rb' + - 'app/models/concerns/ticket/article/adds_metadata_origin_by_id.rb' + - 'app/models/concerns/ticket/article/enqueue_communicate_email_job.rb' + - 'app/models/concerns/ticket/article/enqueue_communicate_facebook_job.rb' + - 'app/models/concerns/ticket/article/enqueue_communicate_sms_job.rb' + - 'app/models/concerns/ticket/article/enqueue_communicate_twitter_job.rb' + - 'app/models/concerns/ticket/article/resets_ticket_state.rb' + - 'app/models/concerns/ticket/sets_last_owner_update_time.rb' + - 'app/models/concerns/ticket/touches_associations.rb' + - 'app/models/concerns/user/performs_geo_lookup.rb' - 'app/models/cti/caller_id.rb' - 'app/models/cti/driver/base.rb' - 'app/models/cti/driver/placetel.rb' @@ -545,18 +556,7 @@ Metrics/CyclomaticComplexity: - 'app/models/karma/activity_log.rb' - 'app/models/knowledge_base.rb' - 'app/models/object_manager/attribute.rb' - - 'app/models/observer/ticket/article/communicate_email.rb' - - 'app/models/observer/ticket/article/communicate_facebook.rb' - - 'app/models/observer/ticket/article/communicate_sms.rb' - - 'app/models/observer/ticket/article/communicate_twitter.rb' - - 'app/models/observer/ticket/article/fillup_from_email.rb' - - 'app/models/observer/ticket/article/fillup_from_general.rb' - - 'app/models/observer/ticket/article/fillup_from_origin_by_id.rb' - - 'app/models/observer/ticket/last_owner_update.rb' - - 'app/models/observer/ticket/ref_object_touch.rb' - - 'app/models/observer/ticket/reset_new_state.rb' - 'app/models/observer/transaction.rb' - - 'app/models/observer/user/geo.rb' - 'app/models/online_notification/assets.rb' - 'app/models/organization/assets.rb' - 'app/models/organization/search.rb' @@ -763,6 +763,16 @@ Metrics/PerceivedComplexity: - 'app/models/concerns/has_rich_text.rb' - 'app/models/concerns/has_search_index_backend.rb' - 'app/models/concerns/has_search_sortable.rb' + - 'app/models/concerns/ticket/article/adds_metadata_email.rb' + - 'app/models/concerns/ticket/article/adds_metadata_general.rb' + - 'app/models/concerns/ticket/article/adds_metadata_origin_by_id.rb' + - 'app/models/concerns/ticket/article/enqueue_communicate_email_job.rb' + - 'app/models/concerns/ticket/article/enqueue_communicate_facebook_job.rb' + - 'app/models/concerns/ticket/article/enqueue_communicate_twitter_job.rb' + - 'app/models/concerns/ticket/article/resets_ticket_state.rb' + - 'app/models/concerns/ticket/sets_last_owner_update_time.rb' + - 'app/models/concerns/ticket/touches_associations.rb' + - 'app/models/concerns/user/performs_geo_lookup.rb' - 'app/models/cti/caller_id.rb' - 'app/models/cti/driver/base.rb' - 'app/models/cti/driver/placetel.rb' @@ -775,16 +785,6 @@ Metrics/PerceivedComplexity: - 'app/models/karma/activity_log.rb' - 'app/models/knowledge_base.rb' - 'app/models/object_manager/attribute.rb' - - 'app/models/observer/ticket/article/communicate_email.rb' - - 'app/models/observer/ticket/article/communicate_facebook.rb' - - 'app/models/observer/ticket/article/communicate_sms.rb' - - 'app/models/observer/ticket/article/communicate_twitter.rb' - - 'app/models/observer/ticket/article/fillup_from_email.rb' - - 'app/models/observer/ticket/article/fillup_from_general.rb' - - 'app/models/observer/ticket/article/fillup_from_origin_by_id.rb' - - 'app/models/observer/ticket/last_owner_update.rb' - - 'app/models/observer/ticket/ref_object_touch.rb' - - 'app/models/observer/ticket/reset_new_state.rb' - 'app/models/observer/transaction.rb' - 'app/models/online_notification/assets.rb' - 'app/models/organization/assets.rb' diff --git a/app/models/concerns/tag/writes_to_ticket_history.rb b/app/models/concerns/tag/writes_to_ticket_history.rb new file mode 100644 index 000000000..15feda087 --- /dev/null +++ b/app/models/concerns/tag/writes_to_ticket_history.rb @@ -0,0 +1,41 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +# Records added/removed tags also in the ticket history. +module Tag::WritesToTicketHistory + extend ActiveSupport::Concern + + included do + after_create :write_tag_added_to_ticket_history + after_destroy :write_tag_removed_to_ticket_history + end + + private + + def write_tag_added_to_ticket_history + + return true if tag_object.name != 'Ticket' + + History.add( + o_id: o_id, + history_type: 'added', + history_object: 'Ticket', + history_attribute: 'tag', + value_to: tag_item.name, + created_by_id: created_by_id, + ) + end + + def write_tag_removed_to_ticket_history + + return true if tag_object.name != 'Ticket' + + History.add( + o_id: o_id, + history_type: 'removed', + history_object: 'Ticket', + history_attribute: 'tag', + value_to: tag_item.name, + created_by_id: created_by_id, + ) + end +end diff --git a/app/models/observer/ticket/article/fillup_from_email.rb b/app/models/concerns/ticket/article/adds_metadata_email.rb similarity index 52% rename from app/models/observer/ticket/article/fillup_from_email.rb rename to app/models/concerns/ticket/article/adds_metadata_email.rb index 86c2fba6d..e0884476c 100644 --- a/app/models/observer/ticket/article/fillup_from_email.rb +++ b/app/models/concerns/ticket/article/adds_metadata_email.rb @@ -1,9 +1,16 @@ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ -class Observer::Ticket::Article::FillupFromEmail < ActiveRecord::Observer - observe 'ticket::_article' +# Adds certain (missing) meta data when creating email articles. +module Ticket::Article::AddsMetadataEmail + extend ActiveSupport::Concern - def before_create(record) + included do + before_create :ticket_article_add_metadata_email + end + + private + + def ticket_article_add_metadata_email # return if we run import mode return true if Setting.get('import_mode') @@ -13,36 +20,36 @@ class Observer::Ticket::Article::FillupFromEmail < ActiveRecord::Observer return if ApplicationHandleInfo.postmaster? # if sender is customer, do not change anything - return true if !record.sender_id + return true if !sender_id - sender = Ticket::Article::Sender.lookup(id: record.sender_id) + sender = Ticket::Article::Sender.lookup(id: sender_id) return true if sender.nil? return true if sender.name == 'Customer' # set email attributes - return true if !record.type_id + return true if !type_id - type = Ticket::Article::Type.lookup(id: record.type_id) + type = Ticket::Article::Type.lookup(id: type_id) return true if type.nil? return true if type.name != 'email' # set subject if empty - ticket = record.ticket - if !record.subject || record.subject == '' - record.subject = ticket.title + ticket = self.ticket + if !subject || subject == '' + self.subject = ticket.title end # clean subject - record.subject = ticket.subject_clean(record.subject) + self.subject = ticket.subject_clean(subject) # generate message id, force it in production, in test allow to set it for testing reasons - if !record.message_id || Rails.env.production? + if !message_id || Rails.env.production? fqdn = Setting.get('fqdn') - record.message_id = "<#{DateTime.current.to_s(:number)}.#{record.ticket_id}.#{rand(999_999_999_999)}@#{fqdn}>" + self.message_id = "<#{DateTime.current.to_s(:number)}.#{ticket_id}.#{rand(999_999_999_999)}@#{fqdn}>" end # generate message_id_md5 - record.check_message_id_md5 + check_message_id_md5 # set sender email_address = ticket.group.email_address @@ -51,20 +58,20 @@ class Observer::Ticket::Article::FillupFromEmail < ActiveRecord::Observer end # remember email address for background job - record.preferences['email_address_id'] = email_address.id + preferences['email_address_id'] = email_address.id # fill from - if record.created_by_id != 1 && Setting.get('ticket_define_email_from') == 'AgentNameSystemAddressName' + if created_by_id != 1 && Setting.get('ticket_define_email_from') == 'AgentNameSystemAddressName' separator = Setting.get('ticket_define_email_from_separator') - sender = User.find(record.created_by_id) + sender = User.find(created_by_id) realname = "#{sender.firstname} #{sender.lastname} #{separator} #{email_address.realname}" - record.from = Channel::EmailBuild.recipient_line(realname, email_address.email) + self.from = Channel::EmailBuild.recipient_line(realname, email_address.email) elsif Setting.get('ticket_define_email_from') == 'AgentName' - sender = User.find(record.created_by_id) + sender = User.find(created_by_id) realname = "#{sender.firstname} #{sender.lastname}" - record.from = Channel::EmailBuild.recipient_line(realname, email_address.email) + self.from = Channel::EmailBuild.recipient_line(realname, email_address.email) else - record.from = Channel::EmailBuild.recipient_line(email_address.realname, email_address.email) + self.from = Channel::EmailBuild.recipient_line(email_address.realname, email_address.email) end true end diff --git a/app/models/observer/ticket/article/fillup_from_general.rb b/app/models/concerns/ticket/article/adds_metadata_general.rb similarity index 58% rename from app/models/observer/ticket/article/fillup_from_general.rb rename to app/models/concerns/ticket/article/adds_metadata_general.rb index 0eba231c6..c67af9c20 100644 --- a/app/models/observer/ticket/article/fillup_from_general.rb +++ b/app/models/concerns/ticket/article/adds_metadata_general.rb @@ -1,9 +1,17 @@ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ -class Observer::Ticket::Article::FillupFromGeneral < ActiveRecord::Observer - observe 'ticket::_article' +# Adds certain (missing) meta data when creating articles. +# This module depends on AddsMetadataOriginById to run before it. +module Ticket::Article::AddsMetadataGeneral + extend ActiveSupport::Concern - def before_create(record) + included do + before_create :ticket_article_add_metadata_general + end + + private + + def ticket_article_add_metadata_general # return if we run import mode return true if Setting.get('import_mode') @@ -13,9 +21,9 @@ class Observer::Ticket::Article::FillupFromGeneral < ActiveRecord::Observer return true if ApplicationHandleInfo.postmaster? # set from on all article types excluding email|twitter status|twitter direct-message|facebook feed post|facebook feed comment - return true if record.type_id.blank? + return true if type_id.blank? - type = Ticket::Article::Type.lookup(id: record.type_id) + type = Ticket::Article::Type.lookup(id: type_id) # from will be set by channel backend return true if type.nil? @@ -26,31 +34,31 @@ class Observer::Ticket::Article::FillupFromGeneral < ActiveRecord::Observer return true if type.name == 'facebook feed comment' return true if type.name == 'sms' - user_id = record.created_by_id + user_id = created_by_id - if record.origin_by_id.present? + if origin_by_id.present? # in case the customer is using origin_by_id, force it to current session user # and set sender to Customer - if !record.created_by.permissions?('ticket.agent') - record.origin_by_id = record.created_by_id - record.sender_id = Ticket::Article::Sender.lookup(name: 'Customer').id + if !created_by.permissions?('ticket.agent') + self.origin_by_id = created_by_id + self.sender_id = Ticket::Article::Sender.lookup(name: 'Customer').id end # in case origin_by is different than created_by, set sender to Customer # Customer in context of this conversation, not as a permission - if record.origin_by != record.created_by_id - record.sender_id = Ticket::Article::Sender.lookup(name: 'Customer').id - user_id = record.origin_by_id + if origin_by != created_by_id + self.sender_id = Ticket::Article::Sender.lookup(name: 'Customer').id + user_id = origin_by_id end end return true if user_id.blank? user = User.find(user_id) if type.name == 'web' || type.name == 'phone' - record.from = "#{user.firstname} #{user.lastname} <#{user.email}>" + self.from = "#{user.firstname} #{user.lastname} <#{user.email}>" return end - record.from = "#{user.firstname} #{user.lastname}" + self.from = "#{user.firstname} #{user.lastname}" end end diff --git a/app/models/concerns/ticket/article/adds_metadata_origin_by_id.rb b/app/models/concerns/ticket/article/adds_metadata_origin_by_id.rb new file mode 100644 index 000000000..dca77e8db --- /dev/null +++ b/app/models/concerns/ticket/article/adds_metadata_origin_by_id.rb @@ -0,0 +1,34 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +# Adds origin_by_id field (if missing) when creating articles. +module Ticket::Article::AddsMetadataOriginById + extend ActiveSupport::Concern + + included do + before_create :ticket_article_add_metadata_origin_by_id + end + + private + + def ticket_article_add_metadata_origin_by_id + + # return if we run import mode + return true if Setting.get('import_mode') + + # only do fill origin_by_id if article got created via application_server (e. g. not + # if article and sender type is set via *.postmaster) + return true if ApplicationHandleInfo.postmaster? + + # check if origin_by_id exists + return true if origin_by_id.present? + return true if ticket.blank? + return true if ticket.customer_id.blank? + return true if sender_id.blank? + return true if sender.name != 'Customer' + + type_name = type.name + return true if type_name != 'phone' && type_name != 'note' && type_name != 'web' + + self.origin_by_id = ticket.customer_id + end +end diff --git a/app/models/observer/ticket/article/communicate_email.rb b/app/models/concerns/ticket/article/enqueue_communicate_email_job.rb similarity index 54% rename from app/models/observer/ticket/article/communicate_email.rb rename to app/models/concerns/ticket/article/enqueue_communicate_email_job.rb index 27c9b91f5..553f342e7 100644 --- a/app/models/observer/ticket/article/communicate_email.rb +++ b/app/models/concerns/ticket/article/enqueue_communicate_email_job.rb @@ -1,9 +1,16 @@ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ -class Observer::Ticket::Article::CommunicateEmail < ActiveRecord::Observer - observe 'ticket::_article' +# Schedules a backgrond communication job for new email articles. +module Ticket::Article::EnqueueCommunicateEmailJob + extend ActiveSupport::Concern - def after_create(record) + included do + after_create :ticket_article_enqueue_communicate_email_job + end + + private + + def ticket_article_enqueue_communicate_email_job # return if we run import mode return true if Setting.get('import_mode') @@ -13,20 +20,20 @@ class Observer::Ticket::Article::CommunicateEmail < ActiveRecord::Observer return true if ApplicationHandleInfo.postmaster? # if sender is customer, do not communicate - return true if !record.sender_id + return true if !sender_id - sender = Ticket::Article::Sender.lookup(id: record.sender_id) + sender = Ticket::Article::Sender.lookup(id: sender_id) return true if sender.nil? return true if sender.name == 'Customer' # only apply on emails - return true if !record.type_id + return true if !type_id - type = Ticket::Article::Type.lookup(id: record.type_id) + type = Ticket::Article::Type.lookup(id: type_id) return true if type.nil? return true if type.name != 'email' # send background job - TicketArticleCommunicateEmailJob.perform_later(record.id) + TicketArticleCommunicateEmailJob.perform_later(id) end end diff --git a/app/models/observer/ticket/article/communicate_facebook.rb b/app/models/concerns/ticket/article/enqueue_communicate_facebook_job.rb similarity index 53% rename from app/models/observer/ticket/article/communicate_facebook.rb rename to app/models/concerns/ticket/article/enqueue_communicate_facebook_job.rb index 195bd9b1d..9f4e3a5c1 100644 --- a/app/models/observer/ticket/article/communicate_facebook.rb +++ b/app/models/concerns/ticket/article/enqueue_communicate_facebook_job.rb @@ -1,8 +1,16 @@ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ -class Observer::Ticket::Article::CommunicateFacebook < ActiveRecord::Observer - observe 'ticket::_article' - def after_create(record) +# Schedules a backgrond communication job for new facebook articles. +module Ticket::Article::EnqueueCommunicateFacebookJob + extend ActiveSupport::Concern + + included do + after_create :ticket_article_enqueue_communicate_facebook_job + end + + private + + def ticket_article_enqueue_communicate_facebook_job # return if we run import mode return true if Setting.get('import_mode') @@ -12,20 +20,20 @@ class Observer::Ticket::Article::CommunicateFacebook < ActiveRecord::Observer return true if ApplicationHandleInfo.postmaster? # if sender is customer, do not communicate - return true if !record.sender_id + return true if !sender_id - sender = Ticket::Article::Sender.lookup(id: record.sender_id) + sender = Ticket::Article::Sender.lookup(id: sender_id) return true if sender.nil? return true if sender.name == 'Customer' # only apply for facebook - return true if !record.type_id + return true if !type_id - type = Ticket::Article::Type.lookup(id: record.type_id) + type = Ticket::Article::Type.lookup(id: type_id) return true if type.nil? return true if !type.name.start_with?('facebook') - CommunicateFacebookJob.perform_later(record.id) + CommunicateFacebookJob.perform_later(id) end end diff --git a/app/models/concerns/ticket/article/enqueue_communicate_sms_job.rb b/app/models/concerns/ticket/article/enqueue_communicate_sms_job.rb new file mode 100644 index 000000000..7b82f9a3e --- /dev/null +++ b/app/models/concerns/ticket/article/enqueue_communicate_sms_job.rb @@ -0,0 +1,34 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +# Schedules a backgrond communication job for new SMS articles. +module Ticket::Article::EnqueueCommunicateSmsJob + extend ActiveSupport::Concern + + included do + after_create :ticket_article_enqueue_communicate_sms_job + end + + private + + def ticket_article_enqueue_communicate_sms_job + + # return if we run import mode + return true if Setting.get('import_mode') + + # if sender is customer, do not communicate + return true if !sender_id + + sender = Ticket::Article::Sender.lookup(id: sender_id) + return true if sender.nil? + return true if sender.name == 'Customer' + + # only apply on sms + return true if !type_id + + type = Ticket::Article::Type.lookup(id: type_id) + return true if type.nil? + return true if type.name != 'sms' + + CommunicateSmsJob.perform_later(id) + end +end diff --git a/app/models/concerns/ticket/article/enqueue_communicate_telegram_job.rb b/app/models/concerns/ticket/article/enqueue_communicate_telegram_job.rb new file mode 100644 index 000000000..ba24a4599 --- /dev/null +++ b/app/models/concerns/ticket/article/enqueue_communicate_telegram_job.rb @@ -0,0 +1,34 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +# Schedules a backgrond communication job for new telegram articles. +module Ticket::Article::EnqueueCommunicateTelegramJob + extend ActiveSupport::Concern + + included do + after_create :ticket_article_enqueue_communicate_telegram_job + end + + private + + def ticket_article_enqueue_communicate_telegram_job + + # return if we run import mode + return true if Setting.get('import_mode') + + # if sender is customer, do not communicate + return true if !sender_id + + sender = Ticket::Article::Sender.lookup(id: sender_id) + return true if sender.nil? + return true if sender.name == 'Customer' + + # only apply on telegram messages + return true if !type_id + + type = Ticket::Article::Type.lookup(id: type_id) + return true if !type.name.match?(/\Atelegram/i) + + CommunicateTelegramJob.perform_later(id) + end + +end diff --git a/app/models/observer/ticket/article/communicate_twitter.rb b/app/models/concerns/ticket/article/enqueue_communicate_twitter_job.rb similarity index 53% rename from app/models/observer/ticket/article/communicate_twitter.rb rename to app/models/concerns/ticket/article/enqueue_communicate_twitter_job.rb index 9b4f9998f..2c55ba376 100644 --- a/app/models/observer/ticket/article/communicate_twitter.rb +++ b/app/models/concerns/ticket/article/enqueue_communicate_twitter_job.rb @@ -1,9 +1,16 @@ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ -class Observer::Ticket::Article::CommunicateTwitter < ActiveRecord::Observer - observe 'ticket::_article' +# Schedules a backgrond communication job for new twitter articles. +module Ticket::Article::EnqueueCommunicateTwitterJob + extend ActiveSupport::Concern - def after_create(record) + included do + after_create :ticket_article_enqueue_communicate_twitter_job + end + + private + + def ticket_article_enqueue_communicate_twitter_job # return if we run import mode return true if Setting.get('import_mode') @@ -13,22 +20,22 @@ class Observer::Ticket::Article::CommunicateTwitter < ActiveRecord::Observer return true if ApplicationHandleInfo.postmaster? # if sender is customer, do not communicate - return true if !record.sender_id + return true if !sender_id - sender = Ticket::Article::Sender.lookup(id: record.sender_id) + sender = Ticket::Article::Sender.lookup(id: sender_id) return true if sender.nil? return true if sender.name == 'Customer' # only apply on tweets - return true if !record.type_id + return true if !type_id - type = Ticket::Article::Type.lookup(id: record.type_id) + type = Ticket::Article::Type.lookup(id: type_id) return true if type.nil? return true if !type.name.match?(/\Atwitter/i) - raise Exceptions::UnprocessableEntity, 'twitter to: parameter is missing' if record.to.blank? && type['name'] == 'twitter direct-message' + raise Exceptions::UnprocessableEntity, 'twitter to: parameter is missing' if to.blank? && type['name'] == 'twitter direct-message' - CommunicateTwitterJob.perform_later(record.id) + CommunicateTwitterJob.perform_later(id) end end diff --git a/app/models/observer/ticket/reset_new_state.rb b/app/models/concerns/ticket/article/resets_ticket_state.rb similarity index 60% rename from app/models/observer/ticket/reset_new_state.rb rename to app/models/concerns/ticket/article/resets_ticket_state.rb index 37ea9315b..42f179b33 100644 --- a/app/models/observer/ticket/reset_new_state.rb +++ b/app/models/concerns/ticket/article/resets_ticket_state.rb @@ -1,9 +1,16 @@ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ -class Observer::Ticket::ResetNewState < ActiveRecord::Observer - observe 'ticket::_article' +# Reopens the ticket in case certain new articles are created. +module Ticket::Article::ResetsTicketState + extend ActiveSupport::Concern - def after_create(record) + included do + after_create :ticket_article_reset_ticket_state + end + + private + + def ticket_article_reset_ticket_state # return if we run import mode return true if Setting.get('import_mode') @@ -12,16 +19,16 @@ class Observer::Ticket::ResetNewState < ActiveRecord::Observer return true if ApplicationHandleInfo.postmaster? # if article in internal - return true if record.internal + return true if internal # if sender is agent - return true if Ticket::Article::Sender.lookup(id: record.sender_id).name != 'Agent' + return true if Ticket::Article::Sender.lookup(id: sender_id).name != 'Agent' # if article is a message to customer - return true if !Ticket::Article::Type.lookup(id: record.type_id).communication + return true if !Ticket::Article::Type.lookup(id: type_id).communication # if current ticket state is still new - ticket = Ticket.find_by(id: record.ticket_id) + ticket = Ticket.find_by(id: ticket_id) return true if !ticket new_state = Ticket::State.find_by(default_create: true) diff --git a/app/models/concerns/ticket/calls_stats_ticket_reopen_log.rb b/app/models/concerns/ticket/calls_stats_ticket_reopen_log.rb new file mode 100644 index 000000000..7dc1757b8 --- /dev/null +++ b/app/models/concerns/ticket/calls_stats_ticket_reopen_log.rb @@ -0,0 +1,23 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +require_dependency 'stats/ticket_reopen' + +# Adds new and updated tickets to the reopen log processing. +module Ticket::CallsStatsTicketReopenLog + extend ActiveSupport::Concern + + included do + before_create :ticket_call_stats_ticket_reopen_log + before_update :ticket_call_stats_ticket_reopen_log + end + + private + + def ticket_call_stats_ticket_reopen_log + + # return if we run import mode + return if Setting.get('import_mode') + + Stats::TicketReopen.log('Ticket', id, saved_changes, updated_by_id) + end +end diff --git a/app/models/concerns/ticket/enqueues_user_ticket_counter_job.rb b/app/models/concerns/ticket/enqueues_user_ticket_counter_job.rb new file mode 100644 index 000000000..eb1d3e66c --- /dev/null +++ b/app/models/concerns/ticket/enqueues_user_ticket_counter_job.rb @@ -0,0 +1,30 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +# Adds a background job to update the user's ticket counter on ticket changes. +module Ticket::EnqueuesUserTicketCounterJob + extend ActiveSupport::Concern + + included do + after_commit :enqueue_user_ticket_counter_job + end + + private + + def enqueue_user_ticket_counter_job + # return if we run import mode + return true if Setting.get('import_mode') + + return true if BulkImportInfo.enabled? + + return true if destroyed? + + return true if !customer_id + + # send background job + TicketUserTicketCounterJob.perform_later( + customer_id, + UserInfo.current_user_id || updated_by_id, + ) + end + +end diff --git a/app/models/concerns/ticket/resets_pending_time_seconds.rb b/app/models/concerns/ticket/resets_pending_time_seconds.rb new file mode 100644 index 000000000..094c1aee2 --- /dev/null +++ b/app/models/concerns/ticket/resets_pending_time_seconds.rb @@ -0,0 +1,21 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +# Ensures pending time is always zero-seconds. +module Ticket::ResetsPendingTimeSeconds + extend ActiveSupport::Concern + + included do + before_create :ticket_reset_pending_time_seconds + before_update :ticket_reset_pending_time_seconds + end + + private + + def ticket_reset_pending_time_seconds + return true if pending_time.blank? + return true if !pending_time_changed? + return true if pending_time.sec.zero? + + self.pending_time = pending_time.change sec: 0 + end +end diff --git a/app/models/observer/ticket/close_time.rb b/app/models/concerns/ticket/sets_close_time.rb similarity index 51% rename from app/models/observer/ticket/close_time.rb rename to app/models/concerns/ticket/sets_close_time.rb index 2d884cfcb..bb31e75fc 100644 --- a/app/models/observer/ticket/close_time.rb +++ b/app/models/concerns/ticket/sets_close_time.rb @@ -1,34 +1,32 @@ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ -class Observer::Ticket::CloseTime < ActiveRecord::Observer - observe 'ticket' +# Adds close time (if missing) when tickets are closed. +module Ticket::SetsCloseTime + extend ActiveSupport::Concern - def before_create(record) - _check(record) - end - - def before_update(record) - _check(record) + included do + before_create :ticket_set_close_time + before_update :ticket_set_close_time end private - def _check(record) + def ticket_set_close_time # return if we run import mode return true if Setting.get('import_mode') # check if close_at is already set - return true if record.close_at + return true if close_at # check if ticket is closed now - return true if !record.state_id + return true if !state_id - state = Ticket::State.lookup(id: record.state_id) + state = Ticket::State.lookup(id: state_id) state_type = Ticket::StateType.lookup(id: state.state_type_id) return true if state_type.name != 'closed' # set close_at - record.close_at = Time.zone.now + self.close_at = Time.zone.now end end diff --git a/app/models/concerns/ticket/sets_last_owner_update_time.rb b/app/models/concerns/ticket/sets_last_owner_update_time.rb new file mode 100644 index 000000000..d99331ec9 --- /dev/null +++ b/app/models/concerns/ticket/sets_last_owner_update_time.rb @@ -0,0 +1,49 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +# Adds a last_owner_update time on ticket changes. +module Ticket::SetsLastOwnerUpdateTime + extend ActiveSupport::Concern + + included do + before_create :ticket_set_last_owner_update_time + before_update :ticket_set_last_owner_update_time + end + + private + + def ticket_set_last_owner_update_time + + # return if we run import mode + return true if Setting.get('import_mode') + # check if owner, state or group has changed + return true if changes_to_save['owner_id'].blank? && changes_to_save['state_id'].blank? && changes_to_save['group_id'].blank? && changes_to_save['last_contact_agent_at'].blank? + + # check if owner is nobody + if changes_to_save['owner_id'].present? && changes_to_save['owner_id'][1] == 1 + self.last_owner_update_at = nil + return true + end + + # check if group is change + if changes_to_save['group_id'].present? + group = Group.lookup(id: changes_to_save['group_id'][1]) + return true if !group + + if group.assignment_timeout.blank? || group.assignment_timeout.zero? + self.last_owner_update_at = nil + return true + end + end + + # check if state is not new/open + if changes_to_save['state_id'].present? + state_ids = Ticket::State.by_category(:work_on).pluck(:id) + if state_ids.exclude?(changes_to_save['state_id'][1]) + self.last_owner_update_at = nil + return true + end + end + + self.last_owner_update_at = Time.zone.now + end +end diff --git a/app/models/concerns/ticket/sets_online_notification_seen.rb b/app/models/concerns/ticket/sets_online_notification_seen.rb new file mode 100644 index 000000000..e8ea0be7a --- /dev/null +++ b/app/models/concerns/ticket/sets_online_notification_seen.rb @@ -0,0 +1,30 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +# Schedules a background job to update the user's ticket seen information on ticket changes. +module Ticket::SetsOnlineNotificationSeen + extend ActiveSupport::Concern + + included do + after_create :ticket_set_online_notification_seen + after_update :ticket_set_online_notification_seen + end + + private + + def ticket_set_online_notification_seen + + # return if we run import mode + return false if Setting.get('import_mode') + + # set seen only if state has changes + return false if !saved_changes? + return false if saved_changes['state_id'].blank? + + # check if existing online notifications for this ticket should be set to seen + return true if !online_notification_seen_state + + # set all online notifications to seen + # send background job + TicketOnlineNotificationSeenJob.perform_later(id, updated_by_id) + end +end diff --git a/app/models/concerns/ticket/touches_associations.rb b/app/models/concerns/ticket/touches_associations.rb new file mode 100644 index 000000000..808a9090d --- /dev/null +++ b/app/models/concerns/ticket/touches_associations.rb @@ -0,0 +1,37 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +# Update assigned customer and organization change_time information on ticket changes. +module Ticket::TouchesAssociations + extend ActiveSupport::Concern + + included do + after_create :ticket_touch_associations + after_update :ticket_touch_associations + after_destroy :ticket_touch_associations + end + + private + + def ticket_touch_associations + + # return if we run import mode + return true if Setting.get('import_mode') + + # touch old customer if changed + customer_id_changed = saved_changes['customer_id'] + if customer_id_changed && customer_id_changed[0] != customer_id_changed[1] && customer_id_changed[0] + User.find(customer_id_changed[0]).touch # rubocop:disable Rails/SkipsModelValidations + end + + # touch new/current customer + customer&.touch # rubocop:disable Rails/SkipsModelValidations + + # touch old organization if changed + organization_id_changed = saved_changes['organization_id'] + if organization_id_changed && organization_id_changed[0] != organization_id_changed[1] && organization_id_changed[0] + Organization.find(organization_id_changed[0]).touch # rubocop:disable Rails/SkipsModelValidations + end + + organization&.touch # rubocop:disable Rails/SkipsModelValidations + end +end diff --git a/app/models/observer/user/geo.rb b/app/models/concerns/user/performs_geo_lookup.rb similarity index 56% rename from app/models/observer/user/geo.rb rename to app/models/concerns/user/performs_geo_lookup.rb index 24f210e72..c91527555 100644 --- a/app/models/observer/user/geo.rb +++ b/app/models/concerns/user/performs_geo_lookup.rb @@ -1,26 +1,23 @@ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ -class Observer::User::Geo < ActiveRecord::Observer - observe 'user' +# Perform geo data lookup on user changes. +module User::PerformsGeoLookup + extend ActiveSupport::Concern - def before_create(record) - check_geo(record) - true + included do + before_create :user_check_geo_location + before_update :user_check_geo_location end - def before_update(record) - check_geo(record) - true - end + private - # check if geo need to be updated - def check_geo(record) + def user_check_geo_location location = %w[address street zip city country] # check if geo update is needed based on old/new location - if record.id - current = User.find_by(id: record.id) + if id + current = User.find_by(id: id) return if !current current_location = {} @@ -32,27 +29,26 @@ class Observer::User::Geo < ActiveRecord::Observer # get full address next_location = {} location.each do |item| - next_location[item] = record[item] + next_location[item] = attributes[item] end # return if address hasn't changed and geo data is already available - return if (current_location == next_location) && record.preferences['lat'] && record.preferences['lng'] + return if (current_location == next_location) && preferences['lat'] && preferences['lng'] # geo update - geo_update(record) + user_update_geo_location end - # update geo data of user - def geo_update(record) + def user_update_geo_location address = '' location = %w[address street zip city country] location.each do |item| - next if record[item].blank? + next if attributes[item].blank? if address.present? address += ', ' end - address += record[item] + address += attributes[item] end # return if no address is given @@ -60,10 +56,11 @@ class Observer::User::Geo < ActiveRecord::Observer # lookup latlng = Service::GeoLocation.geocode(address) + return if !latlng # store data - record.preferences['lat'] = latlng[0] - record.preferences['lng'] = latlng[1] + preferences['lat'] = latlng[0] + preferences['lng'] = latlng[1] end end diff --git a/app/models/concerns/user/touches_organization.rb b/app/models/concerns/user/touches_organization.rb new file mode 100644 index 000000000..ef5390634 --- /dev/null +++ b/app/models/concerns/user/touches_organization.rb @@ -0,0 +1,36 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +# Update assigned organization change_time information on user changes. +module User::TouchesOrganization + extend ActiveSupport::Concern + + included do + after_create :touch_user_organization + after_update :touch_user_organization + after_destroy :touch_user_organization + end + + private + + def touch_user_organization + + # return if we run import mode + return true if Setting.get('import_mode') + + organization_id_changed = saved_changes['organization_id'] + return true if !organization_id_changed + + return true if organization_id_changed[0] == organization_id_changed[1] + + # touch old organization + if organization_id_changed[0] + old_organization = Organization.find(organization_id_changed[0]) + old_organization&.touch # rubocop:disable Rails/SkipsModelValidations + end + + # touch new/current organization + organization&.touch # rubocop:disable Rails/SkipsModelValidations + + true + end +end diff --git a/app/models/concerns/user/updates_ticket_organization.rb b/app/models/concerns/user/updates_ticket_organization.rb new file mode 100644 index 000000000..8fd563e2e --- /dev/null +++ b/app/models/concerns/user/updates_ticket_organization.rb @@ -0,0 +1,28 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +# If a user is assigned to another organization, also assign their latest tickets to it. +module User::UpdatesTicketOrganization + extend ActiveSupport::Concern + + included do + after_create :user_update_ticket_organization + after_update :user_update_ticket_organization + end + + private + + def user_update_ticket_organization + + # check if organization has changed + return true if !saved_change_to_attribute?('organization_id') + + # update last 100 tickets of user + tickets = Ticket.where(customer_id: id).limit(100) + tickets.each do |ticket| + if ticket.organization_id != organization_id + ticket.organization_id = organization_id + ticket.save + end + end + end +end diff --git a/app/models/observer/tag/ticket_history.rb b/app/models/observer/tag/ticket_history.rb deleted file mode 100644 index e9d28335a..000000000 --- a/app/models/observer/tag/ticket_history.rb +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ -class Observer::Tag::TicketHistory < ActiveRecord::Observer - observe 'tag' - - def after_create(record) - - # just process ticket object tags - return true if record.tag_object.name != 'Ticket' - - # add ticket history - History.add( - o_id: record.o_id, - history_type: 'added', - history_object: 'Ticket', - history_attribute: 'tag', - value_to: record.tag_item.name, - created_by_id: record.created_by_id, - ) - end - - def after_destroy(record) - - # just process ticket object tags - return true if record.tag_object.name != 'Ticket' - - # add ticket history - History.add( - o_id: record.o_id, - history_type: 'removed', - history_object: 'Ticket', - history_attribute: 'tag', - value_to: record.tag_item.name, - created_by_id: record.created_by_id, - ) - end -end diff --git a/app/models/observer/ticket/article/communicate_sms.rb b/app/models/observer/ticket/article/communicate_sms.rb deleted file mode 100644 index 81e97096d..000000000 --- a/app/models/observer/ticket/article/communicate_sms.rb +++ /dev/null @@ -1,25 +0,0 @@ -class Observer::Ticket::Article::CommunicateSms < ActiveRecord::Observer - observe 'ticket::_article' - - def after_create(record) - - # return if we run import mode - return true if Setting.get('import_mode') - - # if sender is customer, do not communicate - return true if !record.sender_id - - sender = Ticket::Article::Sender.lookup(id: record.sender_id) - return true if sender.nil? - return true if sender.name == 'Customer' - - # only apply on sms - return true if !record.type_id - - type = Ticket::Article::Type.lookup(id: record.type_id) - return true if type.nil? - return true if type.name != 'sms' - - CommunicateSmsJob.perform_later(record.id) - end -end diff --git a/app/models/observer/ticket/article/communicate_telegram.rb b/app/models/observer/ticket/article/communicate_telegram.rb deleted file mode 100644 index d60dfe578..000000000 --- a/app/models/observer/ticket/article/communicate_telegram.rb +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ - -class Observer::Ticket::Article::CommunicateTelegram < ActiveRecord::Observer - observe 'ticket::_article' - - def after_create(record) - - # return if we run import mode - return true if Setting.get('import_mode') - - # if sender is customer, do not communicate - return true if !record.sender_id - - sender = Ticket::Article::Sender.lookup(id: record.sender_id) - return true if sender.nil? - return true if sender.name == 'Customer' - - # only apply on telegram messages - return true if !record.type_id - - type = Ticket::Article::Type.lookup(id: record.type_id) - return true if !type.name.match?(/\Atelegram/i) - - CommunicateTelegramJob.perform_later(record.id) - end - -end diff --git a/app/models/observer/ticket/article/fillup_from_origin_by_id.rb b/app/models/observer/ticket/article/fillup_from_origin_by_id.rb deleted file mode 100644 index dd1e78b92..000000000 --- a/app/models/observer/ticket/article/fillup_from_origin_by_id.rb +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ - -class Observer::Ticket::Article::FillupFromOriginById < ActiveRecord::Observer - observe 'ticket::_article' - - def before_create(record) - - # return if we run import mode - return true if Setting.get('import_mode') - - # only do fill origin_by_id if article got created via application_server (e. g. not - # if article and sender type is set via *.postmaster) - return true if ApplicationHandleInfo.postmaster? - - # check if origin_by_id exists - return true if record.origin_by_id.present? - return true if record.ticket.blank? - return true if record.ticket.customer_id.blank? - return true if record.sender_id.blank? - return true if record.sender.name != 'Customer' - - type_name = record.type.name - return true if type_name != 'phone' && type_name != 'note' && type_name != 'web' - - record.origin_by_id = record.ticket.customer_id - end -end diff --git a/app/models/observer/ticket/last_owner_update.rb b/app/models/observer/ticket/last_owner_update.rb deleted file mode 100644 index 4e113e77a..000000000 --- a/app/models/observer/ticket/last_owner_update.rb +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ - -class Observer::Ticket::LastOwnerUpdate < ActiveRecord::Observer - observe 'ticket' - - def before_create(record) - _check(record) - end - - def before_update(record) - _check(record) - end - - private - - def _check(record) - - # return if we run import mode - return true if Setting.get('import_mode') - - # check if owner, state or group has changed - return true if record.changes_to_save['owner_id'].blank? && record.changes_to_save['state_id'].blank? && record.changes_to_save['group_id'].blank? && record.changes_to_save['last_contact_agent_at'].blank? - - # check if owner is nobody - if record.changes_to_save['owner_id'].present? && record.changes_to_save['owner_id'][1] == 1 - record.last_owner_update_at = nil - return true - end - - # check if group is change - if record.changes_to_save['group_id'].present? - group = Group.lookup(id: record.changes_to_save['group_id'][1]) - return true if !group - - if group.assignment_timeout.blank? || group.assignment_timeout.zero? - record.last_owner_update_at = nil - return true - end - end - - # check if state is not new/open - if record.changes_to_save['state_id'].present? - state_ids = Ticket::State.by_category(:work_on).pluck(:id) - if state_ids.exclude?(record.changes_to_save['state_id'][1]) - record.last_owner_update_at = nil - return true - end - end - - record.last_owner_update_at = Time.zone.now - end -end diff --git a/app/models/observer/ticket/online_notification_seen.rb b/app/models/observer/ticket/online_notification_seen.rb deleted file mode 100644 index 4125464da..000000000 --- a/app/models/observer/ticket/online_notification_seen.rb +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ - -class Observer::Ticket::OnlineNotificationSeen < ActiveRecord::Observer - observe 'ticket' - - def after_create(record) - _check(record) - end - - def after_update(record) - _check(record) - end - - private - - def _check(record) - - # return if we run import mode - return false if Setting.get('import_mode') - - # set seen only if state has changes - return false if !record.saved_changes? - return false if record.saved_changes['state_id'].blank? - - # check if existing online notifications for this ticket should be set to seen - return true if !record.online_notification_seen_state - - # set all online notifications to seen - # send background job - TicketOnlineNotificationSeenJob.perform_later(record.id, record.updated_by_id) - end -end diff --git a/app/models/observer/ticket/pending_time.rb b/app/models/observer/ticket/pending_time.rb deleted file mode 100644 index 23b52ab27..000000000 --- a/app/models/observer/ticket/pending_time.rb +++ /dev/null @@ -1,22 +0,0 @@ -# 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/observer/ticket/ref_object_touch.rb b/app/models/observer/ticket/ref_object_touch.rb deleted file mode 100644 index f59730db9..000000000 --- a/app/models/observer/ticket/ref_object_touch.rb +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ - -class Observer::Ticket::RefObjectTouch < ActiveRecord::Observer - observe 'ticket' - - def after_create(record) - ref_object_touch(record) - end - - def after_update(record) - ref_object_touch(record) - end - - def after_destroy(record) - ref_object_touch(record) - end - - def ref_object_touch(record) - - # return if we run import mode - return true if Setting.get('import_mode') - - # touch old customer if changed - cutomer_id_changed = record.saved_changes['customer_id'] - if cutomer_id_changed && cutomer_id_changed[0] != cutomer_id_changed[1] && cutomer_id_changed[0] - User.find(cutomer_id_changed[0]).touch # rubocop:disable Rails/SkipsModelValidations - end - - # touch new/current customer - record.customer&.touch # rubocop:disable Rails/SkipsModelValidations - - # touch old organization if changed - organization_id_changed = record.saved_changes['organization_id'] - if organization_id_changed && organization_id_changed[0] != organization_id_changed[1] && organization_id_changed[0] - Organization.find(organization_id_changed[0]).touch # rubocop:disable Rails/SkipsModelValidations - end - - # touch new/current organization - return true if !record.organization - - record.organization.touch # rubocop:disable Rails/SkipsModelValidations - end -end diff --git a/app/models/observer/ticket/stats_reopen.rb b/app/models/observer/ticket/stats_reopen.rb deleted file mode 100644 index 45e441400..000000000 --- a/app/models/observer/ticket/stats_reopen.rb +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ - -require_dependency 'stats/ticket_reopen' - -class Observer::Ticket::StatsReopen < ActiveRecord::Observer - - observe 'ticket' - - def after_create(record) - _check(record) - end - - def after_update(record) - _check(record) - end - - private - - def _check(record) - - # return if we run import mode - return if Setting.get('import_mode') - - Stats::TicketReopen.log('Ticket', record.id, record.saved_changes, record.updated_by_id) - end -end diff --git a/app/models/observer/ticket/user_ticket_counter.rb b/app/models/observer/ticket/user_ticket_counter.rb deleted file mode 100644 index 1137b610b..000000000 --- a/app/models/observer/ticket/user_ticket_counter.rb +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ - -class Observer::Ticket::UserTicketCounter < ActiveRecord::Observer - observe 'ticket' - - def after_commit(record) - user_ticket_counter_update(record) - end - - def user_ticket_counter_update(record) - - # return if we run import mode - return true if Setting.get('import_mode') - - return true if BulkImportInfo.enabled? - - return true if record.destroyed? - - return true if !record.customer_id - - # send background job - TicketUserTicketCounterJob.perform_later( - record.customer_id, - UserInfo.current_user_id || record.updated_by_id, - ) - end - -end diff --git a/app/models/observer/user/ref_object_touch.rb b/app/models/observer/user/ref_object_touch.rb deleted file mode 100644 index f39c4d056..000000000 --- a/app/models/observer/user/ref_object_touch.rb +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ - -class Observer::User::RefObjectTouch < ActiveRecord::Observer - observe 'user' - - def after_create(record) - ref_object_touch(record) - end - - def after_update(record) - ref_object_touch(record) - end - - def after_destroy(record) - ref_object_touch(record) - end - - def ref_object_touch(record) - - # return if we run import mode - return true if Setting.get('import_mode') - - organization_id_changed = record.saved_changes['organization_id'] - return true if !organization_id_changed - - return true if organization_id_changed[0] == organization_id_changed[1] - - # touch old organization - if organization_id_changed[0] - organization = Organization.find(organization_id_changed[0]) - organization.touch # rubocop:disable Rails/SkipsModelValidations - end - - # touch new/current organization - if record&.organization - record.organization.touch # rubocop:disable Rails/SkipsModelValidations - end - - true - end -end diff --git a/app/models/observer/user/ticket_organization.rb b/app/models/observer/user/ticket_organization.rb deleted file mode 100644 index dc60f967d..000000000 --- a/app/models/observer/user/ticket_organization.rb +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ - -class Observer::User::TicketOrganization < ActiveRecord::Observer - observe 'user' - - def after_create(record) - check_organization(record) - end - - def after_update(record) - check_organization(record) - end - - # check if organization need to be updated - def check_organization(record) - - # check if organization has changed - return true if !record.saved_change_to_attribute?('organization_id') - - # update last 100 tickets of user - tickets = Ticket.where(customer_id: record.id).limit(100) - tickets.each do |ticket| - if ticket.organization_id != record.organization_id - ticket.organization_id = record.organization_id - ticket.save - end - end - end - -end diff --git a/app/models/tag.rb b/app/models/tag.rb index 43f77950a..e786d83ad 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -1,6 +1,7 @@ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ class Tag < ApplicationModel + include Tag::WritesToTicketHistory belongs_to :tag_object, class_name: 'Tag::Object', optional: true belongs_to :tag_item, class_name: 'Tag::Item', optional: true diff --git a/app/models/ticket.rb b/app/models/ticket.rb index 182f560a0..7eebd3f58 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -15,6 +15,12 @@ class Ticket < ApplicationModel include HasLinks include HasObjectManagerAttributesValidation include HasTaskbars + include Ticket::CallsStatsTicketReopenLog + include Ticket::EnqueuesUserTicketCounterJob + include Ticket::ResetsPendingTimeSeconds + include Ticket::SetsCloseTime + include Ticket::SetsOnlineNotificationSeen + include Ticket::TouchesAssociations include ::Ticket::Escalation include ::Ticket::Subject @@ -27,6 +33,9 @@ class Ticket < ApplicationModel before_create :check_generate, :check_defaults, :check_title, :set_default_state, :set_default_priority before_update :check_defaults, :check_title, :reset_pending_time, :check_owner_active + # This must be loaded late as it depends on the internal before_create and before_update handlers of ticket.rb. + include Ticket::SetsLastOwnerUpdateTime + validates :group_id, presence: true activity_stream_permission 'ticket.agent' diff --git a/app/models/ticket/article.rb b/app/models/ticket/article.rb index 36708da1d..8afcc1c99 100644 --- a/app/models/ticket/article.rb +++ b/app/models/ticket/article.rb @@ -10,7 +10,18 @@ class Ticket::Article < ApplicationModel include HasObjectManagerAttributesValidation include Ticket::Article::Assets + include Ticket::Article::EnqueueCommunicateEmailJob + include Ticket::Article::EnqueueCommunicateFacebookJob + include Ticket::Article::EnqueueCommunicateSmsJob + include Ticket::Article::EnqueueCommunicateTelegramJob + include Ticket::Article::EnqueueCommunicateTwitterJob include Ticket::Article::HasTicketContactAttributesImpact + include Ticket::Article::ResetsTicketState + + # AddsMetadataGeneral depends on AddsMetadataOriginById, so load that first + include Ticket::Article::AddsMetadataOriginById + include Ticket::Article::AddsMetadataGeneral + include Ticket::Article::AddsMetadataEmail belongs_to :ticket, optional: true has_one :ticket_time_accounting, class_name: 'Ticket::TimeAccounting', foreign_key: :ticket_article_id, dependent: :destroy, inverse_of: :ticket_article diff --git a/app/models/user.rb b/app/models/user.rb index 3abd6338b..9c01912e2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -19,6 +19,9 @@ class User < ApplicationModel include User::Assets include User::Search include User::SearchIndex + include User::TouchesOrganization + include User::PerformsGeoLookup + include User::UpdatesTicketOrganization has_and_belongs_to_many :organizations, after_add: :cache_update, after_remove: :cache_update, class_name: 'Organization' has_and_belongs_to_many :overviews, dependent: :nullify diff --git a/config/application.rb b/config/application.rb index ff72c9734..02eb0083f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -27,26 +27,6 @@ module Zammad # config.active_record.observers = :cacher, :garbage_collector, :forum_observer config.active_record.observers = 'observer::_session', - 'observer::_ticket::_close_time', - 'observer::_ticket::_last_owner_update', - 'observer::_ticket::_pending_time', - 'observer::_ticket::_user_ticket_counter', - 'observer::_ticket::_article::_fillup_from_origin_by_id', - 'observer::_ticket::_article::_fillup_from_general', - 'observer::_ticket::_article::_fillup_from_email', - 'observer::_ticket::_article::_communicate_email', - 'observer::_ticket::_article::_communicate_facebook', - 'observer::_ticket::_article::_communicate_sms', - 'observer::_ticket::_article::_communicate_twitter', - 'observer::_ticket::_article::_communicate_telegram', - 'observer::_ticket::_reset_new_state', - 'observer::_ticket::_ref_object_touch', - 'observer::_ticket::_online_notification_seen', - 'observer::_ticket::_stats_reopen', - 'observer::_tag::_ticket_history', - 'observer::_user::_ref_object_touch', - 'observer::_user::_ticket_organization', - 'observer::_user::_geo', 'observer::_transaction' config.active_job.queue_adapter = :delayed_job diff --git a/spec/jobs/communicate_twitter_job_spec.rb b/spec/jobs/communicate_twitter_job_spec.rb index 839bf5520..f04a2f39d 100644 --- a/spec/jobs/communicate_twitter_job_spec.rb +++ b/spec/jobs/communicate_twitter_job_spec.rb @@ -5,13 +5,6 @@ RSpec.describe CommunicateTwitterJob, type: :job do let(:article) { create(:twitter_article, **(try(:factory_options) || {})) } describe 'core behavior', :use_vcr do - # This job runs automatically whenever an article is created. - # We disable this auto-execution so we can invoke it manually in the tests below. - around do |example| - ActiveRecord::Base.observers.disable('observer::_ticket::_article::_communicate_twitter') - example.run - ActiveRecord::Base.observers.enable('observer::_ticket::_article::_communicate_twitter') - end context 'for tweets' do let(:tweet_attributes) do diff --git a/spec/models/concerns/tag/writes_to_ticket_history_examples.rb b/spec/models/concerns/tag/writes_to_ticket_history_examples.rb new file mode 100644 index 000000000..12f9b33b8 --- /dev/null +++ b/spec/models/concerns/tag/writes_to_ticket_history_examples.rb @@ -0,0 +1,33 @@ +RSpec.shared_examples 'TagWritesToTicketHistory' do + subject { create(described_class.name.underscore) } + + # The concern is for the tag model, but the shared example needs to be loaded in the ticket test. + it 'can only be loaded for tickets' do + expect(described_class).to eq Ticket + end + + it 'creates a ticket history entry for tag_add' do # rubocop:disable RSpec/ExampleLength + subject.tag_add('foo', 1) + expect(subject.history_get.last).to include( + 'object' => described_class.name, + 'o_id' => subject.id, + 'type' => 'added', + 'attribute' => 'tag', + 'value_to' => 'foo', + 'value_from' => nil + ) + end + + it 'creates a ticket history entry for tag_remove' do # rubocop:disable RSpec/ExampleLength + subject.tag_add('foo', 1) + subject.tag_remove('foo', 1) + expect(subject.history_get.last).to include( + 'object' => described_class.name, + 'o_id' => subject.id, + 'type' => 'removed', + 'attribute' => 'tag', + 'value_to' => 'foo', + 'value_from' => nil + ) + end +end diff --git a/spec/models/observer/ticket/article/fillup_from_general_spec.rb b/spec/models/concerns/ticket/article/adds_metadata_general_spec.rb similarity index 80% rename from spec/models/observer/ticket/article/fillup_from_general_spec.rb rename to spec/models/concerns/ticket/article/adds_metadata_general_spec.rb index c73bfadc5..c1bb19834 100644 --- a/spec/models/observer/ticket/article/fillup_from_general_spec.rb +++ b/spec/models/concerns/ticket/article/adds_metadata_general_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Observer::Ticket::Article::FillupFromGeneral, current_user_id: -> { agent.id } do +RSpec.describe Ticket::Article::AddsMetadataGeneral, current_user_id: -> { agent.id } do let(:agent) { create(:agent) } context 'when customer is agent' do diff --git a/spec/models/concerns/ticket/article/enqueue_communicate_email_job_spec.rb b/spec/models/concerns/ticket/article/enqueue_communicate_email_job_spec.rb new file mode 100644 index 000000000..e6bcd3a76 --- /dev/null +++ b/spec/models/concerns/ticket/article/enqueue_communicate_email_job_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe Ticket::Article::EnqueueCommunicateEmailJob, performs_jobs: true do + before { allow(Delayed::Job).to receive(:enqueue).and_call_original } + + let(:article) { create(:ticket_article, **(try(:factory_options) || {})) } + + shared_examples 'for no-op' do + it 'is a no-op' do + expect { article }.not_to have_enqueued_job(TicketArticleCommunicateEmailJob) + end + end + + shared_examples 'for success' do + it 'enqueues the Email background job' do + expect { article }.to have_enqueued_job(TicketArticleCommunicateEmailJob) + end + end + + context 'when in Import Mode' do + before { Setting.set('import_mode', true) } + + include_examples 'for no-op' + end + + context 'when article is created during Channel::EmailParser#process', application_handle: 'scheduler.postmaster' do + include_examples 'for no-op' + end + + context 'when article is from a customer' do + let(:factory_options) { { sender_name: 'Customer' } } + + include_examples 'for no-op' + end + + context 'when article is an email' do + let(:factory_options) { { sender_name: 'Agent', type_name: 'email' } } + + include_examples 'for success' + end +end diff --git a/spec/models/concerns/ticket/article/enqueue_communicate_facebook_job_spec.rb b/spec/models/concerns/ticket/article/enqueue_communicate_facebook_job_spec.rb new file mode 100644 index 000000000..5f6cc1599 --- /dev/null +++ b/spec/models/concerns/ticket/article/enqueue_communicate_facebook_job_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe Ticket::Article::EnqueueCommunicateFacebookJob, performs_jobs: true do + before { allow(Delayed::Job).to receive(:enqueue).and_call_original } + + let(:article) { create(:ticket_article, **(try(:factory_options) || {})) } + + shared_examples 'for no-op' do + it 'is a no-op' do + expect { article }.not_to have_enqueued_job(CommunicateFacebookJob) + end + end + + shared_examples 'for success' do + it 'enqueues the Facebook background job' do + expect { article }.to have_enqueued_job(CommunicateFacebookJob) + end + end + + context 'when in Import Mode' do + before { Setting.set('import_mode', true) } + + include_examples 'for no-op' + end + + context 'when article is created during Channel::EmailParser#process', application_handle: 'scheduler.postmaster' do + include_examples 'for no-op' + end + + context 'when article is from a customer' do + let(:factory_options) { { sender_name: 'Customer' } } + + include_examples 'for no-op' + end + + context 'when article is a facebook post' do + let(:factory_options) { { sender_name: 'Agent', type_name: 'facebook feed post' } } + + include_examples 'for success' + end +end diff --git a/spec/models/concerns/ticket/article/enqueue_communicate_sms_job_spec.rb b/spec/models/concerns/ticket/article/enqueue_communicate_sms_job_spec.rb new file mode 100644 index 000000000..67e2f89ea --- /dev/null +++ b/spec/models/concerns/ticket/article/enqueue_communicate_sms_job_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe Ticket::Article::EnqueueCommunicateSmsJob, performs_jobs: true do + before { allow(Delayed::Job).to receive(:enqueue).and_call_original } + + let(:article) { create(:ticket_article, **(try(:factory_options) || {})) } + + shared_examples 'for no-op' do + it 'is a no-op' do + expect { article }.not_to have_enqueued_job(CommunicateSmsJob) + end + end + + shared_examples 'for success' do + it 'enqueues the SMS background job' do + expect { article }.to have_enqueued_job(CommunicateSmsJob) + end + end + + context 'when in Import Mode' do + before { Setting.set('import_mode', true) } + + include_examples 'for no-op' + end + + context 'when article is created during Channel::EmailParser#process', application_handle: 'scheduler.postmaster' do + include_examples 'for no-op' + end + + context 'when article is from a customer' do + let(:factory_options) { { sender_name: 'Customer' } } + + include_examples 'for no-op' + end + + context 'when article is an sms' do + let(:factory_options) { { sender_name: 'Agent', type_name: 'sms' } } + + include_examples 'for success' + end +end diff --git a/spec/models/concerns/ticket/article/enqueue_communicate_telegram_job_spec.rb b/spec/models/concerns/ticket/article/enqueue_communicate_telegram_job_spec.rb new file mode 100644 index 000000000..e01156c89 --- /dev/null +++ b/spec/models/concerns/ticket/article/enqueue_communicate_telegram_job_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe 'Ticket::Article::EnqueueCommunicateTelegramJob', performs_jobs: true do + before { allow(Delayed::Job).to receive(:enqueue).and_call_original } + + let(:article) { create(:ticket_article, **(try(:factory_options) || {})) } + + shared_examples 'for no-op' do + it 'is a no-op' do + expect { article }.not_to have_enqueued_job(CommunicateTelegramJob) + end + end + + shared_examples 'for success' do + it 'enqueues the Telegram background job' do + expect { article }.to have_enqueued_job(CommunicateTelegramJob) + end + end + + context 'when in Import Mode' do + before { Setting.set('import_mode', true) } + + include_examples 'for no-op' + end + + context 'when article is created during Channel::EmailParser#process', application_handle: 'scheduler.postmaster' do + include_examples 'for no-op' + end + + context 'when article is from a customer' do + let(:factory_options) { { sender_name: 'Customer' } } + + include_examples 'for no-op' + end + + context 'when article is a Telegram message' do + let(:factory_options) { { sender_name: 'Agent', type_name: 'telegram personal-message' } } + + include_examples 'for success' + end +end diff --git a/spec/models/observer/ticket/article/communicate_twitter_spec.rb b/spec/models/concerns/ticket/article/enqueue_communicate_twitter_job_spec.rb similarity index 91% rename from spec/models/observer/ticket/article/communicate_twitter_spec.rb rename to spec/models/concerns/ticket/article/enqueue_communicate_twitter_job_spec.rb index 25f1a4d57..099570ea3 100644 --- a/spec/models/observer/ticket/article/communicate_twitter_spec.rb +++ b/spec/models/concerns/ticket/article/enqueue_communicate_twitter_job_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Observer::Ticket::Article::CommunicateTwitter, performs_jobs: true do +RSpec.describe Ticket::Article::EnqueueCommunicateTwitterJob, performs_jobs: true do before { allow(Delayed::Job).to receive(:enqueue).and_call_original } let(:article) { create(:ticket_article, **(try(:factory_options) || {})) } @@ -17,7 +17,7 @@ RSpec.describe Observer::Ticket::Article::CommunicateTwitter, performs_jobs: tru end end - context 'in Import Mode' do + context 'when in Import Mode' do before { Setting.set('import_mode', true) } include_examples 'for no-op' @@ -50,7 +50,7 @@ RSpec.describe Observer::Ticket::Article::CommunicateTwitter, performs_jobs: tru include_examples 'for success' - context 'but #to attribute is missing' do + context 'when #to attribute is missing' do let(:factory_options) { { sender_name: 'Agent', type_name: 'twitter direct-message', to: nil } } it 'raises an error' do diff --git a/spec/models/concerns/ticket/calls_stats_ticket_reopen_log_examples.rb b/spec/models/concerns/ticket/calls_stats_ticket_reopen_log_examples.rb new file mode 100644 index 000000000..a2116ab4f --- /dev/null +++ b/spec/models/concerns/ticket/calls_stats_ticket_reopen_log_examples.rb @@ -0,0 +1,12 @@ +RSpec.shared_examples 'TicketCallsStatsTicketReopenLog' do + + it 'can only be loaded for Ticket' do + expect(described_class).to eq Ticket + end + + it 'calls Stats::TicketReopen.log' do + allow(Stats::TicketReopen).to receive(:log) + create(described_class.name.underscore) + expect(Stats::TicketReopen).to have_received(:log) + end +end diff --git a/spec/models/concerns/ticket/enqueues_user_ticket_counter_job_examples.rb b/spec/models/concerns/ticket/enqueues_user_ticket_counter_job_examples.rb new file mode 100644 index 000000000..2840bb8aa --- /dev/null +++ b/spec/models/concerns/ticket/enqueues_user_ticket_counter_job_examples.rb @@ -0,0 +1,10 @@ +RSpec.shared_examples 'TicketEnqueuesTicketUserTicketCounterJob', type: :job do + subject { create(described_class.name.underscore) } + + let(:customer) { create('customer') } + + it 'enqueues a job for the customer' do + subject.customer = customer + expect { subject.save }.to have_enqueued_job(TicketUserTicketCounterJob) + end +end diff --git a/spec/models/concerns/ticket/resets_pending_time_seconds_examples.rb b/spec/models/concerns/ticket/resets_pending_time_seconds_examples.rb new file mode 100644 index 000000000..eca2cf872 --- /dev/null +++ b/spec/models/concerns/ticket/resets_pending_time_seconds_examples.rb @@ -0,0 +1,12 @@ +RSpec.shared_examples 'TicketResetsPendingTimeSeconds' do + subject { create(described_class.name.underscore) } + + it 'can only be loaded for tickets' do + expect(described_class).to eq Ticket + end + + it 'resets pending_time seconds' do + subject.update(pending_time: Time.zone.parse('2007-02-10 15:30:45')) + expect(subject.pending_time).to eq(Time.zone.parse('2007-02-10 15:30:00')) + end +end diff --git a/spec/models/concerns/ticket/sets_close_time_examples.rb b/spec/models/concerns/ticket/sets_close_time_examples.rb new file mode 100644 index 000000000..69be5bf02 --- /dev/null +++ b/spec/models/concerns/ticket/sets_close_time_examples.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +RSpec.shared_examples 'TicketSetsCloseTime' do + subject { create(described_class.name.underscore) } + + it 'can only be loaded for tickets' do + expect(described_class).to eq Ticket + end + + before do + travel_to Time.zone.now + end + + it 'resets pending_time seconds' do + subject.update(state: Ticket::State.lookup(name: 'closed')) + expect(subject.close_at).to eq(Time.zone.now) + end +end diff --git a/spec/models/concerns/ticket/sets_last_owner_update_time_examples.rb b/spec/models/concerns/ticket/sets_last_owner_update_time_examples.rb new file mode 100644 index 000000000..c43a6a3ea --- /dev/null +++ b/spec/models/concerns/ticket/sets_last_owner_update_time_examples.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +RSpec.shared_examples 'TicketSetsLastOwnerUpdateTime' do + subject { create(described_class.name.underscore) } + + let(:new_owner) { create(:agent, groups: [subject.group]) } + + it 'can only be loaded for tickets' do + expect(described_class).to eq Ticket + end + + before do + travel_to Time.zone.now + end + + it 'has no last_owner_update_at initially' do + expect(subject.last_owner_update_at).to be_nil + end + + it 'gets last_owner_update_at after user change' do + subject.update(owner: new_owner) + expect(subject.last_owner_update_at).to eq(Time.zone.now) + end +end diff --git a/spec/models/concerns/user/performs_geo_lookup_examples.rb b/spec/models/concerns/user/performs_geo_lookup_examples.rb new file mode 100644 index 000000000..2a5c27fb6 --- /dev/null +++ b/spec/models/concerns/user/performs_geo_lookup_examples.rb @@ -0,0 +1,16 @@ +RSpec.shared_examples 'UserPerformsGeoLookup' do + + it 'can only be loaded for User' do + expect(described_class).to eq User + end + + it 'performs geo lookup' do + + # Mock the geo lookup as it requires an API key. + allow(Service::GeoLocation).to receive(:geocode).with('Marienstraße 18, 10117, Berlin, Germany').and_return([10.0, 20.0]) + + user = create(described_class.name.underscore, street: 'Marienstraße 18', zip: '10117', city: 'Berlin', country: 'Germany') + + expect(user.preferences).to include(lat: 10.0, lng: 20.0) + end +end diff --git a/spec/models/ticket_spec.rb b/spec/models/ticket_spec.rb index 313fe7413..c7aa35472 100644 --- a/spec/models/ticket_spec.rb +++ b/spec/models/ticket_spec.rb @@ -4,9 +4,15 @@ require 'models/concerns/can_be_imported_examples' require 'models/concerns/can_csv_import_examples' require 'models/concerns/has_history_examples' require 'models/concerns/has_tags_examples' +require 'models/concerns/tag/writes_to_ticket_history_examples' require 'models/concerns/has_taskbars_examples' require 'models/concerns/has_xss_sanitized_note_examples' require 'models/concerns/has_object_manager_attributes_validation_examples' +require 'models/concerns/ticket/calls_stats_ticket_reopen_log_examples' +require 'models/concerns/ticket/enqueues_user_ticket_counter_job_examples' +require 'models/concerns/ticket/resets_pending_time_seconds_examples' +require 'models/concerns/ticket/sets_close_time_examples' +require 'models/concerns/ticket/sets_last_owner_update_time_examples' require 'models/ticket/escalation_examples' RSpec.describe Ticket, type: :model do @@ -17,10 +23,16 @@ RSpec.describe Ticket, type: :model do it_behaves_like 'CanCsvImport' it_behaves_like 'HasHistory', history_relation_object: 'Ticket::Article' it_behaves_like 'HasTags' + it_behaves_like 'TagWritesToTicketHistory' it_behaves_like 'HasTaskbars' it_behaves_like 'HasXssSanitizedNote', model_factory: :ticket it_behaves_like 'HasObjectManagerAttributesValidation' it_behaves_like 'Ticket::Escalation' + it_behaves_like 'TicketCallsStatsTicketReopenLog' + it_behaves_like 'TicketEnqueuesTicketUserTicketCounterJob' + it_behaves_like 'TicketResetsPendingTimeSeconds' + it_behaves_like 'TicketSetsCloseTime' + it_behaves_like 'TicketSetsLastOwnerUpdateTime' describe 'Class methods:' do describe '.selectors' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index fcbf74a07..de5b079d0 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -7,6 +7,7 @@ require 'models/concerns/has_groups_permissions_examples' require 'models/concerns/has_xss_sanitized_note_examples' require 'models/concerns/can_be_imported_examples' require 'models/concerns/has_object_manager_attributes_validation_examples' +require 'models/concerns/user/performs_geo_lookup_examples' require 'models/user/has_ticket_create_screen_impact_examples' require 'models/user/can_lookup_search_index_attributes_examples' require 'models/concerns/has_taskbars_examples' @@ -29,6 +30,7 @@ RSpec.describe User, type: :model do it_behaves_like 'User::HasTicketCreateScreenImpact' it_behaves_like 'CanLookupSearchIndexAttributes' it_behaves_like 'HasTaskbars' + it_behaves_like 'UserPerformsGeoLookup' describe 'Class methods:' do describe '.authenticate' do