traceHeader.tsx 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import GuideAnchor from 'sentry/components/assistant/guideAnchor';
  4. import {CopyToClipboardButton} from 'sentry/components/copyToClipboardButton';
  5. import Link from 'sentry/components/links/link';
  6. import LoadingIndicator from 'sentry/components/loadingIndicator';
  7. import {Tooltip} from 'sentry/components/tooltip';
  8. import {IconPlay} from 'sentry/icons';
  9. import {t, tn} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import type {EventTransaction, Organization} from 'sentry/types';
  12. import {getShortEventId} from 'sentry/utils/events';
  13. import {getDuration} from 'sentry/utils/formatters';
  14. import type {
  15. TraceFullDetailed,
  16. TraceMeta,
  17. TraceSplitResults,
  18. } from 'sentry/utils/performance/quickTrace/types';
  19. import type {UseApiQueryResult} from 'sentry/utils/queryClient';
  20. import type RequestError from 'sentry/utils/requestError/requestError';
  21. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  22. import {BrowserDisplay} from '../transactionDetails/eventMetas';
  23. import {MetaData} from '../transactionDetails/styles';
  24. import {TraceDrawerComponents} from './traceDrawer/details/styles';
  25. import type {TraceTree} from './traceModels/traceTree';
  26. import {isTraceNode} from './guards';
  27. function TraceHeaderEmptyTrace() {
  28. return (
  29. <TraceHeaderContainer>
  30. <TraceHeaderRow textAlign="left">
  31. <MetaData
  32. headingText={t('User')}
  33. tooltipText=""
  34. bodyText={'\u2014'}
  35. subtext={null}
  36. />
  37. <MetaData
  38. headingText={t('Browser')}
  39. tooltipText=""
  40. bodyText={'\u2014'}
  41. subtext={null}
  42. />
  43. </TraceHeaderRow>
  44. <TraceHeaderRow textAlign="right">
  45. <GuideAnchor target="trace_view_guide_breakdown">
  46. <MetaData
  47. headingText={t('Events')}
  48. tooltipText=""
  49. bodyText={'\u2014'}
  50. subtext={null}
  51. />
  52. </GuideAnchor>
  53. <MetaData
  54. headingText={t('Issues')}
  55. tooltipText=""
  56. bodyText={'\u2014'}
  57. subtext={null}
  58. />
  59. <MetaData
  60. headingText={t('Total Duration')}
  61. tooltipText=""
  62. bodyText={'\u2014'}
  63. subtext={null}
  64. />
  65. </TraceHeaderRow>
  66. </TraceHeaderContainer>
  67. );
  68. }
  69. type TraceHeaderProps = {
  70. metaResults: UseApiQueryResult<TraceMeta | null, any>;
  71. organization: Organization;
  72. rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
  73. traceID: string | undefined;
  74. traces: TraceSplitResults<TraceFullDetailed> | null;
  75. tree: TraceTree;
  76. };
  77. export function TraceHeader({
  78. metaResults,
  79. rootEventResults,
  80. traces,
  81. organization,
  82. tree,
  83. traceID,
  84. }: TraceHeaderProps) {
  85. if (traces?.transactions.length === 0 && traces.orphan_errors.length === 0) {
  86. return <TraceHeaderEmptyTrace />;
  87. }
  88. const traceNode = tree.root.children[0];
  89. if (!(traceNode && isTraceNode(traceNode))) {
  90. throw new Error('Expected a trace node');
  91. }
  92. const errors = traceNode.errors.size || metaResults.data?.errors || 0;
  93. const performanceIssues =
  94. traceNode.performance_issues.size || metaResults.data?.performance_issues || 0;
  95. const errorsAndIssuesCount = errors + performanceIssues;
  96. const replay_id = rootEventResults?.data?.contexts?.replay?.replay_id;
  97. const showLoadingIndicator =
  98. (rootEventResults.isLoading && rootEventResults.fetchStatus !== 'idle') ||
  99. metaResults.isLoading;
  100. return (
  101. <TraceHeaderContainer>
  102. <TraceHeaderRow textAlign="left">
  103. <MetaData
  104. headingText={t('User')}
  105. tooltipText=""
  106. bodyText={
  107. showLoadingIndicator ? (
  108. <LoadingIndicator size={20} mini />
  109. ) : (
  110. rootEventResults?.data?.user?.email ??
  111. rootEventResults?.data?.user?.name ??
  112. '\u2014'
  113. )
  114. }
  115. subtext={null}
  116. />
  117. <MetaData
  118. headingText={t('Browser')}
  119. tooltipText=""
  120. bodyText={
  121. showLoadingIndicator ? (
  122. <LoadingIndicator size={20} mini />
  123. ) : rootEventResults?.data ? (
  124. <BrowserDisplay event={rootEventResults?.data} showVersion />
  125. ) : (
  126. '\u2014'
  127. )
  128. }
  129. subtext={null}
  130. />
  131. <MetaData
  132. headingText={t('Trace')}
  133. tooltipText=""
  134. bodyText={
  135. showLoadingIndicator ? (
  136. <LoadingIndicator size={20} mini />
  137. ) : traceID ? (
  138. <Fragment>
  139. {getShortEventId(traceID)}
  140. <CopyToClipboardButton
  141. borderless
  142. size="zero"
  143. iconSize="xs"
  144. text={traceID}
  145. />
  146. </Fragment>
  147. ) : (
  148. '\u2014'
  149. )
  150. }
  151. subtext={null}
  152. />
  153. {replay_id && (
  154. <MetaData
  155. headingText={t('Replay')}
  156. tooltipText=""
  157. bodyText={
  158. <Link
  159. to={normalizeUrl(
  160. `/organizations/${organization.slug}/replays/${replay_id}/`
  161. )}
  162. >
  163. <ReplayLinkBody>
  164. {getShortEventId(replay_id)}
  165. <IconPlay size="xs" />
  166. </ReplayLinkBody>
  167. </Link>
  168. }
  169. subtext={null}
  170. />
  171. )}
  172. </TraceHeaderRow>
  173. <TraceHeaderRow textAlign="right">
  174. <GuideAnchor target="trace_view_guide_breakdown">
  175. <MetaData
  176. headingText={t('Events')}
  177. tooltipText=""
  178. bodyText={
  179. metaResults.isLoading ? (
  180. <LoadingIndicator size={20} mini />
  181. ) : metaResults.data ? (
  182. metaResults.data.transactions + metaResults.data.errors
  183. ) : (
  184. '\u2014'
  185. )
  186. }
  187. subtext={null}
  188. />
  189. </GuideAnchor>
  190. <MetaData
  191. headingText={t('Issues')}
  192. tooltipText=""
  193. bodyText={
  194. <Tooltip
  195. title={
  196. errorsAndIssuesCount > 0 ? (
  197. <Fragment>
  198. <div>{tn('%s error issue', '%s error issues', errors)}</div>
  199. <div>
  200. {tn(
  201. '%s performance issue',
  202. '%s performance issues',
  203. performanceIssues
  204. )}
  205. </div>
  206. </Fragment>
  207. ) : null
  208. }
  209. showUnderline
  210. position="bottom"
  211. >
  212. {metaResults.isLoading ? (
  213. <LoadingIndicator size={20} mini />
  214. ) : errorsAndIssuesCount > 0 ? (
  215. <TraceDrawerComponents.IssuesLink>
  216. {errorsAndIssuesCount}
  217. </TraceDrawerComponents.IssuesLink>
  218. ) : (
  219. '\u2014'
  220. )}
  221. </Tooltip>
  222. }
  223. subtext={null}
  224. />
  225. <MetaData
  226. headingText={t('Total Duration')}
  227. tooltipText=""
  228. bodyText={
  229. metaResults.isLoading ? (
  230. <LoadingIndicator size={20} mini />
  231. ) : traceNode.space?.[1] ? (
  232. getDuration(traceNode.space[1] / 1000, 2, true)
  233. ) : (
  234. '\u2014'
  235. )
  236. }
  237. subtext={null}
  238. />
  239. </TraceHeaderRow>
  240. </TraceHeaderContainer>
  241. );
  242. }
  243. const FlexBox = styled('div')`
  244. display: flex;
  245. align-items: center;
  246. `;
  247. const TraceHeaderContainer = styled(FlexBox)`
  248. justify-content: space-between;
  249. background-color: ${p => p.theme.background};
  250. padding: ${space(2)} ${space(2)} 0 ${space(2)};
  251. `;
  252. const TraceHeaderRow = styled(FlexBox)<{textAlign: 'left' | 'right'}>`
  253. gap: ${space(2)};
  254. text-align: ${p => p.textAlign};
  255. `;
  256. const ReplayLinkBody = styled(FlexBox)`
  257. gap: ${space(0.25)};
  258. `;