Skip to content

Commit d85728c

Browse files
getsentry-botcathteng
authored andcommitted
Revert "chore(slack): correctly type issue message builder (#74876)"
This reverts commit 1d9d37b. Co-authored-by: cathteng <[email protected]>
1 parent 1b9b392 commit d85728c

File tree

8 files changed

+112
-87
lines changed

8 files changed

+112
-87
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ module = [
300300
"sentry.integrations.pipeline",
301301
"sentry.integrations.slack.actions.form",
302302
"sentry.integrations.slack.integration",
303+
"sentry.integrations.slack.message_builder.issues",
303304
"sentry.integrations.slack.message_builder.notifications.digest",
304305
"sentry.integrations.slack.message_builder.notifications.issues",
305306
"sentry.integrations.slack.notifications",

src/sentry/integrations/message_builder.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@
2020

2121

2222
def format_actor_options(
23-
actors: Sequence[Team | RpcUser], is_slack: bool = False
23+
actors: Sequence[Team | RpcUser], use_block_kit: bool = False
2424
) -> Sequence[Mapping[str, str]]:
2525
sort_func: Callable[[Mapping[str, str]], Any] = lambda actor: actor["text"]
26-
if is_slack:
26+
if use_block_kit:
2727
sort_func = lambda actor: actor["text"]["text"]
28-
return sorted((format_actor_option(actor, is_slack) for actor in actors), key=sort_func)
28+
return sorted((format_actor_option(actor, use_block_kit) for actor in actors), key=sort_func)
2929

3030

31-
def format_actor_option(actor: Team | RpcUser, is_slack: bool = False) -> Mapping[str, str]:
31+
def format_actor_option(actor: Team | RpcUser, use_block_kit: bool = False) -> Mapping[str, str]:
3232
if isinstance(actor, RpcUser):
33-
if is_slack:
33+
if use_block_kit:
3434
return {
3535
"text": {
3636
"type": "plain_text",
@@ -40,8 +40,8 @@ def format_actor_option(actor: Team | RpcUser, is_slack: bool = False) -> Mappin
4040
}
4141

4242
return {"text": actor.get_display_name(), "value": f"user:{actor.id}"}
43-
elif isinstance(actor, Team):
44-
if is_slack:
43+
if isinstance(actor, Team):
44+
if use_block_kit:
4545
return {
4646
"text": {
4747
"type": "plain_text",
@@ -51,6 +51,8 @@ def format_actor_option(actor: Team | RpcUser, is_slack: bool = False) -> Mappin
5151
}
5252
return {"text": f"#{actor.slug}", "value": f"team:{actor.id}"}
5353

54+
raise NotImplementedError
55+
5456

5557
def build_attachment_title(obj: Group | GroupEvent) -> str:
5658
ev_metadata = obj.get_event_metadata()

src/sentry/integrations/slack/message_builder/issues.py

Lines changed: 75 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
import logging
44
from collections.abc import Mapping, Sequence
55
from datetime import datetime
6-
from typing import Any, TypedDict
6+
from typing import Any
77

88
import orjson
99
from django.core.exceptions import ObjectDoesNotExist
1010
from sentry_relay.processing import parse_release
1111

1212
from sentry import tagstore
1313
from sentry.api.endpoints.group_details import get_group_global_count
14-
from sentry.constants import LOG_LEVELS
14+
from sentry.constants import LOG_LEVELS_MAP
1515
from sentry.eventstore.models import GroupEvent
1616
from sentry.identity.services.identity import RpcIdentity, identity_service
1717
from sentry.integrations.message_builder import (
@@ -109,9 +109,9 @@ def build_assigned_text(identity: RpcIdentity, assignee: str) -> str | None:
109109
except ObjectDoesNotExist:
110110
return None
111111

112-
if isinstance(assigned_actor, Team):
112+
if actor.is_team:
113113
assignee_text = f"#{assigned_actor.slug}"
114-
elif isinstance(assigned_actor, RpcUser):
114+
elif actor.is_user:
115115
assignee_identity = identity_service.get_identity(
116116
filter={
117117
"provider_id": identity.idp_id,
@@ -147,20 +147,40 @@ def build_action_text(identity: RpcIdentity, action: MessageAction) -> str | Non
147147
return f"*Issue {status} by <@{identity.external_id}>*"
148148

149149

150-
def format_release_tag(value: str, event: GroupEvent | None) -> str:
151-
"""Format the release tag using the short version and make it a link"""
152-
if not event:
153-
return ""
150+
def build_tag_fields(
151+
event_for_tags: Any, tags: set[str] | None = None
152+
) -> Sequence[Mapping[str, str | bool]]:
153+
fields = []
154+
if tags:
155+
event_tags = event_for_tags.tags if event_for_tags else []
156+
for key, value in event_tags:
157+
std_key = tagstore.backend.get_standardized_key(key)
158+
if std_key not in tags:
159+
continue
154160

161+
labeled_value = tagstore.backend.get_tag_value_label(key, value)
162+
fields.append(
163+
{
164+
"title": std_key.encode("utf-8"),
165+
"value": labeled_value.encode("utf-8"),
166+
"short": True,
167+
}
168+
)
169+
return fields
170+
171+
172+
def format_release_tag(value: str, event: GroupEvent | Group):
173+
"""Format the release tag using the short version and make it a link"""
155174
path = f"/releases/{value}/"
156175
url = event.project.organization.absolute_url(path)
157176
release_description = parse_release(value, json_loads=orjson.loads).get("description")
158177
return f"<{url}|{release_description}>"
159178

160179

161180
def get_tags(
162-
event_for_tags: GroupEvent | None,
163-
tags: set[str] | list[tuple[str]] | None = None,
181+
group: Group,
182+
event_for_tags: Any,
183+
tags: set[str] | None = None,
164184
) -> Sequence[Mapping[str, str | bool]]:
165185
"""Get tag keys and values for block kit"""
166186
fields = []
@@ -223,30 +243,41 @@ def get_context(group: Group) -> str:
223243
return context_text.rstrip()
224244

225245

226-
class OptionGroup(TypedDict):
227-
label: Mapping[str, str]
228-
options: Sequence[Mapping[str, Any]]
246+
def get_option_groups_block_kit(group: Group) -> Sequence[Mapping[str, Any]]:
247+
all_members = group.project.get_members_as_rpc_users()
248+
members = list({m.id: m for m in all_members}.values())
249+
teams = group.project.teams.all()
250+
251+
option_groups = []
252+
if teams:
253+
team_options = format_actor_options(teams, True)
254+
option_groups.append(
255+
{"label": {"type": "plain_text", "text": "Teams"}, "options": team_options}
256+
)
229257

258+
if members:
259+
member_options = format_actor_options(members, True)
260+
option_groups.append(
261+
{"label": {"type": "plain_text", "text": "People"}, "options": member_options}
262+
)
263+
return option_groups
230264

231-
def get_option_groups(group: Group) -> Sequence[OptionGroup]:
265+
266+
def get_group_assignees(group: Group) -> Sequence[Mapping[str, Any]]:
267+
"""Get teams and users that can be issue assignees for block kit"""
232268
all_members = group.project.get_members_as_rpc_users()
233269
members = list({m.id: m for m in all_members}.values())
234270
teams = group.project.teams.all()
235271

236272
option_groups = []
237273
if teams:
238-
team_option_group: OptionGroup = {
239-
"label": {"type": "plain_text", "text": "Teams"},
240-
"options": format_actor_options(teams, True),
241-
}
242-
option_groups.append(team_option_group)
274+
for team in teams:
275+
option_groups.append({"label": team.slug, "value": f"team:{team.id}"})
243276

244277
if members:
245-
member_option_group: OptionGroup = {
246-
"label": {"type": "plain_text", "text": "People"},
247-
"options": format_actor_options(members, True),
248-
}
249-
option_groups.append(member_option_group)
278+
for member in members:
279+
option_groups.append({"label": member.email, "value": f"user:{member.id}"})
280+
250281
return option_groups
251282

252283

@@ -267,23 +298,20 @@ def get_suggested_assignees(
267298
logger.info("Skipping suspect committers because release does not exist.")
268299
except Exception:
269300
logger.exception("Could not get suspect committers. Continuing execution.")
270-
271301
if suggested_assignees:
272302
suggested_assignees = dedupe_suggested_assignees(suggested_assignees)
273303
assignee_texts = []
274-
275304
for assignee in suggested_assignees:
276305
# skip over any suggested assignees that are the current assignee of the issue, if there is any
277-
if assignee.is_team and not (
278-
isinstance(current_assignee, Team) and assignee.id == current_assignee.id
279-
):
280-
assignee_texts.append(f"#{assignee.slug}")
281-
elif assignee.is_user and not (
306+
if assignee.is_user and not (
282307
isinstance(current_assignee, RpcUser) and assignee.id == current_assignee.id
283308
):
284309
assignee_as_user = assignee.resolve()
285-
if isinstance(assignee_as_user, RpcUser):
286-
assignee_texts.append(assignee_as_user.get_display_name())
310+
assignee_texts.append(assignee_as_user.get_display_name())
311+
elif assignee.is_team and not (
312+
isinstance(current_assignee, Team) and assignee.id == current_assignee.id
313+
):
314+
assignee_texts.append(f"#{assignee.slug}")
287315
return assignee_texts
288316
return []
289317

@@ -389,7 +417,7 @@ def _assign_button() -> MessageAction:
389417
label="Select Assignee...",
390418
type="select",
391419
selected_options=format_actor_options([assignee], True) if assignee else [],
392-
option_groups=get_option_groups(group),
420+
option_groups=get_option_groups_block_kit(group),
393421
)
394422
return assign_button
395423

@@ -449,10 +477,10 @@ def escape_text(self) -> bool:
449477

450478
def get_title_block(
451479
self,
480+
rule_id: int,
481+
notification_uuid: str,
452482
event_or_group: GroupEvent | Group,
453483
has_action: bool,
454-
rule_id: int | None = None,
455-
notification_uuid: str | None = None,
456484
) -> SlackBlock:
457485
title_link = get_title_link(
458486
self.group,
@@ -476,7 +504,11 @@ def get_title_block(
476504
else ACTIONED_CATEGORY_TO_EMOJI.get(self.group.issue_category)
477505
)
478506
elif is_error_issue:
479-
level_text = LOG_LEVELS[self.group.level]
507+
level_text = None
508+
for k, v in LOG_LEVELS_MAP.items():
509+
if self.group.level == v:
510+
level_text = k
511+
480512
title_emoji = LEVEL_TO_EMOJI.get(level_text)
481513
else:
482514
title_emoji = CATEGORY_TO_EMOJI.get(self.group.issue_category)
@@ -552,8 +584,7 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock:
552584
# If an event is unspecified, use the tags of the latest event (if one exists).
553585
event_for_tags = self.event or self.group.get_latest_event()
554586

555-
event_or_group: Group | GroupEvent = self.event if self.event is not None else self.group
556-
587+
obj = self.event if self.event is not None else self.group
557588
action_text = ""
558589

559590
if not self.issue_details or (self.recipient and self.recipient.is_team):
@@ -574,9 +605,9 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock:
574605
action_text = get_action_text(self.actions, self.identity)
575606
has_action = True
576607

577-
blocks = [self.get_title_block(event_or_group, has_action, rule_id, notification_uuid)]
608+
blocks = [self.get_title_block(rule_id, notification_uuid, obj, has_action)]
578609

579-
if culprit_block := self.get_culprit_block(event_or_group):
610+
if culprit_block := self.get_culprit_block(obj):
580611
blocks.append(culprit_block)
581612

582613
# build up text block
@@ -589,7 +620,7 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock:
589620
blocks.append(self.get_markdown_block(action_text))
590621

591622
# build tags block
592-
tags = get_tags(event_for_tags, self.tags)
623+
tags = get_tags(self.group, event_for_tags, self.tags)
593624
if tags:
594625
blocks.append(self.get_tags_block(tags))
595626

@@ -656,7 +687,7 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock:
656687

657688
return self._build_blocks(
658689
*blocks,
659-
fallback_text=self.build_fallback_text(event_or_group, project.slug),
660-
block_id=block_id,
690+
fallback_text=self.build_fallback_text(obj, project.slug),
691+
block_id=orjson.dumps(block_id).decode(),
661692
skip_fallback=self.skip_fallback,
662693
)

src/sentry/integrations/slack/webhooks/options_load.py

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import re
44
from collections.abc import Mapping, Sequence
5-
from typing import Any, TypedDict
5+
from typing import Any
66

77
import orjson
88
from rest_framework import status
@@ -20,11 +20,6 @@
2020
from ..utils import logger
2121

2222

23-
class OptionGroup(TypedDict):
24-
label: Mapping[str, str]
25-
options: Sequence[Mapping[str, Any]]
26-
27-
2823
@region_silo_endpoint
2924
class SlackOptionsLoadEndpoint(Endpoint):
3025
owner = ApiOwner.ECOSYSTEM
@@ -74,17 +69,15 @@ def get_filtered_option_groups(
7469

7570
option_groups = []
7671
if filtered_teams:
77-
team_options_group: OptionGroup = {
78-
"label": {"type": "plain_text", "text": "Teams"},
79-
"options": format_actor_options(filtered_teams, True),
80-
}
81-
option_groups.append(team_options_group)
72+
team_options = format_actor_options(filtered_teams, True)
73+
option_groups.append(
74+
{"label": {"type": "plain_text", "text": "Teams"}, "options": team_options}
75+
)
8276
if filtered_members:
83-
member_options_group: OptionGroup = {
84-
"label": {"type": "plain_text", "text": "People"},
85-
"options": format_actor_options(filtered_members, True),
86-
}
87-
option_groups.append(member_options_group)
77+
member_options = format_actor_options(filtered_members, True)
78+
option_groups.append(
79+
{"label": {"type": "plain_text", "text": "People"}, "options": member_options}
80+
)
8881
return option_groups
8982

9083
# XXX(isabella): atm this endpoint is used only for the assignment dropdown on issue alerts

src/sentry/notifications/utils/participants.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from django.db.models import Q
99

1010
from sentry import features
11-
from sentry.eventstore.models import GroupEvent
1211
from sentry.integrations.types import ExternalProviders
1312
from sentry.integrations.utils.providers import get_provider_enum_from_string
1413
from sentry.models.commit import Commit
@@ -263,7 +262,7 @@ def get_owner_reason(
263262
return None
264263

265264

266-
def get_suspect_commit_users(project: Project, event: Event | GroupEvent) -> list[RpcUser]:
265+
def get_suspect_commit_users(project: Project, event: Event) -> list[RpcUser]:
267266
"""
268267
Returns a list of users that are suspect committers for the given event.
269268
@@ -286,7 +285,7 @@ def get_suspect_commit_users(project: Project, event: Event | GroupEvent) -> lis
286285
return [committer for committer in suspect_committers if committer.id in in_project_user_ids]
287286

288287

289-
def dedupe_suggested_assignees(suggested_assignees: Iterable[Actor]) -> list[Actor]:
288+
def dedupe_suggested_assignees(suggested_assignees: Iterable[Actor]) -> Iterable[Actor]:
290289
return list({assignee.id: assignee for assignee in suggested_assignees}.values())
291290

292291

src/sentry/utils/committers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ def get_event_file_committers(
293293

294294

295295
def get_serialized_event_file_committers(
296-
project: Project, event: Event | GroupEvent, frame_limit: int = 25
296+
project: Project, event: Event, frame_limit: int = 25
297297
) -> Sequence[AuthorCommitsSerialized]:
298298

299299
group_owners = GroupOwner.objects.filter(

tests/sentry/integrations/slack/notifications/test_issue_alert.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
old_get_tags = get_tags
4141

4242

43-
def fake_get_tags(event_for_tags, tags):
44-
return old_get_tags(event_for_tags, None)
43+
def fake_get_tags(group, event_for_tags, tags):
44+
return old_get_tags(group, event_for_tags, None)
4545

4646

4747
class SlackIssueAlertNotificationTest(SlackActivityNotificationTest, PerformanceIssueTestCase):

0 commit comments

Comments
 (0)