Skip to content

ref(np): Dispatch provider renderers through a registry - #124857

Merged
hobzcalvin merged 9 commits into
masterfrom
gp/add_renderer_registry
Sep 22, 2026
Merged

hobzcalvin merged 9 commits into
masterfrom
gp/add_renderer_registry

Conversation

@hobzcalvin

@hobzcalvin hobzcalvin commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

This was all sort of born out of this comment on the original attempt to add MSTeams issue alerts months ago.

Each provider was hand-rolling the same category → renderer dispatch table inside its own get_renderer override. This replaces those with a NotificationRendererRegistry keyed by (provider_key, category).

  • Renderers register themselves where they are defined, e.g. @renderer_registry.register(NotificationProviderKey.SLACK, NotificationCategory.ISSUE).
  • NotificationProvider.get_renderer is now a single base implementation: look the pair up, fall back to default_renderer. The Slack, Discord, and MS Teams overrides are gone, and with them the function-local renderer imports they needed to dodge the renderer → provider circular import.
  • get_renderer takes only the notification data now; the category comes from the notification's registered template rather than the call site.
  • Dropped provider_key from the NotificationRenderer protocol — nothing read it.

Optional renderer_key on NotificationProvider handles the special SlackStagingNotificationProvider case.

All renderers are now imported in src/sentry/notifications/platform/registry.py. I looked into something automatic, but it seemed dicey. At least there's one place where all these imports live now, and other than that, renderers register themselves.

@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Sep 17, 2026
@hobzcalvin
hobzcalvin force-pushed the gp/add_renderer_registry branch from 6f19cf1 to aa068fe Compare September 17, 2026 23:15
@hobzcalvin
hobzcalvin changed the base branch from gv/add_msteams_issue_renderer to master September 17, 2026 23:15
Comment thread src/sentry/notifications/platform/provider.py Outdated
@hobzcalvin
hobzcalvin force-pushed the gp/add_renderer_registry branch from ada4902 to fa6b45c Compare September 18, 2026 21:29
@hobzcalvin
hobzcalvin marked this pull request as ready for review September 18, 2026 22:37
@hobzcalvin
hobzcalvin requested review from a team as code owners September 18, 2026 22:37
Comment thread src/sentry/notifications/platform/registry.py Outdated
@hobzcalvin
hobzcalvin added this pull request to stack #124985 September 18, 2026 22:41
@hobzcalvin
hobzcalvin force-pushed the gp/add_renderer_registry branch from 1a74a96 to d964d6d Compare September 21, 2026 16:34
Comment on lines +143 to +149
# 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this just used as a fallback? Would it be worth just having the notification service and delivery tasks access the registry directly? I could see a case for either approach tbh.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I thought about calling the registry directly, but this couples the cls.renderer_key or cls.key and cls.default_renderer in here nicely, so the caller doesn't need to worry about those details.

@GabeVillalobos GabeVillalobos left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changes look great! This removes a ton of boilerplate and makes it more obvious how these renderers map to their notifications. Nice work

Comment thread src/sentry/notifications/platform/registry.py Outdated
renderer = DiscordNotificationProvider.get_renderer(
data=data, category=NotificationCategory.DEBUG
)
renderer = DiscordNotificationProvider.get_renderer(data=data)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These tests seem a little pointless now, can we remove these in a follow-up PR please?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the get_renderer hop isn't needed, but the rest is...I think I can just remove this go-around?

