123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- import {
- isAutogroupedNode,
- isSpanNode,
- isTraceErrorNode,
- isTransactionNode,
- } from 'sentry/views/performance/newTraceDetails/guards';
- import type {
- TraceTree,
- TraceTreeNode,
- } from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
- import {traceReducerExhaustiveActionCheck} from 'sentry/views/performance/newTraceDetails/traceState';
- export type TraceSearchAction =
- | {query: string | undefined; type: 'set query'}
- | {type: 'go to first match'}
- | {type: 'go to last match'}
- | {type: 'go to next match'}
- | {type: 'go to previous match'}
- | {
- resultIndex: number;
- resultIteratorIndex: number;
- type: 'set search iterator index';
- }
- | {type: 'clear'}
- | {type: 'clear search iterator index'}
- | {type: 'clear query'}
- | {
- node: TraceTreeNode<TraceTree.NodeValue> | null;
- previousNode: {
- resultIndex: number | undefined;
- resultIteratorIndex: number | undefined;
- } | null;
- results: ReadonlyArray<TraceResult>;
- resultsLookup: Map<TraceTreeNode<TraceTree.NodeValue>, number>;
- type: 'set results';
- resultIndex?: number;
- resultIteratorIndex?: number;
- };
- export type TraceSearchState = {
- node: TraceTreeNode<TraceTree.NodeValue> | null;
- query: string | undefined;
- // Index in the list/tree
- resultIndex: number | null;
- // Index in the results array
- resultIteratorIndex: number | null;
- results: ReadonlyArray<TraceResult> | null;
- resultsLookup: Map<TraceTreeNode<TraceTree.NodeValue>, number>;
- status: [ts: number, 'loading' | 'success' | 'error'] | undefined;
- };
- function assertBoundedIndex(index: number, length: number) {
- if (index < 0 || index > length - 1) {
- throw new Error('Search index out of bounds');
- }
- }
- export function traceSearchReducer(
- state: TraceSearchState,
- action: TraceSearchAction
- ): TraceSearchState {
- switch (action.type) {
- case 'clear query': {
- return {
- node: null,
- query: undefined,
- resultIteratorIndex: null,
- results: null,
- resultIndex: null,
- resultsLookup: new Map(),
- status: undefined,
- };
- }
- case 'go to first match': {
- if (!state.results || state.results.length === 0) {
- return state;
- }
- return {
- ...state,
- node: state.results[0].value,
- resultIteratorIndex: 0,
- resultIndex: state.results[0].index,
- };
- }
- case 'go to last match': {
- if (!state.results || state.results.length === 0) {
- return state;
- }
- return {
- ...state,
- resultIteratorIndex: state.results.length - 1,
- resultIndex: state.results[state.results.length - 1].index,
- node: state.results[state.results.length - 1].value,
- };
- }
- case 'go to next match': {
- if (state.resultIteratorIndex === null) {
- if (!state.results || state.results.length === 0) {
- return state;
- }
- return {
- ...state,
- resultIteratorIndex: 0,
- resultIndex: state.results[0].index,
- node: state.results[0].value,
- };
- }
- if (!state.results) return state;
- let next = state.resultIteratorIndex + 1;
- if (next > state.results.length - 1) {
- next = 0;
- }
- assertBoundedIndex(next, state.results.length);
- return {
- ...state,
- resultIteratorIndex: next,
- resultIndex: state.results[next].index,
- node: state.results[next].value,
- };
- }
- case 'go to previous match': {
- if (state.resultIteratorIndex === null) {
- if (!state.results || !state.results.length) {
- return state;
- }
- return {
- ...state,
- resultIteratorIndex: state.results.length - 1,
- resultIndex: state.results[state.results.length - 1].index,
- node: state.results[state.results.length - 1].value,
- };
- }
- if (!state.results) return state;
- let previous = state.resultIteratorIndex - 1;
- if (previous < 0) {
- previous = state.results.length - 1;
- }
- assertBoundedIndex(previous, state.results.length);
- return {
- ...state,
- resultIteratorIndex: previous,
- resultIndex: state.results[previous].index,
- node: state.results[previous].value,
- };
- }
- case 'set results': {
- return {
- ...state,
- status: [performance.now(), 'success'],
- results: action.results,
- resultIteratorIndex: action.resultIteratorIndex ?? null,
- node: action.node ?? null,
- resultIndex: action.resultIndex ?? null,
- resultsLookup: action.resultsLookup,
- };
- }
- case 'set query': {
- return {
- ...state,
- status: [performance.now(), 'loading'],
- query: action.query,
- };
- }
- case 'set search iterator index': {
- return {
- ...state,
- node: state.results?.[action.resultIteratorIndex]?.value ?? null,
- resultIteratorIndex: action.resultIteratorIndex,
- resultIndex: action.resultIndex,
- };
- }
- case 'clear search iterator index':
- return {
- ...state,
- resultIteratorIndex: null,
- resultIndex: null,
- node: null,
- };
- case 'clear': {
- return {...state, node: null, resultIteratorIndex: null, resultIndex: null};
- }
- default: {
- traceReducerExhaustiveActionCheck(action);
- return state;
- }
- }
- }
- type TraceResult = {
- index: number;
- value: TraceTreeNode<TraceTree.NodeValue>;
- };
- export function searchInTraceTree(
- tree: TraceTree,
- query: string,
- previousNode: TraceTreeNode<TraceTree.NodeValue> | null,
- cb: (
- results: [
- ReadonlyArray<TraceResult>,
- Map<TraceTreeNode<TraceTree.NodeValue>, number>,
- {resultIndex: number | undefined; resultIteratorIndex: number | undefined} | null,
- ]
- ) => void
- ): {id: number | null} {
- const raf: {id: number | null} = {id: 0};
- let previousNodeSearchResult: {
- resultIndex: number | undefined;
- resultIteratorIndex: number | undefined;
- } | null = null;
- const results: Array<TraceResult> = [];
- const resultLookup = new Map();
- let i = 0;
- let matchCount = 0;
- const count = tree.list.length;
- function search() {
- const ts = performance.now();
- while (i < count && performance.now() - ts < 12) {
- const node = tree.list[i];
- if (searchInTraceSubset(query, node)) {
- results.push({index: i, value: node});
- resultLookup.set(node, matchCount);
- if (previousNode === node) {
- previousNodeSearchResult = {
- resultIndex: i,
- resultIteratorIndex: matchCount,
- };
- }
- matchCount++;
- }
- i++;
- }
- if (i < count) {
- raf.id = requestAnimationFrame(search);
- }
- if (i === count) {
- cb([results, resultLookup, previousNodeSearchResult]);
- raf.id = null;
- }
- }
- raf.id = requestAnimationFrame(search);
- return raf;
- }
- function searchInTraceSubset(
- query: string,
- node: TraceTreeNode<TraceTree.NodeValue>
- ): boolean {
- if (isSpanNode(node)) {
- if (node.value.op?.includes(query)) {
- return true;
- }
- if (node.value.description?.includes(query)) {
- return true;
- }
- if (node.value.span_id && node.value.span_id === query) {
- return true;
- }
- }
- if (isTransactionNode(node)) {
- if (node.value['transaction.op']?.includes(query)) {
- return true;
- }
- if (node.value.transaction?.includes(query)) {
- return true;
- }
- if (node.value.event_id && node.value.event_id === query) {
- return true;
- }
- }
- if (isAutogroupedNode(node)) {
- if (node.value.op?.includes(query)) {
- return true;
- }
- if (node.value.description?.includes(query)) {
- return true;
- }
- }
- if (isTraceErrorNode(node)) {
- if (node.value.level === query) {
- return true;
- }
- if (node.value.title?.includes(query)) {
- return true;
- }
- }
- return false;
- }
|