-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
chore(slack): correctly type issue message builder #74876
Changes from all commits
2925122
a568577
119df71
ee12ae8
551bae0
0fc3108
1d42f66
173a67d
a11fd07
e8f5bc1
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 |
---|---|---|
|
@@ -3,15 +3,15 @@ | |
import logging | ||
from collections.abc import Mapping, Sequence | ||
from datetime import datetime | ||
from typing import Any | ||
from typing import Any, TypedDict | ||
|
||
import orjson | ||
from django.core.exceptions import ObjectDoesNotExist | ||
from sentry_relay.processing import parse_release | ||
|
||
from sentry import tagstore | ||
from sentry.api.endpoints.group_details import get_group_global_count | ||
from sentry.constants import LOG_LEVELS_MAP | ||
from sentry.constants import LOG_LEVELS | ||
from sentry.eventstore.models import GroupEvent | ||
from sentry.identity.services.identity import RpcIdentity, identity_service | ||
from sentry.integrations.message_builder import ( | ||
|
@@ -109,9 +109,9 @@ def build_assigned_text(identity: RpcIdentity, assignee: str) -> str | None: | |
except ObjectDoesNotExist: | ||
return None | ||
|
||
if actor.is_team: | ||
if isinstance(assigned_actor, Team): | ||
assignee_text = f"#{assigned_actor.slug}" | ||
elif actor.is_user: | ||
elif isinstance(assigned_actor, RpcUser): | ||
assignee_identity = identity_service.get_identity( | ||
filter={ | ||
"provider_id": identity.idp_id, | ||
|
@@ -147,40 +147,20 @@ def build_action_text(identity: RpcIdentity, action: MessageAction) -> str | Non | |
return f"*Issue {status} by <@{identity.external_id}>*" | ||
|
||
|
||
def build_tag_fields( | ||
event_for_tags: Any, tags: set[str] | None = None | ||
) -> Sequence[Mapping[str, str | bool]]: | ||
fields = [] | ||
if tags: | ||
event_tags = event_for_tags.tags if event_for_tags else [] | ||
for key, value in event_tags: | ||
std_key = tagstore.backend.get_standardized_key(key) | ||
if std_key not in tags: | ||
continue | ||
|
||
labeled_value = tagstore.backend.get_tag_value_label(key, value) | ||
fields.append( | ||
{ | ||
"title": std_key.encode("utf-8"), | ||
"value": labeled_value.encode("utf-8"), | ||
"short": True, | ||
} | ||
) | ||
return fields | ||
|
||
|
||
def format_release_tag(value: str, event: GroupEvent | Group): | ||
def format_release_tag(value: str, event: GroupEvent | None) -> str: | ||
"""Format the release tag using the short version and make it a link""" | ||
if not event: | ||
return "" | ||
|
||
path = f"/releases/{value}/" | ||
url = event.project.organization.absolute_url(path) | ||
release_description = parse_release(value, json_loads=orjson.loads).get("description") | ||
return f"<{url}|{release_description}>" | ||
|
||
|
||
def get_tags( | ||
group: Group, | ||
event_for_tags: Any, | ||
tags: set[str] | None = None, | ||
event_for_tags: GroupEvent | None, | ||
tags: set[str] | list[tuple[str]] | None = None, | ||
) -> Sequence[Mapping[str, str | bool]]: | ||
"""Get tag keys and values for block kit""" | ||
fields = [] | ||
|
@@ -243,41 +223,30 @@ def get_context(group: Group) -> str: | |
return context_text.rstrip() | ||
|
||
|
||
def get_option_groups_block_kit(group: Group) -> Sequence[Mapping[str, Any]]: | ||
all_members = group.project.get_members_as_rpc_users() | ||
members = list({m.id: m for m in all_members}.values()) | ||
teams = group.project.teams.all() | ||
|
||
option_groups = [] | ||
if teams: | ||
team_options = format_actor_options(teams, True) | ||
option_groups.append( | ||
{"label": {"type": "plain_text", "text": "Teams"}, "options": team_options} | ||
) | ||
class OptionGroup(TypedDict): | ||
label: Mapping[str, str] | ||
options: Sequence[Mapping[str, Any]] | ||
|
||
if members: | ||
member_options = format_actor_options(members, True) | ||
option_groups.append( | ||
{"label": {"type": "plain_text", "text": "People"}, "options": member_options} | ||
) | ||
return option_groups | ||
|
||
|
||
def get_group_assignees(group: Group) -> Sequence[Mapping[str, Any]]: | ||
"""Get teams and users that can be issue assignees for block kit""" | ||
def get_option_groups(group: Group) -> Sequence[OptionGroup]: | ||
all_members = group.project.get_members_as_rpc_users() | ||
members = list({m.id: m for m in all_members}.values()) | ||
teams = group.project.teams.all() | ||
|
||
option_groups = [] | ||
if teams: | ||
for team in teams: | ||
option_groups.append({"label": team.slug, "value": f"team:{team.id}"}) | ||
team_option_group: OptionGroup = { | ||
"label": {"type": "plain_text", "text": "Teams"}, | ||
"options": format_actor_options(teams, True), | ||
} | ||
option_groups.append(team_option_group) | ||
|
||
if members: | ||
for member in members: | ||
option_groups.append({"label": member.email, "value": f"user:{member.id}"}) | ||
|
||
member_option_group: OptionGroup = { | ||
"label": {"type": "plain_text", "text": "People"}, | ||
"options": format_actor_options(members, True), | ||
} | ||
option_groups.append(member_option_group) | ||
return option_groups | ||
|
||
|
||
|
@@ -298,20 +267,23 @@ def get_suggested_assignees( | |
logger.info("Skipping suspect committers because release does not exist.") | ||
except Exception: | ||
logger.exception("Could not get suspect committers. Continuing execution.") | ||
|
||
if suggested_assignees: | ||
suggested_assignees = dedupe_suggested_assignees(suggested_assignees) | ||
assignee_texts = [] | ||
|
||
for assignee in suggested_assignees: | ||
# skip over any suggested assignees that are the current assignee of the issue, if there is any | ||
if assignee.is_user and not ( | ||
isinstance(current_assignee, RpcUser) and assignee.id == current_assignee.id | ||
): | ||
assignee_as_user = assignee.resolve() | ||
assignee_texts.append(assignee_as_user.get_display_name()) | ||
elif assignee.is_team and not ( | ||
if assignee.is_team and not ( | ||
isinstance(current_assignee, Team) and assignee.id == current_assignee.id | ||
): | ||
assignee_texts.append(f"#{assignee.slug}") | ||
elif assignee.is_user and not ( | ||
isinstance(current_assignee, RpcUser) and assignee.id == current_assignee.id | ||
): | ||
assignee_as_user = assignee.resolve() | ||
if isinstance(assignee_as_user, RpcUser): | ||
assignee_texts.append(assignee_as_user.get_display_name()) | ||
return assignee_texts | ||
return [] | ||
|
||
|
@@ -417,7 +389,7 @@ def _assign_button() -> MessageAction: | |
label="Select Assignee...", | ||
type="select", | ||
selected_options=format_actor_options([assignee], True) if assignee else [], | ||
option_groups=get_option_groups_block_kit(group), | ||
option_groups=get_option_groups(group), | ||
) | ||
return assign_button | ||
|
||
|
@@ -477,10 +449,10 @@ def escape_text(self) -> bool: | |
|
||
def get_title_block( | ||
self, | ||
rule_id: int, | ||
notification_uuid: str, | ||
event_or_group: GroupEvent | Group, | ||
has_action: bool, | ||
rule_id: int | None = None, | ||
notification_uuid: str | None = None, | ||
) -> SlackBlock: | ||
title_link = get_title_link( | ||
self.group, | ||
|
@@ -504,11 +476,7 @@ def get_title_block( | |
else ACTIONED_CATEGORY_TO_EMOJI.get(self.group.issue_category) | ||
) | ||
elif is_error_issue: | ||
level_text = None | ||
for k, v in LOG_LEVELS_MAP.items(): | ||
if self.group.level == v: | ||
level_text = k | ||
|
||
level_text = LOG_LEVELS[self.group.level] | ||
title_emoji = LEVEL_TO_EMOJI.get(level_text) | ||
else: | ||
title_emoji = CATEGORY_TO_EMOJI.get(self.group.issue_category) | ||
|
@@ -584,7 +552,8 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock: | |
# If an event is unspecified, use the tags of the latest event (if one exists). | ||
event_for_tags = self.event or self.group.get_latest_event() | ||
|
||
obj = self.event if self.event is not None else self.group | ||
event_or_group: Group | GroupEvent = self.event if self.event is not None else self.group | ||
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. I don't think this should need an annotation? mypy should be able to infer this union I think 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. it gets angry if i take it away 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. what's the error? -- does it narrow it to 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. yes |
||
|
||
action_text = "" | ||
|
||
if not self.issue_details or (self.recipient and self.recipient.is_team): | ||
|
@@ -605,9 +574,9 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock: | |
action_text = get_action_text(self.actions, self.identity) | ||
has_action = True | ||
|
||
blocks = [self.get_title_block(rule_id, notification_uuid, obj, has_action)] | ||
blocks = [self.get_title_block(event_or_group, has_action, rule_id, notification_uuid)] | ||
|
||
if culprit_block := self.get_culprit_block(obj): | ||
if culprit_block := self.get_culprit_block(event_or_group): | ||
blocks.append(culprit_block) | ||
|
||
# build up text block | ||
|
@@ -620,7 +589,7 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock: | |
blocks.append(self.get_markdown_block(action_text)) | ||
|
||
# build tags block | ||
tags = get_tags(self.group, event_for_tags, self.tags) | ||
tags = get_tags(event_for_tags, self.tags) | ||
if tags: | ||
blocks.append(self.get_tags_block(tags)) | ||
|
||
|
@@ -687,7 +656,7 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock: | |
|
||
return self._build_blocks( | ||
*blocks, | ||
fallback_text=self.build_fallback_text(obj, project.slug), | ||
block_id=orjson.dumps(block_id).decode(), | ||
fallback_text=self.build_fallback_text(event_or_group, project.slug), | ||
block_id=block_id, | ||
skip_fallback=self.skip_fallback, | ||
) |
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.
I think I would probably change the
if
s toelifs
andelse: raise AssertionError('unreachable')