traceSearch.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import type {
  2. TraceTree,
  3. TraceTreeNode,
  4. } from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
  5. import type {TraceSearchResult} from 'sentry/views/performance/newTraceDetails/traceSearch/traceSearchEvaluator';
  6. import {traceReducerExhaustiveActionCheck} from 'sentry/views/performance/newTraceDetails/traceState';
  7. export type TraceSearchAction =
  8. | {query: string | undefined; type: 'set query'}
  9. | {type: 'go to first match'}
  10. | {type: 'go to last match'}
  11. | {type: 'go to next match'}
  12. | {type: 'go to previous match'}
  13. | {
  14. resultIndex: number;
  15. resultIteratorIndex: number;
  16. type: 'set search iterator index';
  17. }
  18. | {type: 'clear'}
  19. | {type: 'clear search iterator index'}
  20. | {type: 'clear query'}
  21. | {
  22. node: TraceTreeNode<TraceTree.NodeValue> | null;
  23. previousNode: {
  24. resultIndex: number | undefined;
  25. resultIteratorIndex: number | undefined;
  26. } | null;
  27. results: ReadonlyArray<TraceSearchResult>;
  28. resultsLookup: Map<TraceTreeNode<TraceTree.NodeValue>, number>;
  29. type: 'set results';
  30. resultIndex?: number;
  31. resultIteratorIndex?: number;
  32. };
  33. export type TraceSearchState = {
  34. node: TraceTreeNode<TraceTree.NodeValue> | null;
  35. query: string | undefined;
  36. // Index in the list/tree
  37. resultIndex: number | null;
  38. // Index in the results array
  39. resultIteratorIndex: number | null;
  40. results: ReadonlyArray<TraceSearchResult> | null;
  41. resultsLookup: Map<TraceTreeNode<TraceTree.NodeValue>, number>;
  42. status: [ts: number, 'loading' | 'success' | 'error'] | undefined;
  43. };
  44. function assertBoundedIndex(index: number, length: number) {
  45. if (index < 0 || index > length - 1) {
  46. throw new Error('Search index out of bounds');
  47. }
  48. }
  49. export function traceSearchReducer(
  50. state: TraceSearchState,
  51. action: TraceSearchAction
  52. ): TraceSearchState {
  53. switch (action.type) {
  54. case 'clear query': {
  55. return {
  56. node: null,
  57. query: undefined,
  58. resultIteratorIndex: null,
  59. results: null,
  60. resultIndex: null,
  61. resultsLookup: new Map(),
  62. status: undefined,
  63. };
  64. }
  65. case 'go to first match': {
  66. if (!state.results || state.results.length === 0) {
  67. return state;
  68. }
  69. return {
  70. ...state,
  71. node: state.results[0].value,
  72. resultIteratorIndex: 0,
  73. resultIndex: state.results[0].index,
  74. };
  75. }
  76. case 'go to last match': {
  77. if (!state.results || state.results.length === 0) {
  78. return state;
  79. }
  80. return {
  81. ...state,
  82. resultIteratorIndex: state.results.length - 1,
  83. resultIndex: state.results[state.results.length - 1].index,
  84. node: state.results[state.results.length - 1].value,
  85. };
  86. }
  87. case 'go to next match': {
  88. if (state.resultIteratorIndex === null) {
  89. if (!state.results || state.results.length === 0) {
  90. return state;
  91. }
  92. return {
  93. ...state,
  94. resultIteratorIndex: 0,
  95. resultIndex: state.results[0].index,
  96. node: state.results[0].value,
  97. };
  98. }
  99. if (!state.results) return state;
  100. let next = state.resultIteratorIndex + 1;
  101. if (next > state.results.length - 1) {
  102. next = 0;
  103. }
  104. assertBoundedIndex(next, state.results.length);
  105. return {
  106. ...state,
  107. resultIteratorIndex: next,
  108. resultIndex: state.results[next].index,
  109. node: state.results[next].value,
  110. };
  111. }
  112. case 'go to previous match': {
  113. if (state.resultIteratorIndex === null) {
  114. if (!state.results || !state.results.length) {
  115. return state;
  116. }
  117. return {
  118. ...state,
  119. resultIteratorIndex: state.results.length - 1,
  120. resultIndex: state.results[state.results.length - 1].index,
  121. node: state.results[state.results.length - 1].value,
  122. };
  123. }
  124. if (!state.results) return state;
  125. let previous = state.resultIteratorIndex - 1;
  126. if (previous < 0) {
  127. previous = state.results.length - 1;
  128. }
  129. assertBoundedIndex(previous, state.results.length);
  130. return {
  131. ...state,
  132. resultIteratorIndex: previous,
  133. resultIndex: state.results[previous].index,
  134. node: state.results[previous].value,
  135. };
  136. }
  137. case 'set results': {
  138. return {
  139. ...state,
  140. status: [performance.now(), 'success'],
  141. results: action.results,
  142. resultIteratorIndex: action.resultIteratorIndex ?? null,
  143. node: action.node ?? null,
  144. resultIndex: action.resultIndex ?? null,
  145. resultsLookup: action.resultsLookup,
  146. };
  147. }
  148. case 'set query': {
  149. return {
  150. ...state,
  151. status: [performance.now(), 'loading'],
  152. query: action.query,
  153. };
  154. }
  155. case 'set search iterator index': {
  156. return {
  157. ...state,
  158. node: state.results?.[action.resultIteratorIndex]?.value ?? null,
  159. resultIteratorIndex: action.resultIteratorIndex,
  160. resultIndex: action.resultIndex,
  161. };
  162. }
  163. case 'clear search iterator index':
  164. return {
  165. ...state,
  166. resultIteratorIndex: null,
  167. resultIndex: null,
  168. node: null,
  169. };
  170. case 'clear': {
  171. return {...state, node: null, resultIteratorIndex: null, resultIndex: null};
  172. }
  173. default: {
  174. traceReducerExhaustiveActionCheck(action);
  175. return state;
  176. }
  177. }
  178. }