@@ -180,6 +180,42 @@ export function formatFrameHeader(
180180 }
181181}
182182
183+ /**
184+ * Whether the shared formatter covers this event type, and so whether its body should be used
185+ * instead of the local rendering.
186+ *
187+ * "default" is an error event without exception data, "generic" a performance regression or
188+ * metric issue, "csp" a Content Security Policy violation. Anything else (a transaction, most
189+ * notably) keeps the local path, which renders things the shared body does not carry such as
190+ * the fetched performance trace.
191+ */
192+ /**
193+ * Whether to read the issue's metadata instead of its top level fields. Performance issues can
194+ * have various categories such as 'db_query', but the issueType starts with 'performance_'.
195+ *
196+ * It matters which side of this an issue falls on: metadata.value is a query pattern for a
197+ * performance issue and the exception message for an error, so reading it unconditionally
198+ * misnames the error text.
199+ */
200+ export function isPerformanceIssueType ( issue : {
201+ issueType ?: string | null ;
202+ issueCategory ?: string | null ;
203+ } ) : boolean {
204+ return (
205+ issue . issueType ?. startsWith ( "performance_" ) === true ||
206+ issue . issueCategory === "performance"
207+ ) ;
208+ }
209+
210+ export function usesSharedFormatterBody ( event : { type ?: unknown } ) : boolean {
211+ return (
212+ event . type === "error" ||
213+ event . type === "default" ||
214+ event . type === "generic" ||
215+ event . type === "csp"
216+ ) ;
217+ }
218+
183219/**
184220 * Formats a Sentry event into a structured markdown output.
185221 * Includes error messages, stack traces, request info, and contextual data.
@@ -1986,11 +2022,7 @@ export function formatIssueOutput({
19862022} ) {
19872023 let output = `# Issue ${ issue . shortId } in **${ organizationSlug } **\n\n` ;
19882024
1989- // Check if this is a performance issue based on issueCategory or issueType
1990- // Performance issues can have various categories like 'db_query' but issueType starts with 'performance_'
1991- const isPerformanceIssue =
1992- issue . issueType ?. startsWith ( "performance_" ) ||
1993- issue . issueCategory === "performance" ;
2025+ const isPerformanceIssue = isPerformanceIssueType ( issue ) ;
19942026
19952027 if ( isPerformanceIssue && issue . metadata ) {
19962028 // For performance issues, use metadata for better context
@@ -2118,14 +2150,7 @@ export function formatIssueOutput({
21182150
21192151 output += `**Event ID**: ${ event . id } \n` ;
21202152 output += `**Type**: ${ event . type } \n` ;
2121- // "default" type represents error events without exception data
2122- // "generic" type represents performance regressions and metric-based issues
2123- // "csp" type represents Content Security Policy violations
2124- const isSharedFormatterType =
2125- event . type === "error" ||
2126- event . type === "default" ||
2127- event . type === "generic" ||
2128- event . type === "csp" ;
2153+ const isSharedFormatterType = usesSharedFormatterBody ( event ) ;
21292154 if ( isSharedFormatterType ) {
21302155 const typedEvent = event as
21312156 | z . infer < typeof ErrorEventSchema >
@@ -2140,7 +2165,13 @@ export function formatIssueOutput({
21402165 output += `**Message**:\n${ event . message } \n` ;
21412166 }
21422167 output += "\n" ;
2143- if ( isSharedFormatterType && event . formatted ?. content ) {
2168+ // only a markdown body belongs in this output; a json body is for structuredContent, and
2169+ // pasting it here would put a serialized object in the middle of the prose
2170+ if (
2171+ isSharedFormatterType &&
2172+ event . formatted ?. format === "markdown" &&
2173+ event . formatted . content
2174+ ) {
21442175 // the shared formatter body doesn't include the replay note — add it here to match formatEventOutput
21452176 output += formatIssueReplayOutput ( {
21462177 apiService,
@@ -2432,7 +2463,7 @@ function formatIssueReplayOutput({
24322463 return `${ lines . join ( "\n" ) } \n\n` ;
24332464}
24342465
2435- function getReplayIdFromEvent ( event : Event ) : string | null {
2466+ export function getReplayIdFromEvent ( event : Event ) : string | null {
24362467 const replayContext = event . contexts ?. replay as
24372468 | Record < string , unknown >
24382469 | undefined ;
@@ -2447,7 +2478,7 @@ function getReplayIdFromEvent(event: Event): string | null {
24472478 return normalizeReplayId ( replayContextId ?? replayTagId ) ;
24482479}
24492480
2450- function dedupeReplayIds ( replayIds : string [ ] ) : string [ ] {
2481+ export function dedupeReplayIds ( replayIds : string [ ] ) : string [ ] {
24512482 const normalizedReplayIds : string [ ] = [ ] ;
24522483 const seenReplayIds = new Set < string > ( ) ;
24532484
0 commit comments