formattedQuery.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import {useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import {FilterValueText} from 'sentry/components/searchQueryBuilder/tokens/filter/filter';
  4. import {FilterKeyOperatorVisual} from 'sentry/components/searchQueryBuilder/tokens/filter/filterKeyOperator';
  5. import {SearchQueryBuilderParenIcon} from 'sentry/components/searchQueryBuilder/tokens/paren';
  6. import type {FieldDefinitionGetter} from 'sentry/components/searchQueryBuilder/types';
  7. import {parseQueryBuilderValue} from 'sentry/components/searchQueryBuilder/utils';
  8. import {
  9. type ParseResultToken,
  10. Token,
  11. type TokenResult,
  12. } from 'sentry/components/searchSyntax/parser';
  13. import {space} from 'sentry/styles/space';
  14. import type {TagCollection} from 'sentry/types/group';
  15. import {getFieldDefinition} from 'sentry/utils/fields';
  16. export type FormattedQueryProps = {
  17. query: string;
  18. fieldDefinitionGetter?: FieldDefinitionGetter;
  19. filterKeys?: TagCollection;
  20. };
  21. type TokenProps = {
  22. token: ParseResultToken;
  23. };
  24. const EMPTY_FILTER_KEYS: TagCollection = {};
  25. function Filter({token}: {token: TokenResult<Token.FILTER>}) {
  26. return (
  27. <FilterWrapper aria-label={token.text}>
  28. <FilterKeyOperatorVisual token={token} />{' '}
  29. <FilterValue>
  30. <FilterValueText token={token} />
  31. </FilterValue>
  32. </FilterWrapper>
  33. );
  34. }
  35. function QueryToken({token}: TokenProps) {
  36. switch (token.type) {
  37. case Token.FILTER:
  38. return <Filter token={token} />;
  39. case Token.FREE_TEXT:
  40. if (token.value.trim()) {
  41. return <span>{token.value.trim()}</span>;
  42. }
  43. return null;
  44. case Token.L_PAREN:
  45. case Token.R_PAREN:
  46. return (
  47. <Boolean>
  48. <SearchQueryBuilderParenIcon token={token} />
  49. </Boolean>
  50. );
  51. case Token.LOGIC_BOOLEAN:
  52. return <Boolean>{token.text}</Boolean>;
  53. default:
  54. return null;
  55. }
  56. }
  57. /**
  58. * Renders a formatted query string similar to how it appears in the search bar,
  59. * but without all the interactivity.
  60. *
  61. * Accepts `filterKeys` and `fieldDefinitionGetter`, but is only necessary for
  62. * rendering some filter types such as dates.
  63. */
  64. export function FormattedQuery({
  65. query,
  66. fieldDefinitionGetter = getFieldDefinition,
  67. filterKeys = EMPTY_FILTER_KEYS,
  68. }: FormattedQueryProps) {
  69. const parsedQuery = useMemo(() => {
  70. return parseQueryBuilderValue(query, fieldDefinitionGetter, {filterKeys});
  71. }, [fieldDefinitionGetter, filterKeys, query]);
  72. if (!parsedQuery) {
  73. return <QueryWrapper />;
  74. }
  75. return (
  76. <QueryWrapper aria-label={query}>
  77. {parsedQuery.map((token, index) => {
  78. return <QueryToken key={index} token={token} />;
  79. })}
  80. </QueryWrapper>
  81. );
  82. }
  83. const QueryWrapper = styled('div')`
  84. display: flex;
  85. align-items: center;
  86. flex-wrap: wrap;
  87. row-gap: ${space(0.5)};
  88. column-gap: ${space(1)};
  89. `;
  90. const FilterWrapper = styled('div')`
  91. display: grid;
  92. grid-template-columns: auto 1fr;
  93. gap: ${space(0.5)};
  94. background: ${p => p.theme.background};
  95. padding: ${space(0.25)} ${space(0.5)};
  96. border: 1px solid ${p => p.theme.innerBorder};
  97. border-radius: ${p => p.theme.borderRadius};
  98. height: 24px;
  99. white-space: nowrap;
  100. overflow: hidden;
  101. `;
  102. const FilterValue = styled('div')`
  103. width: 100%;
  104. max-width: 300px;
  105. color: ${p => p.theme.purple400};
  106. ${p => p.theme.overflowEllipsis};
  107. `;
  108. const Boolean = styled('div')`
  109. display: flex;
  110. align-items: center;
  111. color: ${p => p.theme.subText};
  112. `;