traceSearchTokenizer.spec.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import type {RawSpanType} from 'sentry/components/events/interfaces/spans/types';
  2. import {EntryType, type Event} from 'sentry/types';
  3. import {evaluateTokenForTraceNode} from 'sentry/views/performance/newTraceDetails/traceSearch/traceSearchTokenizer';
  4. import {
  5. type TraceTree,
  6. TraceTreeNode,
  7. } from 'sentry/views/performance/newTraceDetails/traceTree';
  8. import grammar from './traceSearch.pegjs';
  9. const evaluate = evaluateTokenForTraceNode;
  10. const metadata = {
  11. project_slug: 'project',
  12. event_id: 'event_id',
  13. };
  14. function makeSpan(overrides: Partial<RawSpanType> = {}): TraceTree.Span {
  15. return {
  16. op: '',
  17. description: '',
  18. span_id: '',
  19. start_timestamp: 0,
  20. timestamp: 10,
  21. event: makeEvent(),
  22. errors: [],
  23. performance_issues: [],
  24. childTransaction: undefined,
  25. ...overrides,
  26. } as TraceTree.Span;
  27. }
  28. function makeEvent(overrides: Partial<Event> = {}, spans: RawSpanType[] = []): Event {
  29. return {
  30. entries: [{type: EntryType.SPANS, data: spans}],
  31. ...overrides,
  32. } as Event;
  33. }
  34. function makeSpanNode(span: Partial<RawSpanType>): TraceTreeNode<TraceTree.Span> {
  35. return new TraceTreeNode(null, makeSpan(span), metadata);
  36. }
  37. describe('traceSearchTokenizer', () => {
  38. it('empty value', () => {
  39. expect(grammar.parse('')).toEqual([]);
  40. });
  41. test.each([
  42. 'key:value',
  43. 'key :value',
  44. 'key : value',
  45. ' key: value',
  46. 'key: value ',
  47. ])('parses %s', input => {
  48. expect(grammar.parse(input)).toEqual([{type: 'Token', key: 'key', value: 'value'}]);
  49. });
  50. describe('grammar', () => {
  51. it('alphanumeric', () => {
  52. expect(grammar.parse('key:1a_-.!$')[0].value).toBe('1a_-.!$');
  53. });
  54. it('integer', () => {
  55. // @TODO scientific notation?
  56. // @TODO should we evaluate arithmetic expressions?
  57. // Support unit suffies (B, KB, MB, GB), (ms, s, m, h, d, w, y)
  58. expect(grammar.parse('key:1')[0].value).toBe(1);
  59. expect(grammar.parse('key:10')[0].value).toBe(10);
  60. expect(grammar.parse('key:-10')[0].value).toBe(-10);
  61. });
  62. it('float', () => {
  63. expect(grammar.parse('key:.5')[0].value).toBe(0.5);
  64. expect(grammar.parse('key:-.5')[0].value).toBe(-0.5);
  65. expect(grammar.parse('key:1.000')[0].value).toBe(1.0);
  66. expect(grammar.parse('key:1.5')[0].value).toBe(1.5);
  67. expect(grammar.parse('key:-1.0')[0].value).toBe(-1.0);
  68. });
  69. it('boolean', () => {
  70. expect(grammar.parse('key:true')[0].value).toBe(true);
  71. expect(grammar.parse('key:false')[0].value).toBe(false);
  72. });
  73. it('undefined', () => {
  74. expect(grammar.parse('key:undefined')[0].value).toBe(undefined);
  75. });
  76. it('null', () => {
  77. expect(grammar.parse('key:null')[0].value).toBe(null);
  78. });
  79. it('multiple expressions', () => {
  80. expect(grammar.parse('key1:value key2:value')).toEqual([
  81. {type: 'Token', key: 'key1', value: 'value'},
  82. {type: 'Token', key: 'key2', value: 'value'},
  83. ]);
  84. });
  85. it('value operator', () => {
  86. expect(grammar.parse('key:>value')[0].operator).toBe('gt');
  87. expect(grammar.parse('key:>=value')[0].operator).toBe('ge');
  88. expect(grammar.parse('key:<value')[0].operator).toBe('lt');
  89. expect(grammar.parse('key:<=value')[0].operator).toBe('le');
  90. expect(grammar.parse('key:=value')[0].operator).toBe('eq');
  91. });
  92. it('negation', () => {
  93. expect(grammar.parse('!key:value')[0].negated).toBe(true);
  94. });
  95. });
  96. describe('transaction properties', () => {});
  97. describe('autogrouped properties', () => {});
  98. describe('missing instrumentation properties', () => {});
  99. describe('error properties', () => {});
  100. describe('perf issue', () => {});
  101. describe('tag properties', () => {});
  102. describe('measurement properties', () => {});
  103. describe('vitals properties', () => {});
  104. });
  105. describe('lexer', () => {
  106. // it.todo('checks for empty key');
  107. // it.todo('checks for invalid key');
  108. // it.todo('checks for unknown keys');
  109. // it.todo('checks for invalid operator');
  110. // it.todo('checks for invalid value');
  111. // it.todo("supports OR'ing expressions");
  112. // it.todo("supports AND'ing expressions");
  113. // it.todo('supports operator precedence via ()');
  114. });
  115. describe('token evaluator', () => {
  116. it('negates expression', () => {
  117. const node = makeSpanNode({span_id: '1a3'});
  118. expect(evaluate(node, grammar.parse('!span_id:1a3')[0])).toBe(false);
  119. });
  120. describe('string', () => {
  121. const node = makeSpanNode({span_id: '1a3'});
  122. function g(v: string) {
  123. return grammar.parse(`span_id:${v}`)[0];
  124. }
  125. it('exact value', () => {
  126. expect(evaluate(node, g('1a3'))).toBe(true);
  127. });
  128. it('using includes', () => {
  129. expect(evaluate(node, g('1a'))).toBe(true);
  130. });
  131. });
  132. describe('number', () => {
  133. const node = makeSpanNode({start_timestamp: 1000});
  134. function g(v: string) {
  135. return grammar.parse(`start_timestamp:${v}`)[0];
  136. }
  137. it('exact value', () => {
  138. expect(evaluate(node, grammar.parse('start_timestamp:1000')[0])).toBe(true);
  139. });
  140. it('using gt', () => {
  141. expect(evaluate(node, g('>999'))).toBe(true);
  142. });
  143. it('using ge', () => {
  144. expect(evaluate(node, g('>=1000'))).toBe(true);
  145. });
  146. it('using lt', () => {
  147. expect(evaluate(node, g('<1001'))).toBe(true);
  148. });
  149. it('using le', () => {
  150. expect(evaluate(node, g('<=1000'))).toBe(true);
  151. });
  152. it('using eq', () => {
  153. expect(evaluate(node, g('=1000'))).toBe(true);
  154. });
  155. describe('comparing float to int', () => {
  156. it('query is float, value is int', () => {
  157. expect(evaluate(makeSpanNode({start_timestamp: 1000}), g('1000.0'))).toBe(true);
  158. });
  159. it('query is int, value is float', () => {
  160. expect(evaluate(makeSpanNode({start_timestamp: 1000.0}), g('1000'))).toBe(true);
  161. });
  162. });
  163. });
  164. describe('boolean', () => {
  165. it('true', () => {
  166. const node = makeSpanNode({same_process_as_parent: true});
  167. expect(evaluate(node, grammar.parse('same_process_as_parent:true')[0])).toBe(true);
  168. });
  169. it('false', () => {
  170. const node = makeSpanNode({same_process_as_parent: false});
  171. expect(evaluate(node, grammar.parse('same_process_as_parent:false')[0])).toBe(true);
  172. });
  173. });
  174. it('null', () => {
  175. // @ts-expect-error force null on type
  176. const node = makeSpanNode({same_process_as_parent: null});
  177. expect(evaluate(node, grammar.parse('same_process_as_parent:null')[0])).toBe(true);
  178. });
  179. it('undefined', () => {
  180. const node = makeSpanNode({same_process_as_parent: undefined});
  181. expect(evaluate(node, grammar.parse('same_process_as_parent:undefined')[0])).toBe(
  182. true
  183. );
  184. });
  185. });