index.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import {
  2. FilterType,
  3. joinQuery,
  4. parseSearch,
  5. type SearchConfig,
  6. Token,
  7. } from 'sentry/components/searchSyntax/parser';
  8. import {defined} from 'sentry/utils';
  9. import {
  10. MetricSeriesFilterUpdateType,
  11. type MetricsQuery,
  12. } from 'sentry/utils/metrics/types';
  13. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  14. import {addToFilter, excludeFromFilter} from '../../discover/table/cellAction';
  15. /**
  16. * Updates query string with tag values from the passed groupBys.
  17. */
  18. export function extendQueryWithGroupBys(
  19. query: string,
  20. groupBys: (Record<string, string> | undefined)[]
  21. ) {
  22. const mutableSearch = new MutableSearch(query);
  23. groupBys.forEach(groupBy => {
  24. if (!groupBy) {
  25. return;
  26. }
  27. Object.entries(groupBy).forEach(([key, value]) => {
  28. if (!value) {
  29. return;
  30. }
  31. addToFilter(mutableSearch, key, value);
  32. });
  33. });
  34. return mutableSearch.formatString();
  35. }
  36. /**
  37. * Wraps text filters of a search string in quotes if they are not already.
  38. */
  39. export function ensureQuotedTextFilters(
  40. query: string,
  41. configOverrides?: Partial<SearchConfig>
  42. ) {
  43. const parsedSearch = parseSearch(query, configOverrides);
  44. if (!parsedSearch) {
  45. return query;
  46. }
  47. for (let i = 0; i < parsedSearch.length; i++) {
  48. const token = parsedSearch[i];
  49. if (token.type === Token.FILTER && token.filter === FilterType.TEXT) {
  50. // joinQuery() does not access nested tokens, so we need to manipulate the text of the filter instead of its value
  51. if (!token.value.quoted) {
  52. token.text = `${token.negated ? '!' : ''}${token.key.text}:"${token.value.text}"`;
  53. }
  54. const spaceToken = parsedSearch[i + 1];
  55. const afterSpaceToken = parsedSearch[i + 2];
  56. if (
  57. spaceToken &&
  58. afterSpaceToken &&
  59. spaceToken.type === Token.SPACES &&
  60. spaceToken.text === '' &&
  61. afterSpaceToken.type === Token.FILTER
  62. ) {
  63. // Ensure there is a space between two filters
  64. spaceToken.text = ' ';
  65. }
  66. }
  67. }
  68. return joinQuery(parsedSearch);
  69. }
  70. /**
  71. * Used when a user clicks on filter button in the series summary table. Applies
  72. * tag values to the filter string of the query. Removes the tags from query groupyBy
  73. */
  74. export function updateQueryWithSeriesFilter(
  75. query: MetricsQuery,
  76. groupBys: Record<string, string>,
  77. updateType: MetricSeriesFilterUpdateType
  78. ) {
  79. // TODO(metrics): This is a temporary solution to handle the case where the query has OR operator.
  80. // since addToFilter and excludeFromFilter do not handle it properly. We should refactor this to use
  81. // search syntax parser instead.
  82. const queryStr = query.query?.includes('OR') ? `(${query.query})` : query.query ?? '';
  83. const mutableSearch = new MutableSearch(queryStr);
  84. const groupByEntries = Object.entries(groupBys);
  85. groupByEntries.forEach(([key, value]) => {
  86. if (!defined(value)) {
  87. return;
  88. }
  89. updateType === MetricSeriesFilterUpdateType.ADD
  90. ? addToFilter(mutableSearch, key, value)
  91. : excludeFromFilter(mutableSearch, key, value);
  92. });
  93. const extendedQuery = mutableSearch.formatString();
  94. const newGroupBy = (query.groupBy ?? []).filter(tag => !groupBys[tag]);
  95. return {
  96. ...query,
  97. query: ensureQuotedTextFilters(extendedQuery),
  98. groupBy: newGroupBy,
  99. };
  100. }