From e21d7552d3805d9d451e1ce9a365cb44193571b4 Mon Sep 17 00:00:00 2001 From: Jaideep Guntupalli <63718527+JaideepGuntupalli@users.noreply.github.com> Date: Wed, 3 Apr 2024 21:33:23 +0530 Subject: [PATCH] feat: extending lock to single conversation to meta inbox (#9104) This change introduces the ability to lock conversations to a single thread for Instagram and facebook messages within the Meta inbox, mirroring existing functionality in WhatsApp and SMS inboxes. Co-authored-by: Shivam Mishra --- .../messages/facebook/message_builder.rb | 18 +++- .../messages/instagram/message_builder.rb | 25 ++++- .../dashboard/settings/inbox/Settings.vue | 4 +- .../messages/facebook/message_builder_spec.rb | 86 +++++++++++++++++ .../instagram/message_builder_spec.rb | 92 +++++++++++++++++++ .../incoming_fb_text_message.rb | 15 +++ 6 files changed, 235 insertions(+), 5 deletions(-) diff --git a/app/builders/messages/facebook/message_builder.rb b/app/builders/messages/facebook/message_builder.rb index fec298bce..2c55922f6 100644 --- a/app/builders/messages/facebook/message_builder.rb +++ b/app/builders/messages/facebook/message_builder.rb @@ -53,7 +53,23 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder end def conversation - @conversation ||= Conversation.find_by(conversation_params) || build_conversation + @conversation ||= set_conversation_based_on_inbox_config + end + + def set_conversation_based_on_inbox_config + if @inbox.lock_to_single_conversation + Conversation.where(conversation_params).order(created_at: :desc).first || build_conversation + else + find_or_build_for_multiple_conversations + end + end + + def find_or_build_for_multiple_conversations + # If lock to single conversation is disabled, we will create a new conversation if previous conversation is resolved + last_conversation = Conversation.where(conversation_params).where.not(status: :resolved).order(created_at: :desc).first + return build_conversation if last_conversation.nil? + + last_conversation end def build_conversation diff --git a/app/builders/messages/instagram/message_builder.rb b/app/builders/messages/instagram/message_builder.rb index e9debb767..7f1b9cab2 100644 --- a/app/builders/messages/instagram/message_builder.rb +++ b/app/builders/messages/instagram/message_builder.rb @@ -69,9 +69,28 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder end def conversation - @conversation ||= Conversation.where(conversation_params).find_by( - "additional_attributes ->> 'type' = 'instagram_direct_message'" - ) || build_conversation + @conversation ||= set_conversation_based_on_inbox_config + end + + def instagram_direct_message_conversation + Conversation.where(conversation_params) + .where("additional_attributes ->> 'type' = 'instagram_direct_message'") + end + + def set_conversation_based_on_inbox_config + if @inbox.lock_to_single_conversation + instagram_direct_message_conversation.order(created_at: :desc).first || build_conversation + else + find_or_build_for_multiple_conversations + end + end + + def find_or_build_for_multiple_conversations + last_conversation = instagram_direct_message_conversation.where.not(status: :resolved).order(created_at: :desc).first + + return build_conversation if last_conversation.nil? + + last_conversation end def message_content diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue index 87b6e6724..8823e593f 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue @@ -589,7 +589,9 @@ export default { return this.inbox.name; }, canLocktoSingleConversation() { - return this.isASmsInbox || this.isAWhatsAppChannel; + return ( + this.isASmsInbox || this.isAWhatsAppChannel || this.isAFacebookInbox + ); }, inboxNameLabel() { if (this.isAWebWidgetInbox) { diff --git a/spec/builders/messages/facebook/message_builder_spec.rb b/spec/builders/messages/facebook/message_builder_spec.rb index 8219adaa7..4b94c9be4 100644 --- a/spec/builders/messages/facebook/message_builder_spec.rb +++ b/spec/builders/messages/facebook/message_builder_spec.rb @@ -58,5 +58,91 @@ describe Messages::Facebook::MessageBuilder do expect(facebook_channel.inbox.reload.contacts.count).to eq(1) expect(contact.name).to eq(default_name) end + + context 'when lock to single conversation' do + subject(:mocked_message_builder) do + described_class.new(mocked_incoming_fb_text_message, facebook_channel.inbox).perform + end + + let!(:mocked_message_object) { build(:mocked_message_text, sender_id: contact_inbox.source_id).to_json } + let!(:mocked_incoming_fb_text_message) { Integrations::Facebook::MessageParser.new(mocked_message_object) } + let(:contact) { create(:contact, name: 'Jane Dae') } + let(:contact_inbox) { create(:contact_inbox, contact_id: contact.id, inbox_id: facebook_channel.inbox.id) } + + context 'when lock to single conversation is disabled' do + before do + facebook_channel.inbox.update!(lock_to_single_conversation: false) + stub_request(:get, /graph.facebook.com/) + end + + it 'creates a new conversation if existing conversation is not present' do + inital_count = Conversation.count + + mocked_message_builder + + facebook_channel.inbox.reload + + expect(facebook_channel.inbox.conversations.count).to eq(1) + expect(Conversation.count).to eq(inital_count + 1) + end + + it 'will not create a new conversation if last conversation is not resolved' do + existing_conversation = create(:conversation, account_id: facebook_channel.inbox.account.id, inbox_id: facebook_channel.inbox.id, + contact_id: contact.id, contact_inbox_id: contact_inbox.id, + status: :open) + + mocked_message_builder + + facebook_channel.inbox.reload + + expect(facebook_channel.inbox.conversations.last.id).to eq(existing_conversation.id) + end + + it 'creates a new conversation if last conversation is resolved' do + existing_conversation = create(:conversation, account_id: facebook_channel.inbox.account.id, inbox_id: facebook_channel.inbox.id, + contact_id: contact.id, contact_inbox_id: contact_inbox.id, status: :resolved) + + inital_count = Conversation.count + + mocked_message_builder + + facebook_channel.inbox.reload + + expect(facebook_channel.inbox.conversations.last.id).not_to eq(existing_conversation.id) + expect(Conversation.count).to eq(inital_count + 1) + end + end + + context 'when lock to single conversation is enabled' do + before do + facebook_channel.inbox.update!(lock_to_single_conversation: true) + stub_request(:get, /graph.facebook.com/) + end + + it 'creates a new conversation if existing conversation is not present' do + inital_count = Conversation.count + mocked_message_builder + + facebook_channel.inbox.reload + + expect(facebook_channel.inbox.conversations.count).to eq(1) + expect(Conversation.count).to eq(inital_count + 1) + end + + it 'reopens last conversation if last conversation exists' do + existing_conversation = create(:conversation, account_id: facebook_channel.inbox.account.id, inbox_id: facebook_channel.inbox.id, + contact_id: contact.id, contact_inbox_id: contact_inbox.id) + + inital_count = Conversation.count + + mocked_message_builder + + facebook_channel.inbox.reload + + expect(facebook_channel.inbox.conversations.last.id).to eq(existing_conversation.id) + expect(Conversation.count).to eq(inital_count) + end + end + end end end diff --git a/spec/builders/messages/instagram/message_builder_spec.rb b/spec/builders/messages/instagram/message_builder_spec.rb index 618b8bdd6..3db393dab 100644 --- a/spec/builders/messages/instagram/message_builder_spec.rb +++ b/spec/builders/messages/instagram/message_builder_spec.rb @@ -183,4 +183,96 @@ describe Messages::Instagram::MessageBuilder do expect(contact.name).to eq('Jane Dae') end end + + context 'when lock to single conversation is disabled' do + before do + instagram_inbox.update!(lock_to_single_conversation: false) + stub_request(:get, /graph.facebook.com/) + end + + it 'creates a new conversation if existing conversation is not present' do + inital_count = Conversation.count + message = dm_params[:entry][0]['messaging'][0] + contact_inbox + + described_class.new(message, instagram_inbox).perform + + instagram_inbox.reload + contact_inbox.reload + + expect(instagram_inbox.conversations.count).to eq(1) + expect(Conversation.count).to eq(inital_count + 1) + end + + it 'will not create a new conversation if last conversation is not resolved' do + existing_conversation = create(:conversation, account_id: account.id, inbox_id: instagram_inbox.id, contact_id: contact.id, status: :open, + additional_attributes: { type: 'instagram_direct_message', conversation_language: 'en' }) + + message = dm_params[:entry][0]['messaging'][0] + contact_inbox + + described_class.new(message, instagram_inbox).perform + + instagram_inbox.reload + contact_inbox.reload + + expect(instagram_inbox.conversations.last.id).to eq(existing_conversation.id) + end + + it 'creates a new conversation if last conversation is resolved' do + existing_conversation = create(:conversation, account_id: account.id, inbox_id: instagram_inbox.id, contact_id: contact.id, status: :resolved, + additional_attributes: { type: 'instagram_direct_message', conversation_language: 'en' }) + + inital_count = Conversation.count + message = dm_params[:entry][0]['messaging'][0] + contact_inbox + + described_class.new(message, instagram_inbox).perform + + instagram_inbox.reload + contact_inbox.reload + + expect(instagram_inbox.conversations.last.id).not_to eq(existing_conversation.id) + expect(Conversation.count).to eq(inital_count + 1) + end + end + + context 'when lock to single conversation is enabled' do + before do + instagram_inbox.update!(lock_to_single_conversation: true) + stub_request(:get, /graph.facebook.com/) + end + + it 'creates a new conversation if existing conversation is not present' do + inital_count = Conversation.count + message = dm_params[:entry][0]['messaging'][0] + contact_inbox + + described_class.new(message, instagram_inbox).perform + + instagram_inbox.reload + contact_inbox.reload + + expect(instagram_inbox.conversations.count).to eq(1) + expect(Conversation.count).to eq(inital_count + 1) + end + + it 'reopens last conversation if last conversation is resolved' do + existing_conversation = create(:conversation, account_id: account.id, inbox_id: instagram_inbox.id, contact_id: contact.id, status: :resolved, + additional_attributes: { type: 'instagram_direct_message', conversation_language: 'en' }) + + inital_count = Conversation.count + + message = dm_params[:entry][0]['messaging'][0] + contact_inbox + + described_class.new(message, instagram_inbox).perform + + instagram_inbox.reload + contact_inbox.reload + + expect(instagram_inbox.conversations.last.id).to eq(existing_conversation.id) + expect(Conversation.count).to eq(inital_count) + end + end end diff --git a/spec/factories/facebook_message/incoming_fb_text_message.rb b/spec/factories/facebook_message/incoming_fb_text_message.rb index dc75a912c..261e49261 100644 --- a/spec/factories/facebook_message/incoming_fb_text_message.rb +++ b/spec/factories/facebook_message/incoming_fb_text_message.rb @@ -11,6 +11,21 @@ FactoryBot.define do initialize_with { attributes } end + factory :mocked_message_text, class: Hash do + transient do + sender_id { '3383290475046708' } + end + + initialize_with do + { messaging: { sender: { id: sender_id }, + recipient: { id: '117172741761305' }, + message: { mid: 'm_KXGKDUpO6xbVdAmZFBVpzU1AhKVJdAIUnUH4cwkvb_K3iZsWhowDRyJ_DcowEpJjncaBwdCIoRrixvCbbO1PcA', + text: 'facebook message' } } } + end + + # initialize_with { attributes } + end + factory :message_deliveries, class: Hash do messaging do { sender: { id: '3383290475046708' },