spanSearchQueryBuilder.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import {useCallback, useMemo} from 'react';
  2. import {fetchSpanFieldValues} from 'sentry/actionCreators/tags';
  3. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  4. import {SearchQueryBuilder} from 'sentry/components/searchQueryBuilder';
  5. import type {CallbackSearchState} from 'sentry/components/searchQueryBuilder/types';
  6. import {t} from 'sentry/locale';
  7. import type {PageFilters} from 'sentry/types/core';
  8. import {SavedSearchType, type Tag, type TagCollection} from 'sentry/types/group';
  9. import {defined} from 'sentry/utils';
  10. import {isAggregateField, isMeasurement} from 'sentry/utils/discover/fields';
  11. import {
  12. type AggregationKey,
  13. ALLOWED_EXPLORE_VISUALIZE_AGGREGATES,
  14. DEVICE_CLASS_TAG_VALUES,
  15. FieldKind,
  16. getFieldDefinition,
  17. isDeviceClass,
  18. } from 'sentry/utils/fields';
  19. import useApi from 'sentry/utils/useApi';
  20. import useOrganization from 'sentry/utils/useOrganization';
  21. import usePageFilters from 'sentry/utils/usePageFilters';
  22. import {SPANS_FILTER_KEY_SECTIONS} from 'sentry/views/insights/constants';
  23. import {
  24. useSpanFieldCustomTags,
  25. useSpanFieldSupportedTags,
  26. } from 'sentry/views/performance/utils/useSpanFieldSupportedTags';
  27. interface SpanSearchQueryBuilderProps {
  28. initialQuery: string;
  29. searchSource: string;
  30. datetime?: PageFilters['datetime'];
  31. disableLoadingTags?: boolean;
  32. onSearch?: (query: string, state: CallbackSearchState) => void;
  33. placeholder?: string;
  34. projects?: PageFilters['projects'];
  35. }
  36. const getFunctionTags = (supportedAggregates?: AggregationKey[]) => {
  37. if (!supportedAggregates?.length) {
  38. return {};
  39. }
  40. return supportedAggregates.reduce((acc, item) => {
  41. acc[item] = {
  42. key: item,
  43. name: item,
  44. kind: FieldKind.FUNCTION,
  45. };
  46. return acc;
  47. }, {});
  48. };
  49. const getSpanFieldDefinition = (key: string, kind?: FieldKind) => {
  50. return getFieldDefinition(key, 'span', kind);
  51. };
  52. export function SpanSearchQueryBuilder({
  53. initialQuery,
  54. searchSource,
  55. datetime,
  56. onSearch,
  57. placeholder,
  58. projects,
  59. }: SpanSearchQueryBuilderProps) {
  60. const api = useApi();
  61. const organization = useOrganization();
  62. const {selection} = usePageFilters();
  63. const functionTags = useMemo(() => {
  64. return getFunctionTags();
  65. }, []);
  66. const placeholderText = useMemo(() => {
  67. return placeholder ?? t('Search for spans, users, tags, and more');
  68. }, [placeholder]);
  69. const {data: customTags} = useSpanFieldCustomTags({
  70. projects: projects ?? selection.projects,
  71. });
  72. const {data: supportedTags} = useSpanFieldSupportedTags({
  73. projects: projects ?? selection.projects,
  74. });
  75. const filterTags: TagCollection = useMemo(() => {
  76. return {...functionTags, ...supportedTags};
  77. }, [supportedTags, functionTags]);
  78. const filterKeySections = useMemo(() => {
  79. return [
  80. ...SPANS_FILTER_KEY_SECTIONS,
  81. {
  82. value: 'custom_fields',
  83. label: 'Custom Tags',
  84. children: Object.keys(customTags),
  85. },
  86. ];
  87. }, [customTags]);
  88. const getSpanFilterTagValues = useCallback(
  89. async (tag: Tag, queryString: string) => {
  90. if (isAggregateField(tag.key) || isMeasurement(tag.key)) {
  91. // We can't really auto suggest values for aggregate fields
  92. // or measurements, so we simply don't
  93. return Promise.resolve([]);
  94. }
  95. //
  96. // device.class is stored as "numbers" in snuba, but we want to suggest high, medium,
  97. // and low search filter values because discover maps device.class to these values.
  98. if (isDeviceClass(tag.key)) {
  99. return Promise.resolve(DEVICE_CLASS_TAG_VALUES);
  100. }
  101. try {
  102. const results = await fetchSpanFieldValues({
  103. api,
  104. orgSlug: organization.slug,
  105. fieldKey: tag.key,
  106. search: queryString,
  107. projectIds: projects?.map(String) ?? selection.projects?.map(String),
  108. endpointParams: normalizeDateTimeParams(datetime ?? selection.datetime),
  109. });
  110. return results.filter(({name}) => defined(name)).map(({name}) => name);
  111. } catch (e) {
  112. throw new Error(`Unable to fetch event field values: ${e}`);
  113. }
  114. },
  115. [api, organization, datetime, projects, selection.datetime, selection.projects]
  116. );
  117. return (
  118. <SearchQueryBuilder
  119. placeholder={placeholderText}
  120. filterKeys={filterTags}
  121. initialQuery={initialQuery}
  122. fieldDefinitionGetter={getSpanFieldDefinition}
  123. onSearch={onSearch}
  124. searchSource={searchSource}
  125. filterKeySections={filterKeySections}
  126. getTagValues={getSpanFilterTagValues}
  127. disallowUnsupportedFilters
  128. recentSearches={SavedSearchType.SPAN}
  129. showUnsubmittedIndicator
  130. />
  131. );
  132. }
  133. interface EAPSpanSearchQueryBuilderProps extends SpanSearchQueryBuilderProps {
  134. numberTags: TagCollection;
  135. stringTags: TagCollection;
  136. }
  137. export function EAPSpanSearchQueryBuilder({
  138. initialQuery,
  139. placeholder,
  140. onSearch,
  141. searchSource,
  142. numberTags,
  143. stringTags,
  144. }: EAPSpanSearchQueryBuilderProps) {
  145. const api = useApi();
  146. const organization = useOrganization();
  147. const {selection} = usePageFilters();
  148. const placeholderText = placeholder ?? t('Search for spans, users, tags, and more');
  149. const functionTags = useMemo(() => {
  150. return getFunctionTags(ALLOWED_EXPLORE_VISUALIZE_AGGREGATES);
  151. }, []);
  152. const tags = useMemo(() => {
  153. return {...functionTags, ...numberTags, ...stringTags};
  154. }, [numberTags, stringTags, functionTags]);
  155. const filterKeySections = useMemo(() => {
  156. const predefined = new Set(
  157. SPANS_FILTER_KEY_SECTIONS.flatMap(section => section.children)
  158. );
  159. return [
  160. ...SPANS_FILTER_KEY_SECTIONS,
  161. {
  162. value: 'custom_fields',
  163. label: 'Custom Tags',
  164. children: Object.keys(stringTags).filter(key => !predefined.has(key)),
  165. },
  166. ];
  167. }, [stringTags]);
  168. const getSpanFilterTagValues = useCallback(
  169. async (tag: Tag, queryString: string) => {
  170. if (isAggregateField(tag.key) || numberTags.hasOwnProperty(tag.key)) {
  171. // We can't really auto suggest values for aggregate fields
  172. // or measurements, so we simply don't
  173. return Promise.resolve([]);
  174. }
  175. try {
  176. const results = await fetchSpanFieldValues({
  177. api,
  178. orgSlug: organization.slug,
  179. fieldKey: tag.key,
  180. search: queryString,
  181. projectIds: selection.projects.map(String),
  182. endpointParams: normalizeDateTimeParams(selection.datetime),
  183. dataset: 'spans',
  184. });
  185. return results.filter(({name}) => defined(name)).map(({name}) => name);
  186. } catch (e) {
  187. throw new Error(`Unable to fetch event field values: ${e}`);
  188. }
  189. },
  190. [api, organization.slug, selection.projects, selection.datetime, numberTags]
  191. );
  192. return (
  193. <SearchQueryBuilder
  194. placeholder={placeholderText}
  195. filterKeys={tags}
  196. initialQuery={initialQuery}
  197. fieldDefinitionGetter={getSpanFieldDefinition}
  198. onSearch={onSearch}
  199. searchSource={searchSource}
  200. filterKeySections={filterKeySections}
  201. getTagValues={getSpanFilterTagValues}
  202. disallowUnsupportedFilters
  203. recentSearches={SavedSearchType.SPAN}
  204. showUnsubmittedIndicator
  205. />
  206. );
  207. }