Skip to content

Commit

Permalink
fix: live call routing
Browse files Browse the repository at this point in the history
  • Loading branch information
armiiller authored Sep 22, 2022
1 parent e352ac1 commit ef0a3ea
Show file tree
Hide file tree
Showing 35 changed files with 205 additions and 57 deletions.
7 changes: 7 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ You can configure this gem with an initializer file:
PagerTree::Integrations.deferred_request_class = "DeferredReq"
PagerTree::Integrations.integration_parent_class = "Integration"
PagerTree::Integrations.outgoing_webhook_delivery_factory_class = "OutgoingWebhookDeliv"

PagerTree::Integrations::Engine.routes.default_url_options[:host] = "app.pagertree.com"
PagerTree::Integrations::Engine.routes.default_url_options[:protocol] = "https"
PagerTree::Integrations::Engine.routes.default_url_options[:port] = nil
...
```

## Copying assets
Make sure to copy the `public/**/*` folder to you `main_app/public/` folder

## Global Options
These options are for the core PagerTree integrations model

Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
pager_tree-integrations (1.1.1)
pager_tree-integrations (1.1.2)
rails (>= 7.0.1)

GEM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,20 @@ def music

@integration.adapter_source_log = @integration.logs.create!(level: :info, format: :json, message: params.to_unsafe_h) if @integration.try(:log_incoming_requests?)
@integration.adapter_controller = self
@integration.adapter_incoming_request_params = params
@integration.adapter_response_music
end

def dropped
set_integration

@integration.adapter_source_log = @integration.logs.create!(level: :info, format: :json, message: params.to_unsafe_h) if @integration.try(:log_incoming_requests?)
@integration.adapter_controller = self
@integration.adapter_alert = @integration.alerts.find_by(thirdparty_id: params[:CallSid])
@integration.adapter_incoming_request_params = params
@integration.adapter_response_dropped
end

def queue_status
::PagerTree::Integrations.deferred_request_class.constantize.perform_later_from_request!(request)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module PagerTree::Integrations
class LiveCallRouting::Twilio::V3Mailer < ::ApplicationMailer
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.live_call_routing.twilio.v3_mailer.call_recording.subject
#
def call_recording
@recording_url = params[:recording_url]
@alert = params[:alert]
@email = params[:email]
@from = params[:from]

mail(to: @email, subject: I18n.t("pager_tree.integrations.live_call_routing.twilio.v3_mailer.call_recording.subject", tiny_id: @alert.tiny_id, from: @from))
end
end
end
2 changes: 1 addition & 1 deletion app/models/pager_tree/integrations/channel/hangouts/v3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def adapter_process_outgoing
private

def _alert
@_alert ||= adapter_outgoing_event.item
@_alert ||= adapter_outgoing_event.alert
end

def _blocks
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def adapter_process_outgoing
private

def _alert
@_alert ||= adapter_outgoing_event.item
@_alert ||= adapter_outgoing_event.alert
end

def _blocks
Expand Down
2 changes: 1 addition & 1 deletion app/models/pager_tree/integrations/channel/slack/v3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def adapter_process_outgoing
private

def _alert
@_alert ||= adapter_outgoing_event.item
@_alert ||= adapter_outgoing_event.alert
end

def _blocks
Expand Down
4 changes: 4 additions & 0 deletions app/models/pager_tree/integrations/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ def adapter_process_create

def adapter_process_other
end

def adapter_will_route_alert?
false
end
# END basic incoming functions

# START basic outgoing functions
Expand Down
121 changes: 82 additions & 39 deletions app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,24 @@ class LiveCallRouting::Twilio::V3 < Integration
voice: "man"
}

TWILIO_LIVECALL_CONNECT_NOW = "https://app.pagertree.com/assets/sounds/you-are-now-being-connected.mp3"
TWILIO_LIVECALL_MUSIC = "http://com.twilio.sounds.music.s3.amazonaws.com/oldDog_-_endless_goodbye_%28instr.%29.mp3"
TWILIO_LIVECALL_PLEASE_WAIT = "https://app.pagertree.com/assets/sounds/please-wait.mp3"

def option_connect_now_media_url
option_connect_now_media&.url || TWILIO_LIVECALL_CONNECT_NOW
option_connect_now_media.present? ? option_connect_now_media.url : URI.join(Rails.application.routes.url_helpers.root_url, "audios/you-are-now-being-connected.mp3").to_s
end

def option_music_media_url
option_music_media&.url || TWILIO_LIVECALL_MUSIC
option_music_media.present? ? option_music_media.url : "http://com.twilio.sounds.music.s3.amazonaws.com/oldDog_-_endless_goodbye_%28instr.%29.mp3"
end

def option_please_wait_media_url
option_please_wait_media&.url || TWILIO_LIVECALL_PLEASE_WAIT
option_please_wait_media.present? ? option_please_wait_media.url : URI.join(Rails.application.routes.url_helpers.root_url, "audios/please-wait.mp3").to_s
end

def option_no_answer_media_url
option_no_answer_media.present? ? option_no_answer_media.url : URI.join(Rails.application.routes.url_helpers.root_url, "audios/no-answer.mp3").to_s
end

def option_no_answer_thank_you_media_url
option_no_answer_thank_you_media.present? ? option_no_answer_thank_you_media.url : URI.join(Rails.application.routes.url_helpers.root_url, "audios/thanks-for-message.mp3").to_s
end

def option_record_emails=(x)
Expand Down Expand Up @@ -90,10 +94,18 @@ def adapter_supports_outgoing?
true
end

def adapter_outgoing_interest?(event_name)
["alert_acknowledged", "alert_dropped"].include?(event_name) && adapter_alert.source_id == id
end

def adapter_incoming_can_defer?
false
end

def adapter_will_route_alert?
true
end

def adapter_action
:create
end
Expand Down Expand Up @@ -128,13 +140,19 @@ def adapter_response_incoming
end

if selected_team
adapter_alert.logs.create!(message: "Caller selected team '#{selected_team.name}'. Playing please wait media.")
_twiml.play(url: option_please_wait_media&.url || TWILIO_LIVECALL_PLEASE_WAIT)
adapter_alert.logs.create!(message: "Caller selected team '#{selected_team.name}'.") if _teams_size > 1 || option_force_input

adapter_alert.logs.create!(message: "Play please wait media to caller.")
_twiml.play(url: option_please_wait_media_url)
friendly_name = adapter_alert.id

# create the queue and save it off
queue = _client.queues.create(friendly_name: friendly_name)
adapter_alert.meta["live_call_queue_sid"] = queue.sid

adapter_alert.destination_teams = [selected_team]

# save the alert
adapter_alert.save!

_twiml.enqueue(
Expand All @@ -144,6 +162,11 @@ def adapter_response_incoming
wait_url: PagerTree::Integrations::Engine.routes.url_helpers.music_live_call_routing_twilio_v3_path(id, thirdparty_id: _thirdparty_id),
wait_url_method: "GET"
)
adapter_alert.logs.create!(message: "Enqueue caller in Twilio queue '#{friendly_name}'.")

# kick off the alert workflow
adapter_alert.route_later
adapter_alert.logs.create!(message: "Successfully enqueued alert team workflow.")
else
adapter_alert.meta["live_call_repeat_count"] ||= 0
adapter_alert.meta["live_call_repeat_count"] += 1
Expand All @@ -159,6 +182,7 @@ def adapter_response_incoming
end
else
adapter_alert.logs.create!(message: "Caller input bad input (too many times). Hangup.")
adapter_alert.resolve!(self)
_twiml.say(message: "Too much invalid input. Goodbye.", **SPEAK_OPTIONS)
_twiml.hangup
end
Expand Down Expand Up @@ -193,34 +217,30 @@ def adapter_response_music
adapter_controller&.render(xml: _twiml.to_xml)
end

def response_dropped
def adapter_response_dropped
recording_url = adapter_incoming_request_params.dig("RecordingUrl")

if recording_url
if option_no_answer_thank_you_media.present?
_twiml.play(url: option_no_answer_thank_you_media.url)
else
_twiml.say(message: "Thank you for your message. Goodbye.")
end
_twiml.play(url: option_no_answer_thank_you_media_url)
_twiml.hangup

adapter_alert.additional_data.push(AdditionalDatum.new(format: "link", label: "Voicemail", value: recording_url).to_h)
adapter_alert.save!

adapter_alert.logs.create!(message: "Caller left a <a href='#{recording_url}' target='_blank'>voicemail</a>.")

adapter_record_emails.each do |email|
TwilioLiveCallRouting::V3Mailer.with(email: email, alert: alert).call_recording.deliver_later
option_record_emails.each do |email|
LiveCallRouting::Twilio::V3Mailer.with(email: email, alert: adapter_alert, from: adapter_incoming_request_params.dig("From"), recording_url: recording_url).call_recording.deliver_later
end
elsif record
elsif option_record
_twiml.play(url: option_no_answer_media_url)
_twiml.record(max_length: 60)
else
_twiml.say(message: "No one is available to answer this call. Goodbye.")
_twiml.say(message: "No one is available to answer this call. Goodbye.", **SPEAK_OPTIONS)
_twiml.hangup
end

controller.render(xml: _twiml.to_xml)
adapter_controller.render(xml: _twiml.to_xml)
end

def adapter_process_queue_status_deferred
Expand All @@ -229,18 +249,20 @@ def adapter_process_queue_status_deferred

if queue_result == "hangup"
self.adapter_alert = alerts.find_by(thirdparty_id: _thirdparty_id)
adapter_alert.logs.create!(message: "Caller hungup while waiting in queue.")
adapter_alert.resolve!(self)
queue_destroy
end

adapter_source_log&.save!
end

def perform_outgoing(**params)
event = params[:event]
if event == "alert.acknowledged"
on_acknowledge
elsif event == "alert.dropped"
on_drop
def adapter_process_outgoing
event = adapter_outgoing_event.event_name.to_s
if event == "alert_acknowledged"
_on_acknowledge
elsif event == "alert_dropped"
_on_drop
end
end

Expand Down Expand Up @@ -269,7 +291,7 @@ def _client
end

def _call
@_call ||= _client.calls(call_sid).fetch
@_call ||= _client.calls(adapter_alert.thirdparty_id).fetch
end

def _twiml
Expand Down Expand Up @@ -299,23 +321,44 @@ def selected_team
nil
end

def on_acknowledge
def _on_acknowledge
# log that we are going to transfer
adapter_alert.logs.create!(message: "Attempting to transfer the call...")
adapter_alert.logs.create!(message: "The alert was acknowledged. Attempting to transfer the call...")

# try to transfer the caller
number = "+19402733696"
_twiml.play(url: option_connect_now_media_url)
_twiml.pause(length: 1)
_twiml.dial(number: number, caller_id: _call.to, answer_on_bridge: true)
_call.update(twiml: _twiml.to_xml)

# log if we successfully transfered or failed
adapter_alert.logs.create!(message: "Tranferring the call succeeded.")
account_user = adapter_outgoing_event.account_user
number = account_user.user.phone&.phone

adapter_alert.logs.create!(message: "Attempting to transfer the call to #{account_user.user.name} at #{number}...")

if number.present?
_twiml.play(url: option_connect_now_media_url)
_twiml.pause(length: 1)
_twiml.dial(number: number, caller_id: _call.to, answer_on_bridge: true)
_call.update(twiml: _twiml.to_xml)
# log if we successfully transfered or failed
adapter_alert.logs.create!(message: "Tranferring the call succeeded.")
else
_twiml.say(message: "Someone has acknowledged this call, but they do not have a phone number on file. Goodbye.")
_twiml.hangup
_call.update(twiml: _twiml.to_xml)
adapter_alert.logs.create!(message: "Tranferring the call failed. #{account_user.user.name} has no phone number on file.")
end
rescue ::Twilio::REST::RestError => e
# 21220 - Unable to update record. Call is not in-progress. Cannot redirect.
if e.code != 21220
adapter_alert.logs.create!(message: "Tranferring the call failed. #{e.message}")
end
end

def on_drop
_call.update(url: PagerTree::Integrations::Engine.routes.url_helpers.dropped_twilio_live_call_routing_v3_url(id, thirdparty_id: thirdparty_id))
def _on_drop
# log that we are going to transer
adapter_alert.logs.create!(message: "The alert was dropped. Attempting to transfer the call...")
_call.update(url: PagerTree::Integrations::Engine.routes.url_helpers.dropped_live_call_routing_twilio_v3_url(id, thirdparty_id: adapter_alert.thirdparty_id))
music_live_call_routing_twilio_v3_path
adapter_alert.logs.create!(message: "Tranferring the call succeeded.")
rescue ::Twilio::REST::RestError => e
adapter_alert.logs.create!(message: "Tranferring the call failed. #{e.message}")
end

def queue_destroy
Expand Down
9 changes: 9 additions & 0 deletions app/models/pager_tree/integrations/outgoing_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ class OutgoingEvent
attr_accessor :item
attr_accessor :changes
attr_accessor :outgoing_rules_data
attr_accessor :alert
attr_accessor :handoff
attr_accessor :team
attr_accessor :account_user

define_model_callbacks :initialize

Expand All @@ -22,6 +26,11 @@ def initialize(params = {})
self.item ||= nil
self.changes ||= nil
self.outgoing_rules_data ||= {}

self.alert ||= nil
self.handoff ||= nil
self.team ||= nil
self.account_user ||= nil
end
end
end
4 changes: 2 additions & 2 deletions app/models/pager_tree/integrations/outgoing_webhook/v3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def adapter_process_outgoing
if self.option_template.present?
begin
body = JSON.parse(handlebars(self.option_template, {
alert: adapter_outgoing_event.item.try(:v3_format) || adapter_outgoing_event.item,
alert: adapter_outgoing_event.alert.try(:v3_format) || adapter_outgoing_event.alert,
event: {
type: event_type
}
Expand All @@ -84,7 +84,7 @@ def adapter_process_outgoing
end

body ||= {
data: adapter_outgoing_event.item.try(:v3_format) || adapter_outgoing_event.item,
data: adapter_outgoing_event.alert.try(:v3_format) || adapter_outgoing_event.alert,
type: event_type
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/* Email styles need to be inline */
</style>
</head>

<body>
<%= yield %>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= yield %>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= t(".body", tiny_id: @alert.tiny_id, from: @from) %>: <a href="<%=@recording_url%>"><%= @recording_url %></a>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= t(".body", tiny_id: @alert.tiny_id, from: @from) %>: <%= @recording_url %>
Loading

0 comments on commit ef0a3ea

Please sign in to comment.