traceHeader.tsx 5.5 KB

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