@hobzcalvin
hobzcalvin force-pushed the gp/add_renderer_registry branch 2 times, most recently from 963441d to f42dbb7 Compare September 21, 2026 23:30
Comment thread src/sentry/notifications/platform/slack/renderers/seer.py
@hobzcalvin
hobzcalvin requested a review from a team as a code owner September 21, 2026 23:44
@hobzcalvin
hobzcalvin force-pushed the gp/add_renderer_registry branch from f558636 to 75c7b35 Compare September 22, 2026 00:04
@hobzcalvin
hobzcalvin requested a review from a team as a code owner September 22, 2026 00:04
@hobzcalvin
hobzcalvin force-pushed the gp/add_renderer_registry branch from 75c7b35 to 4561aa6 Compare September 22, 2026 17:48
hobzcalvin and others added 4 commits September 22, 2026 10:56
Replaces the per-provider `get_renderer` overrides with a `NotificationRendererRegistry`
keyed by provider and notification source. Renderers register themselves where they are
defined, and `NotificationProvider.get_renderer` resolves the renderer from the
notification's source, falling back to the provider's default renderer.

Keying on source rather than category lets the Seer renderers be registered directly,
replacing the nested source check the Slack provider needed to separate
`SeerAgentWriteApprovalSlackRenderer` from `SeerSlackRenderer`. `SlackStagingNotificationProvider`
resolves against the Slack key, since it sends Slack payloads under its own provider key.

The registry imports renderer modules on first lookup rather than from `AppConfig.ready()`:
renderers reach into integration packages whose __init__ imports `sentry.integrations.jira.client`,
which reads `system.url-prefix` at module scope before the options cache exists.

Also drops the unused `provider_key` attribute from the `NotificationRenderer` protocol.

Co-authored-by: Cursor <cursoragent@cursor.com>
Matches how other registration decorators in the codebase accept a set of values,
e.g. AlertRuleTriggerAction.register_type's supported_target_types.

Co-authored-by: Cursor <cursoragent@cursor.com>
hobzcalvin and others added 5 commits September 22, 2026 10:56
Co-authored-by: Cursor <cursoragent@cursor.com>
Importing the jira integration read `system.url-prefix` at module scope, which
raises when the renderer modules are imported from `AppConfig.ready()`, before
the options store cache is bound.

Co-authored-by: Cursor <cursoragent@cursor.com>
The renderer modules are imported from `AppConfig.ready()`, which Django runs
during `django.setup()` — before `initialize_app` calls
`bind_cache_to_option_store()`. Importing `sentry.integrations.discord` or
`sentry.integrations.slack` at module scope executes those packages' `__init__`,
which reaches `sentry.notifications.notification_action` and from there the Jira
action validator, whose module scope reads `system.url-prefix`. That read asserts
while the options store cache is unbound.

Deferring the imports into `render()` matches what the Discord and Slack
providers and `IssueSlackRenderer` already do, and keeps the registrations eager
at `ready()`.

Co-authored-by: Cursor <cursoragent@cursor.com>
@hobzcalvin
hobzcalvin force-pushed the gp/add_renderer_registry branch from 4561aa6 to 6c595a5 Compare September 22, 2026 17:58
@hobzcalvin
hobzcalvin merged commit 956a3e5 into master Sep 22, 2026
69 checks passed
@hobzcalvin
hobzcalvin deleted the gp/add_renderer_registry branch September 22, 2026 20:05
hobzcalvin added a commit that referenced this pull request Sep 23, 2026
Stacked on #124857 (`gp/add_renderer_registry`). Supersedes #112890,
which could not be reopened after the branch was rebased.

### Summary

Adds `IssueMSTeamsRenderer`, so issue alerts sent through the
notification platform produce the same Adaptive Card the legacy
`MSTeamsIssueMessageBuilder` path produces today. The renderer registers
itself for `(MSTEAMS, ISSUE)` with the registry from the parent PR
rather than adding a `get_renderer` override to the provider.

Original work by @GabeVillalobos; rebased onto current master and
adapted to the registry.

Actions are limited to a single "View Issue" link. The legacy card also
offers resolve/archive/assign, which need the `RpcIntegration` for
action webhook routing; that is coming in #125160

---------

Co-authored-by: Gabe Villalobos <gabriel.villalobos@sentry.io>
Co-authored-by: Cursor <cursoragent@cursor.com>
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.

2 participants