traceHeader.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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. type TraceHeaderProps = {
  26. metaResults: UseApiQueryResult<TraceMeta | null, any>;
  27. organization: Organization;
  28. rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
  29. traces: TraceSplitResults<TraceFullDetailed> | null;
  30. tree: TraceTree;
  31. };
  32. export default function TraceHeader({
  33. metaResults,
  34. rootEventResults,
  35. traces,
  36. organization,
  37. tree,
  38. }: TraceHeaderProps) {
  39. const traceNode = tree.root.children[0];
  40. if (!(traceNode && isTraceNode(traceNode))) {
  41. throw new Error('Expected a trace node');
  42. }
  43. const errors = traceNode.errors.length || metaResults.data?.errors || 0;
  44. const performanceIssues =
  45. traceNode.performance_issues.length || metaResults.data?.performance_issues || 0;
  46. const errorsAndIssuesCount = errors + performanceIssues;
  47. const replay_id = rootEventResults?.data?.contexts.replay?.replay_id;
  48. const isEmptyTrace =
  49. traces?.transactions &&
  50. traces?.transactions.length === 0 &&
  51. traces?.orphan_errors &&
  52. traces.orphan_errors.length === 0;
  53. const showLoadingIndicator =
  54. (rootEventResults.isLoading && rootEventResults.fetchStatus !== 'idle') ||
  55. metaResults.isLoading;
  56. return (
  57. <TraceHeaderContainer>
  58. <TraceHeaderRow>
  59. <MetaData
  60. headingText={t('User')}
  61. tooltipText=""
  62. bodyText={
  63. showLoadingIndicator ? (
  64. <LoadingIndicator size={20} mini />
  65. ) : (
  66. rootEventResults?.data?.user?.email ??
  67. rootEventResults?.data?.user?.name ??
  68. '\u2014'
  69. )
  70. }
  71. subtext={null}
  72. />
  73. <MetaData
  74. headingText={t('Browser')}
  75. tooltipText=""
  76. bodyText={
  77. showLoadingIndicator ? (
  78. <LoadingIndicator size={20} mini />
  79. ) : rootEventResults?.data ? (
  80. <BrowserDisplay event={rootEventResults?.data} showVersion />
  81. ) : (
  82. '\u2014'
  83. )
  84. }
  85. subtext={null}
  86. />
  87. {replay_id && (
  88. <MetaData
  89. headingText={t('Replay')}
  90. tooltipText=""
  91. bodyText={
  92. <Link
  93. to={normalizeUrl(
  94. `/organizations/${organization.slug}/replays/${replay_id}/`
  95. )}
  96. >
  97. <ReplayLinkBody>
  98. {getShortEventId(replay_id)}
  99. <IconPlay size="xs" />
  100. </ReplayLinkBody>
  101. </Link>
  102. }
  103. subtext={null}
  104. />
  105. )}
  106. </TraceHeaderRow>
  107. <TraceHeaderRow>
  108. <GuideAnchor target="trace_view_guide_breakdown">
  109. <MetaData
  110. headingText={t('Events')}
  111. tooltipText=""
  112. bodyText={
  113. metaResults.isLoading ? (
  114. <LoadingIndicator size={20} mini />
  115. ) : metaResults.data ? (
  116. metaResults.data.transactions + metaResults.data.errors
  117. ) : (
  118. '\u2014'
  119. )
  120. }
  121. subtext={null}
  122. />
  123. </GuideAnchor>
  124. <MetaData
  125. headingText={t('Issues')}
  126. tooltipText=""
  127. bodyText={
  128. <Tooltip
  129. title={
  130. errorsAndIssuesCount > 0 ? (
  131. <Fragment>
  132. <div>{tn('%s error issue', '%s error issues', errors)}</div>
  133. <div>
  134. {tn(
  135. '%s performance issue',
  136. '%s performance issues',
  137. performanceIssues
  138. )}
  139. </div>
  140. </Fragment>
  141. ) : null
  142. }
  143. showUnderline
  144. position="bottom"
  145. >
  146. {metaResults.isLoading ? (
  147. <LoadingIndicator size={20} mini />
  148. ) : errorsAndIssuesCount >= 0 ? (
  149. errorsAndIssuesCount
  150. ) : (
  151. '\u2014'
  152. )}
  153. </Tooltip>
  154. }
  155. subtext={null}
  156. />
  157. <MetaData
  158. headingText={t('Total Duration')}
  159. tooltipText=""
  160. bodyText={
  161. isEmptyTrace ? (
  162. getDuration(0, 2, true)
  163. ) : metaResults.isLoading ? (
  164. <LoadingIndicator size={20} mini />
  165. ) : traceNode.space?.[1] ? (
  166. getDuration(traceNode.space[1] / 1000, 2, true)
  167. ) : (
  168. '\u2014'
  169. )
  170. }
  171. subtext={null}
  172. />
  173. </TraceHeaderRow>
  174. </TraceHeaderContainer>
  175. );
  176. }
  177. const FlexBox = styled('div')`
  178. display: flex;
  179. align-items: center;
  180. `;
  181. const TraceHeaderContainer = styled(FlexBox)`
  182. justify-content: space-between;
  183. `;
  184. const TraceHeaderRow = styled(FlexBox)`
  185. gap: ${space(2)};
  186. `;
  187. const ReplayLinkBody = styled(FlexBox)`
  188. gap: ${space(0.25)};
  189. `;