traceHeader.tsx 6.8 KB

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