3
3
import logging
4
4
from collections .abc import Mapping , Sequence
5
5
from datetime import datetime
6
- from typing import Any , TypedDict
6
+ from typing import Any
7
7
8
8
import orjson
9
9
from django .core .exceptions import ObjectDoesNotExist
10
10
from sentry_relay .processing import parse_release
11
11
12
12
from sentry import tagstore
13
13
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
15
15
from sentry .eventstore .models import GroupEvent
16
16
from sentry .identity .services .identity import RpcIdentity , identity_service
17
17
from sentry .integrations .message_builder import (
@@ -109,9 +109,9 @@ def build_assigned_text(identity: RpcIdentity, assignee: str) -> str | None:
109
109
except ObjectDoesNotExist :
110
110
return None
111
111
112
- if isinstance ( assigned_actor , Team ) :
112
+ if actor . is_team :
113
113
assignee_text = f"#{ assigned_actor .slug } "
114
- elif isinstance ( assigned_actor , RpcUser ) :
114
+ elif actor . is_user :
115
115
assignee_identity = identity_service .get_identity (
116
116
filter = {
117
117
"provider_id" : identity .idp_id ,
@@ -147,20 +147,40 @@ def build_action_text(identity: RpcIdentity, action: MessageAction) -> str | Non
147
147
return f"*Issue { status } by <@{ identity .external_id } >*"
148
148
149
149
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
154
160
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"""
155
174
path = f"/releases/{ value } /"
156
175
url = event .project .organization .absolute_url (path )
157
176
release_description = parse_release (value , json_loads = orjson .loads ).get ("description" )
158
177
return f"<{ url } |{ release_description } >"
159
178
160
179
161
180
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 ,
164
184
) -> Sequence [Mapping [str , str | bool ]]:
165
185
"""Get tag keys and values for block kit"""
166
186
fields = []
@@ -223,30 +243,41 @@ def get_context(group: Group) -> str:
223
243
return context_text .rstrip ()
224
244
225
245
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
+ )
229
257
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
230
264
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"""
232
268
all_members = group .project .get_members_as_rpc_users ()
233
269
members = list ({m .id : m for m in all_members }.values ())
234
270
teams = group .project .teams .all ()
235
271
236
272
option_groups = []
237
273
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 } " })
243
276
244
277
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
+
250
281
return option_groups
251
282
252
283
@@ -267,23 +298,20 @@ def get_suggested_assignees(
267
298
logger .info ("Skipping suspect committers because release does not exist." )
268
299
except Exception :
269
300
logger .exception ("Could not get suspect committers. Continuing execution." )
270
-
271
301
if suggested_assignees :
272
302
suggested_assignees = dedupe_suggested_assignees (suggested_assignees )
273
303
assignee_texts = []
274
-
275
304
for assignee in suggested_assignees :
276
305
# 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 (
282
307
isinstance (current_assignee , RpcUser ) and assignee .id == current_assignee .id
283
308
):
284
309
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 } " )
287
315
return assignee_texts
288
316
return []
289
317
@@ -389,7 +417,7 @@ def _assign_button() -> MessageAction:
389
417
label = "Select Assignee..." ,
390
418
type = "select" ,
391
419
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 ),
393
421
)
394
422
return assign_button
395
423
@@ -449,10 +477,10 @@ def escape_text(self) -> bool:
449
477
450
478
def get_title_block (
451
479
self ,
480
+ rule_id : int ,
481
+ notification_uuid : str ,
452
482
event_or_group : GroupEvent | Group ,
453
483
has_action : bool ,
454
- rule_id : int | None = None ,
455
- notification_uuid : str | None = None ,
456
484
) -> SlackBlock :
457
485
title_link = get_title_link (
458
486
self .group ,
@@ -476,7 +504,11 @@ def get_title_block(
476
504
else ACTIONED_CATEGORY_TO_EMOJI .get (self .group .issue_category )
477
505
)
478
506
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
+
480
512
title_emoji = LEVEL_TO_EMOJI .get (level_text )
481
513
else :
482
514
title_emoji = CATEGORY_TO_EMOJI .get (self .group .issue_category )
@@ -552,8 +584,7 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock:
552
584
# If an event is unspecified, use the tags of the latest event (if one exists).
553
585
event_for_tags = self .event or self .group .get_latest_event ()
554
586
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
557
588
action_text = ""
558
589
559
590
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:
574
605
action_text = get_action_text (self .actions , self .identity )
575
606
has_action = True
576
607
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 )]
578
609
579
- if culprit_block := self .get_culprit_block (event_or_group ):
610
+ if culprit_block := self .get_culprit_block (obj ):
580
611
blocks .append (culprit_block )
581
612
582
613
# build up text block
@@ -589,7 +620,7 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock:
589
620
blocks .append (self .get_markdown_block (action_text ))
590
621
591
622
# build tags block
592
- tags = get_tags (event_for_tags , self .tags )
623
+ tags = get_tags (self . group , event_for_tags , self .tags )
593
624
if tags :
594
625
blocks .append (self .get_tags_block (tags ))
595
626
@@ -656,7 +687,7 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock:
656
687
657
688
return self ._build_blocks (
658
689
* 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 () ,
661
692
skip_fallback = self .skip_fallback ,
662
693
)
0 commit comments