-
Notifications
You must be signed in to change notification settings - Fork 291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Phone provider refactoring #1713
Conversation
Remove a.py
|
||
def _slack_format_for_phone_call(self, data): | ||
sf = self.slack_formatter | ||
sf.user_mention_format = "{}" | ||
sf.channel_mention_format = "#{}" | ||
sf.hyperlink_mention_format = "{title}" | ||
return sf.format(data) | ||
|
||
def _escape(self, data): | ||
return escape_for_twilio_phone_call(data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that expected that we don't escape < > &
anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this escape was Twilio Related, so it's moved to TwilioPhoneProvider https://github.com/grafana/oncall/pull/1713/files#diff-764f1941b04b97b1517990791c24bc6446e623f0326b95c44060dd4ec42afd75R26
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good
|
||
|
||
# Duplicate to avoid circular import | ||
class TwilioCallStatuses: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be removed after migration, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'm keeping it to be able to receive Gather and call statuses(digits from phone call) for calls which were made shortly before migration.
engine/apps/twilioapp/gather.py
Outdated
|
||
|
||
def get_gather_url(): | ||
return create_engine_url(reverse("twilioapp:gather"), "https://pretty-mosh-97.loca.lt") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hardcoded url
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
database_operations=database_operations, | ||
state_operations=state_operations | ||
) | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using https://docs.djangoproject.com/en/3.2/ref/models/options/#db-table instead of renaming tables to avoid data migrations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great refactoring, looks like it will make adding new providers so much easier 👍
) | ||
|
||
|
||
class OnCallPhoneCall(models.Model): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: maybe remove "OnCall" from the class name so it's just PhoneCall
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried just PhoneCall name, but it doesn't works
For example we have this method in ProveProvider interface. If I rename OnCallPhoneCall to PhoneCall, then I should rename oncall_phone_call parameter name to phone_call which makes no sense in function singnature. On the other side I don't like PhoneCall model name with oncall_phone_call parameter, since it is confusing.
def make_notification_call(self, number, message, oncall_phone_call):
....
Same issue we have with TwilioPhoneCall model. Is has field oncall_phone_call. Renaming it to just phone call leads to lines like twilio_phone_call.phone_call
.
I'm not 100% happy with OnCallPhoneCall name, but having just PhoneCall introduces more confusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vadimkerr what do you think about PhoneCallRecord instead of OnCallPhoneCall. Also it sounds well: twilio_phone_call.phone_call_record ( at least better that twilio_phone_call.oncall_phone_call)
) | ||
|
||
|
||
class OnCallSMS(models.Model): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Maybe just SMS
?
engine/settings/base.py
Outdated
# Used in get_phone_provider function to dynamically load current provider. | ||
PHONE_PROVIDERS = { | ||
"twilio": "apps.twilioapp.phone_provider.TwilioPhoneProvider", | ||
"simple": "apps.phone_notifications.simple_phone_provider.SimplePhoneProvider", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be removed so it doesn't show up as an available option for PHONE_PROVIDER
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this still be useful?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it is useful only for slack now. We process events triggered from slack synchronously, instead of running celery tasks. It is needed to make re-rendering of slack message faster, when button is pressed. We don't use this system for other chatops integrations, but ideally we should. Just keeping it consistent, waiting when we will refactor messaging backends.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is great! LGTM.
few minor questions/comments:
- do we need the new
get_verification_call
endpoint? - I see you have "refactor tests" listed as a todo, +1 on this
|
||
|
||
def get_gather_message(): | ||
return "Press 1 to acknowledge, 2 to resolve, 3 to silence to 30 minutes" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit move "1", "2", "3" into variables and reference those in the 3 places in this file. ex:
ACK_DIGIT = "1"
RESOLVE_DIGIT = "2"
SILENCE_DIGIT = "3"
def get_gather_message():
return f"Press {ACK_DIGIT} to acknowledge, {RESOLVE_DIGIT} to resolve, {SILENCE_DIGIT} to silence to 30 minutes"
same thing for the process_digit
and process_gather_data
functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉🎉🎉 this is really clean, nicely done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume the majority of this logic was just relocated from Twilio specific code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep
|
||
|
||
def get_call_status_callback_url(): | ||
return create_engine_url(reverse("twilioapp:call_status_events"), "https://pretty-mosh-97.loca.lt") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hardcoded URL
|
||
|
||
def get_sms_status_callback_url(): | ||
return create_engine_url(reverse("twilioapp:sms_status_events"), "https://pretty-mosh-97.loca.lt") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hardcoded URL
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this file be deleted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I'm still using it as a reference while working on PR, will delete it before merge, thanks!
@action( | ||
detail=True, | ||
methods=["get"], | ||
throttle_classes=[GetPhoneVerificationCodeThrottlerPerUser, GetPhoneVerificationCodeThrottlerPerOrg], | ||
) | ||
def get_verification_call(self, request, pk): | ||
logger.info("get_verification_code_via_call: validating reCAPTCHA code") | ||
valid = check_recaptcha_internal_api(request, "mobile_verification_code") | ||
if not valid: | ||
logger.warning(f"get_verification_code_via_call: invalid reCAPTCHA validation") | ||
return Response("failed reCAPTCHA check", status=status.HTTP_400_BAD_REQUEST) | ||
logger.info('get_verification_code_via_call: pass reCAPTCHA validation"') | ||
|
||
user = self.get_object() | ||
phone_backend = PhoneBackend() | ||
try: | ||
phone_backend.make_verification_call(user) | ||
except NumberAlreadyVerified: | ||
return Response("Phone number already verified", status=status.HTTP_400_BAD_REQUEST) | ||
except FailedToStartVerification: | ||
return Response("Something went wrong while calling", status=status.HTTP_500_INTERNAL_SERVER_ERROR) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new endpoint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, it is introduced keeping in mind that some providers can't sent sms, like asterisk. Twilio is also capable of verifying via call, but I didn't supported it to not overcomplicate refactoring.
# Conflicts: # engine/apps/api/views/user.py
Many countries are introducing different requirements for SMS senders to register and/or use alpha numeric ids, short codes or regional numbers or face being blocked. The changes in this PR will give us more flexibility by allowing us to map to different resources in Twilio based on the phone number we are trying to reach. For this first implementation the selection is made based on country code of the recipient. Verification and phone calls were given the same treatment although the immediate need is for SMS. Senders with no country code set can be used as catch-all defaults. This also falls back to the configured live settings/environment variables if not configured. Possible future additions: - Move through list of trying multiple senders before failing notification - Easily expanded to allow per-organization or per-user resources to let users and tenants configure their own Twilio - Add UI + replace live settings so users can configure their own settings - More selection criteria if needed TODO: - [x] Add+Fix Tests - [x] Verify changes are compatible with #1713
# What this PR does This PR moves phone notification logic into separate object PhoneBackend and introduces PhoneProvider interface to hide actual implementation of external phone services provider. It should allow add new phone providers just by implementing one class (See SimplePhoneProvider for example). # Why [Asterisk PR](#1282) showed that our phone notification system is not flexible. However this is one of the most frequent community questions - how to add "X" phone provider. Also, this refactoring move us one step closer to unifying all notification backends, since with PhoneBackend all phone notification logic is collected in one place and independent from concrete realisation. # Highligts 1. PhoneBackend object - contains all phone notifications business logic. 2. PhoneProvider - interface to external phone services provider. 3. TwilioPhoneProvider and SimplePhoneProvider - two examples of PhoneProvider implementation. 4. PhoneCallRecord and SMSRecord models. I introduced these models to keep phone notification limits logic decoupled from external providers. Existing TwilioPhoneCall and TwilioSMS objects will be migrated to the new table to not to reset limits counter. To be able to receive status callbacks and gather from Twilio TwilioPhoneCall and TwilioSMS still exists, but they are linked to PhoneCallRecord and SMSRecord via fk, to not to leat twilio logic into core code. --------- Co-authored-by: Yulia Shanyrova <yulia.shanyrova@grafana.com>
What this PR does
This PR moves phone notification logic into separate object PhoneBackend and introduces PhoneProvider interface to hide actual implementation of external phone services provider. It should allow add new phone providers just by implementing one class (See SimplePhoneProvider for example).
Why
Asterisk PR showed that our phone notification system is not flexible. However this is one of the most frequent community questions - how to add "X" phone provider.
Also, this refactoring move us one step closer to unifying all notification backends, since with PhoneBackend all phone notification logic is collected in one place and independent from concrete realisation.
Highligts