From a4bab89652361bcc07633043f3bcddd683b4eb4a Mon Sep 17 00:00:00 2001 From: "sentry[bot]" <39604003+sentry[bot]@users.noreply.github.com> Date: Sat, 5 Sep 2026 10:46:46 +0000 Subject: [PATCH] fix(integrations): Prevent AssertionError in MS Teams webhook for rules without legacy_rule_id --- .../integrations/messaging/message_builder.py | 7 ++--- .../slack/message_builder/util.py | 2 +- .../notifications/notifications/rules.py | 2 +- .../msteams/test_message_builder.py | 28 +++++++++++++++++++ 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/sentry/integrations/messaging/message_builder.py b/src/sentry/integrations/messaging/message_builder.py index 23b69af3f4e8..6d27bfe163a1 100644 --- a/src/sentry/integrations/messaging/message_builder.py +++ b/src/sentry/integrations/messaging/message_builder.py @@ -14,7 +14,7 @@ from sentry.notifications.notifications.base import BaseNotification from sentry.notifications.notifications.rules import AlertRuleNotification from sentry.notifications.utils.links import create_link_to_workflow -from sentry.notifications.utils.rules import get_key_from_rule_data, get_rule_or_workflow_id +from sentry.notifications.utils.rules import get_rule_or_workflow_id from sentry.services.eventstore.models import Event, GroupEvent from sentry.users.services.user import RpcUser from sentry.utils.http import absolute_uri @@ -247,10 +247,9 @@ def build_attachment_replay_link( return None -def build_rule_url(rule: Any, group: Group, project: Project) -> str: +def build_rule_url(rule_id: str, group: Group, project: Project) -> str: org_slug = group.organization.slug project_slug = project.slug - rule_id = get_key_from_rule_data(rule, "legacy_rule_id") rule_url = f"/organizations/{org_slug}/issues/alerts/rules/{project_slug}/{rule_id}/details/" return absolute_uri(rule_url) @@ -269,7 +268,7 @@ def build_footer( case "workflow_id": rule_url = absolute_uri(create_link_to_workflow(group.organization.slug, value)) case "legacy_rule_id": - rule_url = build_rule_url(rules[0], group, project) + rule_url = build_rule_url(value, group, project) # If this notification is triggered via the "Send Test Notification" # button then the label is not defined, but the url works. diff --git a/src/sentry/integrations/slack/message_builder/util.py b/src/sentry/integrations/slack/message_builder/util.py index adb5876e9420..a621e372e044 100644 --- a/src/sentry/integrations/slack/message_builder/util.py +++ b/src/sentry/integrations/slack/message_builder/util.py @@ -23,7 +23,7 @@ def build_slack_footer( case "workflow_id": rule_url = absolute_uri(create_link_to_workflow(group.organization.slug, value)) case "legacy_rule_id": - rule_url = build_rule_url(rules[0], group, project) + rule_url = build_rule_url(value, group, project) # If this notification is triggered via the "Send Test Notification" # button then the label is not defined, but the url works. text = rules[0].label if rules[0].label else "Test Alert" diff --git a/src/sentry/notifications/notifications/rules.py b/src/sentry/notifications/notifications/rules.py index e2e45ff48da7..fe201eb016da 100644 --- a/src/sentry/notifications/notifications/rules.py +++ b/src/sentry/notifications/notifications/rules.py @@ -302,7 +302,7 @@ def get_notification_title( case "workflow_id": rule_url = absolute_uri(create_link_to_workflow(self.organization.slug, value)) case "legacy_rule_id": - rule_url = build_rule_url(self.rules[0], self.group, self.project) + rule_url = build_rule_url(value, self.group, self.project) title_str += ( f" {self.format_url(text=self.rules[0].label, url=rule_url, provider=provider)}" diff --git a/tests/sentry/integrations/msteams/test_message_builder.py b/tests/sentry/integrations/msteams/test_message_builder.py index e808cf7cd71d..4361ecfe45ea 100644 --- a/tests/sentry/integrations/msteams/test_message_builder.py +++ b/tests/sentry/integrations/msteams/test_message_builder.py @@ -466,6 +466,34 @@ def test_assigned_issue_message(self) -> None: assert ActionType.SUBMIT == assign_action["type"] assert "Unassign" == assign_action["title"] + def test_issue_message_builder_rule_without_legacy_rule_id_in_actions(self) -> None: + """Regression test: build_group_card must not raise AssertionError when a rule's + actions list does not contain 'legacy_rule_id'. The rule's own id should be used + as a fallback.""" + rule_without_legacy_id = self.create_project_rule( + name="rule_no_legacy", include_legacy_rule_id=False + ) + + # Verify that legacy_rule_id is indeed absent from the rule actions data + assert rule_without_legacy_id.data["actions"][0].get("legacy_rule_id") is None + + # This must not raise an AssertionError + issue_card = MSTeamsIssueMessageBuilder( + group=self.group1, + event=self.event1, + rules=[rule_without_legacy_id], + integration=self.integration, + ).build_group_card() + + body = issue_card["body"] + footer = body[2] + assert _is_column_set_block(footer) + issue_id_and_rule = footer["columns"][1]["items"][0] + assert _is_text_block(issue_id_and_rule) + # The footer should contain the rule label and a URL using str(rule.id) + assert "rule_no_legacy" in issue_id_and_rule["text"] + assert str(rule_without_legacy_id.id) in issue_id_and_rule["text"] + class MSTeamsNotificationMessageBuilderTest(TestCase): def setUp(self) -> None: