From 78ce8a4652449b6ca65a8fcda32cf04f35296b8c Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Thu, 19 Oct 2023 12:12:34 +0530 Subject: [PATCH] feat: Add support for Instagram delivery reports (#8125) --- .../widgets/conversation/bubble/Actions.vue | 6 ++- app/jobs/webhooks/instagram_events_job.rb | 6 ++- app/services/instagram/read_status_service.rb | 28 +++++++++++ .../instagram_message_create_event.rb | 26 ++++++++++ .../webhooks/instagram_events_job_spec.rb | 6 +++ .../instagram/read_status_service_spec.rb | 48 +++++++++++++++++++ 6 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 app/services/instagram/read_status_service.rb create mode 100644 spec/services/instagram/read_status_service_spec.rb diff --git a/app/javascript/dashboard/components/widgets/conversation/bubble/Actions.vue b/app/javascript/dashboard/components/widgets/conversation/bubble/Actions.vue index f3c7e4f3c..27eecdc4b 100644 --- a/app/javascript/dashboard/components/widgets/conversation/bubble/Actions.vue +++ b/app/javascript/dashboard/components/widgets/conversation/bubble/Actions.vue @@ -231,7 +231,11 @@ export default { return contactLastSeenAt >= this.createdAt; } - if (this.isAWhatsAppChannel || this.isATwilioChannel) { + if ( + this.isAWhatsAppChannel || + this.isATwilioChannel || + this.isAFacebookInbox + ) { return this.sourceId && this.isRead; } diff --git a/app/jobs/webhooks/instagram_events_job.rb b/app/jobs/webhooks/instagram_events_job.rb index ad1114036..024ba4a23 100644 --- a/app/jobs/webhooks/instagram_events_job.rb +++ b/app/jobs/webhooks/instagram_events_job.rb @@ -7,7 +7,7 @@ class Webhooks::InstagramEventsJob < MutexApplicationJob base_uri 'https://graph.facebook.com/v11.0/me' # @return [Array] We will support further events like reaction or seen in future - SUPPORTED_EVENTS = [:message].freeze + SUPPORTED_EVENTS = [:message, :read].freeze def perform(entries) @entries = entries @@ -45,6 +45,10 @@ class Webhooks::InstagramEventsJob < MutexApplicationJob ::Instagram::MessageText.new(messaging).perform end + def read(messaging) + ::Instagram::ReadStatusService.new(params: messaging).perform + end + def messages(entry) (entry[:messaging].presence || entry[:standby] || []) end diff --git a/app/services/instagram/read_status_service.rb b/app/services/instagram/read_status_service.rb new file mode 100644 index 000000000..80f67d680 --- /dev/null +++ b/app/services/instagram/read_status_service.rb @@ -0,0 +1,28 @@ +class Instagram::ReadStatusService + pattr_initialize [:params!] + + def perform + return if instagram_channel.blank? + + process_status if message.present? + end + + def process_status + @message.status = 'read' + @message.save! + end + + def instagram_id + params[:recipient][:id] + end + + def instagram_channel + @instagram_channel ||= Channel::FacebookPage.find_by(instagram_id: instagram_id) + end + + def message + return unless params[:read][:mid] + + @message ||= @instagram_channel.inbox.messages.find_by(source_id: params[:read][:mid]) + end +end diff --git a/spec/factories/instagram/instagram_message_create_event.rb b/spec/factories/instagram/instagram_message_create_event.rb index 6b66c29a7..4cc8b7f67 100644 --- a/spec/factories/instagram/instagram_message_create_event.rb +++ b/spec/factories/instagram/instagram_message_create_event.rb @@ -242,4 +242,30 @@ FactoryBot.define do end initialize_with { attributes } end + + factory :messaging_seen_event, class: Hash do + entry do + [ + { + 'id': 'instagram-message-id-123', + 'time': '2021-09-08T06:34:04+0000', + 'messaging': [ + { + 'sender': { + 'id': 'Sender-id-1' + }, + 'recipient': { + 'id': 'chatwoot-app-user-id-1' + }, + 'timestamp': '2021-09-08T06:34:04+0000', + 'read': { + 'mid': 'message-id-1' + } + } + ] + } + ] + end + initialize_with { attributes } + end end diff --git a/spec/jobs/webhooks/instagram_events_job_spec.rb b/spec/jobs/webhooks/instagram_events_job_spec.rb index b5e73e2f5..905810512 100644 --- a/spec/jobs/webhooks/instagram_events_job_spec.rb +++ b/spec/jobs/webhooks/instagram_events_job_spec.rb @@ -27,6 +27,7 @@ describe Webhooks::InstagramEventsJob do let!(:attachment_params) { build(:instagram_message_attachment_event).with_indifferent_access } let!(:story_mention_params) { build(:instagram_story_mention_event).with_indifferent_access } let!(:story_mention_echo_params) { build(:instagram_story_mention_event_with_echo).with_indifferent_access } + let!(:messaging_seen_event) { build(:messaging_seen_event).with_indifferent_access } let(:fb_object) { double } describe '#perform' do @@ -151,6 +152,11 @@ describe Webhooks::InstagramEventsJob do expect(instagram_inbox.contact_inboxes.count).to be 0 expect(instagram_inbox.messages.count).to be 0 end + + it 'handle messaging_seen callback' do + expect(Instagram::ReadStatusService).to receive(:new).with(params: messaging_seen_event[:entry][0][:messaging][0]).and_call_original + instagram_webhook.perform_now(messaging_seen_event[:entry]) + end end end end diff --git a/spec/services/instagram/read_status_service_spec.rb b/spec/services/instagram/read_status_service_spec.rb new file mode 100644 index 000000000..db5dc73b3 --- /dev/null +++ b/spec/services/instagram/read_status_service_spec.rb @@ -0,0 +1,48 @@ +require 'rails_helper' + +describe Instagram::ReadStatusService do + before do + create(:message, message_type: :incoming, inbox: instagram_inbox, account: account, conversation: conversation, + source_id: 'chatwoot-app-user-id-1') + end + + let!(:account) { create(:account) } + let!(:instagram_channel) { create(:channel_instagram_fb_page, account: account, instagram_id: 'chatwoot-app-user-id-1') } + let!(:instagram_inbox) { create(:inbox, channel: instagram_channel, account: account, greeting_enabled: false) } + let!(:contact) { create(:contact, account: account) } + let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: instagram_inbox) } + let(:conversation) { create(:conversation, contact: contact, inbox: instagram_inbox, contact_inbox: contact_inbox) } + + describe '#perform' do + context 'when messaging_seen callback is fired' do + let(:message) { conversation.messages.last } + + it 'updates the message status to read if the status is delivered' do + params = { + recipient: { + id: 'chatwoot-app-user-id-1' + }, + read: { + mid: message.source_id + } + } + described_class.new(params: params).perform + expect(conversation.reload.messages.last.status).to eq('read') + end + + it 'does not update the status if message is not found' do + params = { + recipient: { + id: 'chatwoot-app-user-id-1' + }, + read: { + mid: 'random-message-id' + } + } + + described_class.new(params: params).perform + expect(conversation.reload.messages.last.status).not_to eq('read') + end + end + end +end