123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- import {useCallback, useMemo} from 'react';
- import omit from 'lodash/omit';
- import {fetchSpanFieldValues, fetchTagValues} from 'sentry/actionCreators/tags';
- import {
- STATIC_FIELD_TAGS,
- STATIC_FIELD_TAGS_SET,
- STATIC_FIELD_TAGS_WITHOUT_ERROR_FIELDS,
- STATIC_FIELD_TAGS_WITHOUT_TRACING,
- STATIC_FIELD_TAGS_WITHOUT_TRANSACTION_FIELDS,
- STATIC_SEMVER_TAGS,
- STATIC_SPAN_TAGS,
- } from 'sentry/components/events/searchBarFieldConstants';
- import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
- import {SearchQueryBuilder} from 'sentry/components/searchQueryBuilder';
- import type {
- CallbackSearchState,
- FilterKeySection,
- } from 'sentry/components/searchQueryBuilder/types';
- import {t} from 'sentry/locale';
- import {SavedSearchType, type TagCollection} from 'sentry/types/group';
- import {defined} from 'sentry/utils';
- import type {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
- import type {Field} from 'sentry/utils/discover/fields';
- import {
- ALL_INSIGHTS_FILTER_KEY_SECTIONS,
- COMBINED_DATASET_FILTER_KEY_SECTIONS,
- ERRORS_DATASET_FILTER_KEY_SECTIONS,
- isAggregateField,
- isEquation,
- isMeasurement,
- parseFunction,
- } from 'sentry/utils/discover/fields';
- import {
- DiscoverDatasets,
- DiscoverDatasetsToDatasetMap,
- } from 'sentry/utils/discover/types';
- import {
- DEVICE_CLASS_TAG_VALUES,
- FieldKey,
- FieldKind,
- isDeviceClass,
- } from 'sentry/utils/fields';
- import type Measurements from 'sentry/utils/measurements/measurements';
- import {getMeasurements} from 'sentry/utils/measurements/measurements';
- import useApi from 'sentry/utils/useApi';
- import useOrganization from 'sentry/utils/useOrganization';
- import usePageFilters from 'sentry/utils/usePageFilters';
- import useTags from 'sentry/utils/useTags';
- import {isCustomMeasurement} from 'sentry/views/dashboards/utils';
- const getFunctionTags = (fields: Readonly<Field[]> | undefined) => {
- if (!fields?.length) {
- return [];
- }
- return fields.reduce((acc, item) => {
- if (
- !STATIC_FIELD_TAGS_SET.has(item.field) &&
- !isEquation(item.field) &&
- !isCustomMeasurement(item.field)
- ) {
- const parsedFunction = parseFunction(item.field);
- if (parsedFunction) {
- acc[parsedFunction.name] = {
- key: parsedFunction.name,
- name: parsedFunction.name,
- kind: FieldKind.FUNCTION,
- };
- }
- }
- return acc;
- }, {});
- };
- const getMeasurementTags = (
- measurements: Parameters<
- React.ComponentProps<typeof Measurements>['children']
- >[0]['measurements'],
- customMeasurements:
- | Parameters<React.ComponentProps<typeof Measurements>['children']>[0]['measurements']
- | undefined
- ) => {
- const measurementsWithKind = Object.keys(measurements).reduce((tags, key) => {
- tags[key] = {
- ...measurements[key],
- kind: FieldKind.MEASUREMENT,
- };
- return tags;
- }, {});
- if (!customMeasurements) {
- return measurementsWithKind;
- }
- return Object.keys(customMeasurements).reduce((tags, key) => {
- tags[key] = {
- ...customMeasurements[key],
- kind: FieldKind.MEASUREMENT,
- };
- return tags;
- }, measurementsWithKind);
- };
- export const getHasTag = (tags: TagCollection) => ({
- key: FieldKey.HAS,
- name: 'Has property',
- values: Object.keys(tags).sort((a, b) => {
- return a.toLowerCase().localeCompare(b.toLowerCase());
- }),
- predefined: true,
- kind: FieldKind.FIELD,
- });
- type Props = {
- customMeasurements?: CustomMeasurementCollection;
- dataset?: DiscoverDatasets;
- fields?: Readonly<Field[]>;
- includeSessionTagsValues?: boolean;
- includeTransactions?: boolean;
- omitTags?: string[];
- onChange?: (query: string, state: CallbackSearchState) => void;
- onSearch?: (query: string) => void;
- placeholder?: string;
- projectIds?: number[] | Readonly<number[]>;
- query?: string;
- searchSource?: string;
- supportedTags?: TagCollection | undefined;
- };
- const EXCLUDED_FILTER_KEYS = [FieldKey.ENVIRONMENT, FieldKey.TOTAL_COUNT];
- function ResultsSearchQueryBuilder(props: Props) {
- const {
- omitTags,
- fields,
- projectIds,
- includeSessionTagsValues,
- customMeasurements,
- dataset,
- includeTransactions = true,
- placeholder,
- } = props;
- const api = useApi();
- const organization = useOrganization();
- const {selection} = usePageFilters();
- const tags = useTags();
- const filteredTags = useMemo(() => {
- return omitTags && omitTags.length > 0
- ? omit(tags, omitTags, EXCLUDED_FILTER_KEYS)
- : omit(tags, EXCLUDED_FILTER_KEYS);
- }, [tags, omitTags]);
- const placeholderText = useMemo(() => {
- return placeholder ?? t('Search for events, users, tags, and more');
- }, [placeholder]);
- const measurements = useMemo(() => getMeasurements(), []);
- const functionTags = useMemo(() => getFunctionTags(fields), [fields]);
- // Returns array of tag values that substring match `query`; invokes `callback`
- // with data when ready
- const getEventFieldValues = useCallback(
- async (tag, query): Promise<string[]> => {
- const projectIdStrings = (projectIds as Readonly<number>[])?.map(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);
- }
- const fetchPromise =
- dataset === DiscoverDatasets.SPANS_INDEXED
- ? fetchSpanFieldValues({
- api,
- endpointParams: normalizeDateTimeParams(selection.datetime),
- orgSlug: organization.slug,
- fieldKey: tag.key,
- search: query,
- projectIds: projectIdStrings,
- })
- : fetchTagValues({
- api,
- endpointParams: normalizeDateTimeParams(selection.datetime),
- orgSlug: organization.slug,
- tagKey: tag.key,
- search: query,
- projectIds: projectIdStrings,
- // allows searching for tags on transactions as well
- includeTransactions: includeTransactions,
- // allows searching for tags on sessions as well
- includeSessions: includeSessionTagsValues,
- dataset: dataset ? DiscoverDatasetsToDatasetMap[dataset] : undefined,
- });
- try {
- const results = await fetchPromise;
- return results.filter(({name}) => defined(name)).map(({name}) => name);
- } catch (error) {
- throw new Error('Unable to fetch event field values');
- }
- },
- [
- api,
- organization,
- selection.datetime,
- projectIds,
- includeTransactions,
- includeSessionTagsValues,
- dataset,
- ]
- );
- const getTagList: TagCollection = useMemo(() => {
- const measurementsWithKind = getMeasurementTags(measurements, customMeasurements);
- const orgHasPerformanceView = organization.features.includes('performance-view');
- const combinedTags: TagCollection =
- dataset === DiscoverDatasets.ERRORS
- ? Object.assign({}, functionTags, STATIC_FIELD_TAGS_WITHOUT_TRANSACTION_FIELDS)
- : dataset === DiscoverDatasets.TRANSACTIONS ||
- dataset === DiscoverDatasets.METRICS_ENHANCED
- ? Object.assign(
- {},
- measurementsWithKind,
- functionTags,
- STATIC_SPAN_TAGS,
- STATIC_FIELD_TAGS_WITHOUT_ERROR_FIELDS
- )
- : orgHasPerformanceView
- ? Object.assign(
- {},
- measurementsWithKind,
- functionTags,
- STATIC_SPAN_TAGS,
- STATIC_FIELD_TAGS
- )
- : Object.assign({}, STATIC_FIELD_TAGS_WITHOUT_TRACING);
- Object.assign(combinedTags, filteredTags, STATIC_SEMVER_TAGS);
- combinedTags.has = getHasTag(combinedTags);
- return combinedTags;
- }, [
- measurements,
- dataset,
- customMeasurements,
- functionTags,
- filteredTags,
- organization.features,
- ]);
- const filterKeySections = useMemo(() => {
- const customTagsSection: FilterKeySection = {
- value: 'custom_fields',
- label: 'Custom Tags',
- children: Object.keys(filteredTags),
- };
- if (
- dataset === DiscoverDatasets.TRANSACTIONS ||
- dataset === DiscoverDatasets.METRICS_ENHANCED
- ) {
- return [...ALL_INSIGHTS_FILTER_KEY_SECTIONS, customTagsSection];
- }
- if (dataset === DiscoverDatasets.ERRORS) {
- return [...ERRORS_DATASET_FILTER_KEY_SECTIONS, customTagsSection];
- }
- return [...COMBINED_DATASET_FILTER_KEY_SECTIONS, customTagsSection];
- }, [filteredTags, dataset]);
- return (
- <SearchQueryBuilder
- placeholder={placeholderText}
- filterKeys={getTagList}
- initialQuery={props.query ?? ''}
- onSearch={props.onSearch}
- onChange={props.onChange}
- searchSource={props.searchSource || 'eventsv2'}
- filterKeySections={filterKeySections}
- getTagValues={getEventFieldValues}
- recentSearches={SavedSearchType.EVENT}
- showUnsubmittedIndicator
- />
- );
- }
- export default ResultsSearchQueryBuilder;
|