generalInfo.tsx 6.1 KB

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