Skip to content

Commit

Permalink
[improvement] 360dialog extend request to receive message feature (#2053
Browse files Browse the repository at this point in the history
)

### What changed in this PR and Why

We now save the external id of outgoing 360dialog WhatsApp messages to
the message record in the database. This allows us to:

a) Send out a previous personalized message that the contributor might
have forgotten to ask for, but is still interested in responding to, and
b) Support a reply_to feature where the contributor might quote reply an
older message and we can properly assign the incoming message to the
correct request instead of trying to infer it solely based on the latest
message they received and relying on the user to move the message to the
correct request if it has been incorrectly assigned.

Closes #2031

---------

Co-authored-by: Robert Schäfer <git@roschaefer.de>
  • Loading branch information
mattwr18 and roschaefer authored Oct 7, 2024
1 parent 39ed9e5 commit b8baad3
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def initialize_message(whats_app_message)
trigger(REQUEST_FOR_MORE_INFO, sender) if request_for_more_info?(text)
trigger(UNSUBSCRIBE_CONTRIBUTOR, sender) if unsubscribe_text?(text)
trigger(RESUBSCRIBE_CONTRIBUTOR, sender) if resubscribe_text?(text)
trigger(REQUEST_TO_RECEIVE_MESSAGE, sender) if request_to_receive_message?(sender, text)
trigger(REQUEST_TO_RECEIVE_MESSAGE, sender, message) if request_to_receive_message?(sender, text)

message = Message.new(text: text, sender: sender)
message.raw_data.attach(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ def send_message(recipient, message)
WhatsAppAdapter::ThreeSixtyDialogOutbound::Text.perform_later(organization_id: message.organization.id,
payload: text_payload(
recipient, message.text
))
),
message_id: message.id)
else
files.each do |_file|
WhatsAppAdapter::ThreeSixtyDialog::UploadFileJob.perform_later(message_id: message.id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class File < ApplicationJob
queue_as :default

def perform(message_id:, file_id:)
message = Message.find(message_id)
@message = Message.find(message_id)
organization = Organization.find(message.organization.id)

@recipient = message.recipient
Expand All @@ -26,7 +26,7 @@ def perform(message_id:, file_id:)

private

attr_reader :recipient, :file_id
attr_reader :recipient, :file_id, :message

def payload
{
Expand All @@ -41,10 +41,11 @@ def payload
end

def handle_response(response)
case response.code.to_i
when 200
Rails.logger.debug 'Great!'
when 400..599
case response
when Net::HTTPSuccess
external_id = JSON.parse(response.body)['messages'].first['id']
message.update!(external_id: external_id)
when Net::HTTPClientError, Net::HTTPServerError
exception = WhatsAppAdapter::ThreeSixtyDialogError.new(error_code: response.code, message: response.body)
ErrorNotifier.report(exception)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ class ThreeSixtyDialogOutbound
class Text < ApplicationJob
queue_as :default

def perform(organization_id:, payload:)
def perform(organization_id:, payload:, message_id: nil)
organization = Organization.find(organization_id)
@message = Message.find(message_id) if message_id

url = URI.parse("#{ENV.fetch('THREE_SIXTY_DIALOG_WHATS_APP_REST_API_ENDPOINT', 'https://stoplight.io/mocks/360dialog/360dialog-partner-api/24588693')}/messages")
headers = { 'D360-API-KEY' => organization.three_sixty_dialog_client_api_key, 'Content-Type' => 'application/json' }
Expand All @@ -21,13 +22,16 @@ def perform(organization_id:, payload:)
ErrorNotifier.report(e)
end

attr_reader :message

private

def handle_response(response)
case response.code.to_i
when 201
Rails.logger.debug 'Great!'
when 400..599
case response
when Net::HTTPSuccess
external_id = JSON.parse(response.body)['messages'].first['id']
message&.update!(external_id: external_id)
when Net::HTTPClientError, Net::HTTPServerError
exception = WhatsAppAdapter::ThreeSixtyDialogError.new(error_code: response.code, message: response.body)
ErrorNotifier.report(exception)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ def perform(organization_id:, components:)
handle_request_for_more_info(contributor, organization)
end

adapter.on(WhatsAppAdapter::ThreeSixtyDialogInbound::REQUEST_TO_RECEIVE_MESSAGE) do |contributor|
handle_request_to_receive_message(contributor)
adapter.on(WhatsAppAdapter::ThreeSixtyDialogInbound::REQUEST_TO_RECEIVE_MESSAGE) do |contributor, whats_app_message|
handle_request_to_receive_message(contributor, whats_app_message)
end

adapter.on(WhatsAppAdapter::ThreeSixtyDialogInbound::UNSUPPORTED_CONTENT) do |contributor|
Expand All @@ -43,10 +43,11 @@ def handle_unknown_contributor(whats_app_phone_number)
ErrorNotifier.report(exception)
end

def handle_request_to_receive_message(contributor)
def handle_request_to_receive_message(contributor, whats_app_message)
contributor.update!(whats_app_message_template_responded_at: Time.current, whats_app_message_template_sent_at: nil)

WhatsAppAdapter::ThreeSixtyDialogOutbound.send!(contributor.received_messages.first)
external_id = whats_app_message.dig(:context, :id)
message = Message.find_by(external_id: external_id) if external_id
WhatsAppAdapter::ThreeSixtyDialogOutbound.send!(message || contributor.received_messages.first)
end

def handle_request_for_more_info(contributor, organization)
Expand Down
34 changes: 34 additions & 0 deletions spec/adapters/whats_app_adapter/three_sixty_dialog_inbound_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -477,5 +477,39 @@
end
end
end

describe 'REQUEST_TO_RECEIVE_MESSAGE' do
let(:request_to_receive_message_callback) { spy('request_to_receive_message_callback') }

before do
adapter.on(WhatsAppAdapter::ThreeSixtyDialogInbound::REQUEST_TO_RECEIVE_MESSAGE) do |contributor, message|
request_to_receive_message_callback.call(contributor, message)
end
end

subject do
adapter.consume(organization, whats_app_message)
request_to_receive_message_callback
end

before do
create(:message, external_id: 'some_external_id')
whats_app_message[:messages].first[:context] = { id: 'some_external_id' }
end

describe 'with no WhatsApp template sent' do
it 'does not trigger the callback' do
expect(subject).not_to have_received(:call)
end
end

describe 'with a WhatsApp template sent' do
before { contributor.update!(whats_app_message_template_sent_at: 1.hour.ago) }

it 'triggered the callback' do
expect(subject).to have_received(:call)
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,7 @@
}
}
end
let(:latest_message) { contributor.received_messages.first.text }

