From 3c69ddb7f40590da31d393d4b72dccb84c1e023e Mon Sep 17 00:00:00 2001 From: Pranav Date: Thu, 20 Mar 2025 13:14:23 -0700 Subject: [PATCH] feat: Add an activity message in conversations if source_id changes --- .../source_change_activity_job.rb | 38 ++++++ app/models/contact_inbox.rb | 6 + config/locales/en.yml | 5 + .../source_change_activity_job_spec.rb | 118 ++++++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 app/jobs/contact_inbox/source_change_activity_job.rb create mode 100644 spec/jobs/contact_inbox/source_change_activity_job_spec.rb diff --git a/app/jobs/contact_inbox/source_change_activity_job.rb b/app/jobs/contact_inbox/source_change_activity_job.rb new file mode 100644 index 000000000..998e82efd --- /dev/null +++ b/app/jobs/contact_inbox/source_change_activity_job.rb @@ -0,0 +1,38 @@ +class ContactInbox::SourceChangeActivityJob < ApplicationJob + queue_as :default + + def perform(contact_inbox_id, previous_source_id, current_source_id) + contact_inbox = ContactInbox.find_by(id: contact_inbox_id) + return if contact_inbox.blank? + + activity_message = I18n.t( + activity_message_i18n_key(contact_inbox), + previous: previous_source_id, + current: current_source_id + ) + + contact_inbox.conversations.find_each(batch_size: 100) do |conversation| + Conversations::ActivityMessageJob.perform_later( + conversation, + account_id: conversation.account_id, + inbox_id: conversation.inbox_id, + message: activity_message, + message_type: :activity + ) + end + end + + private + + def activity_message_i18n_key(contact_inbox) + type = if contact_inbox.inbox.email? + :email_changed + elsif contact_inbox.inbox.whatsapp? + :phone_number_changed + else + :identifier_changed + end + + "contact_inboxes.source_change.#{type}" + end +end diff --git a/app/models/contact_inbox.rb b/app/models/contact_inbox.rb index 6d034880e..a5ce2815e 100644 --- a/app/models/contact_inbox.rb +++ b/app/models/contact_inbox.rb @@ -31,6 +31,8 @@ class ContactInbox < ApplicationRecord belongs_to :contact belongs_to :inbox + after_update_commit :create_source_change_activity, if: :source_id_changed? + has_many :conversations, dependent: :destroy_async # contact_inboxes that are not associated with any conversation @@ -57,6 +59,10 @@ class ContactInbox < ApplicationRecord private + def create_source_change_activity + ContactInbox::SourceChangeActivityJob.perform_later(id, source_id_was, source_id) + end + def validate_twilio_source_id # https://www.twilio.com/docs/glossary/what-e164#regex-matching-for-e164 if inbox.channel.medium == 'sms' && !TWILIO_CHANNEL_SMS_REGEX.match?(source_id) diff --git a/config/locales/en.yml b/config/locales/en.yml index 4a66a4bc2..480568078 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -30,6 +30,11 @@ # available at https://guides.rubyonrails.org/i18n.html. en: + contact_inboxes: + source_change: + email_changed: 'Email changed from %{previous} to %{current}' + phone_number_changed: 'Phone number changed from %{previous} to %{current}' + identifier_changed: 'Source identifier changed from %{previous} to %{current}' hello: 'Hello world' messages: reset_password_success: Woot! Request for password reset is successful. Check your mail for instructions. diff --git a/spec/jobs/contact_inbox/source_change_activity_job_spec.rb b/spec/jobs/contact_inbox/source_change_activity_job_spec.rb new file mode 100644 index 000000000..49a7b9fb4 --- /dev/null +++ b/spec/jobs/contact_inbox/source_change_activity_job_spec.rb @@ -0,0 +1,118 @@ +require 'rails_helper' + +RSpec.describe ContactInbox::SourceChangeActivityJob do + let(:account) { create(:account) } + let(:contact) { create(:contact, account: account) } + let(:previous_source_id) { 'old_id' } + let(:current_source_id) { 'new_id' } + + describe '#perform' do + context 'when inbox is email' do + let(:channel) { create(:channel_email, account: account) } + let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: channel.inbox) } + let(:conversation) { create(:conversation, contact: contact, inbox: channel.inbox, account: account, contact_inbox: contact_inbox) } + + it 'creates activity message with email change text' do + expected_message = I18n.t( + 'contact_inboxes.source_change.email_changed', + previous: previous_source_id, + current: current_source_id + ) + + expect(Conversations::ActivityMessageJob).to receive(:perform_later).with( + conversation, + account_id: account.id, + inbox_id: channel.inbox.id, + message: expected_message, + message_type: :activity + ) + + described_class.perform_now(contact_inbox.id, previous_source_id, current_source_id) + end + end + + context 'when inbox is whatsapp' do + let(:channel) { create(:channel_whatsapp, account: account, sync_templates: false, validate_provider_config: false) } + let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: channel.inbox) } + let(:conversation) { create(:conversation, contact: contact, inbox: channel.inbox, account: account, contact_inbox: contact_inbox) } + + it 'creates activity message with phone number change text' do + expected_message = I18n.t('contact_inboxes.source_change.phone_number_changed', + previous: previous_source_id, + current: current_source_id) + + expect(Conversations::ActivityMessageJob).to receive(:perform_later).with( + conversation, + account_id: account.id, + inbox_id: channel.inbox.id, + message: expected_message, + message_type: :activity + ) + + described_class.perform_now(contact_inbox.id, previous_source_id, current_source_id) + end + end + + context 'when inbox is of other type' do + let(:channel) { create(:channel_api, account: account) } + let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: channel.inbox) } + let(:conversation) { create(:conversation, contact: contact, inbox: channel.inbox, account: account, contact_inbox: contact_inbox) } + + it 'creates activity message with generic identifier change text' do + expected_message = I18n.t('contact_inboxes.source_change.identifier_changed', + previous: previous_source_id, + current: current_source_id) + + expect(Conversations::ActivityMessageJob).to receive(:perform_later).with( + conversation, + account_id: account.id, + inbox_id: channel.inbox.id, + message: expected_message, + message_type: :activity + ) + + described_class.perform_now(contact_inbox.id, previous_source_id, current_source_id) + end + end + + context 'when multiple conversations exist' do + let(:channel) { create(:channel_whatsapp, account: account, sync_templates: false, validate_provider_config: false) } + let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: channel.inbox) } + let(:conversation) { create(:conversation, contact: contact, inbox: channel.inbox, account: account, contact_inbox: contact_inbox) } + let(:conversation2) { create(:conversation, contact: contact, inbox: channel.inbox, account: account, contact_inbox: contact_inbox) } + let(:conversation3) { create(:conversation, contact: contact, inbox: channel.inbox, account: account) } + + it 'creates activity message for each conversation' do + expected_message = I18n.t('contact_inboxes.source_change.phone_number_changed', + previous: previous_source_id, + current: current_source_id) + + expect(Conversations::ActivityMessageJob).to receive(:perform_later).with( + conversation, + account_id: account.id, + inbox_id: channel.inbox.id, + message: expected_message, + message_type: :activity + ) + + expect(Conversations::ActivityMessageJob).to receive(:perform_later).with( + conversation2, + account_id: account.id, + inbox_id: channel.inbox.id, + message: expected_message, + message_type: :activity + ) + + expect(Conversations::ActivityMessageJob).not_to receive(:perform_later).with( + conversation3, + account_id: account.id, + inbox_id: channel.inbox.id, + message: expected_message, + message_type: :activity + ) + + described_class.perform_now(contact_inbox.id, previous_source_id, current_source_id) + end + end + end +end