useTraceOnLoad.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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. // Node path has higher specificity than eventId. If neither are provided, we check if the
  78. // trace should be automatically expanded
  79. const promise = pathToNodeOrEventId?.path
  80. ? TraceTree.ExpandToPath(tree, pathToNodeOrEventId.path, expandOptions)
  81. : pathToNodeOrEventId?.eventId
  82. ? TraceTree.ExpandToEventID(tree, pathToNodeOrEventId.eventId, expandOptions)
  83. : maybeAutoExpandTrace(tree, expandOptions);
  84. promise
  85. .then(() => {
  86. if (cancel) {
  87. return;
  88. }
  89. setStatus('success');
  90. onTraceLoad();
  91. })
  92. .catch(() => {
  93. if (cancel) {
  94. return;
  95. }
  96. setStatus('error');
  97. });
  98. return () => {
  99. cancel = true;
  100. };
  101. }, [tree, api, onTraceLoad, organization, pathToNodeOrEventId]);
  102. return status;
  103. }
  104. type UseTraceIssuesOnLoadOptions = {
  105. event: Event;
  106. onTraceLoad: () => void;
  107. tree: IssuesTraceTree;
  108. };
  109. export function useTraceIssuesOnLoad(
  110. options: UseTraceIssuesOnLoadOptions
  111. ): 'success' | 'error' | 'pending' | 'idle' {
  112. const api = useApi();
  113. const organization = useOrganization();
  114. const initializedRef = useRef<boolean>(false);
  115. const {tree, onTraceLoad} = options;
  116. const [status, setStatus] = useState<'success' | 'error' | 'pending' | 'idle'>('idle');
  117. const traceState = useTraceState();
  118. const traceStateRef = useRef<TraceReducerState>(traceState);
  119. traceStateRef.current = traceState;
  120. const traceStatePreferencesRef = useRef<
  121. Pick<TraceReducerState['preferences'], 'autogroup' | 'missing_instrumentation'>
  122. >(traceState.preferences);
  123. traceStatePreferencesRef.current = traceState.preferences;
  124. useLayoutEffect(() => {
  125. if (initializedRef.current) {
  126. return undefined;
  127. }
  128. if (tree.type !== 'trace') {
  129. return undefined;
  130. }
  131. let cancel = false;
  132. setStatus('pending');
  133. initializedRef.current = true;
  134. const expandOptions = {
  135. api,
  136. organization,
  137. preferences: traceStatePreferencesRef.current,
  138. };
  139. const promise = options.event
  140. ? IssuesTraceTree.ExpandToEvent(tree, options.event.eventID, expandOptions)
  141. : Promise.resolve();
  142. promise
  143. .then(() => {
  144. if (cancel) {
  145. return;
  146. }
  147. setStatus('success');
  148. onTraceLoad();
  149. })
  150. .catch(() => {
  151. if (cancel) {
  152. return;
  153. }
  154. setStatus('error');
  155. });
  156. return () => {
  157. cancel = true;
  158. };
  159. }, [tree, api, onTraceLoad, organization, options.event]);
  160. return status;
  161. }