From c2ac74faa3b80a9a899e176e14f1864c6332ea4c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 12 May 2023 12:23:42 +0200 Subject: [PATCH] add user settings for info notifications (#1926) # What this PR does ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --- CHANGELOG.md | 4 ++ .../migrations/0006_auto_20230512_0902.py | 34 +++++++++++++++ engine/apps/mobile_app/models.py | 13 ++++++ engine/apps/mobile_app/serializers.py | 4 ++ engine/apps/mobile_app/tasks.py | 43 ++++++++++++++++--- .../mobile_app/tests/test_user_settings.py | 8 ++++ 6 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 engine/apps/mobile_app/migrations/0006_auto_20230512_0902.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a01802a..d0771727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Add mobile settings for info notifications by @imtoori ([#1926](https://github.com/grafana/oncall/pull/1926)) + ### Fixed - Fix bug in the "You're Going Oncall" push notification copy by @joeyorlando ([#1922](https://github.com/grafana/oncall/pull/1922)) diff --git a/engine/apps/mobile_app/migrations/0006_auto_20230512_0902.py b/engine/apps/mobile_app/migrations/0006_auto_20230512_0902.py new file mode 100644 index 00000000..b7c90346 --- /dev/null +++ b/engine/apps/mobile_app/migrations/0006_auto_20230512_0902.py @@ -0,0 +1,34 @@ +# Generated by Django 3.2.19 on 2023-05-12 09:02 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mobile_app', '0005_mobileappusersettings_important_notification_volume_override'), + ] + + operations = [ + migrations.AddField( + model_name='mobileappusersettings', + name='info_notification_sound_name', + field=models.CharField(default='default_sound', max_length=100, null=True), + ), + migrations.AddField( + model_name='mobileappusersettings', + name='info_notification_volume', + field=models.FloatField(default=0.8, null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)]), + ), + migrations.AddField( + model_name='mobileappusersettings', + name='info_notification_volume_override', + field=models.BooleanField(default=False, null=True), + ), + migrations.AddField( + model_name='mobileappusersettings', + name='info_notification_volume_type', + field=models.CharField(choices=[('constant', 'Constant'), ('intensifying', 'Intensifying')], default='constant', max_length=50, null=True), + ), + ] diff --git a/engine/apps/mobile_app/models.py b/engine/apps/mobile_app/models.py index f257c2da..f812c332 100644 --- a/engine/apps/mobile_app/models.py +++ b/engine/apps/mobile_app/models.py @@ -109,10 +109,23 @@ class MobileAppUserSettings(models.Model): # if "override DND" setting is disabled in the app important_notification_override_dnd = models.BooleanField(default=True) + # Push notification settings for info notifications # this is used for non escalation related push notifications such as the # "You're going OnCall soon" push notification info_notifications_enabled = models.BooleanField(default=True) + info_notification_sound_name = models.CharField(max_length=100, default="default_sound", null=True) + info_notification_volume_type = models.CharField( + max_length=50, choices=VolumeType.choices, default=VolumeType.CONSTANT, null=True + ) + + # APNS only allows to specify volume for critical notifications, + # so "info_notification_volume" and "info_notification_volume_override" are only used on Android + info_notification_volume = models.FloatField( + validators=[validators.MinValueValidator(0.0), validators.MaxValueValidator(1.0)], default=0.8, null=True + ) + info_notification_volume_override = models.BooleanField(default=False, null=True) + # these choices + the below column are used to calculate when to send the "You're Going OnCall soon" # push notification # ONE_HOUR, TWELVE_HOURS, ONE_DAY, ONE_WEEK = range(4) diff --git a/engine/apps/mobile_app/serializers.py b/engine/apps/mobile_app/serializers.py index adce5774..d10dcff9 100644 --- a/engine/apps/mobile_app/serializers.py +++ b/engine/apps/mobile_app/serializers.py @@ -7,6 +7,10 @@ class MobileAppUserSettingsSerializer(serializers.ModelSerializer): class Meta: model = MobileAppUserSettings fields = ( + "info_notification_sound_name", + "info_notification_volume_type", + "info_notification_volume", + "info_notification_volume_override", "default_notification_sound_name", "default_notification_volume_type", "default_notification_volume", diff --git a/engine/apps/mobile_app/tasks.py b/engine/apps/mobile_app/tasks.py index 5f300db2..70058499 100644 --- a/engine/apps/mobile_app/tasks.py +++ b/engine/apps/mobile_app/tasks.py @@ -33,9 +33,10 @@ logger = get_task_logger(__name__) logger.setLevel(logging.DEBUG) -class MessageImportanceType(str, Enum): +class MessageType(str, Enum): NORMAL = "oncall.message" CRITICAL = "oncall.critical_message" + INFO = "oncall.info" class FCMMessageData(typing.TypedDict): @@ -99,11 +100,11 @@ def _send_push_notification( def _construct_fcm_message( + message_type: MessageType, device_to_notify: FCMDevice, thread_id: str, data: FCMMessageData, apns_payload: typing.Optional[APNSPayload] = None, - critical_message_type: bool = False, ) -> Message: apns_config_kwargs = {} @@ -116,7 +117,7 @@ def _construct_fcm_message( # from the docs.. # A dictionary of data fields (optional). All keys and values in the dictionary must be strings **data, - "type": MessageImportanceType.CRITICAL if critical_message_type else MessageImportanceType.NORMAL, + "type": message_type, "thread_id": thread_id, }, android=AndroidConfig( @@ -233,18 +234,48 @@ def _get_alert_group_escalation_fcm_message( ), ) - return _construct_fcm_message(device_to_notify, thread_id, fcm_message_data, apns_payload, critical) + message_type = MessageType.CRITICAL if critical else MessageType.NORMAL + + return _construct_fcm_message(message_type, device_to_notify, thread_id, fcm_message_data, apns_payload) def _get_youre_going_oncall_fcm_message( user: User, schedule: OnCallSchedule, device_to_notify: FCMDevice, seconds_until_going_oncall: int ) -> Message: thread_id = f"{schedule.public_primary_key}:{user.public_primary_key}:going-oncall" + + mobile_app_user_settings, _ = MobileAppUserSettings.objects.get_or_create(user=user) + + notification_title = ( + f"You are going on call in {humanize.naturaldelta(seconds_until_going_oncall)} for schedule {schedule.name}" + ) + data: FCMMessageData = { - "title": f"You are going on call in {humanize.naturaldelta(seconds_until_going_oncall)} for schedule {schedule.name}", + "title": notification_title, + "info_notification_sound_name": ( + mobile_app_user_settings.info_notification_sound_name + MobileAppUserSettings.ANDROID_SOUND_NAME_EXTENSION + ), + "info_notification_volume_type": mobile_app_user_settings.info_notification_volume_type, + "info_notification_volume": str(mobile_app_user_settings.info_notification_volume), + "info_notification_volume_override": json.dumps(mobile_app_user_settings.info_notification_volume_override), } - return _construct_fcm_message(device_to_notify, thread_id, data) + apns_payload = APNSPayload( + aps=Aps( + thread_id=thread_id, + alert=ApsAlert(title=notification_title), + sound=CriticalSound( + critical=False, + name=mobile_app_user_settings.info_notification_sound_name + + MobileAppUserSettings.IOS_SOUND_NAME_EXTENSION, + ), + custom_data={ + "interruption-level": "time-sensitive", + }, + ), + ) + + return _construct_fcm_message(MessageType.INFO, device_to_notify, thread_id, data, apns_payload) @shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=MAX_RETRIES) diff --git a/engine/apps/mobile_app/tests/test_user_settings.py b/engine/apps/mobile_app/tests/test_user_settings.py index a8eb5e29..46510928 100644 --- a/engine/apps/mobile_app/tests/test_user_settings.py +++ b/engine/apps/mobile_app/tests/test_user_settings.py @@ -20,6 +20,10 @@ def test_user_settings_get(make_organization_and_user_with_mobile_app_auth_token "default_notification_volume_type": "constant", "default_notification_volume": 0.8, "default_notification_volume_override": False, + "info_notification_sound_name": "default_sound", + "info_notification_volume_type": "constant", + "info_notification_volume": 0.8, + "info_notification_volume_override": False, "important_notification_sound_name": "default_sound_important", "important_notification_volume_type": "constant", "important_notification_volume": 0.8, @@ -52,6 +56,10 @@ def test_user_settings_put( "default_notification_volume_type": "intensifying", "default_notification_volume": 1, "default_notification_volume_override": True, + "info_notification_sound_name": "default_sound", + "info_notification_volume_type": "constant", + "info_notification_volume": 0.8, + "info_notification_volume_override": False, "important_notification_sound_name": "test_important", "important_notification_volume_type": "intensifying", "important_notification_volume": 1,