before do
create(:message, :outbound, request: request, recipient: contributor)
end
let!(:latest_message) { create(:message, :outbound, request: request, recipient: contributor) }

context 'no message template sent' do
it 'creates a messsage' do
Expand All @@ -72,20 +68,44 @@

context 'responding to template' do
before { contributor.update(whats_app_message_template_sent_at: Time.current) }
let(:text) { latest_message }
let(:text) { latest_message.text }

context 'request to receive latest message' do
it 'enqueues a job to send the latest received message' do
expect do
subject.call
end.to have_enqueued_job(WhatsAppAdapter::ThreeSixtyDialogOutbound::Text).on_queue('default').with(text_payload)
end

it 'marks that contributor has responded to template message' do
expect { subject.call }.to change {
contributor.reload.whats_app_message_template_responded_at
}.from(nil).to(kind_of(ActiveSupport::TimeWithZone))
end

describe 'with no external id' do
it 'enqueues a job to send the latest received message' do
expect do
subject.call
end.to have_enqueued_job(WhatsAppAdapter::ThreeSixtyDialogOutbound::Text).on_queue('default').with(
text_payload.merge({ message_id: latest_message.id })
)
end
end

describe 'with an external id' do
let(:previous_message) do
create(:message, :outbound, request: request, recipient: contributor, created_at: 2.days.ago, external_id: 'some_external_id')
end
let(:text) { previous_message.text }

before do
previous_message
components[:messages].first[:context] = { id: 'some_external_id' }
end

it 'sends out the message with that external id' do
expect do
subject.call
end.to have_enqueued_job(WhatsAppAdapter::ThreeSixtyDialogOutbound::Text).on_queue('default').with(
text_payload.merge({ message_id: previous_message.id })
)
end
end
end
end

Expand Down

0 comments on commit b8baad3

Please sign in to comment.