Skip to content

Commit

Permalink
v1.13.6
Browse files Browse the repository at this point in the history
  • Loading branch information
joeyorlando authored Dec 2, 2024
2 parents d27d69e + 26946f0 commit 81e4ffd
Show file tree
Hide file tree
Showing 59 changed files with 2,299 additions and 653 deletions.
25 changes: 18 additions & 7 deletions engine/apps/alerts/models/alert_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ def acknowledge_by_user_or_backsync(
organization_id = user.organization_id if user else self.channel.organization_id
logger.debug(f"Started acknowledge_by_user_or_backsync for alert_group {self.pk}")

# if incident was silenced or resolved, unsilence/unresolve it without starting escalation
# if alert group was silenced or resolved, unsilence/unresolve it without starting escalation
if self.silenced:
self.un_silence()
self.log_records.create(
Expand Down Expand Up @@ -1980,16 +1980,27 @@ def is_presented_in_slack(self):

@property
def slack_channel_id(self) -> str | None:
if not self.channel.organization.slack_team_identity:
return None
elif self.slack_message:
return self.slack_message.channel.slack_id
elif self.channel_filter:
return self.channel_filter.slack_channel_id_or_org_default_id
channel_filter = self.channel_filter

if self.slack_message:
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
#
# return self.slack_message.channel.slack_id
return self.slack_message._channel_id
elif channel_filter and channel_filter.slack_channel_or_org_default:
return channel_filter.slack_channel_or_org_default.slack_id
return None

@property
def slack_message(self) -> typing.Optional["SlackMessage"]:
"""
`slack_message` property returns the first `SlackMessage` for the `AlertGroup`. This corresponds to the
Slack message representing the main message in Slack (ie. not a message in a thread).
This should not be confused with `slack_messages`, which is a `RelatedManager` that returns all `SlackMessage`
instances for the `AlertGroup`.
"""
try:
# prefetched_slack_messages could be set in apps.api.serializers.alert_group.AlertGroupListSerializer
return self.prefetched_slack_messages[0] if self.prefetched_slack_messages else None
Expand Down
38 changes: 20 additions & 18 deletions engine/apps/alerts/models/alert_receive_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
metrics_remove_deleted_integration_from_cache,
metrics_update_integration_cache,
)
from apps.slack.constants import SLACK_RATE_LIMIT_DELAY, SLACK_RATE_LIMIT_TIMEOUT
from apps.slack.constants import SLACK_RATE_LIMIT_TIMEOUT
from apps.slack.tasks import post_slack_rate_limit_message
from apps.slack.utils import post_message_to_channel
from common.api_helpers.utils import create_engine_url
Expand All @@ -43,7 +43,7 @@

from apps.alerts.models import AlertGroup, ChannelFilter
from apps.labels.models import AlertReceiveChannelAssociatedLabel
from apps.user_management.models import Organization, Team
from apps.user_management.models import Organization, Team, User

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -391,7 +391,7 @@ def save(self, *args, **kwargs):

return super().save(*args, **kwargs)

def change_team(self, team_id, user):
def change_team(self, team_id: int, user: "User") -> None:
if team_id == self.team_id:
raise TeamCanNotBeChangedError("Integration is already in this team")

Expand All @@ -409,52 +409,54 @@ def grafana_alerting_sync_manager(self):
return GrafanaAlertingSyncManager(self)

@property
def is_alerting_integration(self):
def is_alerting_integration(self) -> bool:
return self.integration in {
AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING,
AlertReceiveChannel.INTEGRATION_LEGACY_GRAFANA_ALERTING,
}

@cached_property
def team_name(self):
def team_name(self) -> str:
return self.team.name if self.team else "No team"

@cached_property
def team_id_or_no_team(self):
def team_id_or_no_team(self) -> str:
return self.team_id if self.team else "no_team"

@cached_property
def emojized_verbal_name(self):
def emojized_verbal_name(self) -> str:
return emoji.emojize(self.verbal_name, language="alias")

@property
def new_incidents_web_link(self):
def new_incidents_web_link(self) -> str:
from apps.alerts.models import AlertGroup

return UIURLBuilder(self.organization).alert_groups(
f"?integration={self.public_primary_key}&status={AlertGroup.NEW}",
)

@property
def is_rate_limited_in_slack(self):
def is_rate_limited_in_slack(self) -> bool:
return (
self.rate_limited_in_slack_at is not None
and self.rate_limited_in_slack_at + SLACK_RATE_LIMIT_TIMEOUT > timezone.now()
)

def start_send_rate_limit_message_task(self, delay=SLACK_RATE_LIMIT_DELAY):
def start_send_rate_limit_message_task(self, error_message_verb: str, delay: int) -> None:
task_id = celery_uuid()

self.rate_limit_message_task_id = task_id
self.rate_limited_in_slack_at = timezone.now()
self.save(update_fields=["rate_limit_message_task_id", "rate_limited_in_slack_at"])
post_slack_rate_limit_message.apply_async((self.pk,), countdown=delay, task_id=task_id)

post_slack_rate_limit_message.apply_async((self.pk, error_message_verb), countdown=delay, task_id=task_id)

@property
def alert_groups_count(self):
def alert_groups_count(self) -> int:
return self.alert_groups.count()

@property
def alerts_count(self):
def alerts_count(self) -> int:
from apps.alerts.models import Alert

return Alert.objects.filter(group__channel=self).count()
Expand All @@ -464,7 +466,7 @@ def is_able_to_autoresolve(self) -> bool:
return self.config.is_able_to_autoresolve

@property
def is_demo_alert_enabled(self):
def is_demo_alert_enabled(self) -> bool:
return self.config.is_demo_alert_enabled

@property
Expand Down Expand Up @@ -513,7 +515,7 @@ def get_or_create_manual_integration(cls, defaults, **kwargs):
return alert_receive_channel

@property
def short_name(self):
def short_name(self) -> str:
if self.verbal_name is None:
return self.created_name + "" if self.deleted_at is None else "(Deleted)"
elif self.verbal_name == self.created_name:
Expand Down Expand Up @@ -548,14 +550,14 @@ def integration_url(self) -> str | None:
return create_engine_url(f"integrations/v1/{slug}/{self.token}/")

@property
def inbound_email(self):
def inbound_email(self) -> typing.Optional[str]:
if self.integration != AlertReceiveChannel.INTEGRATION_INBOUND_EMAIL:
return None

return f"{self.token}@{live_settings.INBOUND_EMAIL_DOMAIN}"

@property
def default_channel_filter(self):
def default_channel_filter(self) -> typing.Optional["ChannelFilter"]:
return self.channel_filters.filter(is_default=True).first()

# Templating
Expand Down Expand Up @@ -590,7 +592,7 @@ def templates(self):
}

@property
def is_available_for_custom_templates(self):
def is_available_for_custom_templates(self) -> bool:
return True

# Maintenance
Expand Down
10 changes: 2 additions & 8 deletions engine/apps/alerts/models/channel_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,8 @@ def slack_channel_slack_id(self) -> typing.Optional[str]:
return self.slack_channel.slack_id if self.slack_channel else None

@property
def slack_channel_id_or_org_default_id(self):
organization = self.alert_receive_channel.organization

if organization.slack_team_identity is None:
return None
elif self.slack_channel_slack_id is None:
return organization.default_slack_channel_slack_id
return self.slack_channel_slack_id
def slack_channel_or_org_default(self) -> typing.Optional["SlackChannel"]:
return self.slack_channel or self.alert_receive_channel.organization.default_slack_channel

@property
def str_for_clients(self):
Expand Down
4 changes: 0 additions & 4 deletions engine/apps/alerts/tasks/compare_escalations.py

This file was deleted.

3 changes: 1 addition & 2 deletions engine/apps/alerts/tasks/escalate_alert_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from common.custom_celery_tasks import shared_dedicated_queue_retry_task

from .compare_escalations import compare_escalations
from .task_logger import task_logger


Expand All @@ -29,7 +28,7 @@ def escalate_alert_group(alert_group_pk):
except IndexError:
return f"Alert group with pk {alert_group_pk} doesn't exist"

if not compare_escalations(escalate_alert_group.request.id, alert_group.active_escalation_id):
if escalate_alert_group.request.id != alert_group.active_escalation_id:
return "Active escalation ID mismatch. Duplication or non-active escalation triggered. Active: {}".format(
alert_group.active_escalation_id
)
Expand Down
3 changes: 1 addition & 2 deletions engine/apps/alerts/tasks/notify_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from apps.phone_notifications.phone_backend import PhoneBackend
from common.custom_celery_tasks import shared_dedicated_queue_retry_task

from .compare_escalations import compare_escalations
from .task_logger import task_logger

if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -618,7 +617,7 @@ def send_bundled_notification(user_notification_bundle_id: int):
)
return

if not compare_escalations(send_bundled_notification.request.id, user_notification_bundle.notification_task_id):
if send_bundled_notification.request.id != user_notification_bundle.notification_task_id:
task_logger.info(
f"send_bundled_notification: notification_task_id mismatch. "
f"Duplication or non-active notification triggered. "
Expand Down
6 changes: 4 additions & 2 deletions engine/apps/alerts/tasks/unsilence.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from common.custom_celery_tasks import shared_dedicated_queue_retry_task

from .compare_escalations import compare_escalations
from .send_alert_group_signal import send_alert_group_signal
from .task_logger import task_logger

Expand All @@ -17,17 +16,20 @@ def unsilence_task(alert_group_pk):
from apps.alerts.models import AlertGroup, AlertGroupLogRecord

task_logger.info(f"Start unsilence_task for alert_group {alert_group_pk}")

with transaction.atomic():
try:
alert_group = AlertGroup.objects.filter(pk=alert_group_pk).select_for_update()[0] # Lock alert_group:
except IndexError:
task_logger.info(f"unsilence_task. alert_group {alert_group_pk} doesn't exist")
return
if not compare_escalations(unsilence_task.request.id, alert_group.unsilence_task_uuid):

if unsilence_task.request.id != alert_group.unsilence_task_uuid:
task_logger.info(
f"unsilence_task. alert_group {alert_group.pk}.ID mismatch.Active: {alert_group.unsilence_task_uuid}"
)
return

if alert_group.status == AlertGroup.SILENCED and alert_group.is_root_alert_group:
initial_state = alert_group.state
task_logger.info(f"unsilence alert_group {alert_group_pk} and start escalation if needed")
Expand Down
67 changes: 64 additions & 3 deletions engine/apps/alerts/tests/test_alert_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,11 @@ def test_delete(
# Check that appropriate Slack API calls are made
assert mock_chat_delete.call_count == 2
assert mock_chat_delete.call_args_list[0] == call(
channel=resolution_note_1.slack_channel_id, ts=resolution_note_1.ts
channel=resolution_note_1.slack_channel.slack_id, ts=resolution_note_1.ts
)
assert mock_chat_delete.call_args_list[1] == call(channel=slack_message.channel.slack_id, ts=slack_message.slack_id)
mock_reactions_remove.assert_called_once_with(
channel=resolution_note_2.slack_channel_id, name="memo", timestamp=resolution_note_2.ts
channel=resolution_note_2.slack_channel.slack_id, name="memo", timestamp=resolution_note_2.ts
)


Expand Down Expand Up @@ -707,7 +707,7 @@ def test_delete_by_user(


@pytest.mark.django_db
def test_integration_config_on_alert_group_created(make_organization, make_alert_receive_channel, make_channel_filter):
def test_integration_config_on_alert_group_created(make_organization, make_alert_receive_channel):
organization = make_organization()
alert_receive_channel = make_alert_receive_channel(organization, grouping_id_template="group_to_one_group")

Expand Down Expand Up @@ -806,3 +806,64 @@ def test_alert_group_created_if_resolve_condition_but_auto_resolving_disabled(

# the alert will create a new alert group
assert alert.group != resolved_alert_group


class TestAlertGroupSlackChannelID:
@pytest.mark.django_db
def test_slack_channel_id_with_slack_message(
self,
make_organization_with_slack_team_identity,
make_alert_receive_channel,
make_slack_channel,
make_slack_message,
make_alert_group,
):
"""
Test that slack_channel_id returns the _channel_id from slack_message when slack_message exists.
"""
organization, slack_team_identity = make_organization_with_slack_team_identity()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
slack_channel = make_slack_channel(slack_team_identity)
slack_message = make_slack_message(slack_channel, alert_group=alert_group)

# Assert that slack_channel_id returns the _channel_id from slack_message
assert alert_group.slack_channel_id == slack_message._channel_id

@pytest.mark.django_db
def test_slack_channel_id_with_channel_filter(
self,
make_organization_with_slack_team_identity,
make_alert_receive_channel,
make_channel_filter,
make_slack_channel,
make_alert_group,
):
"""
Test that slack_channel_id returns the slack_id from channel_filter.slack_channel_or_org_default.
"""
organization, slack_team_identity = make_organization_with_slack_team_identity()
alert_receive_channel = make_alert_receive_channel(organization)
slack_channel = make_slack_channel(slack_team_identity)
channel_filter = make_channel_filter(alert_receive_channel, slack_channel=slack_channel)
alert_group = make_alert_group(alert_receive_channel, channel_filter=channel_filter)

# Assert that slack_channel_id returns the slack_id from the channel filter's Slack channel
assert alert_group.slack_channel_id == slack_channel.slack_id

@pytest.mark.django_db
def test_slack_channel_id_no_slack_message_no_channel_filter(
self,
make_organization_with_slack_team_identity,
make_alert_receive_channel,
make_alert_group,
):
"""
Test that slack_channel_id returns None when there is no slack_message and no channel_filter.
"""
organization, _ = make_organization_with_slack_team_identity()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel, channel_filter=None)

# Assert that slack_channel_id is None
assert alert_group.slack_channel_id is None
Loading

0 comments on commit 81e4ffd

Please sign in to comment.