From 143299f13802fbc4a608d69ff57e866cb8694f58 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Wed, 24 Jan 2024 12:26:47 +0530 Subject: [PATCH 1/9] feat: Add `contact_type` attribute to contact model (#8768) --- app/models/contact.rb | 3 +++ db/migrate/20240124054340_add_contact_type_to_contacts.rb | 5 +++++ db/schema.rb | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20240124054340_add_contact_type_to_contacts.rb diff --git a/app/models/contact.rb b/app/models/contact.rb index ba10139ba..176109f24 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -6,6 +6,7 @@ # # id :integer not null, primary key # additional_attributes :jsonb +# contact_type :integer default("visitor") # custom_attributes :jsonb # email :string # identifier :string @@ -55,6 +56,8 @@ class Contact < ApplicationRecord after_update_commit :dispatch_update_event after_destroy_commit :dispatch_destroy_event + enum contact_type: { visitor: 0, lead: 1, customer: 2 } + scope :order_on_last_activity_at, lambda { |direction| order( Arel::Nodes::SqlLiteral.new( diff --git a/db/migrate/20240124054340_add_contact_type_to_contacts.rb b/db/migrate/20240124054340_add_contact_type_to_contacts.rb new file mode 100644 index 000000000..d6b939a36 --- /dev/null +++ b/db/migrate/20240124054340_add_contact_type_to_contacts.rb @@ -0,0 +1,5 @@ +class AddContactTypeToContacts < ActiveRecord::Migration[7.0] + def change + add_column :contacts, :contact_type, :integer, default: 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index 0fcf7ba61..b914eca61 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_12_23_040257) do +ActiveRecord::Schema[7.0].define(version: 2024_01_24_054340) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "pg_trgm" @@ -418,6 +418,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_23_040257) do t.string "identifier" t.jsonb "custom_attributes", default: {} t.datetime "last_activity_at", precision: nil + t.integer "contact_type", default: 0 t.index "lower((email)::text), account_id", name: "index_contacts_on_lower_email_account_id" t.index ["account_id", "email", "phone_number", "identifier"], name: "index_contacts_on_nonempty_fields", where: "(((email)::text <> ''::text) OR ((phone_number)::text <> ''::text) OR ((identifier)::text <> ''::text))" t.index ["account_id"], name: "index_contacts_on_account_id" From a861257f738e2c8fcda31c2c52d3a5f6685d8f72 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Wed, 24 Jan 2024 13:58:27 +0400 Subject: [PATCH 2/9] chore: Fix flaky contacts spec (#8773) - The ordering was not guaranteed; hence, the specs were failing randomly. Made changes to the expectations accordingly --- .../api/v1/accounts/contacts_controller_spec.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/controllers/api/v1/accounts/contacts_controller_spec.rb b/spec/controllers/api/v1/accounts/contacts_controller_spec.rb index 7352b842f..ace9e8e03 100644 --- a/spec/controllers/api/v1/accounts/contacts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/contacts_controller_spec.rb @@ -36,9 +36,11 @@ RSpec.describe 'Contacts API', type: :request do expect(response).to have_http_status(:success) response_body = response.parsed_body - expect(response_body['payload'].first['email']).to eq(contact.email) - expect(response_body['payload'].first['contact_inboxes'].first['source_id']).to eq(contact_inbox.source_id) - expect(response_body['payload'].first['contact_inboxes'].first['inbox']['name']).to eq(contact_inbox.inbox.name) + contact_emails = response_body['payload'].pluck('email') + contact_inboxes_source_ids = response_body['payload'].flat_map { |c| c['contact_inboxes'].pluck('source_id') } + + expect(contact_emails).to include(contact.email) + expect(contact_inboxes_source_ids).to include(contact_inbox.source_id) end it 'returns all contacts without contact inboxes' do From 3760f206e82dd01db8490923bf89b23f337b5c26 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 24 Jan 2024 15:48:21 +0530 Subject: [PATCH 3/9] fix: mutex timeout and error handling (#8770) Fixes the follow cases - The ensure block released the lock even on LockAcquisitionError - Custom timeout was not allowed This also refactored the with_lock method, now the key has to be constructed in the parent function itself Co-authored-by: Sojan Jose --- app/jobs/inboxes/fetch_imap_emails_job.rb | 3 +- app/jobs/mutex_application_job.rb | 32 +++++++++++++++------ app/jobs/send_on_slack_job.rb | 3 +- app/jobs/webhooks/facebook_events_job.rb | 3 +- app/jobs/webhooks/instagram_events_job.rb | 3 +- spec/jobs/mutex_application_job_spec.rb | 35 ++++++++++++++--------- 6 files changed, 54 insertions(+), 25 deletions(-) diff --git a/app/jobs/inboxes/fetch_imap_emails_job.rb b/app/jobs/inboxes/fetch_imap_emails_job.rb index 8f61e54b5..0f7718701 100644 --- a/app/jobs/inboxes/fetch_imap_emails_job.rb +++ b/app/jobs/inboxes/fetch_imap_emails_job.rb @@ -6,7 +6,8 @@ class Inboxes::FetchImapEmailsJob < MutexApplicationJob def perform(channel) return unless should_fetch_email?(channel) - with_lock(::Redis::Alfred::EMAIL_MESSAGE_MUTEX, inbox_id: channel.inbox.id) do + key = format(::Redis::Alfred::EMAIL_MESSAGE_MUTEX, inbox_id: channel.inbox.id) + with_lock(key, 5.minutes) do process_email_for_channel(channel) end rescue *ExceptionList::IMAP_EXCEPTIONS => e diff --git a/app/jobs/mutex_application_job.rb b/app/jobs/mutex_application_job.rb index 98e6bf9cd..58c7cbf36 100644 --- a/app/jobs/mutex_application_job.rb +++ b/app/jobs/mutex_application_job.rb @@ -14,20 +14,36 @@ class MutexApplicationJob < ApplicationJob class LockAcquisitionError < StandardError; end - def with_lock(key_format, *args) - lock_key = format(key_format, *args) + def with_lock(lock_key, timeout = Redis::LockManager::LOCK_TIMEOUT) lock_manager = Redis::LockManager.new begin - if lock_manager.lock(lock_key) - Rails.logger.info "[#{self.class.name}] Acquired lock for: #{lock_key} on attempt #{executions}" + if lock_manager.lock(lock_key, timeout) + log_attempt(lock_key, executions) yield + # release the lock after the block has been executed + lock_manager.unlock(lock_key) else - Rails.logger.warn "[#{self.class.name}] Failed to acquire lock on attempt #{executions}: #{lock_key}" - raise LockAcquisitionError, "Failed to acquire lock for key: #{lock_key}" + handle_failed_lock_acquisition(lock_key) end - ensure - lock_manager.unlock(lock_key) + rescue StandardError => e + handle_error(e, lock_manager, lock_key) end end + + private + + def log_attempt(lock_key, executions) + Rails.logger.info "[#{self.class.name}] Acquired lock for: #{lock_key} on attempt #{executions}" + end + + def handle_error(err, lock_manager, lock_key) + lock_manager.unlock(lock_key) unless err.is_a?(LockAcquisitionError) + raise err + end + + def handle_failed_lock_acquisition(lock_key) + Rails.logger.warn "[#{self.class.name}] Failed to acquire lock on attempt #{executions}: #{lock_key}" + raise LockAcquisitionError, "Failed to acquire lock for key: #{lock_key}" + end end diff --git a/app/jobs/send_on_slack_job.rb b/app/jobs/send_on_slack_job.rb index eececa409..c8a556ce7 100644 --- a/app/jobs/send_on_slack_job.rb +++ b/app/jobs/send_on_slack_job.rb @@ -3,7 +3,8 @@ class SendOnSlackJob < MutexApplicationJob retry_on LockAcquisitionError, wait: 1.second, attempts: 8 def perform(message, hook) - with_lock(::Redis::Alfred::SLACK_MESSAGE_MUTEX, conversation_id: message.conversation_id, reference_id: hook.reference_id) do + key = format(::Redis::Alfred::SLACK_MESSAGE_MUTEX, conversation_id: message.conversation_id, reference_id: hook.reference_id) + with_lock(key) do Integrations::Slack::SendOnSlackService.new(message: message, hook: hook).perform end end diff --git a/app/jobs/webhooks/facebook_events_job.rb b/app/jobs/webhooks/facebook_events_job.rb index 4240d62b9..0694a2b85 100644 --- a/app/jobs/webhooks/facebook_events_job.rb +++ b/app/jobs/webhooks/facebook_events_job.rb @@ -5,7 +5,8 @@ class Webhooks::FacebookEventsJob < MutexApplicationJob def perform(message) response = ::Integrations::Facebook::MessageParser.new(message) - with_lock(::Redis::Alfred::FACEBOOK_MESSAGE_MUTEX, sender_id: response.sender_id, recipient_id: response.recipient_id) do + key = format(::Redis::Alfred::FACEBOOK_MESSAGE_MUTEX, sender_id: response.sender_id, recipient_id: response.recipient_id) + with_lock(key) do process_message(response) end end diff --git a/app/jobs/webhooks/instagram_events_job.rb b/app/jobs/webhooks/instagram_events_job.rb index 024ba4a23..825f220a7 100644 --- a/app/jobs/webhooks/instagram_events_job.rb +++ b/app/jobs/webhooks/instagram_events_job.rb @@ -12,7 +12,8 @@ class Webhooks::InstagramEventsJob < MutexApplicationJob def perform(entries) @entries = entries - with_lock(::Redis::Alfred::IG_MESSAGE_MUTEX, sender_id: sender_id, ig_account_id: ig_account_id) do + key = format(::Redis::Alfred::IG_MESSAGE_MUTEX, sender_id: sender_id, ig_account_id: ig_account_id) + with_lock(key) do process_entries(entries) end end diff --git a/spec/jobs/mutex_application_job_spec.rb b/spec/jobs/mutex_application_job_spec.rb index 919195c32..b62db00f0 100644 --- a/spec/jobs/mutex_application_job_spec.rb +++ b/spec/jobs/mutex_application_job_spec.rb @@ -4,16 +4,6 @@ RSpec.describe MutexApplicationJob do let(:lock_manager) { instance_double(Redis::LockManager) } let(:lock_key) { 'test_key' } - let(:test_mutex_job_class) do - stub_const('TestMutexJob', Class.new(MutexApplicationJob) do - def perform - with_lock('test_key') do - # Do nothing - end - end - end) - end - before do allow(Redis::LockManager).to receive(:new).and_return(lock_manager) allow(lock_manager).to receive(:lock).and_return(true) @@ -22,24 +12,43 @@ RSpec.describe MutexApplicationJob do describe '#with_lock' do it 'acquires the lock and yields the block if lock is not acquired' do - expect(lock_manager).to receive(:lock).with(lock_key).and_return(true) + expect(lock_manager).to receive(:lock).with(lock_key, Redis::LockManager::LOCK_TIMEOUT).and_return(true) expect(lock_manager).to receive(:unlock).with(lock_key).and_return(true) expect { |b| described_class.new.send(:with_lock, lock_key, &b) }.to yield_control end + it 'acquires the lock with custom timeout' do + expect(lock_manager).to receive(:lock).with(lock_key, 5.seconds).and_return(true) + expect(lock_manager).to receive(:unlock).with(lock_key).and_return(true) + + expect { |b| described_class.new.send(:with_lock, lock_key, 5.seconds, &b) }.to yield_control + end + it 'raises LockAcquisitionError if it cannot acquire the lock' do - allow(lock_manager).to receive(:lock).with(lock_key).and_return(false) + allow(lock_manager).to receive(:lock).with(lock_key, Redis::LockManager::LOCK_TIMEOUT).and_return(false) expect do described_class.new.send(:with_lock, lock_key) do # Do nothing end end.to raise_error(MutexApplicationJob::LockAcquisitionError) + expect(lock_manager).not_to receive(:unlock) + end + + it 'raises StandardError if it execution raises it' do + allow(lock_manager).to receive(:lock).with(lock_key, Redis::LockManager::LOCK_TIMEOUT).and_return(false) + allow(lock_manager).to receive(:unlock).with(lock_key).and_return(true) + + expect do + described_class.new.send(:with_lock, lock_key) do + raise StandardError + end + end.to raise_error(StandardError) end it 'ensures that the lock is released even if there is an error during block execution' do - expect(lock_manager).to receive(:lock).with(lock_key).and_return(true) + expect(lock_manager).to receive(:lock).with(lock_key, Redis::LockManager::LOCK_TIMEOUT).and_return(true) expect(lock_manager).to receive(:unlock).with(lock_key).and_return(true) expect do From fa907840c7f0bb728db63daef75f5400ce8da70c Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Wed, 24 Jan 2024 16:22:04 +0530 Subject: [PATCH 4/9] feat: Add `middle_name` and `last_name` to contact model (#8771) feat: Add `middle_name` and `last_name` --- app/models/contact.rb | 2 ++ ...40124084032_add_middle_name_and_last_name_to_contacts.rb | 6 ++++++ db/schema.rb | 4 +++- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20240124084032_add_middle_name_and_last_name_to_contacts.rb diff --git a/app/models/contact.rb b/app/models/contact.rb index 176109f24..90f31d71f 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -11,6 +11,8 @@ # email :string # identifier :string # last_activity_at :datetime +# last_name :string default("") +# middle_name :string default("") # name :string default("") # phone_number :string # created_at :datetime not null diff --git a/db/migrate/20240124084032_add_middle_name_and_last_name_to_contacts.rb b/db/migrate/20240124084032_add_middle_name_and_last_name_to_contacts.rb new file mode 100644 index 000000000..5d7a8e266 --- /dev/null +++ b/db/migrate/20240124084032_add_middle_name_and_last_name_to_contacts.rb @@ -0,0 +1,6 @@ +class AddMiddleNameAndLastNameToContacts < ActiveRecord::Migration[7.0] + def change + add_column :contacts, :middle_name, :string, default: '' + add_column :contacts, :last_name, :string, default: '' + end +end diff --git a/db/schema.rb b/db/schema.rb index b914eca61..83065fb80 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_01_24_054340) do +ActiveRecord::Schema[7.0].define(version: 2024_01_24_084032) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "pg_trgm" @@ -419,6 +419,8 @@ ActiveRecord::Schema[7.0].define(version: 2024_01_24_054340) do t.jsonb "custom_attributes", default: {} t.datetime "last_activity_at", precision: nil t.integer "contact_type", default: 0 + t.string "middle_name", default: "" + t.string "last_name", default: "" t.index "lower((email)::text), account_id", name: "index_contacts_on_lower_email_account_id" t.index ["account_id", "email", "phone_number", "identifier"], name: "index_contacts_on_nonempty_fields", where: "(((email)::text <> ''::text) OR ((phone_number)::text <> ''::text) OR ((identifier)::text <> ''::text))" t.index ["account_id"], name: "index_contacts_on_account_id" From 904d76420db98b99a6babb26ceba882ab3cd6ba2 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Thu, 25 Jan 2024 12:05:00 +0530 Subject: [PATCH 5/9] fix: Add `last_activity_at` to notification push event data (#8784) fix: Add last_activity_at to push event data --- app/models/notification.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/notification.rb b/app/models/notification.rb index db8fbb8ea..e72265f47 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -60,6 +60,7 @@ class Notification < ApplicationRecord secondary_actor: secondary_actor&.push_event_data, user: user&.push_event_data, created_at: created_at.to_i, + last_activity_at: last_activity_at.to_i, account_id: account_id } From b7c9f779ade0f5ce4b3e59249ef461f63778d356 Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Wed, 24 Jan 2024 23:40:18 -0800 Subject: [PATCH 6/9] fix: Avoid processing reactions, ephemeral, request_welcome or unsupported messages (#8780) Currently, we do not support reactions, ephemeral messages, or the request_welcome event for the WhatsApp channel. However, if this is the first event we receive in Chatwoot (i.e., there is no previous conversation or contact in Chatwoot), it will create a contact and a conversation without any messages. This confuses our customer, as it may appear that Chatwoot has missed some messages. There are multiple cases where this might be the first event we receive in Chatwoot. One quick example is when the user has sent an outbound campaign from another tool and their customers reacted to the message. Another event like this is request_welcome event. WhatsApp has a concept for welcome messages. You can send an outbound message even though the user has not send a message. You can receive notifications through a webhook whenever a WhatsApp user initiates a chat with you for the first time. (Read the Welcome message section: https://developers.facebook.com/docs/whatsapp/cloud-api/phone-numbers/conversational-components/ ). Although this can help the business send a pro-active message to the user, we don't have it scoped in our feature set. For now, I'm ignoring this event. Fixes https://linear.app/chatwoot/issue/CW-3018/whatsapp-handle-request-welcome-case-properly Fixes https://linear.app/chatwoot/issue/CW-3017/whatsapp-handle-reactions-properly --- .../whatsapp/incoming_message_base_service.rb | 10 +++- .../incoming_message_service_helpers.rb | 2 +- .../jobs/webhooks/whatsapp_events_job_spec.rb | 55 +++++++++++++++++++ .../whatsapp/incoming_message_service_spec.rb | 12 ++-- 4 files changed, 69 insertions(+), 10 deletions(-) diff --git a/app/services/whatsapp/incoming_message_base_service.rb b/app/services/whatsapp/incoming_message_base_service.rb index 2d3b4efd3..f057fadbd 100644 --- a/app/services/whatsapp/incoming_message_base_service.rb +++ b/app/services/whatsapp/incoming_message_base_service.rb @@ -19,7 +19,13 @@ class Whatsapp::IncomingMessageBaseService private def process_messages - # message allready exists so we don't need to process + # We don't support reactions & ephemeral message now, we need to skip processing the message + # if the webhook event is a reaction or an ephermal message or an unsupported message. + return if unprocessable_message_type?(message_type) + + # Multiple webhook event can be received against the same message due to misconfigurations in the Meta + # business manager account. While we have not found the core reason yet, the following line ensure that + # there are no duplicate messages created. return if find_message_by_source_id(@processed_params[:messages].first[:id]) || message_under_process? cache_message_source_id_in_redis @@ -49,8 +55,6 @@ class Whatsapp::IncomingMessageBaseService end def create_messages - return if unprocessable_message_type?(message_type) - message = @processed_params[:messages].first log_error(message) && return if error_webhook_event?(message) diff --git a/app/services/whatsapp/incoming_message_service_helpers.rb b/app/services/whatsapp/incoming_message_service_helpers.rb index 76a31576e..c5474314b 100644 --- a/app/services/whatsapp/incoming_message_service_helpers.rb +++ b/app/services/whatsapp/incoming_message_service_helpers.rb @@ -44,7 +44,7 @@ module Whatsapp::IncomingMessageServiceHelpers end def unprocessable_message_type?(message_type) - %w[reaction ephemeral unsupported].include?(message_type) + %w[reaction ephemeral unsupported request_welcome].include?(message_type) end def brazil_phone_number?(phone_number) diff --git a/spec/jobs/webhooks/whatsapp_events_job_spec.rb b/spec/jobs/webhooks/whatsapp_events_job_spec.rb index 8a7e783ad..1b7ec172f 100644 --- a/spec/jobs/webhooks/whatsapp_events_job_spec.rb +++ b/spec/jobs/webhooks/whatsapp_events_job_spec.rb @@ -146,6 +146,61 @@ RSpec.describe Webhooks::WhatsappEventsJob do end.not_to change(Message, :count) end + it 'ignore reaction type message, would not create contact if the reaction is the first event' do + other_channel = create(:channel_whatsapp, phone_number: '+1987654', provider: 'whatsapp_cloud', sync_templates: false, + validate_provider_config: false) + wb_params = { + phone_number: channel.phone_number, + object: 'whatsapp_business_account', + entry: [{ + changes: [{ + value: { + contacts: [{ profile: { name: 'Test Test' }, wa_id: '1111981136571' }], + messages: [{ + from: '1111981136571', reaction: { emoji: '👍' }, timestamp: '1664799904', type: 'reaction' + }], + metadata: { + phone_number_id: other_channel.provider_config['phone_number_id'], + display_phone_number: other_channel.phone_number.delete('+') + } + } + }] + }] + }.with_indifferent_access + expect do + Whatsapp::IncomingMessageWhatsappCloudService.new(inbox: other_channel.inbox, params: wb_params).perform + end.not_to change(Contact, :count) + end + + it 'ignore request_welcome type message, would not create contact or conversation' do + other_channel = create(:channel_whatsapp, phone_number: '+1987654', provider: 'whatsapp_cloud', sync_templates: false, + validate_provider_config: false) + wb_params = { + phone_number: channel.phone_number, + object: 'whatsapp_business_account', + entry: [{ + changes: [{ + value: { + messages: [{ + from: '1111981136571', timestamp: '1664799904', type: 'request_welcome' + }], + metadata: { + phone_number_id: other_channel.provider_config['phone_number_id'], + display_phone_number: other_channel.phone_number.delete('+') + } + } + }] + }] + }.with_indifferent_access + expect do + Whatsapp::IncomingMessageWhatsappCloudService.new(inbox: other_channel.inbox, params: wb_params).perform + end.not_to change(Contact, :count) + + expect do + Whatsapp::IncomingMessageWhatsappCloudService.new(inbox: other_channel.inbox, params: wb_params).perform + end.not_to change(Conversation, :count) + end + it 'will not enque Whatsapp::IncomingMessageWhatsappCloudService when invalid phone number id' do other_channel = create(:channel_whatsapp, phone_number: '+1987654', provider: 'whatsapp_cloud', sync_templates: false, validate_provider_config: false) diff --git a/spec/services/whatsapp/incoming_message_service_spec.rb b/spec/services/whatsapp/incoming_message_service_spec.rb index 114faf953..0bcbf2a3e 100644 --- a/spec/services/whatsapp/incoming_message_service_spec.rb +++ b/spec/services/whatsapp/incoming_message_service_spec.rb @@ -81,7 +81,7 @@ describe Whatsapp::IncomingMessageService do end context 'when unsupported message types' do - it 'ignores type ephemeral' do + it 'ignores type ephemeral and does not create ghost conversation' do params = { 'contacts' => [{ 'profile' => { 'name' => 'Sojan Jose' }, 'wa_id' => '2423423243' }], 'messages' => [{ 'from' => '2423423243', 'id' => 'SDFADSf23sfasdafasdfa', 'text' => { 'body' => 'Test' }, @@ -89,12 +89,12 @@ describe Whatsapp::IncomingMessageService do }.with_indifferent_access described_class.new(inbox: whatsapp_channel.inbox, params: params).perform - expect(whatsapp_channel.inbox.conversations.count).not_to eq(0) - expect(Contact.all.first.name).to eq('Sojan Jose') + expect(whatsapp_channel.inbox.conversations.count).to eq(0) + expect(Contact.count).to eq(0) expect(whatsapp_channel.inbox.messages.count).to eq(0) end - it 'ignores type unsupported' do + it 'ignores type unsupported and does not create ghost conversation' do params = { 'contacts' => [{ 'profile' => { 'name' => 'Sojan Jose' }, 'wa_id' => '2423423243' }], 'messages' => [{ @@ -105,8 +105,8 @@ describe Whatsapp::IncomingMessageService do }.with_indifferent_access described_class.new(inbox: whatsapp_channel.inbox, params: params).perform - expect(whatsapp_channel.inbox.conversations.count).not_to eq(0) - expect(Contact.all.first.name).to eq('Sojan Jose') + expect(whatsapp_channel.inbox.conversations.count).to eq(0) + expect(Contact.count).to eq(0) expect(whatsapp_channel.inbox.messages.count).to eq(0) end end From 381423b1aedb71f3a959bbe62252c16afa6d7fe6 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Thu, 25 Jan 2024 17:17:16 +0530 Subject: [PATCH 7/9] fix: Removed author section from public help center (#8767) Co-authored-by: Sojan Jose --- .../dashboard/helpcenter/components/ArticleTable.vue | 2 +- .../api/v1/portals/articles/_article_header.html.erb | 7 +++---- .../public/api/v1/portals/categories/show.html.erb | 12 +----------- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/ArticleTable.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/ArticleTable.vue index 6704ce7bf..aa92ced67 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/ArticleTable.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/ArticleTable.vue @@ -1,7 +1,7 @@