From f268730c769d8f02754405fd43d1b5c833d3f601 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Thu, 25 May 2023 14:38:35 +0100 Subject: [PATCH 01/36] IncidentActionsAccessControlMixin -> AlertGroupActionsAccessControlMixin --- .../slack/scenarios/alertgroup_appearance.py | 4 +-- .../apps/slack/scenarios/distribute_alerts.py | 28 ++++++++++--------- engine/apps/slack/scenarios/step_mixins.py | 4 +-- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/engine/apps/slack/scenarios/alertgroup_appearance.py b/engine/apps/slack/scenarios/alertgroup_appearance.py index 649f006f00..5c04485a05 100644 --- a/engine/apps/slack/scenarios/alertgroup_appearance.py +++ b/engine/apps/slack/scenarios/alertgroup_appearance.py @@ -5,11 +5,11 @@ from apps.api.permissions import RBACPermission from apps.slack.scenarios import scenario_step -from .step_mixins import CheckAlertIsUnarchivedMixin, IncidentActionsAccessControlMixin +from .step_mixins import AlertGroupActionsAccessControlMixin, CheckAlertIsUnarchivedMixin class OpenAlertAppearanceDialogStep( - CheckAlertIsUnarchivedMixin, IncidentActionsAccessControlMixin, scenario_step.ScenarioStep + CheckAlertIsUnarchivedMixin, AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] ACTION_VERBOSE = "open Alert Appearance" diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index a916255dee..bd0afef4b7 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -35,7 +35,7 @@ from apps.slack.utils import get_cache_key_update_incident_slack_message from common.utils import clean_markup, is_string_with_visible_characters -from .step_mixins import CheckAlertIsUnarchivedMixin, IncidentActionsAccessControlMixin +from .step_mixins import AlertGroupActionsAccessControlMixin, CheckAlertIsUnarchivedMixin logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -216,7 +216,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): class InviteOtherPersonToIncident( CheckAlertIsUnarchivedMixin, - IncidentActionsAccessControlMixin, + AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep, ): @@ -253,7 +253,7 @@ def process_signal(self, log_record): class SilenceGroupStep( CheckAlertIsUnarchivedMixin, - IncidentActionsAccessControlMixin, + AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep, ): @@ -279,7 +279,7 @@ def process_signal(self, log_record): class UnSilenceGroupStep( CheckAlertIsUnarchivedMixin, - IncidentActionsAccessControlMixin, + AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -298,7 +298,7 @@ def process_signal(self, log_record): class SelectAttachGroupStep( CheckAlertIsUnarchivedMixin, - IncidentActionsAccessControlMixin, + AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -449,7 +449,7 @@ def get_select_incidents_blocks(self, alert_group): class AttachGroupStep( CheckAlertIsUnarchivedMixin, - IncidentActionsAccessControlMixin, + AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -507,7 +507,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): class UnAttachGroupStep( CheckAlertIsUnarchivedMixin, - IncidentActionsAccessControlMixin, + AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -523,7 +523,9 @@ def process_signal(self, log_record): self.alert_group_slack_service.update_alert_group_slack_message(alert_group) -class StopInvitationProcess(CheckAlertIsUnarchivedMixin, IncidentActionsAccessControlMixin, scenario_step.ScenarioStep): +class StopInvitationProcess( + CheckAlertIsUnarchivedMixin, AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep +): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] ACTION_VERBOSE = "stop invitation" @@ -541,7 +543,7 @@ def process_signal(self, log_record): class CustomButtonProcessStep( CheckAlertIsUnarchivedMixin, - IncidentActionsAccessControlMixin, + AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep, ): # TODO: @@ -601,7 +603,7 @@ def process_signal(self, log_record): class ResolveGroupStep( CheckAlertIsUnarchivedMixin, - IncidentActionsAccessControlMixin, + AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -643,7 +645,7 @@ def process_signal(self, log_record): class UnResolveGroupStep( CheckAlertIsUnarchivedMixin, - IncidentActionsAccessControlMixin, + AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -661,7 +663,7 @@ def process_signal(self, log_record): class AcknowledgeGroupStep( CheckAlertIsUnarchivedMixin, - IncidentActionsAccessControlMixin, + AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -682,7 +684,7 @@ def process_signal(self, log_record): class UnAcknowledgeGroupStep( CheckAlertIsUnarchivedMixin, - IncidentActionsAccessControlMixin, + AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 199cde110d..9842e69744 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -24,9 +24,9 @@ def send_denied_message(self, payload): pass -class IncidentActionsAccessControlMixin(AccessControl): +class AlertGroupActionsAccessControlMixin(AccessControl): """ - Mixin for auth in incident actions + Mixin for alert group actions """ def send_denied_message_to_channel(self, payload=None): From 623b164e41b7b969a35adc1e88869b7d3b98ea98 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Thu, 25 May 2023 14:41:40 +0100 Subject: [PATCH 02/36] Simplify AlertGroupActionsAccessControlMixin --- engine/apps/slack/scenarios/step_mixins.py | 23 ++++++---------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 9842e69744..5cceee3b5f 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -1,12 +1,15 @@ import logging -from abc import ABC, abstractmethod from apps.api.permissions import user_is_authorized logger = logging.getLogger(__name__) -class AccessControl(ABC): +class AlertGroupActionsAccessControlMixin: + """ + Mixin for alert group actions intended to use a mixin along with ScenarioStep + """ + REQUIRED_PERMISSIONS = [] ACTION_VERBOSE = "" @@ -19,20 +22,6 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): def check_membership(self): return user_is_authorized(self.user, self.REQUIRED_PERMISSIONS) - @abstractmethod - def send_denied_message(self, payload): - pass - - -class AlertGroupActionsAccessControlMixin(AccessControl): - """ - Mixin for alert group actions - """ - - def send_denied_message_to_channel(self, payload=None): - # Send denied message to thread by default - return False - def send_denied_message(self, payload): try: thread_ts = payload["message_ts"] @@ -58,7 +47,7 @@ def send_denied_message(self, payload): }, }, ], - thread_ts=None if self.send_denied_message_to_channel(payload) else thread_ts, + thread_ts=thread_ts, unfurl_links=True, ) From d44c67e6fa017ba23693608869711ff19373400d Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Thu, 25 May 2023 14:48:37 +0100 Subject: [PATCH 03/36] Move get_alert_group_from_slack_message_payload --- engine/apps/slack/models/slack_message.py | 29 ------------- .../apps/slack/scenarios/distribute_alerts.py | 22 +++++----- engine/apps/slack/scenarios/step_mixins.py | 42 ++++++++++++++++--- 3 files changed, 48 insertions(+), 45 deletions(-) diff --git a/engine/apps/slack/models/slack_message.py b/engine/apps/slack/models/slack_message.py index 2d57637816..26f3abee18 100644 --- a/engine/apps/slack/models/slack_message.py +++ b/engine/apps/slack/models/slack_message.py @@ -217,32 +217,3 @@ def send_slack_notification(self, user, alert_group, notification_policy): pass else: raise e - - @classmethod - def get_alert_group_from_slack_message_payload(cls, slack_team_identity, payload): - - message_ts = payload.get("message_ts") or payload["container"]["message_ts"] # interactive message or block - channel_id = payload["channel"]["id"] - - try: - slack_message = cls.objects.get( - slack_id=message_ts, - _slack_team_identity=slack_team_identity, - channel_id=channel_id, - ) - alert_group = slack_message.get_alert_group() - except cls.DoesNotExist as e: - logger.error( - f"Tried to get SlackMessage from message_ts:" - f"slack_team_identity_id={slack_team_identity.pk}," - f"message_ts={message_ts}" - ) - raise e - except cls.alert.RelatedObjectDoesNotExist as e: - logger.error( - f"Tried to get AlertGroup from SlackMessage:" - f"slack_team_identity_id={slack_team_identity.pk}," - f"message_ts={message_ts}" - ) - raise e - return alert_group diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index bd0afef4b7..7b82cb5ae4 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -226,7 +226,7 @@ class InviteOtherPersonToIncident( def process_scenario(self, slack_user_identity, slack_team_identity, payload): User = apps.get_model("user_management", "User") - alert_group = SlackMessage.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) selected_user = None if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): @@ -267,7 +267,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): except KeyError: silence_delay = int(payload["actions"][0]["selected_option"]["value"]) - alert_group = SlackMessage.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.silence_by_user(self.user, silence_delay, action_source=ActionSource.SLACK) @@ -287,7 +287,7 @@ class UnSilenceGroupStep( def process_scenario(self, slack_user_identity, slack_team_identity, payload): - alert_group = SlackMessage.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.un_silence_by_user(self.user, action_source=ActionSource.SLACK) @@ -495,7 +495,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): root_alert_group_pk = int(payload["actions"][0]["selected_option"]["value"]) root_alert_group = AlertGroup.all_objects.get(pk=root_alert_group_pk) - alert_group = SlackMessage.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group) and self.check_alert_is_unarchived( slack_team_identity, payload, root_alert_group @@ -514,7 +514,7 @@ class UnAttachGroupStep( ACTION_VERBOSE = "Unattach incident" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - alert_group = SlackMessage.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.un_attach_by_user(self.user, action_source=ActionSource.SLACK) @@ -530,7 +530,7 @@ class StopInvitationProcess( ACTION_VERBOSE = "stop invitation" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - alert_group = SlackMessage.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): return @@ -552,7 +552,7 @@ class CustomButtonProcessStep( def process_scenario(self, slack_user_identity, slack_team_identity, payload): CustomButtom = apps.get_model("alerts", "CustomButton") - alert_group = SlackMessage.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): custom_button_pk = payload["actions"][0]["name"].split("_")[1] alert_group_pk = payload["actions"][0]["name"].split("_")[2] @@ -612,7 +612,7 @@ class ResolveGroupStep( def process_scenario(self, slack_user_identity, slack_team_identity, payload): ResolutionNoteModalStep = scenario_step.ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep") - alert_group = SlackMessage.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): return @@ -652,7 +652,7 @@ class UnResolveGroupStep( ACTION_VERBOSE = "unresolve incident" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - alert_group = SlackMessage.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.un_resolve_by_user(self.user, action_source=ActionSource.SLACK) @@ -670,7 +670,7 @@ class AcknowledgeGroupStep( ACTION_VERBOSE = "acknowledge incident" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - alert_group = SlackMessage.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) logger.debug(f"process_scenario in AcknowledgeGroupStep for alert_group {alert_group.pk}") if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.acknowledge_by_user(self.user, action_source=ActionSource.SLACK) @@ -691,7 +691,7 @@ class UnAcknowledgeGroupStep( ACTION_VERBOSE = "unacknowledge incident" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - alert_group = SlackMessage.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) logger.debug(f"process_scenario in UnAcknowledgeGroupStep for alert_group {alert_group.pk}") if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.un_acknowledge_by_user(self.user, action_source=ActionSource.SLACK) diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 5cceee3b5f..3a5386fd7e 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -7,22 +7,54 @@ class AlertGroupActionsAccessControlMixin: """ - Mixin for alert group actions intended to use a mixin along with ScenarioStep + Mixin for alert group actions (ack, resolve, etc.). Intended to be used as a mixin along with ScenarioStep. + It serves two purposes: + 1. Check that user has required permissions to perform an action. Otherwise, send a message to a user ??? + 2. Provide utility method to get AlertGroup instance from Slack message payload. """ REQUIRED_PERMISSIONS = [] ACTION_VERBOSE = "" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - if self.check_membership(): + if self._check_membership(): return super().process_scenario(slack_user_identity, slack_team_identity, payload) else: - self.send_denied_message(payload) + self._send_denied_message(payload) - def check_membership(self): + @classmethod + def get_alert_group_from_slack_message_payload(cls, slack_team_identity, payload): + + message_ts = payload.get("message_ts") or payload["container"]["message_ts"] # interactive message or block + channel_id = payload["channel"]["id"] + + try: + slack_message = cls.objects.get( + slack_id=message_ts, + _slack_team_identity=slack_team_identity, + channel_id=channel_id, + ) + alert_group = slack_message.get_alert_group() + except cls.DoesNotExist as e: + logger.error( + f"Tried to get SlackMessage from message_ts:" + f"slack_team_identity_id={slack_team_identity.pk}," + f"message_ts={message_ts}" + ) + raise e + except cls.alert.RelatedObjectDoesNotExist as e: + logger.error( + f"Tried to get AlertGroup from SlackMessage:" + f"slack_team_identity_id={slack_team_identity.pk}," + f"message_ts={message_ts}" + ) + raise e + return alert_group + + def _check_membership(self): return user_is_authorized(self.user, self.REQUIRED_PERMISSIONS) - def send_denied_message(self, payload): + def _send_denied_message(self, payload): try: thread_ts = payload["message_ts"] except KeyError: From 153072d404ebd1977b053012a95197ccd783fd70 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Thu, 25 May 2023 14:50:27 +0100 Subject: [PATCH 04/36] remove classmethod --- engine/apps/slack/scenarios/step_mixins.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 3a5386fd7e..1f3687c315 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -1,6 +1,7 @@ import logging from apps.api.permissions import user_is_authorized +from apps.slack.models import SlackMessage logger = logging.getLogger(__name__) @@ -22,27 +23,26 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): else: self._send_denied_message(payload) - @classmethod - def get_alert_group_from_slack_message_payload(cls, slack_team_identity, payload): + def get_alert_group_from_slack_message_payload(self, slack_team_identity, payload): message_ts = payload.get("message_ts") or payload["container"]["message_ts"] # interactive message or block channel_id = payload["channel"]["id"] try: - slack_message = cls.objects.get( + slack_message = SlackMessage.objects.get( slack_id=message_ts, _slack_team_identity=slack_team_identity, channel_id=channel_id, ) alert_group = slack_message.get_alert_group() - except cls.DoesNotExist as e: + except SlackMessage.DoesNotExist as e: logger.error( f"Tried to get SlackMessage from message_ts:" f"slack_team_identity_id={slack_team_identity.pk}," f"message_ts={message_ts}" ) raise e - except cls.alert.RelatedObjectDoesNotExist as e: + except SlackMessage.alert_group.RelatedObjectDoesNotExist as e: logger.error( f"Tried to get AlertGroup from SlackMessage:" f"slack_team_identity_id={slack_team_identity.pk}," From 8f0ad013ba6dc6a57d764be509254eb97a129c52 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Thu, 25 May 2023 14:54:29 +0100 Subject: [PATCH 05/36] AlertGroupActionsAccessControlMixin -> AlertGroupActionsMixin --- .../slack/scenarios/alertgroup_appearance.py | 6 ++-- .../apps/slack/scenarios/distribute_alerts.py | 28 +++++++++---------- engine/apps/slack/scenarios/step_mixins.py | 2 +- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/engine/apps/slack/scenarios/alertgroup_appearance.py b/engine/apps/slack/scenarios/alertgroup_appearance.py index 5c04485a05..11e3824a54 100644 --- a/engine/apps/slack/scenarios/alertgroup_appearance.py +++ b/engine/apps/slack/scenarios/alertgroup_appearance.py @@ -5,12 +5,10 @@ from apps.api.permissions import RBACPermission from apps.slack.scenarios import scenario_step -from .step_mixins import AlertGroupActionsAccessControlMixin, CheckAlertIsUnarchivedMixin +from .step_mixins import AlertGroupActionsMixin, CheckAlertIsUnarchivedMixin -class OpenAlertAppearanceDialogStep( - CheckAlertIsUnarchivedMixin, AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep -): +class OpenAlertAppearanceDialogStep(CheckAlertIsUnarchivedMixin, AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] ACTION_VERBOSE = "open Alert Appearance" diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index 7b82cb5ae4..4e8bfa957b 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -35,7 +35,7 @@ from apps.slack.utils import get_cache_key_update_incident_slack_message from common.utils import clean_markup, is_string_with_visible_characters -from .step_mixins import AlertGroupActionsAccessControlMixin, CheckAlertIsUnarchivedMixin +from .step_mixins import AlertGroupActionsMixin, CheckAlertIsUnarchivedMixin logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -216,7 +216,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): class InviteOtherPersonToIncident( CheckAlertIsUnarchivedMixin, - AlertGroupActionsAccessControlMixin, + AlertGroupActionsMixin, scenario_step.ScenarioStep, ): @@ -253,7 +253,7 @@ def process_signal(self, log_record): class SilenceGroupStep( CheckAlertIsUnarchivedMixin, - AlertGroupActionsAccessControlMixin, + AlertGroupActionsMixin, scenario_step.ScenarioStep, ): @@ -279,7 +279,7 @@ def process_signal(self, log_record): class UnSilenceGroupStep( CheckAlertIsUnarchivedMixin, - AlertGroupActionsAccessControlMixin, + AlertGroupActionsMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -298,7 +298,7 @@ def process_signal(self, log_record): class SelectAttachGroupStep( CheckAlertIsUnarchivedMixin, - AlertGroupActionsAccessControlMixin, + AlertGroupActionsMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -449,7 +449,7 @@ def get_select_incidents_blocks(self, alert_group): class AttachGroupStep( CheckAlertIsUnarchivedMixin, - AlertGroupActionsAccessControlMixin, + AlertGroupActionsMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -507,7 +507,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): class UnAttachGroupStep( CheckAlertIsUnarchivedMixin, - AlertGroupActionsAccessControlMixin, + AlertGroupActionsMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -523,9 +523,7 @@ def process_signal(self, log_record): self.alert_group_slack_service.update_alert_group_slack_message(alert_group) -class StopInvitationProcess( - CheckAlertIsUnarchivedMixin, AlertGroupActionsAccessControlMixin, scenario_step.ScenarioStep -): +class StopInvitationProcess(CheckAlertIsUnarchivedMixin, AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] ACTION_VERBOSE = "stop invitation" @@ -543,7 +541,7 @@ def process_signal(self, log_record): class CustomButtonProcessStep( CheckAlertIsUnarchivedMixin, - AlertGroupActionsAccessControlMixin, + AlertGroupActionsMixin, scenario_step.ScenarioStep, ): # TODO: @@ -603,7 +601,7 @@ def process_signal(self, log_record): class ResolveGroupStep( CheckAlertIsUnarchivedMixin, - AlertGroupActionsAccessControlMixin, + AlertGroupActionsMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -645,7 +643,7 @@ def process_signal(self, log_record): class UnResolveGroupStep( CheckAlertIsUnarchivedMixin, - AlertGroupActionsAccessControlMixin, + AlertGroupActionsMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -663,7 +661,7 @@ def process_signal(self, log_record): class AcknowledgeGroupStep( CheckAlertIsUnarchivedMixin, - AlertGroupActionsAccessControlMixin, + AlertGroupActionsMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] @@ -684,7 +682,7 @@ def process_signal(self, log_record): class UnAcknowledgeGroupStep( CheckAlertIsUnarchivedMixin, - AlertGroupActionsAccessControlMixin, + AlertGroupActionsMixin, scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 1f3687c315..2088816275 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -6,7 +6,7 @@ logger = logging.getLogger(__name__) -class AlertGroupActionsAccessControlMixin: +class AlertGroupActionsMixin: """ Mixin for alert group actions (ack, resolve, etc.). Intended to be used as a mixin along with ScenarioStep. It serves two purposes: From b03af6889fe668ac3592d82fc1c0cb236b6eb962 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Thu, 25 May 2023 14:56:19 +0100 Subject: [PATCH 06/36] simplify --- .../apps/slack/scenarios/distribute_alerts.py | 22 +++++++++---------- engine/apps/slack/scenarios/step_mixins.py | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index 4e8bfa957b..d7725847bf 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -226,7 +226,7 @@ class InviteOtherPersonToIncident( def process_scenario(self, slack_user_identity, slack_team_identity, payload): User = apps.get_model("user_management", "User") - alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group(slack_team_identity, payload) selected_user = None if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): @@ -267,7 +267,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): except KeyError: silence_delay = int(payload["actions"][0]["selected_option"]["value"]) - alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group(slack_team_identity, payload) if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.silence_by_user(self.user, silence_delay, action_source=ActionSource.SLACK) @@ -287,7 +287,7 @@ class UnSilenceGroupStep( def process_scenario(self, slack_user_identity, slack_team_identity, payload): - alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group(slack_team_identity, payload) if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.un_silence_by_user(self.user, action_source=ActionSource.SLACK) @@ -495,7 +495,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): root_alert_group_pk = int(payload["actions"][0]["selected_option"]["value"]) root_alert_group = AlertGroup.all_objects.get(pk=root_alert_group_pk) - alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group(slack_team_identity, payload) if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group) and self.check_alert_is_unarchived( slack_team_identity, payload, root_alert_group @@ -514,7 +514,7 @@ class UnAttachGroupStep( ACTION_VERBOSE = "Unattach incident" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group(slack_team_identity, payload) if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.un_attach_by_user(self.user, action_source=ActionSource.SLACK) @@ -528,7 +528,7 @@ class StopInvitationProcess(CheckAlertIsUnarchivedMixin, AlertGroupActionsMixin, ACTION_VERBOSE = "stop invitation" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group(slack_team_identity, payload) if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): return @@ -550,7 +550,7 @@ class CustomButtonProcessStep( def process_scenario(self, slack_user_identity, slack_team_identity, payload): CustomButtom = apps.get_model("alerts", "CustomButton") - alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group(slack_team_identity, payload) if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): custom_button_pk = payload["actions"][0]["name"].split("_")[1] alert_group_pk = payload["actions"][0]["name"].split("_")[2] @@ -610,7 +610,7 @@ class ResolveGroupStep( def process_scenario(self, slack_user_identity, slack_team_identity, payload): ResolutionNoteModalStep = scenario_step.ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep") - alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group(slack_team_identity, payload) if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): return @@ -650,7 +650,7 @@ class UnResolveGroupStep( ACTION_VERBOSE = "unresolve incident" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group(slack_team_identity, payload) if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.un_resolve_by_user(self.user, action_source=ActionSource.SLACK) @@ -668,7 +668,7 @@ class AcknowledgeGroupStep( ACTION_VERBOSE = "acknowledge incident" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group(slack_team_identity, payload) logger.debug(f"process_scenario in AcknowledgeGroupStep for alert_group {alert_group.pk}") if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.acknowledge_by_user(self.user, action_source=ActionSource.SLACK) @@ -689,7 +689,7 @@ class UnAcknowledgeGroupStep( ACTION_VERBOSE = "unacknowledge incident" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - alert_group = self.get_alert_group_from_slack_message_payload(slack_team_identity, payload) + alert_group = self.get_alert_group(slack_team_identity, payload) logger.debug(f"process_scenario in UnAcknowledgeGroupStep for alert_group {alert_group.pk}") if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.un_acknowledge_by_user(self.user, action_source=ActionSource.SLACK) diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 2088816275..10ed308bf5 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -23,7 +23,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): else: self._send_denied_message(payload) - def get_alert_group_from_slack_message_payload(self, slack_team_identity, payload): + def get_alert_group(self, slack_team_identity, payload): message_ts = payload.get("message_ts") or payload["container"]["message_ts"] # interactive message or block channel_id = payload["channel"]["id"] From c52208158c5209c2373483bedf49150eaceb6a24 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Thu, 25 May 2023 15:14:48 +0100 Subject: [PATCH 07/36] simplify --- engine/apps/slack/models/slack_message.py | 3 ++- engine/apps/slack/scenarios/step_mixins.py | 17 +++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/engine/apps/slack/models/slack_message.py b/engine/apps/slack/models/slack_message.py index 26f3abee18..ee482f3ec8 100644 --- a/engine/apps/slack/models/slack_message.py +++ b/engine/apps/slack/models/slack_message.py @@ -80,7 +80,8 @@ def get_alert_group(self): self.alert_group.slack_message = self self.alert_group.save(update_fields=["slack_message"]) return self.alert_group - return self.alert.group + else: + raise @property def permalink(self): diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 10ed308bf5..5bcaa12836 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -1,5 +1,7 @@ import logging +from django.core.exceptions import ObjectDoesNotExist + from apps.api.permissions import user_is_authorized from apps.slack.models import SlackMessage @@ -28,28 +30,31 @@ def get_alert_group(self, slack_team_identity, payload): message_ts = payload.get("message_ts") or payload["container"]["message_ts"] # interactive message or block channel_id = payload["channel"]["id"] + # Get SlackMessage from DB try: slack_message = SlackMessage.objects.get( slack_id=message_ts, _slack_team_identity=slack_team_identity, channel_id=channel_id, ) - alert_group = slack_message.get_alert_group() - except SlackMessage.DoesNotExist as e: + except SlackMessage.DoesNotExist: logger.error( f"Tried to get SlackMessage from message_ts:" f"slack_team_identity_id={slack_team_identity.pk}," f"message_ts={message_ts}" ) - raise e - except SlackMessage.alert_group.RelatedObjectDoesNotExist as e: + raise + + # Get AlertGroup from SlackMessage + try: + return slack_message.get_alert_group() + except ObjectDoesNotExist: logger.error( f"Tried to get AlertGroup from SlackMessage:" f"slack_team_identity_id={slack_team_identity.pk}," f"message_ts={message_ts}" ) - raise e - return alert_group + raise def _check_membership(self): return user_is_authorized(self.user, self.REQUIRED_PERMISSIONS) From 3746523f024ceed9de0462a1d3931ffeb5bb714f Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Tue, 30 May 2023 14:53:32 +0100 Subject: [PATCH 08/36] Unify alert group action button values, pass alert_group_pk --- .../renderers/slack_renderer.py | 68 +++--- .../apps/slack/scenarios/distribute_alerts.py | 18 +- .../apps/slack/tests/test_slack_renderer.py | 215 ++++++++++++++++++ 3 files changed, 260 insertions(+), 41 deletions(-) create mode 100644 engine/apps/slack/tests/test_slack_renderer.py diff --git a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py index 0718bd68db..35a2314b6d 100644 --- a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py @@ -107,7 +107,7 @@ def render_alert_group_attachments(self): "name": ScenarioStep.get_step("distribute_alerts", "UnAttachGroupStep").routing_uid(), "text": "Unattach", "type": "button", - "value": json.dumps({"organization_id": self.alert_group.channel.organization_id}), + "value": self._alert_group_action_value(), } ], } @@ -180,7 +180,7 @@ def _get_buttons_blocks(self): "emoji": True, }, "type": "button", - "value": json.dumps({"organization_id": self.alert_group.channel.organization_id}), + "value": self._alert_group_action_value(), "action_id": ScenarioStep.get_step( "distribute_alerts", "AcknowledgeGroupStep", @@ -196,7 +196,7 @@ def _get_buttons_blocks(self): "emoji": True, }, "type": "button", - "value": json.dumps({"organization_id": self.alert_group.channel.organization_id}), + "value": self._alert_group_action_value(), "action_id": ScenarioStep.get_step( "distribute_alerts", "UnAcknowledgeGroupStep", @@ -208,7 +208,7 @@ def _get_buttons_blocks(self): "text": {"type": "plain_text", "text": "Resolve", "emoji": True}, "type": "button", "style": "primary", - "value": json.dumps({"organization_id": self.alert_group.channel.organization_id}), + "value": self._alert_group_action_value(), "action_id": ScenarioStep.get_step("distribute_alerts", "ResolveGroupStep").routing_uid(), }, ) @@ -221,7 +221,10 @@ def _get_buttons_blocks(self): if not self.alert_group.silenced: silence_options = [ - {"text": {"type": "plain_text", "text": text, "emoji": True}, "value": str(value)} + { + "text": {"type": "plain_text", "text": text, "emoji": True}, + "value": self._alert_group_action_value(delay=value), + } for value, text in AlertGroup.SILENCE_DELAY_OPTIONS ] buttons.append( @@ -230,7 +233,6 @@ def _get_buttons_blocks(self): "type": "static_select", "options": silence_options, "action_id": ScenarioStep.get_step("distribute_alerts", "SilenceGroupStep").routing_uid(), - # "value": json.dumps({"organization_id": self.alert_group.channel.organization_id}), } ) else: @@ -238,7 +240,7 @@ def _get_buttons_blocks(self): { "text": {"type": "plain_text", "text": "Unsilence", "emoji": True}, "type": "button", - "value": json.dumps({"organization_id": self.alert_group.channel.organization_id}), + "value": self._alert_group_action_value(), "action_id": ScenarioStep.get_step("distribute_alerts", "UnSilenceGroupStep").routing_uid(), }, ) @@ -247,12 +249,7 @@ def _get_buttons_blocks(self): "text": {"type": "plain_text", "text": "Attach to ...", "emoji": True}, "type": "button", "action_id": ScenarioStep.get_step("distribute_alerts", "SelectAttachGroupStep").routing_uid(), - "value": json.dumps( - { - "alert_group_pk": self.alert_group.pk, - "organization_id": self.alert_group.channel.organization_id, - } - ), + "value": self._alert_group_action_value(), } buttons.append(attach_button) else: @@ -260,7 +257,7 @@ def _get_buttons_blocks(self): { "text": {"type": "plain_text", "text": "Unresolve", "emoji": True}, "type": "button", - "value": json.dumps({"organization_id": self.alert_group.channel.organization_id}), + "value": self._alert_group_action_value(), "action_id": ScenarioStep.get_step("distribute_alerts", "UnResolveGroupStep").routing_uid(), }, ) @@ -270,12 +267,7 @@ def _get_buttons_blocks(self): { "text": {"type": "plain_text", "text": ":mag: Format Alert", "emoji": True}, "type": "button", - "value": json.dumps( - { - "alert_group_pk": str(self.alert_group.pk), - "organization_id": self.alert_group.channel.organization_id, - } - ), + "value": self._alert_group_action_value(), "action_id": ScenarioStep.get_step( "alertgroup_appearance", "OpenAlertAppearanceDialogStep" ).routing_uid(), @@ -292,13 +284,7 @@ def _get_buttons_blocks(self): }, "type": "button", "action_id": ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep").routing_uid(), - "value": json.dumps( - { - "resolution_note_window_action": "edit", - "alert_group_pk": self.alert_group.pk, - "organization_id": self.alert_group.channel.organization_id, - } - ), + "value": self._alert_group_action_value(resolution_note_window_action="edit"), } if resolution_notes_count == 0: resolution_notes_button["style"] = "primary" @@ -322,7 +308,7 @@ def _get_buttons_blocks(self): "text": {"type": "plain_text", "text": "Resolve", "emoji": True}, "type": "button", "style": "primary", - "value": json.dumps({"organization_id": self.alert_group.channel.organization_id}), + "value": self._alert_group_action_value(), "action_id": ScenarioStep.get_step("distribute_alerts", "ResolveGroupStep").routing_uid(), }, ) @@ -339,13 +325,11 @@ def _get_invitation_attachment(self): invitee_name = invitation.invitee.get_username_with_slack_verbal() buttons.append( { - "name": "{}_{}".format( - ScenarioStep.get_step("distribute_alerts", "StopInvitationProcess").routing_uid(), invitation.pk - ), + "name": ScenarioStep.get_step("distribute_alerts", "StopInvitationProcess").routing_uid(), "text": "Stop inviting {}".format(invitee_name), "type": "button", "style": "primary", - "value": json.dumps({"organization_id": self.alert_group.channel.organization_id}), + "value": self._alert_group_action_value(invitation_id=invitation.pk), }, ) return [ @@ -382,7 +366,10 @@ def _get_select_user_element( user_verbal = f"{user.get_username_with_slack_verbal()}" if len(user_verbal) > 75: user_verbal = user_verbal[:72] + "..." - option = {"text": {"type": "plain_text", "text": user_verbal}, "value": json.dumps({"user_id": user.pk})} + option = { + "text": {"type": "plain_text", "text": user_verbal}, + "value": self._alert_group_action_value(user_id=user.pk), + } options.append(option) if users_count > MAX_STATIC_SELECT_OPTIONS: @@ -397,7 +384,7 @@ def _get_select_user_element( elif users_count == 0: # strange case when there are no users to select option = { "text": {"type": "plain_text", "text": "No users to select"}, - "value": json.dumps({"user_id": None}), + "value": self._alert_group_action_value(user_id=None), } options.append(option) element["options"] = options @@ -413,7 +400,7 @@ def _get_select_user_element( user_verbal = f"{user.get_username_with_slack_verbal()}" option = { "text": {"type": "plain_text", "text": user_verbal}, - "value": json.dumps({"user_id": user.pk}), + "value": self._alert_group_action_value(user_id=user.pk), } initial_options.append(option) element["initial_options"] = initial_options @@ -421,8 +408,17 @@ def _get_select_user_element( user_verbal = f"{initial_user.get_username_with_slack_verbal()}" initial_option = { "text": {"type": "plain_text", "text": user_verbal}, - "value": json.dumps({"user_id": initial_user.pk}), + "value": self._alert_group_action_value(user_id=initial_user.pk), } element["initial_option"] = initial_option return element + + def _alert_group_action_value(self, **kwargs): + # TODO: add comment + data = { + "organization_id": self.alert_group.channel.organization_id, + "alert_group_pk": self.alert_group.pk, + **kwargs, + } + return json.dumps(data) # Slack block elements allow to pass value as string only (max 2000 chars) diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index d7725847bf..6792689587 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -262,10 +262,12 @@ class SilenceGroupStep( def process_scenario(self, slack_user_identity, slack_team_identity, payload): + value = payload["actions"][0]["selected_option"]["value"] try: - silence_delay = int(payload["actions"][0]["selected_options"][0]["value"]) - except KeyError: - silence_delay = int(payload["actions"][0]["selected_option"]["value"]) + silence_delay = json.loads(value)["delay"] + except TypeError: + # Deprecated handler kept for backward compatibility (so older Slack messages can still be processed) + silence_delay = int(value) alert_group = self.get_alert_group(slack_team_identity, payload) @@ -532,8 +534,14 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): return - invitation_pk = payload["actions"][0]["name"].split("_")[1] - Invitation.stop_invitation(invitation_pk, self.user) + try: + value = json.loads(payload["actions"][0]["value"]) + invitation_id = value["invitation_id"] + except KeyError: + # Deprecated handler kept for backward compatibility (so older Slack messages can still be processed) + invitation_id = payload["actions"][0]["name"].split("_")[1] + + Invitation.stop_invitation(invitation_id, self.user) def process_signal(self, log_record): self.alert_group_slack_service.update_alert_group_slack_message(log_record.invitation.alert_group) diff --git a/engine/apps/slack/tests/test_slack_renderer.py b/engine/apps/slack/tests/test_slack_renderer.py new file mode 100644 index 0000000000..27377ea7fb --- /dev/null +++ b/engine/apps/slack/tests/test_slack_renderer.py @@ -0,0 +1,215 @@ +import json + +import pytest + +from apps.alerts.incident_appearance.renderers.slack_renderer import AlertGroupSlackRenderer +from apps.alerts.models import AlertGroup + + +@pytest.mark.django_db +def test_slack_renderer_acknowledge_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert): + organization = make_organization() + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group=alert_group, raw_request_data={}) + + elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] + + ack_button = elements[0] + assert ack_button["text"]["text"] == "Acknowledge" + assert json.loads(ack_button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} + + +@pytest.mark.django_db +def test_slack_renderer_unacknowledge_button( + make_organization, make_alert_receive_channel, make_alert_group, make_alert +): + organization = make_organization() + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, acknowledged=True) + make_alert(alert_group=alert_group, raw_request_data={}) + + elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] + + ack_button = elements[0] + assert ack_button["text"]["text"] == "Unacknowledge" + assert json.loads(ack_button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} + + +@pytest.mark.django_db +def test_slack_renderer_resolve_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert): + organization = make_organization() + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group=alert_group, raw_request_data={}) + + elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] + + ack_button = elements[1] + assert ack_button["text"]["text"] == "Resolve" + assert json.loads(ack_button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} + + +@pytest.mark.django_db +def test_slack_renderer_unresolve_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert): + organization = make_organization() + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, resolved=True) + make_alert(alert_group=alert_group, raw_request_data={}) + + elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] + + ack_button = elements[0] + assert ack_button["text"]["text"] == "Unresolve" + assert json.loads(ack_button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} + + +@pytest.mark.django_db +def test_slack_renderer_invite_action( + make_organization, make_user, make_alert_receive_channel, make_alert_group, make_alert +): + organization = make_organization() + user = make_user(organization=organization) + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group=alert_group, raw_request_data={}) + + elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] + + ack_button = elements[2] + assert ack_button["placeholder"]["text"] == "Invite..." + assert json.loads(ack_button["options"][0]["value"]) == { + "organization_id": organization.pk, + "alert_group_pk": alert_group.pk, + "user_id": user.pk, + } + + +@pytest.mark.django_db +def test_slack_renderer_stop_invite_button( + make_organization, make_user, make_alert_receive_channel, make_alert_group, make_alert, make_invitation +): + organization = make_organization() + user = make_user(organization=organization) + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group=alert_group, raw_request_data={}) + invitation = make_invitation(alert_group, user, user) + + stop_inviting_action = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[1]["actions"][0] + + assert stop_inviting_action["text"] == f"Stop inviting {user.username}" + assert json.loads(stop_inviting_action["value"]) == { + "organization_id": organization.pk, + "alert_group_pk": alert_group.pk, + "invitation_id": invitation.pk, + } + + +@pytest.mark.django_db +def test_slack_renderer_silence_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert): + organization = make_organization() + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group=alert_group, raw_request_data={}) + + elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] + + silence_button = elements[3] + assert silence_button["placeholder"]["text"] == "Silence" + + values = [json.loads(option["value"]) for option in silence_button["options"]] + assert values == [ + {"organization_id": organization.pk, "alert_group_pk": alert_group.pk, "duration": duration} + for duration, _ in AlertGroup.SILENCE_DELAY_OPTIONS + ] + + +@pytest.mark.django_db +def test_slack_renderer_unsilence_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert): + organization = make_organization() + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, silenced=True) + make_alert(alert_group=alert_group, raw_request_data={}) + + elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] + unsilence_button = elements[3] + + assert unsilence_button["text"]["text"] == "Unsilence" + assert json.loads(unsilence_button["value"]) == { + "organization_id": organization.pk, + "alert_group_pk": alert_group.pk, + } + + +@pytest.mark.django_db +def test_slack_renderer_attach_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert): + organization = make_organization() + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, silenced=True) + make_alert(alert_group=alert_group, raw_request_data={}) + + elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] + unsilence_button = elements[4] + + assert unsilence_button["text"]["text"] == "Attach to ..." + assert json.loads(unsilence_button["value"]) == { + "organization_id": organization.pk, + "alert_group_pk": alert_group.pk, + } + + +@pytest.mark.django_db +def test_slack_renderer_unattach_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert): + organization = make_organization() + alert_receive_channel = make_alert_receive_channel(organization) + + root_alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group=root_alert_group, raw_request_data={}) + + alert_group = make_alert_group(alert_receive_channel, root_alert_group=root_alert_group) + make_alert(alert_group=alert_group, raw_request_data={}) + + unattach_action = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["actions"][0] + + assert unattach_action["text"] == "Unattach" + assert json.loads(unattach_action["value"]) == { + "organization_id": organization.pk, + "alert_group_pk": alert_group.pk, + } + + +@pytest.mark.django_db +def test_slack_renderer_format_alert_button( + make_organization, make_alert_receive_channel, make_alert_group, make_alert +): + organization = make_organization() + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group=alert_group, raw_request_data={}) + + elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] + + ack_button = elements[5] + assert ack_button["text"]["text"] == ":mag: Format Alert" + assert json.loads(ack_button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} + + +@pytest.mark.django_db +def test_slack_renderer_resolution_notes_button( + make_organization, make_alert_receive_channel, make_alert_group, make_alert +): + organization = make_organization() + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group=alert_group, raw_request_data={}) + + elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] + + ack_button = elements[6] + assert ack_button["text"]["text"] == "Add Resolution notes" + assert json.loads(ack_button["value"]) == { + "organization_id": organization.pk, + "alert_group_pk": alert_group.pk, + "resolution_note_window_action": "edit", + } From c8fb6997b37dff6cb17710b70627fdbbfd51a015 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Tue, 30 May 2023 14:56:31 +0100 Subject: [PATCH 09/36] Cleanup --- .../slack/scenarios/alertgroup_appearance.py | 1 - .../apps/slack/scenarios/distribute_alerts.py | 11 ++-- engine/apps/slack/scenarios/step_mixins.py | 5 +- .../test_alert_group_actions.py | 53 +++++++++++++++++++ 4 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py diff --git a/engine/apps/slack/scenarios/alertgroup_appearance.py b/engine/apps/slack/scenarios/alertgroup_appearance.py index 11e3824a54..2600d7352f 100644 --- a/engine/apps/slack/scenarios/alertgroup_appearance.py +++ b/engine/apps/slack/scenarios/alertgroup_appearance.py @@ -29,7 +29,6 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = AlertGroup.all_objects.get(pk=alert_group_pk) if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): return - blocks = [] private_metadata = { "organization_id": self.organization.pk if self.organization else alert_group.organization.pk, diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index 6792689587..12896e7b04 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -261,6 +261,7 @@ class SilenceGroupStep( ACTION_VERBOSE = "silence incident" def process_scenario(self, slack_user_identity, slack_team_identity, payload): + alert_group = self.get_alert_group(slack_team_identity, payload) value = payload["actions"][0]["selected_option"]["value"] try: @@ -269,8 +270,6 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): # Deprecated handler kept for backward compatibility (so older Slack messages can still be processed) silence_delay = int(value) - alert_group = self.get_alert_group(slack_team_identity, payload) - if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.silence_by_user(self.user, silence_delay, action_source=ActionSource.SLACK) @@ -335,8 +334,8 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): if attached_incidents_exists: attached_incidents = alert_group.dependent_alert_groups.all() text = ( - f"Oops! This incident cannot be attached to another one because it already has " - f"attached incidents ({attached_incidents.count()}):\n" + f"Oops! This Alert Group cannot be attached to another one because it already has " + f"attached Alert Group ({attached_incidents.count()}):\n" ) for dependent_alert in attached_incidents: if dependent_alert.slack_permalink: @@ -372,7 +371,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): "type": "section", "text": { "type": "mrkdwn", - "text": "Oops! There is no incidents, available to attach.", + "text": "Oops! There are no Alert Groups available to attach.", }, } ) @@ -441,7 +440,7 @@ def get_select_incidents_blocks(self, alert_group): }, "label": { "type": "plain_text", - "text": "Select incident:", + "text": "Select Alert Group:", "emoji": True, }, } diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 5bcaa12836..f60f8c48f5 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -89,10 +89,7 @@ def _send_denied_message(self, payload): ) -class CheckAlertIsUnarchivedMixin(object): - REQUIRED_PERMISSIONS = [] - ACTION_VERBOSE = "" - +class CheckAlertIsUnarchivedMixin: def check_alert_is_unarchived(self, slack_team_identity, payload, alert_group, warning=True): alert_group_is_unarchived = alert_group.started_at.date() > self.organization.archive_alerts_from if not alert_group_is_unarchived: diff --git a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py new file mode 100644 index 0000000000..a99fb89ee9 --- /dev/null +++ b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py @@ -0,0 +1,53 @@ +from unittest.mock import patch + +import pytest + +from apps.slack.models import SlackMessage +from apps.slack.scenarios.scenario_step import ScenarioStep +from apps.slack.slack_client import SlackClientWithErrorHandling + +ALERT_GROUP_ACTIONS_STEPS = [ + # Acknowledge / Unacknowledge buttons + ScenarioStep.get_step("distribute_alerts", "AcknowledgeGroupStep"), + ScenarioStep.get_step("distribute_alerts", "UnAcknowledgeGroupStep"), + # Resolve / Unresolve buttons + ScenarioStep.get_step("distribute_alerts", "ResolveGroupStep"), + ScenarioStep.get_step("distribute_alerts", "UnResolveGroupStep"), + # Invite / Stop inviting buttons + ScenarioStep.get_step("distribute_alerts", "InviteOtherPersonToIncident"), + ScenarioStep.get_step("distribute_alerts", "StopInvitationProcess"), + # Silence / Unsilence buttons + ScenarioStep.get_step("distribute_alerts", "SilenceGroupStep"), + ScenarioStep.get_step("distribute_alerts", "UnSilenceGroupStep"), + # Attach / Unattach buttons + ScenarioStep.get_step("distribute_alerts", "SelectAttachGroupStep"), + ScenarioStep.get_step("distribute_alerts", "UnAttachGroupStep"), + # Format alert button + ScenarioStep.get_step("alertgroup_appearance", "OpenAlertAppearanceDialogStep"), + # Add resolution notes button + ScenarioStep.get_step("resolution_note", "AddToResolutionNoteStep"), +] + + +@pytest.mark.parametrize("step_class", ALERT_GROUP_ACTIONS_STEPS) +@patch.object( + SlackClientWithErrorHandling, + "api_call", + return_value={"ok": True}, +) +@pytest.mark.django_db +def test_alert_group_actions_slack_message_not_in_db( + mock_slack_api_call, step_class, make_organization_and_user_with_slack_identities +): + organization, _, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + payload = { + "message_ts": "RANDOM_MESSAGE_TS", + "channel": {"id": "RANDOM_CHANNEL_ID"}, + } + + step = step_class(organization=organization, slack_team_identity=slack_team_identity) + + with pytest.raises(SlackMessage.DoesNotExist): # TODO: change this + step.process_scenario(slack_user_identity, slack_team_identity, payload) From 3ef9f70ba5c5ce6d427ea803915a3ff8d0877f2b Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Tue, 30 May 2023 15:02:20 +0100 Subject: [PATCH 10/36] fix test --- engine/apps/slack/tests/test_slack_renderer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/apps/slack/tests/test_slack_renderer.py b/engine/apps/slack/tests/test_slack_renderer.py index 27377ea7fb..9de3f94c05 100644 --- a/engine/apps/slack/tests/test_slack_renderer.py +++ b/engine/apps/slack/tests/test_slack_renderer.py @@ -120,8 +120,8 @@ def test_slack_renderer_silence_button(make_organization, make_alert_receive_cha values = [json.loads(option["value"]) for option in silence_button["options"]] assert values == [ - {"organization_id": organization.pk, "alert_group_pk": alert_group.pk, "duration": duration} - for duration, _ in AlertGroup.SILENCE_DELAY_OPTIONS + {"organization_id": organization.pk, "alert_group_pk": alert_group.pk, "delay": delay} + for delay, _ in AlertGroup.SILENCE_DELAY_OPTIONS ] From 2483157b25bea16a87e8355286204d00f66dd34c Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Tue, 30 May 2023 15:06:37 +0100 Subject: [PATCH 11/36] Tidy ACTION_VERBOSE --- .../slack/scenarios/alertgroup_appearance.py | 2 +- .../apps/slack/scenarios/distribute_alerts.py | 24 +++++++++---------- engine/apps/slack/scenarios/step_mixins.py | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/engine/apps/slack/scenarios/alertgroup_appearance.py b/engine/apps/slack/scenarios/alertgroup_appearance.py index 2600d7352f..34c6e28bfb 100644 --- a/engine/apps/slack/scenarios/alertgroup_appearance.py +++ b/engine/apps/slack/scenarios/alertgroup_appearance.py @@ -10,7 +10,7 @@ class OpenAlertAppearanceDialogStep(CheckAlertIsUnarchivedMixin, AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "open Alert Appearance" + ACTION_VERBOSE = "format Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): AlertGroup = apps.get_model("alerts", "AlertGroup") diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index 12896e7b04..da81acb4b6 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -221,7 +221,7 @@ class InviteOtherPersonToIncident( ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "invite to incident" + ACTION_VERBOSE = "invite to Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): User = apps.get_model("user_management", "User") @@ -258,7 +258,7 @@ class SilenceGroupStep( ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "silence incident" + ACTION_VERBOSE = "silence Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = self.get_alert_group(slack_team_identity, payload) @@ -284,7 +284,7 @@ class UnSilenceGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "unsilence incident" + ACTION_VERBOSE = "unsilence Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): @@ -303,7 +303,7 @@ class SelectAttachGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "Select Alert Group for Attaching to" + ACTION_VERBOSE = "select Alert Group for attach" def process_scenario(self, slack_user_identity, slack_team_identity, payload): AlertGroup = apps.get_model("alerts", "AlertGroup") @@ -454,7 +454,7 @@ class AttachGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "Attach incident" + ACTION_VERBOSE = "attach Alert Group" def process_signal(self, log_record): alert_group = log_record.alert_group @@ -512,7 +512,7 @@ class UnAttachGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "Unattach incident" + ACTION_VERBOSE = "unattach Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = self.get_alert_group(slack_team_identity, payload) @@ -612,7 +612,7 @@ class ResolveGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "resolve incident" + ACTION_VERBOSE = "resolve Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): ResolutionNoteModalStep = scenario_step.ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep") @@ -654,7 +654,7 @@ class UnResolveGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "unresolve incident" + ACTION_VERBOSE = "unresolve Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = self.get_alert_group(slack_team_identity, payload) @@ -672,7 +672,7 @@ class AcknowledgeGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "acknowledge incident" + ACTION_VERBOSE = "acknowledge Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = self.get_alert_group(slack_team_identity, payload) @@ -693,7 +693,7 @@ class UnAcknowledgeGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "unacknowledge incident" + ACTION_VERBOSE = "unacknowledge Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = self.get_alert_group(slack_team_identity, payload) @@ -888,7 +888,7 @@ def process_signal(self, log_record): class WipeGroupStep(scenario_step.ScenarioStep): - ACTION_VERBOSE = "wipe incident" + ACTION_VERBOSE = "wipe Alert Group" def process_signal(self, log_record): alert_group = log_record.alert_group @@ -899,7 +899,7 @@ def process_signal(self, log_record): class DeleteGroupStep(scenario_step.ScenarioStep): - ACTION_VERBOSE = "delete incident" + ACTION_VERBOSE = "delete Alert Group" def process_signal(self, log_record): alert_group = log_record.alert_group diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index f60f8c48f5..0b11ce73ca 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -17,7 +17,7 @@ class AlertGroupActionsMixin: """ REQUIRED_PERMISSIONS = [] - ACTION_VERBOSE = "" + ACTION_VERBOSE = "perform action" def process_scenario(self, slack_user_identity, slack_team_identity, payload): if self._check_membership(): From 1aa8af1b9643adda6104fdd3bcfe5eecfc683f45 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Tue, 30 May 2023 17:26:52 +0100 Subject: [PATCH 12/36] Add tests on get_alert_group --- engine/apps/slack/scenarios/step_mixins.py | 27 ++++++ .../test_alert_group_actions.py | 85 +++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 0b11ce73ca..d7a1e5ab3c 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -1,7 +1,9 @@ +import json import logging from django.core.exceptions import ObjectDoesNotExist +from apps.alerts.models import AlertGroup from apps.api.permissions import user_is_authorized from apps.slack.models import SlackMessage @@ -26,7 +28,32 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): self._send_denied_message(payload) def get_alert_group(self, slack_team_identity, payload): + # TODO: comment + action = payload["actions"][0] + action_type = action["type"] + + if action_type == "button": + value_string = action["value"] + elif action_type == "static_select": + value_string = action["selected_option"]["value"] + else: + raise ValueError(f"Unexpected action type: {action_type}") + + try: + value = json.loads(value_string) + except (TypeError, json.JSONDecodeError): + return self._deprecated_get_alert_group(slack_team_identity, payload) + + try: + alert_group_pk = value["alert_group_pk"] + except (KeyError, TypeError): + return self._deprecated_get_alert_group(slack_team_identity, payload) + + return AlertGroup.all_objects.get(pk=alert_group_pk) + + def _deprecated_get_alert_group(self, slack_team_identity, payload): + # TODO: comment message_ts = payload.get("message_ts") or payload["container"]["message_ts"] # interactive message or block channel_id = payload["channel"]["id"] diff --git a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py index a99fb89ee9..ea54dd865e 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py @@ -1,11 +1,18 @@ +import json from unittest.mock import patch import pytest from apps.slack.models import SlackMessage from apps.slack.scenarios.scenario_step import ScenarioStep +from apps.slack.scenarios.step_mixins import AlertGroupActionsMixin from apps.slack.slack_client import SlackClientWithErrorHandling + +class TestScenario(AlertGroupActionsMixin, ScenarioStep): + pass + + ALERT_GROUP_ACTIONS_STEPS = [ # Acknowledge / Unacknowledge buttons ScenarioStep.get_step("distribute_alerts", "AcknowledgeGroupStep"), @@ -51,3 +58,81 @@ def test_alert_group_actions_slack_message_not_in_db( with pytest.raises(SlackMessage.DoesNotExist): # TODO: change this step.process_scenario(slack_user_identity, slack_team_identity, payload) + + +@pytest.mark.django_db +def test_get_alert_group_button( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group +): + organization, user, slack_team_identity, _ = make_organization_and_user_with_slack_identities() + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ] + } + + step = TestScenario(organization=organization, user=user, slack_team_identity=slack_team_identity) + result = step.get_alert_group(slack_team_identity, payload) + + assert alert_group == result + + +@pytest.mark.django_db +def test_get_alert_group_static_select( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group +): + organization, user, slack_team_identity, _ = make_organization_and_user_with_slack_identities() + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + + payload = { + "actions": [ + { + "type": "static_select", + "selected_option": { + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}) + }, + } + ] + } + + step = TestScenario(organization=organization, user=user, slack_team_identity=slack_team_identity) + result = step.get_alert_group(slack_team_identity, payload) + + assert alert_group == result + + +@pytest.mark.django_db +def test_get_alert_group_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, _ = make_organization_and_user_with_slack_identities() + + 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(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [{"type": "button", "value": "RANDOM_VALUE"}], + } + + step = TestScenario(organization=organization, user=user, slack_team_identity=slack_team_identity) + result = step.get_alert_group(slack_team_identity, payload) + + assert alert_group == result From 1ef77987e0ca22cacb821ae6fdb29e6eec8abee2 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Tue, 30 May 2023 17:44:16 +0100 Subject: [PATCH 13/36] comment --- engine/apps/slack/scenarios/distribute_alerts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index da81acb4b6..ae855b0676 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -488,7 +488,8 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): AttachGroupStep.routing_uid() ]["selected_option"]["value"] root_alert_group = AlertGroup.all_objects.get(pk=root_alert_group_pk) - # old version of attach selection by dropdown + # Deprecated handler kept for backward compatibility (so older Slack messages can still be processed) + # Deprecated since at least 28/01/2021 else: try: root_alert_group_pk = int(payload["actions"][0]["selected_options"][0]["value"]) From 879a99ba5f862896826c4968ec7cb5c7b0016bbf Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Tue, 30 May 2023 21:06:26 +0100 Subject: [PATCH 14/36] more tests --- .../slack/scenarios/alertgroup_appearance.py | 18 +- .../apps/slack/scenarios/distribute_alerts.py | 8 +- .../apps/slack/scenarios/resolution_note.py | 20 +- .../test_distribute_alerts.py | 811 ++++++++++++++++++ 4 files changed, 829 insertions(+), 28 deletions(-) diff --git a/engine/apps/slack/scenarios/alertgroup_appearance.py b/engine/apps/slack/scenarios/alertgroup_appearance.py index 34c6e28bfb..c19cc82b44 100644 --- a/engine/apps/slack/scenarios/alertgroup_appearance.py +++ b/engine/apps/slack/scenarios/alertgroup_appearance.py @@ -13,27 +13,15 @@ class OpenAlertAppearanceDialogStep(CheckAlertIsUnarchivedMixin, AlertGroupActio ACTION_VERBOSE = "format Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - AlertGroup = apps.get_model("alerts", "AlertGroup") - - try: - message_ts = payload["message_ts"] - except KeyError: - message_ts = payload["container"]["message_ts"] + alert_group = self.get_alert_group(slack_team_identity, payload) - try: - alert_group_pk = payload["actions"][0]["action_id"].split("_")[1] - except (KeyError, IndexError): - value = json.loads(payload["actions"][0]["value"]) - alert_group_pk = value["alert_group_pk"] - - alert_group = AlertGroup.all_objects.get(pk=alert_group_pk) if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): return private_metadata = { "organization_id": self.organization.pk if self.organization else alert_group.organization.pk, - "alert_group_pk": alert_group_pk, - "message_ts": message_ts, + "alert_group_pk": alert_group.pk, + "message_ts": payload.get("message_ts") or payload["container"]["message_ts"], } alert_receive_channel = alert_group.channel diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index ae855b0676..58b9ce9ef0 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -306,13 +306,11 @@ class SelectAttachGroupStep( ACTION_VERBOSE = "select Alert Group for attach" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - AlertGroup = apps.get_model("alerts", "AlertGroup") - value = json.loads(payload["actions"][0]["value"]) - alert_group_pk = value.get("alert_group_pk") - alert_group = AlertGroup.all_objects.get(pk=alert_group_pk) + alert_group = self.get_alert_group(slack_team_identity, payload) if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): return + blocks = [] view = { "callback_id": AttachGroupStep.routing_uid(), @@ -325,7 +323,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): "private_metadata": json.dumps( { "organization_id": self.organization.pk if self.organization else alert_group.organization.pk, - "alert_group_pk": alert_group_pk, + "alert_group_pk": alert_group.pk, } ), "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, diff --git a/engine/apps/slack/scenarios/resolution_note.py b/engine/apps/slack/scenarios/resolution_note.py index e7316d40a9..e927279681 100644 --- a/engine/apps/slack/scenarios/resolution_note.py +++ b/engine/apps/slack/scenarios/resolution_note.py @@ -10,7 +10,7 @@ from apps.user_management.models import User from common.api_helpers.utils import create_engine_url -from .step_mixins import CheckAlertIsUnarchivedMixin +from .step_mixins import AlertGroupActionsMixin, CheckAlertIsUnarchivedMixin logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -372,18 +372,22 @@ def get_resolution_note_blocks(self, resolution_note): return blocks -class ResolutionNoteModalStep(CheckAlertIsUnarchivedMixin, scenario_step.ScenarioStep): +class ResolutionNoteModalStep(CheckAlertIsUnarchivedMixin, AlertGroupActionsMixin, scenario_step.ScenarioStep): RESOLUTION_NOTE_TEXT_BLOCK_ID = "resolution_note_text" RESOLUTION_NOTE_MESSAGES_MAX_COUNT = 25 def process_scenario(self, slack_user_identity, slack_team_identity, payload, data=None): - AlertGroup = apps.get_model("alerts", "AlertGroup") + if data: + AlertGroup = apps.get_model("alerts", "AlertGroup") + alert_group = AlertGroup.all_objects.get(pk=data["alert_group_pk"]) + else: + alert_group = self.get_alert_group(slack_team_identity, payload) + value = data or json.loads(payload["actions"][0]["value"]) - resolution_note_window_action = value.get("resolution_note_window_action", "") or value.get("action_value", "") - alert_group_pk = value.get("alert_group_pk") action_resolve = value.get("action_resolve", False) + resolution_note_window_action = value.get("resolution_note_window_action", "") or value.get("action_value", "") + channel_id = payload["channel"]["id"] if "channel" in payload else None - alert_group = AlertGroup.all_objects.get(pk=alert_group_pk) if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): return @@ -413,7 +417,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload, da "private_metadata": json.dumps( { "organization_id": self.organization.pk if self.organization else alert_group.organization.pk, - "alert_group_pk": alert_group_pk, + "alert_group_pk": alert_group.pk, } ), } @@ -431,7 +435,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload, da # Ignore "not_found" error, it means that the view was closed by user before the update request. # It doesn't disrupt the user experience. logger.debug( - f"API call to views.update failed for alert group {alert_group_pk}, error: not_found. " + f"API call to views.update failed for alert group {alert_group.pk}, error: not_found. " f"Most likely the view was closed by user before the request was processed. " ) else: diff --git a/engine/apps/slack/tests/test_scenario_steps/test_distribute_alerts.py b/engine/apps/slack/tests/test_scenario_steps/test_distribute_alerts.py index 62ebe71fba..f7164d487c 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_distribute_alerts.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_distribute_alerts.py @@ -1,3 +1,4 @@ +import json from unittest.mock import patch import pytest @@ -33,3 +34,813 @@ def test_restricted_action_error( assert alert_group.slack_message is None assert SlackMessage.objects.count() == 0 assert not alert.delivered + + +@pytest.mark.django_db +def test_step_acknowledge( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "AcknowledgeGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.acknowledged is True + + +@pytest.mark.django_db +def test_step_acknowledge_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "AcknowledgeGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.acknowledged is True + + +@pytest.mark.django_db +def test_step_unacknowledge( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, acknowledged=True) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnAcknowledgeGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.acknowledged is False + + +@pytest.mark.django_db +def test_step_unacknowledge_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, acknowledged=True) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnAcknowledgeGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.acknowledged is False + + +@pytest.mark.django_db +def test_step_resolve( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "ResolveGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.resolved is True + + +@pytest.mark.django_db +def test_step_resolve_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "ResolveGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.resolved is True + + +@pytest.mark.django_db +def test_step_unresolve( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, resolved=True) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnResolveGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.resolved is False + + +@pytest.mark.django_db +def test_step_unresolve_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, resolved=True) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnResolveGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.resolved is False + + +@pytest.mark.django_db +def test_step_invite( + make_organization_and_user_with_slack_identities, + make_user, + make_alert_receive_channel, + make_alert_group, + make_alert, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + second_user = make_user(organization=organization) + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "static_select", + "selected_option": { + "value": json.dumps( + { + "organization_id": organization.pk, + "alert_group_pk": alert_group.pk, + "user_id": second_user.pk, + } + ) + }, + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "InviteOtherPersonToIncident") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.invitations.count() == 1 + + invitation = alert_group.invitations.first() + assert invitation.author == user + assert invitation.invitee == second_user + + +@pytest.mark.django_db +def test_step_invite_deprecated( + make_organization_and_user_with_slack_identities, + make_user, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + second_user = make_user(organization=organization) + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "static_select", + "selected_option": {"value": json.dumps({"user_id": second_user.pk})}, + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "InviteOtherPersonToIncident") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.invitations.count() == 1 + + invitation = alert_group.invitations.first() + assert invitation.author == user + assert invitation.invitee == second_user + + +@pytest.mark.django_db +def test_step_stop_invite( + make_organization_and_user_with_slack_identities, + make_user, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_invitation, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + second_user = make_user(organization=organization) + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + invitation = make_invitation(alert_group, user, second_user) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps( + { + "organization_id": organization.pk, + "alert_group_pk": alert_group.pk, + "invitation_id": invitation.pk, + } + ), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "StopInvitationProcess") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + invitation.refresh_from_db() + assert invitation.is_active is False + + +@pytest.mark.django_db +def test_step_stop_invite_deprecated( + make_organization_and_user_with_slack_identities, + make_user, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, + make_invitation, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + second_user = make_user(organization=organization) + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + invitation = make_invitation(alert_group, user, second_user) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "name": f"StopInvitationProcess_{invitation.pk}", + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "StopInvitationProcess") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + invitation.refresh_from_db() + assert invitation.is_active is False + + +@pytest.mark.django_db +def test_step_silence( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, silenced=False) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "static_select", + "selected_option": { + "value": json.dumps( + {"organization_id": organization.pk, "alert_group_pk": alert_group.pk, "delay": 1800} + ) + }, + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "SilenceGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.silenced is True + + +@pytest.mark.django_db +def test_step_silence_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, silenced=False) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "static_select", + "selected_option": {"value": "1800"}, + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "SilenceGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.silenced is True + + +@pytest.mark.django_db +def test_step_unsilence( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, silenced=True) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnSilenceGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.silenced is False + + +@pytest.mark.django_db +def test_step_unsilence_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, silenced=True) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnSilenceGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.silenced is False + + +@pytest.mark.django_db +def test_step_select_attach( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + payload = { + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "SelectAttachGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + # TODO: assert + + +@pytest.mark.django_db +def test_step_select_attach_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "SelectAttachGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + # TODO: assert + + +@pytest.mark.django_db +def test_step_unattach( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + root_alert_group = make_alert_group(alert_receive_channel) + alert_group = make_alert_group(alert_receive_channel, root_alert_group=root_alert_group) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnAttachGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.root_alert_group is None + + +@pytest.mark.django_db +def test_step_unattach_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + root_alert_group = make_alert_group(alert_receive_channel) + alert_group = make_alert_group(alert_receive_channel, root_alert_group=root_alert_group) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnAttachGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.root_alert_group is None + + +@pytest.mark.django_db +def test_step_format_alert( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + payload = { + "message_ts": "RANDOM_TS", + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("alertgroup_appearance", "OpenAlertAppearanceDialogStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + # TODO: assert + + +@pytest.mark.django_db +def test_step_format_alert_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": str(alert_group.pk)}), + } + ], + } + + step_class = ScenarioStep.get_step("alertgroup_appearance", "OpenAlertAppearanceDialogStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + # TODO: assert + + +@pytest.mark.django_db +def test_step_resolution_note( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + payload = { + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps( + { + "organization_id": organization.pk, + "alert_group_pk": alert_group.pk, + "resolution_note_window_action": "edit", + } + ), + } + ], + } + + step_class = ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + # TODO: assert From a48585c4ef0364cf9e9e9ae1430f75d5d727b64f Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Tue, 30 May 2023 21:13:42 +0100 Subject: [PATCH 15/36] more tests --- .../test_alert_group_actions.py | 814 +++++++++++++++++- .../test_distribute_alerts.py | 811 ----------------- .../test_resolution_note.py | 11 +- 3 files changed, 823 insertions(+), 813 deletions(-) diff --git a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py index ea54dd865e..a2d04616fb 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py @@ -32,7 +32,7 @@ class TestScenario(AlertGroupActionsMixin, ScenarioStep): # Format alert button ScenarioStep.get_step("alertgroup_appearance", "OpenAlertAppearanceDialogStep"), # Add resolution notes button - ScenarioStep.get_step("resolution_note", "AddToResolutionNoteStep"), + ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep"), ] @@ -52,6 +52,8 @@ def test_alert_group_actions_slack_message_not_in_db( payload = { "message_ts": "RANDOM_MESSAGE_TS", "channel": {"id": "RANDOM_CHANNEL_ID"}, + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [{"type": "button", "value": json.dumps({"organization_id": organization.pk})}], } step = step_class(organization=organization, slack_team_identity=slack_team_identity) @@ -136,3 +138,813 @@ def test_get_alert_group_deprecated( result = step.get_alert_group(slack_team_identity, payload) assert alert_group == result + + +@pytest.mark.django_db +def test_step_acknowledge( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "AcknowledgeGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.acknowledged is True + + +@pytest.mark.django_db +def test_step_acknowledge_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "AcknowledgeGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.acknowledged is True + + +@pytest.mark.django_db +def test_step_unacknowledge( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, acknowledged=True) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnAcknowledgeGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.acknowledged is False + + +@pytest.mark.django_db +def test_step_unacknowledge_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, acknowledged=True) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnAcknowledgeGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.acknowledged is False + + +@pytest.mark.django_db +def test_step_resolve( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "ResolveGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.resolved is True + + +@pytest.mark.django_db +def test_step_resolve_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "ResolveGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.resolved is True + + +@pytest.mark.django_db +def test_step_unresolve( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, resolved=True) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnResolveGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.resolved is False + + +@pytest.mark.django_db +def test_step_unresolve_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, resolved=True) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnResolveGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.resolved is False + + +@pytest.mark.django_db +def test_step_invite( + make_organization_and_user_with_slack_identities, + make_user, + make_alert_receive_channel, + make_alert_group, + make_alert, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + second_user = make_user(organization=organization) + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "static_select", + "selected_option": { + "value": json.dumps( + { + "organization_id": organization.pk, + "alert_group_pk": alert_group.pk, + "user_id": second_user.pk, + } + ) + }, + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "InviteOtherPersonToIncident") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.invitations.count() == 1 + + invitation = alert_group.invitations.first() + assert invitation.author == user + assert invitation.invitee == second_user + + +@pytest.mark.django_db +def test_step_invite_deprecated( + make_organization_and_user_with_slack_identities, + make_user, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + second_user = make_user(organization=organization) + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "static_select", + "selected_option": {"value": json.dumps({"user_id": second_user.pk})}, + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "InviteOtherPersonToIncident") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.invitations.count() == 1 + + invitation = alert_group.invitations.first() + assert invitation.author == user + assert invitation.invitee == second_user + + +@pytest.mark.django_db +def test_step_stop_invite( + make_organization_and_user_with_slack_identities, + make_user, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_invitation, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + second_user = make_user(organization=organization) + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + invitation = make_invitation(alert_group, user, second_user) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps( + { + "organization_id": organization.pk, + "alert_group_pk": alert_group.pk, + "invitation_id": invitation.pk, + } + ), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "StopInvitationProcess") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + invitation.refresh_from_db() + assert invitation.is_active is False + + +@pytest.mark.django_db +def test_step_stop_invite_deprecated( + make_organization_and_user_with_slack_identities, + make_user, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, + make_invitation, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + second_user = make_user(organization=organization) + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + invitation = make_invitation(alert_group, user, second_user) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "name": f"StopInvitationProcess_{invitation.pk}", + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "StopInvitationProcess") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + invitation.refresh_from_db() + assert invitation.is_active is False + + +@pytest.mark.django_db +def test_step_silence( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, silenced=False) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "static_select", + "selected_option": { + "value": json.dumps( + {"organization_id": organization.pk, "alert_group_pk": alert_group.pk, "delay": 1800} + ) + }, + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "SilenceGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.silenced is True + + +@pytest.mark.django_db +def test_step_silence_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, silenced=False) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "static_select", + "selected_option": {"value": "1800"}, + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "SilenceGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.silenced is True + + +@pytest.mark.django_db +def test_step_unsilence( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, silenced=True) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnSilenceGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.silenced is False + + +@pytest.mark.django_db +def test_step_unsilence_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel, silenced=True) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnSilenceGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.silenced is False + + +@pytest.mark.django_db +def test_step_select_attach( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + payload = { + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "SelectAttachGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + # TODO: assert + + +@pytest.mark.django_db +def test_step_select_attach_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "SelectAttachGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + # TODO: assert + + +@pytest.mark.django_db +def test_step_unattach( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + root_alert_group = make_alert_group(alert_receive_channel) + alert_group = make_alert_group(alert_receive_channel, root_alert_group=root_alert_group) + make_alert(alert_group, raw_request_data={}) + + payload = { + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnAttachGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.root_alert_group is None + + +@pytest.mark.django_db +def test_step_unattach_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + root_alert_group = make_alert_group(alert_receive_channel) + alert_group = make_alert_group(alert_receive_channel, root_alert_group=root_alert_group) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("distribute_alerts", "UnAttachGroupStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group.root_alert_group is None + + +@pytest.mark.django_db +def test_step_format_alert( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + payload = { + "message_ts": "RANDOM_TS", + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step_class = ScenarioStep.get_step("alertgroup_appearance", "OpenAlertAppearanceDialogStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + # TODO: assert + + +@pytest.mark.django_db +def test_step_format_alert_deprecated( + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + slack_channel = make_slack_channel(slack_team_identity) + slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + + payload = { + "message_ts": slack_message.slack_id, + "channel": {"id": slack_channel.slack_id}, + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": str(alert_group.pk)}), + } + ], + } + + step_class = ScenarioStep.get_step("alertgroup_appearance", "OpenAlertAppearanceDialogStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + # TODO: assert + + +@pytest.mark.django_db +def test_step_resolution_note( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + make_alert(alert_group, raw_request_data={}) + + payload = { + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps( + { + "organization_id": organization.pk, + "alert_group_pk": alert_group.pk, + "resolution_note_window_action": "edit", + } + ), + } + ], + } + + step_class = ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep") + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + # TODO: assert diff --git a/engine/apps/slack/tests/test_scenario_steps/test_distribute_alerts.py b/engine/apps/slack/tests/test_scenario_steps/test_distribute_alerts.py index f7164d487c..62ebe71fba 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_distribute_alerts.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_distribute_alerts.py @@ -1,4 +1,3 @@ -import json from unittest.mock import patch import pytest @@ -34,813 +33,3 @@ def test_restricted_action_error( assert alert_group.slack_message is None assert SlackMessage.objects.count() == 0 assert not alert.delivered - - -@pytest.mark.django_db -def test_step_acknowledge( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "AcknowledgeGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.acknowledged is True - - -@pytest.mark.django_db -def test_step_acknowledge_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "AcknowledgeGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.acknowledged is True - - -@pytest.mark.django_db -def test_step_unacknowledge( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, acknowledged=True) - make_alert(alert_group, raw_request_data={}) - - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "UnAcknowledgeGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.acknowledged is False - - -@pytest.mark.django_db -def test_step_unacknowledge_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, acknowledged=True) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "UnAcknowledgeGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.acknowledged is False - - -@pytest.mark.django_db -def test_step_resolve( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "ResolveGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.resolved is True - - -@pytest.mark.django_db -def test_step_resolve_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "ResolveGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.resolved is True - - -@pytest.mark.django_db -def test_step_unresolve( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, resolved=True) - make_alert(alert_group, raw_request_data={}) - - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "UnResolveGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.resolved is False - - -@pytest.mark.django_db -def test_step_unresolve_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, resolved=True) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "UnResolveGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.resolved is False - - -@pytest.mark.django_db -def test_step_invite( - make_organization_and_user_with_slack_identities, - make_user, - make_alert_receive_channel, - make_alert_group, - make_alert, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - second_user = make_user(organization=organization) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - payload = { - "actions": [ - { - "type": "static_select", - "selected_option": { - "value": json.dumps( - { - "organization_id": organization.pk, - "alert_group_pk": alert_group.pk, - "user_id": second_user.pk, - } - ) - }, - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "InviteOtherPersonToIncident") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.invitations.count() == 1 - - invitation = alert_group.invitations.first() - assert invitation.author == user - assert invitation.invitee == second_user - - -@pytest.mark.django_db -def test_step_invite_deprecated( - make_organization_and_user_with_slack_identities, - make_user, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - second_user = make_user(organization=organization) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "static_select", - "selected_option": {"value": json.dumps({"user_id": second_user.pk})}, - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "InviteOtherPersonToIncident") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.invitations.count() == 1 - - invitation = alert_group.invitations.first() - assert invitation.author == user - assert invitation.invitee == second_user - - -@pytest.mark.django_db -def test_step_stop_invite( - make_organization_and_user_with_slack_identities, - make_user, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_invitation, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - second_user = make_user(organization=organization) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - invitation = make_invitation(alert_group, user, second_user) - - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps( - { - "organization_id": organization.pk, - "alert_group_pk": alert_group.pk, - "invitation_id": invitation.pk, - } - ), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "StopInvitationProcess") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - invitation.refresh_from_db() - assert invitation.is_active is False - - -@pytest.mark.django_db -def test_step_stop_invite_deprecated( - make_organization_and_user_with_slack_identities, - make_user, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, - make_invitation, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - second_user = make_user(organization=organization) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - invitation = make_invitation(alert_group, user, second_user) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "name": f"StopInvitationProcess_{invitation.pk}", - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "StopInvitationProcess") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - invitation.refresh_from_db() - assert invitation.is_active is False - - -@pytest.mark.django_db -def test_step_silence( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, silenced=False) - make_alert(alert_group, raw_request_data={}) - - payload = { - "actions": [ - { - "type": "static_select", - "selected_option": { - "value": json.dumps( - {"organization_id": organization.pk, "alert_group_pk": alert_group.pk, "delay": 1800} - ) - }, - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "SilenceGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.silenced is True - - -@pytest.mark.django_db -def test_step_silence_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, silenced=False) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "static_select", - "selected_option": {"value": "1800"}, - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "SilenceGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.silenced is True - - -@pytest.mark.django_db -def test_step_unsilence( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, silenced=True) - make_alert(alert_group, raw_request_data={}) - - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "UnSilenceGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.silenced is False - - -@pytest.mark.django_db -def test_step_unsilence_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, silenced=True) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "UnSilenceGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.silenced is False - - -@pytest.mark.django_db -def test_step_select_attach( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - payload = { - "trigger_id": "RANDOM_TRIGGER_ID", - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "SelectAttachGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - # TODO: assert - - -@pytest.mark.django_db -def test_step_select_attach_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "trigger_id": "RANDOM_TRIGGER_ID", - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "SelectAttachGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - # TODO: assert - - -@pytest.mark.django_db -def test_step_unattach( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - root_alert_group = make_alert_group(alert_receive_channel) - alert_group = make_alert_group(alert_receive_channel, root_alert_group=root_alert_group) - make_alert(alert_group, raw_request_data={}) - - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "UnAttachGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.root_alert_group is None - - -@pytest.mark.django_db -def test_step_unattach_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - root_alert_group = make_alert_group(alert_receive_channel) - alert_group = make_alert_group(alert_receive_channel, root_alert_group=root_alert_group) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "UnAttachGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.root_alert_group is None - - -@pytest.mark.django_db -def test_step_format_alert( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - payload = { - "message_ts": "RANDOM_TS", - "trigger_id": "RANDOM_TRIGGER_ID", - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("alertgroup_appearance", "OpenAlertAppearanceDialogStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - # TODO: assert - - -@pytest.mark.django_db -def test_step_format_alert_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "trigger_id": "RANDOM_TRIGGER_ID", - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": str(alert_group.pk)}), - } - ], - } - - step_class = ScenarioStep.get_step("alertgroup_appearance", "OpenAlertAppearanceDialogStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - # TODO: assert - - -@pytest.mark.django_db -def test_step_resolution_note( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - payload = { - "trigger_id": "RANDOM_TRIGGER_ID", - "actions": [ - { - "type": "button", - "value": json.dumps( - { - "organization_id": organization.pk, - "alert_group_pk": alert_group.pk, - "resolution_note_window_action": "edit", - } - ), - } - ], - } - - step_class = ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - # TODO: assert diff --git a/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py b/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py index fb1ac4f3af..12363de3ef 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py @@ -203,7 +203,16 @@ def test_resolution_notes_modal_closed_before_update( "trigger_id": "TEST", "view": {"id": "TEST"}, "actions": [ - {"value": json.dumps({"alert_group_pk": alert_group.pk, "resolution_note_window_action": "update"})} + { + "type": "button", + "value": json.dumps( + { + "organization_id": organization.pk, + "alert_group_pk": alert_group.pk, + "resolution_note_window_action": "update", + } + ), + } ], } From 2551655f240ad7591321cd5480285c2ecbf7dd8b Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 11:42:43 +0100 Subject: [PATCH 16/36] more tests --- .../test_alert_group_actions.py | 1043 +++++++---------- 1 file changed, 454 insertions(+), 589 deletions(-) diff --git a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py index a2d04616fb..8d6539a6f9 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py @@ -36,6 +36,44 @@ class TestScenario(AlertGroupActionsMixin, ScenarioStep): ] +ORGANIZATION_ID = 42 +ALERT_GROUP_ID = 24 +SLACK_MESSAGE_TS = "RANDOM_MESSAGE_TS" +SLACK_CHANNEL_ID = "RANDOM_CHANNEL_ID" +USER_ID = 56 +INVITATION_ID = 78 + + +def _get_payload(action_type="button", **kwargs): + """ + Utility function to generate payload to be used by scenario steps. + """ + if action_type == "button": + return { + "actions": [ + { + "type": "button", + "value": json.dumps( + {"organization_id": ORGANIZATION_ID, "alert_group_pk": ALERT_GROUP_ID, **kwargs} + ), + } + ], + } + elif action_type == "static_select": + return { + "actions": [ + { + "type": "static_select", + "selected_option": { + "value": json.dumps( + {"organization_id": ORGANIZATION_ID, "alert_group_pk": ALERT_GROUP_ID, **kwargs} + ) + }, + } + ], + } + + @pytest.mark.parametrize("step_class", ALERT_GROUP_ACTIONS_STEPS) @patch.object( SlackClientWithErrorHandling, @@ -140,25 +178,47 @@ def test_get_alert_group_deprecated( assert alert_group == result +@pytest.mark.parametrize( + "payload", + [ + _get_payload(), + # deprecated payload shape, but still supported to handle older Slack messages + { + "message_ts": SLACK_MESSAGE_TS, + "channel": {"id": SLACK_CHANNEL_ID}, + "actions": [{"type": "button", "value": json.dumps({"organization_id": ORGANIZATION_ID})}], + }, + ], +) @pytest.mark.django_db def test_step_acknowledge( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert + payload, + make_organization, + make_slack_team_identity, + make_user, + make_slack_user_identity, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, ): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + slack_team_identity = make_slack_team_identity() + slack_user_identity = make_slack_user_identity(slack_team_identity=slack_team_identity) + slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) + + organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) + alert_group = make_alert_group(alert_receive_channel, acknowledged=False, pk=ALERT_GROUP_ID) make_alert(alert_group, raw_request_data={}) - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } + slack_message = make_slack_message( + alert_group=alert_group, channel_id=slack_channel.slack_id, slack_id=SLACK_MESSAGE_TS + ) + slack_message.get_alert_group() # fix FKs step_class = ScenarioStep.get_step("distribute_alerts", "AcknowledgeGroupStep") step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) @@ -168,752 +228,555 @@ def test_step_acknowledge( assert alert_group.acknowledged is True +@pytest.mark.parametrize( + "payload", + [ + _get_payload(), + # deprecated payload shape, but still supported to handle older Slack messages + { + "message_ts": SLACK_MESSAGE_TS, + "channel": {"id": SLACK_CHANNEL_ID}, + "actions": [{"type": "button", "value": json.dumps({"organization_id": ORGANIZATION_ID})}], + }, + ], +) @pytest.mark.django_db -def test_step_acknowledge_deprecated( - make_organization_and_user_with_slack_identities, +def test_step_unacknowledge( + payload, + make_organization, + make_slack_team_identity, + make_user, + make_slack_user_identity, make_alert_receive_channel, make_alert_group, make_alert, make_slack_channel, make_slack_message, ): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + slack_team_identity = make_slack_team_identity() + slack_user_identity = make_slack_user_identity(slack_team_identity=slack_team_identity) + slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) + + organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) + alert_group = make_alert_group(alert_receive_channel, acknowledged=True, pk=ALERT_GROUP_ID) make_alert(alert_group, raw_request_data={}) - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } + slack_message = make_slack_message( + alert_group=alert_group, channel_id=slack_channel.slack_id, slack_id=SLACK_MESSAGE_TS + ) + slack_message.get_alert_group() # fix FKs - step_class = ScenarioStep.get_step("distribute_alerts", "AcknowledgeGroupStep") + step_class = ScenarioStep.get_step("distribute_alerts", "UnAcknowledgeGroupStep") step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) step.process_scenario(slack_user_identity, slack_team_identity, payload) alert_group.refresh_from_db() - assert alert_group.acknowledged is True + assert alert_group.acknowledged is False +@pytest.mark.parametrize( + "payload", + [ + _get_payload(), + # deprecated payload shape, but still supported to handle older Slack messages + { + "message_ts": SLACK_MESSAGE_TS, + "channel": {"id": SLACK_CHANNEL_ID}, + "actions": [{"type": "button", "value": json.dumps({"organization_id": ORGANIZATION_ID})}], + }, + ], +) @pytest.mark.django_db -def test_step_unacknowledge( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +def test_step_resolve( + payload, + make_organization, + make_slack_team_identity, + make_user, + make_slack_user_identity, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, ): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + slack_team_identity = make_slack_team_identity() + slack_user_identity = make_slack_user_identity(slack_team_identity=slack_team_identity) + slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) + + organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, acknowledged=True) + alert_group = make_alert_group(alert_receive_channel, resolved=False, pk=ALERT_GROUP_ID) make_alert(alert_group, raw_request_data={}) - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } + slack_message = make_slack_message( + alert_group=alert_group, channel_id=slack_channel.slack_id, slack_id=SLACK_MESSAGE_TS + ) + slack_message.get_alert_group() # fix FKs - step_class = ScenarioStep.get_step("distribute_alerts", "UnAcknowledgeGroupStep") + step_class = ScenarioStep.get_step("distribute_alerts", "ResolveGroupStep") step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) step.process_scenario(slack_user_identity, slack_team_identity, payload) alert_group.refresh_from_db() - assert alert_group.acknowledged is False + assert alert_group.resolved is True +@pytest.mark.parametrize( + "payload", + [ + _get_payload(), + # deprecated payload shape, but still supported to handle older Slack messages + { + "message_ts": SLACK_MESSAGE_TS, + "channel": {"id": SLACK_CHANNEL_ID}, + "actions": [{"type": "button", "value": json.dumps({"organization_id": ORGANIZATION_ID})}], + }, + ], +) @pytest.mark.django_db -def test_step_unacknowledge_deprecated( - make_organization_and_user_with_slack_identities, +def test_step_unresolve( + payload, + make_organization, + make_slack_team_identity, + make_user, + make_slack_user_identity, make_alert_receive_channel, make_alert_group, make_alert, make_slack_channel, make_slack_message, ): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + slack_team_identity = make_slack_team_identity() + slack_user_identity = make_slack_user_identity(slack_team_identity=slack_team_identity) + slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) + + organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, acknowledged=True) + alert_group = make_alert_group(alert_receive_channel, resolved=True, pk=ALERT_GROUP_ID) make_alert(alert_group, raw_request_data={}) - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } + slack_message = make_slack_message( + alert_group=alert_group, channel_id=slack_channel.slack_id, slack_id=SLACK_MESSAGE_TS + ) + slack_message.get_alert_group() # fix FKs - step_class = ScenarioStep.get_step("distribute_alerts", "UnAcknowledgeGroupStep") + step_class = ScenarioStep.get_step("distribute_alerts", "UnResolveGroupStep") step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) step.process_scenario(slack_user_identity, slack_team_identity, payload) alert_group.refresh_from_db() - assert alert_group.acknowledged is False + assert alert_group.resolved is False +@pytest.mark.parametrize( + "payload", + [ + _get_payload(action_type="static_select", user_id=USER_ID), + # deprecated payload shape, but still supported to handle older Slack messages + { + "message_ts": SLACK_MESSAGE_TS, + "channel": {"id": SLACK_CHANNEL_ID}, + "actions": [ + { + "type": "static_select", + "selected_option": {"value": json.dumps({"user_id": USER_ID})}, + } + ], + }, + ], +) @pytest.mark.django_db -def test_step_resolve( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +def test_step_invite( + payload, + make_organization, + make_slack_team_identity, + make_user, + make_slack_user_identity, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, ): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + slack_team_identity = make_slack_team_identity() + slack_user_identity = make_slack_user_identity(slack_team_identity=slack_team_identity) + slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) + + organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + user = make_user(organization=organization, slack_user_identity=slack_user_identity) + second_user = make_user(organization=organization, pk=USER_ID) alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) + alert_group = make_alert_group(alert_receive_channel, resolved=True, pk=ALERT_GROUP_ID) make_alert(alert_group, raw_request_data={}) - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } + slack_message = make_slack_message( + alert_group=alert_group, channel_id=slack_channel.slack_id, slack_id=SLACK_MESSAGE_TS + ) + slack_message.get_alert_group() # fix FKs - step_class = ScenarioStep.get_step("distribute_alerts", "ResolveGroupStep") + step_class = ScenarioStep.get_step("distribute_alerts", "InviteOtherPersonToIncident") step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) step.process_scenario(slack_user_identity, slack_team_identity, payload) alert_group.refresh_from_db() - assert alert_group.resolved is True + assert alert_group.invitations.count() == 1 + + invitation = alert_group.invitations.first() + assert invitation.author == user + assert invitation.invitee == second_user +@pytest.mark.parametrize( + "payload", + [ + _get_payload(invitation_id=INVITATION_ID), + # deprecated payload shape, but still supported to handle older Slack messages + { + "message_ts": SLACK_MESSAGE_TS, + "channel": {"id": SLACK_CHANNEL_ID}, + "actions": [ + { + "name": f"StopInvitationProcess_{INVITATION_ID}", + "type": "button", + "value": json.dumps({"organization_id": ORGANIZATION_ID}), + } + ], + }, + ], +) @pytest.mark.django_db -def test_step_resolve_deprecated( - make_organization_and_user_with_slack_identities, +def test_step_stop_invite( + payload, + make_organization, + make_slack_team_identity, + make_user, + make_slack_user_identity, make_alert_receive_channel, make_alert_group, make_alert, make_slack_channel, make_slack_message, + make_invitation, ): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + slack_team_identity = make_slack_team_identity() + slack_user_identity = make_slack_user_identity(slack_team_identity=slack_team_identity) + slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) + + organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + user = make_user(organization=organization, slack_user_identity=slack_user_identity) + second_user = make_user(organization=organization, pk=USER_ID) alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) + alert_group = make_alert_group(alert_receive_channel, resolved=True, pk=ALERT_GROUP_ID) make_alert(alert_group, raw_request_data={}) - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) + slack_message = make_slack_message( + alert_group=alert_group, channel_id=slack_channel.slack_id, slack_id=SLACK_MESSAGE_TS + ) + slack_message.get_alert_group() # fix FKs - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } + invitation = make_invitation(alert_group, user, second_user, pk=INVITATION_ID) - step_class = ScenarioStep.get_step("distribute_alerts", "ResolveGroupStep") + step_class = ScenarioStep.get_step("distribute_alerts", "StopInvitationProcess") step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) step.process_scenario(slack_user_identity, slack_team_identity, payload) - alert_group.refresh_from_db() - assert alert_group.resolved is True + invitation.refresh_from_db() + assert invitation.is_active is False +@pytest.mark.parametrize( + "payload", + [ + _get_payload(action_type="static_select", delay=1800), + # deprecated payload shape, but still supported to handle older Slack messages + { + "message_ts": SLACK_MESSAGE_TS, + "channel": {"id": SLACK_CHANNEL_ID}, + "actions": [ + { + "type": "static_select", + "selected_option": {"value": "1800"}, + } + ], + }, + ], +) @pytest.mark.django_db -def test_step_unresolve( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert +def test_step_silence( + payload, + make_organization, + make_slack_team_identity, + make_user, + make_slack_user_identity, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_slack_channel, + make_slack_message, ): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + slack_team_identity = make_slack_team_identity() + slack_user_identity = make_slack_user_identity(slack_team_identity=slack_team_identity) + slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) + + organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, resolved=True) + alert_group = make_alert_group(alert_receive_channel, silenced=False, pk=ALERT_GROUP_ID) make_alert(alert_group, raw_request_data={}) - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } + slack_message = make_slack_message( + alert_group=alert_group, channel_id=slack_channel.slack_id, slack_id=SLACK_MESSAGE_TS + ) + slack_message.get_alert_group() # fix FKs - step_class = ScenarioStep.get_step("distribute_alerts", "UnResolveGroupStep") + step_class = ScenarioStep.get_step("distribute_alerts", "SilenceGroupStep") step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) step.process_scenario(slack_user_identity, slack_team_identity, payload) alert_group.refresh_from_db() - assert alert_group.resolved is False + assert alert_group.silenced is True +@pytest.mark.parametrize( + "payload", + [ + _get_payload(action_type="static_select", delay=1800), + # deprecated payload shape, but still supported to handle older Slack messages + { + "message_ts": SLACK_MESSAGE_TS, + "channel": {"id": SLACK_CHANNEL_ID}, + "actions": [ + { + "type": "static_select", + "selected_option": {"value": "1800"}, + } + ], + }, + ], +) @pytest.mark.django_db -def test_step_unresolve_deprecated( - make_organization_and_user_with_slack_identities, +def test_step_unsilence( + payload, + make_organization, + make_slack_team_identity, + make_user, + make_slack_user_identity, make_alert_receive_channel, make_alert_group, make_alert, make_slack_channel, make_slack_message, ): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + slack_team_identity = make_slack_team_identity() + slack_user_identity = make_slack_user_identity(slack_team_identity=slack_team_identity) + slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) + + organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, resolved=True) + alert_group = make_alert_group(alert_receive_channel, silenced=True, pk=ALERT_GROUP_ID) make_alert(alert_group, raw_request_data={}) - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } + slack_message = make_slack_message( + alert_group=alert_group, channel_id=slack_channel.slack_id, slack_id=SLACK_MESSAGE_TS + ) + slack_message.get_alert_group() # fix FKs - step_class = ScenarioStep.get_step("distribute_alerts", "UnResolveGroupStep") + step_class = ScenarioStep.get_step("distribute_alerts", "UnSilenceGroupStep") step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) step.process_scenario(slack_user_identity, slack_team_identity, payload) alert_group.refresh_from_db() - assert alert_group.resolved is False + assert alert_group.silenced is False +@pytest.mark.parametrize( + "payload", + [ + _get_payload() | {"trigger_id": "RANDOM_TRIGGER_ID"}, + # deprecated payload shape, but still supported to handle older Slack messages + { + "message_ts": SLACK_MESSAGE_TS, + "channel": {"id": SLACK_CHANNEL_ID}, + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": ORGANIZATION_ID}), + } + ], + }, + ], +) @pytest.mark.django_db -def test_step_invite( - make_organization_and_user_with_slack_identities, +def test_step_select_attach( + payload, + make_organization, + make_slack_team_identity, make_user, + make_slack_user_identity, make_alert_receive_channel, make_alert_group, make_alert, + make_slack_channel, + make_slack_message, ): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - second_user = make_user(organization=organization) + slack_team_identity = make_slack_team_identity() + slack_user_identity = make_slack_user_identity(slack_team_identity=slack_team_identity) + slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) + + organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) + alert_group = make_alert_group(alert_receive_channel, pk=ALERT_GROUP_ID) make_alert(alert_group, raw_request_data={}) - payload = { - "actions": [ - { - "type": "static_select", - "selected_option": { - "value": json.dumps( - { - "organization_id": organization.pk, - "alert_group_pk": alert_group.pk, - "user_id": second_user.pk, - } - ) - }, - } - ], - } + slack_message = make_slack_message( + alert_group=alert_group, channel_id=slack_channel.slack_id, slack_id=SLACK_MESSAGE_TS + ) + slack_message.get_alert_group() # fix FKs - step_class = ScenarioStep.get_step("distribute_alerts", "InviteOtherPersonToIncident") + step_class = ScenarioStep.get_step("distribute_alerts", "SelectAttachGroupStep") step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.invitations.count() == 1 - - invitation = alert_group.invitations.first() - assert invitation.author == user - assert invitation.invitee == second_user + with patch.object(step._slack_client, "api_call") as mock_slack_api_call: + step.process_scenario(slack_user_identity, slack_team_identity, payload) + assert mock_slack_api_call.call_args.args == ("views.open",) + + +@pytest.mark.parametrize( + "payload", + [ + _get_payload() | {"trigger_id": "RANDOM_TRIGGER_ID"}, + # deprecated payload shape, but still supported to handle older Slack messages + { + "message_ts": SLACK_MESSAGE_TS, + "channel": {"id": SLACK_CHANNEL_ID}, + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": ORGANIZATION_ID}), + } + ], + }, + ], +) @pytest.mark.django_db -def test_step_invite_deprecated( - make_organization_and_user_with_slack_identities, +def test_step_unattach( + payload, + make_organization, + make_slack_team_identity, make_user, + make_slack_user_identity, make_alert_receive_channel, make_alert_group, make_alert, make_slack_channel, make_slack_message, ): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - second_user = make_user(organization=organization) + slack_team_identity = make_slack_team_identity() + slack_user_identity = make_slack_user_identity(slack_team_identity=slack_team_identity) + slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) + + organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) + root_alert_group = make_alert_group(alert_receive_channel) + alert_group = make_alert_group(alert_receive_channel, root_alert_group=root_alert_group, pk=ALERT_GROUP_ID) make_alert(alert_group, raw_request_data={}) - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "static_select", - "selected_option": {"value": json.dumps({"user_id": second_user.pk})}, - } - ], - } + slack_message = make_slack_message( + alert_group=alert_group, channel_id=slack_channel.slack_id, slack_id=SLACK_MESSAGE_TS + ) + slack_message.get_alert_group() # fix FKs - step_class = ScenarioStep.get_step("distribute_alerts", "InviteOtherPersonToIncident") + step_class = ScenarioStep.get_step("distribute_alerts", "UnAttachGroupStep") step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) step.process_scenario(slack_user_identity, slack_team_identity, payload) alert_group.refresh_from_db() - assert alert_group.invitations.count() == 1 - - invitation = alert_group.invitations.first() - assert invitation.author == user - assert invitation.invitee == second_user + assert alert_group.root_alert_group is None +@pytest.mark.parametrize( + "payload", + [ + _get_payload() | {"message_ts": "RANDOM_TS", "trigger_id": "RANDOM_TRIGGER_ID"}, + # deprecated payload shape, but still supported to handle older Slack messages + { + "message_ts": SLACK_MESSAGE_TS, + "channel": {"id": SLACK_CHANNEL_ID}, + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": ORGANIZATION_ID, "alert_group_pk": str(ALERT_GROUP_ID)}), + } + ], + }, + ], +) @pytest.mark.django_db -def test_step_stop_invite( - make_organization_and_user_with_slack_identities, +def test_step_format_alert( + payload, + make_organization, + make_slack_team_identity, make_user, + make_slack_user_identity, make_alert_receive_channel, make_alert_group, make_alert, - make_invitation, + make_slack_channel, + make_slack_message, ): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - second_user = make_user(organization=organization) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - invitation = make_invitation(alert_group, user, second_user) - - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps( - { - "organization_id": organization.pk, - "alert_group_pk": alert_group.pk, - "invitation_id": invitation.pk, - } - ), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "StopInvitationProcess") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - invitation.refresh_from_db() - assert invitation.is_active is False - - -@pytest.mark.django_db -def test_step_stop_invite_deprecated( - make_organization_and_user_with_slack_identities, - make_user, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, - make_invitation, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - second_user = make_user(organization=organization) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - invitation = make_invitation(alert_group, user, second_user) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "name": f"StopInvitationProcess_{invitation.pk}", - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } + slack_team_identity = make_slack_team_identity() + slack_user_identity = make_slack_user_identity(slack_team_identity=slack_team_identity) + slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) - step_class = ScenarioStep.get_step("distribute_alerts", "StopInvitationProcess") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - invitation.refresh_from_db() - assert invitation.is_active is False - - -@pytest.mark.django_db -def test_step_silence( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, silenced=False) + alert_group = make_alert_group(alert_receive_channel, pk=ALERT_GROUP_ID) make_alert(alert_group, raw_request_data={}) - payload = { - "actions": [ - { - "type": "static_select", - "selected_option": { - "value": json.dumps( - {"organization_id": organization.pk, "alert_group_pk": alert_group.pk, "delay": 1800} - ) - }, - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "SilenceGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.silenced is True - - -@pytest.mark.django_db -def test_step_silence_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, silenced=False) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "static_select", - "selected_option": {"value": "1800"}, - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "SilenceGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.silenced is True - - -@pytest.mark.django_db -def test_step_unsilence( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, silenced=True) - make_alert(alert_group, raw_request_data={}) - - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "UnSilenceGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.silenced is False - - -@pytest.mark.django_db -def test_step_unsilence_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel, silenced=True) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "UnSilenceGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.silenced is False - - -@pytest.mark.django_db -def test_step_select_attach( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - payload = { - "trigger_id": "RANDOM_TRIGGER_ID", - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "SelectAttachGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - # TODO: assert - - -@pytest.mark.django_db -def test_step_select_attach_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "trigger_id": "RANDOM_TRIGGER_ID", - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "SelectAttachGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - # TODO: assert - - -@pytest.mark.django_db -def test_step_unattach( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - root_alert_group = make_alert_group(alert_receive_channel) - alert_group = make_alert_group(alert_receive_channel, root_alert_group=root_alert_group) - make_alert(alert_group, raw_request_data={}) - - payload = { - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "UnAttachGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.root_alert_group is None - - -@pytest.mark.django_db -def test_step_unattach_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - root_alert_group = make_alert_group(alert_receive_channel) - alert_group = make_alert_group(alert_receive_channel, root_alert_group=root_alert_group) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk}), - } - ], - } - - step_class = ScenarioStep.get_step("distribute_alerts", "UnAttachGroupStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - alert_group.refresh_from_db() - assert alert_group.root_alert_group is None - - -@pytest.mark.django_db -def test_step_format_alert( - make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - payload = { - "message_ts": "RANDOM_TS", - "trigger_id": "RANDOM_TRIGGER_ID", - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), - } - ], - } + slack_message = make_slack_message( + alert_group=alert_group, channel_id=slack_channel.slack_id, slack_id=SLACK_MESSAGE_TS + ) + slack_message.get_alert_group() # fix FKs step_class = ScenarioStep.get_step("alertgroup_appearance", "OpenAlertAppearanceDialogStep") step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - # TODO: assert - - -@pytest.mark.django_db -def test_step_format_alert_deprecated( - make_organization_and_user_with_slack_identities, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_channel, - make_slack_message, -): - organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - alert_receive_channel = make_alert_receive_channel(organization) - alert_group = make_alert_group(alert_receive_channel) - make_alert(alert_group, raw_request_data={}) - - slack_channel = make_slack_channel(slack_team_identity) - slack_message = make_slack_message(alert_group=alert_group, channel_id=slack_channel.slack_id) - payload = { - "message_ts": slack_message.slack_id, - "channel": {"id": slack_channel.slack_id}, - "trigger_id": "RANDOM_TRIGGER_ID", - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": str(alert_group.pk)}), - } - ], - } - - step_class = ScenarioStep.get_step("alertgroup_appearance", "OpenAlertAppearanceDialogStep") - step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) + with patch.object(step._slack_client, "api_call") as mock_slack_api_call: + step.process_scenario(slack_user_identity, slack_team_identity, payload) - # TODO: assert + assert mock_slack_api_call.call_args.args == ("views.open",) @pytest.mark.django_db @@ -945,6 +808,8 @@ def test_step_resolution_note( step_class = ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep") step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) - step.process_scenario(slack_user_identity, slack_team_identity, payload) - # TODO: assert + with patch.object(step._slack_client, "api_call") as mock_slack_api_call: + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + assert mock_slack_api_call.call_args.args == ("views.open",) From 1de8d3ce8ea1a871e1cfd4f76a9a88eb7f58877f Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 14:47:40 +0100 Subject: [PATCH 17/36] more tests --- .../slack/tests/test_scenario_steps/test_alert_group_actions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py index 8d6539a6f9..1d90a65369 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py @@ -13,6 +13,7 @@ class TestScenario(AlertGroupActionsMixin, ScenarioStep): pass +# List of steps to be tested for alert group actions (getting alert group from Slack payload + user permissions check) ALERT_GROUP_ACTIONS_STEPS = [ # Acknowledge / Unacknowledge buttons ScenarioStep.get_step("distribute_alerts", "AcknowledgeGroupStep"), @@ -36,6 +37,7 @@ class TestScenario(AlertGroupActionsMixin, ScenarioStep): ] +# Constants to simplify parametrized tests ORGANIZATION_ID = 42 ALERT_GROUP_ID = 24 SLACK_MESSAGE_TS = "RANDOM_MESSAGE_TS" From d29d25d1ca64d1a470454e490a644dc0a5632457 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 15:38:24 +0100 Subject: [PATCH 18/36] Fix auth --- .../slack/scenarios/alertgroup_appearance.py | 4 +- .../apps/slack/scenarios/distribute_alerts.py | 69 ++++++++++++------- .../apps/slack/scenarios/resolution_note.py | 6 ++ engine/apps/slack/scenarios/step_mixins.py | 45 +++--------- .../test_alert_group_actions.py | 34 +++++++++ 5 files changed, 97 insertions(+), 61 deletions(-) diff --git a/engine/apps/slack/scenarios/alertgroup_appearance.py b/engine/apps/slack/scenarios/alertgroup_appearance.py index c19cc82b44..dc1ea8a2c1 100644 --- a/engine/apps/slack/scenarios/alertgroup_appearance.py +++ b/engine/apps/slack/scenarios/alertgroup_appearance.py @@ -10,10 +10,12 @@ class OpenAlertAppearanceDialogStep(CheckAlertIsUnarchivedMixin, AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "format Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = self.get_alert_group(slack_team_identity, payload) + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): return diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index 58b9ce9ef0..0567dcc043 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -221,12 +221,15 @@ class InviteOtherPersonToIncident( ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "invite to Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): User = apps.get_model("user_management", "User") alert_group = self.get_alert_group(slack_team_identity, payload) + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return + selected_user = None if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): @@ -258,10 +261,12 @@ class SilenceGroupStep( ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "silence Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = self.get_alert_group(slack_team_identity, payload) + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return value = payload["actions"][0]["selected_option"]["value"] try: @@ -284,11 +289,13 @@ class UnSilenceGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "unsilence Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): - alert_group = self.get_alert_group(slack_team_identity, payload) + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return + if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.un_silence_by_user(self.user, action_source=ActionSource.SLACK) @@ -303,10 +310,12 @@ class SelectAttachGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "select Alert Group for attach" def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = self.get_alert_group(slack_team_identity, payload) + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): return @@ -452,7 +461,6 @@ class AttachGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "attach Alert Group" def process_signal(self, log_record): alert_group = log_record.alert_group @@ -497,6 +505,10 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): root_alert_group = AlertGroup.all_objects.get(pk=root_alert_group_pk) alert_group = self.get_alert_group(slack_team_identity, payload) + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return + if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group) and self.check_alert_is_unarchived( slack_team_identity, payload, root_alert_group ): @@ -511,10 +523,13 @@ class UnAttachGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "unattach Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = self.get_alert_group(slack_team_identity, payload) + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return + if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.un_attach_by_user(self.user, action_source=ActionSource.SLACK) @@ -525,10 +540,13 @@ def process_signal(self, log_record): class StopInvitationProcess(CheckAlertIsUnarchivedMixin, AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "stop invitation" def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = self.get_alert_group(slack_team_identity, payload) + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return + if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): return @@ -550,13 +568,15 @@ class CustomButtonProcessStep( AlertGroupActionsMixin, scenario_step.ScenarioStep, ): - # TODO: REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "click custom button" def process_scenario(self, slack_user_identity, slack_team_identity, payload): CustomButtom = apps.get_model("alerts", "CustomButton") alert_group = self.get_alert_group(slack_team_identity, payload) + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return + if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): custom_button_pk = payload["actions"][0]["name"].split("_")[1] alert_group_pk = payload["actions"][0]["name"].split("_")[2] @@ -611,12 +631,14 @@ class ResolveGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "resolve Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): ResolutionNoteModalStep = scenario_step.ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep") alert_group = self.get_alert_group(slack_team_identity, payload) + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): return @@ -653,10 +675,13 @@ class UnResolveGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "unresolve Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = self.get_alert_group(slack_team_identity, payload) + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return + if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.un_resolve_by_user(self.user, action_source=ActionSource.SLACK) @@ -671,19 +696,19 @@ class AcknowledgeGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "acknowledge Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = self.get_alert_group(slack_team_identity, payload) - logger.debug(f"process_scenario in AcknowledgeGroupStep for alert_group {alert_group.pk}") + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return + if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.acknowledge_by_user(self.user, action_source=ActionSource.SLACK) def process_signal(self, log_record): alert_group = log_record.alert_group - logger.debug(f"Started process_signal in AcknowledgeGroupStep for alert_group {alert_group.pk}") self.alert_group_slack_service.update_alert_group_slack_message(alert_group) - logger.debug(f"Finished process_signal in AcknowledgeGroupStep for alert_group {alert_group.pk}") class UnAcknowledgeGroupStep( @@ -692,11 +717,13 @@ class UnAcknowledgeGroupStep( scenario_step.ScenarioStep, ): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] - ACTION_VERBOSE = "unacknowledge Alert Group" def process_scenario(self, slack_user_identity, slack_team_identity, payload): alert_group = self.get_alert_group(slack_team_identity, payload) - logger.debug(f"process_scenario in UnAcknowledgeGroupStep for alert_group {alert_group.pk}") + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return + if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): alert_group.un_acknowledge_by_user(self.user, action_source=ActionSource.SLACK) @@ -753,8 +780,6 @@ def process_signal(self, log_record): class AcknowledgeConfirmationStep(AcknowledgeGroupStep): - ACTION_VERBOSE = "confirm acknowledge status" - def process_scenario(self, slack_user_identity, slack_team_identity, payload): AlertGroup = apps.get_model("alerts", "AlertGroup") alert_group_id = payload["actions"][0]["value"].split("_")[1] @@ -887,8 +912,6 @@ def process_signal(self, log_record): class WipeGroupStep(scenario_step.ScenarioStep): - ACTION_VERBOSE = "wipe Alert Group" - def process_signal(self, log_record): alert_group = log_record.alert_group user_verbal = log_record.author.get_username_with_slack_verbal() @@ -898,8 +921,6 @@ def process_signal(self, log_record): class DeleteGroupStep(scenario_step.ScenarioStep): - ACTION_VERBOSE = "delete Alert Group" - def process_signal(self, log_record): alert_group = log_record.alert_group diff --git a/engine/apps/slack/scenarios/resolution_note.py b/engine/apps/slack/scenarios/resolution_note.py index e927279681..f71ece38fc 100644 --- a/engine/apps/slack/scenarios/resolution_note.py +++ b/engine/apps/slack/scenarios/resolution_note.py @@ -5,6 +5,7 @@ from django.db.models import Q from django.utils import timezone +from apps.api.permissions import RBACPermission from apps.slack.scenarios import scenario_step from apps.slack.slack_client.exceptions import SlackAPIException from apps.user_management.models import User @@ -373,6 +374,7 @@ def get_resolution_note_blocks(self, resolution_note): class ResolutionNoteModalStep(CheckAlertIsUnarchivedMixin, AlertGroupActionsMixin, scenario_step.ScenarioStep): + REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] RESOLUTION_NOTE_TEXT_BLOCK_ID = "resolution_note_text" RESOLUTION_NOTE_MESSAGES_MAX_COUNT = 25 @@ -383,6 +385,10 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload, da else: alert_group = self.get_alert_group(slack_team_identity, payload) + if not self.is_authorized(alert_group): + self.open_unauthorized_warning(payload) + return + value = data or json.loads(payload["actions"][0]["value"]) action_resolve = value.get("action_resolve", False) resolution_note_window_action = value.get("resolution_note_window_action", "") or value.get("action_value", "") diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index d7a1e5ab3c..74790f236f 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -14,18 +14,11 @@ class AlertGroupActionsMixin: """ Mixin for alert group actions (ack, resolve, etc.). Intended to be used as a mixin along with ScenarioStep. It serves two purposes: - 1. Check that user has required permissions to perform an action. Otherwise, send a message to a user ??? + 1. Check that user has required permissions to perform an action. Otherwise, send open a warning window. 2. Provide utility method to get AlertGroup instance from Slack message payload. """ REQUIRED_PERMISSIONS = [] - ACTION_VERBOSE = "perform action" - - def process_scenario(self, slack_user_identity, slack_team_identity, payload): - if self._check_membership(): - return super().process_scenario(slack_user_identity, slack_team_identity, payload) - else: - self._send_denied_message(payload) def get_alert_group(self, slack_team_identity, payload): # TODO: comment @@ -83,36 +76,16 @@ def _deprecated_get_alert_group(self, slack_team_identity, payload): ) raise - def _check_membership(self): - return user_is_authorized(self.user, self.REQUIRED_PERMISSIONS) - - def _send_denied_message(self, payload): - try: - thread_ts = payload["message_ts"] - except KeyError: - thread_ts = payload["message"]["ts"] - - text = "Attempted to {} by {}, but failed due to a lack of permissions.".format( - self.ACTION_VERBOSE, - self.user.get_username_with_slack_verbal(), + def is_authorized(self, alert_group): + return self.user.organization == alert_group.channel.organization and user_is_authorized( + self.user, self.REQUIRED_PERMISSIONS ) - self._slack_client.api_call( - "chat.postMessage", - channel=payload["channel"]["id"], - text=text, - blocks=[ - { - "type": "section", - "block_id": "alert", - "text": { - "type": "mrkdwn", - "text": text, - }, - }, - ], - thread_ts=thread_ts, - unfurl_links=True, + def open_unauthorized_warning(self, payload): + self.open_warning_window( + payload, + warning_text="You do not have permission to perform this action. Ask an admin to upgrade your permissions.", + title="Permission denied", ) diff --git a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py index 1d90a65369..ffa83db776 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py @@ -3,6 +3,7 @@ import pytest +from apps.api.permissions import LegacyAccessControlRole from apps.slack.models import SlackMessage from apps.slack.scenarios.scenario_step import ScenarioStep from apps.slack.scenarios.step_mixins import AlertGroupActionsMixin @@ -102,6 +103,39 @@ def test_alert_group_actions_slack_message_not_in_db( step.process_scenario(slack_user_identity, slack_team_identity, payload) +@pytest.mark.parametrize("step_class", ALERT_GROUP_ACTIONS_STEPS) +@pytest.mark.django_db +def test_alert_group_actions_unauthorized( + step_class, make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group +): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities( + role=LegacyAccessControlRole.VIEWER + ) + organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + + payload = { + "message_ts": "RANDOM_MESSAGE_TS", + "channel": {"id": "RANDOM_CHANNEL_ID"}, + "trigger_id": "RANDOM_TRIGGER_ID", + "actions": [ + { + "type": "button", + "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), + } + ], + } + + step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) + + with patch.object(step, "open_unauthorized_warning") as mock_open_unauthorized_warning: + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + mock_open_unauthorized_warning.assert_called_once() + + @pytest.mark.django_db def test_get_alert_group_button( make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group From 93c6a39a29d5d9b87700a32b2f76dcba1a14fa79 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 15:46:38 +0100 Subject: [PATCH 19/36] fix --- engine/apps/slack/scenarios/resolution_note.py | 2 +- engine/apps/slack/scenarios/step_mixins.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/apps/slack/scenarios/resolution_note.py b/engine/apps/slack/scenarios/resolution_note.py index f71ece38fc..41b07c7d1b 100644 --- a/engine/apps/slack/scenarios/resolution_note.py +++ b/engine/apps/slack/scenarios/resolution_note.py @@ -390,8 +390,8 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload, da return value = data or json.loads(payload["actions"][0]["value"]) - action_resolve = value.get("action_resolve", False) resolution_note_window_action = value.get("resolution_note_window_action", "") or value.get("action_value", "") + action_resolve = value.get("action_resolve", False) channel_id = payload["channel"]["id"] if "channel" in payload else None diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 74790f236f..cf8c666227 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -14,7 +14,7 @@ class AlertGroupActionsMixin: """ Mixin for alert group actions (ack, resolve, etc.). Intended to be used as a mixin along with ScenarioStep. It serves two purposes: - 1. Check that user has required permissions to perform an action. Otherwise, send open a warning window. + 1. Check that user has required permissions to perform an action. Otherwise, open a warning window. 2. Provide utility method to get AlertGroup instance from Slack message payload. """ From 3c38ef6303e458b30e4edd938f959bd1300fdc67 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 17:29:42 +0100 Subject: [PATCH 20/36] Repair orphaned messages --- engine/apps/slack/scenarios/step_mixins.py | 130 +++++++++++++++++---- 1 file changed, 108 insertions(+), 22 deletions(-) diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index cf8c666227..4c5816f504 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -5,7 +5,7 @@ from apps.alerts.models import AlertGroup from apps.api.permissions import user_is_authorized -from apps.slack.models import SlackMessage +from apps.slack.models import SlackMessage, SlackTeamIdentity logger = logging.getLogger(__name__) @@ -13,15 +13,73 @@ class AlertGroupActionsMixin: """ Mixin for alert group actions (ack, resolve, etc.). Intended to be used as a mixin along with ScenarioStep. - It serves two purposes: - 1. Check that user has required permissions to perform an action. Otherwise, open a warning window. - 2. Provide utility method to get AlertGroup instance from Slack message payload. """ REQUIRED_PERMISSIONS = [] - def get_alert_group(self, slack_team_identity, payload): - # TODO: comment + def get_alert_group(self, slack_team_identity: SlackTeamIdentity, payload: dict) -> AlertGroup: + """ + Get AlertGroup instance from payload sent by Slack on button click or select menu change. + """ + + alert_group = ( + self._get_alert_group_from_action(payload) # Try to get alert_group_pk from PRESSED button + or self._get_alert_group_from_message(payload) # Try to use alert_group_pk from ANY button in message + or self._get_alert_group_from_slack_message_in_db(slack_team_identity, payload) # Fetch message from DB + ) + + # Repair alert group if Slack message is orphaned + if alert_group.slack_message is None: + self._repair_alert_group(slack_team_identity, alert_group, payload) + + return alert_group + + def is_authorized(self, alert_group: AlertGroup) -> bool: + """ + Check that user has required permissions to perform an action + """ + + return self.user.organization == alert_group.channel.organization and user_is_authorized( + self.user, self.REQUIRED_PERMISSIONS + ) + + def open_unauthorized_warning(self, payload: dict) -> None: + self.open_warning_window( + payload, + warning_text="You do not have permission to perform this action. Ask an admin to upgrade your permissions.", + title="Permission denied", + ) + + def _repair_alert_group( + self, slack_team_identity: SlackTeamIdentity, alert_group: AlertGroup, payload: dict + ) -> None: + """ + There's a possibility that OnCall failed to create a SlackMessage instance for an AlertGroup, but the message + was sent to Slack. This method creates SlackMessage instance for such orphaned messages. + """ + + channel_id = payload["channel"]["id"] + try: + message_id = payload["message"]["ts"] + except KeyError: + message_id = payload["original_message"]["ts"] + + slack_message = SlackMessage.objects.create( + slack_id=message_id, + organization=alert_group.channel.organization, + _slack_team_identity=slack_team_identity, + channel_id=channel_id, + alert_group=alert_group, + ) + + alert_group.slack_message = slack_message + alert_group.save(update_fields=["slack_message"]) + + def _get_alert_group_from_action(self, payload: dict) -> AlertGroup | None: + """ + Get AlertGroup instance from action data in payload. Action data is data encoded into buttons and select + menus in apps.alerts.incident_appearance.renderers.slack_renderer.AlertGroupSlackRenderer._get_buttons_blocks + """ action = payload["actions"][0] action_type = action["type"] @@ -36,17 +94,57 @@ def get_alert_group(self, slack_team_identity, payload): try: value = json.loads(value_string) except (TypeError, json.JSONDecodeError): - return self._deprecated_get_alert_group(slack_team_identity, payload) + return None try: alert_group_pk = value["alert_group_pk"] except (KeyError, TypeError): - return self._deprecated_get_alert_group(slack_team_identity, payload) + return None return AlertGroup.all_objects.get(pk=alert_group_pk) - def _deprecated_get_alert_group(self, slack_team_identity, payload): - # TODO: comment + def _get_alert_group_from_message(self, payload: dict) -> AlertGroup | None: + """ + Get AlertGroup instance from message data in payload. It's similar to _get_alert_group_from_action, + but it tries to get alert_group_pk from ANY button in the message, not just the one that was clicked. + """ + + try: + elements = payload["message"]["attachments"][0]["blocks"][0]["elements"] + except (KeyError, IndexError): + # sometimes message is in "original_message" field, not "message" + elements = payload["original_message"]["attachments"][0]["blocks"][0]["elements"] + except (KeyError, IndexError): + return None + + for element in elements: + if element.get("type") != "button": + continue + + value_string = element.get("value") + if not value_string: + continue + + try: + value = json.loads(value_string) + except (TypeError, json.JSONDecodeError): + continue + + try: + alert_group_pk = value["alert_group_pk"] + except (KeyError, TypeError): + continue + + return AlertGroup.all_objects.get(pk=alert_group_pk) + + def _get_alert_group_from_slack_message_in_db( + self, slack_team_identity: SlackTeamIdentity, payload: dict + ) -> AlertGroup: + """ + Get AlertGroup instance from SlackMessage instance. + Old messages may not have alert_group_pk encoded into buttons, so we need to query SlackMessage to figure out + the AlertGroup. + """ message_ts = payload.get("message_ts") or payload["container"]["message_ts"] # interactive message or block channel_id = payload["channel"]["id"] @@ -76,18 +174,6 @@ def _deprecated_get_alert_group(self, slack_team_identity, payload): ) raise - def is_authorized(self, alert_group): - return self.user.organization == alert_group.channel.organization and user_is_authorized( - self.user, self.REQUIRED_PERMISSIONS - ) - - def open_unauthorized_warning(self, payload): - self.open_warning_window( - payload, - warning_text="You do not have permission to perform this action. Ask an admin to upgrade your permissions.", - title="Permission denied", - ) - class CheckAlertIsUnarchivedMixin: def check_alert_is_unarchived(self, slack_team_identity, payload, alert_group, warning=True): From 250d112e1640240af9a5ccdbe3f72ca95992c6ff Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 17:35:59 +0100 Subject: [PATCH 21/36] simplify --- engine/apps/slack/scenarios/step_mixins.py | 32 ++++------------------ 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 4c5816f504..9784a90202 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -1,8 +1,6 @@ import json import logging -from django.core.exceptions import ObjectDoesNotExist - from apps.alerts.models import AlertGroup from apps.api.permissions import user_is_authorized from apps.slack.models import SlackMessage, SlackTeamIdentity @@ -149,30 +147,12 @@ def _get_alert_group_from_slack_message_in_db( channel_id = payload["channel"]["id"] # Get SlackMessage from DB - try: - slack_message = SlackMessage.objects.get( - slack_id=message_ts, - _slack_team_identity=slack_team_identity, - channel_id=channel_id, - ) - except SlackMessage.DoesNotExist: - logger.error( - f"Tried to get SlackMessage from message_ts:" - f"slack_team_identity_id={slack_team_identity.pk}," - f"message_ts={message_ts}" - ) - raise - - # Get AlertGroup from SlackMessage - try: - return slack_message.get_alert_group() - except ObjectDoesNotExist: - logger.error( - f"Tried to get AlertGroup from SlackMessage:" - f"slack_team_identity_id={slack_team_identity.pk}," - f"message_ts={message_ts}" - ) - raise + slack_message = SlackMessage.objects.get( + slack_id=message_ts, + _slack_team_identity=slack_team_identity, + channel_id=channel_id, + ) + return slack_message.get_alert_group() class CheckAlertIsUnarchivedMixin: From 815cd36df5f2b0bbdd515d6b5121576f2d1b33fe Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 17:41:32 +0100 Subject: [PATCH 22/36] comment --- engine/apps/slack/scenarios/step_mixins.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 9784a90202..c9769d4349 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -143,9 +143,14 @@ def _get_alert_group_from_slack_message_in_db( Old messages may not have alert_group_pk encoded into buttons, so we need to query SlackMessage to figure out the AlertGroup. """ + message_ts = payload.get("message_ts") or payload["container"]["message_ts"] # interactive message or block channel_id = payload["channel"]["id"] + # All Slack messages from OnCall should have alert_group_pk encoded into buttons, so reaching this point means + # something probably went wrong. + logger.warning(f"alert_group_pk not found in payload, fetching SlackMessage from DB. message_ts: {message_ts}") + # Get SlackMessage from DB slack_message = SlackMessage.objects.get( slack_id=message_ts, From e4f6108531db07463536d244129d96c8e152d8c0 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 17:54:25 +0100 Subject: [PATCH 23/36] don't pass additional data to invite user select --- .../renderers/slack_renderer.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py index 35a2314b6d..bd1061083a 100644 --- a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py @@ -343,6 +343,13 @@ def _get_invitation_attachment(self): def _get_select_user_element( self, action_id, multi_select=False, initial_user=None, initial_users_list=None, text=None ): + def get_action_value(user_id): + """ + In contrast to other buttons and select menus, self._alert_group_action_value is not used here. + It's because there could be a lot of users, and we don't want to increase the payload size too much. + """ + return json.dumps({"user_id": user_id}) + MAX_STATIC_SELECT_OPTIONS = 100 if not text: @@ -368,7 +375,7 @@ def _get_select_user_element( user_verbal = user_verbal[:72] + "..." option = { "text": {"type": "plain_text", "text": user_verbal}, - "value": self._alert_group_action_value(user_id=user.pk), + "value": get_action_value(user.pk), } options.append(option) @@ -384,7 +391,7 @@ def _get_select_user_element( elif users_count == 0: # strange case when there are no users to select option = { "text": {"type": "plain_text", "text": "No users to select"}, - "value": self._alert_group_action_value(user_id=None), + "value": get_action_value(None), } options.append(option) element["options"] = options @@ -400,7 +407,7 @@ def _get_select_user_element( user_verbal = f"{user.get_username_with_slack_verbal()}" option = { "text": {"type": "plain_text", "text": user_verbal}, - "value": self._alert_group_action_value(user_id=user.pk), + "value": get_action_value(user.pk), } initial_options.append(option) element["initial_options"] = initial_options @@ -408,7 +415,7 @@ def _get_select_user_element( user_verbal = f"{initial_user.get_username_with_slack_verbal()}" initial_option = { "text": {"type": "plain_text", "text": user_verbal}, - "value": self._alert_group_action_value(user_id=initial_user.pk), + "value": get_action_value(initial_user.pk), } element["initial_option"] = initial_option From 9f2a5447d276e2b08bf20e93a01db659210cba61 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 17:56:13 +0100 Subject: [PATCH 24/36] simplify --- .../alerts/incident_appearance/renderers/slack_renderer.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py index bd1061083a..8589f86f5a 100644 --- a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py @@ -373,10 +373,7 @@ def get_action_value(user_id): user_verbal = f"{user.get_username_with_slack_verbal()}" if len(user_verbal) > 75: user_verbal = user_verbal[:72] + "..." - option = { - "text": {"type": "plain_text", "text": user_verbal}, - "value": get_action_value(user.pk), - } + option = {"text": {"type": "plain_text", "text": user_verbal}, "value": get_action_value(user.pk)} options.append(option) if users_count > MAX_STATIC_SELECT_OPTIONS: From 936549815af43c9b8bfac6d8bc229225bcde6f7d Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 18:02:03 +0100 Subject: [PATCH 25/36] Add comment --- .../alerts/incident_appearance/renderers/slack_renderer.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py index 8589f86f5a..5f32166d93 100644 --- a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py @@ -419,7 +419,12 @@ def get_action_value(user_id): return element def _alert_group_action_value(self, **kwargs): - # TODO: add comment + """ + Store organization and alert group IDs in Slack button or select menu values. + alert_group_pk is used in apps.slack.scenarios.step_mixins.AlertGroupActionsMixin to get the right alert group + when handling AG actions in Slack. + """ + data = { "organization_id": self.alert_group.channel.organization_id, "alert_group_pk": self.alert_group.pk, From a6196140ce8287f08d234c3873f01d4fc5513d9e Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 18:08:28 +0100 Subject: [PATCH 26/36] Fix _get_alert_group_from_message --- engine/apps/slack/scenarios/step_mixins.py | 5 ++--- .../tests/test_scenario_steps/test_alert_group_actions.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index c9769d4349..2ee3427298 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -108,10 +108,9 @@ def _get_alert_group_from_message(self, payload: dict) -> AlertGroup | None: """ try: - elements = payload["message"]["attachments"][0]["blocks"][0]["elements"] - except (KeyError, IndexError): # sometimes message is in "original_message" field, not "message" - elements = payload["original_message"]["attachments"][0]["blocks"][0]["elements"] + message = payload.get("message") or payload["original_message"] + elements = message["attachments"][0]["blocks"][0]["elements"] except (KeyError, IndexError): return None diff --git a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py index ffa83db776..879746094d 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py @@ -417,8 +417,8 @@ def test_step_unresolve( @pytest.mark.parametrize( "payload", [ - _get_payload(action_type="static_select", user_id=USER_ID), - # deprecated payload shape, but still supported to handle older Slack messages + # Usual data such as alert_group_pk is not passed to InviteOtherPersonToIncident, so it doesn't increase + # payload size too much. { "message_ts": SLACK_MESSAGE_TS, "channel": {"id": SLACK_CHANNEL_ID}, From 873d9b5b24f9fc704fae545a00d588cfc38d80bb Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 18:47:00 +0100 Subject: [PATCH 27/36] more tests --- engine/apps/slack/scenarios/step_mixins.py | 3 - .../test_alert_group_actions.py | 57 ++++++++++++++++--- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 2ee3427298..6784e9fc3b 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -115,9 +115,6 @@ def _get_alert_group_from_message(self, payload: dict) -> AlertGroup | None: return None for element in elements: - if element.get("type") != "button": - continue - value_string = element.get("value") if not value_string: continue diff --git a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py index 879746094d..a24c2640bf 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py @@ -117,15 +117,15 @@ def test_alert_group_actions_unauthorized( alert_group = make_alert_group(alert_receive_channel) payload = { - "message_ts": "RANDOM_MESSAGE_TS", - "channel": {"id": "RANDOM_CHANNEL_ID"}, - "trigger_id": "RANDOM_TRIGGER_ID", "actions": [ { "type": "button", "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), } ], + "channel": {"id": "RANDOM_CHANNEL_ID"}, + "message": {"ts": "RANDOM_MESSAGE_TS"}, + "trigger_id": "RANDOM_TRIGGER_ID", } step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity) @@ -151,13 +151,17 @@ def test_get_alert_group_button( "type": "button", "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}), } - ] + ], + "channel": {"id": "RANDOM_CHANNEL_ID"}, + "message": {"ts": "RANDOM_MESSAGE_TS"}, } step = TestScenario(organization=organization, user=user, slack_team_identity=slack_team_identity) result = step.get_alert_group(slack_team_identity, payload) - assert alert_group == result + alert_group.refresh_from_db() + assert alert_group == result # check it's the right alert group + assert alert_group.slack_message is not None # check that orphaned Slack message is repaired @pytest.mark.django_db @@ -177,17 +181,52 @@ def test_get_alert_group_static_select( "value": json.dumps({"organization_id": organization.pk, "alert_group_pk": alert_group.pk}) }, } - ] + ], + "channel": {"id": "RANDOM_CHANNEL_ID"}, + "message": {"ts": "RANDOM_MESSAGE_TS"}, } step = TestScenario(organization=organization, user=user, slack_team_identity=slack_team_identity) result = step.get_alert_group(slack_team_identity, payload) - assert alert_group == result + alert_group.refresh_from_db() + assert alert_group == result # check it's the right alert group + assert alert_group.slack_message is not None # check that orphaned Slack message is repaired + + +@pytest.mark.django_db +def test_get_alert_group_from_message( + make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group +): + organization, user, slack_team_identity, _ = make_organization_and_user_with_slack_identities() + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + + payload = { + "actions": [ + { + "type": "button", + "value": "no alert_group_pk", + } + ], + "message": { + "ts": "RANDOM_MESSAGE_TS", + "attachments": [{"blocks": [{"elements": [{"value": json.dumps({"alert_group_pk": alert_group.pk})}]}]}], + }, + "channel": {"id": "RANDOM_CHANNEL_ID"}, + } + + step = TestScenario(organization=organization, user=user, slack_team_identity=slack_team_identity) + result = step.get_alert_group(slack_team_identity, payload) + + alert_group.refresh_from_db() + assert alert_group == result # check it's the right alert group + assert alert_group.slack_message is not None # check that orphaned Slack message is repaired @pytest.mark.django_db -def test_get_alert_group_deprecated( +def test_get_alert_group_from_slack_message_in_db( make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, @@ -840,6 +879,8 @@ def test_step_resolution_note( ), } ], + "channel": {"id": "RANDOM_CHANNEL_ID"}, + "message": {"ts": "RANDOM_MESSAGE_TS"}, } step_class = ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep") From 784efda9e1de8755d192f61c143e6dbfa6076c98 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 18:54:28 +0100 Subject: [PATCH 28/36] more tests --- .../test_alert_group_actions.py | 28 ------- .../apps/slack/tests/test_slack_renderer.py | 74 +++++++++---------- 2 files changed, 36 insertions(+), 66 deletions(-) diff --git a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py index a24c2640bf..a31eaad564 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py @@ -4,10 +4,8 @@ import pytest from apps.api.permissions import LegacyAccessControlRole -from apps.slack.models import SlackMessage from apps.slack.scenarios.scenario_step import ScenarioStep from apps.slack.scenarios.step_mixins import AlertGroupActionsMixin -from apps.slack.slack_client import SlackClientWithErrorHandling class TestScenario(AlertGroupActionsMixin, ScenarioStep): @@ -77,32 +75,6 @@ def _get_payload(action_type="button", **kwargs): } -@pytest.mark.parametrize("step_class", ALERT_GROUP_ACTIONS_STEPS) -@patch.object( - SlackClientWithErrorHandling, - "api_call", - return_value={"ok": True}, -) -@pytest.mark.django_db -def test_alert_group_actions_slack_message_not_in_db( - mock_slack_api_call, step_class, make_organization_and_user_with_slack_identities -): - organization, _, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from - - payload = { - "message_ts": "RANDOM_MESSAGE_TS", - "channel": {"id": "RANDOM_CHANNEL_ID"}, - "trigger_id": "RANDOM_TRIGGER_ID", - "actions": [{"type": "button", "value": json.dumps({"organization_id": organization.pk})}], - } - - step = step_class(organization=organization, slack_team_identity=slack_team_identity) - - with pytest.raises(SlackMessage.DoesNotExist): # TODO: change this - step.process_scenario(slack_user_identity, slack_team_identity, payload) - - @pytest.mark.parametrize("step_class", ALERT_GROUP_ACTIONS_STEPS) @pytest.mark.django_db def test_alert_group_actions_unauthorized( diff --git a/engine/apps/slack/tests/test_slack_renderer.py b/engine/apps/slack/tests/test_slack_renderer.py index 9de3f94c05..9c2f15ef19 100644 --- a/engine/apps/slack/tests/test_slack_renderer.py +++ b/engine/apps/slack/tests/test_slack_renderer.py @@ -15,9 +15,9 @@ def test_slack_renderer_acknowledge_button(make_organization, make_alert_receive elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] - ack_button = elements[0] - assert ack_button["text"]["text"] == "Acknowledge" - assert json.loads(ack_button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} + button = elements[0] + assert button["text"]["text"] == "Acknowledge" + assert json.loads(button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} @pytest.mark.django_db @@ -31,9 +31,9 @@ def test_slack_renderer_unacknowledge_button( elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] - ack_button = elements[0] - assert ack_button["text"]["text"] == "Unacknowledge" - assert json.loads(ack_button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} + button = elements[0] + assert button["text"]["text"] == "Unacknowledge" + assert json.loads(button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} @pytest.mark.django_db @@ -45,9 +45,9 @@ def test_slack_renderer_resolve_button(make_organization, make_alert_receive_cha elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] - ack_button = elements[1] - assert ack_button["text"]["text"] == "Resolve" - assert json.loads(ack_button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} + button = elements[1] + assert button["text"]["text"] == "Resolve" + assert json.loads(button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} @pytest.mark.django_db @@ -59,9 +59,9 @@ def test_slack_renderer_unresolve_button(make_organization, make_alert_receive_c elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] - ack_button = elements[0] - assert ack_button["text"]["text"] == "Unresolve" - assert json.loads(ack_button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} + button = elements[0] + assert button["text"]["text"] == "Unresolve" + assert json.loads(button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} @pytest.mark.django_db @@ -78,11 +78,9 @@ def test_slack_renderer_invite_action( ack_button = elements[2] assert ack_button["placeholder"]["text"] == "Invite..." - assert json.loads(ack_button["options"][0]["value"]) == { - "organization_id": organization.pk, - "alert_group_pk": alert_group.pk, - "user_id": user.pk, - } + + # Check only user_id is passed. Otherwise, if there are a lot of users, the payload could be unnecessarily large. + assert json.loads(ack_button["options"][0]["value"]) == {"user_id": user.pk} @pytest.mark.django_db @@ -96,10 +94,10 @@ def test_slack_renderer_stop_invite_button( make_alert(alert_group=alert_group, raw_request_data={}) invitation = make_invitation(alert_group, user, user) - stop_inviting_action = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[1]["actions"][0] + action = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[1]["actions"][0] - assert stop_inviting_action["text"] == f"Stop inviting {user.username}" - assert json.loads(stop_inviting_action["value"]) == { + assert action["text"] == f"Stop inviting {user.username}" + assert json.loads(action["value"]) == { "organization_id": organization.pk, "alert_group_pk": alert_group.pk, "invitation_id": invitation.pk, @@ -115,10 +113,10 @@ def test_slack_renderer_silence_button(make_organization, make_alert_receive_cha elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] - silence_button = elements[3] - assert silence_button["placeholder"]["text"] == "Silence" + button = elements[3] + assert button["placeholder"]["text"] == "Silence" - values = [json.loads(option["value"]) for option in silence_button["options"]] + values = [json.loads(option["value"]) for option in button["options"]] assert values == [ {"organization_id": organization.pk, "alert_group_pk": alert_group.pk, "delay": delay} for delay, _ in AlertGroup.SILENCE_DELAY_OPTIONS @@ -133,10 +131,10 @@ def test_slack_renderer_unsilence_button(make_organization, make_alert_receive_c make_alert(alert_group=alert_group, raw_request_data={}) elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] - unsilence_button = elements[3] + button = elements[3] - assert unsilence_button["text"]["text"] == "Unsilence" - assert json.loads(unsilence_button["value"]) == { + assert button["text"]["text"] == "Unsilence" + assert json.loads(button["value"]) == { "organization_id": organization.pk, "alert_group_pk": alert_group.pk, } @@ -150,10 +148,10 @@ def test_slack_renderer_attach_button(make_organization, make_alert_receive_chan make_alert(alert_group=alert_group, raw_request_data={}) elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] - unsilence_button = elements[4] + button = elements[4] - assert unsilence_button["text"]["text"] == "Attach to ..." - assert json.loads(unsilence_button["value"]) == { + assert button["text"]["text"] == "Attach to ..." + assert json.loads(button["value"]) == { "organization_id": organization.pk, "alert_group_pk": alert_group.pk, } @@ -170,10 +168,10 @@ def test_slack_renderer_unattach_button(make_organization, make_alert_receive_ch alert_group = make_alert_group(alert_receive_channel, root_alert_group=root_alert_group) make_alert(alert_group=alert_group, raw_request_data={}) - unattach_action = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["actions"][0] + action = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["actions"][0] - assert unattach_action["text"] == "Unattach" - assert json.loads(unattach_action["value"]) == { + assert action["text"] == "Unattach" + assert json.loads(action["value"]) == { "organization_id": organization.pk, "alert_group_pk": alert_group.pk, } @@ -190,9 +188,9 @@ def test_slack_renderer_format_alert_button( elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] - ack_button = elements[5] - assert ack_button["text"]["text"] == ":mag: Format Alert" - assert json.loads(ack_button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} + button = elements[5] + assert button["text"]["text"] == ":mag: Format Alert" + assert json.loads(button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk} @pytest.mark.django_db @@ -206,9 +204,9 @@ def test_slack_renderer_resolution_notes_button( elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"] - ack_button = elements[6] - assert ack_button["text"]["text"] == "Add Resolution notes" - assert json.loads(ack_button["value"]) == { + button = elements[6] + assert button["text"]["text"] == "Add Resolution notes" + assert json.loads(button["value"]) == { "organization_id": organization.pk, "alert_group_pk": alert_group.pk, "resolution_note_window_action": "edit", From feba98968be2509d7e6885790f4eb8a88910bc94 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 19:03:02 +0100 Subject: [PATCH 29/36] comments --- engine/apps/slack/scenarios/resolution_note.py | 1 - engine/apps/slack/scenarios/step_mixins.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/engine/apps/slack/scenarios/resolution_note.py b/engine/apps/slack/scenarios/resolution_note.py index 41b07c7d1b..2af123ac10 100644 --- a/engine/apps/slack/scenarios/resolution_note.py +++ b/engine/apps/slack/scenarios/resolution_note.py @@ -392,7 +392,6 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload, da value = data or json.loads(payload["actions"][0]["value"]) resolution_note_window_action = value.get("resolution_note_window_action", "") or value.get("action_value", "") action_resolve = value.get("action_resolve", False) - channel_id = payload["channel"]["id"] if "channel" in payload else None if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 6784e9fc3b..91c5e43e80 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -17,7 +17,7 @@ class AlertGroupActionsMixin: def get_alert_group(self, slack_team_identity: SlackTeamIdentity, payload: dict) -> AlertGroup: """ - Get AlertGroup instance from payload sent by Slack on button click or select menu change. + Get AlertGroup instance on AG Slack message button click or select menu change. """ alert_group = ( @@ -34,7 +34,7 @@ def get_alert_group(self, slack_team_identity: SlackTeamIdentity, payload: dict) def is_authorized(self, alert_group: AlertGroup) -> bool: """ - Check that user has required permissions to perform an action + Check that user has required permissions to perform an action. """ return self.user.organization == alert_group.channel.organization and user_is_authorized( @@ -76,7 +76,7 @@ def _repair_alert_group( def _get_alert_group_from_action(self, payload: dict) -> AlertGroup | None: """ Get AlertGroup instance from action data in payload. Action data is data encoded into buttons and select - menus in apps.alerts.incident_appearance.renderers.slack_renderer.AlertGroupSlackRenderer._get_buttons_blocks + menus in apps.alerts.incident_appearance.renderers.slack_renderer.AlertGroupSlackRenderer._get_buttons_blocks. """ action = payload["actions"][0] From be8c6592900105afc56d0a5f17968c4a6ca33d45 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 19:09:31 +0100 Subject: [PATCH 30/36] fix AttachGroupStep --- engine/apps/slack/scenarios/distribute_alerts.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index 0567dcc043..d9c01c3f62 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -460,7 +460,7 @@ class AttachGroupStep( AlertGroupActionsMixin, scenario_step.ScenarioStep, ): - REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] + REQUIRED_PERMISSIONS = [] # Permissions are handled in SelectAttachGroupStep def process_signal(self, log_record): alert_group = log_record.alert_group @@ -494,8 +494,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): AttachGroupStep.routing_uid() ]["selected_option"]["value"] root_alert_group = AlertGroup.all_objects.get(pk=root_alert_group_pk) - # Deprecated handler kept for backward compatibility (so older Slack messages can still be processed) - # Deprecated since at least 28/01/2021 + # old version of attach selection by dropdown else: try: root_alert_group_pk = int(payload["actions"][0]["selected_options"][0]["value"]) @@ -504,11 +503,6 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): root_alert_group = AlertGroup.all_objects.get(pk=root_alert_group_pk) alert_group = self.get_alert_group(slack_team_identity, payload) - - if not self.is_authorized(alert_group): - self.open_unauthorized_warning(payload) - return - if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group) and self.check_alert_is_unarchived( slack_team_identity, payload, root_alert_group ): From 76a3c064f9ec6da770dfb50fe5de1d590cad42d9 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 19:25:03 +0100 Subject: [PATCH 31/36] incident -> AlertGroup --- engine/apps/slack/scenarios/distribute_alerts.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index d9c01c3f62..28ae9f7865 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -742,7 +742,7 @@ def process_signal(self, log_record): ] text = ( f"{user_verbal} hasn't responded to an acknowledge timeout reminder." - f" Alert Group is unacknowledged automatically" + f" Alert Group is unacknowledged automatically." ) if alert_group.slack_message.ack_reminder_message_ts: try: @@ -785,7 +785,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): if alert_group.acknowledged_by == AlertGroup.USER: if self.user == alert_group.acknowledged_by_user: user_verbal = alert_group.acknowledged_by_user.get_username_with_slack_verbal() - text = f"{user_verbal} confirmed that the incident is still acknowledged" + text = f"{user_verbal} confirmed that the Alert Group is still acknowledged." self._slack_client.api_call( "chat.update", channel=channel, @@ -799,11 +799,11 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): "chat.postEphemeral", channel=channel, user=slack_user_identity.slack_id, - text="This alert is acknowledged by another user. Acknowledge it yourself first.", + text="This Alert Group is acknowledged by another user. Acknowledge it yourself first.", ) elif alert_group.acknowledged_by == AlertGroup.SOURCE: user_verbal = self.user.get_username_with_slack_verbal() - text = f"{user_verbal} confirmed that the incident is still acknowledged" + text = f"{user_verbal} confirmed that the Alert Group is still acknowledged." self._slack_client.api_call( "chat.update", channel=channel, @@ -822,7 +822,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): "chat.postEphemeral", channel=channel, user=slack_user_identity.slack_id, - text="This alert is already unacknowledged.", + text="This Alert Group is already unacknowledged.", ) def process_signal(self, log_record): @@ -832,12 +832,12 @@ def process_signal(self, log_record): alert_group = log_record.alert_group channel_id = alert_group.slack_message.channel_id user_verbal = log_record.author.get_username_with_slack_verbal(mention=True) - text = f"{user_verbal}, please confirm that you're still working on this incident." + text = f"{user_verbal}, please confirm that you're still working on this Alert Group." if alert_group.channel.organization.unacknowledge_timeout != Organization.UNACKNOWLEDGE_TIMEOUT_NEVER: attachments = [ { - "fallback": "Are you still working on this incident?", + "fallback": "Are you still working on this Alert Group?", "text": text, "callback_id": "alert", "attachment_type": "default", From 06572881a8e0e4f2637887ef779a7d95da9e46e3 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 19:33:23 +0100 Subject: [PATCH 32/36] more tests --- .../test_alert_group_actions.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py index a31eaad564..4d01d37d67 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py @@ -610,8 +610,8 @@ def test_step_silence( "channel": {"id": SLACK_CHANNEL_ID}, "actions": [ { - "type": "static_select", - "selected_option": {"value": "1800"}, + "type": "button", + "value": json.dumps({"organization_id": ORGANIZATION_ID}), } ], }, @@ -659,18 +659,6 @@ def test_step_unsilence( "payload", [ _get_payload() | {"trigger_id": "RANDOM_TRIGGER_ID"}, - # deprecated payload shape, but still supported to handle older Slack messages - { - "message_ts": SLACK_MESSAGE_TS, - "channel": {"id": SLACK_CHANNEL_ID}, - "trigger_id": "RANDOM_TRIGGER_ID", - "actions": [ - { - "type": "button", - "value": json.dumps({"organization_id": ORGANIZATION_ID}), - } - ], - }, ], ) @pytest.mark.django_db From 6ecd44bf51b3f3e94cad105a557adaee9bb0985e Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 19:57:02 +0100 Subject: [PATCH 33/36] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1770025221..88da0fd477 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fix templates when slack or telegram is disabled ([#2064](https://github.com/grafana/oncall/pull/2064)) +- Fix orphaned messages in Slack by @vadimkerr ([#2023](https://github.com/grafana/oncall/pull/2023)) ## v1.2.33 (2023-05-30) From ce0916b8225143168c8dd88c5fd1610b2cb19f85 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Wed, 31 May 2023 20:01:55 +0100 Subject: [PATCH 34/36] fix tests --- .../test_scenario_steps/test_resolution_note.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py b/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py index 12363de3ef..cc463b01d1 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py @@ -189,15 +189,23 @@ def test_get_resolution_notes_blocks_latest_limit( side_effect=SlackAPIException(response={"ok": False, "error": "not_found"}), ) def test_resolution_notes_modal_closed_before_update( - mock_slack_api_call, make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group + mock_slack_api_call, + make_organization_and_user_with_slack_identities, + make_alert_receive_channel, + make_alert_group, + make_slack_message, ): ResolutionNoteModalStep = ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep") - organization, _, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from alert_receive_channel = make_alert_receive_channel(organization) alert_group = make_alert_group(alert_receive_channel) + slack_message = make_slack_message( + alert_group=alert_group, channel_id="RANDOM_CHANNEL_ID", slack_id="RANDOM_MESSAGE_ID" + ) + slack_message.get_alert_group() # fix FKs payload = { "trigger_id": "TEST", @@ -217,7 +225,7 @@ def test_resolution_notes_modal_closed_before_update( } # Check that no error is raised even if the Slack API call fails - step = ResolutionNoteModalStep(organization=organization, slack_team_identity=slack_team_identity) + step = ResolutionNoteModalStep(organization=organization, user=user, slack_team_identity=slack_team_identity) step.process_scenario(slack_user_identity, slack_team_identity, payload) # Check that "views.update" API call was made From 9bf93ed6e23c376148e2009afddf4c833a77a239 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Thu, 1 Jun 2023 10:49:17 +0100 Subject: [PATCH 35/36] comments --- engine/apps/slack/scenarios/resolution_note.py | 2 ++ engine/apps/slack/scenarios/step_mixins.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/apps/slack/scenarios/resolution_note.py b/engine/apps/slack/scenarios/resolution_note.py index 2af123ac10..e3c327eb34 100644 --- a/engine/apps/slack/scenarios/resolution_note.py +++ b/engine/apps/slack/scenarios/resolution_note.py @@ -380,9 +380,11 @@ class ResolutionNoteModalStep(CheckAlertIsUnarchivedMixin, AlertGroupActionsMixi def process_scenario(self, slack_user_identity, slack_team_identity, payload, data=None): if data: + # Argument "data" is used when step is called from other step, e.g. AddRemoveThreadMessageStep AlertGroup = apps.get_model("alerts", "AlertGroup") alert_group = AlertGroup.all_objects.get(pk=data["alert_group_pk"]) else: + # Handle "Add Resolution notes" button click alert_group = self.get_alert_group(slack_team_identity, payload) if not self.is_authorized(alert_group): diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 91c5e43e80..6a134427f5 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -17,7 +17,7 @@ class AlertGroupActionsMixin: def get_alert_group(self, slack_team_identity: SlackTeamIdentity, payload: dict) -> AlertGroup: """ - Get AlertGroup instance on AG Slack message button click or select menu change. + Get AlertGroup instance on Slack message button click or select menu change. """ alert_group = ( From 4be903ced84e2401b88cd2248832f1a76205338f Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Thu, 1 Jun 2023 11:07:53 +0100 Subject: [PATCH 36/36] Check for user=None in is_authorized --- engine/apps/slack/scenarios/step_mixins.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 6a134427f5..cf677001aa 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -37,8 +37,10 @@ def is_authorized(self, alert_group: AlertGroup) -> bool: Check that user has required permissions to perform an action. """ - return self.user.organization == alert_group.channel.organization and user_is_authorized( - self.user, self.REQUIRED_PERMISSIONS + return ( + self.user is not None + and self.user.organization == alert_group.channel.organization + and user_is_authorized(self.user, self.REQUIRED_PERMISSIONS) ) def open_unauthorized_warning(self, payload: dict) -> None: