# 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.
90 lines
3.9 KiB
Python
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
|