traceHeader.tsx 5.4 KB

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