metricsSearchBar.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import {useEffect, useState} from 'react';
  2. import {ClassNames} from '@emotion/react';
  3. import memoize from 'lodash/memoize';
  4. import {addErrorMessage} from 'sentry/actionCreators/indicator';
  5. import SmartSearchBar from 'sentry/components/smartSearchBar';
  6. import {NEGATION_OPERATOR, SEARCH_WILDCARD} from 'sentry/constants';
  7. import {t} from 'sentry/locale';
  8. import {MetricsTag, MetricsTagValue, Organization, Tag} from 'sentry/types';
  9. import useApi from 'sentry/utils/useApi';
  10. const SEARCH_SPECIAL_CHARS_REGEXP = new RegExp(
  11. `^${NEGATION_OPERATOR}|\\${SEARCH_WILDCARD}`,
  12. 'g'
  13. );
  14. type Props = Pick<
  15. React.ComponentProps<typeof SmartSearchBar>,
  16. 'onSearch' | 'onBlur' | 'query' | 'maxQueryLength' | 'searchSource'
  17. > & {
  18. orgSlug: Organization['slug'];
  19. projectIds: number[] | readonly number[];
  20. className?: string;
  21. };
  22. function MetricsSearchBar({
  23. orgSlug,
  24. onSearch,
  25. onBlur,
  26. maxQueryLength,
  27. searchSource,
  28. projectIds,
  29. className,
  30. ...props
  31. }: Props) {
  32. const api = useApi();
  33. const [tags, setTags] = useState<MetricsTag[]>([]);
  34. useEffect(() => {
  35. fetchTags();
  36. }, [projectIds]);
  37. async function fetchTags() {
  38. try {
  39. const response = await api.requestPromise(
  40. `/organizations/${orgSlug}/metrics/tags/`,
  41. {
  42. query: {
  43. project: !projectIds.length ? undefined : projectIds,
  44. },
  45. }
  46. );
  47. setTags(response);
  48. } catch {
  49. addErrorMessage(t('Unable to fetch search bar tags'));
  50. }
  51. }
  52. /**
  53. * Prepare query string (e.g. strip special characters like negation operator)
  54. */
  55. function prepareQuery(query: string) {
  56. return query.replace(SEARCH_SPECIAL_CHARS_REGEXP, '');
  57. }
  58. function fetchTagValues(tagKey: string) {
  59. return api.requestPromise(`/organizations/${orgSlug}/metrics/tags/${tagKey}/`, {
  60. query: {project: projectIds},
  61. });
  62. }
  63. function getTagValues(tag: Tag, _query: string): Promise<string[]> {
  64. return fetchTagValues(tag.key).then(
  65. tagValues => (tagValues as MetricsTagValue[]).map(({value}) => value),
  66. () => {
  67. throw new Error('Unable to fetch tag values');
  68. }
  69. );
  70. }
  71. const supportedTags = tags.reduce((acc, {key}) => {
  72. acc[key] = {key, name: key};
  73. return acc;
  74. }, {});
  75. return (
  76. <ClassNames>
  77. {({css}) => (
  78. <SmartSearchBar
  79. onGetTagValues={memoize(getTagValues, ({key}, query) => `${key}-${query}`)}
  80. supportedTags={supportedTags}
  81. prepareQuery={prepareQuery}
  82. excludeEnvironment
  83. dropdownClassName={css`
  84. max-height: 300px;
  85. overflow-y: auto;
  86. `}
  87. onSearch={onSearch}
  88. onBlur={onBlur}
  89. maxQueryLength={maxQueryLength}
  90. searchSource={searchSource}
  91. className={className}
  92. query={props.query}
  93. hasRecentSearches
  94. />
  95. )}
  96. </ClassNames>
  97. );
  98. }
  99. export default MetricsSearchBar;