-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
[WIP][Slack] Catch All - Code cleanup #70169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ebfe7ac
fe9208e
c28ac44
0c7da1a
0072cf2
5d01565
45c0293
5a17034
51def70
2dd8752
0253ce3
c4cc46e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
from __future__ import annotations | ||
|
||
from collections.abc import Mapping | ||
from dataclasses import dataclass | ||
from typing import Any | ||
|
||
from sentry.notifications.utils.actions import BaseMessageAction | ||
|
||
|
||
@dataclass | ||
class SlackMessageAction(BaseMessageAction): | ||
""" | ||
Class that holds information about a Slack message action. | ||
Has helper functions that can provide Slack specific outputs for the particular message action | ||
""" | ||
|
||
label: str | ||
|
||
@staticmethod | ||
def to_slack_message_action(original: BaseMessageAction) -> SlackMessageAction: | ||
""" | ||
Converts the original message into a specific SlackMessageAction object with the proper defaults | ||
""" | ||
return SlackMessageAction( | ||
name=original.name, | ||
type=original.type, | ||
label=original.label if original.label else "", | ||
url=original.url, | ||
value=original.value, | ||
action_id=original.action_id, | ||
block_id=original.block_id, | ||
option_groups=original.option_groups, | ||
selected_options=original.selected_options, | ||
) | ||
|
||
def _get_button_text_value(self) -> str: | ||
""" | ||
Returns the proper text value for the button that should be displayed to the user. | ||
Favors the label field if it is set, otherwise falls back to the name of the message action | ||
""" | ||
return self.label or self.name | ||
|
||
def _get_button_text(self) -> dict[str, str]: | ||
""" | ||
Returns the proper structure for the button text that Slack expects to display | ||
""" | ||
return {"type": "plain_text", "text": self._get_button_text_value()} | ||
|
||
def get_button(self) -> Mapping[str, Any]: | ||
""" | ||
Create a block kit supported button for this action to be used in a Slack message | ||
""" | ||
button = { | ||
"type": "button", | ||
"text": self._get_button_text(), | ||
} | ||
if self.value: | ||
button["action_id"] = self.value | ||
button["value"] = self.value | ||
|
||
if self.action_id: | ||
button["action_id"] = self.action_id | ||
|
||
if self.url: | ||
button["url"] = self.url | ||
button["value"] = "link_clicked" | ||
Comment on lines
+53
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could add some constants here to keep it safer |
||
|
||
return button |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,22 @@ | ||
from __future__ import annotations | ||
|
||
from abc import ABC | ||
from collections.abc import Mapping, MutableMapping | ||
from typing import Any | ||
|
||
from sentry.eventstore.models import Event, GroupEvent | ||
from sentry.integrations.slack.message_builder import SlackBody | ||
from sentry.models.group import Group | ||
from sentry.notifications.utils.actions import MessageAction | ||
|
||
|
||
def get_slack_button(action: MessageAction) -> Mapping[str, Any]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove a bunch of unused methods and functions |
||
kwargs: MutableMapping[str, Any] = { | ||
"text": action.label or action.name, | ||
"name": action.name, | ||
"type": action.type, | ||
} | ||
for field in ("style", "url", "value", "action_id"): | ||
value = getattr(action, field, None) | ||
if value: | ||
kwargs[field] = value | ||
|
||
if action.type == "select": | ||
kwargs["selected_options"] = action.selected_options or [] | ||
kwargs["option_groups"] = action.option_groups or [] | ||
|
||
return kwargs | ||
|
||
|
||
class SlackMessageBuilder(ABC): | ||
def build(self) -> SlackBody: | ||
"""Abstract `build` method that all inheritors must implement.""" | ||
raise NotImplementedError | ||
|
||
def build_fallback_text(self, obj: Group | Event | GroupEvent, project_slug: str) -> str: | ||
@classmethod | ||
def build_fallback_text(cls, obj: Group | Event | GroupEvent, project_slug: str) -> str: | ||
"""Fallback text is used in the message preview popup.""" | ||
title = obj.title | ||
if isinstance(obj, GroupEvent) and obj.occurrence is not None: | ||
title = obj.occurrence.issue_title | ||
|
||
return f"[{project_slug}] {title}" | ||
|
||
@property | ||
def escape_text(self) -> bool: | ||
""" | ||
Returns True if we need to escape the text in the message. | ||
""" | ||
return False |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ | |
format_actor_options, | ||
get_title_link, | ||
) | ||
from sentry.integrations.slack.actions.message_action import SlackMessageAction | ||
from sentry.integrations.slack.message_builder import ( | ||
ACTION_EMOJI, | ||
ACTIONED_CATEGORY_TO_EMOJI, | ||
|
@@ -372,13 +373,52 @@ def get_action_text(text: str, actions: Sequence[Any], identity: RpcIdentity) -> | |
return action_text | ||
|
||
|
||
def _ignore_action(group: Group) -> SlackMessageAction | None: | ||
if group.issue_category == GroupCategory.FEEDBACK: | ||
return None | ||
|
||
if group.get_status() == GroupStatus.IGNORED: | ||
return SlackMessageAction( | ||
name="status", label="Mark as Ongoing", value="unresolved:ongoing" | ||
) | ||
|
||
return SlackMessageAction(name="status", label="Archive", value="archive_dialog") | ||
|
||
|
||
def _resolve_action(group: Group, project: Project) -> SlackMessageAction: | ||
if group.get_status() == GroupStatus.RESOLVED: | ||
return SlackMessageAction( | ||
name="unresolved:ongoing", label="Unresolve", value="unresolved:ongoing" | ||
) | ||
if not project.flags.has_releases: | ||
return SlackMessageAction(name="status", label="Resolve", value="resolved") | ||
|
||
return SlackMessageAction( | ||
name="status", | ||
label="Resolve", | ||
value="resolve_dialog", | ||
) | ||
|
||
|
||
def _assign_action(group: Group) -> SlackMessageAction: | ||
Comment on lines
+376
to
+403
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved these out from being anonymous closures in a function to named functions that we can now test and enforce contracts |
||
assignee = group.get_assignee() | ||
assign_button = SlackMessageAction( | ||
name="assign", | ||
label="Select Assignee...", | ||
type="select", | ||
selected_options=format_actor_options([assignee], True) if assignee else [], | ||
option_groups=get_option_groups_block_kit(group), | ||
) | ||
return assign_button | ||
|
||
|
||
def build_actions( | ||
group: Group, | ||
project: Project, | ||
text: str, | ||
actions: Sequence[MessageAction] | None = None, | ||
identity: RpcIdentity | None = None, | ||
) -> tuple[Sequence[MessageAction], str, bool]: | ||
) -> tuple[Sequence[SlackMessageAction], str, bool]: | ||
"""Having actions means a button will be shown on the Slack message e.g. ignore, resolve, assign.""" | ||
if actions and identity: | ||
text = get_action_text(text, actions, identity) | ||
|
@@ -387,50 +427,9 @@ def build_actions( | |
return [], text, True | ||
return [], text, False | ||
|
||
status = group.get_status() | ||
|
||
def _ignore_button() -> MessageAction | None: | ||
if group.issue_category == GroupCategory.FEEDBACK: | ||
return None | ||
if status == GroupStatus.IGNORED: | ||
return MessageAction(name="status", label="Mark as Ongoing", value="unresolved:ongoing") | ||
|
||
return MessageAction(name="status", label="Archive", value="archive_dialog") | ||
|
||
def _resolve_button() -> MessageAction: | ||
if status == GroupStatus.RESOLVED: | ||
return MessageAction( | ||
name="unresolved:ongoing", label="Unresolve", value="unresolved:ongoing" | ||
) | ||
if not project.flags.has_releases: | ||
return MessageAction(name="status", label="Resolve", value="resolved") | ||
|
||
return MessageAction( | ||
name="status", | ||
label="Resolve", | ||
value="resolve_dialog", | ||
) | ||
|
||
def _assign_button() -> MessageAction: | ||
assignee = group.get_assignee() | ||
assign_button = MessageAction( | ||
name="assign", | ||
label="Select Assignee...", | ||
type="select", | ||
selected_options=format_actor_options([assignee], True) if assignee else [], | ||
option_groups=get_option_groups_block_kit(group), | ||
) | ||
return assign_button | ||
|
||
action_list = [ | ||
a | ||
for a in [ | ||
_resolve_button(), | ||
_ignore_button(), | ||
_assign_button(), | ||
] | ||
if a is not None | ||
] | ||
action_list = [_resolve_action(group=group, project=project), _assign_action(group=group)] | ||
if (ignore_action := _ignore_action(group=group)) is not None: | ||
action_list.append(ignore_action) | ||
|
||
return action_list, text, False | ||
|
||
|
@@ -596,7 +595,7 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock: | |
"Unresolve", | ||
"Resolve...", | ||
): | ||
actions.append(self.get_button_action(action)) | ||
actions.append(action.get_button()) | ||
elif action.name == "assign": | ||
actions.append( | ||
self.get_external_select_action( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this over from
BlockKitMessageAction
to be more accurate to Slack specific namespace, and added helper functions so it's easier to enact on the object, and add tests to verify functionality and contracts