oncall-engine/engine/apps/alerts/models/invitation.py
Joey Orlando deb6a45588
chore: convert two slack channel ID char fields to foreign keys (#5224)
# What this PR does

Similar to https://github.com/grafana/oncall/pull/5199

Converts follow char fields to primary key relationships on
`SlackChannel` table:
- `ResolutionNoteSlackMessage.channel_id` ->
`ResolutionNoteSlackMessage.slack_channel`
- `ChannelFilter.slack_channel_id` -> `ChannelFilter.slack_channel`

## 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-11-04 13:34:06 -05:00

133 lines
4.5 KiB
Python

import datetime
import logging
import typing
from functools import partial
from django.db import models, transaction
from apps.alerts import tasks
if typing.TYPE_CHECKING:
from apps.alerts.models import AlertGroup
from apps.user_management.models import User
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
class Invitation(models.Model):
"""
It's an invitation of a user to join working on Alert Group
"""
alert_group: "AlertGroup"
author: typing.Optional["User"]
invitee: typing.Optional["User"]
ATTEMPTS_LIMIT = 10
time_deltas_by_attempts = [
datetime.timedelta(minutes=6),
datetime.timedelta(minutes=16),
datetime.timedelta(minutes=31),
datetime.timedelta(hours=1, minutes=1),
datetime.timedelta(hours=3, minutes=1),
]
author = models.ForeignKey(
"user_management.User",
null=True,
on_delete=models.SET_NULL,
related_name="author_of_invitations",
)
invitee = models.ForeignKey(
"user_management.User",
null=True,
on_delete=models.SET_NULL,
related_name="invitee_in_invitations",
)
created_at = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
alert_group = models.ForeignKey("alerts.AlertGroup", on_delete=models.CASCADE, related_name="invitations")
attempt = models.IntegerField(default=0)
@property
def attempts_left(self) -> int:
return Invitation.ATTEMPTS_LIMIT - self.attempt
@staticmethod
def get_delay_by_attempt(attempt: int) -> datetime.timedelta:
countdown = Invitation.time_deltas_by_attempts[-1]
if attempt < len(Invitation.time_deltas_by_attempts):
countdown = Invitation.time_deltas_by_attempts[attempt]
return countdown
@staticmethod
def invite_user(invitee_user: "User", alert_group: "AlertGroup", user: "User") -> None:
from apps.alerts.models import AlertGroupLogRecord
# RFCT - why atomic? without select for update?
with transaction.atomic():
try:
invitation = Invitation.objects.get(
invitee=invitee_user,
alert_group=alert_group,
is_active=True,
)
invitation.is_active = False
invitation.save(update_fields=["is_active"])
log_record = AlertGroupLogRecord(
type=AlertGroupLogRecord.TYPE_RE_INVITE, author=user, alert_group=alert_group
)
except Invitation.DoesNotExist:
log_record = AlertGroupLogRecord(
type=AlertGroupLogRecord.TYPE_INVITE,
author=user,
alert_group=alert_group,
)
invitation = Invitation(
invitee=invitee_user,
alert_group=alert_group,
is_active=True,
author=user,
)
invitation.save()
log_record.invitation = invitation
log_record.save()
logger.debug(
f"call send_alert_group_signal for alert_group {alert_group.pk}, "
f"log record {log_record.pk} with type '{log_record.get_type_display()}'"
)
transaction.on_commit(partial(tasks.send_alert_group_signal.delay, log_record.pk))
transaction.on_commit(partial(tasks.invite_user_to_join_incident.delay, invitation.pk))
@staticmethod
def stop_invitation(invitation_pk: int, user: "User") -> None:
from apps.alerts.models import AlertGroupLogRecord
with transaction.atomic():
try:
invitation = Invitation.objects.filter(pk=invitation_pk).select_for_update()[0]
except IndexError:
return f"stop_invitation: Invitation with pk {invitation_pk} doesn't exist"
invitation.is_active = False
invitation.save(update_fields=["is_active"])
log_record = AlertGroupLogRecord(
type=AlertGroupLogRecord.TYPE_STOP_INVITATION,
author=user,
alert_group=invitation.alert_group,
invitation=invitation,
)
log_record.save()
logger.debug(
f"call send_alert_group_signal for alert_group {invitation.alert_group.pk}, "
f"log record {log_record.pk} with type '{log_record.get_type_display()}'"
)
transaction.on_commit(partial(tasks.send_alert_group_signal.delay, log_record.pk))