traceHeader.tsx 5.8 KB

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