index.tsx 12 KB

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