useTraceOnLoad.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import {useLayoutEffect, useRef, useState} from 'react';
  2. import * as Sentry from '@sentry/react';
  3. import type {Client} from 'sentry/api';
  4. import type {Event} from 'sentry/types/event';
  5. import type {Organization} from 'sentry/types/organization';
  6. import useApi from 'sentry/utils/useApi';
  7. import useOrganization from 'sentry/utils/useOrganization';
  8. import {IssuesTraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/issuesTraceTree';
  9. import {TraceTree} from './traceModels/traceTree';
  10. import type {TracePreferencesState} from './traceState/tracePreferences';
  11. import {useTraceState} from './traceState/traceStateProvider';
  12. import {isTransactionNode} from './traceGuards';
  13. import type {TraceReducerState} from './traceState';
  14. import type {useTraceScrollToPath} from './useTraceScrollToPath';
  15. // If a trace has less than 3 transactions, we automatically expand all transactions.
  16. // We do this as the tree is otherwise likely to be very small and not very useful.
  17. const AUTO_EXPAND_TRANSACTION_THRESHOLD = 3;
  18. async function maybeAutoExpandTrace(
  19. tree: TraceTree,
  20. options: {
  21. api: Client;
  22. organization: Organization;
  23. preferences: Pick<TracePreferencesState, 'autogroup' | 'missing_instrumentation'>;
  24. }
  25. ): Promise<TraceTree> {
  26. const transactions = TraceTree.FindAll(tree.root, node => isTransactionNode(node));
  27. if (transactions.length >= AUTO_EXPAND_TRANSACTION_THRESHOLD) {
  28. return tree;
  29. }
  30. const promises: Promise<any>[] = [];
  31. for (const transaction of transactions) {
  32. promises.push(tree.zoom(transaction, true, options));
  33. }
  34. await Promise.allSettled(promises).catch(_e => {
  35. Sentry.withScope(scope => {
  36. scope.setFingerprint(['trace-auto-expand']);
  37. Sentry.captureMessage('Failed to auto expand trace with low transaction count');
  38. });
  39. });
  40. return tree;
  41. }
  42. type UseTraceScrollToEventOnLoadOptions = {
  43. onTraceLoad: () => void;
  44. pathToNodeOrEventId: ReturnType<typeof useTraceScrollToPath>['current'];
  45. tree: TraceTree;
  46. };
  47. export function useTraceOnLoad(
  48. options: UseTraceScrollToEventOnLoadOptions
  49. ): 'success' | 'error' | 'pending' | 'idle' {
  50. const api = useApi();
  51. const organization = useOrganization();
  52. const initializedRef = useRef<boolean>(false);
  53. const {tree, pathToNodeOrEventId, onTraceLoad} = options;
  54. const [status, setStatus] = useState<'success' | 'error' | 'pending' | 'idle'>('idle');
  55. const traceState = useTraceState();
  56. const traceStateRef = useRef<TraceReducerState>(traceState);
  57. traceStateRef.current = traceState;
  58. const traceStatePreferencesRef = useRef<
  59. Pick<TraceReducerState['preferences'], 'autogroup' | 'missing_instrumentation'>
  60. >(traceState.preferences);
  61. traceStatePreferencesRef.current = traceState.preferences;
  62. useLayoutEffect(() => {
  63. if (initializedRef.current) {
  64. return undefined;
  65. }
  66. if (tree.type !== 'trace') {
  67. return undefined;
  68. }
  69. let cancel = false;
  70. setStatus('pending');
  71. initializedRef.current = true;
  72. const expandOptions = {
  73. api,
  74. organization,
  75. preferences: traceStatePreferencesRef.current,
  76. };
  77. // If eligible, auto-expand the trace
  78. maybeAutoExpandTrace(tree, expandOptions)
  79. .then(() => {
  80. if (cancel) {
  81. return Promise.resolve();
  82. }
  83. // Node path has higher specificity than eventId
  84. const {path, eventId} = pathToNodeOrEventId || {};
  85. if (path) {
  86. return TraceTree.ExpandToPath(tree, path, expandOptions);
  87. }
  88. if (eventId) {
  89. return TraceTree.ExpandToEventID(tree, eventId, expandOptions);
  90. }
  91. return Promise.resolve();
  92. })
  93. .then(() => {
  94. if (cancel) {
  95. return;
  96. }
  97. setStatus('success');
  98. onTraceLoad();
  99. })
  100. .catch(() => {
  101. if (cancel) {
  102. return;
  103. }
  104. setStatus('error');
  105. });
  106. return () => {
  107. cancel = true;
  108. };
  109. }, [tree, api, onTraceLoad, organization, pathToNodeOrEventId]);
  110. return status;
  111. }
  112. type UseTraceIssuesOnLoadOptions = {
  113. event: Event;
  114. onTraceLoad: () => void;
  115. tree: IssuesTraceTree;
  116. };
  117. export function useTraceIssuesOnLoad(
  118. options: UseTraceIssuesOnLoadOptions
  119. ): 'success' | 'error' | 'pending' | 'idle' {
  120. const api = useApi();
  121. const organization = useOrganization();
  122. const initializedRef = useRef<boolean>(false);
  123. const {tree, onTraceLoad} = options;
  124. const [status, setStatus] = useState<'success' | 'error' | 'pending' | 'idle'>('idle');
  125. const traceState = useTraceState();
  126. const traceStateRef = useRef<TraceReducerState>(traceState);
  127. traceStateRef.current = traceState;
  128. const traceStatePreferencesRef = useRef<
  129. Pick<TraceReducerState['preferences'], 'autogroup' | 'missing_instrumentation'>
  130. >(traceState.preferences);
  131. traceStatePreferencesRef.current = traceState.preferences;
  132. useLayoutEffect(() => {
  133. if (initializedRef.current) {
  134. return undefined;
  135. }
  136. if (tree.type !== 'trace') {
  137. return undefined;
  138. }
  139. let cancel = false;
  140. setStatus('pending');
  141. initializedRef.current = true;
  142. const expandOptions = {
  143. api,
  144. organization,
  145. preferences: traceStatePreferencesRef.current,
  146. };
  147. const promise = options.event
  148. ? IssuesTraceTree.ExpandToEvent(tree, options.event.eventID, expandOptions)
  149. : Promise.resolve();
  150. promise
  151. .then(() => {
  152. if (cancel) {
  153. return;
  154. }
  155. setStatus('success');
  156. onTraceLoad();
  157. })
  158. .catch(() => {
  159. if (cancel) {
  160. return;
  161. }
  162. setStatus('error');
  163. });
  164. return () => {
  165. cancel = true;
  166. };
  167. }, [tree, api, onTraceLoad, organization, options.event]);
  168. return status;
  169. }