index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. import {Fragment, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import Alert from 'sentry/components/alert';
  4. import {EventContexts} from 'sentry/components/events/contexts';
  5. import {EventAttachments} from 'sentry/components/events/eventAttachments';
  6. import {EventEvidence} from 'sentry/components/events/eventEvidence';
  7. import {EventViewHierarchy} from 'sentry/components/events/eventViewHierarchy';
  8. import {EventRRWebIntegration} from 'sentry/components/events/rrwebIntegration';
  9. import ProjectBadge from 'sentry/components/idBadge/projectBadge';
  10. import type {LazyRenderProps} from 'sentry/components/lazyRender';
  11. import ExternalLink from 'sentry/components/links/externalLink';
  12. import LoadingError from 'sentry/components/loadingError';
  13. import LoadingIndicator from 'sentry/components/loadingIndicator';
  14. import {
  15. CustomMetricsEventData,
  16. eventHasCustomMetrics,
  17. } from 'sentry/components/metrics/customMetricsEventData';
  18. import {Tooltip} from 'sentry/components/tooltip';
  19. import {t, tct} from 'sentry/locale';
  20. import {space} from 'sentry/styles/space';
  21. import type {EventTransaction} from 'sentry/types/event';
  22. import type {Organization} from 'sentry/types/organization';
  23. import type {Project} from 'sentry/types/project';
  24. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  25. import {useLocation} from 'sentry/utils/useLocation';
  26. import useProjects from 'sentry/utils/useProjects';
  27. import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover';
  28. import type {
  29. SpanMetricsQueryFilters,
  30. SpanMetricsResponse,
  31. } from 'sentry/views/insights/types';
  32. import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
  33. import {Referrer} from '../../../referrers';
  34. import {traceAnalytics} from '../../../traceAnalytics';
  35. import {useTransaction} from '../../../traceApi/useTransaction';
  36. import {getCustomInstrumentationLink} from '../../../traceConfigurations';
  37. import {CacheMetrics} from '../../../traceDrawer/details/transaction/sections/cacheMetrics';
  38. import type {TraceTreeNodeDetailsProps} from '../../../traceDrawer/tabs/traceTreeNodeDetails';
  39. import type {TraceTree} from '../../../traceModels/traceTree';
  40. import type {TraceTreeNode} from '../../../traceModels/traceTreeNode';
  41. import {useHasTraceNewUi} from '../../../useHasTraceNewUi';
  42. import {IssueList} from '../issues/issues';
  43. import {TraceDrawerComponents} from '../styles';
  44. import {AdditionalData, hasAdditionalData} from './sections/additionalData';
  45. import {BreadCrumbs} from './sections/breadCrumbs';
  46. import {BuiltIn} from './sections/builtIn';
  47. import {Entries} from './sections/entries';
  48. import GeneralInfo from './sections/generalInfo';
  49. import {TransactionHighlights} from './sections/highlights';
  50. import {hasMeasurements, Measurements} from './sections/measurements';
  51. import ReplayPreview from './sections/replayPreview';
  52. import {Request} from './sections/request';
  53. import {hasSDKContext, Sdk} from './sections/sdk';
  54. export const LAZY_RENDER_PROPS: Partial<LazyRenderProps> = {
  55. observerOptions: {rootMargin: '50px'},
  56. };
  57. type TransactionNodeDetailHeaderProps = {
  58. event: EventTransaction;
  59. node: TraceTreeNode<TraceTree.Transaction>;
  60. onTabScrollToNode: (node: TraceTreeNode<any>) => void;
  61. organization: Organization;
  62. project: Project | undefined;
  63. };
  64. function TransactionNodeDetailHeader({
  65. node,
  66. organization,
  67. project,
  68. onTabScrollToNode,
  69. event,
  70. }: TransactionNodeDetailHeaderProps) {
  71. const hasNewTraceUi = useHasTraceNewUi();
  72. if (!hasNewTraceUi) {
  73. return (
  74. <LegacyTransactionNodeDetailHeader
  75. node={node}
  76. organization={organization}
  77. project={project}
  78. onTabScrollToNode={onTabScrollToNode}
  79. event={event}
  80. />
  81. );
  82. }
  83. return (
  84. <TraceDrawerComponents.HeaderContainer>
  85. <TraceDrawerComponents.Title>
  86. <TraceDrawerComponents.LegacyTitleText>
  87. <TraceDrawerComponents.TitleText>
  88. {t('Transaction')}
  89. </TraceDrawerComponents.TitleText>
  90. <TraceDrawerComponents.SubtitleWithCopyButton
  91. text={`ID: ${node.value.event_id}`}
  92. />
  93. </TraceDrawerComponents.LegacyTitleText>
  94. </TraceDrawerComponents.Title>
  95. <TraceDrawerComponents.NodeActions
  96. node={node}
  97. organization={organization}
  98. onTabScrollToNode={onTabScrollToNode}
  99. eventSize={event?.size}
  100. />
  101. </TraceDrawerComponents.HeaderContainer>
  102. );
  103. }
  104. function LegacyTransactionNodeDetailHeader({
  105. node,
  106. organization,
  107. project,
  108. onTabScrollToNode,
  109. event,
  110. }: TransactionNodeDetailHeaderProps) {
  111. return (
  112. <TraceDrawerComponents.LegacyHeaderContainer>
  113. <TraceDrawerComponents.Title>
  114. <Tooltip title={node.value.project_slug}>
  115. <ProjectBadge
  116. project={project ? project : {slug: node.value.project_slug}}
  117. avatarSize={30}
  118. hideName
  119. />
  120. </Tooltip>
  121. <TraceDrawerComponents.LegacyTitleText>
  122. <div>{t('transaction')}</div>
  123. <TraceDrawerComponents.TitleOp
  124. text={node.value['transaction.op'] + ' - ' + node.value.transaction}
  125. />
  126. </TraceDrawerComponents.LegacyTitleText>
  127. </TraceDrawerComponents.Title>
  128. <TraceDrawerComponents.NodeActions
  129. node={node}
  130. organization={organization}
  131. onTabScrollToNode={onTabScrollToNode}
  132. eventSize={event?.size}
  133. />
  134. </TraceDrawerComponents.LegacyHeaderContainer>
  135. );
  136. }
  137. export function TransactionNodeDetails({
  138. node,
  139. organization,
  140. onTabScrollToNode,
  141. onParentClick,
  142. replay,
  143. }: TraceTreeNodeDetailsProps<TraceTreeNode<TraceTree.Transaction>>) {
  144. const {projects} = useProjects();
  145. const issues = useMemo(() => {
  146. return [...node.errors, ...node.performance_issues];
  147. }, [node.errors, node.performance_issues]);
  148. const {
  149. data: event,
  150. isError,
  151. isPending,
  152. } = useTransaction({
  153. node,
  154. organization,
  155. });
  156. const hasNewTraceUi = useHasTraceNewUi();
  157. const {data: cacheMetrics} = useSpanMetrics(
  158. {
  159. search: MutableSearch.fromQueryObject({
  160. transaction: node.value.transaction,
  161. } satisfies SpanMetricsQueryFilters),
  162. fields: ['avg(cache.item_size)', 'cache_miss_rate()'],
  163. },
  164. Referrer.TRACE_DRAWER_TRANSACTION_CACHE_METRICS
  165. );
  166. if (isPending) {
  167. return <LoadingIndicator />;
  168. }
  169. if (isError) {
  170. return <LoadingError message={t('Failed to fetch transaction details')} />;
  171. }
  172. const project = projects.find(proj => proj.slug === event?.projectSlug);
  173. return (
  174. <TraceDrawerComponents.DetailContainer hasNewTraceUi={hasNewTraceUi}>
  175. {!node.canFetch ? (
  176. <StyledAlert type="info" showIcon>
  177. {tct(
  178. 'This transaction does not have any child spans. You can add more child spans via [customInstrumentationLink:custom instrumentation].',
  179. {
  180. customInstrumentationLink: (
  181. <ExternalLink
  182. onClick={() => {
  183. traceAnalytics.trackMissingSpansDocLinkClicked(organization);
  184. }}
  185. href={getCustomInstrumentationLink(project)}
  186. />
  187. ),
  188. }
  189. )}
  190. </StyledAlert>
  191. ) : null}
  192. <TransactionNodeDetailHeader
  193. node={node}
  194. organization={organization}
  195. project={project}
  196. event={event}
  197. onTabScrollToNode={onTabScrollToNode}
  198. />
  199. <IssueList node={node} organization={organization} issues={issues} />
  200. {hasNewTraceUi ? (
  201. <TransactionHighlights
  202. event={event}
  203. node={node}
  204. project={project}
  205. organization={organization}
  206. />
  207. ) : null}
  208. <TransactionSpecificSections
  209. event={event}
  210. node={node}
  211. onParentClick={onParentClick}
  212. organization={organization}
  213. cacheMetrics={cacheMetrics}
  214. />
  215. {event.projectSlug ? (
  216. <Entries
  217. definedEvent={event}
  218. projectSlug={event.projectSlug}
  219. group={undefined}
  220. organization={organization}
  221. />
  222. ) : null}
  223. <TraceDrawerComponents.EventTags
  224. projectSlug={node.value.project_slug}
  225. event={event}
  226. />
  227. <EventContexts event={event} />
  228. {project ? <EventEvidence event={event} project={project} /> : null}
  229. {replay ? null : <ReplayPreview event={event} organization={organization} />}
  230. <BreadCrumbs event={event} organization={organization} />
  231. {project ? (
  232. <EventAttachments event={event} project={project} group={undefined} />
  233. ) : null}
  234. {project ? <EventViewHierarchy event={event} project={project} /> : null}
  235. {event.projectSlug ? (
  236. <EventRRWebIntegration
  237. event={event}
  238. orgId={organization.slug}
  239. projectSlug={event.projectSlug}
  240. />
  241. ) : null}
  242. </TraceDrawerComponents.DetailContainer>
  243. );
  244. }
  245. type TransactionSpecificSectionsProps = {
  246. cacheMetrics: Pick<SpanMetricsResponse, 'avg(cache.item_size)' | 'cache_miss_rate()'>[];
  247. event: EventTransaction;
  248. node: TraceTreeNode<TraceTree.Transaction>;
  249. onParentClick: (node: TraceTreeNode<TraceTree.NodeValue>) => void;
  250. organization: Organization;
  251. };
  252. function TransactionSpecificSections(props: TransactionSpecificSectionsProps) {
  253. const location = useLocation();
  254. const hasNewTraceUi = useHasTraceNewUi();
  255. const {event, node, onParentClick, organization, cacheMetrics} = props;
  256. if (!hasNewTraceUi) {
  257. return <LegacyTransactionSpecificSections {...props} />;
  258. }
  259. return (
  260. <Fragment>
  261. <GeneralInfo
  262. node={node}
  263. onParentClick={onParentClick}
  264. organization={organization}
  265. event={event}
  266. location={location}
  267. cacheMetrics={cacheMetrics}
  268. />
  269. <InterimSection
  270. title={t('Transaction Specific')}
  271. type="transaction_specifc"
  272. initialCollapse
  273. >
  274. <TraceDrawerComponents.SectionCardGroup>
  275. {hasSDKContext(event) || cacheMetrics.length > 0 ? (
  276. <BuiltIn event={event} cacheMetrics={cacheMetrics} />
  277. ) : null}
  278. {hasAdditionalData(event) ? <AdditionalData event={event} /> : null}
  279. {hasMeasurements(event) ? (
  280. <Measurements event={event} location={location} organization={organization} />
  281. ) : null}
  282. {eventHasCustomMetrics(organization, event._metrics_summary) ? (
  283. <CustomMetricsEventData
  284. metricsSummary={event._metrics_summary}
  285. startTimestamp={event.startTimestamp}
  286. projectId={event.projectID}
  287. />
  288. ) : null}
  289. {event.contexts.trace?.data ? (
  290. <TraceDrawerComponents.TraceDataSection event={event} />
  291. ) : null}
  292. </TraceDrawerComponents.SectionCardGroup>
  293. <Request event={event} />
  294. </InterimSection>
  295. </Fragment>
  296. );
  297. }
  298. function LegacyTransactionSpecificSections({
  299. event,
  300. node,
  301. onParentClick,
  302. organization,
  303. cacheMetrics,
  304. }: TransactionSpecificSectionsProps) {
  305. const location = useLocation();
  306. return (
  307. <Fragment>
  308. <TraceDrawerComponents.SectionCardGroup>
  309. <GeneralInfo
  310. node={node}
  311. onParentClick={onParentClick}
  312. organization={organization}
  313. event={event}
  314. location={location}
  315. cacheMetrics={cacheMetrics}
  316. />
  317. {hasAdditionalData(event) ? <AdditionalData event={event} /> : null}
  318. {hasMeasurements(event) ? (
  319. <Measurements event={event} location={location} organization={organization} />
  320. ) : null}
  321. {cacheMetrics.length > 0 ? <CacheMetrics cacheMetrics={cacheMetrics} /> : null}
  322. {hasSDKContext(event) ? <Sdk event={event} /> : null}
  323. {eventHasCustomMetrics(organization, event._metrics_summary) ? (
  324. <CustomMetricsEventData
  325. metricsSummary={event._metrics_summary}
  326. startTimestamp={event.startTimestamp}
  327. projectId={event.projectID}
  328. />
  329. ) : null}
  330. {event.contexts.trace?.data ? (
  331. <TraceDrawerComponents.TraceDataSection event={event} />
  332. ) : null}
  333. </TraceDrawerComponents.SectionCardGroup>
  334. <Request event={event} />
  335. </Fragment>
  336. );
  337. }
  338. const StyledAlert = styled(Alert)`
  339. margin-bottom: ${space(2)};
  340. `;