diff --git a/static/app/components/metrics/metricSearchBar.tsx b/static/app/components/metrics/metricSearchBar.tsx index 8e39cc11dfbdd..dd80cf5178613 100644 --- a/static/app/components/metrics/metricSearchBar.tsx +++ b/static/app/components/metrics/metricSearchBar.tsx @@ -1,14 +1,21 @@ import {useCallback, useMemo} from 'react'; -import {css} from '@emotion/react'; +import {css, type SerializedStyles} from '@emotion/react'; import {useId} from '@react-aria/utils'; import {QueryFieldGroup} from 'sentry/components/metrics/queryFieldGroup'; +import { + SearchQueryBuilder, + type SearchQueryBuilderProps, +} from 'sentry/components/searchQueryBuilder'; import type {SmartSearchBarProps} from 'sentry/components/smartSearchBar'; import SmartSearchBar from 'sentry/components/smartSearchBar'; import {t} from 'sentry/locale'; import {SavedSearchType, type TagCollection} from 'sentry/types/group'; import type {MRI} from 'sentry/types/metrics'; -import {hasMetricsNewInputs} from 'sentry/utils/metrics/features'; +import { + hasMetricsNewInputs, + hasMetricsNewSearchQueryBuilder, +} from 'sentry/utils/metrics/features'; import {getUseCaseFromMRI} from 'sentry/utils/metrics/mri'; import {useMetricsTags} from 'sentry/utils/metrics/useMetricsTags'; import useApi from 'sentry/utils/useApi'; @@ -40,7 +47,7 @@ export function MetricSearchBar({ id: idProp, ...props }: MetricSearchBarProps) { - const org = useOrganization(); + const organization = useOrganization(); const api = useApi(); const {selection} = usePageFilters(); const selectedProjects = useSelectedProjects(); @@ -82,16 +89,19 @@ export function MetricSearchBar({ const fetchTagValues = useCallback( (tagKey: string, search: string) => { - return api.requestPromise(`/organizations/${org.slug}/metrics/tags/${tagKey}/`, { - query: { - prefix: search, - metric: mri, - useCase: getUseCaseFromMRI(mri), - project: selection.projects, - }, - }); + return api.requestPromise( + `/organizations/${organization.slug}/metrics/tags/${tagKey}/`, + { + query: { + prefix: search, + metric: mri, + useCase: getUseCaseFromMRI(mri), + project: selection.projects, + }, + } + ); }, - [api, mri, org.slug, selection.projects] + [api, mri, organization.slug, selection.projects] ); const getTagValues = useCallback( @@ -118,47 +128,52 @@ export function MetricSearchBar({ [onChange, searchConfig] ); - if (hasMetricsNewInputs(org)) { - return ( - - ); + const searchQueryBuilderProps: SearchQueryBuilderProps & {css: SerializedStyles} = { + disabled, + onChange: handleChange, + placeholder: t('Filter by tags'), + initialQuery: query ?? '', + getTagValues, + recentSearches: SavedSearchType.METRIC, + // don't highlight tags while loading as we don't know yet if they are supported + disallowUnsupportedFilters: !isLoading, + filterKeys: searchConfig.supportedTags, + disallowFreeText: searchConfig.disallowFreeText, + searchSource: props.searchSource ?? 'metrics', + css: wideSearchBarCss(disabled), + }; + + const smartSearchProps: Partial & {css: SerializedStyles} = { + id, + disabled, + maxMenuHeight: 220, + organization, + onGetTagValues: getTagValues, + // don't highlight tags while loading as we don't know yet if they are supported + highlightUnsupportedTags: !isLoading, + onClose: handleChange, + onSearch: handleChange, + placeholder: t('Filter by tags'), + query, + savedSearchType: SavedSearchType.METRIC, + css: wideSearchBarCss(disabled), + ...props, + ...searchConfig, + }; + + if (hasMetricsNewInputs(organization)) { + if (hasMetricsNewSearchQueryBuilder(organization)) { + return ; + } + + return ; } - return ( - - ); + if (hasMetricsNewSearchQueryBuilder(organization)) { + return ; + } + + return ; } function wideSearchBarCss(disabled?: boolean) { diff --git a/static/app/components/metrics/queryFieldGroup.tsx b/static/app/components/metrics/queryFieldGroup.tsx index e0e55b97ad580..3bdef0ebe16a4 100644 --- a/static/app/components/metrics/queryFieldGroup.tsx +++ b/static/app/components/metrics/queryFieldGroup.tsx @@ -1,3 +1,4 @@ +import type {Theme} from '@emotion/react'; import {css, useTheme} from '@emotion/react'; import styled from '@emotion/styled'; @@ -10,6 +11,7 @@ import { type SingleSelectProps, } from 'sentry/components/compactSelect'; import {DebouncedInput as _DebouncedInput} from 'sentry/components/modals/metricWidgetViewerModal/queries'; +import {SearchQueryBuilder as _SearchQueryBuilder} from 'sentry/components/searchQueryBuilder'; import _SmartSearchBar from 'sentry/components/smartSearchBar'; import {Tooltip} from 'sentry/components/tooltip'; import {SLOW_TOOLTIP_DELAY} from 'sentry/constants'; @@ -97,17 +99,25 @@ const ComboBox = styled(_ComboBox)` } `; -const SmartSearchBar = styled(_SmartSearchBar)` +const searchCss = (theme: Theme) => css` border-radius: 0; :last-child { - border-radius: 0 ${p => p.theme.borderRadius} ${p => p.theme.borderRadius} 0; + border-radius: 0 ${theme.borderRadius} ${theme.borderRadius} 0; } label { - color: ${p => p.theme.gray500}; + color: ${theme.gray500}; } `; +const SmartSearchBar = styled(_SmartSearchBar)` + ${p => searchCss(p.theme)} +`; + +const SearchQueryBuilder = styled(_SearchQueryBuilder)` + ${p => searchCss(p.theme)} +`; + const FieldGroup = styled('div')` flex-grow: 1; @@ -155,5 +165,6 @@ QueryFieldGroup.Label = Label; QueryFieldGroup.CompactSelect = CompactSelect; QueryFieldGroup.ComboBox = ComboBox; QueryFieldGroup.SmartSearchBar = SmartSearchBar; +QueryFieldGroup.SearchQueryBuilder = SearchQueryBuilder; QueryFieldGroup.DebouncedInput = DebouncedInput; QueryFieldGroup.DeleteButton = DeleteButton; diff --git a/static/app/utils/metrics/features.tsx b/static/app/utils/metrics/features.tsx index 4f11ff5ca3b21..c2682ebfe556f 100644 --- a/static/app/utils/metrics/features.tsx +++ b/static/app/utils/metrics/features.tsx @@ -28,6 +28,10 @@ export function hasMetricsNewInputs(organization: Organization) { return organization.features.includes('metrics-new-inputs'); } +export function hasMetricsNewSearchQueryBuilder(organization: Organization) { + return organization.features.includes('search-query-builder-metrics'); +} + /** * Returns the forceMetricsLayer query param for the alert * wrapped in an object so it can be spread into existing query params