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)
This commit is contained in:
Salvatore Giordano 2023-05-12 12:23:42 +02:00 committed by GitHub
parent ae5c4d368f
commit c2ac74faa3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 100 additions and 6 deletions

View file

@ -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))

View file

@ -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),
),
]

View file

@ -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)

View file

@ -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",

View file

@ -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)

View file

@ -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,