oncall-engine/engine/apps/alerts/models/user_notification_bundle.py
Yulya Artyukhina 1a6d77888e
Fix notification plan builder (#4726)
# What this PR does
Fixes building notification plan if one or more notifications were
bundled

## 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] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.
2024-07-24 15:49:03 +00:00

87 lines
3.6 KiB
Python

import datetime
import typing
from django.db import models
from django.utils import timezone
from apps.alerts.constants import BUNDLED_NOTIFICATION_DELAY_SECONDS
from apps.base.models import UserNotificationPolicy
from apps.base.models.user_notification_policy import validate_channel_choice
if typing.TYPE_CHECKING:
from django.db.models.manager import RelatedManager
from apps.alerts.models import AlertGroup, AlertReceiveChannel
from apps.user_management.models import User
class UserNotificationBundle(models.Model):
user: "User"
notifications: "RelatedManager['BundledNotification']"
NOTIFICATION_CHANNELS_TO_BUNDLE = [
UserNotificationPolicy.NotificationChannel.SMS,
]
user = models.ForeignKey("user_management.User", on_delete=models.CASCADE, related_name="notification_bundles")
important = models.BooleanField()
notification_channel = models.PositiveSmallIntegerField(
validators=[validate_channel_choice], null=True, default=None
)
last_notified_at = models.DateTimeField(default=None, null=True)
notification_task_id = models.CharField(max_length=100, null=True, default=None)
# estimated time of arrival for scheduled send_bundled_notification task
eta = models.DateTimeField(default=None, null=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["user", "important", "notification_channel"], name="unique_user_notification_bundle"
)
]
def notified_recently(self) -> bool:
return (
timezone.now() - self.last_notified_at < timezone.timedelta(seconds=BUNDLED_NOTIFICATION_DELAY_SECONDS)
if self.last_notified_at
else False
)
def eta_is_valid(self) -> bool:
"""
`eta` shows eta of scheduled send_bundled_notification task and should never be less than the current time
(with a 1 minute buffer provided).
`eta` is None means that there is no scheduled task.
"""
if not self.eta or self.eta + timezone.timedelta(minutes=1) >= timezone.now():
return True
return False
def get_notification_eta(self) -> datetime.datetime:
last_notified = self.last_notified_at if self.last_notified_at else timezone.now()
return last_notified + timezone.timedelta(seconds=BUNDLED_NOTIFICATION_DELAY_SECONDS)
def append_notification(self, alert_group: "AlertGroup", notification_policy: "UserNotificationPolicy"):
self.notifications.create(
alert_group=alert_group, notification_policy=notification_policy, alert_receive_channel=alert_group.channel
)
@classmethod
def notification_is_bundleable(cls, notification_channel):
return notification_channel in cls.NOTIFICATION_CHANNELS_TO_BUNDLE
class BundledNotification(models.Model):
alert_group: "AlertGroup"
alert_receive_channel: "AlertReceiveChannel"
notification_policy: typing.Optional["UserNotificationPolicy"]
notification_bundle: "UserNotificationBundle"
alert_group = models.ForeignKey("alerts.AlertGroup", on_delete=models.CASCADE, related_name="bundled_notifications")
alert_receive_channel = models.ForeignKey("alerts.AlertReceiveChannel", on_delete=models.CASCADE)
notification_policy = models.ForeignKey("base.UserNotificationPolicy", on_delete=models.SET_NULL, null=True)
notification_bundle = models.ForeignKey(
UserNotificationBundle, on_delete=models.CASCADE, related_name="notifications"
)
created_at = models.DateTimeField(auto_now_add=True)
bundle_uuid = models.CharField(max_length=100, null=True, default=None, db_index=True)