traceSearchTokenizer.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import type {
  2. NoDataNode,
  3. ParentAutogroupNode,
  4. SiblingAutogroupNode,
  5. TraceTree,
  6. TraceTreeNode,
  7. } from './../traceTree';
  8. import grammar from './traceSearch.pegjs';
  9. interface SearchToken {
  10. key: string;
  11. type: 'Token';
  12. value: string | number;
  13. negated?: boolean;
  14. operator?: 'gt' | 'ge' | 'lt' | 'le' | 'eq';
  15. }
  16. type Token = SearchToken;
  17. // typeof can return one of the following string values - we ignore BigInt, Symbol as there
  18. // is no practical case for them + they are not supported by JSON.
  19. // object (special handling for arrays), number, string, boolean, undefined, null
  20. type Type = 'object' | 'number' | 'string' | 'boolean' | 'undefined' | 'null';
  21. // @ts-expect-error we ignore some keys on purpose, the TS error makes it helpful
  22. // for seeing exactly which ones we are ignoring for when we want to add support for them
  23. const SPAN_KEYS: Record<keyof TraceTree.Span, Type> = {
  24. hash: 'string',
  25. span_id: 'string',
  26. start_timestamp: 'number',
  27. timestamp: 'number',
  28. trace_id: 'string',
  29. description: 'string',
  30. exclusive_time: 'number',
  31. op: 'string',
  32. origin: 'string',
  33. parent_span_id: 'string',
  34. same_process_as_parent: 'boolean',
  35. // This one will need to be flattened
  36. 'span.average_time': 'number',
  37. status: 'string',
  38. // These are both records and will need to be handled differently
  39. sentry_tags: 'string',
  40. tags: 'string',
  41. };
  42. export function traceSearchTokenizer(input: string): Token[] {
  43. return grammar.parse(input);
  44. }
  45. export function traceSearchLexer(_input: string): string[] {
  46. throw new Error('Not implemented');
  47. }
  48. export function evaluateTokenForTraceNode(
  49. node:
  50. | TraceTreeNode<TraceTree.NodeValue>
  51. | ParentAutogroupNode
  52. | SiblingAutogroupNode
  53. | NoDataNode,
  54. token: Token
  55. ): boolean {
  56. const type = SPAN_KEYS[token.key];
  57. // @ts-expect-error ignore the lookup as the value will be dynamic
  58. const value = node.value[token.key];
  59. let match: undefined | boolean = undefined;
  60. if (token.value === undefined) {
  61. match = value === undefined;
  62. }
  63. if (token.value === null) {
  64. match = value === null;
  65. }
  66. // @TODO check for the distinction between obj and array here as L78
  67. // does not guarantee exact same primitive type in this case
  68. if (typeof value !== type && token.value !== null && token.value !== undefined) {
  69. // The two types are not the same.
  70. return false;
  71. }
  72. // prettier-ignore
  73. switch (type) {
  74. case 'string': {
  75. match = value === token.value || value.includes(token.value);
  76. break;
  77. }
  78. case 'number': {
  79. if (!token.operator) {
  80. match = value === token.value;
  81. break;
  82. }
  83. // prettier-ignore
  84. switch (token.operator) {
  85. case 'gt': match = value > token.value; break;
  86. case 'ge': match = value >= token.value; break;
  87. case 'lt': match = value < token.value; break;
  88. case 'le': match = value <= token.value; break;
  89. case 'eq': match = value === token.value; break;
  90. default: break;
  91. }
  92. break;
  93. }
  94. case 'boolean': {
  95. match = value === token.value;
  96. break;
  97. }
  98. case 'object': {
  99. return false;
  100. }
  101. default: break;
  102. }
  103. if (match === undefined) {
  104. return false;
  105. }
  106. return token.negated ? !match : match;
  107. }