parser.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. import * as Sentry from '@sentry/react';
  2. import {SpanStatus} from '@sentry/tracing';
  3. import moment from 'moment';
  4. import {LocationRange} from 'pegjs';
  5. import {t} from 'app/locale';
  6. import {
  7. isMeasurement,
  8. isSpanOperationBreakdownField,
  9. measurementType,
  10. } from 'app/utils/discover/fields';
  11. import grammar from './grammar.pegjs';
  12. type TextFn = () => string;
  13. type LocationFn = () => LocationRange;
  14. type ListItem<K> = [
  15. space: ReturnType<TokenConverter['tokenSpaces']>,
  16. comma: string,
  17. space: ReturnType<TokenConverter['tokenSpaces']>,
  18. key: K
  19. ];
  20. const listJoiner = <K,>([s1, comma, s2, value]: ListItem<K>) => ({
  21. separator: [s1.value, comma, s2.value].join(''),
  22. value,
  23. });
  24. /**
  25. * A token represents a node in the syntax tree. These are all extrapolated
  26. * from the grammar and may not be named exactly the same.
  27. */
  28. export enum Token {
  29. Spaces = 'spaces',
  30. Filter = 'filter',
  31. FreeText = 'freeText',
  32. LogicGroup = 'logicGroup',
  33. LogicBoolean = 'logicBoolean',
  34. KeySimple = 'keySimple',
  35. KeyExplicitTag = 'keyExplicitTag',
  36. KeyAggregate = 'keyAggregate',
  37. KeyAggregateArgs = 'keyAggregateArgs',
  38. ValueIso8601Date = 'valueIso8601Date',
  39. ValueRelativeDate = 'valueRelativeDate',
  40. ValueDuration = 'valueDuration',
  41. ValuePercentage = 'valuePercentage',
  42. ValueBoolean = 'valueBoolean',
  43. ValueNumber = 'valueNumber',
  44. ValueText = 'valueText',
  45. ValueNumberList = 'valueNumberList',
  46. ValueTextList = 'valueTextList',
  47. }
  48. /**
  49. * An operator in a key value term
  50. */
  51. export enum TermOperator {
  52. Default = '',
  53. GreaterThanEqual = '>=',
  54. LessThanEqual = '<=',
  55. GreaterThan = '>',
  56. LessThan = '<',
  57. Equal = '=',
  58. NotEqual = '!=',
  59. }
  60. /**
  61. * Logic operators
  62. */
  63. export enum BooleanOperator {
  64. And = 'AND',
  65. Or = 'OR',
  66. }
  67. /**
  68. * The Token.Filter may be one of many types of filters. This enum declares the
  69. * each variant filter type.
  70. */
  71. export enum FilterType {
  72. Text = 'text',
  73. TextIn = 'textIn',
  74. Date = 'date',
  75. SpecificDate = 'specificDate',
  76. RelativeDate = 'relativeDate',
  77. Duration = 'duration',
  78. Numeric = 'numeric',
  79. NumericIn = 'numericIn',
  80. Boolean = 'boolean',
  81. AggregateDuration = 'aggregateDuration',
  82. AggregatePercentage = 'aggregatePercentage',
  83. AggregateNumeric = 'aggregateNumeric',
  84. AggregateDate = 'aggregateDate',
  85. AggregateRelativeDate = 'aggregateRelativeDate',
  86. Has = 'has',
  87. Is = 'is',
  88. }
  89. const allOperators = [
  90. TermOperator.Default,
  91. TermOperator.GreaterThanEqual,
  92. TermOperator.LessThanEqual,
  93. TermOperator.GreaterThan,
  94. TermOperator.LessThan,
  95. TermOperator.Equal,
  96. TermOperator.NotEqual,
  97. ] as const;
  98. const textKeys = [Token.KeySimple, Token.KeyExplicitTag] as const;
  99. const numberUnits = {
  100. b: 1_000_000_000,
  101. m: 1_000_000,
  102. k: 1_000,
  103. };
  104. /**
  105. * This constant-type configuration object declares how each filter type
  106. * operates. Including what types of keys, operators, and values it may
  107. * recieve.
  108. *
  109. * This configuration is used to generate the discriminate Filter type that is
  110. * returned from the tokenFilter converter.
  111. */
  112. export const filterTypeConfig = {
  113. [FilterType.Text]: {
  114. validKeys: textKeys,
  115. validOps: [],
  116. validValues: [Token.ValueText],
  117. canNegate: true,
  118. },
  119. [FilterType.TextIn]: {
  120. validKeys: textKeys,
  121. validOps: [],
  122. validValues: [Token.ValueTextList],
  123. canNegate: true,
  124. },
  125. [FilterType.Date]: {
  126. validKeys: [Token.KeySimple],
  127. validOps: allOperators,
  128. validValues: [Token.ValueIso8601Date],
  129. canNegate: false,
  130. },
  131. [FilterType.SpecificDate]: {
  132. validKeys: [Token.KeySimple],
  133. validOps: [],
  134. validValues: [Token.ValueIso8601Date],
  135. canNegate: false,
  136. },
  137. [FilterType.RelativeDate]: {
  138. validKeys: [Token.KeySimple],
  139. validOps: [],
  140. validValues: [Token.ValueRelativeDate],
  141. canNegate: false,
  142. },
  143. [FilterType.Duration]: {
  144. validKeys: [Token.KeySimple],
  145. validOps: allOperators,
  146. validValues: [Token.ValueDuration],
  147. canNegate: false,
  148. },
  149. [FilterType.Numeric]: {
  150. validKeys: [Token.KeySimple],
  151. validOps: allOperators,
  152. validValues: [Token.ValueNumber],
  153. canNegate: false,
  154. },
  155. [FilterType.NumericIn]: {
  156. validKeys: [Token.KeySimple],
  157. validOps: [],
  158. validValues: [Token.ValueNumberList],
  159. canNegate: false,
  160. },
  161. [FilterType.Boolean]: {
  162. validKeys: [Token.KeySimple],
  163. validOps: [],
  164. validValues: [Token.ValueBoolean],
  165. canNegate: true,
  166. },
  167. [FilterType.AggregateDuration]: {
  168. validKeys: [Token.KeyAggregate],
  169. validOps: allOperators,
  170. validValues: [Token.ValueDuration],
  171. canNegate: true,
  172. },
  173. [FilterType.AggregateNumeric]: {
  174. validKeys: [Token.KeyAggregate],
  175. validOps: allOperators,
  176. validValues: [Token.ValueNumber],
  177. canNegate: true,
  178. },
  179. [FilterType.AggregatePercentage]: {
  180. validKeys: [Token.KeyAggregate],
  181. validOps: allOperators,
  182. validValues: [Token.ValuePercentage],
  183. canNegate: true,
  184. },
  185. [FilterType.AggregateDate]: {
  186. validKeys: [Token.KeyAggregate],
  187. validOps: allOperators,
  188. validValues: [Token.ValueIso8601Date],
  189. canNegate: true,
  190. },
  191. [FilterType.AggregateRelativeDate]: {
  192. validKeys: [Token.KeyAggregate],
  193. validOps: allOperators,
  194. validValues: [Token.ValueRelativeDate],
  195. canNegate: true,
  196. },
  197. [FilterType.Has]: {
  198. validKeys: [Token.KeySimple],
  199. validOps: [],
  200. validValues: [],
  201. canNegate: true,
  202. },
  203. [FilterType.Is]: {
  204. validKeys: [Token.KeySimple],
  205. validOps: [],
  206. validValues: [Token.ValueText],
  207. canNegate: true,
  208. },
  209. } as const;
  210. type FilterTypeConfig = typeof filterTypeConfig;
  211. /**
  212. * Object representing an invalid filter state
  213. */
  214. type InvalidFilter = {
  215. /**
  216. * The message indicating why the filter is invalid
  217. */
  218. reason: string;
  219. /**
  220. * In the case where a filter is invalid, we may be expecting a different
  221. * type for this filter based on the key. This can be useful to hint to the
  222. * user what values they should be providing.
  223. *
  224. * This may be multiple filter types.
  225. */
  226. expectedType?: FilterType[];
  227. };
  228. type FilterMap = {
  229. [F in keyof FilterTypeConfig]: {
  230. type: Token.Filter;
  231. /**
  232. * The filter type being represented
  233. */
  234. filter: F;
  235. /**
  236. * The key of the filter
  237. */
  238. key: KVConverter<FilterTypeConfig[F]['validKeys'][number]>;
  239. /**
  240. * The value of the filter
  241. */
  242. value: KVConverter<FilterTypeConfig[F]['validValues'][number]>;
  243. /**
  244. * The operator applied to the filter
  245. */
  246. operator: FilterTypeConfig[F]['validOps'][number];
  247. /**
  248. * Indicates if the filter has been negated
  249. */
  250. negated: FilterTypeConfig[F]['canNegate'] extends true ? boolean : false;
  251. /**
  252. * When a filter is marked as 'invalid' a reason is given. If the filter is
  253. * not invalid this will always be null
  254. */
  255. invalid: InvalidFilter | null;
  256. };
  257. };
  258. type TextFilter = FilterMap[FilterType.Text];
  259. /**
  260. * The Filter type discriminates on the FilterType enum using the `filter` key.
  261. *
  262. * When receiving this type you may narrow it to a specific filter by checking
  263. * this field. This will give you proper types on what the key, value, and
  264. * operator results are.
  265. */
  266. type FilterResult = FilterMap[FilterType];
  267. /**
  268. * Utility to get the string name of any type of key.
  269. */
  270. const getKeyName = (
  271. key: ReturnType<
  272. TokenConverter['tokenKeySimple' | 'tokenKeyExplicitTag' | 'tokenKeyAggregate']
  273. >
  274. ) => {
  275. switch (key.type) {
  276. case Token.KeySimple:
  277. return key.value;
  278. case Token.KeyExplicitTag:
  279. return key.key.value;
  280. case Token.KeyAggregate:
  281. return key.name.value;
  282. default:
  283. return '';
  284. }
  285. };
  286. type TokenConverterOpts = {
  287. text: TextFn;
  288. location: LocationFn;
  289. config: SearchConfig;
  290. };
  291. /**
  292. * Used to construct token results via the token grammar
  293. */
  294. class TokenConverter {
  295. text: TextFn;
  296. location: LocationFn;
  297. config: SearchConfig;
  298. constructor({text, location, config}: TokenConverterOpts) {
  299. this.text = text;
  300. this.location = location;
  301. this.config = config;
  302. }
  303. /**
  304. * Validates various types of keys
  305. */
  306. keyValidation = {
  307. isNumeric: (key: string) => this.config.numericKeys.has(key) || isMeasurement(key),
  308. isBoolean: (key: string) => this.config.booleanKeys.has(key),
  309. isPercentage: (key: string) => this.config.percentageKeys.has(key),
  310. isDate: (key: string) => this.config.dateKeys.has(key),
  311. isDuration: (key: string) =>
  312. this.config.durationKeys.has(key) ||
  313. isSpanOperationBreakdownField(key) ||
  314. measurementType(key) === 'duration',
  315. };
  316. /**
  317. * Creates a token with common `text` and `location` keys.
  318. */
  319. makeToken = <T,>(args: T) => ({
  320. text: this.text(),
  321. location: this.location(),
  322. ...args,
  323. });
  324. tokenSpaces = (value: string) =>
  325. this.makeToken({
  326. type: Token.Spaces as const,
  327. value,
  328. });
  329. tokenFilter = <T extends FilterType>(
  330. filter: T,
  331. key: FilterMap[T]['key'],
  332. value: FilterMap[T]['value'],
  333. operator: FilterMap[T]['operator'] | undefined,
  334. negated: FilterMap[T]['negated']
  335. ) =>
  336. this.makeToken({
  337. type: Token.Filter,
  338. filter,
  339. key,
  340. value,
  341. negated,
  342. operator: operator ?? TermOperator.Default,
  343. invalid: this.checkInvalidFilter(filter, key, value),
  344. } as FilterResult);
  345. tokenFreeText = (value: string, quoted: boolean) =>
  346. this.makeToken({
  347. type: Token.FreeText as const,
  348. value,
  349. quoted,
  350. });
  351. tokenLogicGroup = (
  352. inner: Array<
  353. | ReturnType<TokenConverter['tokenLogicBoolean']>
  354. | ReturnType<TokenConverter['tokenFilter']>
  355. | ReturnType<TokenConverter['tokenFreeText']>
  356. >
  357. ) =>
  358. this.makeToken({
  359. type: Token.LogicGroup as const,
  360. inner,
  361. });
  362. tokenLogicBoolean = (bool: BooleanOperator) =>
  363. this.makeToken({
  364. type: Token.LogicBoolean as const,
  365. value: bool,
  366. });
  367. tokenKeySimple = (value: string, quoted: boolean) =>
  368. this.makeToken({
  369. type: Token.KeySimple as const,
  370. value,
  371. quoted,
  372. });
  373. tokenKeyExplicitTag = (
  374. prefix: string,
  375. key: ReturnType<TokenConverter['tokenKeySimple']>
  376. ) =>
  377. this.makeToken({
  378. type: Token.KeyExplicitTag as const,
  379. prefix,
  380. key,
  381. });
  382. tokenKeyAggregate = (
  383. name: ReturnType<TokenConverter['tokenKeySimple']>,
  384. args: ReturnType<TokenConverter['tokenKeyAggregateArgs']> | null,
  385. argsSpaceBefore: ReturnType<TokenConverter['tokenSpaces']>,
  386. argsSpaceAfter: ReturnType<TokenConverter['tokenSpaces']>
  387. ) =>
  388. this.makeToken({
  389. type: Token.KeyAggregate as const,
  390. name,
  391. args,
  392. argsSpaceBefore,
  393. argsSpaceAfter,
  394. });
  395. tokenKeyAggregateArgs = (
  396. arg1: ReturnType<TokenConverter['tokenKeySimple']>,
  397. args: ListItem<ReturnType<TokenConverter['tokenKeySimple']>>[]
  398. ) =>
  399. this.makeToken({
  400. type: Token.KeyAggregateArgs as const,
  401. args: [{separator: '', value: arg1}, ...args.map(listJoiner)],
  402. });
  403. tokenValueIso8601Date = (value: string) =>
  404. this.makeToken({
  405. type: Token.ValueIso8601Date as const,
  406. value: moment(value),
  407. });
  408. tokenValueRelativeDate = (
  409. value: string,
  410. sign: '-' | '+',
  411. unit: 'w' | 'd' | 'h' | 'm'
  412. ) =>
  413. this.makeToken({
  414. type: Token.ValueRelativeDate as const,
  415. value: Number(value),
  416. sign,
  417. unit,
  418. });
  419. tokenValueDuration = (
  420. value: string,
  421. unit: 'ms' | 's' | 'min' | 'm' | 'hr' | 'h' | 'day' | 'd' | 'wk' | 'w'
  422. ) =>
  423. this.makeToken({
  424. type: Token.ValueDuration as const,
  425. value: Number(value),
  426. unit,
  427. });
  428. tokenValuePercentage = (value: string) =>
  429. this.makeToken({
  430. type: Token.ValuePercentage as const,
  431. value: Number(value),
  432. });
  433. tokenValueBoolean = (value: string) =>
  434. this.makeToken({
  435. type: Token.ValueBoolean as const,
  436. value: ['1', 'true'].includes(value.toLowerCase()),
  437. });
  438. tokenValueNumber = (value: string, unit: string) =>
  439. this.makeToken({
  440. type: Token.ValueNumber as const,
  441. value,
  442. rawValue: Number(value) * (numberUnits[unit] ?? 1),
  443. unit,
  444. });
  445. tokenValueNumberList = (
  446. item1: ReturnType<TokenConverter['tokenValueNumber']>,
  447. items: ListItem<ReturnType<TokenConverter['tokenValueNumber']>>[]
  448. ) =>
  449. this.makeToken({
  450. type: Token.ValueNumberList as const,
  451. items: [{separator: '', value: item1}, ...items.map(listJoiner)],
  452. });
  453. tokenValueTextList = (
  454. item1: ReturnType<TokenConverter['tokenValueText']>,
  455. items: ListItem<ReturnType<TokenConverter['tokenValueText']>>[]
  456. ) =>
  457. this.makeToken({
  458. type: Token.ValueTextList as const,
  459. items: [{separator: '', value: item1}, ...items.map(listJoiner)],
  460. });
  461. tokenValueText = (value: string, quoted: boolean) =>
  462. this.makeToken({
  463. type: Token.ValueText as const,
  464. value,
  465. quoted,
  466. });
  467. /**
  468. * This method is used while tokenizing to predicate whether a filter should
  469. * match or not. We do this because not all keys are valid for specific
  470. * filter types. For example, boolean filters should only match for keys
  471. * which can be filtered as booleans.
  472. *
  473. * See [0] and look for &{ predicate } to understand how predicates are
  474. * declared in the grammar
  475. *
  476. * [0]:https://pegjs.org/documentation
  477. */
  478. predicateFilter = <T extends FilterType>(type: T, key: FilterMap[T]['key']) => {
  479. const keyName = getKeyName(key);
  480. const aggregateKey = key as ReturnType<TokenConverter['tokenKeyAggregate']>;
  481. const {isNumeric, isDuration, isBoolean, isDate, isPercentage} = this.keyValidation;
  482. switch (type) {
  483. case FilterType.Numeric:
  484. case FilterType.NumericIn:
  485. return isNumeric(keyName);
  486. case FilterType.Duration:
  487. return isDuration(keyName);
  488. case FilterType.Boolean:
  489. return isBoolean(keyName);
  490. case FilterType.Date:
  491. case FilterType.RelativeDate:
  492. case FilterType.SpecificDate:
  493. return isDate(keyName);
  494. case FilterType.AggregateDuration:
  495. return aggregateKey.args?.args.some(arg => isDuration(arg.value.value));
  496. case FilterType.AggregateDate:
  497. return aggregateKey.args?.args.some(arg => isDate(arg.value.value));
  498. case FilterType.AggregatePercentage:
  499. return aggregateKey.args?.args.some(arg => isPercentage(arg.value.value));
  500. default:
  501. return true;
  502. }
  503. };
  504. /**
  505. * Predicates weather a text filter have operators for specific keys.
  506. */
  507. predicateTextOperator = (key: TextFilter['key']) =>
  508. this.config.textOperatorKeys.has(getKeyName(key));
  509. /**
  510. * Checks a filter against some non-grammar validation rules
  511. */
  512. checkInvalidFilter = <T extends FilterType>(
  513. filter: T,
  514. key: FilterMap[T]['key'],
  515. value: FilterMap[T]['value']
  516. ) => {
  517. // Only text filters may currently be invalid, since the text filter is the
  518. // "fall through" filter that will match when other filter predicates fail.
  519. if (filter !== FilterType.Text) {
  520. return null;
  521. }
  522. return this.checkInvalidTextFilter(
  523. key as TextFilter['key'],
  524. value as TextFilter['value']
  525. );
  526. };
  527. /**
  528. * Validates text filters which may have failed predication
  529. */
  530. checkInvalidTextFilter = (key: TextFilter['key'], value: TextFilter['value']) => {
  531. // Explicit tag keys will always be treated as text filters
  532. if (key.type === Token.KeyExplicitTag) {
  533. return this.checkInvalidTextValue(value);
  534. }
  535. const keyName = getKeyName(key);
  536. if (this.keyValidation.isDuration(keyName)) {
  537. return {
  538. reason: t('Invalid duration. Expected number followed by duration unit suffix.'),
  539. expectedType: [FilterType.Duration],
  540. };
  541. }
  542. if (this.keyValidation.isDate(keyName)) {
  543. return {
  544. reason: t(
  545. 'Invalid date format. Expected +/-duration (e.g. +1h) or ISO 8601-like (e.g. {now}).',
  546. new Date().toISOString()
  547. ),
  548. expectedType: [FilterType.Date, FilterType.SpecificDate, FilterType.RelativeDate],
  549. };
  550. }
  551. if (this.keyValidation.isBoolean(keyName)) {
  552. return {
  553. reason: t('Invalid boolean. Expected true, 1, false, or 0.'),
  554. expectedType: [FilterType.Boolean],
  555. };
  556. }
  557. if (this.keyValidation.isNumeric(keyName)) {
  558. return {
  559. reason: t(
  560. 'Invalid number. Expected number then optional k, m, or b suffix (e.g. 500k).'
  561. ),
  562. expectedType: [FilterType.Numeric, FilterType.NumericIn],
  563. };
  564. }
  565. return this.checkInvalidTextValue(value);
  566. };
  567. /**
  568. * Validates the value of a text filter
  569. */
  570. checkInvalidTextValue = (value: TextFilter['value']) => {
  571. if (!value.quoted && /(^|[^\\])"/.test(value.value)) {
  572. return {reason: t('Quotes must enclose text or be escaped.')};
  573. }
  574. if (!value.quoted && value.value === '') {
  575. return {reason: t('Filter must have a value.')};
  576. }
  577. return null;
  578. };
  579. }
  580. /**
  581. * Maps token conversion methods to their result types
  582. */
  583. type ConverterResultMap = {
  584. [K in keyof TokenConverter & `token${string}`]: ReturnType<TokenConverter[K]>;
  585. };
  586. type Converter = keyof ConverterResultMap;
  587. /**
  588. * Converter keys specific to Key and Value tokens
  589. */
  590. type KVTokens = Converter & `token${'Key' | 'Value'}${string}`;
  591. /**
  592. * Similar to TokenResult, but only includes Key* and Value* token type
  593. * results. This avoids a circular reference when this is used for the Filter
  594. * token converter result
  595. */
  596. type KVConverter<T extends Token> = ConverterResultMap[KVTokens] & {type: T};
  597. /**
  598. * Each token type is discriminated by the `type` field.
  599. */
  600. export type TokenResult<T extends Token> = ConverterResultMap[Converter] & {type: T};
  601. /**
  602. * Result from parsing a search query.
  603. */
  604. export type ParseResult = Array<
  605. | TokenResult<Token.LogicBoolean>
  606. | TokenResult<Token.LogicGroup>
  607. | TokenResult<Token.Filter>
  608. | TokenResult<Token.FreeText>
  609. | TokenResult<Token.Spaces>
  610. >;
  611. /**
  612. * Configures behavior of search parsing
  613. */
  614. export type SearchConfig = {
  615. /**
  616. * Keys which are considered valid for duration filters
  617. */
  618. durationKeys: Set<string>;
  619. /**
  620. * Text filter keys we allow to have operators
  621. */
  622. textOperatorKeys: Set<string>;
  623. /**
  624. * Keys considered valid for the percentage aggregate and may have percentage
  625. * search values
  626. */
  627. percentageKeys: Set<string>;
  628. /**
  629. * Keys considered valid for numeric filter types
  630. */
  631. numericKeys: Set<string>;
  632. /**
  633. * Keys considered valid for date filter types
  634. */
  635. dateKeys: Set<string>;
  636. /**
  637. * Keys considered valid for boolean filter types
  638. */
  639. booleanKeys: Set<string>;
  640. /**
  641. * Enables boolean filtering (AND / OR)
  642. */
  643. allowBoolean: boolean;
  644. };
  645. const defaultConfig: SearchConfig = {
  646. textOperatorKeys: new Set(['sentry.semver']),
  647. durationKeys: new Set(['transaction.duration']),
  648. percentageKeys: new Set(['percentage']),
  649. numericKeys: new Set([
  650. 'project_id',
  651. 'project.id',
  652. 'issue.id',
  653. 'stack.colno',
  654. 'stack.lineno',
  655. 'stack.stack_level',
  656. 'transaction.duration',
  657. 'apdex',
  658. 'p75',
  659. 'p95',
  660. 'p99',
  661. 'failure_rate',
  662. 'count_miserable',
  663. 'user_misery',
  664. 'count_miserable_new',
  665. 'user_miser_new',
  666. ]),
  667. dateKeys: new Set([
  668. 'start',
  669. 'end',
  670. 'first_seen',
  671. 'last_seen',
  672. 'time',
  673. 'timestamp',
  674. 'timestamp.to_hour',
  675. 'timestamp.to_day',
  676. 'transaction.start_time',
  677. 'transaction.end_time',
  678. ]),
  679. booleanKeys: new Set([
  680. 'error.handled',
  681. 'error.unhandled',
  682. 'stack.in_app',
  683. 'key_transaction',
  684. 'team_key_transaction',
  685. ]),
  686. allowBoolean: true,
  687. };
  688. const options = {
  689. TokenConverter,
  690. TermOperator,
  691. FilterType,
  692. config: defaultConfig,
  693. };
  694. /**
  695. * Parse a search query into a ParseResult. Failing to parse the search query
  696. * will result in null.
  697. */
  698. export function parseSearch(query: string): ParseResult | null {
  699. const transaction = Sentry.startTransaction({name: 'parseSearch'});
  700. transaction.setData('query', query);
  701. let parsed: ParseResult | null = null;
  702. try {
  703. parsed = grammar.parse(query, options);
  704. } catch (e) {
  705. transaction.setData('parseError', String(e));
  706. }
  707. transaction.setStatus(parsed === null ? SpanStatus.UnknownError : SpanStatus.Ok);
  708. transaction.finish();
  709. return parsed;
  710. }