diff --git a/src/sentry/features/temporary.py b/src/sentry/features/temporary.py index 05cf5bf6374b40..c2256c8ae6a53f 100644 --- a/src/sentry/features/temporary.py +++ b/src/sentry/features/temporary.py @@ -402,6 +402,8 @@ def register_temporary_features(manager: FeatureManager) -> None: manager.add("organizations:autofix-on-explorer", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) # Enable Seer Workflows in Slack manager.add("organizations:seer-slack-workflows", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) + # Enable new compact issue alert UI in Slack + manager.add("organizations:slack-compact-alerts", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) # Enable search query builder boolean operator select feature manager.add("organizations:search-query-builder-add-boolean-operator-select", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) # Enable search query builder conditionals in combobox menus diff --git a/src/sentry/integrations/slack/message_builder/issues.py b/src/sentry/integrations/slack/message_builder/issues.py index adae703f42cb76..4ca0f06fb197e9 100644 --- a/src/sentry/integrations/slack/message_builder/issues.py +++ b/src/sentry/integrations/slack/message_builder/issues.py @@ -505,6 +505,9 @@ def __init__( self.skip_fallback = skip_fallback self.notes = notes self.issue_summary: dict[str, Any] | None = None + self._is_compact = features.has( + "organizations:slack-compact-alerts", self.group.organization + ) def get_title_block( self, @@ -515,6 +518,8 @@ def get_title_block( summary_headline = self.get_issue_summary_headline(event_or_group) title = summary_headline or build_attachment_title(event_or_group) title_emojis = self.get_title_emoji(has_action) + if self._is_compact: + title = build_attachment_title(event_or_group) title_text = f"{title_emojis} <{title_link}|*{escape_slack_text(title)}*>" return self.get_markdown_block(title_text) @@ -538,6 +543,7 @@ def get_title_emoji(self, has_action: bool) -> str: return " ".join(title_emojis) + # Can be removed when 'slack-compact-alerts' is GA def get_issue_summary_headline(self, event_or_group: Event | GroupEvent | Group) -> str | None: if self.issue_summary is None: return None @@ -571,13 +577,18 @@ def get_issue_summary_text(self) -> str | None: if not parts: return None - return escape_slack_markdown_text("\n\n".join(parts)) + + if self._is_compact: + return f"*Initial Guess*: {escape_slack_markdown_text(' '.join(parts))}" + else: + return escape_slack_markdown_text("\n\n".join(parts)) def get_culprit_block(self, event_or_group: Event | GroupEvent | Group) -> SlackBlock | None: if event_or_group.culprit and isinstance(event_or_group.culprit, str): return self.get_context_block(event_or_group.culprit) return None + # 'small' param can be removed when 'slack-compact-alerts' is GA def get_text_block(self, text, small: bool = False) -> SlackBlock: if self.group.issue_category == GroupCategory.FEEDBACK: max_block_text_length = USER_FEEDBACK_MAX_BLOCK_TEXT_LENGTH @@ -589,12 +600,26 @@ def get_text_block(self, text, small: bool = False) -> SlackBlock: else: return self.get_context_block(text) + # Can be removed when 'slack-compact-alerts' is GA def get_suggested_assignees_block(self, suggested_assignees: list[str]) -> SlackBlock: suggested_assignee_text = "Suggested Assignees: " for assignee in suggested_assignees: suggested_assignee_text += assignee + ", " return self.get_context_block(suggested_assignee_text[:-2]) # get rid of comma at the end + def get_group_context_block(self, suggested_assignees: list[str]) -> SlackBlock | None: + """Combine stats (events, users, state, first seen) with suggested assignees in one context block.""" + context_text = get_context(self.group, self.rules) + + if suggested_assignees: + suggested_text = ", ".join(suggested_assignees) + context_text += f" Suggested: {suggested_text}" + + if not context_text: + return None + + return self.get_context_block(context_text) + def get_footer(self) -> SlackBlock: # This link does not contain user input (it's a static label and a url), must not escape it. replay_link = build_attachment_replay_link( @@ -719,8 +744,9 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock: if culprit_block := self.get_culprit_block(event_or_group): blocks.append(culprit_block) - # Use issue summary if available, otherwise use the default text - if summary_text := self.get_issue_summary_text(): + # Use issue summary if available (and not flagged for compact alerts), otherwise use the default text + summary_text = self.get_issue_summary_text() + if summary_text and not self._is_compact: blocks.append(self.get_text_block(summary_text, small=True)) else: text = text.lstrip(" ") @@ -728,9 +754,6 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock: if text: blocks.append(self.get_text_block(text)) - if self.actions: - blocks.append(self.get_markdown_block(action_text)) - # set up block id block_id = {"issue": self.group.id} if rule_id: @@ -741,18 +764,34 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock: if tags: blocks.append(self.get_tags_block(tags, block_id)) - # add event count, user count, substate, first seen - context = get_context(self.group, self.rules) - if context: - blocks.append(self.get_context_block(context)) - - # build actions - actions = [] try: assignee = self.group.get_assignee() except Actor.InvalidActor: assignee = None + suggested_assignees = [] + if event_for_tags: + suggested_assignees = get_suggested_assignees( + self.group.project, event_for_tags, assignee + ) + + if self._is_compact: + if group_context_block := self.get_group_context_block( + suggested_assignees=suggested_assignees + ): + blocks.append(group_context_block) + else: + # add event count, user count, substate, first seen + context = get_context(self.group, self.rules) + if context: + blocks.append(self.get_context_block(context)) + + # If an action has been taken, add the text for it (e.g. "Issue resolved by <@U0234567890>") + if self.actions: + blocks.append(self.get_markdown_block(action_text)) + + # build actions + actions = [] for action in payload_actions: if action.label in ( "Archive", @@ -787,19 +826,17 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock: action_block = {"type": "actions", "elements": [action for action in actions]} blocks.append(action_block) - # suggested assignees - suggested_assignees = [] - if event_for_tags: - suggested_assignees = get_suggested_assignees( - self.group.project, event_for_tags, assignee - ) - if len(suggested_assignees) > 0: + if self._is_compact and summary_text: + blocks.append(self.get_context_block(summary_text)) + + if not self._is_compact and len(suggested_assignees) > 0: blocks.append(self.get_suggested_assignees_block(suggested_assignees)) - # add suspect commit info - suspect_commit_text = get_suspect_commit_text(self.group) - if suspect_commit_text: - blocks.append(self.get_context_block(suspect_commit_text)) + if not self._is_compact: + # add suspect commit info + suspect_commit_text = get_suspect_commit_text(self.group) + if suspect_commit_text: + blocks.append(self.get_context_block(suspect_commit_text)) # add notes if self.notes: @@ -808,7 +845,8 @@ def build(self, notification_uuid: str | None = None) -> SlackBlock: # build footer block blocks.append(self.get_footer()) - blocks.append(self.get_divider()) + if not self._is_compact: + blocks.append(self.get_divider()) chart_block = ImageBlockBuilder(group=self.group).build_image_block() if chart_block: diff --git a/tests/sentry/integrations/slack/test_message_builder.py b/tests/sentry/integrations/slack/test_message_builder.py index 8b4805db3d53b7..0d8260581376d3 100644 --- a/tests/sentry/integrations/slack/test_message_builder.py +++ b/tests/sentry/integrations/slack/test_message_builder.py @@ -1139,6 +1139,160 @@ def test_build_group_block_with_ai_summary_without_org_acknowledgement( blocks = SlackIssuesMessageBuilder(group).build() assert "IntegrationError" in blocks["blocks"][0]["text"]["text"] + @with_feature("organizations:slack-compact-alerts") + def test_compact_alerts_basic_layout(self) -> None: + """ + Test that with the slack-compact-alerts flag enabled, the message uses a compact layout: + - No divider at the end + - Context block includes stats + """ + event = self.store_event( + data={ + "event_id": "a" * 32, + "message": "IntegrationError", + "fingerprint": ["group-1"], + "exception": { + "values": [ + { + "type": "IntegrationError", + "value": "Identity not found.", + } + ] + }, + "level": "error", + }, + project_id=self.project.id, + ) + assert event.group + group = event.group + group.type = ErrorGroupType.type_id + group.save() + + self.project.flags.has_releases = True + self.project.save(update_fields=["flags"]) + + blocks = SlackIssuesMessageBuilder(group).build() + + assert "IntegrationError" in blocks["blocks"][0]["text"]["text"] + assert blocks["blocks"][-1]["type"] != "divider" + + @with_feature("organizations:slack-compact-alerts") + @override_options({"alerts.issue_summary_timeout": 5}) + @with_feature({"organizations:gen-ai-features"}) + @patch( + "sentry.integrations.utils.issue_summary_for_alerts.get_seer_org_acknowledgement", + return_value=True, + ) + def test_compact_alerts_with_ai_summary(self, mock_get_seer_org_acknowledgement) -> None: + """ + Test that with the slack-compact-alerts flag enabled and AI summary available: + - Title uses build_attachment_title() (not AI headline) + - Issue summary appears after action buttons as context with "Initial Guess:" prefix + """ + event = self.store_event( + data={ + "event_id": "a" * 32, + "message": "IntegrationError", + "fingerprint": ["group-1"], + "exception": { + "values": [ + { + "type": "IntegrationError", + "value": "Identity not found.", + } + ] + }, + "level": "error", + }, + project_id=self.project.id, + ) + assert event.group + group = event.group + group.type = ErrorGroupType.type_id + group.save() + + self.project.flags.has_releases = True + self.project.save(update_fields=["flags"]) + self.project.update_option("sentry:seer_scanner_automation", True) + self.organization.update_option("sentry:enable_seer_enhanced_alerts", True) + + mock_summary = { + "headline": "Custom AI Title", + "whatsWrong": "This is what's wrong with the issue", + "trace": "This is trace information", + "possibleCause": "This is a possible cause", + } + patch_path = "sentry.integrations.utils.issue_summary_for_alerts.get_issue_summary" + serializer_path = "sentry.api.serializers.models.event.EventSerializer.serialize" + serializer_mock = Mock(return_value={}) + + with ( + patch(patch_path) as mock_get_summary, + patch(serializer_path, serializer_mock), + ): + mock_get_summary.return_value = (mock_summary, 200) + + blocks = SlackIssuesMessageBuilder(group).build() + + title_block = blocks["blocks"][0]["text"]["text"] + assert "IntegrationError" in title_block + assert "Custom AI Title" not in title_block + + found_initial_guess = False + for block in blocks["blocks"]: + if block.get("type") == "context": + elements = block.get("elements", []) + for element in elements: + if "Initial Guess" in element.get("text", ""): + found_initial_guess = True + assert "This is a possible cause" in element["text"] + break + + assert found_initial_guess, "Initial Guess context block not found" + assert blocks["blocks"][-1]["type"] != "divider" + + @with_feature("organizations:slack-compact-alerts") + def test_compact_alerts_context_includes_suggested_assignees(self) -> None: + """ + Test that with compact alerts, suggested assignees are included in the context block + rather than in a separate block. + """ + event = self.store_event( + data={ + "event_id": "a" * 32, + "message": "Hello world", + "fingerprint": ["group-1"], + "level": "error", + "stacktrace": {"frames": [{"filename": "foo.py"}]}, + }, + project_id=self.project.id, + ) + assert event.group + group = event.group + + # Set up ownership to create suggested assignees + rule = Rule(Matcher("path", "*"), [Owner("team", self.team.slug)]) + ProjectOwnership.objects.create(project_id=self.project.id, schema=dump_schema([rule])) + + blocks = SlackIssuesMessageBuilder(group, event).build() + + found_suggested_in_context = False + found_old_suggested_assignees = False + for block in blocks["blocks"]: + if block.get("type") == "context": + elements = block.get("elements", []) + for element in elements: + text = element.get("text", "") + if "Suggested:" in text: + found_suggested_in_context = True + if "Suggested Assignees:" in text: + found_old_suggested_assignees = True + + assert found_suggested_in_context, "Suggested assignees should be in context block" + assert ( + not found_old_suggested_assignees + ), "Old 'Suggested Assignees:' format should not appear" + class BuildGroupAttachmentReplaysTest(TestCase): @patch("sentry.models.group.Group.has_replays") diff --git a/tests/sentry/integrations/slack/webhooks/actions/test_status.py b/tests/sentry/integrations/slack/webhooks/actions/test_status.py index 8e46b71e969fd8..24d3213ab206ba 100644 --- a/tests/sentry/integrations/slack/webhooks/actions/test_status.py +++ b/tests/sentry/integrations/slack/webhooks/actions/test_status.py @@ -262,7 +262,7 @@ def test_archive_issue_until_escalating( expect_status = f"*Issue archived by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) assert "via" not in blocks[4]["elements"][0]["text"] assert ":white_circle:" in blocks[0]["text"]["text"] @@ -288,7 +288,7 @@ def test_archive_issue_until_escalating_through_unfurl(self, mock_tags: MagicMoc expect_status = f"*Issue archived by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) @patch("sentry.integrations.slack.message_builder.issues.get_tags", return_value=[]) def test_archive_issue_until_condition_met(self, mock_tags: MagicMock) -> None: @@ -306,7 +306,7 @@ def test_archive_issue_until_condition_met(self, mock_tags: MagicMock) -> None: expect_status = f"*Issue archived by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) @patch("sentry.integrations.slack.message_builder.issues.get_tags", return_value=[]) def test_archive_issue_until_condition_met_through_unfurl(self, mock_tags: MagicMock) -> None: @@ -327,7 +327,7 @@ def test_archive_issue_until_condition_met_through_unfurl(self, mock_tags: Magic expect_status = f"*Issue archived by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) @patch("sentry.integrations.slack.message_builder.issues.get_tags", return_value=[]) def test_archive_issue_forever(self, mock_tags: MagicMock) -> None: @@ -343,7 +343,7 @@ def test_archive_issue_forever(self, mock_tags: MagicMock) -> None: expect_status = f"*Issue archived by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) @patch("sentry.models.organization.Organization.has_access", return_value=False) def test_archive_issue_forever_error(self, mock_access: MagicMock) -> None: @@ -374,7 +374,7 @@ def test_archive_issue_forever_through_unfurl(self, mock_tags: MagicMock) -> Non expect_status = f"*Issue archived by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) def test_archive_issue_with_additional_user_auth(self) -> None: """ @@ -397,7 +397,7 @@ def test_archive_issue_with_additional_user_auth(self) -> None: expect_status = f"*Issue archived by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) def test_archive_issue_with_additional_user_auth_through_unfurl(self) -> None: """ @@ -420,7 +420,7 @@ def test_archive_issue_with_additional_user_auth_through_unfurl(self) -> None: expect_status = f"*Issue archived by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) @patch("sentry.integrations.slack.message_builder.issues.get_tags", return_value=[]) def test_unarchive_issue(self, mock_tags: MagicMock) -> None: @@ -445,7 +445,7 @@ def test_unarchive_issue(self, mock_tags: MagicMock) -> None: expect_status = f"*Issue re-opened by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) @patch("sentry.integrations.slack.message_builder.issues.get_tags", return_value=[]) def test_unarchive_issue_through_unfurl(self, mock_tags: MagicMock) -> None: @@ -471,7 +471,7 @@ def test_unarchive_issue_through_unfurl(self, mock_tags: MagicMock) -> None: expect_status = f"*Issue re-opened by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) @patch("sentry.integrations.slack.message_builder.issues.get_tags", return_value=[]) def test_assign_issue(self, mock_tags: MagicMock) -> None: @@ -488,7 +488,7 @@ def test_assign_issue(self, mock_tags: MagicMock) -> None: text = self.mock_post.call_args.kwargs["text"] expect_status = f"*Issue assigned to {user2.get_display_name()} by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status), text + assert blocks[3]["text"]["text"].endswith(expect_status), text assert ":white_circle:" in blocks[0]["text"]["text"] # Assign to team @@ -500,7 +500,7 @@ def test_assign_issue(self, mock_tags: MagicMock) -> None: text = self.mock_post.call_args.kwargs["text"] expect_status = f"*Issue assigned to #{self.team.slug} by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status), text + assert blocks[3]["text"]["text"].endswith(expect_status), text assert ":white_circle:" in blocks[0]["text"]["text"] # Assert group assignment activity recorded @@ -543,7 +543,7 @@ def test_assign_issue_error(self, mock_logger: MagicMock) -> None: assert GroupAssignee.objects.filter(group=self.group, user_id=user2.id).exists() expect_status = f"*Issue assigned to {user2.get_display_name()} by <@{self.external_id}>*" assert self.notification_text in resp.data["blocks"][1]["text"]["text"] - assert resp.data["blocks"][2]["text"]["text"].endswith(expect_status), resp.data["text"] + assert resp.data["blocks"][3]["text"]["text"].endswith(expect_status), resp.data["text"] assert ":white_circle:" in resp.data["blocks"][0]["text"]["text"] # Assert group assignment activity recorded @@ -569,7 +569,7 @@ def test_assign_issue_through_unfurl(self) -> None: text = self.mock_post.call_args.kwargs["text"] expect_status = f"*Issue assigned to {user2.get_display_name()} by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status), text + assert blocks[3]["text"]["text"].endswith(expect_status), text # Assign to team self.assign_issue(original_message, self.team, payload_data) @@ -578,7 +578,7 @@ def test_assign_issue_through_unfurl(self) -> None: text = self.mock_post.call_args.kwargs["text"] expect_status = f"*Issue assigned to #{self.team.slug} by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status), text + assert blocks[3]["text"]["text"].endswith(expect_status), text # Assert group assignment activity recorded group_activity = list(Activity.objects.filter(group=self.group)) @@ -643,7 +643,7 @@ def test_assign_issue_user_has_identity(self) -> None: f"*Issue assigned to <@{user2_identity.external_id}> by <@{self.external_id}>*" ) assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status), text + assert blocks[3]["text"]["text"].endswith(expect_status), text def test_assign_issue_user_has_identity_through_unfurl(self) -> None: user2 = self.create_user(is_superuser=False) @@ -665,7 +665,7 @@ def test_assign_issue_user_has_identity_through_unfurl(self) -> None: f"*Issue assigned to <@{user2_identity.external_id}> by <@{self.external_id}>*" ) assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status), text + assert blocks[3]["text"]["text"].endswith(expect_status), text def test_assign_user_with_multiple_identities(self) -> None: org2 = self.create_organization(owner=None) @@ -691,7 +691,7 @@ def test_assign_user_with_multiple_identities(self) -> None: assignee=self.external_id ) assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status), text + assert blocks[3]["text"]["text"].endswith(expect_status), text def test_assign_user_with_multiple_identities_through_unfurl(self) -> None: org2 = self.create_organization(owner=None) @@ -718,7 +718,7 @@ def test_assign_user_with_multiple_identities_through_unfurl(self) -> None: assignee=self.external_id ) assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status), text + assert blocks[3]["text"]["text"].endswith(expect_status), text @patch("sentry.integrations.utils.metrics.EventLifecycle.record_event") @patch("sentry.integrations.slack.message_builder.issues.get_tags", return_value=[]) @@ -735,7 +735,7 @@ def test_resolve_issue(self, mock_tags: MagicMock, mock_record: MagicMock) -> No expect_status = f"*Issue resolved by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"] == expect_status + assert blocks[3]["text"]["text"] == expect_status assert ":white_circle:" in blocks[0]["text"]["text"] @with_feature("organizations:workflow-engine-single-process-workflows") @@ -757,7 +757,7 @@ def test_resolve_issue_during_aci_rollout( expect_status = f"*Issue resolved by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"] == expect_status + assert blocks[3]["text"]["text"] == expect_status assert ":white_circle:" in blocks[0]["text"]["text"] @patch("sentry.integrations.utils.metrics.EventLifecycle.record_event") @@ -791,7 +791,7 @@ def test_resolve_perf_issue(self, mock_tags: MagicMock, mock_record: MagicMock) "db - SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author` WHERE `books_author`.`id` = %s LIMIT 21" in blocks[2]["text"]["text"] ) - assert blocks[3]["text"]["text"] == expect_status + assert blocks[4]["text"]["text"] == expect_status assert ":white_circle: :chart_with_upwards_trend:" in blocks[0]["text"]["text"] @patch("sentry.integrations.slack.message_builder.issues.get_tags", return_value=[]) @@ -809,7 +809,7 @@ def test_resolve_issue_through_unfurl(self, mock_tags: MagicMock) -> None: expect_status = f"*Issue resolved by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"] == expect_status + assert blocks[3]["text"]["text"] == expect_status @patch("sentry.integrations.utils.metrics.EventLifecycle.record_event") @patch("sentry.integrations.slack.message_builder.issues.get_tags", return_value=[]) @@ -836,7 +836,7 @@ def test_resolve_issue_in_current_release( expect_status = f"*Issue resolved by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) @patch("sentry.integrations.slack.message_builder.issues.get_tags", return_value=[]) def test_resolve_issue_in_current_release_through_unfurl(self, mock_tags: MagicMock) -> None: @@ -861,7 +861,7 @@ def test_resolve_issue_in_current_release_through_unfurl(self, mock_tags: MagicM expect_status = f"*Issue resolved by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) @patch("sentry.integrations.utils.metrics.EventLifecycle.record_event") @patch("sentry.integrations.slack.message_builder.issues.get_tags", return_value=[]) @@ -885,7 +885,7 @@ def test_resolve_in_next_release(self, mock_tags: MagicMock, mock_record: MagicM expect_status = f"*Issue resolved by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) @patch("sentry.integrations.slack.message_builder.issues.get_tags", return_value=[]) def test_resolve_in_next_release_through_unfurl(self, mock_tags: MagicMock) -> None: @@ -909,7 +909,7 @@ def test_resolve_in_next_release_through_unfurl(self, mock_tags: MagicMock) -> N expect_status = f"*Issue resolved by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) @patch( "slack_sdk.web.WebClient.views_update", @@ -960,7 +960,7 @@ def test_response_differs_on_bot_message(self, _mock_view_updates_open: MagicMoc expect_status = f"*Issue archived by <@{self.external_id}>*" assert self.notification_text in blocks[1]["text"]["text"] - assert blocks[2]["text"]["text"].endswith(expect_status) + assert blocks[3]["text"]["text"].endswith(expect_status) @patch( "slack_sdk.web.WebClient.views_update",