diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 497225703..5bc9a9639 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -48,4 +48,3 @@ before_script: - source /etc/profile.d/rvm.sh - bundle install -j $(nproc) --path vendor - bundle exec ruby script/build/database_config.rb - diff --git a/.rubocop/todo.rspec.yml b/.rubocop/todo.rspec.yml index d4081517d..43b2234b7 100644 --- a/.rubocop/todo.rspec.yml +++ b/.rubocop/todo.rspec.yml @@ -31,6 +31,7 @@ RSpec/ContextWording: - 'spec/db/migrate/issue_2345_es_attachment_max_size_in_mb_setting_lower_default_spec.rb' - 'spec/db/migrate/issue_2368_add_indices_to_histories_and_tickets_spec.rb' - 'spec/db/migrate/issue_2541_fix_notification_email_without_body_spec.rb' + - 'spec/jobs/communicate_twitter_job_spec.rb' - 'spec/jobs/concerns/has_active_job_lock_spec.rb' - 'spec/jobs/concerns/has_collection_update_spec.rb' - 'spec/jobs/concerns/has_ticket_create_screen_impact_spec.rb' @@ -116,7 +117,6 @@ RSpec/ContextWording: - 'spec/models/object_manager/attribute/validation/required_spec.rb' - 'spec/models/object_manager/attribute/validation_spec.rb' - 'spec/models/object_manager/attribute_spec.rb' - - 'spec/models/observer/ticket/article/communicate_twitter/background_job_spec.rb' - 'spec/models/observer/ticket/article/communicate_twitter_spec.rb' - 'spec/models/overview_spec.rb' - 'spec/models/role_spec.rb' @@ -170,7 +170,9 @@ RSpec/ExampleLength: - 'spec/db/migrate/issue_2867_footer_header_public_link_spec.rb' - 'spec/db/migrate/object_manager_attribute_date_remove_future_past_spec.rb' - 'spec/db/migrate/rename_locale_on_users_spec.rb' + - 'spec/jobs/communicate_twitter_job_spec.rb' - 'spec/jobs/concerns/has_active_job_lock_spec.rb' + - 'spec/jobs/migrate_ldap_samaccountname_to_uid_job_spec.rb' - 'spec/jobs/ticket_user_ticket_counter_job_spec.rb' - 'spec/jobs/user_device_log_job_spec.rb' - 'spec/lib/auth/internal_spec.rb' @@ -209,7 +211,6 @@ RSpec/ExampleLength: - 'spec/lib/ldap/group_spec.rb' - 'spec/lib/ldap/user_spec.rb' - 'spec/lib/ldap_spec.rb' - - 'spec/lib/migration_job/ldap_samaccountname_to_uid_spec.rb' - 'spec/lib/notification_factory/mailer_spec.rb' - 'spec/lib/notification_factory/renderer_spec.rb' - 'spec/lib/notification_factory/slack_spec.rb' @@ -249,7 +250,6 @@ RSpec/ExampleLength: - 'spec/models/history_spec.rb' - 'spec/models/import_job_spec.rb' - 'spec/models/object_manager/attribute_spec.rb' - - 'spec/models/observer/ticket/article/communicate_twitter/background_job_spec.rb' - 'spec/models/overview_spec.rb' - 'spec/models/recent_view_spec.rb' - 'spec/models/role_group_spec.rb' @@ -357,6 +357,7 @@ RSpec/InstanceVariable: RSpec/LetSetup: Exclude: + - 'spec/jobs/communicate_twitter_job_spec.rb' - 'spec/jobs/ticket_online_notification_seen_job_spec.rb' - 'spec/lib/external_credential/google_spec.rb' - 'spec/lib/external_credential/office365_spec.rb' @@ -367,7 +368,6 @@ RSpec/LetSetup: - 'spec/models/cti/caller_id_spec.rb' - 'spec/models/cti/driver/base_spec.rb' - 'spec/models/cti/log_spec.rb' - - 'spec/models/observer/ticket/article/communicate_twitter/background_job_spec.rb' - 'spec/models/organization_spec.rb' - 'spec/models/role_group_spec.rb' - 'spec/models/tag_spec.rb' @@ -431,7 +431,6 @@ RSpec/MessageSpies: - 'spec/lib/ldap/guid_spec.rb' - 'spec/lib/ldap/user_spec.rb' - 'spec/lib/ldap_spec.rb' - - 'spec/lib/migration_job/ldap_samaccountname_to_uid_spec.rb' - 'spec/lib/sequencer/sequence/import/ldap/users_spec.rb' - 'spec/lib/sequencer/unit/common/attribute_mapper_spec.rb' - 'spec/lib/sequencer/unit/import/common/mapping/flat_keys_spec.rb' @@ -468,7 +467,9 @@ RSpec/MultipleExpectations: - 'spec/db/migrate/issue_1905_exchange_login_from_remote_id_spec.rb' - 'spec/db/migrate/object_manager_attribute_date_remove_future_past_spec.rb' - 'spec/db/migrate/rename_locale_on_users_spec.rb' + - 'spec/jobs/communicate_twitter_job_spec.rb' - 'spec/jobs/concerns/has_active_job_lock_spec.rb' + - 'spec/jobs/migrate_ldap_samaccountname_to_uid_job_spec.rb' - 'spec/jobs/search_index_job_spec.rb' - 'spec/jobs/ticket_user_ticket_counter_job_spec.rb' - 'spec/lib/auth/developer_spec.rb' @@ -505,7 +506,6 @@ RSpec/MultipleExpectations: - 'spec/lib/ldap/guid_spec.rb' - 'spec/lib/ldap/user_spec.rb' - 'spec/lib/ldap_spec.rb' - - 'spec/lib/migration_job/ldap_samaccountname_to_uid_spec.rb' - 'spec/lib/notification_factory/mailer_spec.rb' - 'spec/lib/password_hash_spec.rb' - 'spec/lib/secure_mailing/smime_spec.rb' @@ -538,7 +538,6 @@ RSpec/MultipleExpectations: - 'spec/models/object_manager/attribute/validation/backend_spec.rb' - 'spec/models/object_manager/attribute/validation_spec.rb' - 'spec/models/object_manager/attribute_spec.rb' - - 'spec/models/observer/ticket/article/communicate_twitter/background_job_spec.rb' - 'spec/models/overview_spec.rb' - 'spec/models/scheduler_spec.rb' - 'spec/models/smime_certificate_spec.rb' @@ -613,6 +612,7 @@ RSpec/NamedSubject: RSpec/NestedGroups: Exclude: - 'spec/db/migrate/issue_2541_fix_notification_email_without_body_spec.rb' + - 'spec/jobs/communicate_twitter_job_spec.rb' - 'spec/jobs/concerns/has_collection_update_spec.rb' - 'spec/jobs/concerns/has_ticket_create_screen_impact_spec.rb' - 'spec/lib/application_handle_info_spec.rb' @@ -656,7 +656,6 @@ RSpec/NestedGroups: - 'spec/models/object_manager/attribute/validation/required_spec.rb' - 'spec/models/object_manager/attribute/validation_spec.rb' - 'spec/models/object_manager/attribute_spec.rb' - - 'spec/models/observer/ticket/article/communicate_twitter/background_job_spec.rb' - 'spec/models/organization_spec.rb' - 'spec/models/recent_view_spec.rb' - 'spec/models/role_spec.rb' @@ -727,6 +726,7 @@ RSpec/SubjectStub: RSpec/VerifiedDoubles: Exclude: - 'spec/db/migrate/issue_2460_fix_corrupted_twitter_ids_spec.rb' + - 'spec/jobs/communicate_twitter_job_spec.rb' - 'spec/lib/auth/ldap_spec.rb' - 'spec/lib/external_sync_spec.rb' - 'spec/lib/import/zendesk/object_attribute/base_examples.rb' @@ -744,7 +744,6 @@ RSpec/VerifiedDoubles: - 'spec/lib/sequencer/unit/import/zendesk/sub_sequence/base_examples.rb' - 'spec/lib/sequencer/unit/import/zendesk/ticket/comment/attachment/request_spec.rb' - 'spec/lib/sequencer/unit/import/zendesk/ticket/comment/source_based_spec.rb' - - 'spec/models/observer/ticket/article/communicate_twitter/background_job_spec.rb' - 'spec/models/ticket/number_spec.rb' RSpec/MultipleMemoizedHelpers: diff --git a/.rubocop/todo.yml b/.rubocop/todo.yml index 8815b42a8..351639f77 100644 --- a/.rubocop/todo.yml +++ b/.rubocop/todo.yml @@ -75,8 +75,13 @@ Metrics/AbcSize: - 'app/controllers/users_controller.rb' - 'app/helpers/knowledge_base_rich_text_helper.rb' - 'app/jobs/collection_update_job.rb' + - 'app/jobs/communicate_facebook_job.rb' + - 'app/jobs/communicate_sms_job.rb' + - 'app/jobs/communicate_telegram_job.rb' + - 'app/jobs/communicate_twitter_job.rb' - 'app/jobs/concerns/has_active_job_lock.rb' - 'app/jobs/imap_authentication_migration_cleanup_job.rb' + - 'app/jobs/migrate_ldap_samaccountname_to_uid_job.rb' - 'app/jobs/ticket_article_communicate_email_job.rb' - 'app/jobs/ticket_user_ticket_counter_job.rb' - 'app/models/activity_stream.rb' @@ -154,13 +159,9 @@ Metrics/AbcSize: - 'app/models/observer/sla/ticket_rebuild_escalation.rb' - 'app/models/observer/ticket/article/communicate_email.rb' - 'app/models/observer/ticket/article/communicate_facebook.rb' - - 'app/models/observer/ticket/article/communicate_facebook/background_job.rb' - 'app/models/observer/ticket/article/communicate_sms.rb' - - 'app/models/observer/ticket/article/communicate_sms/background_job.rb' - 'app/models/observer/ticket/article/communicate_telegram.rb' - - 'app/models/observer/ticket/article/communicate_telegram/background_job.rb' - 'app/models/observer/ticket/article/communicate_twitter.rb' - - 'app/models/observer/ticket/article/communicate_twitter/background_job.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' @@ -290,7 +291,6 @@ Metrics/AbcSize: - 'lib/knowledge_base/menu_item_update_action.rb' - 'lib/ldap/group.rb' - 'lib/ldap/user.rb' - - 'lib/migration_job/ldap_samaccountname_to_uid.rb' - 'lib/models.rb' - 'lib/notification_factory/mailer.rb' - 'lib/notification_factory/renderer.rb' @@ -486,6 +486,10 @@ Metrics/CyclomaticComplexity: - 'app/controllers/user_access_token_controller.rb' - 'app/controllers/users_controller.rb' - 'app/jobs/collection_update_job.rb' + - 'app/jobs/communicate_facebook_job.rb' + - 'app/jobs/communicate_sms_job.rb' + - 'app/jobs/communicate_telegram_job.rb' + - 'app/jobs/communicate_twitter_job.rb' - 'app/jobs/ticket_article_communicate_email_job.rb' - 'app/jobs/ticket_create_screen_job.rb' - 'app/jobs/ticket_user_ticket_counter_job.rb' @@ -545,12 +549,8 @@ Metrics/CyclomaticComplexity: - 'app/models/observer/sla/ticket_rebuild_escalation.rb' - 'app/models/observer/ticket/article/communicate_email.rb' - 'app/models/observer/ticket/article/communicate_facebook.rb' - - 'app/models/observer/ticket/article/communicate_facebook/background_job.rb' - 'app/models/observer/ticket/article/communicate_sms.rb' - - 'app/models/observer/ticket/article/communicate_sms/background_job.rb' - - 'app/models/observer/ticket/article/communicate_telegram/background_job.rb' - 'app/models/observer/ticket/article/communicate_twitter.rb' - - 'app/models/observer/ticket/article/communicate_twitter/background_job.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' @@ -720,6 +720,10 @@ Metrics/PerceivedComplexity: - 'app/controllers/time_accountings_controller.rb' - 'app/controllers/users_controller.rb' - 'app/jobs/collection_update_job.rb' + - 'app/jobs/communicate_facebook_job.rb' + - 'app/jobs/communicate_sms_job.rb' + - 'app/jobs/communicate_telegram_job.rb' + - 'app/jobs/communicate_twitter_job.rb' - 'app/jobs/ticket_article_communicate_email_job.rb' - 'app/models/activity_stream/assets.rb' - 'app/models/application_model/can_assets.rb' @@ -775,12 +779,8 @@ Metrics/PerceivedComplexity: - 'app/models/observer/sla/ticket_rebuild_escalation.rb' - 'app/models/observer/ticket/article/communicate_email.rb' - 'app/models/observer/ticket/article/communicate_facebook.rb' - - 'app/models/observer/ticket/article/communicate_facebook/background_job.rb' - 'app/models/observer/ticket/article/communicate_sms.rb' - - 'app/models/observer/ticket/article/communicate_sms/background_job.rb' - - 'app/models/observer/ticket/article/communicate_telegram/background_job.rb' - 'app/models/observer/ticket/article/communicate_twitter.rb' - - 'app/models/observer/ticket/article/communicate_twitter/background_job.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' diff --git a/app/controllers/concerns/integration/import_job_base.rb b/app/controllers/concerns/integration/import_job_base.rb index bf1fbc7a4..075fff011 100644 --- a/app/controllers/concerns/integration/import_job_base.rb +++ b/app/controllers/concerns/integration/import_job_base.rb @@ -22,7 +22,7 @@ module Integration::ImportJobBase def job_start_create if !ImportJob.exists?(name: backend, finished_at: nil) job = ImportJob.create(name: backend) - job.delay.start + AsyncImportJob.perform_later(job) end render json: { result: 'ok', diff --git a/app/controllers/import_otrs_controller.rb b/app/controllers/import_otrs_controller.rb index 34cd62a75..b9e109ae5 100644 --- a/app/controllers/import_otrs_controller.rb +++ b/app/controllers/import_otrs_controller.rb @@ -115,7 +115,7 @@ class ImportOtrsController < ApplicationController end # start migration - Import::OTRS.delay.start_bg + AsyncOtrsImportJob.perform_later render json: { result: 'ok', diff --git a/app/controllers/import_zendesk_controller.rb b/app/controllers/import_zendesk_controller.rb index 3d0a97920..ead7e99e6 100644 --- a/app/controllers/import_zendesk_controller.rb +++ b/app/controllers/import_zendesk_controller.rb @@ -98,7 +98,7 @@ class ImportZendeskController < ApplicationController Setting.set('import_backend', 'zendesk') job = ImportJob.create(name: 'Import::Zendesk') - job.delay.start + AsyncImportJob.perform_later(job) render json: { result: 'ok', diff --git a/app/jobs/async_import_job.rb b/app/jobs/async_import_job.rb new file mode 100644 index 000000000..67a1416ee --- /dev/null +++ b/app/jobs/async_import_job.rb @@ -0,0 +1,5 @@ +class AsyncImportJob < ApplicationJob + def perform(import_job) + import_job.start + end +end diff --git a/app/jobs/async_otrs_import_job.rb b/app/jobs/async_otrs_import_job.rb new file mode 100644 index 000000000..1e50a1e7b --- /dev/null +++ b/app/jobs/async_otrs_import_job.rb @@ -0,0 +1,5 @@ +class AsyncOtrsImportJob < ApplicationJob + def perform + Import::OTRS.start_bg + end +end diff --git a/app/jobs/chat_leave_job.rb b/app/jobs/chat_leave_job.rb new file mode 100644 index 000000000..6d20a11a5 --- /dev/null +++ b/app/jobs/chat_leave_job.rb @@ -0,0 +1,34 @@ +class ChatLeaveJob < ApplicationJob + def perform(chat_session_id, client_id, session) + + # check if customer has permanently left the conversation + chat_session = Chat::Session.find_by(id: chat_session_id) + return if !chat_session + return if chat_session.recipients_active? + + chat_session.state = 'closed' + chat_session.save + + realname = 'Anonymous' + + # if it is a agent session, use the realname if the agent for close message + if session && session['id'] && chat_session.user_id + agent_user = chat_session.agent_user + if agent_user[:name] + realname = agent_user[:name] + end + end + + # notify participants + message = { + event: 'chat_session_left', + data: { + realname: realname, + session_id: chat_session.session_id, + }, + } + chat_session.send_to_recipients(message, client_id) + + Chat.broadcast_agent_state_update([chat_session.chat_id]) + end +end diff --git a/app/jobs/communicate_facebook_job.rb b/app/jobs/communicate_facebook_job.rb new file mode 100644 index 000000000..a3acaad4e --- /dev/null +++ b/app/jobs/communicate_facebook_job.rb @@ -0,0 +1,94 @@ +class CommunicateFacebookJob < ApplicationJob + + retry_on StandardError, attempts: 4, wait: lambda { |executions| + executions * 120.seconds + } + + def perform(article_id) + article = Ticket::Article.find(article_id) + + # set retry count + article.preferences['delivery_retry'] ||= 0 + article.preferences['delivery_retry'] += 1 + + ticket = Ticket.lookup(id: article.ticket_id) + log_error(article, "Can't find ticket.preferences for Ticket.find(#{article.ticket_id})") if !ticket.preferences + log_error(article, "Can't find ticket.preferences['channel_id'] for Ticket.find(#{article.ticket_id})") if !ticket.preferences['channel_id'] + channel = Channel.lookup(id: ticket.preferences['channel_id']) + log_error(article, "Channel.find(#{channel.id}) isn't a twitter channel!") if !channel.options[:adapter].match?(/\Afacebook/i) + + # check source object id + if !ticket.preferences['channel_fb_object_id'] + log_error(article, "fb object id is missing in ticket.preferences['channel_fb_object_id'] for Ticket.find(#{ticket.id})") + end + + # fill in_reply_to + if article.in_reply_to.blank? + article.in_reply_to = ticket.articles.first.message_id + end + + begin + facebook = Channel::Driver::Facebook.new + post = facebook.send( + channel.options, + ticket.preferences[:channel_fb_object_id], + { + type: article.type.name, + to: article.to, + body: article.body, + in_reply_to: article.in_reply_to, + } + ) + rescue => e + log_error(article, e.message) + return + end + + if !post + log_error(article, 'Got no post!') + return + end + + # fill article with post info + article.from = post['from']['name'] + article.message_id = post['id'] + + # set delivery status + article.preferences['delivery_status_message'] = nil + article.preferences['delivery_status'] = 'success' + article.preferences['delivery_status_date'] = Time.zone.now + + article.save! + + Rails.logger.info "Send facebook to: '#{article.to}' (from #{article.from})" + + article + end + + def log_error(local_record, message) + local_record.preferences['delivery_status'] = 'fail' + local_record.preferences['delivery_status_message'] = message.encode!('UTF-8', 'UTF-8', invalid: :replace, replace: '?') + local_record.preferences['delivery_status_date'] = Time.zone.now + local_record.save + Rails.logger.error message + + if local_record.preferences['delivery_retry'] > 3 + Ticket::Article.create( + ticket_id: local_record.ticket_id, + content_type: 'text/plain', + body: "Unable to send post: #{message}", + internal: true, + sender: Ticket::Article::Sender.find_by(name: 'System'), + type: Ticket::Article::Type.find_by(name: 'note'), + preferences: { + delivery_article_id_related: local_record.id, + delivery_message: true, + }, + updated_by_id: 1, + created_by_id: 1, + ) + end + + raise message + end +end diff --git a/app/jobs/communicate_sms_job.rb b/app/jobs/communicate_sms_job.rb new file mode 100644 index 000000000..db3a64f97 --- /dev/null +++ b/app/jobs/communicate_sms_job.rb @@ -0,0 +1,110 @@ +class CommunicateSmsJob < ApplicationJob + + retry_on StandardError, attempts: 4, wait: lambda { |executions| + executions * 120.seconds + } + + def perform(article_id) + article = Ticket::Article.find(article_id) + + # set retry count + article.preferences['delivery_retry'] ||= 0 + article.preferences['delivery_retry'] += 1 + + ticket = Ticket.lookup(id: article.ticket_id) + log_error(article, "Can't find article.preferences for Ticket::Article.find(#{article.id})") if !article.preferences + + # if sender is system, take article channel + if article.sender.name == 'System' + log_error(article, "Can't find article.preferences['sms_recipients'] for Ticket::Article.find(#{article.id})") if !article.preferences['sms_recipients'] + log_error(article, "Can't find article.preferences['channel_id'] for Ticket::Article.find(#{article.id})") if !article.preferences['channel_id'] + channel = Channel.lookup(id: article.preferences['channel_id']) + log_error(article, "No such channel id #{article.preferences['channel_id']}") if !channel + + # if sender is agent, take create channel + else + log_error(article, "Can't find ticket.preferences['channel_id'] for Ticket.find(#{ticket.id})") if !ticket.preferences['channel_id'] + channel = Channel.lookup(id: ticket.preferences['channel_id']) + log_error(article, "No such channel id #{ticket.preferences['channel_id']}") if !channel + end + + begin + if article.sender.name == 'System' + article.preferences['sms_recipients'].each do |recipient| + channel.deliver( + recipient: recipient, + message: article.body.first(160), + ) + end + else + channel.deliver( + recipient: article.to, + message: article.body.first(160), + ) + end + rescue => e + log_error(article, e.message) + return + end + + log_success(article) + + return if article.sender.name == 'Agent' + + log_history(article, ticket, 'sms', article.to) + end + + # log successful delivery + def log_success(article) + article.preferences['delivery_status_message'] = nil + article.preferences['delivery_status'] = 'success' + article.preferences['delivery_status_date'] = Time.zone.now + article.save! + end + + def log_error(local_record, message) + local_record.preferences['delivery_status'] = 'fail' + local_record.preferences['delivery_status_message'] = message + local_record.preferences['delivery_status_date'] = Time.zone.now + local_record.save! + Rails.logger.error message + + if local_record.preferences['delivery_retry'] >= max_attempts + Ticket::Article.create( + ticket_id: local_record.ticket_id, + content_type: 'text/plain', + body: "#{log_error_prefix}: #{message}", + internal: true, + sender: Ticket::Article::Sender.find_by(name: 'System'), + type: Ticket::Article::Type.find_by(name: 'note'), + preferences: { + delivery_article_id_related: local_record.id, + delivery_message: true, + }, + updated_by_id: 1, + created_by_id: 1, + ) + end + + raise message + end + + def log_history(article, ticket, history_type, recipient_list) + return if recipient_list.blank? + + History.add( + o_id: article.id, + history_type: history_type, + history_object: 'Ticket::Article', + related_o_id: ticket.id, + related_history_object: 'Ticket', + value_from: article.subject, + value_to: recipient_list, + created_by_id: article.created_by_id, + ) + end + + def log_error_prefix + 'Unable to send sms message' + end +end diff --git a/app/jobs/communicate_telegram_job.rb b/app/jobs/communicate_telegram_job.rb new file mode 100644 index 000000000..e808199b2 --- /dev/null +++ b/app/jobs/communicate_telegram_job.rb @@ -0,0 +1,113 @@ +class CommunicateTelegramJob < ApplicationJob + + retry_on StandardError, attempts: 4, wait: lambda { |executions| + executions * 120.seconds + } + + def perform(article_id) + article = Ticket::Article.find(article_id) + + # set retry count + article.preferences['delivery_retry'] ||= 0 + article.preferences['delivery_retry'] += 1 + + ticket = Ticket.lookup(id: article.ticket_id) + log_error(article, "Can't find ticket.preferences for Ticket.find(#{article.ticket_id})") if !ticket.preferences + log_error(article, "Can't find ticket.preferences['telegram'] for Ticket.find(#{article.ticket_id})") if !ticket.preferences['telegram'] + log_error(article, "Can't find ticket.preferences['telegram']['chat_id'] for Ticket.find(#{article.ticket_id})") if !ticket.preferences['telegram']['chat_id'] + if ticket.preferences['telegram'] && ticket.preferences['telegram']['bid'] + channel = Telegram.bot_by_bot_id(ticket.preferences['telegram']['bid']) + end + if !channel + channel = Channel.lookup(id: ticket.preferences['channel_id']) + end + log_error(article, "No such channel for bot #{ticket.preferences['bid']} or channel id #{ticket.preferences['channel_id']}") if !channel + #log_error(article, "Channel.find(#{channel.id}) isn't a telegram channel!") if channel.options[:adapter] !~ /\Atelegram/i + log_error(article, "Channel.find(#{channel.id}) has not telegram api token!") if channel.options[:api_token].blank? + + begin + api = TelegramAPI.new(channel.options[:api_token]) + chat_id = ticket.preferences[:telegram][:chat_id] + result = api.sendMessage(chat_id, article.body) + me = api.getMe() + article.attachments.each do |file| + parts = file.filename.split(/^(.*)(\..+?)$/) + t = Tempfile.new([parts[1], parts[2]]) + t.binmode + t.write(file.content) + t.rewind + api.sendDocument(chat_id, t.path.to_s) + end + rescue => e + log_error(article, e.message) + return + end + + Rails.logger.debug { "result info: #{result}" } + + # only private, group messages. channel messages do not have from key + if result['from'] && result['chat'] + # fill article with message info + article.from = "@#{result['from']['username']}" + article.to = "@#{result['chat']['username']}" + + article.preferences['telegram'] = { + date: result['date'], + from_id: result['from']['id'], + chat_id: result['chat']['id'], + message_id: result['message_id'] + } + else + # fill article with message info (telegram channel) + article.from = "@#{me['username']}" + article.to = "#{result['chat']['title']} Channel" + + article.preferences['telegram'] = { + date: result['date'], + from_id: me['id'], + chat_id: result['chat']['id'], + message_id: result['message_id'] + } + end + + # set delivery status + article.preferences['delivery_status_message'] = nil + article.preferences['delivery_status'] = 'success' + article.preferences['delivery_status_date'] = Time.zone.now + + article.message_id = "telegram.#{result['message_id']}.#{result['chat']['id']}" + + article.save! + + Rails.logger.info "Send telegram message to: '#{article.to}' (from #{article.from})" + + article + end + + def log_error(local_record, message) + local_record.preferences['delivery_status'] = 'fail' + local_record.preferences['delivery_status_message'] = message.encode!('UTF-8', 'UTF-8', invalid: :replace, replace: '?') + local_record.preferences['delivery_status_date'] = Time.zone.now + local_record.save + Rails.logger.error message + + if local_record.preferences['delivery_retry'] > 3 + Ticket::Article.create( + ticket_id: local_record.ticket_id, + content_type: 'text/plain', + body: "Unable to send telegram message: #{message}", + internal: true, + sender: Ticket::Article::Sender.find_by(name: 'System'), + type: Ticket::Article::Type.find_by(name: 'note'), + preferences: { + delivery_article_id_related: local_record.id, + delivery_message: true, + }, + updated_by_id: 1, + created_by_id: 1, + ) + end + + raise message + end +end diff --git a/app/jobs/communicate_twitter_job.rb b/app/jobs/communicate_twitter_job.rb new file mode 100644 index 000000000..c4ecd152d --- /dev/null +++ b/app/jobs/communicate_twitter_job.rb @@ -0,0 +1,156 @@ +class CommunicateTwitterJob < ApplicationJob + + retry_on StandardError, attempts: 4, wait: lambda { |executions| + executions * 120.seconds + } + + def perform(article_id) + article = Ticket::Article.find(article_id) + + # set retry count + article.preferences['delivery_retry'] ||= 0 + article.preferences['delivery_retry'] += 1 + + ticket = Ticket.lookup(id: article.ticket_id) + log_error(article, "Can't find ticket.preferences['channel_id'] for Ticket.find(#{article.ticket_id})") if !ticket.preferences['channel_id'] + channel = Channel.lookup(id: ticket.preferences['channel_id']) + + # search for same channel channel_screen_name, in case the channel got re-added + if !channel + Channel.where(area: 'Twitter::Account', active: true).each do |local_channel| + next if ticket.preferences[:channel_screen_name].blank? + next if !local_channel.options + next if local_channel.options[:user].blank? + next if local_channel.options[:user][:screen_name].blank? + next if local_channel.options[:user][:screen_name] != ticket.preferences[:channel_screen_name] + + channel = local_channel + break + end + end + + log_error(article, "No such channel id #{ticket.preferences['channel_id']}") if !channel + log_error(article, "Channel.find(#{channel.id}) isn't a twitter channel!") if !channel.options[:adapter].try(:match?, /\Atwitter/i) + + begin + tweet = channel.deliver( + type: article.type.name, + to: article.to, + body: article.body, + in_reply_to: article.in_reply_to + ) + rescue => e + log_error(article, e.message) + return + end + if !tweet + log_error(article, 'Got no tweet!') + return + end + + # fill article with tweet info + + # direct message + if tweet.is_a?(Hash) + tweet_type = 'DirectMessage' + article.message_id = tweet[:event][:id].to_s + if tweet[:event] && tweet[:event][:type] == 'message_create' + #article.from = "@#{tweet.sender.screen_name}" + #article.to = "@#{tweet.recipient.screen_name}" + + article.preferences['twitter'] = { + recipient_id: tweet[:event][:message_create][:target][:recipient_id], + sender_id: tweet[:event][:message_create][:sender_id], + } + + article.preferences['links'] = [ + { + url: TwitterSync::DM_URL_TEMPLATE % article.preferences[:twitter].slice(:recipient_id, :sender_id).values.map(&:to_i).sort.join('-'), + target: '_blank', + name: 'on Twitter', + }, + ] + end + + # regular tweet + elsif tweet.instance_of?(Twitter::Tweet) + tweet_type = 'Tweet' + tweet_id = tweet.id.to_s + article.from = "@#{tweet.user.screen_name}" + if tweet.user_mentions + to = '' + mention_ids = [] + tweet.user_mentions.each do |user| + if to != '' + to += ' ' + end + to += "@#{user.screen_name}" + mention_ids.push user.id + end + article.to = to + article.preferences['twitter'] = TwitterSync.preferences_cleanup( + mention_ids: mention_ids, + geo: tweet.geo, + retweeted: tweet.retweeted?, + possibly_sensitive: tweet.possibly_sensitive?, + in_reply_to_user_id: tweet.in_reply_to_user_id, + place: tweet.place, + retweet_count: tweet.retweet_count, + source: tweet.source, + favorited: tweet.favorited?, + truncated: tweet.truncated?, + created_at: tweet.created_at, + ) + + article.message_id = tweet_id + article.preferences['links'] = [ + { + url: TwitterSync::STATUS_URL_TEMPLATE % tweet.id, + target: '_blank', + name: 'on Twitter', + }, + ] + end + else + raise "Unknown tweet type '#{tweet.class}'" + end + + # set delivery status + article.preferences['delivery_status_message'] = nil + article.preferences['delivery_status'] = 'success' + article.preferences['delivery_status_date'] = Time.zone.now + + article.save! + + Rails.logger.info "Send twitter (#{tweet_type}) to: '#{article.to}' (from #{article.from})" + + article + end + + def log_error(local_record, message) + local_record.preferences['delivery_status'] = 'fail' + local_record.preferences['delivery_status_message'] = message.encode!('UTF-8', 'UTF-8', invalid: :replace, replace: '?') + local_record.preferences['delivery_status_date'] = Time.zone.now + local_record.save + Rails.logger.error message + + if local_record.preferences['delivery_retry'] > 3 + Ticket::Article.create( + ticket_id: local_record.ticket_id, + content_type: 'text/plain', + body: "Unable to send tweet: #{message}", + internal: true, + sender: Ticket::Article::Sender.find_by(name: 'System'), + type: Ticket::Article::Type.find_by(name: 'note'), + preferences: { + delivery_article_id_related: local_record.id, + delivery_message: true, + }, + updated_by_id: 1, + created_by_id: 1, + ) + end + + raise message + end +end diff --git a/app/jobs/migrate_ldap_samaccountname_to_uid_job.rb b/app/jobs/migrate_ldap_samaccountname_to_uid_job.rb new file mode 100644 index 000000000..bff405bfa --- /dev/null +++ b/app/jobs/migrate_ldap_samaccountname_to_uid_job.rb @@ -0,0 +1,56 @@ +require_dependency 'ldap' +require_dependency 'ldap/user' + +class MigrateLdapSamaccountnameToUidJob < ApplicationJob + + def perform + Rails.logger.info 'Checking for active LDAP configuration...' + + if ldap_config.blank? + Rails.logger.info 'Blank LDAP configuration. Exiting.' + return + end + + Rails.logger.info 'Checking for different LDAP uid attribute...' + if uid_attribute_obsolete == uid_attribute_new + Rails.logger.info 'Equal LDAP uid attributes. Exiting.' + return + end + + Rails.logger.info 'Starting to migrate LDAP config to new uid attribute...' + migrate_ldap_config + Rails.logger.info 'LDAP uid attribute migration completed.' + end + + private + + def ldap + @ldap ||= ::Ldap.new(ldap_config) + end + + def ldap_config + @ldap_config ||= Import::Ldap.config + end + + def uid_attribute_new + @uid_attribute_new ||= begin + config = { + filter: ldap_config['user_filter'] + } + + ::Ldap::User.new(config, ldap: ldap).uid_attribute + end + end + + def uid_attribute_obsolete + @uid_attribute_obsolete ||= ldap_config['user_uid'] + end + + def migrate_ldap_config + ldap_config_new = ldap_config.merge( + 'user_uid' => uid_attribute_new + ) + + Setting.set('ldap_config', ldap_config_new) + end +end diff --git a/app/jobs/transaction_job.rb b/app/jobs/transaction_job.rb new file mode 100644 index 000000000..07f394152 --- /dev/null +++ b/app/jobs/transaction_job.rb @@ -0,0 +1,26 @@ +class TransactionJob < ApplicationJob + +=begin + { + object: 'Ticket', + type: 'update', + ticket_id: 123, + interface_handle: 'application_server', # application_server|websocket|scheduler + changes: { + 'attribute1' => [before,now], + 'attribute2' => [before,now], + }, + created_at: Time.zone.now, + user_id: 123, + }, +=end + + def perform(item, params = {}) + Setting.where(area: 'Transaction::Backend::Async').order(:name).each do |setting| + backend = Setting.get(setting.name) + next if params[:disable]&.include?(backend) + + Observer::Transaction.execute_singel_backend(backend.constantize, item, params) + end + end +end diff --git a/app/models/import_job.rb b/app/models/import_job.rb index 273893a22..bf0dcbf52 100644 --- a/app/models/import_job.rb +++ b/app/models/import_job.rb @@ -75,12 +75,12 @@ class ImportJob < ApplicationModel return if exists?(name: params[:name], dry_run: true, finished_at: nil) params[:dry_run] = true - instance = create(params.except(:delay)) + job = create(params.except(:delay)) if params.fetch(:delay, true) - instance.delay.start + AsyncImportJob.perform_later(job) else - instance.start + job.start end end diff --git a/app/models/observer/chat/leave/background_job.rb b/app/models/observer/chat/leave/background_job.rb index 1be9cdbe4..f4b615f88 100644 --- a/app/models/observer/chat/leave/background_job.rb +++ b/app/models/observer/chat/leave/background_job.rb @@ -6,36 +6,10 @@ class Observer::Chat::Leave::BackgroundJob end def perform - - # check if customer has permanently left the conversation - chat_session = Chat::Session.find_by(id: @chat_session_id) - return if !chat_session - return if chat_session.recipients_active? - - chat_session.state = 'closed' - chat_session.save - - realname = 'Anonymous' - - # if it is a agent session, use the realname if the agent for close message - if @session && @session['id'] && chat_session.user_id - agent_user = chat_session.agent_user - if agent_user[:name] - realname = agent_user[:name] - end + if Gem::Version.new(Version.get) >= Gem::Version.new('4.0.x') + ActiveSupport::Deprecation.warn("This file has been migrated to the ActiveJob 'ChatLeaveJob' and is therefore deprecated and should get removed.") end - # notify participants - message = { - event: 'chat_session_left', - data: { - realname: realname, - session_id: chat_session.session_id, - }, - } - chat_session.send_to_recipients(message, @client_id) - - Chat.broadcast_agent_state_update([chat_session.chat_id]) + ChatLeaveJob.perform_now(@chat_session_id, @client_id, @session) end - end diff --git a/app/models/observer/ticket/article/communicate_facebook.rb b/app/models/observer/ticket/article/communicate_facebook.rb index a791de0e1..195bd9b1d 100644 --- a/app/models/observer/ticket/article/communicate_facebook.rb +++ b/app/models/observer/ticket/article/communicate_facebook.rb @@ -25,7 +25,7 @@ class Observer::Ticket::Article::CommunicateFacebook < ActiveRecord::Observer return true if type.nil? return true if !type.name.start_with?('facebook') - Delayed::Job.enqueue(Observer::Ticket::Article::CommunicateFacebook::BackgroundJob.new(record.id)) + CommunicateFacebookJob.perform_later(record.id) end end diff --git a/app/models/observer/ticket/article/communicate_facebook/background_job.rb b/app/models/observer/ticket/article/communicate_facebook/background_job.rb index f77208d01..eae2713da 100644 --- a/app/models/observer/ticket/article/communicate_facebook/background_job.rb +++ b/app/models/observer/ticket/article/communicate_facebook/background_job.rb @@ -4,102 +4,10 @@ class Observer::Ticket::Article::CommunicateFacebook::BackgroundJob end def perform - article = Ticket::Article.find(@article_id) - - # set retry count - article.preferences['delivery_retry'] ||= 0 - article.preferences['delivery_retry'] += 1 - - ticket = Ticket.lookup(id: article.ticket_id) - log_error(article, "Can't find ticket.preferences for Ticket.find(#{article.ticket_id})") if !ticket.preferences - log_error(article, "Can't find ticket.preferences['channel_id'] for Ticket.find(#{article.ticket_id})") if !ticket.preferences['channel_id'] - channel = Channel.lookup(id: ticket.preferences['channel_id']) - log_error(article, "Channel.find(#{channel.id}) isn't a twitter channel!") if !channel.options[:adapter].match?(/\Afacebook/i) - - # check source object id - if !ticket.preferences['channel_fb_object_id'] - log_error(article, "fb object id is missing in ticket.preferences['channel_fb_object_id'] for Ticket.find(#{ticket.id})") + if Gem::Version.new(Version.get) >= Gem::Version.new('4.0.x') + ActiveSupport::Deprecation.warn("This file has been migrated to the ActiveJob 'CommunicateFacebookJob' and is therefore deprecated and should get removed.") end - # fill in_reply_to - if article.in_reply_to.blank? - article.in_reply_to = ticket.articles.first.message_id - end - - begin - facebook = Channel::Driver::Facebook.new - post = facebook.send( - channel.options, - ticket.preferences[:channel_fb_object_id], - { - type: article.type.name, - to: article.to, - body: article.body, - in_reply_to: article.in_reply_to, - } - ) - rescue => e - log_error(article, e.message) - return - end - - if !post - log_error(article, 'Got no post!') - return - end - - # fill article with post info - article.from = post['from']['name'] - article.message_id = post['id'] - - # set delivery status - article.preferences['delivery_status_message'] = nil - article.preferences['delivery_status'] = 'success' - article.preferences['delivery_status_date'] = Time.zone.now - - article.save! - - Rails.logger.info "Send facebook to: '#{article.to}' (from #{article.from})" - - article - end - - def log_error(local_record, message) - local_record.preferences['delivery_status'] = 'fail' - local_record.preferences['delivery_status_message'] = message.encode!('UTF-8', 'UTF-8', invalid: :replace, replace: '?') - local_record.preferences['delivery_status_date'] = Time.zone.now - local_record.save - Rails.logger.error message - - if local_record.preferences['delivery_retry'] > 3 - Ticket::Article.create( - ticket_id: local_record.ticket_id, - content_type: 'text/plain', - body: "Unable to send post: #{message}", - internal: true, - sender: Ticket::Article::Sender.find_by(name: 'System'), - type: Ticket::Article::Type.find_by(name: 'note'), - preferences: { - delivery_article_id_related: local_record.id, - delivery_message: true, - }, - updated_by_id: 1, - created_by_id: 1, - ) - end - - raise message - end - - def max_attempts - 4 - end - - def reschedule_at(current_time, attempts) - if Rails.env.production? - return current_time + attempts * 120.seconds - end - - current_time + 5.seconds + CommunicateFacebookJob.perform_now(@article_id) end end diff --git a/app/models/observer/ticket/article/communicate_sms.rb b/app/models/observer/ticket/article/communicate_sms.rb index 10ddcc759..81e97096d 100644 --- a/app/models/observer/ticket/article/communicate_sms.rb +++ b/app/models/observer/ticket/article/communicate_sms.rb @@ -20,6 +20,6 @@ class Observer::Ticket::Article::CommunicateSms < ActiveRecord::Observer return true if type.nil? return true if type.name != 'sms' - Delayed::Job.enqueue(Observer::Ticket::Article::CommunicateSms::BackgroundJob.new(record.id)) + CommunicateSmsJob.perform_later(record.id) end end diff --git a/app/models/observer/ticket/article/communicate_sms/background_job.rb b/app/models/observer/ticket/article/communicate_sms/background_job.rb index 57c3a42ea..493cc3f63 100644 --- a/app/models/observer/ticket/article/communicate_sms/background_job.rb +++ b/app/models/observer/ticket/article/communicate_sms/background_job.rb @@ -4,119 +4,10 @@ class Observer::Ticket::Article::CommunicateSms::BackgroundJob end def perform - article = Ticket::Article.find(@article_id) - - # set retry count - article.preferences['delivery_retry'] ||= 0 - article.preferences['delivery_retry'] += 1 - - ticket = Ticket.lookup(id: article.ticket_id) - log_error(article, "Can't find article.preferences for Ticket::Article.find(#{article.id})") if !article.preferences - - # if sender is system, take article channel - if article.sender.name == 'System' - log_error(article, "Can't find article.preferences['sms_recipients'] for Ticket::Article.find(#{article.id})") if !article.preferences['sms_recipients'] - log_error(article, "Can't find article.preferences['channel_id'] for Ticket::Article.find(#{article.id})") if !article.preferences['channel_id'] - channel = Channel.lookup(id: article.preferences['channel_id']) - log_error(article, "No such channel id #{article.preferences['channel_id']}") if !channel - - # if sender is agent, take create channel - else - log_error(article, "Can't find ticket.preferences['channel_id'] for Ticket.find(#{ticket.id})") if !ticket.preferences['channel_id'] - channel = Channel.lookup(id: ticket.preferences['channel_id']) - log_error(article, "No such channel id #{ticket.preferences['channel_id']}") if !channel + if Gem::Version.new(Version.get) >= Gem::Version.new('4.0.x') + ActiveSupport::Deprecation.warn("This file has been migrated to the ActiveJob 'CommunicateSmsJob' and is therefore deprecated and should get removed.") end - begin - if article.sender.name == 'System' - article.preferences['sms_recipients'].each do |recipient| - channel.deliver( - recipient: recipient, - message: article.body.first(160), - ) - end - else - channel.deliver( - recipient: article.to, - message: article.body.first(160), - ) - end - rescue => e - log_error(article, e.message) - return - end - - log_success(article) - - return if article.sender.name == 'Agent' - - log_history(article, ticket, 'sms', article.to) + CommunicateSmsJob.perform_now(@article_id) end - - # log successful delivery - def log_success(article) - article.preferences['delivery_status_message'] = nil - article.preferences['delivery_status'] = 'success' - article.preferences['delivery_status_date'] = Time.zone.now - article.save! - end - - def log_error(local_record, message) - local_record.preferences['delivery_status'] = 'fail' - local_record.preferences['delivery_status_message'] = message - local_record.preferences['delivery_status_date'] = Time.zone.now - local_record.save! - Rails.logger.error message - - if local_record.preferences['delivery_retry'] >= max_attempts - Ticket::Article.create( - ticket_id: local_record.ticket_id, - content_type: 'text/plain', - body: "#{log_error_prefix}: #{message}", - internal: true, - sender: Ticket::Article::Sender.find_by(name: 'System'), - type: Ticket::Article::Type.find_by(name: 'note'), - preferences: { - delivery_article_id_related: local_record.id, - delivery_message: true, - }, - updated_by_id: 1, - created_by_id: 1, - ) - end - - raise message - end - - def log_history(article, ticket, history_type, recipient_list) - return if recipient_list.blank? - - History.add( - o_id: article.id, - history_type: history_type, - history_object: 'Ticket::Article', - related_o_id: ticket.id, - related_history_object: 'Ticket', - value_from: article.subject, - value_to: recipient_list, - created_by_id: article.created_by_id, - ) - end - - def log_error_prefix - 'Unable to send sms message' - end - - def max_attempts - 4 - end - - def reschedule_at(current_time, attempts) - if Rails.env.production? - return current_time + attempts * 120.seconds - end - - current_time + 5.seconds - end - end diff --git a/app/models/observer/ticket/article/communicate_telegram.rb b/app/models/observer/ticket/article/communicate_telegram.rb index efbcb4331..d60dfe578 100644 --- a/app/models/observer/ticket/article/communicate_telegram.rb +++ b/app/models/observer/ticket/article/communicate_telegram.rb @@ -21,7 +21,7 @@ class Observer::Ticket::Article::CommunicateTelegram < ActiveRecord::Observer type = Ticket::Article::Type.lookup(id: record.type_id) return true if !type.name.match?(/\Atelegram/i) - Delayed::Job.enqueue(Observer::Ticket::Article::CommunicateTelegram::BackgroundJob.new(record.id)) + CommunicateTelegramJob.perform_later(record.id) end end diff --git a/app/models/observer/ticket/article/communicate_telegram/background_job.rb b/app/models/observer/ticket/article/communicate_telegram/background_job.rb index 7d0657e46..708127d2a 100644 --- a/app/models/observer/ticket/article/communicate_telegram/background_job.rb +++ b/app/models/observer/ticket/article/communicate_telegram/background_job.rb @@ -4,121 +4,10 @@ class Observer::Ticket::Article::CommunicateTelegram::BackgroundJob end def perform - article = Ticket::Article.find(@article_id) - - # set retry count - article.preferences['delivery_retry'] ||= 0 - article.preferences['delivery_retry'] += 1 - - ticket = Ticket.lookup(id: article.ticket_id) - log_error(article, "Can't find ticket.preferences for Ticket.find(#{article.ticket_id})") if !ticket.preferences - log_error(article, "Can't find ticket.preferences['telegram'] for Ticket.find(#{article.ticket_id})") if !ticket.preferences['telegram'] - log_error(article, "Can't find ticket.preferences['telegram']['chat_id'] for Ticket.find(#{article.ticket_id})") if !ticket.preferences['telegram']['chat_id'] - if ticket.preferences['telegram'] && ticket.preferences['telegram']['bid'] - channel = Telegram.bot_by_bot_id(ticket.preferences['telegram']['bid']) - end - if !channel - channel = Channel.lookup(id: ticket.preferences['channel_id']) - end - log_error(article, "No such channel for bot #{ticket.preferences['bid']} or channel id #{ticket.preferences['channel_id']}") if !channel - #log_error(article, "Channel.find(#{channel.id}) isn't a telegram channel!") if channel.options[:adapter] !~ /\Atelegram/i - log_error(article, "Channel.find(#{channel.id}) has not telegram api token!") if channel.options[:api_token].blank? - - begin - api = TelegramAPI.new(channel.options[:api_token]) - chat_id = ticket.preferences[:telegram][:chat_id] - result = api.sendMessage(chat_id, article.body) - me = api.getMe() - article.attachments.each do |file| - parts = file.filename.split(/^(.*)(\..+?)$/) - t = Tempfile.new([parts[1], parts[2]]) - t.binmode - t.write(file.content) - t.rewind - api.sendDocument(chat_id, t.path.to_s) - end - rescue => e - log_error(article, e.message) - return + if Gem::Version.new(Version.get) >= Gem::Version.new('4.0.x') + ActiveSupport::Deprecation.warn("This file has been migrated to the ActiveJob 'CommunicateTelegramJob' and is therefore deprecated and should get removed.") end - Rails.logger.debug { "result info: #{result}" } - - # only private, group messages. channel messages do not have from key - if result['from'] && result['chat'] - # fill article with message info - article.from = "@#{result['from']['username']}" - article.to = "@#{result['chat']['username']}" - - article.preferences['telegram'] = { - date: result['date'], - from_id: result['from']['id'], - chat_id: result['chat']['id'], - message_id: result['message_id'] - } - else - # fill article with message info (telegram channel) - article.from = "@#{me['username']}" - article.to = "#{result['chat']['title']} Channel" - - article.preferences['telegram'] = { - date: result['date'], - from_id: me['id'], - chat_id: result['chat']['id'], - message_id: result['message_id'] - } - end - - # set delivery status - article.preferences['delivery_status_message'] = nil - article.preferences['delivery_status'] = 'success' - article.preferences['delivery_status_date'] = Time.zone.now - - article.message_id = "telegram.#{result['message_id']}.#{result['chat']['id']}" - - article.save! - - Rails.logger.info "Send telegram message to: '#{article.to}' (from #{article.from})" - - article - end - - def log_error(local_record, message) - local_record.preferences['delivery_status'] = 'fail' - local_record.preferences['delivery_status_message'] = message.encode!('UTF-8', 'UTF-8', invalid: :replace, replace: '?') - local_record.preferences['delivery_status_date'] = Time.zone.now - local_record.save - Rails.logger.error message - - if local_record.preferences['delivery_retry'] > 3 - Ticket::Article.create( - ticket_id: local_record.ticket_id, - content_type: 'text/plain', - body: "Unable to send telegram message: #{message}", - internal: true, - sender: Ticket::Article::Sender.find_by(name: 'System'), - type: Ticket::Article::Type.find_by(name: 'note'), - preferences: { - delivery_article_id_related: local_record.id, - delivery_message: true, - }, - updated_by_id: 1, - created_by_id: 1, - ) - end - - raise message - end - - def max_attempts - 4 - end - - def reschedule_at(current_time, attempts) - if Rails.env.production? - return current_time + attempts * 120.seconds - end - - current_time + 5.seconds + CommunicateTelegramJob.perform_now(@article_id) end end diff --git a/app/models/observer/ticket/article/communicate_twitter.rb b/app/models/observer/ticket/article/communicate_twitter.rb index 4def4a802..9b4f9998f 100644 --- a/app/models/observer/ticket/article/communicate_twitter.rb +++ b/app/models/observer/ticket/article/communicate_twitter.rb @@ -28,7 +28,7 @@ class Observer::Ticket::Article::CommunicateTwitter < ActiveRecord::Observer raise Exceptions::UnprocessableEntity, 'twitter to: parameter is missing' if record.to.blank? && type['name'] == 'twitter direct-message' - Delayed::Job.enqueue(Observer::Ticket::Article::CommunicateTwitter::BackgroundJob.new(record.id)) + CommunicateTwitterJob.perform_later(record.id) end end diff --git a/app/models/observer/ticket/article/communicate_twitter/background_job.rb b/app/models/observer/ticket/article/communicate_twitter/background_job.rb index 582dfa575..8a8526272 100644 --- a/app/models/observer/ticket/article/communicate_twitter/background_job.rb +++ b/app/models/observer/ticket/article/communicate_twitter/background_job.rb @@ -4,163 +4,10 @@ class Observer::Ticket::Article::CommunicateTwitter::BackgroundJob end def perform - article = Ticket::Article.find(@article_id) - - # set retry count - article.preferences['delivery_retry'] ||= 0 - article.preferences['delivery_retry'] += 1 - - ticket = Ticket.lookup(id: article.ticket_id) - log_error(article, "Can't find ticket.preferences['channel_id'] for Ticket.find(#{article.ticket_id})") if !ticket.preferences['channel_id'] - channel = Channel.lookup(id: ticket.preferences['channel_id']) - - # search for same channel channel_screen_name, in case the channel got re-added - if !channel - Channel.where(area: 'Twitter::Account', active: true).each do |local_channel| - next if ticket.preferences[:channel_screen_name].blank? - next if !local_channel.options - next if local_channel.options[:user].blank? - next if local_channel.options[:user][:screen_name].blank? - next if local_channel.options[:user][:screen_name] != ticket.preferences[:channel_screen_name] - - channel = local_channel - break - end + if Gem::Version.new(Version.get) >= Gem::Version.new('4.0.x') + ActiveSupport::Deprecation.warn("This file has been migrated to the ActiveJob 'CommunicateTwitterJob' and is therefore deprecated and should get removed.") end - log_error(article, "No such channel id #{ticket.preferences['channel_id']}") if !channel - log_error(article, "Channel.find(#{channel.id}) isn't a twitter channel!") if !channel.options[:adapter].try(:match?, /\Atwitter/i) - - begin - tweet = channel.deliver( - type: article.type.name, - to: article.to, - body: article.body, - in_reply_to: article.in_reply_to - ) - rescue => e - log_error(article, e.message) - return - end - if !tweet - log_error(article, 'Got no tweet!') - return - end - - # fill article with tweet info - - # direct message - if tweet.is_a?(Hash) - tweet_type = 'DirectMessage' - article.message_id = tweet[:event][:id].to_s - if tweet[:event] && tweet[:event][:type] == 'message_create' - #article.from = "@#{tweet.sender.screen_name}" - #article.to = "@#{tweet.recipient.screen_name}" - - article.preferences['twitter'] = { - recipient_id: tweet[:event][:message_create][:target][:recipient_id], - sender_id: tweet[:event][:message_create][:sender_id], - } - - article.preferences['links'] = [ - { - url: TwitterSync::DM_URL_TEMPLATE % article.preferences[:twitter].slice(:recipient_id, :sender_id).values.map(&:to_i).sort.join('-'), - target: '_blank', - name: 'on Twitter', - }, - ] - end - - # regular tweet - elsif tweet.instance_of?(Twitter::Tweet) - tweet_type = 'Tweet' - article.from = "@#{tweet.user.screen_name}" - if tweet.user_mentions - to = '' - mention_ids = [] - tweet.user_mentions.each do |user| - if to != '' - to += ' ' - end - to += "@#{user.screen_name}" - mention_ids.push user.id - end - article.to = to - article.preferences['twitter'] = TwitterSync.preferences_cleanup( - mention_ids: mention_ids, - geo: tweet.geo, - retweeted: tweet.retweeted?, - possibly_sensitive: tweet.possibly_sensitive?, - in_reply_to_user_id: tweet.in_reply_to_user_id, - place: tweet.place, - retweet_count: tweet.retweet_count, - source: tweet.source, - favorited: tweet.favorited?, - truncated: tweet.truncated?, - created_at: tweet.created_at, - ) - end - - article.message_id = tweet.id.to_s - article.preferences['links'] = [ - { - url: TwitterSync::STATUS_URL_TEMPLATE % tweet.id, - target: '_blank', - name: 'on Twitter', - }, - ] - else - raise "Unknown tweet type '#{tweet.class}'" - end - - # set delivery status - article.preferences['delivery_status_message'] = nil - article.preferences['delivery_status'] = 'success' - article.preferences['delivery_status_date'] = Time.zone.now - - article.save! - - Rails.logger.info "Send twitter (#{tweet_type}) to: '#{article.to}' (from #{article.from})" - - article - end - - def log_error(local_record, message) - local_record.preferences['delivery_status'] = 'fail' - local_record.preferences['delivery_status_message'] = message.encode!('UTF-8', 'UTF-8', invalid: :replace, replace: '?') - local_record.preferences['delivery_status_date'] = Time.zone.now - local_record.save - Rails.logger.error message - - if local_record.preferences['delivery_retry'] > 3 - Ticket::Article.create( - ticket_id: local_record.ticket_id, - content_type: 'text/plain', - body: "Unable to send tweet: #{message}", - internal: true, - sender: Ticket::Article::Sender.find_by(name: 'System'), - type: Ticket::Article::Type.find_by(name: 'note'), - preferences: { - delivery_article_id_related: local_record.id, - delivery_message: true, - }, - updated_by_id: 1, - created_by_id: 1, - ) - end - - raise message - end - - def max_attempts - 4 - end - - def reschedule_at(current_time, attempts) - if Rails.env.production? - return current_time + attempts * 120.seconds - end - - current_time + 5.seconds + CommunicateTwitterJob.perform_now(@article_id) end end diff --git a/app/models/observer/transaction.rb b/app/models/observer/transaction.rb index 7da56c69a..e6b23ea04 100644 --- a/app/models/observer/transaction.rb +++ b/app/models/observer/transaction.rb @@ -49,7 +49,7 @@ class Observer::Transaction < ActiveRecord::Observer end # execute async backends - Delayed::Job.enqueue(Transaction::BackgroundJob.new(item, params)) + TransactionJob.perform_later(item, params) end end end diff --git a/app/models/ticket.rb b/app/models/ticket.rb index cbb3cdb9c..489bf715a 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -179,7 +179,7 @@ returns end # send notification - Transaction::BackgroundJob.run( + TransactionJob.perform_now( object: 'Ticket', type: 'reminder_reached', object_id: ticket.id, @@ -220,7 +220,7 @@ returns # send escalation if ticket.escalation_at < Time.zone.now - Transaction::BackgroundJob.run( + TransactionJob.perform_now( object: 'Ticket', type: 'escalation', object_id: ticket.id, @@ -232,7 +232,7 @@ returns end # check if warning need to be sent - Transaction::BackgroundJob.run( + TransactionJob.perform_now( object: 'Ticket', type: 'escalation_warning', object_id: ticket.id, @@ -1067,7 +1067,7 @@ perform active triggers on ticket local_options[:reset_user_id] = true local_options[:disable] = ['Transaction::Notification'] local_options[:trigger_ids] ||= {} - local_options[:trigger_ids][ticket.id] ||= [] + local_options[:trigger_ids][ticket.id.to_s] ||= [] local_options[:loop_count] ||= 0 local_options[:loop_count] += 1 @@ -1220,11 +1220,11 @@ perform active triggers on ticket end end - if local_options[:trigger_ids][ticket.id].include?(trigger.id) + if local_options[:trigger_ids][ticket.id.to_s].include?(trigger.id) logger.info { "Skip trigger (#{trigger.name}/#{trigger.id}) because was already executed for this object (Ticket:#{ticket.id}/Loop:#{local_options[:loop_count]})" } next end - local_options[:trigger_ids][ticket.id].push trigger.id + local_options[:trigger_ids][ticket.id.to_s].push trigger.id logger.info { "Execute trigger (#{trigger.name}/#{trigger.id}) for this object (Ticket:#{ticket.id}/Loop:#{local_options[:loop_count]})" } ticket.perform_changes(trigger.perform, 'trigger', item, user_id) diff --git a/app/models/transaction/background_job.rb b/app/models/transaction/background_job.rb index 5b7680f20..1fb08c52a 100644 --- a/app/models/transaction/background_job.rb +++ b/app/models/transaction/background_job.rb @@ -1,5 +1,3 @@ -# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ - class Transaction::BackgroundJob def initialize(item, params = {}) @@ -23,17 +21,10 @@ class Transaction::BackgroundJob end def perform - Setting.where(area: 'Transaction::Backend::Async').order(:name).each do |setting| - backend = Setting.get(setting.name) - next if @params[:disable]&.include?(backend) - - Observer::Transaction.execute_singel_backend(backend.constantize, @item, @params) + if Gem::Version.new(Version.get) >= Gem::Version.new('4.0.x') + ActiveSupport::Deprecation.warn("This file has been migrated to the ActiveJob 'TransactionJob' and is therefore deprecated and should get removed.") end - end - def self.run(item, params = {}) - generic = new(item, params) - generic.perform + TransactionJob.perform_now(@item, @params) end - end diff --git a/config/initializers/workaround_active_job_time_serialization.rb b/config/initializers/workaround_active_job_time_serialization.rb new file mode 100644 index 000000000..e08825706 --- /dev/null +++ b/config/initializers/workaround_active_job_time_serialization.rb @@ -0,0 +1,45 @@ +# Required workaround to serialize ActiveSupport::TimeWithZone, Time, Date and DateTime for ActiveJob +# until Rails 6 is used. See: +# - https://github.com/rails/rails/issues/18519 +# - https://github.com/rails/rails/pull/32026 +# - https://github.com/rails/rails/tree/6-0-stable/activejob/lib/active_job/serializers + +class ActiveSupport::TimeWithZone + include GlobalID::Identification + + alias id iso8601 + + def self.find(iso8601) + Time.iso8601(iso8601).in_time_zone + end +end + +class Time + include GlobalID::Identification + + alias id iso8601 + + def self.find(iso8601) + Time.iso8601(iso8601) + end +end + +class Date + include GlobalID::Identification + + alias id iso8601 + + def self.find(iso8601) + Date.iso8601(iso8601) + end +end + +class DateTime + include GlobalID::Identification + + alias id iso8601 + + def self.find(iso8601) + DateTime.iso8601(iso8601) + end +end diff --git a/db/migrate/20180111000001_ldap_samaccountname_to_uid.rb b/db/migrate/20180111000001_ldap_samaccountname_to_uid.rb index 92d8dcc46..bb89053a2 100644 --- a/db/migrate/20180111000001_ldap_samaccountname_to_uid.rb +++ b/db/migrate/20180111000001_ldap_samaccountname_to_uid.rb @@ -4,7 +4,7 @@ class LdapSamaccountnameToUid < ActiveRecord::Migration[5.1] # return if it's a new setup to avoid running the migration return if !Setting.exists?(name: 'system_init_done') - Delayed::Job.enqueue MigrationJob::LdapSamaccountnameToUid.new + MigrateLdapSamaccountnameToUidJob.perform_later end end diff --git a/lib/migration_job/ldap_samaccountname_to_uid.rb b/lib/migration_job/ldap_samaccountname_to_uid.rb deleted file mode 100644 index b3f3dfcc6..000000000 --- a/lib/migration_job/ldap_samaccountname_to_uid.rb +++ /dev/null @@ -1,58 +0,0 @@ -require_dependency 'ldap' -require_dependency 'ldap/user' - -module MigrationJob - class LdapSamaccountnameToUid - - def perform - Rails.logger.info 'Checking for active LDAP configuration...' - - if ldap_config.blank? - Rails.logger.info 'Blank LDAP configuration. Exiting.' - return - end - - Rails.logger.info 'Checking for different LDAP uid attribute...' - if uid_attribute_obsolete == uid_attribute_new - Rails.logger.info 'Equal LDAP uid attributes. Exiting.' - return - end - - Rails.logger.info 'Starting to migrate LDAP config to new uid attribute...' - migrate_ldap_config - Rails.logger.info 'LDAP uid attribute migration completed.' - end - - private - - def ldap - @ldap ||= ::Ldap.new(ldap_config) - end - - def ldap_config - @ldap_config ||= Import::Ldap.config - end - - def uid_attribute_new - @uid_attribute_new ||= begin - config = { - filter: ldap_config['user_filter'] - } - - ::Ldap::User.new(config, ldap: ldap).uid_attribute - end - end - - def uid_attribute_obsolete - @uid_attribute_obsolete ||= ldap_config['user_uid'] - end - - def migrate_ldap_config - ldap_config_new = ldap_config.merge( - 'user_uid' => uid_attribute_new - ) - - Setting.set('ldap_config', ldap_config_new) - end - end -end diff --git a/lib/sessions/event/chat_session_leave_temporary.rb b/lib/sessions/event/chat_session_leave_temporary.rb index 4d9620295..2344a4373 100644 --- a/lib/sessions/event/chat_session_leave_temporary.rb +++ b/lib/sessions/event/chat_session_leave_temporary.rb @@ -6,12 +6,7 @@ class Sessions::Event::ChatSessionLeaveTemporary < Sessions::Event::ChatBase chat_session = current_chat_session - Delayed::Job.enqueue( - Observer::Chat::Leave::BackgroundJob.new(chat_session.id, @client_id, @session), - { - run_at: Time.zone.now + 0.5.minutes - } - ) + ChatLeaveJob.set(wait: 0.5.minutes).perform_later(chat_session.id, @client_id, @session) false end diff --git a/spec/models/observer/ticket/article/communicate_twitter/background_job_spec.rb b/spec/jobs/communicate_twitter_job_spec.rb similarity index 89% rename from spec/models/observer/ticket/article/communicate_twitter/background_job_spec.rb rename to spec/jobs/communicate_twitter_job_spec.rb index c8c3d20be..839bf5520 100644 --- a/spec/models/observer/ticket/article/communicate_twitter/background_job_spec.rb +++ b/spec/jobs/communicate_twitter_job_spec.rb @@ -1,6 +1,7 @@ require 'rails_helper' -RSpec.describe Observer::Ticket::Article::CommunicateTwitter::BackgroundJob, type: :job do +RSpec.describe CommunicateTwitterJob, type: :job do + let(:article) { create(:twitter_article, **(try(:factory_options) || {})) } describe 'core behavior', :use_vcr do @@ -37,12 +38,12 @@ RSpec.describe Observer::Ticket::Article::CommunicateTwitter::BackgroundJob, typ end it 'increments the "delivery_retry" preference' do - expect { described_class.new(article.id).perform } + expect { described_class.perform_now(article.id) } .to change { article.reload.preferences[:delivery_retry] }.to(1) end it 'dispatches the tweet' do - described_class.new(article.id).perform + described_class.perform_now(article.id) expect(WebMock) .to have_requested(:post, 'https://api.twitter.com/1.1/statuses/update.json') @@ -50,14 +51,14 @@ RSpec.describe Observer::Ticket::Article::CommunicateTwitter::BackgroundJob, typ end it 'updates the article with tweet attributes' do - expect { described_class.new(article.id).perform } + expect { described_class.perform_now(article.id) } .to change { article.reload.message_id }.to('1244937367435108360') .and change { article.reload.preferences[:twitter] }.to(hash_including(tweet_attributes)) .and change { article.reload.preferences[:links] }.to(links_array) end it 'sets the appropriate delivery status attributes' do - expect { described_class.new(article.id).perform } + expect { described_class.perform_now(article.id) } .to change { article.reload.preferences[:delivery_status] }.to('success') .and change { article.reload.preferences[:delivery_status_date] }.to(an_instance_of(ActiveSupport::TimeWithZone)) .and not_change { article.reload.preferences[:delivery_status_message] }.from(nil) @@ -67,7 +68,7 @@ RSpec.describe Observer::Ticket::Article::CommunicateTwitter::BackgroundJob, typ let(:factory_options) { { body: '@twitter @twitterlive Don’t mind me, just testing the API' } } it 'updates the article with tweet recipients' do - expect { described_class.new(article.id).perform } + expect { described_class.perform_now(article.id) } .to change { article.reload.to }.to('@Twitter @TwitterLive') end end @@ -105,12 +106,12 @@ RSpec.describe Observer::Ticket::Article::CommunicateTwitter::BackgroundJob, typ end it 'increments the "delivery_retry" preference' do - expect { described_class.new(article.id).perform } + expect { described_class.perform_now(article.id) } .to change { article.reload.preferences[:delivery_retry] }.to(1) end it 'dispatches the DM' do - described_class.new(article.id).perform + described_class.perform_now(article.id) expect(WebMock) .to have_requested(:post, 'https://api.twitter.com/1.1/direct_messages/events/new.json') @@ -118,14 +119,14 @@ RSpec.describe Observer::Ticket::Article::CommunicateTwitter::BackgroundJob, typ end it 'updates the article with DM attributes' do - expect { described_class.new(article.id).perform } + expect { described_class.perform_now(article.id) } .to change { article.reload.message_id }.to('1244953398509617156') .and change { article.reload.preferences[:twitter] }.to(hash_including(dm_attributes)) .and change { article.reload.preferences[:links] }.to(links_array) end it 'sets the appropriate delivery status attributes' do - expect { described_class.new(article.id).perform } + expect { described_class.perform_now(article.id) } .to change { article.reload.preferences[:delivery_status] }.to('success') .and change { article.reload.preferences[:delivery_status_date] }.to(an_instance_of(ActiveSupport::TimeWithZone)) .and not_change { article.reload.preferences[:delivery_status_message] }.from(nil) @@ -135,9 +136,8 @@ RSpec.describe Observer::Ticket::Article::CommunicateTwitter::BackgroundJob, typ describe 'failure cases' do shared_examples 'for failure cases' do it 'raises an error and sets the appropriate delivery status messages' do - expect { described_class.new(article.id).perform } - .to raise_error(error_message) - .and change { article.reload.preferences[:delivery_status] }.to('fail') + expect { described_class.perform_now(article.id) } + .to change { article.reload.preferences[:delivery_status] }.to('fail') .and change { article.reload.preferences[:delivery_status_date] }.to(an_instance_of(ActiveSupport::TimeWithZone)) .and change { article.reload.preferences[:delivery_status_message] }.to(error_message) end @@ -166,7 +166,7 @@ RSpec.describe Observer::Ticket::Article::CommunicateTwitter::BackgroundJob, typ let!(:new_channel) { create(:twitter_channel, custom_options: { user: { screen_name: channel.options[:user][:screen_name] } }) } it 'uses that channel' do - described_class.new(article.id).perform + described_class.perform_now(article.id) expect(WebMock) .to have_requested(:post, 'https://api.twitter.com/1.1/statuses/update.json') @@ -216,9 +216,8 @@ RSpec.describe Observer::Ticket::Article::CommunicateTwitter::BackgroundJob, typ let(:factory_options) { { preferences: { delivery_retry: 3 } } } it 'adds a delivery failure note (article) to the ticket' do - expect { described_class.new(article.id).perform } - .to raise_error(error_message) - .and change { article.ticket.reload.articles.count }.by(1) + expect { described_class.perform_now(article.id) } + .to change { article.ticket.reload.articles.count }.by(1) expect(Ticket::Article.last.attributes).to include( 'content_type' => 'text/plain', diff --git a/spec/lib/migration_job/ldap_samaccountname_to_uid_spec.rb b/spec/jobs/migrate_ldap_samaccountname_to_uid_job_spec.rb similarity index 72% rename from spec/lib/migration_job/ldap_samaccountname_to_uid_spec.rb rename to spec/jobs/migrate_ldap_samaccountname_to_uid_job_spec.rb index b4b1075a6..f4d330f82 100644 --- a/spec/lib/migration_job/ldap_samaccountname_to_uid_spec.rb +++ b/spec/jobs/migrate_ldap_samaccountname_to_uid_job_spec.rb @@ -1,12 +1,14 @@ require 'rails_helper' -RSpec.describe MigrationJob::LdapSamaccountnameToUid do +RSpec.describe MigrateLdapSamaccountnameToUidJob, type: :job do it 'performs no changes if no LDAP config present' do allow(Setting).to receive(:set) allow(Import::Ldap).to receive(:config).and_return(nil) - described_class.new.perform + described_class.perform_now + + expect(Import::Ldap).to have_received(:config) expect(Setting).not_to have_received(:set) end @@ -24,8 +26,12 @@ RSpec.describe MigrationJob::LdapSamaccountnameToUid do allow(::Ldap).to receive(:new) - described_class.new.perform + described_class.perform_now + expect(Setting).not_to have_received(:set) + expect(Import::Ldap).to have_received(:config) + expect(ldap_user).to have_received(:uid_attribute) + expect(::Ldap::User).to have_received(:new) end it 'performs Setting change if uid attribute differ' do @@ -37,6 +43,7 @@ RSpec.describe MigrationJob::LdapSamaccountnameToUid do } allow(Setting).to receive(:set) + allow(Setting).to receive(:set).with('ldap_config', ldap_config_new) allow(Import::Ldap).to receive(:config).and_return(ldap_config_obsolete) @@ -46,8 +53,9 @@ RSpec.describe MigrationJob::LdapSamaccountnameToUid do allow(::Ldap).to receive(:new) - described_class.new.perform + described_class.perform_now expect(Setting).to have_received(:set).with('ldap_config', ldap_config_new) + expect(ldap_user).to have_received(:uid_attribute) end end diff --git a/spec/models/channel/driver/twitter_spec.rb b/spec/models/channel/driver/twitter_spec.rb index 5c6a88a69..3c4907371 100644 --- a/spec/models/channel/driver/twitter_spec.rb +++ b/spec/models/channel/driver/twitter_spec.rb @@ -677,7 +677,7 @@ RSpec.describe Channel::Driver::Twitter do describe '#send', :use_vcr do shared_examples 'for #send' do # Channel#deliver takes a hash in the following format - # (see Observer::Ticket::Article::CommunicateTwitter::BackgroundJob#perform) + # (see CommunicateTwitterJob#perform) # # Why not just accept the whole article? # Presumably so all channels have a consistent interface... @@ -990,10 +990,7 @@ RSpec.describe Channel::Driver::Twitter do # and then manually copied into the existing VCR cassette for this example. context '…but before the BG job has "synced" article.message_id with tweet.id)' do - let(:twitter_job) { Delayed::Job.find_by(handler: <<~YML) } - --- !ruby/object:Observer::Ticket::Article::CommunicateTwitter::BackgroundJob - article_id: #{tweet.id} - YML + let(:twitter_job) { Delayed::Job.where("handler LIKE '%job_class: CommunicateTwitterJob%#{tweet.id}%'").first } around do |example| # Run BG job (Why not use Scheduler.worker? diff --git a/spec/models/observer/ticket/article/communicate_twitter_spec.rb b/spec/models/observer/ticket/article/communicate_twitter_spec.rb index 8951bf998..25f1a4d57 100644 --- a/spec/models/observer/ticket/article/communicate_twitter_spec.rb +++ b/spec/models/observer/ticket/article/communicate_twitter_spec.rb @@ -1,27 +1,19 @@ require 'rails_helper' -RSpec.describe Observer::Ticket::Article::CommunicateTwitter do +RSpec.describe Observer::Ticket::Article::CommunicateTwitter, 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(Delayed::Job) - .not_to receive(:enqueue) - .with(instance_of(Observer::Ticket::Article::CommunicateTwitter::BackgroundJob)) - - article + expect { article }.not_to have_enqueued_job(CommunicateTwitterJob) end end shared_examples 'for success' do it 'enqueues the Twitter background job' do - expect(Delayed::Job) - .to receive(:enqueue) - .with(an_instance_of(Observer::Ticket::Article::CommunicateTwitter::BackgroundJob)) - - article + expect { article }.to have_enqueued_job(CommunicateTwitterJob) end end diff --git a/test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/and_another_suitable_channel_exists_matching_on_ticket_preferences_channel_screen_name_uses_that_channel.yml b/test/data/vcr_cassettes/jobs/communicate_twitter_job/and_another_suitable_channel_exists_matching_on_ticket_preferences_channel_screen_name_uses_that_channel.yml similarity index 100% rename from test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/and_another_suitable_channel_exists_matching_on_ticket_preferences_channel_screen_name_uses_that_channel.yml rename to test/data/vcr_cassettes/jobs/communicate_twitter_job/and_another_suitable_channel_exists_matching_on_ticket_preferences_channel_screen_name_uses_that_channel.yml diff --git a/test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_dispatches_the_dm.yml b/test/data/vcr_cassettes/jobs/communicate_twitter_job/for_dms_dispatches_the_dm.yml similarity index 100% rename from test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_dispatches_the_dm.yml rename to test/data/vcr_cassettes/jobs/communicate_twitter_job/for_dms_dispatches_the_dm.yml diff --git a/test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_increments_the_delivery_retry_preference.yml b/test/data/vcr_cassettes/jobs/communicate_twitter_job/for_dms_increments_the_delivery_retry_preference.yml similarity index 100% rename from test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_increments_the_delivery_retry_preference.yml rename to test/data/vcr_cassettes/jobs/communicate_twitter_job/for_dms_increments_the_delivery_retry_preference.yml diff --git a/test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_sets_the_appropriate_delivery_status_attributes.yml b/test/data/vcr_cassettes/jobs/communicate_twitter_job/for_dms_sets_the_appropriate_delivery_status_attributes.yml similarity index 100% rename from test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_sets_the_appropriate_delivery_status_attributes.yml rename to test/data/vcr_cassettes/jobs/communicate_twitter_job/for_dms_sets_the_appropriate_delivery_status_attributes.yml diff --git a/test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_updates_the_article_with_dm_attributes.yml b/test/data/vcr_cassettes/jobs/communicate_twitter_job/for_dms_updates_the_article_with_dm_attributes.yml similarity index 100% rename from test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_updates_the_article_with_dm_attributes.yml rename to test/data/vcr_cassettes/jobs/communicate_twitter_job/for_dms_updates_the_article_with_dm_attributes.yml diff --git a/test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_dispatches_the_tweet.yml b/test/data/vcr_cassettes/jobs/communicate_twitter_job/for_tweets_dispatches_the_tweet.yml similarity index 100% rename from test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_dispatches_the_tweet.yml rename to test/data/vcr_cassettes/jobs/communicate_twitter_job/for_tweets_dispatches_the_tweet.yml diff --git a/test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_increments_the_delivery_retry_preference.yml b/test/data/vcr_cassettes/jobs/communicate_twitter_job/for_tweets_increments_the_delivery_retry_preference.yml similarity index 100% rename from test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_increments_the_delivery_retry_preference.yml rename to test/data/vcr_cassettes/jobs/communicate_twitter_job/for_tweets_increments_the_delivery_retry_preference.yml diff --git a/test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_sets_the_appropriate_delivery_status_attributes.yml b/test/data/vcr_cassettes/jobs/communicate_twitter_job/for_tweets_sets_the_appropriate_delivery_status_attributes.yml similarity index 100% rename from test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_sets_the_appropriate_delivery_status_attributes.yml rename to test/data/vcr_cassettes/jobs/communicate_twitter_job/for_tweets_sets_the_appropriate_delivery_status_attributes.yml diff --git a/test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_sets_the_appropriate_delivery_status_message.yml b/test/data/vcr_cassettes/jobs/communicate_twitter_job/for_tweets_sets_the_appropriate_delivery_status_message.yml similarity index 100% rename from test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_sets_the_appropriate_delivery_status_message.yml rename to test/data/vcr_cassettes/jobs/communicate_twitter_job/for_tweets_sets_the_appropriate_delivery_status_message.yml diff --git a/test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_updates_the_article_with_tweet_attributes.yml b/test/data/vcr_cassettes/jobs/communicate_twitter_job/for_tweets_updates_the_article_with_tweet_attributes.yml similarity index 100% rename from test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_updates_the_article_with_tweet_attributes.yml rename to test/data/vcr_cassettes/jobs/communicate_twitter_job/for_tweets_updates_the_article_with_tweet_attributes.yml diff --git a/test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/when_tweet_dispatch_fails_e_g__due_to_authentication_error_raises_an_error_and_sets_the_appropriate_delivery_status_messages.yml b/test/data/vcr_cassettes/jobs/communicate_twitter_job/when_tweet_dispatch_fails_e_g__due_to_authentication_error_raises_an_error_and_sets_the_appropriate_delivery_status_messages.yml similarity index 100% rename from test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/when_tweet_dispatch_fails_e_g__due_to_authentication_error_raises_an_error_and_sets_the_appropriate_delivery_status_messages.yml rename to test/data/vcr_cassettes/jobs/communicate_twitter_job/when_tweet_dispatch_fails_e_g__due_to_authentication_error_raises_an_error_and_sets_the_appropriate_delivery_status_messages.yml diff --git a/test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/with_a_user_mention_updates_the_article_with_tweet_recipients.yml b/test/data/vcr_cassettes/jobs/communicate_twitter_job/with_a_user_mention_updates_the_article_with_tweet_recipients.yml similarity index 100% rename from test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/with_a_user_mention_updates_the_article_with_tweet_recipients.yml rename to test/data/vcr_cassettes/jobs/communicate_twitter_job/with_a_user_mention_updates_the_article_with_tweet_recipients.yml