feat: mark unread conversations (#49)
* feat: add MESSAGES_UNREAD event and implement unread action in ConversationsController * feat: rename MESSAGES_UNREAD event to CONVERSATION_UNREAD and update unread action in ConversationsController * feat: implement send_unread_conversation method and update conversation_unread event handling * feat: add test for dispatching conversation.unread event in ConversationsController * feat: add conversation_unread event handling in ChannelListener * feat: add send_unread_conversation method and corresponding tests in Whatsapp channel * feat: add logging for nil last_message in send_unread_conversation method * feat: add send_unread_conversation to error handling methods * feat: implement send_unread_conversation method with logging for nil last_message * fix: add nil check and logging for last_message in send_unread_conversation method * fix: update conversation_unread method to call unread_conversation instead of send_unread_conversation * refactor: rename send_unread_conversation to unread_conversation and update method implementation * refactor: rename send_unread_conversation to send_unread_messages and update implementation to handle multiple messages * refactor: rename send_read_messages to read_messages and send_unread_messages to unread_message for clarity * refactor: rename send_read_messages to read_messages and send_unread_messages to unread_message for clarity * refactor: rename send_read_messages to read_messages for consistency
This commit is contained in:
parent
5735134772
commit
21133c3383
@ -118,6 +118,8 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
end
|
||||
|
||||
def unread
|
||||
Rails.configuration.dispatcher.dispatch(Events::Types::CONVERSATION_UNREAD, Time.zone.now, conversation: @conversation)
|
||||
|
||||
last_incoming_message = @conversation.messages.incoming.last
|
||||
last_seen_at = last_incoming_message.created_at - 1.second if last_incoming_message.present?
|
||||
update_last_seen_on_conversation(last_seen_at, true)
|
||||
|
||||
@ -11,6 +11,14 @@ class ChannelListener < BaseListener
|
||||
handle_typing_event(event)
|
||||
end
|
||||
|
||||
def conversation_unread(event)
|
||||
conversation = event.data[:conversation]
|
||||
channel = conversation.inbox.channel
|
||||
return unless channel.respond_to?(:unread_conversation)
|
||||
|
||||
channel.unread_conversation(conversation)
|
||||
end
|
||||
|
||||
def account_presence_updated(event)
|
||||
account_id, user_id, status = event.data.values_at(:account_id, :user_id, :status)
|
||||
account = Account.find(account_id)
|
||||
@ -26,13 +34,13 @@ class ChannelListener < BaseListener
|
||||
conversation, last_seen_at = event.data.values_at(:conversation, :last_seen_at)
|
||||
|
||||
channel = conversation.inbox.channel
|
||||
return unless channel.respond_to?(:send_read_messages)
|
||||
return unless channel.respond_to?(:read_messages)
|
||||
|
||||
messages = conversation.messages.where(message_type: :incoming).where.not(status: :read)
|
||||
|
||||
messages = messages.where('updated_at > ?', last_seen_at) if last_seen_at.present?
|
||||
|
||||
channel.send_read_messages(messages, conversation: conversation) if messages.any?
|
||||
channel.read_messages(messages, conversation: conversation) if messages.any?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@ -91,10 +91,17 @@ class Channel::Whatsapp < ApplicationRecord
|
||||
provider_service.update_presence(status)
|
||||
end
|
||||
|
||||
def send_read_messages(messages, conversation:)
|
||||
return unless provider_service.respond_to?(:send_read_messages)
|
||||
def read_messages(messages, conversation:)
|
||||
return unless provider_service.respond_to?(:read_messages)
|
||||
|
||||
provider_service.send_read_messages(conversation.contact.phone_number, messages)
|
||||
provider_service.read_messages(conversation.contact.phone_number, messages)
|
||||
end
|
||||
|
||||
def unread_conversation(conversation)
|
||||
return unless provider_service.respond_to?(:unread_message)
|
||||
|
||||
last_message = conversation.messages.last
|
||||
provider_service.unread_message(conversation.contact.phone_number, last_message) if last_message
|
||||
end
|
||||
|
||||
delegate :setup_channel_provider, to: :provider_service
|
||||
|
||||
@ -108,7 +108,7 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
process_response(response)
|
||||
end
|
||||
|
||||
def send_read_messages(phone_number, messages)
|
||||
def read_messages(phone_number, messages)
|
||||
@phone_number = phone_number
|
||||
|
||||
response = HTTParty.post(
|
||||
@ -129,6 +129,31 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
process_response(response)
|
||||
end
|
||||
|
||||
def unread_message(phone_number, message) # rubocop:disable Metrics/MethodLength
|
||||
@phone_number = phone_number
|
||||
|
||||
response = HTTParty.post(
|
||||
"#{provider_url}/connections/#{whatsapp_channel.phone_number}/chat-modify",
|
||||
headers: api_headers,
|
||||
body: {
|
||||
jid: remote_jid,
|
||||
mod: {
|
||||
markRead: false,
|
||||
lastMessages: {
|
||||
key: {
|
||||
id: message.source_id,
|
||||
remoteJid: remote_jid,
|
||||
fromMe: message.message_type == 'outgoing'
|
||||
},
|
||||
messageTimestamp: message.content_attributes[:external_created_at]
|
||||
}
|
||||
}
|
||||
}.to_json
|
||||
)
|
||||
|
||||
process_response(response)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def provider_url
|
||||
@ -220,5 +245,6 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
:send_message,
|
||||
:toggle_typing_status,
|
||||
:update_presence,
|
||||
:send_read_messages
|
||||
:read_messages,
|
||||
:unread_message
|
||||
end
|
||||
|
||||
@ -31,6 +31,7 @@ module Events::Types
|
||||
CONVERSATION_RECORDING = 'conversation.recording'
|
||||
CONVERSATION_TYPING_OFF = 'conversation.typing_off'
|
||||
CONVERSATION_MENTIONED = 'conversation.mentioned'
|
||||
CONVERSATION_UNREAD = 'conversation.unread'
|
||||
|
||||
# message events
|
||||
MESSAGE_CREATED = 'message.created'
|
||||
|
||||
@ -723,6 +723,19 @@ RSpec.describe 'Conversations API', type: :request do
|
||||
create(:message, conversation: conversation, account: account, inbox: conversation.inbox, content: 'Hello', message_type: 'incoming')
|
||||
end
|
||||
|
||||
it 'dispatches conversation.unread event' do
|
||||
freeze_time
|
||||
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
||||
.with(Events::Types::CONVERSATION_UNREAD, Time.zone.now, conversation: conversation)
|
||||
|
||||
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/unread",
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(Rails.configuration.dispatcher).to have_received(:dispatch)
|
||||
end
|
||||
|
||||
it 'updates last seen' do
|
||||
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/unread",
|
||||
headers: agent.create_new_auth_token,
|
||||
|
||||
@ -60,6 +60,20 @@ describe ChannelListener do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#conversation_unread' do
|
||||
let(:channel) { create(:channel_whatsapp, sync_templates: false, validate_provider_config: false) }
|
||||
let(:conversation) { create(:conversation, inbox: create(:inbox, channel: channel)) }
|
||||
let(:event) { Events::Base.new(Events::Types::CONVERSATION_UNREAD, Time.zone.now, conversation: conversation) }
|
||||
|
||||
it 'calls unread_conversation on the channel' do
|
||||
allow(channel).to receive(:unread_conversation).with(conversation)
|
||||
|
||||
listener.conversation_unread(event)
|
||||
|
||||
expect(channel).to have_received(:unread_conversation)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#account_presence_updated' do
|
||||
let(:account_user) { create(:account_user) }
|
||||
let(:inbox) { create(:inbox, account: account_user.account) }
|
||||
@ -101,11 +115,11 @@ describe ChannelListener do
|
||||
create(:message, conversation: conversation, message_type: :incoming, status: :read)
|
||||
sent_message = create(:message, conversation: conversation, message_type: :incoming, status: :sent)
|
||||
|
||||
allow(channel).to receive(:send_read_messages).with([sent_message], conversation: conversation)
|
||||
allow(channel).to receive(:read_messages).with([sent_message], conversation: conversation)
|
||||
|
||||
listener.messages_read(event)
|
||||
|
||||
expect(channel).to have_received(:send_read_messages)
|
||||
expect(channel).to have_received(:read_messages)
|
||||
end
|
||||
|
||||
it 'skips the event if the channel does not respond to send_read_messages' do
|
||||
@ -119,33 +133,33 @@ describe ChannelListener do
|
||||
it 'skips the event if there are no unread messages' do
|
||||
create(:message, conversation: conversation, message_type: :incoming, status: :read)
|
||||
|
||||
allow(channel).to receive(:send_read_messages)
|
||||
allow(channel).to receive(:read_messages)
|
||||
|
||||
listener.messages_read(event)
|
||||
|
||||
expect(channel).not_to have_received(:send_read_messages)
|
||||
expect(channel).not_to have_received(:read_messages)
|
||||
end
|
||||
|
||||
it 'filters messages ignoring last_seen_at' do
|
||||
old_message = create(:message, conversation: conversation, message_type: :incoming, status: :sent, updated_at: last_seen_at - 1.day)
|
||||
recent_message = create(:message, conversation: conversation, message_type: :incoming, status: :sent, updated_at: Time.zone.now)
|
||||
|
||||
allow(channel).to receive(:send_read_messages).with([old_message, recent_message], conversation: conversation)
|
||||
allow(channel).to receive(:read_messages).with([old_message, recent_message], conversation: conversation)
|
||||
|
||||
listener.messages_read(Events::Base.new(Events::Types::MESSAGES_READ, Time.zone.now, conversation: conversation, last_seen_at: nil))
|
||||
|
||||
expect(channel).to have_received(:send_read_messages)
|
||||
expect(channel).to have_received(:read_messages)
|
||||
end
|
||||
|
||||
it 'filters messages based on last_seen_at' do
|
||||
create(:message, conversation: conversation, message_type: :incoming, status: :sent, updated_at: last_seen_at - 1.day)
|
||||
recent_message = create(:message, conversation: conversation, message_type: :incoming, status: :sent, updated_at: Time.zone.now)
|
||||
|
||||
allow(channel).to receive(:send_read_messages).with([recent_message], conversation: conversation)
|
||||
allow(channel).to receive(:read_messages).with([recent_message], conversation: conversation)
|
||||
|
||||
listener.messages_read(event)
|
||||
|
||||
expect(channel).to have_received(:send_read_messages)
|
||||
expect(channel).to have_received(:read_messages)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -120,21 +120,21 @@ RSpec.describe Channel::Whatsapp do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#send_read_messages' do
|
||||
describe '#read_messages' do
|
||||
let(:channel) { create(:channel_whatsapp, provider: 'baileys', validate_provider_config: false, sync_templates: false) }
|
||||
let(:conversation) { create(:conversation) }
|
||||
let(:message) { create(:message) }
|
||||
|
||||
it 'calls provider service method' do
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, send_read_messages: nil)
|
||||
allow(provider_double).to receive(:send_read_messages).with([message], conversation.contact.phone_number)
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, read_messages: nil)
|
||||
allow(provider_double).to receive(:read_messages).with([message], conversation.contact.phone_number)
|
||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
||||
.with(whatsapp_channel: channel)
|
||||
.and_return(provider_double)
|
||||
|
||||
channel.send_read_messages([message], conversation: conversation)
|
||||
channel.read_messages([message], conversation: conversation)
|
||||
|
||||
expect(provider_double).to have_received(:send_read_messages)
|
||||
expect(provider_double).to have_received(:read_messages)
|
||||
end
|
||||
|
||||
it 'does not call method if provider service does not implement it' do
|
||||
@ -145,11 +145,50 @@ RSpec.describe Channel::Whatsapp do
|
||||
.and_return(provider_double)
|
||||
|
||||
expect do
|
||||
channel.send_read_messages([message], conversation: conversation)
|
||||
channel.read_messages([message], conversation: conversation)
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe '#unread_conversation' do
|
||||
let(:channel) { create(:channel_whatsapp, provider: 'baileys', validate_provider_config: false, sync_templates: false) }
|
||||
let(:conversation) { create(:conversation) }
|
||||
|
||||
it 'calls provider service method' do
|
||||
message = create(:message, conversation: conversation)
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, unread_message: nil)
|
||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new).with(whatsapp_channel: channel).and_return(provider_double)
|
||||
allow(provider_double).to receive(:unread_message).with(conversation.contact.phone_number, [message])
|
||||
|
||||
channel.unread_conversation(conversation)
|
||||
|
||||
expect(provider_double).to have_received(:unread_message)
|
||||
end
|
||||
|
||||
it 'does not call method if provider service does not implement it' do
|
||||
# NOTE: This message ensures that there are messages but the provider does not implement the method.
|
||||
create(:message, conversation: conversation)
|
||||
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService)
|
||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new).with(whatsapp_channel: channel).and_return(provider_double)
|
||||
|
||||
expect do
|
||||
channel.unread_conversation(conversation)
|
||||
end
|
||||
.not_to raise_error
|
||||
end
|
||||
|
||||
it 'does not call method if there are no messages' do
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, unread_message: nil)
|
||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new).with(whatsapp_channel: channel).and_return(provider_double)
|
||||
allow(provider_double).to receive(:unread_message)
|
||||
|
||||
channel.unread_conversation(conversation)
|
||||
|
||||
expect(provider_double).not_to have_received(:unread_message)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'callbacks' do
|
||||
describe '#disconnect_channel_provider' do
|
||||
context 'when provider is baileys' do
|
||||
|
||||
@ -4,7 +4,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
subject(:service) { described_class.new(whatsapp_channel: whatsapp_channel) }
|
||||
|
||||
let(:whatsapp_channel) { create(:channel_whatsapp, provider: 'baileys', validate_provider_config: false) }
|
||||
let(:message) { create(:message, source_id: 'msg_123') }
|
||||
let(:message) { create(:message, source_id: 'msg_123', content_attributes: { external_created_at: 123 }) }
|
||||
|
||||
let(:test_send_phone_number) { '551187654321' }
|
||||
let(:test_send_jid) { '551187654321@s.whatsapp.net' }
|
||||
@ -371,7 +371,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#send_read_messages' do
|
||||
describe '#read_messages' do
|
||||
it 'send read messages request' do
|
||||
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/read-messages")
|
||||
.with(
|
||||
@ -379,7 +379,30 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
body: { keys: [{ id: message.source_id, remoteJid: test_send_jid, fromMe: false }] }.to_json
|
||||
).to_return(status: 200, body: '', headers: {})
|
||||
|
||||
result = service.send_read_messages(test_send_phone_number, [message])
|
||||
result = service.read_messages(test_send_phone_number, [message])
|
||||
|
||||
expect(result).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#unread_message' do
|
||||
it 'send unread message request' do
|
||||
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/chat-modify")
|
||||
.with(
|
||||
headers: stub_headers(whatsapp_channel),
|
||||
body: {
|
||||
jid: test_send_jid,
|
||||
mod: {
|
||||
markRead: false,
|
||||
lastMessages: {
|
||||
key: { id: 'msg_123', remoteJid: test_send_jid, fromMe: false },
|
||||
messageTimestamp: 123
|
||||
}
|
||||
}
|
||||
}.to_json
|
||||
).to_return(status: 200)
|
||||
|
||||
result = service.unread_message(test_send_phone_number, message)
|
||||
|
||||
expect(result).to be(true)
|
||||
end
|
||||
|
||||
Loading…
Reference in New Issue
Block a user