diff --git a/.agents/skills/notification-platform/SKILL.md b/.agents/skills/notification-platform/SKILL.md
index c25580e265e8..ceb1c85de965 100644
--- a/.agents/skills/notification-platform/SKILL.md
+++ b/.agents/skills/notification-platform/SKILL.md
@@ -13,7 +13,7 @@ Sentry's NotificationPlatform is a provider-based system for sending notificatio
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `NotificationData` | Protocol. Frozen dataclass carrying the payload for a single notification. Must declare a `source` class variable. | `types.py` |
| `NotificationTemplate` | Abstract class. Converts `NotificationData` into a `NotificationRenderedTemplate`. Registered per `NotificationSource`. | `types.py` |
-| `NotificationRenderedTemplate` | Dataclass. Provider-agnostic output: subject, body blocks, actions, chart, footer, optional email paths. | `types.py` |
+| `NotificationRenderedTemplate` | Dataclass. Provider-agnostic output: subject (str or text blocks), body (sections containing text blocks), actions, chart, footer (str or text blocks), optional email paths. | `types.py` |
| `NotificationProvider` | Protocol. Knows how to validate a target, pick a renderer, and send the final renderable (Email, Slack, etc.). | `provider.py` |
| `NotificationRenderer` | Protocol. Converts a `NotificationRenderedTemplate` into a provider-specific renderable (HTML email, Slack blocks, etc.). | `renderer.py` |
| `NotificationTarget` | Protocol. Identifies the recipient: email address, channel ID, or DM user ID. Two concrete classes: `GenericNotificationTarget` (email) and `IntegrationNotificationTarget` (Slack/Discord/MSTeams). | `target.py` |
@@ -98,7 +98,7 @@ from sentry.notifications.platform.types import (
NotificationRenderedAction,
NotificationRenderedTemplate,
NotificationTemplate,
- ParagraphBlock,
+ ParagraphSection,
PlainTextBlock,
)
@@ -114,7 +114,7 @@ class MyNotificationTemplate(NotificationTemplate[MyNotificationData]):
return NotificationRenderedTemplate(
subject=data.title,
body=[
- ParagraphBlock(blocks=[PlainTextBlock(text="Something happened.")])
+ ParagraphSection(blocks=[PlainTextBlock(text="Something happened.")])
],
actions=[
NotificationRenderedAction(label="View Details", link=data.detail_url)
@@ -122,9 +122,29 @@ class MyNotificationTemplate(NotificationTemplate[MyNotificationData]):
)
```
-**Available body block types:**
+**Available types:**
-Refer to `src/sentry/notifications/platform/types.py` for the latest available block types.
+A notification is composed of **sections** and **text blocks**. Text blocks can appear in sections (for the body), and also directly in the `subject` and `footer` fields (as `list[NotificationTextBlock]` instead of plain `str`).
+
+Sections (portions of a message, used in `body`):
+
+| Section | Description |
+| ------------------- | ------------------------------------------------- |
+| `ParagraphSection` | A block of text with a line break before. |
+| `CodeSection` | A code block with a line break before. |
+| `BlockQuoteSection` | A quoted block of text, rendered as a blockquote. |
+
+Text blocks (parts of text — used inside sections, and also in `subject` and `footer`):
+
+| Block | Description |
+| ----------------- | ----------------------------------------- |
+| `PlainTextBlock` | Unformatted text. |
+| `BoldTextBlock` | Bold text. |
+| `ItalicTextBlock` | Italic text. |
+| `CodeTextBlock` | Inline code. |
+| `LinkTextBlock` | A hyperlink with `text` and `url` fields. |
+
+Refer to `src/sentry/notifications/platform/types.py` for the full definitions.
**Register the import** in `templates/__init__.py`:
@@ -186,7 +206,7 @@ if NotificationService.has_access(organization, data.source):
## Step 6: Add a Custom Renderer
-Custom renderers bypass the default template-to-renderable conversion for a specific provider + category combination. Use when the default block-based rendering is too limiting (e.g., interactive Slack buttons, rich card layouts).
+Custom renderers bypass the default template-to-renderable conversion for a specific provider + category combination. Use when the default section/block-based rendering is too limiting (e.g., interactive Slack buttons, rich card layouts).
**When to use:**
diff --git a/.agents/skills/notification-platform/references/custom-renderers.md b/.agents/skills/notification-platform/references/custom-renderers.md
index b2fbbdf11b4c..ffec5e5df57f 100644
--- a/.agents/skills/notification-platform/references/custom-renderers.md
+++ b/.agents/skills/notification-platform/references/custom-renderers.md
@@ -4,7 +4,7 @@
The default flow is: `NotificationData` → `NotificationTemplate.render()` → `NotificationRenderedTemplate` → `NotificationRenderer.render()` → provider-specific renderable.
-Custom renderers replace the last step. The provider's `get_renderer()` method dispatches to a custom renderer class based on category or data type, bypassing the default block-to-renderable conversion.
+Custom renderers replace the last step. The provider's `get_renderer()` method dispatches to a custom renderer class based on category or data type, bypassing the default section/block-to-renderable conversion.
```
Template.render(data) → NotificationRenderedTemplate
@@ -27,7 +27,7 @@ Use a custom renderer when:
Do NOT use a custom renderer when:
-- The default block types (`ParagraphBlock`, `CodeBlock`, `PlainTextBlock`, `BoldTextBlock`, `CodeTextBlock`) are sufficient
+- The default section and block types (`ParagraphSection`, `CodeSection`, `BlockQuoteSection`, `PlainTextBlock`, `BoldTextBlock`, `ItalicTextBlock`, `CodeTextBlock`, `LinkTextBlock`) are sufficient
- You only need to tweak styling — the default renderers establish common styles that the majority of notifications should abide by.
## File Placement
@@ -139,6 +139,6 @@ class SeerAutofixUpdateTemplate(NotificationTemplate[SeerAutofixUpdate]):
def render(self, data: SeerAutofixUpdate) -> NotificationRenderedTemplate:
return NotificationRenderedTemplate(
subject="Seer Autofix Update",
- body=[ParagraphBlock(blocks=[PlainTextBlock(text="Update")])],
+ body=[ParagraphSection(blocks=[PlainTextBlock(text="Update")])],
)
```
diff --git a/.agents/skills/notification-platform/references/data-and-templates.md b/.agents/skills/notification-platform/references/data-and-templates.md
index 44b507de923e..67865223b44e 100644
--- a/.agents/skills/notification-platform/references/data-and-templates.md
+++ b/.agents/skills/notification-platform/references/data-and-templates.md
@@ -18,7 +18,7 @@ from sentry.notifications.platform.types import (
NotificationRenderedTemplate,
NotificationSource,
NotificationTemplate,
- ParagraphBlock,
+ ParagraphSection,
PlainTextBlock,
)
@@ -46,7 +46,7 @@ class DataExportSuccessTemplate(NotificationTemplate[DataExportSuccess]):
return NotificationRenderedTemplate(
subject="Your data is ready.",
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(
text="See, that wasn't so bad. We're all done assembling your download."
@@ -59,7 +59,7 @@ class DataExportSuccessTemplate(NotificationTemplate[DataExportSuccess]):
)
```
-## Complete Example: DataExportFailure (with CodeBlock)
+## Complete Example: DataExportFailure (with CodeSection)
```python
@dataclass(frozen=True)
@@ -83,20 +83,20 @@ class DataExportFailureTemplate(NotificationTemplate[DataExportFailure]):
return NotificationRenderedTemplate(
subject="We couldn't export your data.",
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(
text=f"The data export you created at {format_date(data.creation_date)} didn't work."
)
]
),
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(text="It looks like there was an error: "),
CodeTextBlock(text=data.error_message),
]
),
- CodeBlock(blocks=[PlainTextBlock(text=orjson.dumps(data.error_payload).decode())]),
+ CodeSection(blocks=[PlainTextBlock(text=orjson.dumps(data.error_payload).decode())]),
],
actions=[
NotificationRenderedAction(label="Documentation", link="https://docs.sentry.io/"),
@@ -110,15 +110,15 @@ class DataExportFailureTemplate(NotificationTemplate[DataExportFailure]):
## NotificationRenderedTemplate Field Reference
-| Field | Type | Required | Description |
-| ----------------- | --------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
-| `subject` | `str` | Yes | Title/subject line. No formatting — displayed as-is. |
-| `body` | `list[NotificationBodyFormattingBlock]` | Yes | Main content using block types below. |
-| `actions` | `list[NotificationRenderedAction]` | No | Buttons/links. Each has `label` (str) and `link` (str). |
-| `chart` | `NotificationRenderedImage` | No | Image with `url` and `alt_text` fields. |
-| `footer` | `str` | No | Extra text after actions. No formatting. |
-| `email_html_path` | `str` | No | Custom Django HTML template path. Data class passed as context. Default: `sentry/emails/platform/default.html`. |
-| `email_text_path` | `str` | No | Custom Django text template path. Data class passed as context. |
+| Field | Type | Required | Description |
+| ----------------- | ------------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `subject` | `str \| list[NotificationTextBlock]` | Yes | Title/subject line. Can be plain text or a list of text blocks for rich formatting. |
+| `body` | `list[NotificationSection]` | Yes | Main content. A list of sections (`ParagraphSection`, `CodeSection`, `BlockQuoteSection`), each containing text blocks (`PlainTextBlock`, `BoldTextBlock`, `ItalicTextBlock`, `CodeTextBlock`, `LinkTextBlock`). |
+| `actions` | `list[NotificationRenderedAction]` | No | Buttons/links. Each has `label` (str) and `link` (str). |
+| `chart` | `NotificationRenderedImage` | No | Image with `url` and `alt_text` fields. |
+| `footer` | `str \| list[NotificationTextBlock]` | No | Extra text after actions. Can be plain text or a list of text blocks. |
+| `email_html_path` | `str` | No | Custom Django HTML template path. Data class passed as context. Default: `sentry/emails/platform/default.html`. |
+| `email_text_path` | `str` | No | Custom Django text template path. Data class passed as context. |
## Notes on Special Template Fields
diff --git a/.agents/skills/notification-platform/references/provider-template.md b/.agents/skills/notification-platform/references/provider-template.md
index bf7bc01db31a..fad167c93b16 100644
--- a/.agents/skills/notification-platform/references/provider-template.md
+++ b/.agents/skills/notification-platform/references/provider-template.md
@@ -30,15 +30,15 @@ from sentry.notifications.platform.target import (
PreparedIntegrationNotificationTarget,
)
from sentry.notifications.platform.types import (
- NotificationBodyFormattingBlock,
- NotificationBodyFormattingBlockType,
- NotificationBodyTextBlock,
- NotificationBodyTextBlockType,
NotificationData,
NotificationProviderKey,
NotificationRenderedTemplate,
+ NotificationSection,
+ NotificationSectionType,
NotificationTarget,
NotificationTargetResourceType,
+ NotificationTextBlock,
+ NotificationTextBlockType,
)
from sentry.organizations.services.organization.model import RpcOrganizationSummary
@@ -53,31 +53,37 @@ class MyDefaultRenderer(NotificationRenderer[MyRenderable]):
def render[DataT: NotificationData](
cls, *, data: DataT, rendered_template: NotificationRenderedTemplate
) -> MyRenderable:
- # Convert rendered_template blocks into provider-specific format
- body = cls.render_body_blocks(rendered_template.body)
- # Build and return provider-specific renderable
- return {"subject": rendered_template.subject, "body": body}
+ body = cls.render_sections(rendered_template.body)
+ subject = rendered_template.subject_text
+ footer = rendered_template.footer_text
+ return {"subject": subject, "body": body, "footer": footer}
@classmethod
- def render_body_blocks(cls, body: list[NotificationBodyFormattingBlock]) -> str:
+ def render_sections(cls, sections: list[NotificationSection]) -> str:
parts = []
- for block in body:
- if block.type == NotificationBodyFormattingBlockType.PARAGRAPH:
- parts.append(cls.render_text_blocks(block.blocks))
- elif block.type == NotificationBodyFormattingBlockType.CODE_BLOCK:
- parts.append(f"```{cls.render_text_blocks(block.blocks)}```")
+ for section in sections:
+ if section.type == NotificationSectionType.PARAGRAPH:
+ parts.append(cls.render_text_blocks(section.blocks))
+ elif section.type == NotificationSectionType.CODE_BLOCK:
+ parts.append(f"```{cls.render_text_blocks(section.blocks)}```")
+ elif section.type == NotificationSectionType.BLOCK_QUOTE:
+ parts.append(f"> {cls.render_text_blocks(section.blocks)}")
return "\n".join(parts)
@classmethod
- def render_text_blocks(cls, blocks: list[NotificationBodyTextBlock]) -> str:
+ def render_text_blocks(cls, blocks: list[NotificationTextBlock]) -> str:
texts = []
for block in blocks:
- if block.type == NotificationBodyTextBlockType.PLAIN_TEXT:
+ if block.type == NotificationTextBlockType.PLAIN_TEXT:
texts.append(block.text)
- elif block.type == NotificationBodyTextBlockType.BOLD_TEXT:
+ elif block.type == NotificationTextBlockType.BOLD_TEXT:
texts.append(f"**{block.text}**")
- elif block.type == NotificationBodyTextBlockType.CODE:
+ elif block.type == NotificationTextBlockType.ITALIC_TEXT:
+ texts.append(f"_{block.text}_")
+ elif block.type == NotificationTextBlockType.CODE:
texts.append(f"`{block.text}`")
+ elif block.type == NotificationTextBlockType.LINK:
+ texts.append(f"[{block.text}]({block.url})")
return " ".join(texts)
diff --git a/src/sentry/notifications/platform/templates/data_export.py b/src/sentry/notifications/platform/templates/data_export.py
index 68dfd18404ea..12565fe1bf5b 100644
--- a/src/sentry/notifications/platform/templates/data_export.py
+++ b/src/sentry/notifications/platform/templates/data_export.py
@@ -6,7 +6,7 @@
from sentry.notifications.platform.registry import template_registry
from sentry.notifications.platform.types import (
- CodeBlock,
+ CodeSection,
CodeTextBlock,
NotificationCategory,
NotificationData,
@@ -14,7 +14,7 @@
NotificationRenderedTemplate,
NotificationSource,
NotificationTemplate,
- ParagraphBlock,
+ ParagraphSection,
PlainTextBlock,
)
@@ -41,7 +41,7 @@ def render(self, data: DataExportSuccess) -> NotificationRenderedTemplate:
return NotificationRenderedTemplate(
subject="Your data is ready.",
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(
text="See, that wasn't so bad. We're all done assembling your download. Now have at it."
@@ -78,27 +78,29 @@ def render(self, data: DataExportFailure) -> NotificationRenderedTemplate:
return NotificationRenderedTemplate(
subject="We couldn't export your data.",
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(
text=f"Well, this is a little awkward. The data export you created at {format_date(data.creation_date)} didn't work. Sorry about that."
)
]
),
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(text="It looks like there was an error: "),
CodeTextBlock(text=data.error_message),
]
),
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(
text="This is what you sent us. Maybe it'll help you sort this out: "
)
]
),
- CodeBlock(blocks=[PlainTextBlock(text=orjson.dumps(data.error_payload).decode())]),
+ CodeSection(
+ blocks=[PlainTextBlock(text=orjson.dumps(data.error_payload).decode())]
+ ),
],
actions=[
NotificationRenderedAction(label="Documentation", link="https://docs.sentry.io/"),
diff --git a/src/sentry/notifications/platform/templates/repository.py b/src/sentry/notifications/platform/templates/repository.py
index deb79ccd7b8a..c38e55913627 100644
--- a/src/sentry/notifications/platform/templates/repository.py
+++ b/src/sentry/notifications/platform/templates/repository.py
@@ -6,7 +6,7 @@
NotificationRenderedTemplate,
NotificationSource,
NotificationTemplate,
- ParagraphBlock,
+ ParagraphSection,
PlainTextBlock,
)
@@ -31,7 +31,7 @@ def render(self, data: UnableToDeleteRepository) -> NotificationRenderedTemplate
return NotificationRenderedTemplate(
subject="Unable to Delete Repository Webhooks",
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(
text=f"We were unable to delete webhooks in {data.provider_name} for your repository "
@@ -40,8 +40,8 @@ def render(self, data: UnableToDeleteRepository) -> NotificationRenderedTemplate
PlainTextBlock(text=" due to the following error:"),
]
),
- ParagraphBlock(blocks=[CodeTextBlock(text=data.error_message)]),
- ParagraphBlock(
+ ParagraphSection(blocks=[CodeTextBlock(text=data.error_message)]),
+ ParagraphSection(
blocks=[
PlainTextBlock(
text=f"You will need to remove these webhooks manually in {data.provider_name} in order to stop sending commit data to Sentry."
diff --git a/src/sentry/notifications/platform/templates/sample.py b/src/sentry/notifications/platform/templates/sample.py
index ec95132a5261..e9d3c34a0ee6 100644
--- a/src/sentry/notifications/platform/templates/sample.py
+++ b/src/sentry/notifications/platform/templates/sample.py
@@ -3,7 +3,7 @@
from sentry.notifications.platform.registry import template_registry
from sentry.notifications.platform.types import (
BoldTextBlock,
- CodeBlock,
+ CodeSection,
CodeTextBlock,
LinkTextBlock,
NotificationCategory,
@@ -13,7 +13,7 @@
NotificationRenderedTemplate,
NotificationSource,
NotificationTemplate,
- ParagraphBlock,
+ ParagraphSection,
PlainTextBlock,
)
@@ -54,7 +54,7 @@ def render(self, data: ErrorAlertData) -> NotificationRenderedTemplate:
LinkTextBlock(text=data.project_name, url="https://example.com/project"),
],
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(text="A new"),
CodeTextBlock(text=data.error_type),
@@ -64,9 +64,9 @@ def render(self, data: ErrorAlertData) -> NotificationRenderedTemplate:
BoldTextBlock(text=f"{data.error_count} occurrences."),
],
),
- ParagraphBlock(blocks=[PlainTextBlock(text="The error message is:")]),
- CodeBlock(blocks=[PlainTextBlock(text=data.error_message)]),
- ParagraphBlock(
+ ParagraphSection(blocks=[PlainTextBlock(text="The error message is:")]),
+ CodeSection(blocks=[PlainTextBlock(text=data.error_message)]),
+ ParagraphSection(
blocks=[
PlainTextBlock(
text=f"This error was first seen at {data.first_seen} and requires immediate attention.",
@@ -120,21 +120,21 @@ def render(self, data: DeploymentData) -> NotificationRenderedTemplate:
return NotificationRenderedTemplate(
subject=f"Deployment to {data.environment}: {data.version}",
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(
text=f"Version {data.version} has been successfully deployed to {data.environment} for project {data.project_name}. ",
)
],
),
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(
text=f"The deployment was initiated by {data.deployer} with commit {data.commit_sha[:8]}: {data.commit_message}. ",
)
],
),
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(
text="Monitor the deployment status and be ready to rollback if any issues are detected.",
@@ -186,7 +186,7 @@ def render(self, data: SlowLoadMetricAlertData) -> NotificationRenderedTemplate:
return NotificationRenderedTemplate(
subject=f"{data.severity.upper()}: {data.alert_type} in {data.project_name}",
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[PlainTextBlock(text=f"{data.measurement} since {data.start_time}")],
),
],
@@ -227,21 +227,21 @@ def render(self, data: PerformanceAlertData) -> NotificationRenderedTemplate:
return NotificationRenderedTemplate(
subject=f"Performance Alert: {data.metric_name} threshold exceeded",
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(
text=f"Performance alert triggered for {data.metric_name} in project {data.project_name}. ",
)
],
),
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(
text=f"The current value of {data.current_value} exceeds the threshold of {data.threshold}. ",
)
],
),
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(
text="Immediate investigation is recommended to identify and resolve the performance degradation.",
@@ -285,15 +285,15 @@ def render(self, data: TeamUpdateData) -> NotificationRenderedTemplate:
return NotificationRenderedTemplate(
subject=f"Team Update: {data.update_type}",
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(
text=f"Team {data.team_name} has posted a {data.update_type} update. ",
)
],
),
- ParagraphBlock(blocks=[PlainTextBlock(text=f"Message: {data.message} ")]),
- ParagraphBlock(
+ ParagraphSection(blocks=[PlainTextBlock(text=f"Message: {data.message} ")]),
+ ParagraphSection(
blocks=[PlainTextBlock(text=f"Posted by {data.author} at {data.timestamp}.")],
),
],
diff --git a/src/sentry/notifications/platform/templates/seer.py b/src/sentry/notifications/platform/templates/seer.py
index 53bd10900a43..56c4c7d349aa 100644
--- a/src/sentry/notifications/platform/templates/seer.py
+++ b/src/sentry/notifications/platform/templates/seer.py
@@ -12,7 +12,7 @@
NotificationRenderedTemplate,
NotificationSource,
NotificationTemplate,
- ParagraphBlock,
+ ParagraphSection,
PlainTextBlock,
)
from sentry.organizations.services.organization.service import organization_service
@@ -49,7 +49,7 @@ class SeerAutofixErrorTemplate(NotificationTemplate[SeerAutofixError]):
def render(self, data: SeerAutofixError) -> NotificationRenderedTemplate:
return NotificationRenderedTemplate(
subject=data.error_title,
- body=[ParagraphBlock(blocks=[PlainTextBlock(text=data.error_message)])],
+ body=[ParagraphSection(blocks=[PlainTextBlock(text=data.error_message)])],
)
@@ -181,7 +181,7 @@ class SeerAgentErrorTemplate(NotificationTemplate[SeerAgentError]):
def render(self, data: SeerAgentError) -> NotificationRenderedTemplate:
return NotificationRenderedTemplate(
subject=data.error_title,
- body=[ParagraphBlock(blocks=[PlainTextBlock(text=data.error_message)])],
+ body=[ParagraphSection(blocks=[PlainTextBlock(text=data.error_message)])],
)
diff --git a/src/sentry/notifications/platform/templates/sentry_app_webhook_disabled.py b/src/sentry/notifications/platform/templates/sentry_app_webhook_disabled.py
index 7d929662f44f..c487f99ce8e6 100644
--- a/src/sentry/notifications/platform/templates/sentry_app_webhook_disabled.py
+++ b/src/sentry/notifications/platform/templates/sentry_app_webhook_disabled.py
@@ -7,7 +7,7 @@
NotificationRenderedTemplate,
NotificationSource,
NotificationTemplate,
- ParagraphBlock,
+ ParagraphSection,
PlainTextBlock,
)
@@ -34,7 +34,7 @@ def render(self, data: SentryAppWebhookDisabled) -> NotificationRenderedTemplate
return NotificationRenderedTemplate(
subject=f"Webhook delivery paused for {data.sentry_app_name}",
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(text="We're temporarily pausing webhook deliveries to "),
CodeTextBlock(text=data.webhook_url),
diff --git a/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_base.py b/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_base.py
index 76ae5a320b0d..a9883ed88d8f 100644
--- a/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_base.py
+++ b/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_base.py
@@ -10,7 +10,7 @@
NotificationSection,
NotificationSource,
NotificationTextBlock,
- ParagraphBlock,
+ ParagraphSection,
PlainTextBlock,
)
from sentry.types.activity import ActivityType
@@ -54,7 +54,7 @@ def get_issue_description(group: Group) -> list[NotificationSection]:
if blocks:
blocks.append(PlainTextBlock(text="—"))
blocks.append(CodeTextBlock(text=culprit))
- return [ParagraphBlock(blocks=blocks)]
+ return [ParagraphSection(blocks=blocks)]
def get_subject(label: str, group: Group) -> list[NotificationTextBlock]:
@@ -98,7 +98,7 @@ def build_template(
def get_example_issue_description() -> list[NotificationSection]:
return [
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(text="ExampleError: something went wrong"),
PlainTextBlock(text="—"),
diff --git a/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_iteration_completed.py b/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_iteration_completed.py
index 9bf0fdd0f421..0f7e4e263142 100644
--- a/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_iteration_completed.py
+++ b/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_iteration_completed.py
@@ -15,7 +15,7 @@
NotificationSource,
NotificationTemplate,
NotificationTextBlock,
- ParagraphBlock,
+ ParagraphSection,
PlainTextBlock,
)
from sentry.types.activity import ActivityType
@@ -38,7 +38,7 @@ def render_example(self) -> NotificationRenderedTemplate:
subject="Seer PR Iteration Completed for EXAMPLE-1",
body=[
*get_example_issue_description(),
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(text="Iteration #2: "),
LinkTextBlock(
@@ -84,7 +84,7 @@ def render(self, data: WorkflowEngineActivityAction) -> NotificationRenderedTemp
if not detail_blocks:
detail_blocks.append(PlainTextBlock(text=prefix))
- body.append(ParagraphBlock(blocks=detail_blocks))
+ body.append(ParagraphSection(blocks=detail_blocks))
return build_template(
data=data,
diff --git a/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_pr_created.py b/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_pr_created.py
index e4937920ab47..4094d856d9fc 100644
--- a/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_pr_created.py
+++ b/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_pr_created.py
@@ -15,7 +15,7 @@
NotificationSource,
NotificationTemplate,
NotificationTextBlock,
- ParagraphBlock,
+ ParagraphSection,
)
from sentry.types.activity import ActivityType
@@ -37,7 +37,7 @@ def render_example(self) -> NotificationRenderedTemplate:
subject="Seer PR Created for EXAMPLE-1",
body=[
*get_example_issue_description(),
- ParagraphBlock(
+ ParagraphSection(
blocks=[
LinkTextBlock(
text="getsentry/sentry (#1234)",
@@ -69,7 +69,7 @@ def render(self, data: WorkflowEngineActivityAction) -> NotificationRenderedTemp
body: list[NotificationSection] = [*get_issue_description(group)]
if pr_links:
- body.append(ParagraphBlock(blocks=pr_links))
+ body.append(ParagraphSection(blocks=pr_links))
return build_template(
data=data, subject=get_subject("Pull Request Created", group), body=body
diff --git a/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_rca_completed.py b/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_rca_completed.py
index 38c48df0bbcd..0bcf13d2cb71 100644
--- a/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_rca_completed.py
+++ b/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_rca_completed.py
@@ -8,7 +8,7 @@
get_subject,
)
from sentry.notifications.platform.types import (
- CodeBlock,
+ CodeSection,
NotificationCategory,
NotificationRenderedTemplate,
NotificationSection,
@@ -36,7 +36,7 @@ def render_example(self) -> NotificationRenderedTemplate:
subject="Seer RCA Completed for EXAMPLE-1",
body=[
*get_example_issue_description(),
- CodeBlock(
+ CodeSection(
blocks=[
PlainTextBlock(
text="The error is caused by a null pointer dereference in the user authentication flow."
@@ -58,7 +58,7 @@ def render(self, data: WorkflowEngineActivityAction) -> NotificationRenderedTemp
body: list[NotificationSection] = [*get_issue_description(group)]
if activity.data:
summary_block = PlainTextBlock(text=activity.data.get("summary", fallback))
- body.append(CodeBlock(blocks=[summary_block]))
+ body.append(CodeSection(blocks=[summary_block]))
return build_template(
data=data, subject=get_subject("Root Cause Analysis Completed", group), body=body
)
diff --git a/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_solution_completed.py b/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_solution_completed.py
index fb43fe8b930f..91cf09d3506e 100644
--- a/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_solution_completed.py
+++ b/src/sentry/notifications/platform/templates/workflow_engine/activity/seer_solution_completed.py
@@ -8,7 +8,7 @@
get_subject,
)
from sentry.notifications.platform.types import (
- CodeBlock,
+ CodeSection,
NotificationCategory,
NotificationRenderedTemplate,
NotificationSection,
@@ -36,7 +36,7 @@ def render_example(self) -> NotificationRenderedTemplate:
subject="Seer Solution Completed for EXAMPLE-1",
body=[
*get_example_issue_description(),
- CodeBlock(
+ CodeSection(
blocks=[
PlainTextBlock(
text="Add a null check before accessing user.session in the authentication middleware."
@@ -58,7 +58,7 @@ def render(self, data: WorkflowEngineActivityAction) -> NotificationRenderedTemp
body: list[NotificationSection] = [*get_issue_description(group)]
if activity.data:
summary_block = PlainTextBlock(text=activity.data.get("summary", fallback))
- body.append(CodeBlock(blocks=[summary_block]))
+ body.append(CodeSection(blocks=[summary_block]))
return build_template(
data=data, subject=get_subject("Planning Completed", group), body=body
)
diff --git a/src/sentry/notifications/platform/types.py b/src/sentry/notifications/platform/types.py
index 4b1e6caf5690..54afde2e4bb2 100644
--- a/src/sentry/notifications/platform/types.py
+++ b/src/sentry/notifications/platform/types.py
@@ -309,7 +309,7 @@ def footer_text(self) -> str:
class NotificationTextBlockType(StrEnum):
"""
- Represents a block of text to be rendered in the notification body.
+ Represents a block of text to be rendered in the notification.
"""
PLAIN_TEXT = "plain_text"
@@ -355,42 +355,42 @@ class NotificationSectionType(StrEnum):
class NotificationSection(Protocol):
"""
- A block that applies formatting such as a newline and encapsulates other text.
+ A section of text that applies formatting such as a newline and encapsulates other text.
"""
type: NotificationSectionType
"""
- The type of the block, such as ParagraphBlock, BoldTextBlock, etc.
+ The type of the section, such as ParagraphSection, CodeSection, etc.
"""
blocks: list[NotificationTextBlock]
"""
- Some blocks may want to contain other blocks, such as a ParagraphBlock containing a BoldTextBlock.
+ The text blocks contain actual content, such as BoldTextBlock, ItalicTextBlock, etc.
"""
class NotificationTextBlock(Protocol):
"""
- Represents a block of text to be rendered in the notification body.
+ Represents a block of text to be rendered in the notification.
"""
type: NotificationTextBlockType
"""
- The type of the block, such as BoldTextBlock, CodeBlock, etc.
+ The type of the block, such as BoldTextBlock, CodeTextBlock, etc.
"""
text: str
"""
- Text to be rendered in the body.
+ Text to be rendered in the notification.
"""
@dataclass
-class ParagraphBlock(NotificationSection):
+class ParagraphSection(NotificationSection):
blocks: list[NotificationTextBlock]
type: Literal[NotificationSectionType.PARAGRAPH] = NotificationSectionType.PARAGRAPH
@dataclass
-class CodeBlock(NotificationSection):
+class CodeSection(NotificationSection):
blocks: list[NotificationTextBlock]
type: Literal[NotificationSectionType.CODE_BLOCK] = NotificationSectionType.CODE_BLOCK
diff --git a/src/sentry/testutils/notifications/platform.py b/src/sentry/testutils/notifications/platform.py
index f6de4e6fc8d5..acfc44b8d3cf 100644
--- a/src/sentry/testutils/notifications/platform.py
+++ b/src/sentry/testutils/notifications/platform.py
@@ -2,7 +2,7 @@
from sentry.notifications.platform.types import (
BlockQuoteSection,
BoldTextBlock,
- CodeBlock,
+ CodeSection,
CodeTextBlock,
ItalicTextBlock,
LinkTextBlock,
@@ -15,7 +15,7 @@
NotificationStrategy,
NotificationTarget,
NotificationTemplate,
- ParagraphBlock,
+ ParagraphSection,
PlainTextBlock,
)
@@ -37,7 +37,7 @@ def render(self, data: MockNotification) -> NotificationRenderedTemplate:
ItalicTextBlock(text="Mock Notification"),
],
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(text=data.message),
BoldTextBlock(text="important"),
@@ -45,7 +45,7 @@ def render(self, data: MockNotification) -> NotificationRenderedTemplate:
LinkTextBlock(text="View Issue", url="https://sentry.io/issue/1"),
]
),
- CodeBlock(blocks=[PlainTextBlock(text="raise Exception('test')")]),
+ CodeSection(blocks=[PlainTextBlock(text="raise Exception('test')")]),
BlockQuoteSection(blocks=[PlainTextBlock(text="This is a quoted message")]),
],
actions=[
diff --git a/tests/sentry/notifications/platform/discord/test_provider.py b/tests/sentry/notifications/platform/discord/test_provider.py
index 5a8c0e210bb5..843a4c8f96ec 100644
--- a/tests/sentry/notifications/platform/discord/test_provider.py
+++ b/tests/sentry/notifications/platform/discord/test_provider.py
@@ -24,7 +24,7 @@
NotificationRenderedImage,
NotificationRenderedTemplate,
NotificationTargetResourceType,
- ParagraphBlock,
+ ParagraphSection,
PlainTextBlock,
)
from sentry.testutils.cases import TestCase
@@ -97,7 +97,7 @@ def test_default_renderer(self) -> None:
def test_renderer_without_chart(self) -> None:
rendered_template = NotificationRenderedTemplate(
subject="Test Without Chart",
- body=[ParagraphBlock(blocks=[PlainTextBlock(text="test without chart")])],
+ body=[ParagraphSection(blocks=[PlainTextBlock(text="test without chart")])],
actions=[
NotificationRenderedAction(label="Visit Sentry", link="https://www.sentry.io")
],
@@ -118,7 +118,7 @@ def test_renderer_without_chart(self) -> None:
def test_renderer_without_footer(self) -> None:
rendered_template = NotificationRenderedTemplate(
subject="Test Without Footer",
- body=[ParagraphBlock(blocks=[PlainTextBlock(text="test without footer")])],
+ body=[ParagraphSection(blocks=[PlainTextBlock(text="test without footer")])],
actions=[
NotificationRenderedAction(label="Visit Sentry", link="https://www.sentry.io")
],
@@ -142,7 +142,7 @@ def test_renderer_without_footer(self) -> None:
def test_renderer_without_actions(self) -> None:
rendered_template = NotificationRenderedTemplate(
subject="Test Without Actions",
- body=[ParagraphBlock(blocks=[PlainTextBlock(text="test without actions")])],
+ body=[ParagraphSection(blocks=[PlainTextBlock(text="test without actions")])],
actions=[], # No actions
footer="Test footer",
chart=NotificationRenderedImage(
@@ -172,7 +172,7 @@ def test_renderer_multiple_actions(self) -> None:
# Create a custom rendered template with multiple actions
rendered_template = NotificationRenderedTemplate(
subject="Test Multiple Actions",
- body=[ParagraphBlock(blocks=[PlainTextBlock(text="test with multiple actions")])],
+ body=[ParagraphSection(blocks=[PlainTextBlock(text="test with multiple actions")])],
actions=actions,
footer="Test footer",
chart=NotificationRenderedImage(
diff --git a/tests/sentry/notifications/platform/email/test_provider.py b/tests/sentry/notifications/platform/email/test_provider.py
index 443e50e2f1c9..29be72c67de3 100644
--- a/tests/sentry/notifications/platform/email/test_provider.py
+++ b/tests/sentry/notifications/platform/email/test_provider.py
@@ -15,7 +15,7 @@
NotificationTargetResourceType,
NotificationTextBlock,
NotificationTextBlockType,
- ParagraphBlock,
+ ParagraphSection,
PlainTextBlock,
)
from sentry.testutils.cases import TestCase
@@ -108,7 +108,7 @@ def test_xss_protection(self) -> None:
xss_template = NotificationRenderedTemplate(
subject="Test XSS",
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[
PlainTextBlock(text=""),
BoldTextBlock(text="
"),
@@ -137,7 +137,7 @@ def test_xss_protection_link_block(self) -> None:
xss_template = NotificationRenderedTemplate(
subject="Test XSS Link",
body=[
- ParagraphBlock(
+ ParagraphSection(
blocks=[
LinkTextBlock(
text='',
diff --git a/tests/sentry/notifications/platform/msteams/test_provider.py b/tests/sentry/notifications/platform/msteams/test_provider.py
index 3e45ef2d8e51..e8725b1c2d7b 100644
--- a/tests/sentry/notifications/platform/msteams/test_provider.py
+++ b/tests/sentry/notifications/platform/msteams/test_provider.py
@@ -52,7 +52,7 @@ def test_default_renderer(self) -> None:
assert title_block["size"] == TextSize.LARGE
assert title_block["weight"] == TextWeight.BOLDER
- # Verify body block 1 (ParagraphBlock)
+ # Verify body section 1 (ParagraphSection)
body_block_1 = body_blocks[1]
assert body_block_1["type"] == "TextBlock"
assert (
@@ -60,12 +60,12 @@ def test_default_renderer(self) -> None:
== "test **important** _urgent_ [View Issue](https://sentry.io/issue/1)"
)
- # Verify body block 2 (CodeBlock)
+ # Verify body section 2 (CodeSection)
body_block_2 = body_blocks[2]
assert body_block_2["type"] == "CodeBlock"
assert body_block_2["codeSnippet"] == "raise Exception('test')"
- # Verify body block 3 (BlockQuoteSection)
+ # Verify body section 3 (BlockQuoteSection)
body_block_3 = body_blocks[3]
assert body_block_3["type"] == "TextBlock"
assert "This is a quoted message" in body_block_3["text"]
diff --git a/tests/sentry/notifications/platform/templates/test_seer.py b/tests/sentry/notifications/platform/templates/test_seer.py
index e31833f1670d..5285106a09be 100644
--- a/tests/sentry/notifications/platform/templates/test_seer.py
+++ b/tests/sentry/notifications/platform/templates/test_seer.py
@@ -10,7 +10,7 @@
SeerAutofixUpdate,
_get_next_stopping_point,
)
-from sentry.notifications.platform.types import ParagraphBlock
+from sentry.notifications.platform.types import ParagraphSection
from sentry.seer.autofix.utils import AutofixStoppingPoint
from sentry.testutils.cases import TestCase
@@ -116,7 +116,7 @@ def test_render(self) -> None:
assert rendered.subject == "Seer had some trouble..."
assert len(rendered.body) == 1
- assert isinstance(rendered.body[0], ParagraphBlock)
+ assert isinstance(rendered.body[0], ParagraphSection)
assert rendered.body[0].blocks[0].text == "Seer could not explore your organization."
def test_render_custom_title(self) -> None: