diff --git a/src/sentry/notifications/platform/api/endpoints/internal_registered_templates.py b/src/sentry/notifications/platform/api/endpoints/internal_registered_templates.py index 98a1428a8943..9171b4f0c9cf 100644 --- a/src/sentry/notifications/platform/api/endpoints/internal_registered_templates.py +++ b/src/sentry/notifications/platform/api/endpoints/internal_registered_templates.py @@ -104,7 +104,7 @@ def serialize_slack_preview[T: NotificationData]( ) -> dict[str, Any]: data = template.example_data rendered_template = template.render_example() - renderer = SlackNotificationProvider.get_renderer(data=data, category=template.category) + renderer = SlackNotificationProvider.get_renderer(data=data) message = renderer.render(data=data, rendered_template=rendered_template) serialized_blocks = [] diff --git a/src/sentry/notifications/platform/discord/provider.py b/src/sentry/notifications/platform/discord/provider.py index a9e29683ef3b..b92e23fa7a56 100644 --- a/src/sentry/notifications/platform/discord/provider.py +++ b/src/sentry/notifications/platform/discord/provider.py @@ -18,7 +18,6 @@ from sentry.notifications.platform.threading import ThreadContext from sentry.notifications.platform.types import ( LinkTextBlock, - NotificationCategory, NotificationData, NotificationProviderKey, NotificationRenderedTemplate, @@ -40,8 +39,6 @@ class DiscordRenderer(NotificationRenderer[DiscordRenderable]): - provider_key = NotificationProviderKey.DISCORD - @classmethod def render[DataT: NotificationData]( cls, *, data: DataT, rendered_template: NotificationRenderedTemplate @@ -152,21 +149,6 @@ def is_available(cls, *, organization: RpcOrganizationSummary | None = None) -> # TODO(ecosystem): Check for the integration, maybe a feature as well return False - @classmethod - def get_renderer( - cls, *, data: NotificationData, category: NotificationCategory - ) -> type[NotificationRenderer[DiscordRenderable]]: - from sentry.notifications.platform.discord.renderers.issue import IssueDiscordRenderer - from sentry.notifications.platform.discord.renderers.metric_alert import ( - DiscordMetricAlertRenderer, - ) - - if category == NotificationCategory.ISSUE: - return IssueDiscordRenderer - if category == NotificationCategory.METRIC_ALERT: - return DiscordMetricAlertRenderer - return cls.default_renderer - @classmethod def send( cls, diff --git a/src/sentry/notifications/platform/discord/renderers/issue.py b/src/sentry/notifications/platform/discord/renderers/issue.py index 855f2a163cec..6b80b4b62974 100644 --- a/src/sentry/notifications/platform/discord/renderers/issue.py +++ b/src/sentry/notifications/platform/discord/renderers/issue.py @@ -4,6 +4,7 @@ from sentry.integrations.discord.message_builder.issues import DiscordIssuesMessageBuilder from sentry.models.group import Group from sentry.notifications.platform.discord.provider import DiscordRenderable +from sentry.notifications.platform.registry import renderer_registry from sentry.notifications.platform.renderer import NotificationRenderer from sentry.notifications.platform.service import NotificationRenderError from sentry.notifications.platform.templates.issue import IssueNotificationData @@ -11,13 +12,13 @@ NotificationData, NotificationProviderKey, NotificationRenderedTemplate, + NotificationSource, ) from sentry.services.eventstore.models import Event +@renderer_registry.register(NotificationProviderKey.DISCORD, sources=[NotificationSource.ISSUE]) class IssueDiscordRenderer(NotificationRenderer[DiscordRenderable]): - provider_key = NotificationProviderKey.DISCORD - @classmethod def render[DataT: NotificationData]( cls, *, data: DataT, rendered_template: NotificationRenderedTemplate diff --git a/src/sentry/notifications/platform/discord/renderers/metric_alert.py b/src/sentry/notifications/platform/discord/renderers/metric_alert.py index 4b6ab9127693..8c0b07087b98 100644 --- a/src/sentry/notifications/platform/discord/renderers/metric_alert.py +++ b/src/sentry/notifications/platform/discord/renderers/metric_alert.py @@ -8,18 +8,21 @@ from sentry.integrations.discord.message_builder.metric_alerts import get_started_at from sentry.integrations.metric_alerts import get_status_text from sentry.notifications.platform.discord.provider import DiscordRenderable +from sentry.notifications.platform.registry import renderer_registry from sentry.notifications.platform.renderer import NotificationRenderer from sentry.notifications.platform.templates.metric_alert import MetricAlertNotificationData from sentry.notifications.platform.types import ( NotificationData, NotificationProviderKey, NotificationRenderedTemplate, + NotificationSource, ) +@renderer_registry.register( + NotificationProviderKey.DISCORD, sources=[NotificationSource.METRIC_ALERT] +) class DiscordMetricAlertRenderer(NotificationRenderer[DiscordRenderable]): - provider_key = NotificationProviderKey.DISCORD - @classmethod def render[DataT: NotificationData]( cls, *, data: DataT, rendered_template: NotificationRenderedTemplate diff --git a/src/sentry/notifications/platform/email/provider.py b/src/sentry/notifications/platform/email/provider.py index 14db78280d64..0e108f7e9e1a 100644 --- a/src/sentry/notifications/platform/email/provider.py +++ b/src/sentry/notifications/platform/email/provider.py @@ -38,8 +38,6 @@ class EmailRenderer(NotificationRenderer[EmailRenderable]): - provider_key = NotificationProviderKey.EMAIL - @classmethod def render[DataT: NotificationData]( cls, *, data: DataT, rendered_template: NotificationRenderedTemplate diff --git a/src/sentry/notifications/platform/msteams/provider.py b/src/sentry/notifications/platform/msteams/provider.py index e4949ec20b8b..6325e6716d4e 100644 --- a/src/sentry/notifications/platform/msteams/provider.py +++ b/src/sentry/notifications/platform/msteams/provider.py @@ -38,8 +38,6 @@ class MSTeamsRenderer(NotificationRenderer[MSTeamsRenderable]): - provider_key = NotificationProviderKey.MSTEAMS - @classmethod def render[DataT: NotificationData]( cls, *, data: DataT, rendered_template: NotificationRenderedTemplate diff --git a/src/sentry/notifications/platform/provider.py b/src/sentry/notifications/platform/provider.py index 6a1b15adb451..b5e5c3d7f183 100644 --- a/src/sentry/notifications/platform/provider.py +++ b/src/sentry/notifications/platform/provider.py @@ -8,7 +8,6 @@ from sentry.notifications.platform.target import IntegrationNotificationTarget from sentry.notifications.platform.threading import ThreadContext from sentry.notifications.platform.types import ( - NotificationCategory, NotificationData, NotificationProviderKey, NotificationTarget, @@ -106,6 +105,8 @@ class NotificationProvider[RenderableT](Protocol): """ key: NotificationProviderKey + renderer_key: NotificationProviderKey | None = None + """The key renderers are registered against, when it differs from `key`.""" default_renderer: type[NotificationRenderer[RenderableT]] target_class: type[NotificationTarget] target_resource_types: list[NotificationTargetResourceType] @@ -133,15 +134,19 @@ def validate_target(cls, *, target: NotificationTarget) -> None: return @classmethod - def get_renderer( - cls, *, data: NotificationData, category: NotificationCategory - ) -> type[NotificationRenderer[RenderableT]]: + def get_renderer(cls, *, data: NotificationData) -> type[NotificationRenderer[RenderableT]]: """ Returns an instance of a renderer for a given notification, falling back to the default renderer. Override this to method to permit different renderers for the provider, though keep in mind that this may produce inconsistencies between notifications. """ - return cls.default_renderer + # Imported here since the registry imports this module to type its registrations. + from sentry.notifications.platform.registry import renderer_registry + + renderer = renderer_registry.get( + provider_key=cls.renderer_key or cls.key, source=data.source + ) + return renderer or cls.default_renderer @classmethod def is_available(cls, *, organization: RpcOrganizationSummary | None = None) -> bool: diff --git a/src/sentry/notifications/platform/registry.py b/src/sentry/notifications/platform/registry.py index 84d313d4293f..25623cfd014c 100644 --- a/src/sentry/notifications/platform/registry.py +++ b/src/sentry/notifications/platform/registry.py @@ -1,11 +1,17 @@ from __future__ import annotations +from collections.abc import Callable, Sequence from typing import Any from sentry.notifications.platform.provider import NotificationProvider -from sentry.notifications.platform.types import NotificationTemplate +from sentry.notifications.platform.renderer import NotificationRenderer +from sentry.notifications.platform.types import ( + NotificationProviderKey, + NotificationSource, + NotificationTemplate, +) from sentry.organizations.services.organization.model import RpcOrganizationSummary -from sentry.utils.registry import Registry +from sentry.utils.registry import AlreadyRegisteredError, Registry class NotificationProviderRegistry(Registry[type[NotificationProvider[Any]]]): @@ -33,5 +39,68 @@ def get_available( ] +class NotificationRendererRegistry: + """ + A registry for renderers which override a provider's default renderer. Renderers are keyed by + the provider they produce output for, and the notification sources they render. + """ + + def __init__(self) -> None: + self.registrations: dict[ + tuple[NotificationProviderKey, NotificationSource], type[NotificationRenderer[Any]] + ] = {} + self._loaded = False + + def _load(self) -> None: + """ + Import the renderer modules so their registrations take effect. Renderers reach into + integration packages, which read options while being imported, so they cannot be loaded + from `AppConfig.ready()` the way providers and templates are. + """ + if self._loaded: + return + self._loaded = True + + import sentry.notifications.platform.discord.renderers.issue # noqa: F401 + import sentry.notifications.platform.discord.renderers.metric_alert # noqa: F401 + import sentry.notifications.platform.slack.renderers.issue # noqa: F401 + import sentry.notifications.platform.slack.renderers.metric_alert # noqa: F401 + import sentry.notifications.platform.slack.renderers.seer # noqa: F401 + import sentry.notifications.platform.slack.renderers.seer_agent_write_approval # noqa: F401 + + def register[RenderableT]( + self, provider_key: NotificationProviderKey, sources: Sequence[NotificationSource] + ) -> Callable[ + [type[NotificationRenderer[RenderableT]]], type[NotificationRenderer[RenderableT]] + ]: + if not sources: + raise ValueError("At least one notification source is required to register a renderer") + + def inner( + renderer: type[NotificationRenderer[RenderableT]], + ) -> type[NotificationRenderer[RenderableT]]: + for source in sources: + key = (provider_key, source) + if key in self.registrations: + raise AlreadyRegisteredError( + f"A registration already exists for {key}: {self.registrations[key]}" + ) + self.registrations[key] = renderer + return renderer + + return inner + + def get( + self, *, provider_key: NotificationProviderKey, source: NotificationSource + ) -> type[NotificationRenderer[Any]] | None: + """ + Returns the registered renderer for the provider/source pair, or `None` if the provider has + no override and should fall back to its default renderer. + """ + self._load() + return self.registrations.get((provider_key, source)) + + provider_registry = NotificationProviderRegistry() template_registry = Registry[type[NotificationTemplate[Any]]]() +renderer_registry = NotificationRendererRegistry() diff --git a/src/sentry/notifications/platform/renderer.py b/src/sentry/notifications/platform/renderer.py index 9cbea90090bd..b84a890420ea 100644 --- a/src/sentry/notifications/platform/renderer.py +++ b/src/sentry/notifications/platform/renderer.py @@ -2,7 +2,6 @@ from sentry.notifications.platform.types import ( NotificationData, - NotificationProviderKey, NotificationRenderedTemplate, ) @@ -17,8 +16,6 @@ class NotificationRenderer[RenderableT](Protocol): RenderableT is a type that matches the connected provider. """ - provider_key: NotificationProviderKey - @classmethod def render[DataT: NotificationData]( cls, *, data: DataT, rendered_template: NotificationRenderedTemplate diff --git a/src/sentry/notifications/platform/service.py b/src/sentry/notifications/platform/service.py index bbe95c0931b1..72aa5d0715a4 100644 --- a/src/sentry/notifications/platform/service.py +++ b/src/sentry/notifications/platform/service.py @@ -171,7 +171,7 @@ def render_template[RenderableT]( provider: type[NotificationProvider[RenderableT]], ) -> RenderableT: rendered_template = template.render(data=data) - renderer = provider.get_renderer(data=data, category=template.category) + renderer = provider.get_renderer(data=data) return renderer.render(data=data, rendered_template=rendered_template) @staticmethod diff --git a/src/sentry/notifications/platform/slack/provider.py b/src/sentry/notifications/platform/slack/provider.py index d441c674c1c9..ad0663764708 100644 --- a/src/sentry/notifications/platform/slack/provider.py +++ b/src/sentry/notifications/platform/slack/provider.py @@ -32,13 +32,11 @@ from sentry.notifications.platform.threading import ThreadContext from sentry.notifications.platform.types import ( LinkTextBlock, - NotificationCategory, NotificationData, NotificationProviderKey, NotificationRenderedTemplate, NotificationSection, NotificationSectionType, - NotificationSource, NotificationTarget, NotificationTargetResourceType, NotificationTextBlock, @@ -65,8 +63,6 @@ class SlackRenderable(TypedDict): class SlackRenderer(NotificationRenderer[SlackRenderable]): - provider_key = NotificationProviderKey.SLACK - @classmethod def render[DataT: NotificationData]( cls, *, data: DataT, rendered_template: NotificationRenderedTemplate @@ -144,31 +140,6 @@ def is_available(cls, *, organization: RpcOrganizationSummary | None = None) -> # I currently view this as akin to a rollout or feature flag for the registry return False - @classmethod - def get_renderer( - cls, *, data: NotificationData, category: NotificationCategory - ) -> type[NotificationRenderer[SlackRenderable]]: - from sentry.notifications.platform.slack.renderers.issue import ( - IssueSlackRenderer, - ) - from sentry.notifications.platform.slack.renderers.metric_alert import ( - SlackMetricAlertRenderer, - ) - from sentry.notifications.platform.slack.renderers.seer import SeerSlackRenderer - from sentry.notifications.platform.slack.renderers.seer_agent_write_approval import ( - SeerAgentWriteApprovalSlackRenderer, - ) - - if category == NotificationCategory.SEER: - if data.source == NotificationSource.SEER_AGENT_WRITE_APPROVAL: - return SeerAgentWriteApprovalSlackRenderer - return SeerSlackRenderer - if category == NotificationCategory.ISSUE: - return IssueSlackRenderer - if category == NotificationCategory.METRIC_ALERT: - return SlackMetricAlertRenderer - return cls.default_renderer - @classmethod def send( cls, @@ -228,3 +199,4 @@ def _send_with_threading( @provider_registry.register(NotificationProviderKey.SLACK_STAGING) class SlackStagingNotificationProvider(SlackNotificationProvider): key = NotificationProviderKey.SLACK_STAGING + renderer_key = NotificationProviderKey.SLACK diff --git a/src/sentry/notifications/platform/slack/renderers/issue.py b/src/sentry/notifications/platform/slack/renderers/issue.py index 096fd3b73513..76642c29fb1f 100644 --- a/src/sentry/notifications/platform/slack/renderers/issue.py +++ b/src/sentry/notifications/platform/slack/renderers/issue.py @@ -2,6 +2,7 @@ from sentry import eventstore from sentry.models.group import Group +from sentry.notifications.platform.registry import renderer_registry from sentry.notifications.platform.renderer import NotificationRenderer from sentry.notifications.platform.slack.provider import SlackRenderable from sentry.notifications.platform.templates.issue import IssueNotificationData @@ -9,12 +10,12 @@ NotificationData, NotificationProviderKey, NotificationRenderedTemplate, + NotificationSource, ) +@renderer_registry.register(NotificationProviderKey.SLACK, sources=[NotificationSource.ISSUE]) class IssueSlackRenderer(NotificationRenderer[SlackRenderable]): - provider_key = NotificationProviderKey.SLACK - @classmethod def render[DataT: NotificationData]( cls, *, data: DataT, rendered_template: NotificationRenderedTemplate diff --git a/src/sentry/notifications/platform/slack/renderers/metric_alert.py b/src/sentry/notifications/platform/slack/renderers/metric_alert.py index 099a124d544e..1a60a55b53e7 100644 --- a/src/sentry/notifications/platform/slack/renderers/metric_alert.py +++ b/src/sentry/notifications/platform/slack/renderers/metric_alert.py @@ -7,6 +7,7 @@ from sentry.integrations.slack.message_builder.incidents import get_started_at from sentry.integrations.slack.message_builder.types import INCIDENT_COLOR_MAPPING from sentry.integrations.slack.utils.escape import escape_slack_text +from sentry.notifications.platform.registry import renderer_registry from sentry.notifications.platform.renderer import NotificationRenderer from sentry.notifications.platform.slack.provider import SlackRenderable from sentry.notifications.platform.templates.metric_alert import MetricAlertNotificationData @@ -14,12 +15,14 @@ NotificationData, NotificationProviderKey, NotificationRenderedTemplate, + NotificationSource, ) +@renderer_registry.register( + NotificationProviderKey.SLACK, sources=[NotificationSource.METRIC_ALERT] +) class SlackMetricAlertRenderer(NotificationRenderer[SlackRenderable]): - provider_key = NotificationProviderKey.SLACK - @classmethod def render[DataT: NotificationData]( cls, *, data: DataT, rendered_template: NotificationRenderedTemplate diff --git a/src/sentry/notifications/platform/slack/renderers/seer.py b/src/sentry/notifications/platform/slack/renderers/seer.py index 8a6b9c30fe2e..66c567d2653f 100644 --- a/src/sentry/notifications/platform/slack/renderers/seer.py +++ b/src/sentry/notifications/platform/slack/renderers/seer.py @@ -21,6 +21,7 @@ SectionBlock, ) +from sentry.notifications.platform.registry import renderer_registry from sentry.notifications.platform.renderer import NotificationRenderer from sentry.notifications.platform.slack.provider import SlackRenderable from sentry.notifications.platform.templates.seer import ( @@ -32,7 +33,9 @@ ) from sentry.notifications.platform.types import ( NotificationData, + NotificationProviderKey, NotificationRenderedTemplate, + NotificationSource, ) from sentry.seer.autofix.utils import AutofixStoppingPoint, CodingAgentProviderType @@ -85,6 +88,17 @@ class AutofixStageConfig(TypedDict): } +@renderer_registry.register( + NotificationProviderKey.SLACK, + sources=[ + NotificationSource.SEER_AUTOFIX_TRIGGER, + NotificationSource.SEER_AUTOFIX_ERROR, + NotificationSource.SEER_AUTOFIX_SUCCESS, + NotificationSource.SEER_AUTOFIX_UPDATE, + NotificationSource.SEER_AGENT_RESPONSE, + NotificationSource.SEER_AGENT_ERROR, + ], +) class SeerSlackRenderer(NotificationRenderer[SlackRenderable]): @classmethod def render[DataT: NotificationData]( diff --git a/src/sentry/notifications/platform/slack/renderers/seer_agent_write_approval.py b/src/sentry/notifications/platform/slack/renderers/seer_agent_write_approval.py index 9016e8ccc645..7a62987f0252 100644 --- a/src/sentry/notifications/platform/slack/renderers/seer_agent_write_approval.py +++ b/src/sentry/notifications/platform/slack/renderers/seer_agent_write_approval.py @@ -3,6 +3,7 @@ from sentry.integrations.slack.message_builder.routing import encode_action_id from sentry.integrations.slack.message_builder.types import SlackAction +from sentry.notifications.platform.registry import renderer_registry from sentry.notifications.platform.renderer import NotificationRenderer from sentry.notifications.platform.slack.provider import SlackRenderable from sentry.notifications.platform.templates.seer import SeerAgentWriteApproval @@ -10,12 +11,14 @@ NotificationData, NotificationProviderKey, NotificationRenderedTemplate, + NotificationSource, ) +@renderer_registry.register( + NotificationProviderKey.SLACK, sources=[NotificationSource.SEER_AGENT_WRITE_APPROVAL] +) class SeerAgentWriteApprovalSlackRenderer(NotificationRenderer[SlackRenderable]): - provider_key = NotificationProviderKey.SLACK - @classmethod def render[DataT: NotificationData]( cls, *, data: DataT, rendered_template: NotificationRenderedTemplate diff --git a/tests/sentry/integrations/slack/test_integration.py b/tests/sentry/integrations/slack/test_integration.py index df2e6e02e7b7..fb96a94f767c 100644 --- a/tests/sentry/integrations/slack/test_integration.py +++ b/tests/sentry/integrations/slack/test_integration.py @@ -21,7 +21,6 @@ from sentry.notifications.platform.slack.provider import SlackNotificationProvider from sentry.notifications.platform.target import IntegrationNotificationTarget from sentry.notifications.platform.types import ( - NotificationCategory, NotificationProviderKey, NotificationTargetResourceType, ) @@ -316,9 +315,7 @@ def setUp(self) -> None: ) data = MockNotification(message="test") rendered_template = MockNotificationTemplate().render(data) - renderer = SlackNotificationProvider.get_renderer( - data=data, category=NotificationCategory.DEBUG - ) + renderer = SlackNotificationProvider.get_renderer(data=data) self.slack_renderable = renderer.render(data=data, rendered_template=rendered_template) @patch("sentry.integrations.slack.sdk_client.SlackSdkClient.chat_postMessage") diff --git a/tests/sentry/notifications/platform/discord/renderers/test_issue.py b/tests/sentry/notifications/platform/discord/renderers/test_issue.py index 0a54b939efdb..3fea480a9d8d 100644 --- a/tests/sentry/notifications/platform/discord/renderers/test_issue.py +++ b/tests/sentry/notifications/platform/discord/renderers/test_issue.py @@ -15,12 +15,12 @@ SerializableRuleProxy, ) from sentry.notifications.platform.types import ( - NotificationCategory, NotificationRenderedTemplate, NotificationSource, ) from sentry.services.eventstore.models import Event from sentry.testutils.cases import TestCase +from sentry.testutils.notifications.platform import MockNotification class IssueDiscordRendererTest(TestCase): @@ -133,22 +133,10 @@ def test_provider_returns_issue_renderer(self) -> None: id=1, label="Test Detector", data={}, project_id=self.project.id ), ) - renderer = DiscordNotificationProvider.get_renderer( - data=data, - category=NotificationCategory.ISSUE, - ) + renderer = DiscordNotificationProvider.get_renderer(data=data) assert renderer is IssueDiscordRenderer - def test_provider_returns_default_for_unknown_category(self) -> None: - data = IssueNotificationData( - group_id=self.group.id, - rule=SerializableRuleProxy( - id=1, label="Test Detector", data={}, project_id=self.project.id - ), - tags=["environment", "level"], - ) - renderer = DiscordNotificationProvider.get_renderer( - data=data, - category=NotificationCategory.DEBUG, - ) + def test_provider_returns_default_for_unregistered_source(self) -> None: + data = MockNotification(message="test") + renderer = DiscordNotificationProvider.get_renderer(data=data) assert renderer is DiscordNotificationProvider.default_renderer diff --git a/tests/sentry/notifications/platform/discord/renderers/test_metric_alert.py b/tests/sentry/notifications/platform/discord/renderers/test_metric_alert.py index 18c17d5d7557..a876041861ab 100644 --- a/tests/sentry/notifications/platform/discord/renderers/test_metric_alert.py +++ b/tests/sentry/notifications/platform/discord/renderers/test_metric_alert.py @@ -17,10 +17,10 @@ from sentry.notifications.platform.templates.metric_alert import MetricAlertNotificationData from sentry.notifications.platform.templates.seer import SeerAutofixError from sentry.notifications.platform.types import ( - NotificationCategory, NotificationRenderedTemplate, ) from sentry.testutils.cases import TestCase +from sentry.testutils.notifications.platform import MockNotification from tests.sentry.notifications.notification_action.test_metric_alert_registry_handlers import ( MetricAlertHandlerBase, ) @@ -62,18 +62,12 @@ def test_render_raises_on_invalid_data_type(self) -> None: class DiscordMetricAlertProviderDispatchTest(TestCase): def test_provider_returns_metric_alert_renderer(self) -> None: data = _make_notification_data() - renderer = DiscordNotificationProvider.get_renderer( - data=data, - category=NotificationCategory.METRIC_ALERT, - ) + renderer = DiscordNotificationProvider.get_renderer(data=data) assert renderer is DiscordMetricAlertRenderer - def test_provider_returns_default_for_unknown_category(self) -> None: - data = _make_notification_data() - renderer = DiscordNotificationProvider.get_renderer( - data=data, - category=NotificationCategory.DEBUG, - ) + def test_provider_returns_default_for_unregistered_source(self) -> None: + data = MockNotification(message="test") + renderer = DiscordNotificationProvider.get_renderer(data=data) assert renderer is DiscordNotificationProvider.default_renderer diff --git a/tests/sentry/notifications/platform/discord/test_provider.py b/tests/sentry/notifications/platform/discord/test_provider.py index 843a4c8f96ec..4a9679a9583d 100644 --- a/tests/sentry/notifications/platform/discord/test_provider.py +++ b/tests/sentry/notifications/platform/discord/test_provider.py @@ -18,7 +18,6 @@ ) from sentry.notifications.platform.target import IntegrationNotificationTarget from sentry.notifications.platform.types import ( - NotificationCategory, NotificationProviderKey, NotificationRenderedAction, NotificationRenderedImage, @@ -55,9 +54,7 @@ def test_default_renderer(self) -> None: data = MockNotification(message="test") template = MockNotificationTemplate() rendered_template = template.render(data) - renderer = DiscordNotificationProvider.get_renderer( - data=data, category=NotificationCategory.DEBUG - ) + renderer = DiscordNotificationProvider.get_renderer(data=data) renderable = renderer.render(data=data, rendered_template=rendered_template) @@ -106,9 +103,7 @@ def test_renderer_without_chart(self) -> None: ) data = MockNotification(message="test without chart") - renderer = DiscordNotificationProvider.get_renderer( - data=data, category=NotificationCategory.DEBUG - ) + renderer = DiscordNotificationProvider.get_renderer(data=data) renderable = renderer.render(data=data, rendered_template=rendered_template) @@ -130,9 +125,7 @@ def test_renderer_without_footer(self) -> None: ) data = MockNotification(message="test without footer") - renderer = DiscordNotificationProvider.get_renderer( - data=data, category=NotificationCategory.DEBUG - ) + renderer = DiscordNotificationProvider.get_renderer(data=data) renderable = renderer.render(data=data, rendered_template=rendered_template) @@ -152,9 +145,7 @@ def test_renderer_without_actions(self) -> None: ) data = MockNotification(message="test without actions") - renderer = DiscordNotificationProvider.get_renderer( - data=data, category=NotificationCategory.DEBUG - ) + renderer = DiscordNotificationProvider.get_renderer(data=data) renderable = renderer.render(data=data, rendered_template=rendered_template) @@ -182,9 +173,7 @@ def test_renderer_multiple_actions(self) -> None: ) data = MockNotification(message="test with multiple actions") - renderer = DiscordNotificationProvider.get_renderer( - data=data, category=NotificationCategory.DEBUG - ) + renderer = DiscordNotificationProvider.get_renderer(data=data) renderable = renderer.render(data=data, rendered_template=rendered_template) diff --git a/tests/sentry/notifications/platform/msteams/test_provider.py b/tests/sentry/notifications/platform/msteams/test_provider.py index 784213098047..a91c6ab287f7 100644 --- a/tests/sentry/notifications/platform/msteams/test_provider.py +++ b/tests/sentry/notifications/platform/msteams/test_provider.py @@ -16,7 +16,6 @@ from sentry.notifications.platform.provider import SendFailure, SendFailureStatus from sentry.notifications.platform.target import IntegrationNotificationTarget from sentry.notifications.platform.types import ( - NotificationCategory, NotificationProviderKey, NotificationRenderedAction, NotificationRenderedTemplate, @@ -32,9 +31,7 @@ def test_default_renderer(self) -> None: data = MockNotification(message="test") template = MockNotificationTemplate() rendered_template = template.render(data) - renderer = MSTeamsNotificationProvider.get_renderer( - data=data, category=NotificationCategory.DEBUG - ) + renderer = MSTeamsNotificationProvider.get_renderer(data=data) renderable = renderer.render(data=data, rendered_template=rendered_template) @@ -108,9 +105,7 @@ def test_renderer_without_chart(self) -> None: footer=base_template.footer, chart=None, # No chart ) - renderer = MSTeamsNotificationProvider.get_renderer( - data=data, category=NotificationCategory.DEBUG - ) + renderer = MSTeamsNotificationProvider.get_renderer(data=data) renderable = renderer.render(data=data, rendered_template=rendered_template) @@ -133,9 +128,7 @@ def test_renderer_without_footer(self) -> None: footer=None, # No footer chart=base_template.chart, ) - renderer = MSTeamsNotificationProvider.get_renderer( - data=data, category=NotificationCategory.DEBUG - ) + renderer = MSTeamsNotificationProvider.get_renderer(data=data) renderable = renderer.render(data=data, rendered_template=rendered_template) @@ -162,9 +155,7 @@ def test_renderer_without_actions(self) -> None: footer=base_template.footer, chart=base_template.chart, ) - renderer = MSTeamsNotificationProvider.get_renderer( - data=data, category=NotificationCategory.DEBUG - ) + renderer = MSTeamsNotificationProvider.get_renderer(data=data) renderable = renderer.render(data=data, rendered_template=rendered_template) @@ -200,9 +191,7 @@ def test_renderer_multiple_actions(self) -> None: footer=None, chart=None, ) - renderer = MSTeamsNotificationProvider.get_renderer( - data=data, category=NotificationCategory.DEBUG - ) + renderer = MSTeamsNotificationProvider.get_renderer(data=data) renderable = renderer.render(data=data, rendered_template=rendered_template) diff --git a/tests/sentry/notifications/platform/slack/renderers/test_issue.py b/tests/sentry/notifications/platform/slack/renderers/test_issue.py index 25c14fb3245f..70ea6a6e2b5a 100644 --- a/tests/sentry/notifications/platform/slack/renderers/test_issue.py +++ b/tests/sentry/notifications/platform/slack/renderers/test_issue.py @@ -16,11 +16,11 @@ SerializableRuleProxy, ) from sentry.notifications.platform.types import ( - NotificationCategory, NotificationRenderedTemplate, NotificationSource, ) from sentry.testutils.cases import TestCase +from sentry.testutils.notifications.platform import MockNotification from sentry.utils import json from sentry.workflow_engine.models import Action from sentry.workflow_engine.types import ActionInvocation, WorkflowEventData @@ -335,21 +335,10 @@ def test_provider_returns_issue_renderer(self) -> None: id=1, label="Test Detector", data={}, project_id=self.project.id ), ) - renderer = SlackNotificationProvider.get_renderer( - data=data, - category=NotificationCategory.ISSUE, - ) + renderer = SlackNotificationProvider.get_renderer(data=data) assert renderer is IssueSlackRenderer - def test_provider_returns_default_for_unknown_category(self) -> None: - data = IssueNotificationData( - group_id=self.group.id, - rule=SerializableRuleProxy( - id=1, label="Test Detector", data={}, project_id=self.project.id - ), - ) - renderer = SlackNotificationProvider.get_renderer( - data=data, - category=NotificationCategory.DEBUG, - ) + def test_provider_returns_default_for_unregistered_source(self) -> None: + data = MockNotification(message="test") + renderer = SlackNotificationProvider.get_renderer(data=data) assert renderer is SlackNotificationProvider.default_renderer diff --git a/tests/sentry/notifications/platform/slack/renderers/test_metric_alert.py b/tests/sentry/notifications/platform/slack/renderers/test_metric_alert.py index 55647d670411..2113f3acf149 100644 --- a/tests/sentry/notifications/platform/slack/renderers/test_metric_alert.py +++ b/tests/sentry/notifications/platform/slack/renderers/test_metric_alert.py @@ -12,10 +12,10 @@ from sentry.notifications.platform.templates.metric_alert import MetricAlertNotificationData from sentry.notifications.platform.templates.seer import SeerAutofixError from sentry.notifications.platform.types import ( - NotificationCategory, NotificationRenderedTemplate, ) from sentry.testutils.cases import TestCase +from sentry.testutils.notifications.platform import MockNotification from tests.sentry.notifications.notification_action.test_metric_alert_registry_handlers import ( MetricAlertHandlerBase, ) @@ -57,18 +57,12 @@ def test_render_raises_on_invalid_data_type(self) -> None: class SlackMetricAlertProviderDispatchTest(TestCase): def test_provider_returns_metric_alert_renderer(self) -> None: data = _make_notification_data() - renderer = SlackNotificationProvider.get_renderer( - data=data, - category=NotificationCategory.METRIC_ALERT, - ) + renderer = SlackNotificationProvider.get_renderer(data=data) assert renderer is SlackMetricAlertRenderer - def test_provider_returns_default_for_unknown_category(self) -> None: - data = _make_notification_data() - renderer = SlackNotificationProvider.get_renderer( - data=data, - category=NotificationCategory.DEBUG, - ) + def test_provider_returns_default_for_unregistered_source(self) -> None: + data = MockNotification(message="test") + renderer = SlackNotificationProvider.get_renderer(data=data) assert renderer is SlackNotificationProvider.default_renderer diff --git a/tests/sentry/notifications/platform/slack/renderers/test_seer.py b/tests/sentry/notifications/platform/slack/renderers/test_seer.py index 657ff15ce0ff..e8e9adc935c4 100644 --- a/tests/sentry/notifications/platform/slack/renderers/test_seer.py +++ b/tests/sentry/notifications/platform/slack/renderers/test_seer.py @@ -28,7 +28,7 @@ SeerAutofixPullRequest, SeerAutofixUpdate, ) -from sentry.notifications.platform.types import NotificationCategory, NotificationRenderedTemplate +from sentry.notifications.platform.types import NotificationRenderedTemplate from sentry.seer.autofix.utils import AutofixStoppingPoint from sentry.testutils.cases import TestCase @@ -409,9 +409,7 @@ def test_render_request_and_provider_dispatch(self) -> None: scopes=["org:write"], ) - renderer = SlackNotificationProvider.get_renderer( - data=data, category=NotificationCategory.SEER - ) + renderer = SlackNotificationProvider.get_renderer(data=data) assert renderer is SeerAgentWriteApprovalSlackRenderer renderable = renderer.render(data=data, rendered_template=self.rendered_template) diff --git a/tests/sentry/notifications/platform/slack/test_provider.py b/tests/sentry/notifications/platform/slack/test_provider.py index 3bc57a1f52c2..adb708cf2eaa 100644 --- a/tests/sentry/notifications/platform/slack/test_provider.py +++ b/tests/sentry/notifications/platform/slack/test_provider.py @@ -1,3 +1,4 @@ +from datetime import datetime from unittest.mock import Mock, patch import pytest @@ -10,6 +11,8 @@ ) from slack_sdk.web import SlackResponse +from sentry.incidents.models.incident import IncidentStatus +from sentry.incidents.typings.metric_detector import OpenPeriodContext from sentry.integrations.types import IntegrationProviderSlug from sentry.notifications.models.notificationthread import NotificationThread from sentry.notifications.platform.provider import ( @@ -21,14 +24,16 @@ from sentry.notifications.platform.slack.provider import ( SlackNotificationProvider, SlackRenderable, + SlackStagingNotificationProvider, ) +from sentry.notifications.platform.slack.renderers.metric_alert import SlackMetricAlertRenderer from sentry.notifications.platform.target import ( GenericNotificationTarget, IntegrationNotificationTarget, ) +from sentry.notifications.platform.templates.metric_alert import MetricAlertNotificationData from sentry.notifications.platform.threading import ThreadContext, ThreadKey from sentry.notifications.platform.types import ( - NotificationCategory, NotificationProviderKey, NotificationSource, NotificationTargetResourceType, @@ -42,9 +47,7 @@ def test_default_renderer(self) -> None: data = MockNotification(message="test") template = MockNotificationTemplate() rendered_template = template.render(data) - renderer = SlackNotificationProvider.get_renderer( - data=data, category=NotificationCategory.DEBUG - ) + renderer = SlackNotificationProvider.get_renderer(data=data) rendererable = renderer.render(data=data, rendered_template=rendered_template) rendererable_dict = [block.to_dict() for block in rendererable.get("blocks", [])] @@ -113,6 +116,24 @@ def test_is_available(self) -> None: assert SlackNotificationProvider.is_available() is False assert SlackNotificationProvider.is_available(organization=self.organization) is False + def test_staging_resolves_slack_renderers(self) -> None: + data = MetricAlertNotificationData( + group_id=1, + organization_id=self.organization.id, + notification_uuid="test-uuid", + action_id=1, + open_period_context=OpenPeriodContext(id=1, date_started=datetime(2024, 1, 1)), + new_status=IncidentStatus.CRITICAL.value, + title="Critical: Example Alert", + title_link="https://sentry.io", + text="123 events in the last 5 minutes", + ) + assert ( + SlackStagingNotificationProvider.get_renderer(data=data) + is SlackNotificationProvider.get_renderer(data=data) + is SlackMetricAlertRenderer + ) + class SlackNotificationProviderSendTest(TestCase): def setUp(self) -> None: diff --git a/tests/sentry/notifications/platform/test_provider.py b/tests/sentry/notifications/platform/test_provider.py index 3b2511f456ea..82474fc47c67 100644 --- a/tests/sentry/notifications/platform/test_provider.py +++ b/tests/sentry/notifications/platform/test_provider.py @@ -9,7 +9,6 @@ IntegrationNotificationTarget, ) from sentry.notifications.platform.types import ( - NotificationCategory, NotificationProviderKey, NotificationTargetResourceType, ) @@ -38,9 +37,7 @@ def test_all_registrants_follow_protocol(self) -> None: for resource_type in provider.target_resource_types: assert resource_type in NotificationTargetResourceType # Ensures the default renderer links back to its connected provider key - assert provider.default_renderer == provider.get_renderer( - data=self.data, category=NotificationCategory.DEBUG - ) + assert provider.default_renderer == provider.get_renderer(data=self.data) assert isinstance(provider.is_available(), bool) assert isinstance( provider.is_available(