# 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.
87 lines
3.6 KiB
Python
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)
|