2022-06-03 08:09:47 -06:00
|
|
|
import logging
|
|
|
|
|
import time
|
2023-07-28 17:11:38 +02:00
|
|
|
import typing
|
2022-06-03 08:09:47 -06:00
|
|
|
import uuid
|
|
|
|
|
|
2024-12-02 14:40:30 -05:00
|
|
|
from celery import uuid as celery_uuid
|
|
|
|
|
from django.core.cache import cache
|
2022-06-03 08:09:47 -06:00
|
|
|
from django.db import models
|
2024-12-02 14:40:30 -05:00
|
|
|
from django.utils import timezone
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2023-09-12 10:49:16 +01:00
|
|
|
from apps.slack.client import SlackClient
|
Truncate slack block text so it is not rejected by slack API (#5121)
# What this PR does
Truncates text for slack message to avoid this error:
```
File "/usr/local/lib/python3.12/site-packages/slack_sdk/web/slack_response.py", line 199, in validate
raise e.SlackApiError(message=msg, response=self)
slack_sdk.errors.SlackApiError: The request to the Slack API failed. (url: https://www.slack.com/api/chat.postMessage)
The server responded with: {'ok': False, 'error': 'invalid_blocks', 'errors': ['failed to match all allowed schemas [json-pointer:/blocks/0/text]', 'must be less than 3001 characters [json-pointer:/blocks/0/text/text]'], 'response_metadata': {'messages': ['[ERROR] failed to match all allowed schemas [json-pointer:/blocks/0/text]', '[ERROR] must be less than 3001 characters [json-pointer:/blocks/0/text/text]']}}
```
## Which issue(s) this PR closes
Related to [issue link here]
<!--
*Note*: If you want the issue to be auto-closed once the PR is merged,
change "Related to" to "Closes" in the line above.
If you have more than one GitHub issue that this PR closes, be sure to
preface
each issue link with a [closing
keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue).
This ensures that the issue(s) are auto-closed once the PR has been
merged.
-->
## 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-10-03 12:45:23 -06:00
|
|
|
from apps.slack.constants import BLOCK_SECTION_TEXT_MAX_SIZE
|
2023-09-12 10:49:16 +01:00
|
|
|
from apps.slack.errors import (
|
|
|
|
|
SlackAPIChannelArchivedError,
|
|
|
|
|
SlackAPIError,
|
|
|
|
|
SlackAPIFetchMembersFailedError,
|
|
|
|
|
SlackAPIMethodNotSupportedForChannelTypeError,
|
2024-06-07 21:54:45 +08:00
|
|
|
SlackAPIRatelimitError,
|
2023-09-12 10:49:16 +01:00
|
|
|
SlackAPITokenError,
|
2022-06-03 08:09:47 -06:00
|
|
|
)
|
2024-12-02 14:40:30 -05:00
|
|
|
from apps.slack.tasks import update_alert_group_slack_message
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2023-07-28 17:11:38 +02:00
|
|
|
if typing.TYPE_CHECKING:
|
|
|
|
|
from apps.alerts.models import AlertGroup
|
2024-11-26 06:03:38 -05:00
|
|
|
from apps.base.models import UserNotificationPolicy
|
2024-12-06 11:43:40 -05:00
|
|
|
from apps.slack.models import SlackChannel, SlackTeamIdentity
|
2024-11-26 06:03:38 -05:00
|
|
|
from apps.user_management.models import User
|
2023-07-28 17:11:38 +02:00
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SlackMessage(models.Model):
|
2023-07-28 17:11:38 +02:00
|
|
|
alert_group: typing.Optional["AlertGroup"]
|
2024-11-26 06:03:38 -05:00
|
|
|
channel: "SlackChannel"
|
2023-07-28 17:11:38 +02:00
|
|
|
|
2024-12-02 14:40:30 -05:00
|
|
|
ALERT_GROUP_UPDATE_DEBOUNCE_INTERVAL_SECONDS = 45
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
id = models.CharField(primary_key=True, default=uuid.uuid4, editable=False, max_length=36)
|
|
|
|
|
slack_id = models.CharField(max_length=100)
|
2024-11-04 13:34:06 -05:00
|
|
|
|
2024-11-26 06:03:38 -05:00
|
|
|
channel = models.ForeignKey(
|
|
|
|
|
"slack.SlackChannel", on_delete=models.CASCADE, null=True, default=None, related_name="slack_messages"
|
|
|
|
|
)
|
|
|
|
|
"""
|
2024-12-06 11:43:40 -05:00
|
|
|
TODO: set null=False + remove default=None in a subsequent PR/release.
|
|
|
|
|
(a slack message always needs to have a slack channel associated with it)
|
2024-11-26 06:03:38 -05:00
|
|
|
"""
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
_slack_team_identity = models.ForeignKey(
|
|
|
|
|
"slack.SlackTeamIdentity",
|
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
|
null=True,
|
|
|
|
|
default=None,
|
|
|
|
|
related_name="slack_message",
|
|
|
|
|
db_column="slack_team_identity",
|
|
|
|
|
)
|
2024-11-26 06:03:38 -05:00
|
|
|
"""
|
2024-12-06 11:43:40 -05:00
|
|
|
TODO: rename this from _slack_team_identity to slack_team_identity in a subsequent PR/release
|
|
|
|
|
|
|
|
|
|
This involves also updating the Meta.constraints to use the new field name, this may involve
|
|
|
|
|
migrations.RemoveConstraint and migrations.AddConstraint operations, which we need to investigate further...
|
2024-11-26 06:03:38 -05:00
|
|
|
|
2024-12-06 11:43:40 -05:00
|
|
|
Also, set null=False + remove default in a subsequent PR/release (a slack message always needs to have
|
|
|
|
|
a slack team identity associated with it)
|
2024-11-26 06:03:38 -05:00
|
|
|
"""
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
ack_reminder_message_ts = models.CharField(max_length=100, null=True, default=None)
|
|
|
|
|
|
|
|
|
|
cached_permalink = models.URLField(max_length=250, null=True, default=None)
|
|
|
|
|
|
2024-11-29 08:21:29 -05:00
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
2022-06-03 08:09:47 -06:00
|
|
|
last_updated = models.DateTimeField(null=True, default=None)
|
|
|
|
|
|
|
|
|
|
alert_group = models.ForeignKey(
|
|
|
|
|
"alerts.AlertGroup",
|
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
|
null=True,
|
|
|
|
|
default=None,
|
|
|
|
|
related_name="slack_messages",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
# slack_id is unique within the context of a channel or conversation
|
|
|
|
|
constraints = [
|
|
|
|
|
models.UniqueConstraint(fields=["slack_id", "channel_id", "_slack_team_identity"], name="unique slack_id")
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
@property
|
2024-12-06 11:43:40 -05:00
|
|
|
def slack_team_identity(self) -> "SlackTeamIdentity":
|
|
|
|
|
"""
|
|
|
|
|
See TODO note under _slack_team_identity field
|
|
|
|
|
"""
|
|
|
|
|
return self._slack_team_identity
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
@property
|
2023-09-12 10:49:16 +01:00
|
|
|
def permalink(self) -> typing.Optional[str]:
|
2024-11-26 06:03:38 -05:00
|
|
|
# Don't send request for permalink if slack token has been revoked
|
|
|
|
|
if self.cached_permalink or self.slack_team_identity.detected_token_revoked:
|
2022-06-03 08:09:47 -06:00
|
|
|
return self.cached_permalink
|
|
|
|
|
|
2023-09-12 10:49:16 +01:00
|
|
|
try:
|
|
|
|
|
result = SlackClient(self.slack_team_identity).chat_getPermalink(
|
2024-12-06 09:28:26 -05:00
|
|
|
channel=self.channel.slack_id,
|
2024-11-29 08:21:29 -05:00
|
|
|
message_ts=self.slack_id,
|
2023-09-12 10:49:16 +01:00
|
|
|
)
|
|
|
|
|
except SlackAPIError:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
self.cached_permalink = result["permalink"]
|
|
|
|
|
self.save(update_fields=["cached_permalink"])
|
|
|
|
|
|
|
|
|
|
return self.cached_permalink
|
|
|
|
|
|
2024-04-12 10:25:48 -03:00
|
|
|
@property
|
|
|
|
|
def deep_link(self) -> str:
|
2024-12-06 09:28:26 -05:00
|
|
|
return f"https://slack.com/app_redirect?channel={self.channel.slack_id}&team={self.slack_team_identity.slack_id}&message={self.slack_id}"
|
2024-11-26 06:03:38 -05:00
|
|
|
|
|
|
|
|
def send_slack_notification(
|
2024-12-06 11:43:40 -05:00
|
|
|
self, user: "User", alert_group: "AlertGroup", notification_policy: "UserNotificationPolicy"
|
2024-11-26 06:03:38 -05:00
|
|
|
) -> None:
|
|
|
|
|
"""
|
|
|
|
|
NOTE: the reason why we pass in `alert_group` as an argument here, as opposed to just doing
|
|
|
|
|
`self.alert_group`, is that it "looks like" we may have a race condition occuring between two celery tasks:
|
|
|
|
|
- one which sends out the initial slack message
|
|
|
|
|
- one which notifies the user (this method) inside of the above slack message's thread
|
|
|
|
|
|
|
|
|
|
Still some more investigation needed to confirm this, but for now, we'll pass in the `alert_group` as an argument
|
|
|
|
|
"""
|
2023-07-25 10:43:23 +01:00
|
|
|
from apps.base.models import UserNotificationPolicyLogRecord
|
|
|
|
|
|
2023-09-04 19:03:18 +01:00
|
|
|
slack_message = alert_group.slack_message
|
2024-11-26 06:03:38 -05:00
|
|
|
slack_channel = slack_message.channel
|
2024-12-06 11:43:40 -05:00
|
|
|
slack_team_identity = self.slack_team_identity
|
2024-11-26 06:03:38 -05:00
|
|
|
channel_id = slack_channel.slack_id
|
|
|
|
|
|
2023-03-07 18:09:37 +08:00
|
|
|
user_verbal = user.get_username_with_slack_verbal(mention=True)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
slack_user_identity = user.slack_user_identity
|
|
|
|
|
if slack_user_identity is None:
|
2023-03-16 17:43:49 +01:00
|
|
|
text = "{}\nTried to invite {} to look at the alert group. Unfortunately {} is not in slack.".format(
|
2022-06-03 08:09:47 -06:00
|
|
|
alert_group.long_verbose_name, user_verbal, user_verbal
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
UserNotificationPolicyLogRecord(
|
|
|
|
|
author=user,
|
|
|
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
|
|
|
|
|
notification_policy=notification_policy,
|
|
|
|
|
alert_group=alert_group,
|
|
|
|
|
reason="User is not in Slack",
|
|
|
|
|
notification_step=notification_policy.step,
|
|
|
|
|
notification_channel=notification_policy.notify_by,
|
|
|
|
|
notification_error_code=UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_IN_SLACK_USER_NOT_IN_SLACK,
|
|
|
|
|
).save()
|
|
|
|
|
else:
|
2023-03-16 17:43:49 +01:00
|
|
|
text = "{}\nInviting {} to look at the alert group.".format(alert_group.long_verbose_name, user_verbal)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
Truncate slack block text so it is not rejected by slack API (#5121)
# What this PR does
Truncates text for slack message to avoid this error:
```
File "/usr/local/lib/python3.12/site-packages/slack_sdk/web/slack_response.py", line 199, in validate
raise e.SlackApiError(message=msg, response=self)
slack_sdk.errors.SlackApiError: The request to the Slack API failed. (url: https://www.slack.com/api/chat.postMessage)
The server responded with: {'ok': False, 'error': 'invalid_blocks', 'errors': ['failed to match all allowed schemas [json-pointer:/blocks/0/text]', 'must be less than 3001 characters [json-pointer:/blocks/0/text/text]'], 'response_metadata': {'messages': ['[ERROR] failed to match all allowed schemas [json-pointer:/blocks/0/text]', '[ERROR] must be less than 3001 characters [json-pointer:/blocks/0/text/text]']}}
```
## Which issue(s) this PR closes
Related to [issue link here]
<!--
*Note*: If you want the issue to be auto-closed once the PR is merged,
change "Related to" to "Closes" in the line above.
If you have more than one GitHub issue that this PR closes, be sure to
preface
each issue link with a [closing
keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue).
This ensures that the issue(s) are auto-closed once the PR has been
merged.
-->
## 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-10-03 12:45:23 -06:00
|
|
|
text = text[:BLOCK_SECTION_TEXT_MAX_SIZE]
|
|
|
|
|
|
2022-08-02 15:24:13 -06:00
|
|
|
blocks = [
|
|
|
|
|
{
|
|
|
|
|
"type": "section",
|
|
|
|
|
"block_id": "alert",
|
|
|
|
|
"text": {
|
|
|
|
|
"type": "mrkdwn",
|
|
|
|
|
"text": text,
|
|
|
|
|
},
|
|
|
|
|
}
|
2022-06-03 08:09:47 -06:00
|
|
|
]
|
2024-11-26 06:03:38 -05:00
|
|
|
|
2024-12-06 11:43:40 -05:00
|
|
|
sc = SlackClient(slack_team_identity, enable_ratelimit_retry=True)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
try:
|
2023-09-05 11:31:59 +02:00
|
|
|
result = sc.chat_postMessage(
|
2022-06-03 08:09:47 -06:00
|
|
|
channel=channel_id,
|
2022-08-02 11:50:38 -06:00
|
|
|
text=text,
|
2022-08-02 15:24:13 -06:00
|
|
|
blocks=blocks,
|
2022-06-03 08:09:47 -06:00
|
|
|
thread_ts=slack_message.slack_id,
|
|
|
|
|
unfurl_links=True,
|
|
|
|
|
)
|
2024-06-07 21:54:45 +08:00
|
|
|
except SlackAPIRatelimitError:
|
|
|
|
|
UserNotificationPolicyLogRecord(
|
|
|
|
|
author=user,
|
|
|
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
|
|
|
|
|
notification_policy=notification_policy,
|
|
|
|
|
alert_group=alert_group,
|
|
|
|
|
reason="Slack API rate limit error",
|
|
|
|
|
notification_step=notification_policy.step,
|
|
|
|
|
notification_channel=notification_policy.notify_by,
|
|
|
|
|
notification_error_code=UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_IN_SLACK_RATELIMIT,
|
|
|
|
|
).save()
|
|
|
|
|
return
|
2023-09-12 10:49:16 +01:00
|
|
|
except SlackAPITokenError:
|
2022-06-03 08:09:47 -06:00
|
|
|
UserNotificationPolicyLogRecord(
|
|
|
|
|
author=user,
|
|
|
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
|
|
|
|
|
notification_policy=notification_policy,
|
|
|
|
|
alert_group=alert_group,
|
|
|
|
|
reason="Slack token error",
|
|
|
|
|
notification_step=notification_policy.step,
|
|
|
|
|
notification_channel=notification_policy.notify_by,
|
|
|
|
|
notification_error_code=UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_IN_SLACK_TOKEN_ERROR,
|
|
|
|
|
).save()
|
|
|
|
|
return
|
2023-09-12 10:49:16 +01:00
|
|
|
except SlackAPIChannelArchivedError:
|
2022-06-03 08:09:47 -06:00
|
|
|
UserNotificationPolicyLogRecord(
|
|
|
|
|
author=user,
|
|
|
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
|
|
|
|
|
notification_policy=notification_policy,
|
|
|
|
|
alert_group=alert_group,
|
|
|
|
|
reason="channel is archived",
|
|
|
|
|
notification_step=notification_policy.step,
|
|
|
|
|
notification_channel=notification_policy.notify_by,
|
|
|
|
|
notification_error_code=UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_IN_SLACK_CHANNEL_IS_ARCHIVED,
|
|
|
|
|
).save()
|
|
|
|
|
return
|
|
|
|
|
else:
|
2023-09-04 19:03:18 +01:00
|
|
|
alert_group.slack_messages.create(
|
2022-06-03 08:09:47 -06:00
|
|
|
slack_id=result["ts"],
|
2024-12-06 11:43:40 -05:00
|
|
|
_slack_team_identity=slack_team_identity,
|
2024-11-26 06:03:38 -05:00
|
|
|
channel=slack_channel,
|
2023-09-04 19:03:18 +01:00
|
|
|
)
|
2024-11-26 06:03:38 -05:00
|
|
|
|
2023-12-14 15:32:26 -03:00
|
|
|
# create success record
|
|
|
|
|
UserNotificationPolicyLogRecord.objects.create(
|
|
|
|
|
author=user,
|
|
|
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_SUCCESS,
|
|
|
|
|
notification_policy=notification_policy,
|
|
|
|
|
alert_group=alert_group,
|
|
|
|
|
notification_step=notification_policy.step,
|
|
|
|
|
notification_channel=notification_policy.notify_by,
|
|
|
|
|
)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
# Check if escalated user is in channel. Otherwise send notification and request to invite him.
|
|
|
|
|
try:
|
|
|
|
|
if slack_user_identity:
|
|
|
|
|
channel_members = []
|
|
|
|
|
try:
|
2023-09-05 11:31:59 +02:00
|
|
|
channel_members = sc.conversations_members(channel=channel_id)["members"]
|
2023-09-12 10:49:16 +01:00
|
|
|
except SlackAPIFetchMembersFailedError:
|
|
|
|
|
pass
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
if slack_user_identity.slack_id not in channel_members:
|
|
|
|
|
time.sleep(5) # 2 messages in the same moment are ratelimited by Slack. Dirty hack.
|
2023-01-18 16:08:15 +00:00
|
|
|
slack_user_identity.send_link_to_slack_message(slack_message)
|
2023-09-12 10:49:16 +01:00
|
|
|
except (SlackAPITokenError, SlackAPIMethodNotSupportedForChannelTypeError):
|
|
|
|
|
pass
|
2024-12-02 14:40:30 -05:00
|
|
|
|
|
|
|
|
def _get_update_message_cache_key(self) -> str:
|
|
|
|
|
return f"update_alert_group_slack_message_{self.alert_group.pk}"
|
|
|
|
|
|
|
|
|
|
def get_active_update_task_id(self) -> typing.Optional[str]:
|
|
|
|
|
return cache.get(self._get_update_message_cache_key(), default=None)
|
|
|
|
|
|
|
|
|
|
def set_active_update_task_id(self, task_id: str) -> None:
|
|
|
|
|
"""
|
|
|
|
|
NOTE: we store the task ID in the cache for twice the debounce interval to ensure that the task ID is
|
|
|
|
|
EVENTUALLY removed. The background task which updates the message will remove the task ID from the cache, but
|
|
|
|
|
this is a safety measure in case the task fails to run or complete. The task ID would be removed from the cache
|
|
|
|
|
which would then allow the message to be updated again in a subsequent call to this method.
|
|
|
|
|
"""
|
|
|
|
|
cache.set(
|
|
|
|
|
self._get_update_message_cache_key(),
|
|
|
|
|
task_id,
|
|
|
|
|
timeout=self.ALERT_GROUP_UPDATE_DEBOUNCE_INTERVAL_SECONDS * 2,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def mark_active_update_task_as_complete(self) -> None:
|
|
|
|
|
self.last_updated = timezone.now()
|
|
|
|
|
self.save(update_fields=["last_updated"])
|
|
|
|
|
|
|
|
|
|
cache.delete(self._get_update_message_cache_key())
|
|
|
|
|
|
|
|
|
|
def update_alert_groups_message(self, debounce: bool) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Schedule an update task for the associated alert group's Slack message, respecting the debounce interval.
|
|
|
|
|
|
|
|
|
|
This method ensures that updates to the Slack message related to an alert group are not performed
|
|
|
|
|
too frequently, adhering to the `ALERT_GROUP_UPDATE_DEBOUNCE_INTERVAL_SECONDS` debounce interval.
|
|
|
|
|
It schedules a background task to update the message after the appropriate countdown.
|
|
|
|
|
|
|
|
|
|
The method performs the following steps:
|
|
|
|
|
- Checks if there's already an active update task ID set in the cache. If so, exits to prevent
|
|
|
|
|
duplicate scheduling.
|
|
|
|
|
- Calculates the time since the last update (`last_updated` field) and determines the remaining time needed
|
|
|
|
|
to respect the debounce interval.
|
|
|
|
|
- Schedules the `update_alert_group_slack_message` task with the calculated countdown.
|
|
|
|
|
- Stores the task ID in the cache to prevent multiple tasks from being scheduled.
|
|
|
|
|
|
|
|
|
|
debounce: bool - this is intended to be used when we want to debounce updates to the message. Examples:
|
|
|
|
|
- when set to True, we will skip scheduling an update task if there's an active update task (eg. debounce it)
|
|
|
|
|
- when set to False, we will immediately schedule an update task
|
|
|
|
|
"""
|
|
|
|
|
if not self.alert_group:
|
|
|
|
|
logger.warning(
|
|
|
|
|
f"skipping update_alert_groups_message as SlackMessage {self.pk} has no alert_group associated with it"
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
active_update_task_id = self.get_active_update_task_id()
|
|
|
|
|
if debounce and active_update_task_id is not None:
|
|
|
|
|
logger.info(
|
|
|
|
|
f"skipping update_alert_groups_message as SlackMessage {self.pk} has an active update task "
|
|
|
|
|
f"{active_update_task_id} and debounce is set to True"
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
now = timezone.now()
|
|
|
|
|
|
|
|
|
|
# we previously weren't updating the last_updated field for messages, so there will be cases
|
|
|
|
|
# where the last_updated field is None
|
|
|
|
|
last_updated = self.last_updated or now
|
|
|
|
|
|
|
|
|
|
time_since_last_update = (now - last_updated).total_seconds()
|
|
|
|
|
remaining_time = self.ALERT_GROUP_UPDATE_DEBOUNCE_INTERVAL_SECONDS - int(time_since_last_update)
|
|
|
|
|
countdown = max(remaining_time, 10) if debounce else 0
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
f"updating message for alert_group {self.alert_group.pk} in {countdown} seconds "
|
|
|
|
|
f"(debounce interval: {self.ALERT_GROUP_UPDATE_DEBOUNCE_INTERVAL_SECONDS})"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
task_id = celery_uuid()
|
|
|
|
|
|
|
|
|
|
# NOTE: we need to persist the task ID in the cache before scheduling the task to prevent
|
|
|
|
|
# a race condition where the task starts before the task ID is stored in the cache as the task
|
|
|
|
|
# does a check to verify that the celery task id matches the one stored in the cache
|
|
|
|
|
#
|
|
|
|
|
# (see update_alert_group_slack_message task for more details)
|
|
|
|
|
self.set_active_update_task_id(task_id)
|
|
|
|
|
update_alert_group_slack_message.apply_async((self.pk,), countdown=countdown, task_id=task_id)
|