groupEventDetailsContent.tsx 21 KB

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