Skip to content

feat(np): Adds renderer for MSTeams issue alerts#112890

Draft
GabeVillalobos wants to merge 1 commit intomasterfrom
gv/add_msteams_issue_renderer
Draft

feat(np): Adds renderer for MSTeams issue alerts#112890
GabeVillalobos wants to merge 1 commit intomasterfrom
gv/add_msteams_issue_renderer

Conversation

@GabeVillalobos
Copy link
Copy Markdown
Member

Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. and is gonna need some rights from me in order to utilize my contributions in this here PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.

@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Apr 13, 2026
return MSTeamsMessageBuilder().build(
title=cls.build_title(group=group, issue_url=issue_url),
fields=fields,
actions=cls.build_actions(issue_url=issue_url),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build_actions called without required group parameter causes TypeError

On line 89, cls.build_actions(issue_url=issue_url) is called but the method signature on line 159 requires both group: Group and issue_url: str parameters. This will raise TypeError: build_actions() missing 1 required keyword-only argument: 'group' when rendering any MSTeams issue notification. The group variable is available in scope and should be passed.

Verification

Read the full file to confirm the method signature at line 159 is def build_actions(cls, *, group: Group, issue_url: str) -> list[Action] and the call at line 89 only passes issue_url=issue_url. The group parameter is required (keyword-only after *) but not provided.

Suggested fix: Pass the group parameter to build_actions

Suggested change
actions=cls.build_actions(issue_url=issue_url),
actions=cls.build_actions(group=group, issue_url=issue_url),

Identified by Warden sentry-backend-bugs · 259-7AC

Comment on lines 161 to 162
event_data = invocation.event_data

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code: Registry.get() never returns None, so ValueError is never raised

In issue_notification_data_factory, the code checks if handler is None after calling issue_alert_handler_registry.get(action.type). However, Registry.get() raises NoRegistrationExistsError when the key is not found - it never returns None. This means the ValueError with the custom error message is dead code and will never execute. If an unsupported action type is passed, callers receive NoRegistrationExistsError instead of the intended ValueError.

Verification

Read sentry/utils/registry.py lines 45-48 confirming Registry.get() raises NoRegistrationExistsError on missing keys. The None check at line 161-162 is unreachable.

Identified by Warden sentry-backend-bugs · DTW-E3M

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 14, 2026

Backend Test Failures

Failures on 80664fb in this run:

tests/sentry/api/endpoints/test_project_rule_actions.py::ProjectRuleActionsEndpointWorkflowEngineTest::test_name_action_defaultlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/api/endpoints/test_project_rule_actions.py:114: in test_name_action_default
    self.get_success_response(self.organization.slug, self.project.slug, actions=action_data)
src/sentry/testutils/cases.py:641: in get_success_response
    assert_status_code(response, 200, 300)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=400, "application/json">
E   assert 400 < 300
E    +  where 400 = <Response status_code=400, "application/json">.status_code
tests/sentry/api/endpoints/test_project_rule_actions.py::ProjectRuleActionsEndpointWorkflowEngineTest::test_sample_event_raises_exceptions_workflow_enginelog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/api/endpoints/test_project_rule_actions.py:147: in test_sample_event_raises_exceptions_workflow_engine
    assert mock_create_issue.call_count == 1
E   AssertionError: assert 0 == 1
E    +  where 0 = <MagicMock name='create_issue' id='140569120777824'>.call_count
tests/sentry/mail/test_actions.py::NotifyLegacyEmailTest::test_full_integrationlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/mail/test_actions.py:190: in test_full_integration
    assert len(mail.outbox) == 1
E   assert 0 == 1
E    +  where 0 = len([])
E    +    where [] = mail.outbox
tests/sentry/mail/test_actions.py::NotifyLegacyEmailTest::test_hack_mail_workflowlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/mail/test_actions.py:295: in test_hack_mail_workflow
    assert len(mail.outbox) == 3
E   assert 0 == 3
E    +  where 0 = len([])
E    +    where [] = mail.outbox
tests/sentry/notifications/platform/msteams/renderers/test_issue.py::IssueMSTeamsRendererTest::test_render_produces_cardlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/notifications/platform/msteams/renderers/test_issue.py:146: in test_render_produces_card
    result = IssueMSTeamsRenderer.render(
src/sentry/notifications/platform/msteams/renderers/issue.py:89: in render
    actions=cls.build_actions(issue_url=issue_url),
E   TypeError: IssueMSTeamsRenderer.build_actions() missing 1 required keyword-only argument: 'group'
tests/sentry/notifications/test_notifications.py::ActivityNotificationTest::test_sends_issue_notificationlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/notifications/test_notifications.py:633: in test_sends_issue_notification
    msg = mail.outbox[0]
E   IndexError: list index out of range
tests/sentry/workflow_engine/endpoints/test_organization_test_fire_action.py::TestFireActionsEndpointTest::test_updates_action_with_validated_datalog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/workflow_engine/endpoints/test_organization_test_fire_action.py:304: in test_updates_action_with_validated_data
    self.get_success_response(self.organization.slug, actions=action_data)
src/sentry/testutils/cases.py:641: in get_success_response
    assert_status_code(response, 200, 300)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=400, "application/json">
E   assert 400 < 300
E    +  where 400 = <Response status_code=400, "application/json">.status_code
tests/sentry/workflow_engine/handlers/action/test_action_handlers.py::TestNotificationActionHandler::test_execute_error_group_typelog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/workflow_engine/handlers/action/test_action_handlers.py:36: in test_execute_error_group_type
    self.action.trigger(self.event_data, notification_uuid=notification_uuid)
src/sentry/workflow_engine/models/action.py:140: in trigger
    handler.execute(invocation)
src/sentry/integrations/discord/handlers/discord_handler.py:57: in execute
    execute_via_group_type_registry(invocation)
src/sentry/notifications/notification_action/utils.py:61: in execute_via_group_type_registry
    return execute_via_issue_alert_handler(invocation)
src/sentry/notifications/notification_action/utils.py:82: in execute_via_issue_alert_handler
    issue_data = issue_notification_data_factory(invocation)
src/sentry/notifications/notification_action/utils.py:167: in issue_notification_data_factory
    rule_instance = handler.create_rule_instance_from_action(
src/sentry/notifications/notification_action/types.py:198: in create_rule_instance_from_action
    "actions": [cls.build_rule_action_blob(action, detector.project.organization.id)],
src/sentry/notifications/notification_action/types.py:175: in build_rule_action_blob
    blob.update(cls.get_integration_id(action, mapping))
src/sentry/notifications/notification_action/types.py:122: in get_integration_id
    raise ValueError(f"No integration id found for action type: {action.type}")
E   ValueError: No integration id found for action type: discord
tests/sentry/api/endpoints/test_project_rule_actions.py::ProjectRuleActionsEndpointWorkflowEngineTest::test_actionslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/api/endpoints/test_project_rule_actions.py:74: in test_actions
    self.get_success_response(self.organization.slug, self.project.slug, actions=action_data)
src/sentry/testutils/cases.py:641: in get_success_response
    assert_status_code(response, 200, 300)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=400, "application/json">
E   assert 400 < 300
E    +  where 400 = <Response status_code=400, "application/json">.status_code
tests/sentry/api/endpoints/test_project_rule_actions.py::ProjectRuleActionsEndpointWorkflowEngineTest::test_email_actionlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/api/endpoints/test_project_rule_actions.py:181: in test_email_action
    self.get_success_response(self.organization.slug, self.project.slug, actions=action_data)
src/sentry/testutils/cases.py:641: in get_success_response
    assert_status_code(response, 200, 300)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=400, "application/json">
E   assert 400 < 300
E    +  where 400 = <Response status_code=400, "application/json">.status_code
tests/sentry/api/endpoints/test_project_rule_actions.py::ProjectRuleActionsEndpointWorkflowEngineTest::test_sentry_app_actionlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/api/endpoints/test_project_rule_actions.py:249: in test_sentry_app_action
    self.get_success_response(self.organization.slug, self.project.slug, actions=action_data)
src/sentry/testutils/cases.py:641: in get_success_response
    assert_status_code(response, 200, 300)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=400, "application/json">
E   assert 400 < 300
E    +  where 400 = <Response status_code=400, "application/json">.status_code
tests/sentry/mail/test_actions.py::NotifyEmailTest::test_full_integration_all_members_fallthroughlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/mail/test_actions.py:213: in test_full_integration_all_members_fallthrough
    assert len(mail.outbox) == 1
E   assert 0 == 1
E    +  where 0 = len([])
E    +    where [] = mail.outbox
tests/sentry/mail/test_actions.py::NotifyEmailTest::test_hack_mail_workflowlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/mail/test_actions.py:295: in test_hack_mail_workflow
    assert len(mail.outbox) == 3
E   assert 0 == 3
E    +  where 0 = len([])
E    +    where [] = mail.outbox
tests/sentry/mail/test_actions.py::NotifyLegacyEmailTest::test_full_integration_fallthrough_not_providedlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/mail/test_actions.py:254: in test_full_integration_fallthrough_not_provided
    assert len(mail.outbox) == 1
E   assert 0 == 1
E    +  where 0 = len([])
E    +    where [] = mail.outbox
tests/sentry/mail/test_actions.py::NotifyLegacyEmailTest::test_legacy_full_integration_fallthrough_not_providedlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/mail/test_actions.py:427: in test_legacy_full_integration_fallthrough_not_provided
    assert len(mail.outbox) == 1
E   assert 0 == 1
E    +  where 0 = len([])
E    +    where [] = mail.outbox
tests/sentry/mail/test_actions.py::NotifyLegacyEmailTest::test_legacy_full_integration_performancelog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/mail/test_actions.py:464: in test_legacy_full_integration_performance
    assert len(mail.outbox) == 1
E   assert 0 == 1
E    +  where 0 = len([])
E    +    where [] = mail.outbox
tests/sentry/mail/test_actions.py::NotifyLegacyEmailTest::test_legacy_hack_mail_workflowlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/mail/test_actions.py:504: in test_legacy_hack_mail_workflow
    assert len(mail.outbox) == 3
E   assert 0 == 3
E    +  where 0 = len([])
E    +    where [] = mail.outbox
tests/sentry/workflow_engine/endpoints/test_organization_test_fire_action.py::TestFireActionsEndpointTest::test_pagerduty_actionlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/workflow_engine/endpoints/test_organization_test_fire_action.py:97: in test_pagerduty_action
    response = self.get_success_response(self.organization.slug, actions=action_data)
src/sentry/testutils/cases.py:641: in get_success_response
    assert_status_code(response, 200, 300)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=400, "application/json">
E   assert 400 < 300
E    +  where 400 = <Response status_code=400, "application/json">.status_code
tests/sentry/workflow_engine/endpoints/test_organization_test_fire_action.py::TestFireActionsEndpointTest::test_plugin_notify_event_actionlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/workflow_engine/endpoints/test_organization_test_fire_action.py:130: in test_plugin_notify_event_action
    response = self.get_success_response(self.organization.slug, actions=action_data)
src/sentry/testutils/cases.py:641: in get_success_response
    assert_status_code(response, 200, 300)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=400, "application/json">
E   assert 400 < 300
E    +  where 400 = <Response status_code=400, "application/json">.status_code
tests/sentry/mail/test_actions.py::NotifyEmailTest::test_full_integrationlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/mail/test_actions.py:190: in test_full_integration
    assert len(mail.outbox) == 1
E   assert 0 == 1
E    +  where 0 = len([])
E    +    where [] = mail.outbox
tests/sentry/mail/test_actions.py::NotifyEmailTest::test_full_integration_fallthrough_not_providedlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/mail/test_actions.py:254: in test_full_integration_fallthrough_not_provided
    assert len(mail.outbox) == 1
E   assert 0 == 1
E    +  where 0 = len([])
E    +    where [] = mail.outbox
tests/sentry/mail/test_actions.py::NotifyLegacyEmailTest::test_full_integration_all_members_fallthroughlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/mail/test_actions.py:213: in test_full_integration_all_members_fallthrough
    assert len(mail.outbox) == 1
E   assert 0 == 1
E    +  where 0 = len([])
E    +    where [] = mail.outbox
tests/sentry/mail/test_actions.py::NotifyLegacyEmailTest::test_legacy_full_integrationlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/mail/test_actions.py:343: in test_legacy_full_integration
    assert len(mail.outbox) == 1
E   assert 0 == 1
E    +  where 0 = len([])
E    +    where [] = mail.outbox
tests/sentry/mail/test_actions.py::NotifyLegacyEmailTest::test_legacy_full_integration_fallthroughlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/mail/test_actions.py:372: in test_legacy_full_integration_fallthrough
    assert len(mail.outbox) == 1
E   assert 0 == 1
E    +  where 0 = len([])
E    +    where [] = mail.outbox
tests/sentry/notifications/platform/msteams/renderers/test_issue.py::IssueMSTeamsRendererTest::test_render_with_tagslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/notifications/platform/msteams/renderers/test_issue.py:160: in test_render_with_tags
    result = IssueMSTeamsRenderer.render(
src/sentry/notifications/platform/msteams/renderers/issue.py:89: in render
    actions=cls.build_actions(issue_url=issue_url),
E   TypeError: IssueMSTeamsRenderer.build_actions() missing 1 required keyword-only argument: 'group'
tests/sentry/rules/actions/test_notify_event_service.py::NotifyEventServiceWebhookActionTest::test_applies_correctly_for_legacy_webhooks_acilog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/rules/actions/test_notify_event_service.py:132: in test_applies_correctly_for_legacy_webhooks_aci
    assert len(responses.calls) == 1
E   assert 0 == 1
E    +  where 0 = len(<responses.CallList object at 0x7fb2d96601a0>)
E    +    where <responses.CallList object at 0x7fb2d96601a0> = responses.calls
tests/sentry/workflow_engine/endpoints/test_organization_test_fire_action.py::TestFireActionsEndpointTest::test_action_with_integration_form_errorlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/workflow_engine/endpoints/test_organization_test_fire_action.py:168: in test_action_with_integration_form_error
    assert response.data == {"actions": [str(form_errors)]}
E   assert {'actions': [... 'abc-1234'"]} == {'actions': [...ent wrong'}"]}
E     
E     Differing items:
E     {'actions': ["An unexpected error occurred. Error ID: 'abc-1234'"]} != {'actions': ["{'broken': 'something went wrong'}"]}
E     
E     Full diff:
E       {
E           'actions': [
E     -         "{'broken': 'something went wrong'}",
E     +         "An unexpected error occurred. Error ID: 'abc-1234'",
E           ],
E       }
tests/sentry/workflow_engine/endpoints/test_organization_test_fire_action.py::TestFireActionsEndpointTest::test_action_with_unexpected_errorlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/workflow_engine/endpoints/test_organization_test_fire_action.py:205: in test_action_with_unexpected_error
    assert mock_create_issue.call_count == 1
E   AssertionError: assert 0 == 1
E    +  where 0 = <MagicMock name='create_issue' id='140474859643840'>.call_count
tests/sentry/workflow_engine/handlers/action/test_action_handlers.py::TestNotificationActionHandler::test_execute_metric_alert_typelog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/workflow_engine/handlers/action/test_action_handlers.py:73: in test_execute_metric_alert_type
    self.action.trigger(self.event_data, notification_uuid=str(uuid.uuid4()))
src/sentry/workflow_engine/models/action.py:140: in trigger
    handler.execute(invocation)
src/sentry/integrations/discord/handlers/discord_handler.py:57: in execute
    execute_via_group_type_registry(invocation)
src/sentry/notifications/notification_action/utils.py:61: in execute_via_group_type_registry
    return execute_via_issue_alert_handler(invocation)
src/sentry/notifications/notification_action/utils.py:82: in execute_via_issue_alert_handler
    issue_data = issue_notification_data_factory(invocation)
src/sentry/notifications/notification_action/utils.py:167: in issue_notification_data_factory
    rule_instance = handler.create_rule_instance_from_action(
src/sentry/notifications/notification_action/types.py:198: in create_rule_instance_from_action
    "actions": [cls.build_rule_action_blob(action, detector.project.organization.id)],
src/sentry/notifications/notification_action/types.py:175: in build_rule_action_blob
    blob.update(cls.get_integration_id(action, mapping))
src/sentry/notifications/notification_action/types.py:122: in get_integration_id
    raise ValueError(f"No integration id found for action type: {action.type}")
E   ValueError: No integration id found for action type: discord
tests/sentry/workflow_engine/endpoints/validators/actions/test_sentry_app.py::TestSentryAppActionValidator::test_validate_settings_action_triggerlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/workflow_engine/endpoints/validators/actions/test_sentry_app.py:139: in test_validate_settings_action_trigger
    action.trigger(
src/sentry/workflow_engine/models/action.py:140: in trigger
    handler.execute(invocation)
src/sentry/notifications/notification_action/action_handler_registry/sentry_app_handler.py:48: in execute
    execute_via_group_type_registry(invocation)
src/sentry/notifications/notification_action/utils.py:61: in execute_via_group_type_registry
    return execute_via_issue_alert_handler(invocation)
src/sentry/notifications/notification_action/utils.py:112: in execute_via_issue_alert_handler
    raise ValueError(f"Unsupported action type: {action.type}")
E   ValueError: Unsupported action type: sentry_app
tests/sentry/workflow_engine/handlers/action/test_action_handlers.py::TestNotificationActionHandler::test_execute_unknown_detectorlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/workflow_engine/handlers/action/test_action_handlers.py:99: in test_execute_unknown_detector
    mock_logger.warning.assert_called_once_with(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:988: in assert_called_once_with
    raise AssertionError(msg)
E   AssertionError: Expected 'warning' to be called once. Called 0 times.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant