Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.13.6 #5318

Merged
merged 7 commits into from
Dec 2, 2024
Merged

v1.13.6 #5318

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading