|
@@ -0,0 +1,101 @@
|
|
|
+import {useCallback, useMemo} from 'react';
|
|
|
+
|
|
|
+import {fetchSpanFieldValues} from 'sentry/actionCreators/tags';
|
|
|
+import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
|
|
|
+import {SearchQueryBuilder} from 'sentry/components/searchQueryBuilder';
|
|
|
+import type {CallbackSearchState} from 'sentry/components/searchQueryBuilder/types';
|
|
|
+import {t} from 'sentry/locale';
|
|
|
+import type {PageFilters} from 'sentry/types/core';
|
|
|
+import {SavedSearchType, type Tag, type TagCollection} from 'sentry/types/group';
|
|
|
+import {defined} from 'sentry/utils';
|
|
|
+import {isAggregateField, isMeasurement} from 'sentry/utils/discover/fields';
|
|
|
+import {DEVICE_CLASS_TAG_VALUES, isDeviceClass} from 'sentry/utils/fields';
|
|
|
+import useApi from 'sentry/utils/useApi';
|
|
|
+import useOrganization from 'sentry/utils/useOrganization';
|
|
|
+import usePageFilters from 'sentry/utils/usePageFilters';
|
|
|
+import {useSpanFieldSupportedTags} from 'sentry/views/performance/utils/useSpanFieldSupportedTags';
|
|
|
+
|
|
|
+interface SpanSearchQueryBuilderProps {
|
|
|
+ initialQuery: string;
|
|
|
+ searchSource: string;
|
|
|
+ datetime?: PageFilters['datetime'];
|
|
|
+ disableLoadingTags?: boolean;
|
|
|
+ onSearch?: (query: string, state: CallbackSearchState) => void;
|
|
|
+ placeholder?: string;
|
|
|
+ projects?: PageFilters['projects'];
|
|
|
+}
|
|
|
+
|
|
|
+export function SpanSearchQueryBuilder({
|
|
|
+ initialQuery,
|
|
|
+ searchSource,
|
|
|
+ datetime,
|
|
|
+ onSearch,
|
|
|
+ placeholder,
|
|
|
+ projects,
|
|
|
+}: SpanSearchQueryBuilderProps) {
|
|
|
+ const api = useApi();
|
|
|
+ const organization = useOrganization();
|
|
|
+ const {selection} = usePageFilters();
|
|
|
+
|
|
|
+ const placeholderText = useMemo(() => {
|
|
|
+ return placeholder ?? t('Search for spans, users, tags, and more');
|
|
|
+ }, [placeholder]);
|
|
|
+
|
|
|
+ const supportedTags = useSpanFieldSupportedTags({
|
|
|
+ projects: projects ?? selection.projects,
|
|
|
+ });
|
|
|
+
|
|
|
+ const filterTags: TagCollection = useMemo(() => {
|
|
|
+ return {...supportedTags};
|
|
|
+ }, [supportedTags]);
|
|
|
+
|
|
|
+ const filterKeySections = useMemo(() => {
|
|
|
+ return [];
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const getSpanFilterTagValues = useCallback(
|
|
|
+ async (tag: Tag, queryString: string) => {
|
|
|
+ if (isAggregateField(tag.key) || isMeasurement(tag.key)) {
|
|
|
+ // We can't really auto suggest values for aggregate fields
|
|
|
+ // or measurements, so we simply don't
|
|
|
+ return Promise.resolve([]);
|
|
|
+ }
|
|
|
+ //
|
|
|
+ // device.class is stored as "numbers" in snuba, but we want to suggest high, medium,
|
|
|
+ // and low search filter values because discover maps device.class to these values.
|
|
|
+ if (isDeviceClass(tag.key)) {
|
|
|
+ return Promise.resolve(DEVICE_CLASS_TAG_VALUES);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const results = await fetchSpanFieldValues({
|
|
|
+ api,
|
|
|
+ orgSlug: organization.slug,
|
|
|
+ fieldKey: tag.key,
|
|
|
+ search: queryString,
|
|
|
+ projectIds: projects?.map(String) ?? selection.projects?.map(String),
|
|
|
+ endpointParams: normalizeDateTimeParams(datetime ?? selection.datetime),
|
|
|
+ });
|
|
|
+ return results.filter(({name}) => defined(name)).map(({name}) => name);
|
|
|
+ } catch (e) {
|
|
|
+ throw new Error(`Unable to fetch event field values: ${e}`);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ [api, organization, datetime, projects, selection.datetime, selection.projects]
|
|
|
+ );
|
|
|
+
|
|
|
+ return (
|
|
|
+ <SearchQueryBuilder
|
|
|
+ placeholder={placeholderText}
|
|
|
+ filterKeys={filterTags}
|
|
|
+ initialQuery={initialQuery}
|
|
|
+ onSearch={onSearch}
|
|
|
+ searchSource={searchSource}
|
|
|
+ filterKeySections={filterKeySections}
|
|
|
+ getTagValues={getSpanFilterTagValues}
|
|
|
+ disallowFreeText
|
|
|
+ disallowUnsupportedFilters
|
|
|
+ recentSearches={SavedSearchType.EVENT}
|
|
|
+ />
|
|
|
+ );
|
|
|
+}
|