# What this PR does Related to https://github.com/grafana/oncall/pull/5287 Few random "clean-ups", type improvements, etc. Additionally, fixes a change made in #5292; we should wait to read from `slack_message.channel.slack_id`, until we've performed the data-migration mentioned in that PR (in the mean-time we should continue to use `slack_message._channel_id`). ## 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.
289 lines
12 KiB
Python
289 lines
12 KiB
Python
import logging
|
|
import typing
|
|
|
|
from celery.utils.log import get_task_logger
|
|
from django.conf import settings
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
|
|
from apps.alerts.constants import ActionSource
|
|
from apps.alerts.representative import AlertGroupAbstractRepresentative
|
|
from apps.slack.scenarios.scenario_step import ScenarioStep
|
|
from common.custom_celery_tasks import shared_dedicated_queue_retry_task
|
|
|
|
if typing.TYPE_CHECKING:
|
|
from apps.alerts.models import AlertGroupLogRecord
|
|
|
|
logger = get_task_logger(__name__)
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
|
|
@shared_dedicated_queue_retry_task(
|
|
autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else None
|
|
)
|
|
def on_create_alert_slack_representative_async(alert_pk):
|
|
"""
|
|
It's asynced in order to prevent Slack downtime causing issues with SMS and other destinations.
|
|
"""
|
|
from apps.alerts.models import Alert
|
|
|
|
alert = (
|
|
Alert.objects.filter(pk=alert_pk)
|
|
.select_related(
|
|
"group",
|
|
"group__channel",
|
|
"group__channel__organization",
|
|
"group__channel__organization__slack_team_identity",
|
|
)
|
|
.get()
|
|
)
|
|
logger.debug(f"Start on_create_alert_slack_representative for alert {alert_pk} from alert_group {alert.group_id}")
|
|
|
|
organization = alert.group.channel.organization
|
|
if organization.slack_team_identity:
|
|
logger.debug(
|
|
f"Process on_create_alert_slack_representative for alert {alert_pk} from alert_group {alert.group_id}"
|
|
)
|
|
IncomingAlertStep = ScenarioStep.get_step("distribute_alerts", "IncomingAlertStep")
|
|
step = IncomingAlertStep(organization.slack_team_identity, organization)
|
|
step.process_signal(alert)
|
|
else:
|
|
logger.debug(
|
|
f"Drop on_create_alert_slack_representative for alert {alert_pk} from alert_group {alert.group_id}"
|
|
)
|
|
logger.debug(f"Finish on_create_alert_slack_representative for alert {alert_pk} from alert_group {alert.group_id}")
|
|
|
|
|
|
@shared_dedicated_queue_retry_task(
|
|
autoretry_for=(Exception,),
|
|
retry_backoff=True,
|
|
dont_autoretry_for=(ObjectDoesNotExist,),
|
|
max_retries=1 if settings.DEBUG else None,
|
|
)
|
|
def on_alert_group_action_triggered_async(log_record_id):
|
|
from apps.alerts.models import AlertGroupLogRecord
|
|
|
|
try:
|
|
log_record = AlertGroupLogRecord.objects.get(pk=log_record_id)
|
|
except AlertGroupLogRecord.DoesNotExist as e:
|
|
logger.warning(f"SLACK representative: log record {log_record_id} never created or has been deleted")
|
|
raise e
|
|
|
|
alert_group_id = log_record.alert_group_id
|
|
logger.debug(f"Start on_alert_group_action_triggered for alert_group {alert_group_id}, log record {log_record_id}")
|
|
instance = AlertGroupSlackRepresentative(log_record)
|
|
if instance.is_applicable():
|
|
logger.debug(f"SLACK representative is applicable for alert_group {alert_group_id}, log record {log_record_id}")
|
|
handler = instance.get_handler()
|
|
logger.debug(
|
|
f"Found handler {handler.__name__} in SLACK representative for alert_group {alert_group_id}, "
|
|
f"log record {log_record_id}"
|
|
)
|
|
handler()
|
|
logger.debug(
|
|
f"Finish handler {handler.__name__} in SLACK representative for alert_group {alert_group_id}, "
|
|
f"log record {log_record_id}"
|
|
)
|
|
else:
|
|
logger.debug(
|
|
f"SLACK representative is NOT applicable for alert_group {alert_group_id}, log record {log_record_id}"
|
|
)
|
|
logger.debug(f"Finish on_alert_group_action_triggered for alert_group {alert_group_id}, log record {log_record_id}")
|
|
|
|
|
|
class AlertGroupSlackRepresentative(AlertGroupAbstractRepresentative):
|
|
def __init__(self, log_record: "AlertGroupLogRecord"):
|
|
self.log_record = log_record
|
|
|
|
def is_applicable(self):
|
|
slack_message = self.log_record.alert_group.slack_message
|
|
slack_team_identity = self.log_record.alert_group.channel.organization.slack_team_identity
|
|
return (
|
|
slack_message is not None
|
|
and slack_team_identity is not None
|
|
and slack_message.slack_team_identity == slack_team_identity
|
|
)
|
|
|
|
@classmethod
|
|
def on_create_alert(cls, **kwargs):
|
|
from apps.alerts.models import Alert
|
|
|
|
alert = kwargs["alert"]
|
|
if isinstance(alert, Alert):
|
|
alert_id = alert.pk
|
|
else:
|
|
alert_id = alert
|
|
alert = Alert.objects.get(pk=alert_id)
|
|
|
|
logger.debug(
|
|
f"Received alert_create_signal in SLACK representative for alert {alert_id} "
|
|
f"from alert_group {alert.group_id}"
|
|
)
|
|
|
|
if alert.group.notify_in_slack_enabled is False:
|
|
logger.debug(
|
|
f"Skipping alert with id {alert_id} from alert_group {alert.group_id} since notify_in_slack is disabled"
|
|
)
|
|
return
|
|
on_create_alert_slack_representative_async.apply_async((alert_id,))
|
|
|
|
logger.debug(
|
|
f"Async process alert_create_signal in SLACK representative for alert {alert_id} "
|
|
f"from alert_group {alert.group_id}"
|
|
)
|
|
|
|
@classmethod
|
|
def on_alert_group_action_triggered(cls, **kwargs):
|
|
logger.debug("Received alert_group_action_triggered signal in SLACK representative")
|
|
log_record = cls.get_log_record_from_kwargs(**kwargs)
|
|
if not log_record:
|
|
return
|
|
force_sync = kwargs.get("force_sync", False)
|
|
|
|
if log_record.action_source == ActionSource.SLACK or force_sync:
|
|
logger.debug(f"SLACK on_alert_group_action_triggered: sync {log_record.id} {force_sync}")
|
|
on_alert_group_action_triggered_async(log_record.id)
|
|
else:
|
|
logger.debug(f"SLACK on_alert_group_action_triggered: async {log_record.id} {force_sync}")
|
|
on_alert_group_action_triggered_async.apply_async((log_record.id,))
|
|
|
|
@classmethod
|
|
def on_alert_group_update_resolution_note(cls, **kwargs):
|
|
alert_group = kwargs["alert_group"]
|
|
resolution_note = kwargs.get("resolution_note")
|
|
organization = alert_group.channel.organization
|
|
logger.debug(
|
|
f"Received alert_group_update_resolution_note signal in SLACK representative for alert_group {alert_group.pk}"
|
|
)
|
|
if alert_group.slack_message and organization.slack_team_identity:
|
|
UpdateResolutionNoteStep = ScenarioStep.get_step("resolution_note", "UpdateResolutionNoteStep")
|
|
step = UpdateResolutionNoteStep(organization.slack_team_identity, organization)
|
|
step.process_signal(alert_group, resolution_note)
|
|
|
|
@classmethod
|
|
def on_alert_group_post_acknowledge_reminder_message(cls, **kwargs):
|
|
log_record = cls.get_log_record_from_kwargs(**kwargs)
|
|
if not log_record:
|
|
return
|
|
slack_team_identity = log_record.alert_group.channel.organization.slack_team_identity
|
|
alert_group = log_record.alert_group
|
|
logger.debug(
|
|
f"Received post_ack_reminder_message_signal signal in SLACK representative for alert_group {alert_group.id}"
|
|
)
|
|
if not (log_record.alert_group.slack_message and slack_team_identity):
|
|
logger.debug(
|
|
f"SLACK representative is NOT applicable for alert_group {alert_group.id}, log record {log_record.id}"
|
|
)
|
|
return
|
|
|
|
AcknowledgeConfirmationStep = ScenarioStep.get_step("distribute_alerts", "AcknowledgeConfirmationStep")
|
|
step = AcknowledgeConfirmationStep(slack_team_identity)
|
|
step.process_signal(log_record)
|
|
|
|
def on_acknowledge(self):
|
|
AcknowledgeGroupStep = ScenarioStep.get_step("distribute_alerts", "AcknowledgeGroupStep")
|
|
step = AcknowledgeGroupStep(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def on_un_acknowledge(self):
|
|
UnAcknowledgeGroupStep = ScenarioStep.get_step("distribute_alerts", "UnAcknowledgeGroupStep")
|
|
step = UnAcknowledgeGroupStep(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def on_resolve(self):
|
|
ResolveGroupStep = ScenarioStep.get_step("distribute_alerts", "ResolveGroupStep")
|
|
step = ResolveGroupStep(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def on_un_resolve(self):
|
|
UnResolveGroupStep = ScenarioStep.get_step("distribute_alerts", "UnResolveGroupStep")
|
|
step = UnResolveGroupStep(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def on_attach(self):
|
|
AttachGroupStep = ScenarioStep.get_step("distribute_alerts", "AttachGroupStep")
|
|
step = AttachGroupStep(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def on_fail_attach(self):
|
|
AttachGroupStep = ScenarioStep.get_step("distribute_alerts", "AttachGroupStep")
|
|
step = AttachGroupStep(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def on_un_attach(self):
|
|
UnAttachGroupStep = ScenarioStep.get_step("distribute_alerts", "UnAttachGroupStep")
|
|
step = UnAttachGroupStep(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def on_silence(self):
|
|
SilenceGroupStep = ScenarioStep.get_step("distribute_alerts", "SilenceGroupStep")
|
|
step = SilenceGroupStep(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def on_un_silence(self):
|
|
UnSilenceGroupStep = ScenarioStep.get_step("distribute_alerts", "UnSilenceGroupStep")
|
|
step = UnSilenceGroupStep(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def on_invite(self):
|
|
InviteOtherPersonToIncident = ScenarioStep.get_step("distribute_alerts", "InviteOtherPersonToIncident")
|
|
step = InviteOtherPersonToIncident(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def on_re_invite(self):
|
|
self.on_invite()
|
|
|
|
def on_un_invite(self):
|
|
StopInvitationProcess = ScenarioStep.get_step("distribute_alerts", "StopInvitationProcess")
|
|
step = StopInvitationProcess(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def on_auto_un_acknowledge(self):
|
|
self.on_un_acknowledge()
|
|
|
|
def on_ack_reminder_triggered(self):
|
|
# deprecated, remove this handler after release
|
|
AcknowledgeConfirmationStep = ScenarioStep.get_step("distribute_alerts", "AcknowledgeConfirmationStep")
|
|
step = AcknowledgeConfirmationStep(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def on_wiped(self):
|
|
WipeGroupStep = ScenarioStep.get_step("distribute_alerts", "WipeGroupStep")
|
|
step = WipeGroupStep(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def on_deleted(self):
|
|
DeleteGroupStep = ScenarioStep.get_step("distribute_alerts", "DeleteGroupStep")
|
|
step = DeleteGroupStep(self.log_record.alert_group.channel.organization.slack_team_identity)
|
|
step.process_signal(self.log_record)
|
|
|
|
def get_handler(self):
|
|
handler_name = self.get_handler_name()
|
|
if hasattr(self, handler_name):
|
|
handler = getattr(self, handler_name)
|
|
else:
|
|
handler = self.on_handler_not_found
|
|
|
|
return handler
|
|
|
|
def get_handler_name(self):
|
|
return self.HANDLER_PREFIX + self.get_handlers_map()[self.log_record.type]
|
|
|
|
@classmethod
|
|
def on_handler_not_found(cls):
|
|
pass
|
|
|
|
@classmethod
|
|
def get_log_record_from_kwargs(cls, **kwargs) -> typing.Optional["AlertGroupLogRecord"]:
|
|
from apps.alerts.models import AlertGroupLogRecord
|
|
|
|
log_record = None
|
|
log_record_id = kwargs["log_record"]
|
|
|
|
if not isinstance(log_record_id, AlertGroupLogRecord):
|
|
try:
|
|
log_record = AlertGroupLogRecord.objects.get(pk=log_record_id)
|
|
except AlertGroupLogRecord.DoesNotExist:
|
|
logger.warning(f"log record {log_record_id} never created or has been deleted")
|
|
else:
|
|
log_record = log_record_id
|
|
return log_record
|