index.tsx 12 KB

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