Fix orphaned messages in Slack (#2023)
# What this PR does Reworks Slack handlers for buttons and select menus for AG Slack messages. <img width="602" alt="Screenshot 2023-05-31 at 19 34 05" src="https://github.com/grafana/oncall/assets/20116910/857bf096-7bdd-427b-94b6-15aad873a8ac"> ## Current implementation - It's possible to end up with orphaned Slack messages that are posted to Slack but have no `SlackMessage` instance in the DB. For such messages, clicking buttons will result in an exception and HTTP 500. See private repo [issue](https://github.com/grafana/oncall-private/issues/1841) for more info. - Bug in authorization system, which effectively bypasses any permission checks. For example, it's possible to resolve an alert group while being a Viewer. - No tests covering most buttons. ## Changes in this PR - Make the system more robust, don't use `SlackMessage` model to figure out the alert group being interacted on, instead embed `alert_group_pk` to every button and use it when receiving interaction requests from Slack. - Existing orphaned Slack messages will be repaired. Clicking buttons under orphaned messages will work (and missing `SlackMessage` instance will be created on interaction). This is possible because some buttons already have `alert_group_pk` embedded, and it's possible to get this data on button clicks (even if the clicked button itself doesn't have `alert_group_pk` embedded). - Fix authorization. Show warning window when unauthorized: <img width="511" alt="Screenshot 2023-05-31 at 19 40 02" src="https://github.com/grafana/oncall/assets/20116910/5abeeaa7-1b61-4a47-b3af-0e21d5cd1907"> - Added tests for all the buttons under AG message. Add tests checking authorization, actual execution of scenario steps, orphan message repairing, backward compatibility, etc. Also add tests on `AlertGroupSlackRenderer` checking that correct data is embedded into buttons. - Cosmetic changes such as renaming `incident` to `Alert Group`. ## Which issue(s) this PR fixes Related to https://github.com/grafana/oncall-private/issues/1841 ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required)
This commit is contained in:
parent
e66fe67174
commit
d1373b58d2
10 changed files with 1386 additions and 215 deletions
|
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
|
||||
- Fix a bug with permissions for telegram user settings by @alexintech ([#2075](https://github.com/grafana/oncall/pull/2075))
|
||||
- Fix orphaned messages in Slack by @vadimkerr ([#2023](https://github.com/grafana/oncall/pull/2023))
|
||||
|
||||
## v1.2.34 (2023-05-31)
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
"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 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
"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 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
"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 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
"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 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
|
||||
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 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
"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 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
{
|
||||
"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 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
"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 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
{
|
||||
"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 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
{
|
||||
"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 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
},
|
||||
"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 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
"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 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
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 [
|
||||
|
|
@ -359,6 +343,13 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
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:
|
||||
|
|
@ -382,7 +373,7 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
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": get_action_value(user.pk)}
|
||||
options.append(option)
|
||||
|
||||
if users_count > MAX_STATIC_SELECT_OPTIONS:
|
||||
|
|
@ -397,7 +388,7 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
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": get_action_value(None),
|
||||
}
|
||||
options.append(option)
|
||||
element["options"] = options
|
||||
|
|
@ -413,7 +404,7 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
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": get_action_value(user.pk),
|
||||
}
|
||||
initial_options.append(option)
|
||||
element["initial_options"] = initial_options
|
||||
|
|
@ -421,8 +412,22 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
|
|||
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": get_action_value(initial_user.pk),
|
||||
}
|
||||
element["initial_option"] = initial_option
|
||||
|
||||
return element
|
||||
|
||||
def _alert_group_action_value(self, **kwargs):
|
||||
"""
|
||||
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,
|
||||
**kwargs,
|
||||
}
|
||||
return json.dumps(data) # Slack block elements allow to pass value as string only (max 2000 chars)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@ class SlackMessage(models.Model):
|
|||
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):
|
||||
|
|
@ -217,32 +218,3 @@ class SlackMessage(models.Model):
|
|||
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
|
||||
|
|
|
|||
|
|
@ -5,38 +5,25 @@ from django.apps import apps
|
|||
from apps.api.permissions import RBACPermission
|
||||
from apps.slack.scenarios import scenario_step
|
||||
|
||||
from .step_mixins import CheckAlertIsUnarchivedMixin, IncidentActionsAccessControlMixin
|
||||
from .step_mixins import AlertGroupActionsMixin, CheckAlertIsUnarchivedMixin
|
||||
|
||||
|
||||
class OpenAlertAppearanceDialogStep(
|
||||
CheckAlertIsUnarchivedMixin, IncidentActionsAccessControlMixin, scenario_step.ScenarioStep
|
||||
):
|
||||
class OpenAlertAppearanceDialogStep(CheckAlertIsUnarchivedMixin, AlertGroupActionsMixin, scenario_step.ScenarioStep):
|
||||
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
|
||||
ACTION_VERBOSE = "open Alert Appearance"
|
||||
|
||||
def process_scenario(self, slack_user_identity, slack_team_identity, payload):
|
||||
AlertGroup = apps.get_model("alerts", "AlertGroup")
|
||||
alert_group = self.get_alert_group(slack_team_identity, payload)
|
||||
if not self.is_authorized(alert_group):
|
||||
self.open_unauthorized_warning(payload)
|
||||
return
|
||||
|
||||
try:
|
||||
message_ts = payload["message_ts"]
|
||||
except KeyError:
|
||||
message_ts = payload["container"]["message_ts"]
|
||||
|
||||
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
|
||||
blocks = []
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ from apps.slack.tasks import (
|
|||
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 AlertGroupActionsMixin, CheckAlertIsUnarchivedMixin
|
||||
|
||||
ATTACH_TO_ALERT_GROUPS_LIMIT = 20
|
||||
|
||||
|
|
@ -218,17 +218,20 @@ class AlertShootingStep(scenario_step.ScenarioStep):
|
|||
|
||||
class InviteOtherPersonToIncident(
|
||||
CheckAlertIsUnarchivedMixin,
|
||||
IncidentActionsAccessControlMixin,
|
||||
AlertGroupActionsMixin,
|
||||
scenario_step.ScenarioStep,
|
||||
):
|
||||
|
||||
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
|
||||
ACTION_VERBOSE = "invite to incident"
|
||||
|
||||
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(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):
|
||||
|
|
@ -255,21 +258,24 @@ class InviteOtherPersonToIncident(
|
|||
|
||||
class SilenceGroupStep(
|
||||
CheckAlertIsUnarchivedMixin,
|
||||
IncidentActionsAccessControlMixin,
|
||||
AlertGroupActionsMixin,
|
||||
scenario_step.ScenarioStep,
|
||||
):
|
||||
|
||||
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
|
||||
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)
|
||||
if not self.is_authorized(alert_group):
|
||||
self.open_unauthorized_warning(payload)
|
||||
return
|
||||
|
||||
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"])
|
||||
|
||||
alert_group = SlackMessage.get_alert_group_from_slack_message_payload(slack_team_identity, payload)
|
||||
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)
|
||||
|
||||
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)
|
||||
|
|
@ -281,15 +287,17 @@ class SilenceGroupStep(
|
|||
|
||||
class UnSilenceGroupStep(
|
||||
CheckAlertIsUnarchivedMixin,
|
||||
IncidentActionsAccessControlMixin,
|
||||
AlertGroupActionsMixin,
|
||||
scenario_step.ScenarioStep,
|
||||
):
|
||||
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
|
||||
ACTION_VERBOSE = "unsilence incident"
|
||||
|
||||
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
|
||||
|
||||
alert_group = SlackMessage.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)
|
||||
|
||||
|
|
@ -300,20 +308,20 @@ class UnSilenceGroupStep(
|
|||
|
||||
class SelectAttachGroupStep(
|
||||
CheckAlertIsUnarchivedMixin,
|
||||
IncidentActionsAccessControlMixin,
|
||||
AlertGroupActionsMixin,
|
||||
scenario_step.ScenarioStep,
|
||||
):
|
||||
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
|
||||
ACTION_VERBOSE = "Select Alert Group for Attaching to"
|
||||
|
||||
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.is_authorized(alert_group):
|
||||
self.open_unauthorized_warning(payload)
|
||||
return
|
||||
|
||||
if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group):
|
||||
return
|
||||
|
||||
blocks = []
|
||||
view = {
|
||||
"callback_id": AttachGroupStep.routing_uid(),
|
||||
|
|
@ -326,7 +334,7 @@ class SelectAttachGroupStep(
|
|||
"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},
|
||||
|
|
@ -335,8 +343,8 @@ class SelectAttachGroupStep(
|
|||
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 +380,7 @@ class SelectAttachGroupStep(
|
|||
"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 +449,7 @@ class SelectAttachGroupStep(
|
|||
},
|
||||
"label": {
|
||||
"type": "plain_text",
|
||||
"text": "Select incident:",
|
||||
"text": "Select Alert Group:",
|
||||
"emoji": True,
|
||||
},
|
||||
}
|
||||
|
|
@ -451,11 +459,10 @@ class SelectAttachGroupStep(
|
|||
|
||||
class AttachGroupStep(
|
||||
CheckAlertIsUnarchivedMixin,
|
||||
IncidentActionsAccessControlMixin,
|
||||
AlertGroupActionsMixin,
|
||||
scenario_step.ScenarioStep,
|
||||
):
|
||||
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
|
||||
ACTION_VERBOSE = "Attach incident"
|
||||
REQUIRED_PERMISSIONS = [] # Permissions are handled in SelectAttachGroupStep
|
||||
|
||||
def process_signal(self, log_record):
|
||||
alert_group = log_record.alert_group
|
||||
|
|
@ -497,8 +504,7 @@ class AttachGroupStep(
|
|||
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(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
|
||||
):
|
||||
|
|
@ -509,14 +515,17 @@ class AttachGroupStep(
|
|||
|
||||
class UnAttachGroupStep(
|
||||
CheckAlertIsUnarchivedMixin,
|
||||
IncidentActionsAccessControlMixin,
|
||||
AlertGroupActionsMixin,
|
||||
scenario_step.ScenarioStep,
|
||||
):
|
||||
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
|
||||
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(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,17 +534,26 @@ class UnAttachGroupStep(
|
|||
self.alert_group_slack_service.update_alert_group_slack_message(alert_group)
|
||||
|
||||
|
||||
class StopInvitationProcess(CheckAlertIsUnarchivedMixin, IncidentActionsAccessControlMixin, scenario_step.ScenarioStep):
|
||||
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 = SlackMessage.get_alert_group_from_slack_message_payload(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
|
||||
|
||||
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)
|
||||
|
|
@ -543,16 +561,18 @@ class StopInvitationProcess(CheckAlertIsUnarchivedMixin, IncidentActionsAccessCo
|
|||
|
||||
class CustomButtonProcessStep(
|
||||
CheckAlertIsUnarchivedMixin,
|
||||
IncidentActionsAccessControlMixin,
|
||||
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 = SlackMessage.get_alert_group_from_slack_message_payload(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):
|
||||
custom_button_pk = payload["actions"][0]["name"].split("_")[1]
|
||||
alert_group_pk = payload["actions"][0]["name"].split("_")[2]
|
||||
|
|
@ -603,16 +623,18 @@ class CustomButtonProcessStep(
|
|||
|
||||
class ResolveGroupStep(
|
||||
CheckAlertIsUnarchivedMixin,
|
||||
IncidentActionsAccessControlMixin,
|
||||
AlertGroupActionsMixin,
|
||||
scenario_step.ScenarioStep,
|
||||
):
|
||||
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
|
||||
ACTION_VERBOSE = "resolve incident"
|
||||
|
||||
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(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
|
||||
|
|
@ -645,14 +667,17 @@ class ResolveGroupStep(
|
|||
|
||||
class UnResolveGroupStep(
|
||||
CheckAlertIsUnarchivedMixin,
|
||||
IncidentActionsAccessControlMixin,
|
||||
AlertGroupActionsMixin,
|
||||
scenario_step.ScenarioStep,
|
||||
):
|
||||
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
|
||||
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(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)
|
||||
|
||||
|
|
@ -663,36 +688,38 @@ class UnResolveGroupStep(
|
|||
|
||||
class AcknowledgeGroupStep(
|
||||
CheckAlertIsUnarchivedMixin,
|
||||
IncidentActionsAccessControlMixin,
|
||||
AlertGroupActionsMixin,
|
||||
scenario_step.ScenarioStep,
|
||||
):
|
||||
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
|
||||
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)
|
||||
logger.debug(f"process_scenario in AcknowledgeGroupStep for alert_group {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):
|
||||
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(
|
||||
CheckAlertIsUnarchivedMixin,
|
||||
IncidentActionsAccessControlMixin,
|
||||
AlertGroupActionsMixin,
|
||||
scenario_step.ScenarioStep,
|
||||
):
|
||||
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
|
||||
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)
|
||||
logger.debug(f"process_scenario in UnAcknowledgeGroupStep for alert_group {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):
|
||||
alert_group.un_acknowledge_by_user(self.user, action_source=ActionSource.SLACK)
|
||||
|
||||
|
|
@ -717,7 +744,7 @@ class UnAcknowledgeGroupStep(
|
|||
]
|
||||
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:
|
||||
|
|
@ -749,8 +776,6 @@ class UnAcknowledgeGroupStep(
|
|||
|
||||
|
||||
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]
|
||||
|
|
@ -762,7 +787,7 @@ class AcknowledgeConfirmationStep(AcknowledgeGroupStep):
|
|||
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,
|
||||
|
|
@ -776,11 +801,11 @@ class AcknowledgeConfirmationStep(AcknowledgeGroupStep):
|
|||
"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,
|
||||
|
|
@ -799,7 +824,7 @@ class AcknowledgeConfirmationStep(AcknowledgeGroupStep):
|
|||
"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):
|
||||
|
|
@ -809,12 +834,12 @@ class AcknowledgeConfirmationStep(AcknowledgeGroupStep):
|
|||
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",
|
||||
|
|
@ -883,8 +908,6 @@ class AcknowledgeConfirmationStep(AcknowledgeGroupStep):
|
|||
|
||||
|
||||
class WipeGroupStep(scenario_step.ScenarioStep):
|
||||
ACTION_VERBOSE = "wipe incident"
|
||||
|
||||
def process_signal(self, log_record):
|
||||
alert_group = log_record.alert_group
|
||||
user_verbal = log_record.author.get_username_with_slack_verbal()
|
||||
|
|
@ -894,8 +917,6 @@ class WipeGroupStep(scenario_step.ScenarioStep):
|
|||
|
||||
|
||||
class DeleteGroupStep(scenario_step.ScenarioStep):
|
||||
ACTION_VERBOSE = "delete incident"
|
||||
|
||||
def process_signal(self, log_record):
|
||||
alert_group = log_record.alert_group
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@ from django.apps import apps
|
|||
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
|
||||
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 +373,28 @@ class UpdateResolutionNoteStep(scenario_step.ScenarioStep):
|
|||
return blocks
|
||||
|
||||
|
||||
class ResolutionNoteModalStep(CheckAlertIsUnarchivedMixin, scenario_step.ScenarioStep):
|
||||
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
|
||||
|
||||
def process_scenario(self, slack_user_identity, slack_team_identity, payload, data=None):
|
||||
AlertGroup = apps.get_model("alerts", "AlertGroup")
|
||||
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):
|
||||
self.open_unauthorized_warning(payload)
|
||||
return
|
||||
|
||||
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)
|
||||
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 +424,7 @@ class ResolutionNoteModalStep(CheckAlertIsUnarchivedMixin, scenario_step.Scenari
|
|||
"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 +442,7 @@ class ResolutionNoteModalStep(CheckAlertIsUnarchivedMixin, scenario_step.Scenari
|
|||
# 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:
|
||||
|
|
|
|||
|
|
@ -1,72 +1,164 @@
|
|||
import json
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from apps.alerts.models import AlertGroup
|
||||
from apps.api.permissions import user_is_authorized
|
||||
from apps.slack.models import SlackMessage, SlackTeamIdentity
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccessControl(ABC):
|
||||
class AlertGroupActionsMixin:
|
||||
"""
|
||||
Mixin for alert group actions (ack, resolve, etc.). Intended to be used as a mixin along with ScenarioStep.
|
||||
"""
|
||||
|
||||
REQUIRED_PERMISSIONS = []
|
||||
ACTION_VERBOSE = ""
|
||||
|
||||
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: SlackTeamIdentity, payload: dict) -> AlertGroup:
|
||||
"""
|
||||
Get AlertGroup instance on Slack message button click or select menu change.
|
||||
"""
|
||||
|
||||
def check_membership(self):
|
||||
return user_is_authorized(self.user, self.REQUIRED_PERMISSIONS)
|
||||
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
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def send_denied_message(self, payload):
|
||||
pass
|
||||
# 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
|
||||
|
||||
class IncidentActionsAccessControlMixin(AccessControl):
|
||||
"""
|
||||
Mixin for auth in incident actions
|
||||
"""
|
||||
def is_authorized(self, alert_group: AlertGroup) -> bool:
|
||||
"""
|
||||
Check that user has required permissions to perform an action.
|
||||
"""
|
||||
|
||||
def send_denied_message_to_channel(self, payload=None):
|
||||
# Send denied message to thread by default
|
||||
return False
|
||||
return (
|
||||
self.user is not None
|
||||
and self.user.organization == alert_group.channel.organization
|
||||
and user_is_authorized(self.user, self.REQUIRED_PERMISSIONS)
|
||||
)
|
||||
|
||||
def send_denied_message(self, payload):
|
||||
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:
|
||||
thread_ts = payload["message_ts"]
|
||||
message_id = payload["message"]["ts"]
|
||||
except KeyError:
|
||||
thread_ts = payload["message"]["ts"]
|
||||
message_id = payload["original_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(),
|
||||
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,
|
||||
)
|
||||
|
||||
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=None if self.send_denied_message_to_channel(payload) else thread_ts,
|
||||
unfurl_links=True,
|
||||
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"]
|
||||
|
||||
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 None
|
||||
|
||||
try:
|
||||
alert_group_pk = value["alert_group_pk"]
|
||||
except (KeyError, TypeError):
|
||||
return None
|
||||
|
||||
return AlertGroup.all_objects.get(pk=alert_group_pk)
|
||||
|
||||
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:
|
||||
# sometimes message is in "original_message" field, not "message"
|
||||
message = payload.get("message") or payload["original_message"]
|
||||
elements = message["attachments"][0]["blocks"][0]["elements"]
|
||||
except (KeyError, IndexError):
|
||||
return None
|
||||
|
||||
for element in elements:
|
||||
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"]
|
||||
|
||||
# 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,
|
||||
_slack_team_identity=slack_team_identity,
|
||||
channel_id=channel_id,
|
||||
)
|
||||
return slack_message.get_alert_group()
|
||||
|
||||
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,852 @@
|
|||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from apps.api.permissions import LegacyAccessControlRole
|
||||
from apps.slack.scenarios.scenario_step import ScenarioStep
|
||||
from apps.slack.scenarios.step_mixins import AlertGroupActionsMixin
|
||||
|
||||
|
||||
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"),
|
||||
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", "ResolutionNoteModalStep"),
|
||||
]
|
||||
|
||||
|
||||
# Constants to simplify parametrized tests
|
||||
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)
|
||||
@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 = {
|
||||
"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)
|
||||
|
||||
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
|
||||
):
|
||||
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}),
|
||||
}
|
||||
],
|
||||
"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)
|
||||
|
||||
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_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})
|
||||
},
|
||||
}
|
||||
],
|
||||
"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)
|
||||
|
||||
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_from_slack_message_in_db(
|
||||
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
|
||||
|
||||
|
||||
@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(
|
||||
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,
|
||||
):
|
||||
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=False, pk=ALERT_GROUP_ID)
|
||||
make_alert(alert_group, raw_request_data={})
|
||||
|
||||
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)
|
||||
step.process_scenario(slack_user_identity, slack_team_identity, payload)
|
||||
|
||||
alert_group.refresh_from_db()
|
||||
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_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,
|
||||
):
|
||||
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, pk=ALERT_GROUP_ID)
|
||||
make_alert(alert_group, raw_request_data={})
|
||||
|
||||
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 = 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.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_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,
|
||||
):
|
||||
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=False, pk=ALERT_GROUP_ID)
|
||||
make_alert(alert_group, raw_request_data={})
|
||||
|
||||
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 = 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.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_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,
|
||||
):
|
||||
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, pk=ALERT_GROUP_ID)
|
||||
make_alert(alert_group, raw_request_data={})
|
||||
|
||||
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 = 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.parametrize(
|
||||
"payload",
|
||||
[
|
||||
# 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},
|
||||
"actions": [
|
||||
{
|
||||
"type": "static_select",
|
||||
"selected_option": {"value": json.dumps({"user_id": USER_ID})},
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
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,
|
||||
):
|
||||
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, resolved=True, pk=ALERT_GROUP_ID)
|
||||
make_alert(alert_group, raw_request_data={})
|
||||
|
||||
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 = 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.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_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,
|
||||
):
|
||||
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, resolved=True, pk=ALERT_GROUP_ID)
|
||||
make_alert(alert_group, raw_request_data={})
|
||||
|
||||
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
|
||||
|
||||
invitation = make_invitation(alert_group, user, second_user, pk=INVITATION_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.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_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,
|
||||
):
|
||||
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, silenced=False, pk=ALERT_GROUP_ID)
|
||||
make_alert(alert_group, raw_request_data={})
|
||||
|
||||
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", "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.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": "button",
|
||||
"value": json.dumps({"organization_id": ORGANIZATION_ID}),
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
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,
|
||||
):
|
||||
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, silenced=True, pk=ALERT_GROUP_ID)
|
||||
make_alert(alert_group, raw_request_data={})
|
||||
|
||||
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", "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.parametrize(
|
||||
"payload",
|
||||
[
|
||||
_get_payload() | {"trigger_id": "RANDOM_TRIGGER_ID"},
|
||||
],
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
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,
|
||||
):
|
||||
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, pk=ALERT_GROUP_ID)
|
||||
make_alert(alert_group, raw_request_data={})
|
||||
|
||||
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", "SelectAttachGroupStep")
|
||||
step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity)
|
||||
|
||||
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_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,
|
||||
):
|
||||
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)
|
||||
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_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", "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.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_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_slack_channel,
|
||||
make_slack_message,
|
||||
):
|
||||
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, pk=ALERT_GROUP_ID)
|
||||
make_alert(alert_group, raw_request_data={})
|
||||
|
||||
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)
|
||||
|
||||
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.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",
|
||||
}
|
||||
),
|
||||
}
|
||||
],
|
||||
"channel": {"id": "RANDOM_CHANNEL_ID"},
|
||||
"message": {"ts": "RANDOM_MESSAGE_TS"},
|
||||
}
|
||||
|
||||
step_class = ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep")
|
||||
step = step_class(organization=organization, user=user, slack_team_identity=slack_team_identity)
|
||||
|
||||
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",)
|
||||
|
|
@ -189,26 +189,43 @@ 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",
|
||||
"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",
|
||||
}
|
||||
),
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
# 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
|
||||
|
|
|
|||
213
engine/apps/slack/tests/test_slack_renderer.py
Normal file
213
engine/apps/slack/tests/test_slack_renderer.py
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
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"]
|
||||
|
||||
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
|
||||
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"]
|
||||
|
||||
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
|
||||
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"]
|
||||
|
||||
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
|
||||
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"]
|
||||
|
||||
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
|
||||
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..."
|
||||
|
||||
# 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
|
||||
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)
|
||||
|
||||
action = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[1]["actions"][0]
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
|
||||
@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"]
|
||||
|
||||
button = elements[3]
|
||||
assert button["placeholder"]["text"] == "Silence"
|
||||
|
||||
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
|
||||
]
|
||||
|
||||
|
||||
@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"]
|
||||
button = elements[3]
|
||||
|
||||
assert button["text"]["text"] == "Unsilence"
|
||||
assert json.loads(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"]
|
||||
button = elements[4]
|
||||
|
||||
assert button["text"]["text"] == "Attach to ..."
|
||||
assert json.loads(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={})
|
||||
|
||||
action = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["actions"][0]
|
||||
|
||||
assert action["text"] == "Unattach"
|
||||
assert json.loads(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"]
|
||||
|
||||
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
|
||||
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"]
|
||||
|
||||
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",
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue