Skip to content
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

Add mobile app push notifications for shift swap requests #2717

Merged
merged 24 commits into from
Aug 2, 2023

Conversation

vstpme
Copy link
Member

@vstpme vstpme commented Aug 1, 2023

What this PR does

Adds mobile app push notifications for shift swap requests.

Which issue(s) this PR fixes

#2630

Checklist

  • Unit, integration, and e2e (if applicable) tests updated
  • Documentation added (or pr:no public docs PR label added if not required)
  • CHANGELOG.md updated (or pr:no changelog PR label added if not required)

@vstpme vstpme added the pr:no public docs Added to a PR that does not require public documentation updates label Aug 1, 2023
@vstpme vstpme changed the title Shift swap request push notifications Add mobile app push notifications for shift swap requests Aug 1, 2023
@@ -230,13 +231,12 @@ def _get_youre_going_oncall_notification_title(seconds_until_going_oncall: int)
return f"Your on-call shift starts in {humanize.naturaldelta(seconds_until_going_oncall)}"


def _get_youre_going_oncall_notification_subtitle(
def _get_shift_subtitle(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reusing this method to generate subtitles both for on-call shifts and SSRs

@@ -499,3 +499,183 @@ def conditionally_send_going_oncall_push_notifications_for_schedule(schedule_pk)
def conditionally_send_going_oncall_push_notifications_for_all_schedules() -> None:
for schedule in OnCallSchedule.objects.all():
conditionally_send_going_oncall_push_notifications_for_schedule.apply_async((schedule.pk,))


# TODO: break down tasks.py into multiple files
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this TODO out of the scope of this PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, good idea. Created #2722 to track this.

@@ -57,7 +57,7 @@


RE_ICAL_SEARCH_USERNAME = r"SUMMARY:(\[L[0-9]+\] )?{}"
RE_ICAL_FETCH_USERNAME = re.compile(r"SUMMARY:(?:\[L[0-9]+\] )?(\w+)")
RE_ICAL_FETCH_USERNAME = re.compile(r"SUMMARY:(?:\[L[0-9]+\] )?([^\s]+)")
Copy link
Member Author

@vstpme vstpme Aug 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This didn't match usernames containing dots (e.g. vadim.stepanov). Added a test for this case.

@@ -60,7 +60,7 @@ class ShiftSwapRequest(models.Model):
default=generate_public_primary_key_for_shift_swap_request,
)

created_at = models.DateTimeField(auto_now_add=True)
created_at = models.DateTimeField(default=timezone.now)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is required to override created_at in tests: FactoryBoy/factory_boy#102 (comment)

from apps.mobile_app.models import MobileAppUserSettings

thread_id = f"{shift_swap_request.public_primary_key}:{user.public_primary_key}:ssr"
notification_title = "You have a new shift swap request" # TODO: decide on the exact wording
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me know if you have any ideas on notification title

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about "Your teammate has opened a shift swap request"? wdyt?

Comment on lines 656 to 657
# TODO: check with the mobile team
resource_url = reverse("api-internal:shift_swap-detail", kwargs={"pk": shift_swap_request.public_primary_key})
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to check this with @grafana/grafana-oncall-mobile, see here for the actual URL format

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vstpme vstpme marked this pull request as ready for review August 1, 2023 23:08
@vstpme vstpme requested a review from a team August 1, 2023 23:08
Copy link
Contributor

@joeyorlando joeyorlando left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great work, looks awesome!

@@ -480,6 +480,10 @@ class BrokerTypes:
"task": "apps.mobile_app.tasks.conditionally_send_going_oncall_push_notifications_for_all_schedules",
"schedule": 10 * 60,
},
"notify_shift_swap_requests": {
"task": "apps.mobile_app.tasks.notify_shift_swap_requests",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this also needs to be added to CELERY_TASK_ROUTES (and also in oncall-private), right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added in 747f3d7

@@ -283,6 +285,30 @@ def timezone(self) -> typing.Optional[str]:
def timezone(self, value):
self._timezone = value

def is_in_working_hours(self, dt: datetime.datetime, tz: typing.Optional[str] = None) -> bool:
assert dt.tzinfo == pytz.utc # only pass in UTC
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -499,3 +499,183 @@ def conditionally_send_going_oncall_push_notifications_for_schedule(schedule_pk)
def conditionally_send_going_oncall_push_notifications_for_all_schedules() -> None:
for schedule in OnCallSchedule.objects.all():
conditionally_send_going_oncall_push_notifications_for_schedule.apply_async((schedule.pk,))


# TODO: break down tasks.py into multiple files
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, good idea. Created #2722 to track this.

for user in shift_swap_request.possible_benefactors:
if _should_notify_user_about_shift_swap_request(shift_swap_request, user, now):
notify_user_about_shift_swap_request.delay(shift_swap_request.pk, user.pk)
_mark_shift_swap_request_notified_for_user(shift_swap_request, user)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this happen at the very end of notify_user_about_shift_swap_request? In case something inside there fails we probably don't want to mark the notification as sent. wdyt?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't want to do that to make sure the mobile app doesn't spam people. I'd rather not receive any SSR notifications at all than receive more than one (potentially once every 15 minutes if something goes wrong between sending the push notification and marking the notification sent). The notify_user_about_shift_swap_request task has a retry policy, so if something goes wrong while sending the push notification (e.g. network issue), the task will probably succeed after a retry. Lmk if this makes sense

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

task has a retry policy

ahh yes, forgot about this. Makes sense 👍

from apps.mobile_app.models import MobileAppUserSettings

thread_id = f"{shift_swap_request.public_primary_key}:{user.public_primary_key}:ssr"
notification_title = "You have a new shift swap request" # TODO: decide on the exact wording
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about "Your teammate has opened a shift swap request"? wdyt?

logger.info(f"Info notifications are not enabled for user {user_pk}")
return

if shift_swap_request.status != ShiftSwapRequest.Statuses.OPEN:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we could create a @property on ShiftSwapRequest as such:

@property
def is_open(self) -> bool:
  return not any([self.is_deleted, self.is_taken, self.is_past_due])

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, added in f747ec1

Comment on lines +623 to +627
return (
mobile_app_user_settings.info_notifications_enabled # info notifications must be enabled
and user.is_in_working_hours(now, mobile_app_user_settings.time_zone) # user must be in working hours
and not _has_user_been_notified_for_shift_swap_request(shift_swap_request, user) # don't notify twice
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr:no public docs Added to a PR that does not require public documentation updates
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants