generalInfo.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import {Fragment, useMemo} from 'react';
  2. import LoadingIndicator from 'sentry/components/loadingIndicator';
  3. import {useReplayContext} from 'sentry/components/replays/replayContext';
  4. import {Tooltip} from 'sentry/components/tooltip';
  5. import {t, tn} from 'sentry/locale';
  6. import type {EventTransaction} from 'sentry/types/event';
  7. import type {Organization} from 'sentry/types/organization';
  8. import getDuration from 'sentry/utils/duration/getDuration';
  9. import type {
  10. TraceErrorOrIssue,
  11. TraceFullDetailed,
  12. TraceSplitResults,
  13. } from 'sentry/utils/performance/quickTrace/types';
  14. import type {UseApiQueryResult} from 'sentry/utils/queryClient';
  15. import type RequestError from 'sentry/utils/requestError/requestError';
  16. import {useParams} from 'sentry/utils/useParams';
  17. import {SpanTimeRenderer} from 'sentry/views/traces/fieldRenderers';
  18. import {isTraceNode} from '../../../guards';
  19. import type {TraceMetaQueryResults} from '../../../traceApi/useTraceMeta';
  20. import type {TraceTree, TraceTreeNode} from '../../../traceModels/traceTree';
  21. import {type SectionCardKeyValueList, TraceDrawerComponents} from '../../details/styles';
  22. type GeneralInfoProps = {
  23. metaResults: TraceMetaQueryResults;
  24. node: TraceTreeNode<TraceTree.NodeValue>;
  25. organization: Organization;
  26. rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
  27. traces: TraceSplitResults<TraceFullDetailed> | null;
  28. tree: TraceTree;
  29. };
  30. export function GeneralInfo(props: GeneralInfoProps) {
  31. const params = useParams<{traceSlug?: string}>();
  32. const {replay} = useReplayContext();
  33. const traceNode = props.tree.root.children[0];
  34. const uniqueErrorIssues = useMemo(() => {
  35. if (!traceNode) {
  36. return [];
  37. }
  38. const unique: TraceErrorOrIssue[] = [];
  39. const seenIssues: Set<number> = new Set();
  40. for (const issue of traceNode.errors) {
  41. if (seenIssues.has(issue.issue_id)) {
  42. continue;
  43. }
  44. seenIssues.add(issue.issue_id);
  45. unique.push(issue);
  46. }
  47. return unique;
  48. }, [traceNode]);
  49. const uniquePerformanceIssues = useMemo(() => {
  50. if (!traceNode) {
  51. return [];
  52. }
  53. const unique: TraceErrorOrIssue[] = [];
  54. const seenIssues: Set<number> = new Set();
  55. for (const issue of traceNode.performance_issues) {
  56. if (seenIssues.has(issue.issue_id)) {
  57. continue;
  58. }
  59. seenIssues.add(issue.issue_id);
  60. unique.push(issue);
  61. }
  62. return unique;
  63. }, [traceNode]);
  64. const uniqueIssuesCount = uniqueErrorIssues.length + uniquePerformanceIssues.length;
  65. const traceSlug = useMemo(() => {
  66. return params.traceSlug?.trim() ?? '';
  67. }, [params.traceSlug]);
  68. const isLoading = useMemo(() => {
  69. return (
  70. props.metaResults.isLoading ||
  71. (props.rootEventResults.isPending && props.rootEventResults.fetchStatus !== 'idle')
  72. );
  73. }, [
  74. props.metaResults.isLoading,
  75. props.rootEventResults.isPending,
  76. props.rootEventResults.fetchStatus,
  77. ]);
  78. if (isLoading) {
  79. return (
  80. <TraceDrawerComponents.SectionCard
  81. items={[
  82. {
  83. key: 'trace_general_loading',
  84. subject: t('Loading...'),
  85. subjectNode: null,
  86. value: <LoadingIndicator size={30} />,
  87. },
  88. ]}
  89. title={t('General')}
  90. />
  91. );
  92. }
  93. if (!(traceNode && isTraceNode(traceNode))) {
  94. throw new Error('Expected a trace node');
  95. }
  96. if (
  97. props.traces?.transactions.length === 0 &&
  98. props.traces.orphan_errors.length === 0
  99. ) {
  100. return null;
  101. }
  102. const browser = props.rootEventResults?.data?.contexts?.browser;
  103. const items: SectionCardKeyValueList = [];
  104. // Hide trace_id inside replays because a replay could be connected to multiple traces.
  105. if (!replay) {
  106. items.push({
  107. key: 'trace_id',
  108. subject: t('Trace ID'),
  109. value: <TraceDrawerComponents.CopyableCardValueWithLink value={traceSlug} />,
  110. });
  111. }
  112. items.push(
  113. {
  114. key: 'events',
  115. subject: t('Events'),
  116. value: props.metaResults.data
  117. ? props.metaResults.data.transactions + props.metaResults.data.errors
  118. : '\u2014',
  119. },
  120. {
  121. key: 'issues',
  122. subject: t('Issues'),
  123. value: (
  124. <Tooltip
  125. title={
  126. uniqueIssuesCount > 0 ? (
  127. <Fragment>
  128. <div>
  129. {tn('%s error issue', '%s error issues', uniqueErrorIssues.length)}
  130. </div>
  131. <div>
  132. {tn(
  133. '%s performance issue',
  134. '%s performance issues',
  135. uniquePerformanceIssues.length
  136. )}
  137. </div>
  138. </Fragment>
  139. ) : null
  140. }
  141. showUnderline
  142. position="bottom"
  143. >
  144. {uniqueIssuesCount > 0 ? (
  145. <TraceDrawerComponents.IssuesLink node={props.node}>
  146. {uniqueIssuesCount}
  147. </TraceDrawerComponents.IssuesLink>
  148. ) : uniqueIssuesCount === 0 ? (
  149. 0
  150. ) : (
  151. '\u2014'
  152. )}
  153. </Tooltip>
  154. ),
  155. },
  156. {
  157. key: 'start_timestamp',
  158. subject: t('Start Timestamp'),
  159. value: traceNode.space?.[1] ? (
  160. <SpanTimeRenderer timestamp={traceNode.space?.[0]} tooltipShowSeconds />
  161. ) : (
  162. '\u2014'
  163. ),
  164. },
  165. {
  166. key: 'total_duration',
  167. subject: t('Total Duration'),
  168. value: traceNode.space?.[1]
  169. ? getDuration(traceNode.space[1] / 1000, 2, true)
  170. : '\u2014',
  171. },
  172. {
  173. key: 'user',
  174. subject: t('User'),
  175. value:
  176. props.rootEventResults?.data?.user?.email ??
  177. props.rootEventResults?.data?.user?.name ??
  178. '\u2014',
  179. },
  180. {
  181. key: 'browser',
  182. subject: t('Browser'),
  183. value: browser ? browser.name + ' ' + browser.version : '\u2014',
  184. }
  185. );
  186. return (
  187. <TraceDrawerComponents.SectionCard
  188. items={items}
  189. title={t('General')}
  190. disableTruncate
  191. />
  192. );
  193. }