3
3
import logging
4
4
from collections .abc import Mapping , Sequence
5
5
from datetime import datetime
6
- from typing import Any
6
+ from typing import Any , TypedDict
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_MAP
14
+ from sentry .constants import LOG_LEVELS
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 actor . is_team :
112
+ if isinstance ( assigned_actor , Team ) :
113
113
assignee_text = f"#{ assigned_actor .slug } "
114
- elif actor . is_user :
114
+ elif isinstance ( assigned_actor , RpcUser ) :
115
115
assignee_identity = identity_service .get_identity (
116
116
filter = {
117
117
"provider_id" : identity .idp_id ,
@@ -147,40 +147,20 @@ 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 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
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 ):
150
+ def format_release_tag (value : str , event : GroupEvent | None ) -> str :
173
151
"""Format the release tag using the short version and make it a link"""
152
+ if not event :
153
+ return ""
154
+
174
155
path = f"/releases/{ value } /"
175
156
url = event .project .organization .absolute_url (path )
176
157
release_description = parse_release (value , json_loads = orjson .loads ).get ("description" )
177
158
return f"<{ url } |{ release_description } >"
178
159
179
160
180
161
def get_tags (
181
- group : Group ,
182
- event_for_tags : Any ,
183
- tags : set [str ] | None = None ,
162
+ event_for_tags : GroupEvent | None ,
163
+ tags : set [str ] | list [tuple [str ]] | None = None ,
184
164
) -> Sequence [Mapping [str , str | bool ]]:
185
165
"""Get tag keys and values for block kit"""
186
166
fields = []
@@ -243,41 +223,30 @@ def get_context(group: Group) -> str:
243
223
return context_text .rstrip ()
244
224
245
225
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
- )
226
+ class OptionGroup (TypedDict ):
227
+ label : Mapping [str , str ]
228
+ options : Sequence [Mapping [str , Any ]]
257
229
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
264
230
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"""
231
+ def get_option_groups (group : Group ) -> Sequence [OptionGroup ]:
268
232
all_members = group .project .get_members_as_rpc_users ()
269
233
members = list ({m .id : m for m in all_members }.values ())
270
234
teams = group .project .teams .all ()
271
235
272
236
option_groups = []
273
237
if teams :
274
- for team in teams :
275
- option_groups .append ({"label" : team .slug , "value" : f"team:{ team .id } " })
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 )
276
243
277
244
if members :
278
- for member in members :
279
- option_groups .append ({"label" : member .email , "value" : f"user:{ member .id } " })
280
-
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 )
281
250
return option_groups
282
251
283
252
@@ -298,20 +267,23 @@ def get_suggested_assignees(
298
267
logger .info ("Skipping suspect committers because release does not exist." )
299
268
except Exception :
300
269
logger .exception ("Could not get suspect committers. Continuing execution." )
270
+
301
271
if suggested_assignees :
302
272
suggested_assignees = dedupe_suggested_assignees (suggested_assignees )
303
273
assignee_texts = []
274
+
304
275
for assignee in suggested_assignees :
305
276
# skip over any suggested assignees that are the current assignee of the issue, if there is any
306
- if assignee .is_user and not (
307
- isinstance (current_assignee , RpcUser ) and assignee .id == current_assignee .id
308
- ):
309
- assignee_as_user = assignee .resolve ()
310
- assignee_texts .append (assignee_as_user .get_display_name ())
311
- elif assignee .is_team and not (
277
+ if assignee .is_team and not (
312
278
isinstance (current_assignee , Team ) and assignee .id == current_assignee .id
313
279
):
314
280
assignee_texts .append (f"#{ assignee .slug } " )
281
+ elif assignee .is_user and not (
282
+ isinstance (current_assignee , RpcUser ) and assignee .id == current_assignee .id
283
+ ):
284
+ assignee_as_user = assignee .resolve ()
285
+ if isinstance (assignee_as_user , RpcUser ):
286
+ assignee_texts .append (assignee_as_user .get_display_name ())
315
287
return assignee_texts
316
288
return []
317
289
@@ -417,7 +389,7 @@ def _assign_button() -> MessageAction:
417
389
label = "Select Assignee..." ,
418
390
type = "select" ,
419
391
selected_options = format_actor_options ([assignee ], True ) if assignee else [],
420
- option_groups = get_option_groups_block_kit (group ),
392
+ option_groups = get_option_groups (group ),
421
393
)
422
394
return assign_button
423
395
@@ -477,10 +449,10 @@ def escape_text(self) -> bool:
477
449
478
450
def get_title_block (
479
451
self ,
480
- rule_id : int ,
481
- notification_uuid : str ,
482
452
event_or_group : GroupEvent | Group ,
483
453
has_action : bool ,
454
+ rule_id : int | None = None ,
455
+ notification_uuid : str | None = None ,
484
456
) -> SlackBlock :
485
457
title_link = get_title_link (
486
458
self .group ,
@@ -504,11 +476,7 @@ def get_title_block(
504
476
else ACTIONED_CATEGORY_TO_EMOJI .get (self .group .issue_category )
505
477
)
506
478
elif is_error_issue :
507
- level_text = None
508
- for k , v in LOG_LEVELS_MAP .items ():
509
- if self .group .level == v :
510
- level_text = k
511
-
479
+ level_text = LOG_LEVELS [self .group .level ]
512
480
title_emoji = LEVEL_TO_EMOJI .get (level_text )
513
481
else :
514
482
title_emoji = CATEGORY_TO_EMOJI .get (self .group .issue_category )
@@ -584,7 +552,8 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock:
584
552
# If an event is unspecified, use the tags of the latest event (if one exists).
585
553
event_for_tags = self .event or self .group .get_latest_event ()
586
554
587
- obj = self .event if self .event is not None else self .group
555
+ event_or_group : Group | GroupEvent = self .event if self .event is not None else self .group
556
+
588
557
action_text = ""
589
558
590
559
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:
605
574
action_text = get_action_text (self .actions , self .identity )
606
575
has_action = True
607
576
608
- blocks = [self .get_title_block (rule_id , notification_uuid , obj , has_action )]
577
+ blocks = [self .get_title_block (event_or_group , has_action , rule_id , notification_uuid )]
609
578
610
- if culprit_block := self .get_culprit_block (obj ):
579
+ if culprit_block := self .get_culprit_block (event_or_group ):
611
580
blocks .append (culprit_block )
612
581
613
582
# build up text block
@@ -620,7 +589,7 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock:
620
589
blocks .append (self .get_markdown_block (action_text ))
621
590
622
591
# build tags block
623
- tags = get_tags (self . group , event_for_tags , self .tags )
592
+ tags = get_tags (event_for_tags , self .tags )
624
593
if tags :
625
594
blocks .append (self .get_tags_block (tags ))
626
595
@@ -687,7 +656,7 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock:
687
656
688
657
return self ._build_blocks (
689
658
* blocks ,
690
- fallback_text = self .build_fallback_text (obj , project .slug ),
691
- block_id = orjson . dumps ( block_id ). decode () ,
659
+ fallback_text = self .build_fallback_text (event_or_group , project .slug ),
660
+ block_id = block_id ,
692
661
skip_fallback = self .skip_fallback ,
693
662
)
0 commit comments