groupEventDetailsContent.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  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 {EventDataSection} from 'sentry/components/events/eventDataSection';
  12. import {EventEntry} from 'sentry/components/events/eventEntry';
  13. import {EventEvidence} from 'sentry/components/events/eventEvidence';
  14. import {EventExtraData} from 'sentry/components/events/eventExtraData';
  15. import EventHydrationDiff from 'sentry/components/events/eventHydrationDiff';
  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 {EventViewHierarchy} from 'sentry/components/events/eventViewHierarchy';
  29. import {EventGroupingInfo} from 'sentry/components/events/groupingInfo';
  30. import HighlightsDataSection from 'sentry/components/events/highlights/highlightsDataSection';
  31. import {ActionableItems} from 'sentry/components/events/interfaces/crashContent/exception/actionableItems';
  32. import {actionableItemsEnabled} from 'sentry/components/events/interfaces/crashContent/exception/useActionableItems';
  33. import {CronTimelineSection} from 'sentry/components/events/interfaces/crons/cronTimelineSection';
  34. import {AnrRootCause} from 'sentry/components/events/interfaces/performance/anrRootCause';
  35. import {SpanEvidenceSection} from 'sentry/components/events/interfaces/performance/spanEvidence';
  36. import {UptimeDataSection} from 'sentry/components/events/interfaces/uptime/uptimeDataSection';
  37. import {EventPackageData} from 'sentry/components/events/packageData';
  38. import {EventRRWebIntegration} from 'sentry/components/events/rrwebIntegration';
  39. import {DataSection} from 'sentry/components/events/styles';
  40. import {SuspectCommits} from 'sentry/components/events/suspectCommits';
  41. import {EventUserFeedback} from 'sentry/components/events/userFeedback';
  42. import LazyLoad from 'sentry/components/lazyLoad';
  43. import Placeholder from 'sentry/components/placeholder';
  44. import {useHasNewTimelineUI} from 'sentry/components/timeline/utils';
  45. import {IconChevron} from 'sentry/icons';
  46. import {t} from 'sentry/locale';
  47. import {space} from 'sentry/styles/space';
  48. import type {EntryException, Event, EventTransaction} from 'sentry/types/event';
  49. import {EntryType, EventOrGroupType} from 'sentry/types/event';
  50. import type {Group} from 'sentry/types/group';
  51. import {IssueCategory, IssueType} from 'sentry/types/group';
  52. import type {Project} from 'sentry/types/project';
  53. import {shouldShowCustomErrorResourceConfig} from 'sentry/utils/issueTypeConfig';
  54. import {QuickTraceContext} from 'sentry/utils/performance/quickTrace/quickTraceContext';
  55. import QuickTraceQuery from 'sentry/utils/performance/quickTrace/quickTraceQuery';
  56. import {getReplayIdFromEvent} from 'sentry/utils/replays/getReplayIdFromEvent';
  57. import {useLocation} from 'sentry/utils/useLocation';
  58. import useOrganization from 'sentry/utils/useOrganization';
  59. import {ResourcesAndPossibleSolutions} from 'sentry/views/issueDetails/resourcesAndPossibleSolutions';
  60. import {TraceTimeLineOrRelatedIssue} from 'sentry/views/issueDetails/traceTimelineOrRelatedIssue';
  61. const LLMMonitoringSection = lazy(
  62. () => import('sentry/components/events/interfaces/llm-monitoring/llmMonitoringSection')
  63. );
  64. type GroupEventDetailsContentProps = {
  65. group: Group;
  66. project: Project;
  67. event?: Event;
  68. };
  69. type GroupEventEntryProps = {
  70. entryType: EntryType;
  71. event: Event;
  72. group: Group;
  73. project: Project;
  74. };
  75. function GroupEventEntry({event, entryType, group, project}: GroupEventEntryProps) {
  76. const organization = useOrganization();
  77. const matchingEntry = event.entries.find(entry => entry.type === entryType);
  78. if (!matchingEntry) {
  79. return null;
  80. }
  81. return (
  82. <EventEntry
  83. projectSlug={project.slug}
  84. group={group}
  85. entry={matchingEntry}
  86. {...{organization, event}}
  87. />
  88. );
  89. }
  90. function DefaultGroupEventDetailsContent({
  91. group,
  92. event,
  93. project,
  94. }: Required<GroupEventDetailsContentProps>) {
  95. const organization = useOrganization();
  96. const location = useLocation();
  97. const hasNewTimelineUI = useHasNewTimelineUI();
  98. const tagsRef = useRef<HTMLDivElement>(null);
  99. const projectSlug = project.slug;
  100. const hasReplay = Boolean(getReplayIdFromEvent(event));
  101. const mechanism = event.tags?.find(({key}) => key === 'mechanism')?.value;
  102. const isANR = mechanism === 'ANR' || mechanism === 'AppExitInfo';
  103. const hasAnrImprovementsFeature = organization.features.includes('anr-improvements');
  104. const showPossibleSolutionsHigher = shouldShowCustomErrorResourceConfig(group, project);
  105. const eventEntryProps = {group, event, project};
  106. const hasActionableItems = actionableItemsEnabled({
  107. eventId: event.id,
  108. organization,
  109. projectSlug,
  110. });
  111. const {
  112. isLoading: promptLoading,
  113. isError: promptError,
  114. isPromptDismissed,
  115. dismissPrompt,
  116. showPrompt,
  117. } = usePrompt({
  118. feature: 'issue_feedback_hidden',
  119. organization,
  120. projectId: project.id,
  121. });
  122. // default to show on error or isPromptDismissed === undefined
  123. const showFeedback = !isPromptDismissed || promptError;
  124. return (
  125. <Fragment>
  126. {hasActionableItems && (
  127. <ActionableItems event={event} project={project} isShare={false} />
  128. )}
  129. <StyledDataSection>
  130. <TraceTimeLineOrRelatedIssue event={event} />
  131. <SuspectCommits
  132. project={project}
  133. eventId={event.id}
  134. group={group}
  135. commitRow={CommitRow}
  136. />
  137. </StyledDataSection>
  138. {event.userReport && (
  139. <EventDataSection
  140. title={t('User Feedback')}
  141. type="user-feedback"
  142. actions={
  143. <ErrorBoundary mini>
  144. <Button
  145. size="xs"
  146. icon={<IconChevron direction={showFeedback ? 'up' : 'down'} />}
  147. onClick={showFeedback ? dismissPrompt : showPrompt}
  148. title={
  149. showFeedback
  150. ? t('Hide feedback on all issue details')
  151. : t('Unhide feedback on all issue details')
  152. }
  153. disabled={promptError}
  154. busy={promptLoading}
  155. >
  156. {showFeedback ? t('Hide') : t('Show')}
  157. </Button>
  158. </ErrorBoundary>
  159. }
  160. >
  161. {promptLoading ? (
  162. <Placeholder />
  163. ) : showFeedback ? (
  164. <EventUserFeedback
  165. report={event.userReport}
  166. orgSlug={organization.slug}
  167. issueId={group.id}
  168. showEventLink={false}
  169. />
  170. ) : null}
  171. </EventDataSection>
  172. )}
  173. {event.type === EventOrGroupType.ERROR &&
  174. organization.features.includes('insights-addon-modules') &&
  175. event?.entries
  176. ?.filter((x): x is EntryException => x.type === EntryType.EXCEPTION)
  177. .flatMap(x => x.data.values ?? [])
  178. .some(({value}) => {
  179. const lowerText = value?.toLowerCase();
  180. return (
  181. lowerText &&
  182. (lowerText.includes('api key') || lowerText.includes('429')) &&
  183. (lowerText.includes('openai') ||
  184. lowerText.includes('anthropic') ||
  185. lowerText.includes('cohere') ||
  186. lowerText.includes('langchain'))
  187. );
  188. }) ? (
  189. <LazyLoad
  190. LazyComponent={LLMMonitoringSection}
  191. event={event}
  192. organization={organization}
  193. />
  194. ) : null}
  195. {group.issueCategory === IssueCategory.UPTIME && (
  196. <UptimeDataSection group={group} />
  197. )}
  198. {group.issueCategory === IssueCategory.CRON && (
  199. <CronTimelineSection
  200. event={event}
  201. organization={organization}
  202. project={project}
  203. />
  204. )}
  205. <HighlightsDataSection event={event} project={project} viewAllRef={tagsRef} />
  206. {showPossibleSolutionsHigher && (
  207. <ResourcesAndPossibleSolutionsIssueDetailsContent
  208. event={event}
  209. project={project}
  210. group={group}
  211. />
  212. )}
  213. <EventEvidence event={event} group={group} project={project} />
  214. <GroupEventEntry entryType={EntryType.MESSAGE} {...eventEntryProps} />
  215. <GroupEventEntry entryType={EntryType.EXCEPTION} {...eventEntryProps} />
  216. <GroupEventEntry entryType={EntryType.STACKTRACE} {...eventEntryProps} />
  217. <GroupEventEntry entryType={EntryType.THREADS} {...eventEntryProps} />
  218. {hasAnrImprovementsFeature && isANR && (
  219. <QuickTraceQuery
  220. event={event}
  221. location={location}
  222. orgSlug={organization.slug}
  223. type={'spans'}
  224. skipLight
  225. >
  226. {results => {
  227. return (
  228. <QuickTraceContext.Provider value={results}>
  229. <AnrRootCause event={event} organization={organization} />
  230. </QuickTraceContext.Provider>
  231. );
  232. }}
  233. </QuickTraceQuery>
  234. )}
  235. {group.issueCategory === IssueCategory.PERFORMANCE && (
  236. <SpanEvidenceSection
  237. event={event as EventTransaction}
  238. organization={organization}
  239. projectSlug={project.slug}
  240. />
  241. )}
  242. <EventHydrationDiff event={event} group={group} />
  243. <EventReplay event={event} group={group} projectSlug={project.slug} />
  244. <GroupEventEntry entryType={EntryType.HPKP} {...eventEntryProps} />
  245. <GroupEventEntry entryType={EntryType.CSP} {...eventEntryProps} />
  246. <GroupEventEntry entryType={EntryType.EXPECTCT} {...eventEntryProps} />
  247. <GroupEventEntry entryType={EntryType.EXPECTSTAPLE} {...eventEntryProps} />
  248. <GroupEventEntry entryType={EntryType.TEMPLATE} {...eventEntryProps} />
  249. {hasNewTimelineUI ? (
  250. <BreadcrumbsDataSection event={event} group={group} project={project} />
  251. ) : (
  252. <GroupEventEntry entryType={EntryType.BREADCRUMBS} {...eventEntryProps} />
  253. )}
  254. {!showPossibleSolutionsHigher && (
  255. <ResourcesAndPossibleSolutionsIssueDetailsContent
  256. event={event}
  257. project={project}
  258. group={group}
  259. />
  260. )}
  261. <GroupEventEntry entryType={EntryType.DEBUGMETA} {...eventEntryProps} />
  262. <GroupEventEntry entryType={EntryType.REQUEST} {...eventEntryProps} />
  263. <div ref={tagsRef}>
  264. <EventTagsAndScreenshot event={event} projectSlug={project.slug} />
  265. </div>
  266. <EventContexts group={group} event={event} />
  267. <EventExtraData event={event} />
  268. <EventPackageData event={event} />
  269. <EventDevice event={event} />
  270. <EventViewHierarchy event={event} project={project} />
  271. <EventAttachments event={event} projectSlug={project.slug} />
  272. <EventSdk sdk={event.sdk} meta={event._meta?.sdk} />
  273. {event.groupID && (
  274. <EventGroupingInfo
  275. projectSlug={project.slug}
  276. event={event}
  277. showGroupingConfig={
  278. organization.features.includes('set-grouping-config') &&
  279. 'groupingConfig' in event
  280. }
  281. group={group}
  282. />
  283. )}
  284. {!hasReplay && (
  285. <EventRRWebIntegration
  286. event={event}
  287. orgId={organization.slug}
  288. projectSlug={project.slug}
  289. />
  290. )}
  291. </Fragment>
  292. );
  293. }
  294. function ResourcesAndPossibleSolutionsIssueDetailsContent({
  295. event,
  296. project,
  297. group,
  298. }: Required<GroupEventDetailsContentProps>) {
  299. return (
  300. <ErrorBoundary mini>
  301. <ResourcesAndPossibleSolutions event={event} project={project} group={group} />
  302. </ErrorBoundary>
  303. );
  304. }
  305. function PerformanceDurationRegressionIssueDetailsContent({
  306. group,
  307. event,
  308. project,
  309. }: Required<GroupEventDetailsContentProps>) {
  310. return (
  311. <Fragment>
  312. <ErrorBoundary mini>
  313. <EventRegressionSummary event={event} group={group} />
  314. </ErrorBoundary>
  315. <ErrorBoundary mini>
  316. <EventBreakpointChart event={event} />
  317. </ErrorBoundary>
  318. <ErrorBoundary mini>
  319. <AggregateSpanDiff event={event} project={project} />
  320. </ErrorBoundary>
  321. <ErrorBoundary mini>
  322. <EventComparison event={event} project={project} />
  323. </ErrorBoundary>
  324. </Fragment>
  325. );
  326. }
  327. function ProfilingDurationRegressionIssueDetailsContent({
  328. group,
  329. event,
  330. project,
  331. }: Required<GroupEventDetailsContentProps>) {
  332. return (
  333. <TransactionsDeltaProvider event={event} project={project}>
  334. <Fragment>
  335. <ErrorBoundary mini>
  336. <EventRegressionSummary event={event} group={group} />
  337. </ErrorBoundary>
  338. <ErrorBoundary mini>
  339. <EventFunctionBreakpointChart event={event} />
  340. </ErrorBoundary>
  341. <ErrorBoundary mini>
  342. <EventAffectedTransactions event={event} group={group} project={project} />
  343. </ErrorBoundary>
  344. <ErrorBoundary mini>
  345. <DataSection>
  346. <b>{t('Largest Changes in Call Stack Frequency')}</b>
  347. <p>
  348. {t(`See which functions changed the most before and after the regression. The
  349. frame with the largest increase in call stack population likely
  350. contributed to the cause for the duration regression.`)}
  351. </p>
  352. <EventDifferentialFlamegraph event={event} />
  353. </DataSection>
  354. </ErrorBoundary>
  355. <ErrorBoundary mini>
  356. <EventFunctionComparisonList event={event} group={group} project={project} />
  357. </ErrorBoundary>
  358. </Fragment>
  359. </TransactionsDeltaProvider>
  360. );
  361. }
  362. function GroupEventDetailsContent({
  363. group,
  364. event,
  365. project,
  366. }: GroupEventDetailsContentProps) {
  367. if (!event) {
  368. return (
  369. <NotFoundMessage>
  370. <h3>{t('Latest event not available')}</h3>
  371. </NotFoundMessage>
  372. );
  373. }
  374. switch (group.issueType) {
  375. case IssueType.PERFORMANCE_DURATION_REGRESSION:
  376. case IssueType.PERFORMANCE_ENDPOINT_REGRESSION: {
  377. return (
  378. <PerformanceDurationRegressionIssueDetailsContent
  379. group={group}
  380. event={event}
  381. project={project}
  382. />
  383. );
  384. }
  385. case IssueType.PROFILE_FUNCTION_REGRESSION_EXPERIMENTAL:
  386. case IssueType.PROFILE_FUNCTION_REGRESSION: {
  387. return (
  388. <ProfilingDurationRegressionIssueDetailsContent
  389. group={group}
  390. event={event}
  391. project={project}
  392. />
  393. );
  394. }
  395. default: {
  396. return (
  397. <DefaultGroupEventDetailsContent group={group} event={event} project={project} />
  398. );
  399. }
  400. }
  401. }
  402. const NotFoundMessage = styled('div')`
  403. padding: ${space(2)} ${space(4)};
  404. `;
  405. const StyledDataSection = styled(DataSection)`
  406. padding: ${space(0.5)} ${space(2)};
  407. @media (min-width: ${p => p.theme.breakpoints.medium}) {
  408. padding: ${space(1)} ${space(4)};
  409. }
  410. &:empty {
  411. display: none;
  412. }
  413. `;
  414. export default GroupEventDetailsContent;