groupEventDetailsContent.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. import {Fragment, lazy, useMemo, useRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import {usePrompt} from 'sentry/actionCreators/prompts';
  4. import {Button} from 'sentry/components/button';
  5. import {CommitRow} from 'sentry/components/commitRow';
  6. import ErrorBoundary from 'sentry/components/errorBoundary';
  7. import BreadcrumbsDataSection from 'sentry/components/events/breadcrumbs/breadcrumbsDataSection';
  8. import {EventContexts} from 'sentry/components/events/contexts';
  9. import {EventDevice} from 'sentry/components/events/device';
  10. import {EventAttachments} from 'sentry/components/events/eventAttachments';
  11. import {EventDataSection} from 'sentry/components/events/eventDataSection';
  12. import {EventEvidence} from 'sentry/components/events/eventEvidence';
  13. import {EventExtraData} from 'sentry/components/events/eventExtraData';
  14. import EventHydrationDiff from 'sentry/components/events/eventHydrationDiff';
  15. import {EventProcessingErrors} from 'sentry/components/events/eventProcessingErrors';
  16. import EventReplay from 'sentry/components/events/eventReplay';
  17. import {EventSdk} from 'sentry/components/events/eventSdk';
  18. import AggregateSpanDiff from 'sentry/components/events/eventStatisticalDetector/aggregateSpanDiff';
  19. import EventBreakpointChart from 'sentry/components/events/eventStatisticalDetector/breakpointChart';
  20. import {EventAffectedTransactions} from 'sentry/components/events/eventStatisticalDetector/eventAffectedTransactions';
  21. import EventComparison from 'sentry/components/events/eventStatisticalDetector/eventComparison';
  22. import {EventDifferentialFlamegraph} from 'sentry/components/events/eventStatisticalDetector/eventDifferentialFlamegraph';
  23. import {EventFunctionComparisonList} from 'sentry/components/events/eventStatisticalDetector/eventFunctionComparisonList';
  24. import {EventRegressionSummary} from 'sentry/components/events/eventStatisticalDetector/eventRegressionSummary';
  25. import {EventFunctionBreakpointChart} from 'sentry/components/events/eventStatisticalDetector/functionBreakpointChart';
  26. import {TransactionsDeltaProvider} from 'sentry/components/events/eventStatisticalDetector/transactionsDeltaProvider';
  27. import {EventTagsAndScreenshot} from 'sentry/components/events/eventTagsAndScreenshot';
  28. import {ScreenshotDataSection} from 'sentry/components/events/eventTagsAndScreenshot/screenshot/screenshotDataSection';
  29. import EventTagsDataSection from 'sentry/components/events/eventTagsAndScreenshot/tags';
  30. import {EventViewHierarchy} from 'sentry/components/events/eventViewHierarchy';
  31. import {EventFeatureFlagList} from 'sentry/components/events/featureFlags/eventFeatureFlagList';
  32. import {EventGroupingInfoSection} from 'sentry/components/events/groupingInfo/groupingInfoSection';
  33. import HighlightsDataSection from 'sentry/components/events/highlights/highlightsDataSection';
  34. import {HighlightsIconSummary} from 'sentry/components/events/highlights/highlightsIconSummary';
  35. import {ActionableItems} from 'sentry/components/events/interfaces/crashContent/exception/actionableItems';
  36. import {actionableItemsEnabled} from 'sentry/components/events/interfaces/crashContent/exception/useActionableItems';
  37. import {CronTimelineSection} from 'sentry/components/events/interfaces/crons/cronTimelineSection';
  38. import {Csp} from 'sentry/components/events/interfaces/csp';
  39. import {DebugMeta} from 'sentry/components/events/interfaces/debugMeta';
  40. import {Exception} from 'sentry/components/events/interfaces/exception';
  41. import {Generic} from 'sentry/components/events/interfaces/generic';
  42. import {Message} from 'sentry/components/events/interfaces/message';
  43. import {AnrRootCause} from 'sentry/components/events/interfaces/performance/anrRootCause';
  44. import {EventTraceView} from 'sentry/components/events/interfaces/performance/eventTraceView';
  45. import {SpanEvidenceSection} from 'sentry/components/events/interfaces/performance/spanEvidence';
  46. import {Request} from 'sentry/components/events/interfaces/request';
  47. import {StackTrace} from 'sentry/components/events/interfaces/stackTrace';
  48. import {Template} from 'sentry/components/events/interfaces/template';
  49. import {Threads} from 'sentry/components/events/interfaces/threads';
  50. import {UptimeDataSection} from 'sentry/components/events/interfaces/uptime/uptimeDataSection';
  51. import {EventPackageData} from 'sentry/components/events/packageData';
  52. import {EventRRWebIntegration} from 'sentry/components/events/rrwebIntegration';
  53. import {DataSection} from 'sentry/components/events/styles';
  54. import {SuspectCommits} from 'sentry/components/events/suspectCommits';
  55. import {EventUserFeedback} from 'sentry/components/events/userFeedback';
  56. import LazyLoad from 'sentry/components/lazyLoad';
  57. import Placeholder from 'sentry/components/placeholder';
  58. import {IconChevron} from 'sentry/icons';
  59. import {t} from 'sentry/locale';
  60. import {space} from 'sentry/styles/space';
  61. import type {Entry, EntryException, Event, EventTransaction} from 'sentry/types/event';
  62. import {EntryType, EventOrGroupType} from 'sentry/types/event';
  63. import type {Group} from 'sentry/types/group';
  64. import {IssueCategory} from 'sentry/types/group';
  65. import type {Project} from 'sentry/types/project';
  66. import {defined} from 'sentry/utils';
  67. import {
  68. getConfigForIssueType,
  69. shouldShowCustomErrorResourceConfig,
  70. } from 'sentry/utils/issueTypeConfig';
  71. import {QuickTraceContext} from 'sentry/utils/performance/quickTrace/quickTraceContext';
  72. import QuickTraceQuery from 'sentry/utils/performance/quickTrace/quickTraceQuery';
  73. import {getReplayIdFromEvent} from 'sentry/utils/replays/getReplayIdFromEvent';
  74. import {useLocation} from 'sentry/utils/useLocation';
  75. import useOrganization from 'sentry/utils/useOrganization';
  76. import {ResourcesAndPossibleSolutions} from 'sentry/views/issueDetails/resourcesAndPossibleSolutions';
  77. import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
  78. import {EventDetails} from 'sentry/views/issueDetails/streamline/eventDetails';
  79. import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
  80. import {TraceDataSection} from 'sentry/views/issueDetails/traceDataSection';
  81. import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';
  82. const LLMMonitoringSection = lazy(
  83. () => import('sentry/components/events/interfaces/llm-monitoring/llmMonitoringSection')
  84. );
  85. export interface EventDetailsContentProps {
  86. group: Group;
  87. project: Project;
  88. event?: Event;
  89. }
  90. export function EventDetailsContent({
  91. group,
  92. event,
  93. project,
  94. }: Required<EventDetailsContentProps>) {
  95. const organization = useOrganization();
  96. const location = useLocation();
  97. const hasStreamlinedUI = useHasStreamlinedUI();
  98. const tagsRef = useRef<HTMLDivElement>(null);
  99. const eventEntries = useMemo(() => {
  100. const {entries = []} = event;
  101. return entries.reduce<{[key in EntryType]?: Entry}>((entryMap, entry) => {
  102. entryMap[entry.type] = entry;
  103. return entryMap;
  104. }, {});
  105. }, [event]);
  106. const projectSlug = project.slug;
  107. const hasReplay = Boolean(getReplayIdFromEvent(event));
  108. const mechanism = event.tags?.find(({key}) => key === 'mechanism')?.value;
  109. const isANR = mechanism === 'ANR' || mechanism === 'AppExitInfo';
  110. const showPossibleSolutionsHigher = shouldShowCustomErrorResourceConfig(group, project);
  111. const groupingCurrentLevel = group?.metadata?.current_level;
  112. const hasFeatureFlagSection = organization.features.includes('feature-flag-ui');
  113. const hasActionableItems = actionableItemsEnabled({
  114. eventId: event.id,
  115. organization,
  116. projectSlug,
  117. });
  118. const {
  119. isLoading: promptLoading,
  120. isError: promptError,
  121. isPromptDismissed,
  122. dismissPrompt,
  123. showPrompt,
  124. } = usePrompt({
  125. feature: 'issue_feedback_hidden',
  126. organization,
  127. projectId: project.id,
  128. });
  129. // default to show on error or isPromptDismissed === undefined
  130. const showFeedback = !isPromptDismissed || promptError || hasStreamlinedUI;
  131. const issueTypeConfig = getConfigForIssueType(group, group.project);
  132. return (
  133. <Fragment>
  134. {hasStreamlinedUI && <HighlightsIconSummary event={event} group={group} />}
  135. {hasActionableItems && !hasStreamlinedUI && (
  136. <ActionableItems event={event} project={project} isShare={false} />
  137. )}
  138. <StyledDataSection>
  139. {!hasStreamlinedUI && <TraceDataSection event={event} />}
  140. {!hasStreamlinedUI && (
  141. <SuspectCommits
  142. projectSlug={project.slug}
  143. eventId={event.id}
  144. group={group}
  145. commitRow={CommitRow}
  146. />
  147. )}
  148. </StyledDataSection>
  149. {event.userReport && (
  150. <InterimSection
  151. title={t('User Feedback')}
  152. type={SectionKey.USER_FEEDBACK}
  153. actions={
  154. hasStreamlinedUI ? null : (
  155. <ErrorBoundary mini>
  156. <Button
  157. size="xs"
  158. icon={<IconChevron direction={showFeedback ? 'up' : 'down'} />}
  159. onClick={showFeedback ? dismissPrompt : showPrompt}
  160. title={
  161. showFeedback
  162. ? t('Hide feedback on all issue details')
  163. : t('Unhide feedback on all issue details')
  164. }
  165. disabled={promptError}
  166. busy={promptLoading}
  167. >
  168. {showFeedback ? t('Hide') : t('Show')}
  169. </Button>
  170. </ErrorBoundary>
  171. )
  172. }
  173. >
  174. {promptLoading ? (
  175. <Placeholder />
  176. ) : showFeedback ? (
  177. <EventUserFeedback
  178. report={event.userReport}
  179. orgSlug={organization.slug}
  180. issueId={group.id}
  181. showEventLink={false}
  182. />
  183. ) : null}
  184. </InterimSection>
  185. )}
  186. {event.type === EventOrGroupType.ERROR &&
  187. organization.features.includes('insights-addon-modules') &&
  188. event?.entries
  189. ?.filter((x): x is EntryException => x.type === EntryType.EXCEPTION)
  190. .flatMap(x => x.data.values ?? [])
  191. .some(({value}) => {
  192. const lowerText = value?.toLowerCase();
  193. return (
  194. lowerText &&
  195. (lowerText.includes('api key') || lowerText.includes('429')) &&
  196. (lowerText.includes('openai') ||
  197. lowerText.includes('anthropic') ||
  198. lowerText.includes('cohere') ||
  199. lowerText.includes('langchain'))
  200. );
  201. }) ? (
  202. <LazyLoad
  203. LazyComponent={LLMMonitoringSection}
  204. event={event}
  205. organization={organization}
  206. />
  207. ) : null}
  208. {group.issueCategory === IssueCategory.UPTIME && (
  209. <UptimeDataSection event={event} project={project} group={group} />
  210. )}
  211. {group.issueCategory === IssueCategory.CRON && (
  212. <CronTimelineSection
  213. event={event}
  214. organization={organization}
  215. project={project}
  216. />
  217. )}
  218. {!hasStreamlinedUI && issueTypeConfig.tags.enabled && (
  219. <HighlightsDataSection event={event} project={project} viewAllRef={tagsRef} />
  220. )}
  221. {showPossibleSolutionsHigher && (
  222. <ResourcesAndPossibleSolutionsIssueDetailsContent
  223. event={event}
  224. project={project}
  225. group={group}
  226. />
  227. )}
  228. <EventEvidence event={event} group={group} project={project} />
  229. {defined(eventEntries[EntryType.MESSAGE]) && (
  230. <EntryErrorBoundary type={EntryType.MESSAGE}>
  231. <Message event={event} data={eventEntries[EntryType.MESSAGE].data} />
  232. </EntryErrorBoundary>
  233. )}
  234. {defined(eventEntries[EntryType.EXCEPTION]) && (
  235. <EntryErrorBoundary type={EntryType.EXCEPTION}>
  236. <Exception
  237. event={event}
  238. data={eventEntries[EntryType.EXCEPTION].data}
  239. projectSlug={project.slug}
  240. group={group}
  241. groupingCurrentLevel={groupingCurrentLevel}
  242. />
  243. </EntryErrorBoundary>
  244. )}
  245. {issueTypeConfig.stacktrace.enabled &&
  246. defined(eventEntries[EntryType.STACKTRACE]) && (
  247. <EntryErrorBoundary type={EntryType.STACKTRACE}>
  248. <StackTrace
  249. event={event}
  250. data={eventEntries[EntryType.STACKTRACE].data}
  251. projectSlug={projectSlug}
  252. groupingCurrentLevel={groupingCurrentLevel}
  253. />
  254. </EntryErrorBoundary>
  255. )}
  256. {defined(eventEntries[EntryType.THREADS]) && (
  257. <EntryErrorBoundary type={EntryType.THREADS}>
  258. <Threads
  259. event={event}
  260. data={eventEntries[EntryType.THREADS].data}
  261. projectSlug={project.slug}
  262. groupingCurrentLevel={groupingCurrentLevel}
  263. group={group}
  264. />
  265. </EntryErrorBoundary>
  266. )}
  267. {hasStreamlinedUI && (
  268. <ScreenshotDataSection event={event} projectSlug={project.slug} />
  269. )}
  270. {isANR && (
  271. <QuickTraceQuery
  272. event={event}
  273. location={location}
  274. orgSlug={organization.slug}
  275. type={'spans'}
  276. skipLight
  277. >
  278. {results => {
  279. return (
  280. <QuickTraceContext.Provider value={results}>
  281. <AnrRootCause event={event} organization={organization} />
  282. </QuickTraceContext.Provider>
  283. );
  284. }}
  285. </QuickTraceQuery>
  286. )}
  287. {issueTypeConfig.spanEvidence.enabled && (
  288. <SpanEvidenceSection
  289. event={event as EventTransaction}
  290. organization={organization}
  291. projectSlug={project.slug}
  292. />
  293. )}
  294. {issueTypeConfig.regression.enabled && (
  295. <ErrorBoundary mini>
  296. <EventRegressionSummary event={event} group={group} />
  297. </ErrorBoundary>
  298. )}
  299. {issueTypeConfig.performanceDurationRegression.enabled && (
  300. <Fragment>
  301. <ErrorBoundary mini>
  302. <EventBreakpointChart event={event} />
  303. </ErrorBoundary>
  304. <ErrorBoundary mini>
  305. <AggregateSpanDiff event={event} project={project} />
  306. </ErrorBoundary>
  307. <ErrorBoundary mini>
  308. <EventComparison event={event} project={project} />
  309. </ErrorBoundary>
  310. </Fragment>
  311. )}
  312. {issueTypeConfig.profilingDurationRegression.enabled && (
  313. <Fragment>
  314. <TransactionsDeltaProvider event={event} project={project}>
  315. <ErrorBoundary mini>
  316. <EventFunctionBreakpointChart event={event} />
  317. </ErrorBoundary>
  318. <ErrorBoundary mini>
  319. <EventAffectedTransactions event={event} group={group} project={project} />
  320. </ErrorBoundary>
  321. <ErrorBoundary mini>
  322. <InterimSection
  323. type={SectionKey.REGRESSION_FLAMEGRAPH}
  324. title={t('Regression Flamegraph')}
  325. >
  326. <b>{t('Largest Changes in Call Stack Frequency')}</b>
  327. <p>
  328. {t(`See which functions changed the most before and after the regression. The
  329. frame with the largest increase in call stack population likely
  330. contributed to the cause for the duration regression.`)}
  331. </p>
  332. <EventDifferentialFlamegraph event={event} />
  333. </InterimSection>
  334. </ErrorBoundary>
  335. <ErrorBoundary mini>
  336. <EventFunctionComparisonList
  337. event={event}
  338. group={group}
  339. project={project}
  340. />
  341. </ErrorBoundary>
  342. </TransactionsDeltaProvider>
  343. </Fragment>
  344. )}
  345. <EventHydrationDiff event={event} group={group} />
  346. {issueTypeConfig.replays.enabled && (
  347. <EventReplay event={event} group={group} projectSlug={project.slug} />
  348. )}
  349. {defined(eventEntries[EntryType.HPKP]) && (
  350. <EntryErrorBoundary type={EntryType.HPKP}>
  351. <Generic
  352. type={EntryType.HPKP}
  353. data={eventEntries[EntryType.HPKP].data}
  354. meta={event._meta?.hpkp ?? {}}
  355. />
  356. </EntryErrorBoundary>
  357. )}
  358. {defined(eventEntries[EntryType.CSP]) && (
  359. <EntryErrorBoundary type={EntryType.CSP}>
  360. <Csp event={event} data={eventEntries[EntryType.CSP].data} />
  361. </EntryErrorBoundary>
  362. )}
  363. {defined(eventEntries[EntryType.EXPECTCT]) && (
  364. <EntryErrorBoundary type={EntryType.EXPECTCT}>
  365. <Generic
  366. type={EntryType.EXPECTCT}
  367. data={eventEntries[EntryType.EXPECTCT].data}
  368. />
  369. </EntryErrorBoundary>
  370. )}
  371. {defined(eventEntries[EntryType.EXPECTSTAPLE]) && (
  372. <EntryErrorBoundary type={EntryType.EXPECTSTAPLE}>
  373. <Generic
  374. type={EntryType.EXPECTSTAPLE}
  375. data={eventEntries[EntryType.EXPECTSTAPLE].data}
  376. />
  377. </EntryErrorBoundary>
  378. )}
  379. {defined(eventEntries[EntryType.TEMPLATE]) && (
  380. <EntryErrorBoundary type={EntryType.TEMPLATE}>
  381. <Template event={event} data={eventEntries[EntryType.TEMPLATE].data} />
  382. </EntryErrorBoundary>
  383. )}
  384. <BreadcrumbsDataSection event={event} group={group} project={project} />
  385. {hasStreamlinedUI && (
  386. <EventTraceView group={group} event={event} organization={organization} />
  387. )}
  388. {!showPossibleSolutionsHigher && (
  389. <ResourcesAndPossibleSolutionsIssueDetailsContent
  390. event={event}
  391. project={project}
  392. group={group}
  393. />
  394. )}
  395. {defined(eventEntries[EntryType.DEBUGMETA]) && (
  396. <EntryErrorBoundary type={EntryType.DEBUGMETA}>
  397. <DebugMeta
  398. event={event}
  399. projectSlug={projectSlug}
  400. groupId={group?.id}
  401. data={eventEntries[EntryType.DEBUGMETA].data}
  402. />
  403. </EntryErrorBoundary>
  404. )}
  405. {defined(eventEntries[EntryType.REQUEST]) && (
  406. <EntryErrorBoundary type={EntryType.REQUEST}>
  407. <Request event={event} data={eventEntries[EntryType.REQUEST].data} />
  408. </EntryErrorBoundary>
  409. )}
  410. {issueTypeConfig.tags.enabled ? (
  411. <Fragment>
  412. {hasStreamlinedUI ? (
  413. <Fragment>
  414. <HighlightsDataSection
  415. event={event}
  416. project={project}
  417. viewAllRef={tagsRef}
  418. />
  419. <EventTagsDataSection
  420. event={event}
  421. projectSlug={project.slug}
  422. ref={tagsRef}
  423. />
  424. </Fragment>
  425. ) : (
  426. <div ref={tagsRef}>
  427. <EventTagsAndScreenshot event={event} projectSlug={project.slug} />
  428. </div>
  429. )}
  430. </Fragment>
  431. ) : null}
  432. <EventContexts group={group} event={event} />
  433. {hasFeatureFlagSection && (
  434. <EventFeatureFlagList group={group} project={project} event={event} />
  435. )}
  436. <EventExtraData event={event} />
  437. <EventPackageData event={event} />
  438. <EventDevice event={event} />
  439. <EventViewHierarchy event={event} project={project} />
  440. <EventAttachments event={event} project={project} group={group} />
  441. <EventSdk sdk={event.sdk} meta={event._meta?.sdk} />
  442. {hasStreamlinedUI && (
  443. <EventProcessingErrors event={event} project={project} isShare={false} />
  444. )}
  445. {event.groupID && (
  446. <EventGroupingInfoSection
  447. projectSlug={project.slug}
  448. event={event}
  449. showGroupingConfig={
  450. organization.features.includes('set-grouping-config') &&
  451. 'groupingConfig' in event
  452. }
  453. group={group}
  454. />
  455. )}
  456. {!hasReplay && (
  457. <EventRRWebIntegration
  458. event={event}
  459. orgId={organization.slug}
  460. projectSlug={project.slug}
  461. />
  462. )}
  463. </Fragment>
  464. );
  465. }
  466. function ResourcesAndPossibleSolutionsIssueDetailsContent({
  467. event,
  468. project,
  469. group,
  470. }: Required<EventDetailsContentProps>) {
  471. return (
  472. <ErrorBoundary mini>
  473. <ResourcesAndPossibleSolutions event={event} project={project} group={group} />
  474. </ErrorBoundary>
  475. );
  476. }
  477. export default function GroupEventDetailsContent({
  478. group,
  479. event,
  480. project,
  481. }: EventDetailsContentProps) {
  482. const hasStreamlinedUI = useHasStreamlinedUI();
  483. if (!event) {
  484. return (
  485. <NotFoundMessage>
  486. <h3>{t('Latest event not available')}</h3>
  487. </NotFoundMessage>
  488. );
  489. }
  490. return hasStreamlinedUI ? (
  491. <EventDetails event={event} group={group} project={project} />
  492. ) : (
  493. <EventDetailsContent group={group} event={event} project={project} />
  494. );
  495. }
  496. /**
  497. * This component is only necessary while the streamlined UI is not in place.
  498. * The FoldSection by default wraps its children with an ErrorBoundary, preventing content
  499. * from crashing the whole page if an error occurs, but EventDataSection does not do this.
  500. */
  501. function EntryErrorBoundary({
  502. children,
  503. type,
  504. }: {
  505. children: React.ReactNode;
  506. type: EntryType;
  507. }) {
  508. return (
  509. <ErrorBoundary
  510. customComponent={
  511. <EventDataSection type={type} title={type}>
  512. <p>{t('There was an error rendering this data.')}</p>
  513. </EventDataSection>
  514. }
  515. >
  516. {children}
  517. </ErrorBoundary>
  518. );
  519. }
  520. const NotFoundMessage = styled('div')`
  521. padding: ${space(2)} ${space(4)};
  522. `;
  523. const StyledDataSection = styled(DataSection)`
  524. padding: ${space(0.5)} ${space(2)};
  525. @media (min-width: ${p => p.theme.breakpoints.medium}) {
  526. padding: ${space(1)} ${space(4)};
  527. }
  528. &:empty {
  529. display: none;
  530. }
  531. `;