oncall-engine/engine/apps/slack/scenarios/notification_delivery.py
Joey Orlando a29e35c25a
refactor SlackMessage.channel_id (CHAR field) to SlackMessage.channel (foreign key relationship) (#5292)
# What this PR does

Related to https://github.com/grafana/oncall-private/issues/2947

**NOTE**

This PR introduces steps 1 and 2 of the 3 part migration proposed
[here](https://raintank-corp.slack.com/archives/C06K1MQ07GS/p1732555465144099).
Step 3, swapping reads to be from the new-column and dropping
dual-writes, will be done in a future PR/release.

---

I’m tackling this work now because _ultimately_ I want to move
`AlertReceiveChannel.rate_limited_in_slack_at` to
`SlackChannel.rate_limited_at` , but first I sorta need to refactor
`SlackMessage.channel_id` from a `CHAR` field to a foreign key
relationship (because in the spots where we touch Slack rate limiting,
like
[here](https://github.com/grafana/oncall/blob/dev/engine/apps/slack/alert_group_slack_service.py#L42-L50)
for example, we only have `slack_message.channel_id`, which means I need
to do extra queries to fetch the appropriate `SlackChannel` to then be
able to get/set `SlackChannel.rate_limited_at`

Other minor stuffs:
- it also prepares us to drop `SlackMessage._slack_team_identity`. We
already have a `@property` of `SlackMessage.slack_team_identity` (which
[previously had some hacky
logic](https://github.com/grafana/oncall/blob/dev/engine/apps/slack/models/slack_message.py#L74-L84)).
I've refactored `SlackMessage.slack_team_identity` to simply point to
`self.organization.slack_team_identity` + updated our code to _stop_
setting `SlackMessage._slack_team_identity` (will drop this column in
future release)

## 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-26 11:03:38 +00:00

90 lines
3.9 KiB
Python

import typing
from apps.slack.errors import (
SlackAPIChannelArchivedError,
SlackAPIChannelNotFoundError,
SlackAPIInvalidAuthError,
SlackAPITokenError,
)
from apps.slack.scenarios import scenario_step
if typing.TYPE_CHECKING:
from apps.alerts.models import AlertGroupLogRecord
class NotificationDeliveryStep(scenario_step.ScenarioStep):
def process_signal(self, log_record: "AlertGroupLogRecord") -> None:
from apps.base.models import UserNotificationPolicy, UserNotificationPolicyLogRecord
user = log_record.author
alert_group = log_record.alert_group
slack_channel_id = alert_group.slack_message.channel.slack_id
user_verbal_with_mention = user.get_username_with_slack_verbal(mention=True)
# move message generation to UserNotificationPolicyLogRecord
if log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED:
if log_record.notification_error_code in UserNotificationPolicyLogRecord.ERRORS_TO_SEND_IN_SLACK_CHANNEL:
if (
log_record.notification_error_code
== UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_SMS_LIMIT_EXCEEDED
):
self._post_message_to_channel(
f"Attempt to send an SMS to {user_verbal_with_mention} has been failed due to a plan limit",
slack_channel_id,
)
elif (
log_record.notification_error_code
== UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_PHONE_CALLS_LIMIT_EXCEEDED
):
self._post_message_to_channel(
f"Attempt to call to {user_verbal_with_mention} has been failed due to a plan limit",
slack_channel_id,
)
elif (
log_record.notification_error_code
== UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_MAIL_LIMIT_EXCEEDED
):
self._post_message_to_channel(
f"Failed to send email to {user_verbal_with_mention}. Exceeded limit for mails",
slack_channel_id,
)
elif (
log_record.notification_error_code
== UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_PHONE_NUMBER_IS_NOT_VERIFIED
):
if log_record.notification_channel == UserNotificationPolicy.NotificationChannel.SMS:
self._post_message_to_channel(
f"Failed to send an SMS to {user_verbal_with_mention}. Phone number is not verified",
slack_channel_id,
)
elif log_record.notification_channel == UserNotificationPolicy.NotificationChannel.PHONE_CALL:
self._post_message_to_channel(
f"Failed to call to {user_verbal_with_mention}. Phone number is not verified",
slack_channel_id,
)
def _post_message_to_channel(self, text: str, channel_id: str) -> None:
try:
self._slack_client.chat_postMessage(
channel=channel_id,
text=text,
blocks=[
{
"type": "section",
"block_id": "alert",
"text": {
"type": "mrkdwn",
"text": text,
},
},
],
unfurl_links=True,
)
except (
SlackAPITokenError,
SlackAPIChannelNotFoundError,
SlackAPIChannelArchivedError,
SlackAPIInvalidAuthError,
):
pass