oncall-engine/engine/apps/alerts/models/resolution_note.py
Joey Orlando 1bd30b3cf8
chore: remove deprecated AlertGroupPostMortem model + recently refactored/deprecated slack channel related columns (#5240)
# What this PR does

- `AlertGroupPostMortem` has no references in the codebase.. I stumbled
across it while working on https://github.com/grafana/oncall/pull/5224
and decided to just remove it
- Removing old Slack channel related `VARCHAR` columns; these were
refactored to foreign key references to `slack_slackchannel` table in
following PRs:
  - https://github.com/grafana/oncall/pull/5224
  - https://github.com/grafana/oncall/pull/5199
  - https://github.com/grafana/oncall/pull/5191 

## 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-19 19:23:48 +00:00

207 lines
6.8 KiB
Python

import typing
import humanize
from django.conf import settings
from django.core.validators import MinLengthValidator
from django.db import models
from django.utils import timezone
from rest_framework.fields import DateTimeField
from apps.slack.slack_formatter import SlackFormatter
from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length
from common.utils import clean_markup
if typing.TYPE_CHECKING:
from apps.alerts.models import AlertGroup
from apps.slack.models import SlackChannel
from apps.user_management.models import User
def generate_public_primary_key_for_resolution_note():
prefix = "M"
new_public_primary_key = generate_public_primary_key(prefix)
failure_counter = 0
while ResolutionNote.objects.filter(public_primary_key=new_public_primary_key).exists():
new_public_primary_key = increase_public_primary_key_length(
failure_counter=failure_counter, prefix=prefix, model_name="ResolutionNote"
)
failure_counter += 1
return new_public_primary_key
class ResolutionNoteSlackMessageQueryset(models.QuerySet):
def delete(self):
resolution_note = self.get_resolution_note()
if resolution_note:
resolution_note.delete()
super().delete()
class ResolutionNoteSlackMessage(models.Model):
alert_group: "AlertGroup"
resolution_note: typing.Optional["ResolutionNote"]
slack_channel: typing.Optional["SlackChannel"]
alert_group = models.ForeignKey(
"alerts.AlertGroup",
on_delete=models.CASCADE,
related_name="resolution_note_slack_messages",
)
user = models.ForeignKey(
"user_management.User",
null=True,
on_delete=models.SET_NULL,
related_name="authored_resolution_note_slack_messages",
)
added_by_user = models.ForeignKey(
"user_management.User",
null=True,
on_delete=models.SET_NULL,
related_name="added_resolution_note_slack_messages",
)
text = models.TextField(max_length=3000, default=None, null=True)
slack_channel = models.ForeignKey(
"slack.SlackChannel",
null=True,
default=None,
on_delete=models.SET_NULL,
related_name="+",
)
ts = models.CharField(max_length=100, null=True, default=None)
thread_ts = models.CharField(max_length=100, null=True, default=None)
permalink = models.CharField(max_length=250, null=True, default=None)
added_to_resolution_note = models.BooleanField(default=False)
posted_by_bot = models.BooleanField(default=False)
class Meta:
unique_together = ("thread_ts", "ts")
indexes = [
models.Index(fields=["ts", "thread_ts", "alert_group_id"]),
models.Index(fields=["ts", "thread_ts", "slack_channel_id"]),
]
@property
def slack_channel_slack_id(self) -> typing.Optional[str]:
return self.slack_channel.slack_id if self.slack_channel else None
def get_resolution_note(self) -> typing.Optional["ResolutionNote"]:
try:
return self.resolution_note
except ResolutionNoteSlackMessage.resolution_note.RelatedObjectDoesNotExist:
return None
def delete(self, *args, **kwargs) -> typing.Tuple[int, typing.Dict[str, int]]:
resolution_note = self.get_resolution_note()
if resolution_note:
resolution_note.delete()
return super().delete(*args, **kwargs)
class ResolutionNoteQueryset(models.QuerySet):
def delete(self):
self.update(deleted_at=timezone.now())
def hard_delete(self):
super().delete()
def filter(self, *args, **kwargs):
return super().filter(*args, **kwargs, deleted_at__isnull=True)
class ResolutionNote(models.Model):
alert_group: "AlertGroup"
author: typing.Optional["User"]
resolution_note_slack_message: typing.Optional[ResolutionNoteSlackMessage]
objects = ResolutionNoteQueryset.as_manager()
objects_with_deleted = models.Manager()
class Source(models.IntegerChoices):
SLACK = 0, "Slack"
WEB = 1, "Web"
MOBILE_APP = 2, "Mobile App"
public_primary_key = models.CharField(
max_length=20,
validators=[MinLengthValidator(settings.PUBLIC_PRIMARY_KEY_MIN_LENGTH + 1)],
unique=True,
default=generate_public_primary_key_for_resolution_note,
)
alert_group = models.ForeignKey(
"alerts.AlertGroup",
on_delete=models.CASCADE,
related_name="resolution_notes",
)
source = models.IntegerField(choices=Source.choices, default=None, null=True)
author = models.ForeignKey(
"user_management.User",
on_delete=models.SET_NULL,
null=True,
default=None,
related_name="authored_resolution_notes",
)
message_text = models.TextField(max_length=3000, default=None, null=True)
created_at = models.DateTimeField(auto_now_add=True)
resolution_note_slack_message = models.OneToOneField(
"alerts.ResolutionNoteSlackMessage",
on_delete=models.SET_NULL,
null=True,
default=None,
related_name="resolution_note",
)
deleted_at = models.DateTimeField(default=None, null=True)
def delete(self):
ResolutionNote.objects.filter(pk=self.pk).delete()
def hard_delete(self):
super().delete()
@property
def text(self):
if self.source == ResolutionNote.Source.SLACK:
return self.resolution_note_slack_message.text
return self.message_text
def recreate(self):
"""
Recreates soft-deleted resolution note.
E.g. resolution note can be removed and then added again in slack.
"""
self.deleted_at = None
self.save(update_fields=["deleted_at"])
def render_log_line_json(self):
time = humanize.naturaldelta(self.alert_group.started_at - self.created_at)
created_at = DateTimeField().to_representation(self.created_at)
organization = self.alert_group.channel.organization
author = self.author.short(organization) if self.author is not None else None
sf = SlackFormatter(organization)
action = sf.format(self.text)
action = clean_markup(action)
result = {
"time": time,
"action": action,
"realm": "resolution_note",
"type": self.source,
"created_at": created_at,
"author": author,
}
return result
def author_verbal(self, mention: bool) -> str:
"""
Postmortems to resolution notes included migrating `AlertGroupPostmortem` to `ResolutionNote`s.
But `AlertGroupPostmortem` has no author field. So this method was introduced as a workaround.
(see git history for more details on what `AlertGroupPostmortem` was)
"""
return "" if self.author is None else self.author.get_username_with_slack_verbal(mention)