metricSearchBar.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import {useCallback, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import memoize from 'lodash/memoize';
  4. import {BooleanOperator} from 'sentry/components/searchSyntax/parser';
  5. import SmartSearchBar, {SmartSearchBarProps} from 'sentry/components/smartSearchBar';
  6. import {t} from 'sentry/locale';
  7. import {MRI, SavedSearchType, TagCollection} from 'sentry/types';
  8. import {getUseCaseFromMRI} from 'sentry/utils/metrics/mri';
  9. import {useMetricsTags} from 'sentry/utils/metrics/useMetricsTags';
  10. import useApi from 'sentry/utils/useApi';
  11. import useOrganization from 'sentry/utils/useOrganization';
  12. import usePageFilters from 'sentry/utils/usePageFilters';
  13. interface MetricSearchBarProps extends Partial<SmartSearchBarProps> {
  14. onChange: (value: string) => void;
  15. projectIds: string[];
  16. disabled?: boolean;
  17. mri?: MRI;
  18. query?: string;
  19. }
  20. const EMPTY_ARRAY = [];
  21. const EMPTY_SET = new Set<never>();
  22. const DISSALLOWED_LOGICAL_OPERATORS = new Set([BooleanOperator.OR]);
  23. export function MetricSearchBar({
  24. mri,
  25. disabled,
  26. onChange,
  27. query,
  28. projectIds,
  29. ...props
  30. }: MetricSearchBarProps) {
  31. const org = useOrganization();
  32. const api = useApi();
  33. const {selection} = usePageFilters();
  34. const projectIdNumbers = useMemo(
  35. () => projectIds.map(id => parseInt(id, 10)),
  36. [projectIds]
  37. );
  38. const {data: tags = EMPTY_ARRAY, isLoading} = useMetricsTags(mri, projectIdNumbers);
  39. const supportedTags: TagCollection = useMemo(
  40. () => tags.reduce((acc, tag) => ({...acc, [tag.key]: tag}), {}),
  41. [tags]
  42. );
  43. const fetchTagValues = useMemo(() => {
  44. const fn = memoize((tagKey: string) => {
  45. // clear response from cache after 10 seconds
  46. setTimeout(() => {
  47. fn.cache.delete(tagKey);
  48. }, 10000);
  49. return api.requestPromise(`/organizations/${org.slug}/metrics/tags/${tagKey}/`, {
  50. query: {
  51. metric: mri,
  52. useCase: getUseCaseFromMRI(mri),
  53. project: selection.projects,
  54. },
  55. });
  56. });
  57. return fn;
  58. }, [api, mri, org.slug, selection.projects]);
  59. const getTagValues = useCallback(
  60. async (tag: any, search: string) => {
  61. const tagsValues = await fetchTagValues(tag.key);
  62. return tagsValues
  63. .filter(
  64. tv =>
  65. tv.value !== '' &&
  66. tv.value.toLocaleLowerCase().includes(search.toLocaleLowerCase())
  67. )
  68. .map(tv => tv.value);
  69. },
  70. [fetchTagValues]
  71. );
  72. const handleChange = useCallback(
  73. (value: string, {validSearch} = {validSearch: true}) => {
  74. if (validSearch) {
  75. onChange(value);
  76. }
  77. },
  78. [onChange]
  79. );
  80. return (
  81. <WideSearchBar
  82. disabled={disabled}
  83. maxMenuHeight={220}
  84. organization={org}
  85. onGetTagValues={getTagValues}
  86. supportedTags={supportedTags}
  87. // don't highlight tags while loading as we don't know yet if they are supported
  88. highlightUnsupportedTags={!isLoading}
  89. disallowedLogicalOperators={DISSALLOWED_LOGICAL_OPERATORS}
  90. disallowFreeText
  91. onClose={handleChange}
  92. onSearch={handleChange}
  93. placeholder={t('Filter by tags')}
  94. query={query}
  95. savedSearchType={SavedSearchType.METRIC}
  96. durationKeys={EMPTY_SET}
  97. percentageKeys={EMPTY_SET}
  98. numericKeys={EMPTY_SET}
  99. dateKeys={EMPTY_SET}
  100. booleanKeys={EMPTY_SET}
  101. sizeKeys={EMPTY_SET}
  102. textOperatorKeys={EMPTY_SET}
  103. {...props}
  104. />
  105. );
  106. }
  107. const WideSearchBar = styled(SmartSearchBar)`
  108. width: 100%;
  109. opacity: ${p => (p.disabled ? '0.6' : '1')};
  110. `;