diff --git a/static/app/debug/notifications/components/notificationBodyRenderer.tsx b/static/app/debug/notifications/components/notificationBodyRenderer.tsx new file mode 100644 index 00000000000000..cc8355853e8c2d --- /dev/null +++ b/static/app/debug/notifications/components/notificationBodyRenderer.tsx @@ -0,0 +1,112 @@ +import {Fragment} from 'react'; +import styled from '@emotion/styled'; + +// Match the Python types from notifications/platform/types.py +enum NotificationBodyFormattingBlockType { + PARAGRAPH = 'paragraph', + CODE_BLOCK = 'code_block', +} + +enum NotificationBodyTextBlockType { + PLAIN_TEXT = 'plain_text', + BOLD_TEXT = 'bold_text', + CODE = 'code', +} + +interface NotificationBodyTextBlock { + text: string; + type: NotificationBodyTextBlockType; +} + +export interface NotificationBodyFormattingBlock { + blocks: NotificationBodyTextBlock[]; + type: NotificationBodyFormattingBlockType; +} + +interface NotificationBodyRendererProps { + body: NotificationBodyFormattingBlock[]; + codeBlockBackground?: string; + codeBlockBorder?: string; + codeBlockTextColor?: string; +} + +function renderTextBlock(block: NotificationBodyTextBlock, index: number) { + switch (block.type) { + case NotificationBodyTextBlockType.PLAIN_TEXT: + return {block.text} ; + case NotificationBodyTextBlockType.BOLD_TEXT: + return {block.text} ; + case NotificationBodyTextBlockType.CODE: + return {block.text} ; + default: + return {block.text} ; + } +} + +function renderFormattingBlock( + block: NotificationBodyFormattingBlock, + index: number, + codeBlockBg: string, + codeBlockBorder: string, + codeBlockTextColor: string +) { + if (block.type === NotificationBodyFormattingBlockType.PARAGRAPH) { + return ( +
+ {block.blocks.map((textBlock, i) => renderTextBlock(textBlock, i))} +
+ ); + } + if (block.type === NotificationBodyFormattingBlockType.CODE_BLOCK) { + return ( +
+ + {block.blocks.map((textBlock, i) => renderTextBlock(textBlock, i))} + +
+ ); + } + return null; +} + +const StyledCodeBlock = styled('code')<{ + backgroundColor: string; + borderColor: string; + textColor: string; +}>` + display: block; + padding: 12px; + background-color: ${p => p.backgroundColor}; + border: 1px solid ${p => p.borderColor}; + border-radius: 6px; + font-family: monospace; + font-size: 13px; + white-space: pre-wrap; + word-break: break-word; + color: ${p => p.textColor}; +`; + +export function NotificationBodyRenderer({ + body, + codeBlockBackground = '#f6f8fa', + codeBlockBorder = '#e1e4e8', + codeBlockTextColor = '#24292e', +}: NotificationBodyRendererProps) { + return ( +
+ {body.map((block, index) => + renderFormattingBlock( + block, + index, + codeBlockBackground, + codeBlockBorder, + codeBlockTextColor + ) + )} +
+ ); +} diff --git a/static/app/debug/notifications/previews/discordPreview.tsx b/static/app/debug/notifications/previews/discordPreview.tsx index b8580f84f31f9f..ecbe0789a4b189 100644 --- a/static/app/debug/notifications/previews/discordPreview.tsx +++ b/static/app/debug/notifications/previews/discordPreview.tsx @@ -8,6 +8,7 @@ import {Image} from 'sentry/components/core/image'; import {Container, Flex, Grid} from 'sentry/components/core/layout'; import {Text} from 'sentry/components/core/text'; import {DebugNotificationsPreview} from 'sentry/debug/notifications/components/debugNotificationsPreview'; +import {NotificationBodyRenderer} from 'sentry/debug/notifications/components/notificationBodyRenderer'; import { NotificationProviderKey, type NotificationTemplateRegistration, @@ -46,7 +47,14 @@ export function DiscordPreview({ {subject} - {JSON.stringify(body)} + + + {chart && ( {subject} - {JSON.stringify(body)} + + + {actions.map(action => ( diff --git a/static/app/debug/notifications/previews/teamsPreview.tsx b/static/app/debug/notifications/previews/teamsPreview.tsx index 03ce198379f5df..077e189ce61f91 100644 --- a/static/app/debug/notifications/previews/teamsPreview.tsx +++ b/static/app/debug/notifications/previews/teamsPreview.tsx @@ -11,6 +11,7 @@ import {Image} from 'sentry/components/core/image/image'; import {Container, Flex, Grid} from 'sentry/components/core/layout'; import {Text} from 'sentry/components/core/text'; import {DebugNotificationsPreview} from 'sentry/debug/notifications/components/debugNotificationsPreview'; +import {NotificationBodyRenderer} from 'sentry/debug/notifications/components/notificationBodyRenderer'; import { NotificationProviderKey, type NotificationTemplateRegistration, @@ -75,7 +76,13 @@ export function TeamsPreview({ {subject} - {JSON.stringify(body)} + + + {actions.map(action => ( diff --git a/static/app/debug/notifications/types.ts b/static/app/debug/notifications/types.ts index 3a20b98a658b56..57dd8697db5206 100644 --- a/static/app/debug/notifications/types.ts +++ b/static/app/debug/notifications/types.ts @@ -1,3 +1,5 @@ +import type {NotificationBodyFormattingBlock} from 'sentry/debug/notifications/components/notificationBodyRenderer'; + export enum NotificationProviderKey { EMAIL = 'email', SLACK = 'slack', @@ -9,7 +11,7 @@ export interface NotificationTemplateRegistration { category: string; example: { actions: Array<{label: string; link: string}>; - body: string; + body: NotificationBodyFormattingBlock[]; subject: string; chart?: {alt_text: string; url: string}; footer?: string;