traceHeader.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  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={metaLoading ? loadingIndicator : meta?.transactions ?? '\u2014'}
  98. subtext={null}
  99. />
  100. </GuideAnchor>
  101. <MetaData
  102. headingText={t('Issues')}
  103. tooltipText=""
  104. bodyText={
  105. <Tooltip
  106. title={
  107. errors + performanceIssues > 0 ? (
  108. <Fragment>
  109. <div>{tn('%s error issue', '%s error issues', errors)}</div>
  110. <div>
  111. {tn(
  112. '%s performance issue',
  113. '%s performance issues',
  114. performanceIssues
  115. )}
  116. </div>
  117. </Fragment>
  118. ) : null
  119. }
  120. showUnderline
  121. position="bottom"
  122. >
  123. {metaLoading
  124. ? loadingIndicator
  125. : errors || performanceIssues
  126. ? errors + performanceIssues
  127. : 0}
  128. </Tooltip>
  129. }
  130. subtext={null}
  131. />
  132. <MetaData
  133. headingText={t('Total Duration')}
  134. tooltipText=""
  135. bodyText={
  136. emptyTrace
  137. ? getDuration(0, 2, true)
  138. : traceInfo.startTimestamp && traceInfo.endTimestamp
  139. ? getDuration(traceInfo.endTimestamp - traceInfo.startTimestamp, 2, true)
  140. : loadingIndicator
  141. }
  142. subtext={null}
  143. />
  144. </TraceHeaderRow>
  145. </TraceHeaderContainer>
  146. );
  147. }
  148. const FlexBox = styled('div')`
  149. display: flex;
  150. align-items: center;
  151. `;
  152. const TraceHeaderContainer = styled(FlexBox)`
  153. justify-content: space-between;
  154. `;
  155. const TraceHeaderRow = styled(FlexBox)`
  156. gap: ${space(2)};
  157. `;
  158. const ReplayLinkBody = styled(FlexBox)`
  159. gap: ${space(0.25)};
  160. `;