diff --git a/engine/apps/twilioapp/gather.py b/engine/apps/twilioapp/gather.py
index 8b4f3036b0..8afe4ec65e 100644
--- a/engine/apps/twilioapp/gather.py
+++ b/engine/apps/twilioapp/gather.py
@@ -24,16 +24,22 @@ def process_gather_data(call_sid: str, digit: str) -> VoiceResponse:
response = VoiceResponse()
+ success_messages = {
+ "1": "Acknowledged",
+ "2": "Resolved",
+ "3": "Silenced",
+ }
if digit in ["1", "2", "3"]:
# Success case
- response.say(f"You have pressed digit {digit}")
+ msg = success_messages.get(digit, f"You have pressed digit {digit}")
+ response.say(msg)
process_digit(call_sid, digit)
else:
# Error wrong digit pressing
gather = Gather(action=get_gather_url(), method="POST", num_digits=1)
response.say("Wrong digit")
- gather.say(get_gather_message())
+ gather.say(get_alert_group_gather_instructions())
response.append(gather)
@@ -85,5 +91,5 @@ def get_gather_url():
return create_engine_url(reverse("twilioapp:gather"))
-def get_gather_message():
- return "Press 1 to acknowledge, 2 to resolve, 3 to silence to 30 minutes"
+def get_alert_group_gather_instructions():
+ return "Press 1 to acknowledge, 2 to resolve, 3 to silence for 30 minutes"
diff --git a/engine/apps/twilioapp/phone_provider.py b/engine/apps/twilioapp/phone_provider.py
index 988746f1c8..89811bad3e 100644
--- a/engine/apps/twilioapp/phone_provider.py
+++ b/engine/apps/twilioapp/phone_provider.py
@@ -6,6 +6,7 @@
from phonenumbers import COUNTRY_CODE_TO_REGION_CODE
from twilio.base.exceptions import TwilioRestException
from twilio.rest import Client
+from twilio.twiml.voice_response import Gather, Say, VoiceResponse
from apps.base.models import LiveSetting
from apps.base.utils import live_settings
@@ -16,7 +17,7 @@
FailedToStartVerification,
)
from apps.phone_notifications.phone_provider import PhoneProvider, ProviderFlags
-from apps.twilioapp.gather import get_gather_message, get_gather_url
+from apps.twilioapp.gather import get_alert_group_gather_instructions, get_gather_url
from apps.twilioapp.models import (
TwilioCallStatuses,
TwilioPhoneCall,
@@ -34,13 +35,13 @@ class TwilioPhoneProvider(PhoneProvider):
def make_notification_call(self, number: str, message: str) -> TwilioPhoneCall | None:
message = self._escape_call_message(message)
- twiml_query = self._message_to_twiml(message, with_gather=True)
+ twiml = self._message_to_twiml_gather(message)
response = None
try_without_callback = False
try:
- response = self._call_create(twiml_query, number, with_callback=True)
+ response = self._call_create(twiml, number, with_callback=True)
except TwilioRestException as e:
# If status callback is not valid and not accessible from public url then trying to send message without it
# https://www.twilio.com/docs/api/errors/21609
@@ -53,7 +54,7 @@ def make_notification_call(self, number: str, message: str) -> TwilioPhoneCall |
if try_without_callback:
try:
- response = self._call_create(twiml_query, number, with_callback=False)
+ response = self._call_create(twiml, number, with_callback=False)
except TwilioRestException as e:
logger.error(f"TwilioPhoneProvider.make_notification_call: failed {e}")
raise FailedToMakeCall(graceful_msg=self._get_graceful_msg(e, number))
@@ -146,9 +147,9 @@ def _get_graceful_msg(self, e, number):
return None
def make_call(self, number: str, message: str):
- twiml_query = self._message_to_twiml(message, with_gather=False)
+ twiml = self._message_to_twiml_say(message)
try:
- self._call_create(twiml_query, number, with_callback=False)
+ self._call_create(twiml, number, with_callback=False)
except TwilioRestException as e:
logger.error(f"TwilioPhoneProvider.make_call: failed {e}")
raise FailedToMakeCall(graceful_msg=self._get_graceful_msg(e, number))
@@ -160,18 +161,25 @@ def send_sms(self, number: str, message: str):
logger.error(f"TwilioPhoneProvider.send_sms: failed {e}")
raise FailedToSendSMS(graceful_msg=self._get_graceful_msg(e, number))
- def _message_to_twiml(self, message: str, with_gather=False):
- q = f"{message}"
- if with_gather:
- gather_subquery = f'{get_gather_message()}'
- q = f"{message}{gather_subquery}"
- return urllib.parse.quote(
- q,
- safe="",
- )
-
- def _call_create(self, twiml_query: str, to: str, with_callback: bool):
+ def _message_to_twiml_say(self, message: str) -> VoiceResponse:
+ response = VoiceResponse()
+ say = Say(message)
+ response.append(say)
+ return response
+
+ def _message_to_twiml_gather(self, message: str) -> VoiceResponse:
+ response = VoiceResponse()
+ gather = Gather(action=get_gather_url(), method="POST", num_digits=1)
+ gather.say(message)
+ gather.pause(length=1)
+ gather.say(get_alert_group_gather_instructions())
+ response.append(gather)
+ return response
+
+ def _call_create(self, twiml: VoiceResponse, to: str, with_callback: bool):
client, from_ = self._phone_sender(to)
+ # encode twiml VoiceResponse to use in url
+ twiml_query = urllib.parse.quote(str(twiml), safe="")
url = "http://twimlets.com/echo?Twiml=" + twiml_query
if with_callback:
status_callback = get_call_status_callback_url()
diff --git a/engine/apps/twilioapp/tests/test_phone_calls.py b/engine/apps/twilioapp/tests/test_phone_calls.py
index c66977e1f0..5a8d196631 100644
--- a/engine/apps/twilioapp/tests/test_phone_calls.py
+++ b/engine/apps/twilioapp/tests/test_phone_calls.py
@@ -138,7 +138,7 @@ def test_acknowledge_by_phone(mock_has_permission, mock_get_gather_url, make_twi
content = response.content.decode("utf-8")
assert response.status_code == 200
- assert "You have pressed digit 1" in content
+ assert "Acknowledged" in content
alert_group.refresh_from_db()
assert alert_group.acknowledged is True
@@ -173,7 +173,7 @@ def test_resolve_by_phone(mock_has_permission, mock_get_gather_url, make_twilio_
content = BeautifulSoup(content, features="xml").findAll(string=True)
assert response.status_code == 200
- assert "You have pressed digit 2" in content
+ assert "Resolved" in content
alert_group.refresh_from_db()
assert alert_group.resolved is True
@@ -207,7 +207,7 @@ def test_silence_by_phone(mock_has_permission, mock_get_gather_url, make_twilio_
content = response.content.decode("utf-8")
assert response.status_code == 200
- assert "You have pressed digit 3" in content
+ assert "Silenced" in content
alert_group.refresh_from_db()
assert alert_group.silenced_until is not None
diff --git a/engine/apps/twilioapp/tests/test_twilio_provider.py b/engine/apps/twilioapp/tests/test_twilio_provider.py
index 1d7384b635..5054c37559 100644
--- a/engine/apps/twilioapp/tests/test_twilio_provider.py
+++ b/engine/apps/twilioapp/tests/test_twilio_provider.py
@@ -19,13 +19,15 @@ class MockTwilioMessageInstance:
@pytest.mark.django_db
@mock.patch("apps.twilioapp.phone_provider.TwilioPhoneProvider._call_create", return_value=MockTwilioCallInstance())
-@mock.patch("apps.twilioapp.phone_provider.TwilioPhoneProvider._message_to_twiml", return_value="mocked_twiml")
+@mock.patch(
+ "apps.twilioapp.phone_provider.TwilioPhoneProvider._message_to_twiml_gather", return_value="twiml_gather_response"
+)
def test_make_notification_call(mock_twiml, mock_call_create):
number = "+1234567890"
message = "Hello"
provider = TwilioPhoneProvider()
provider_call = provider.make_notification_call(number, message)
- mock_call_create.assert_called_once_with("mocked_twiml", number, with_callback=True)
+ mock_call_create.assert_called_once_with("twiml_gather_response", number, with_callback=True)
assert provider_call is not None
assert provider_call.sid == MockTwilioCallInstance.sid
assert provider_call.id is None # test that provider_call is returned by notification call and NOT saved
@@ -33,14 +35,16 @@ def test_make_notification_call(mock_twiml, mock_call_create):
@pytest.mark.django_db
@mock.patch("apps.twilioapp.phone_provider.TwilioPhoneProvider._call_create", return_value=MockTwilioCallInstance())
-@mock.patch("apps.twilioapp.phone_provider.TwilioPhoneProvider._message_to_twiml", return_value="mocked_twiml")
+@mock.patch(
+ "apps.twilioapp.phone_provider.TwilioPhoneProvider._message_to_twiml_say", return_value="twiml_say_response"
+)
def test_make_call(mock_twiml, mock_call_create):
number = "+1234567890"
message = "Hello"
provider = TwilioPhoneProvider()
provider_call = provider.make_call(number, message)
assert provider_call is None # test that provider_call is not returned from make_call
- mock_call_create.assert_called_once_with("mocked_twiml", number, with_callback=False)
+ mock_call_create.assert_called_once_with("twiml_say_response", number, with_callback=False)
class MockTwilioSMSInstance: