groupEventDetailsContent.tsx 19 KB

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