Skip to content

[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

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/sentry/integrations/slack/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from .message_builder.base.block import * # noqa: F401,F403
from .message_builder.disconnected import * # noqa: F401,F403
from .message_builder.discover import * # noqa: F401,F403
from .message_builder.event import * # noqa: F401,F403
from .message_builder.help import * # noqa: F401,F403
from .message_builder.incidents import * # noqa: F401,F403
from .message_builder.issues import * # noqa: F401,F403
Expand Down
68 changes: 68 additions & 0 deletions src/sentry/integrations/slack/actions/message_action.py
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):
Copy link
Contributor Author

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

"""
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could add some constants here to keep it safer


return button
31 changes: 2 additions & 29 deletions src/sentry/integrations/slack/message_builder/base/base.py
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]:
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
19 changes: 0 additions & 19 deletions src/sentry/integrations/slack/message_builder/event.py

This file was deleted.

91 changes: 45 additions & 46 deletions src/sentry/integrations/slack/message_builder/issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
Expand All @@ -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

Expand Down Expand Up @@ -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(
Expand Down
Loading
Loading