metricsSearchBar.tsx 3.0 KB

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