From 9cbb94a841afcadb3cb53fcd4cf6b443c2a3de89 Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Wed, 5 Nov 2025 17:15:51 -0500 Subject: [PATCH 1/2] retrieve and display predefined values of a tag if it has any --- .../tokens/filter/valueCombobox.tsx | 2 +- .../globalFilter/filterSelector.tsx | 48 ++++++++++++++++++- .../views/dashboards/globalFilter/utils.tsx | 14 ++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/static/app/components/searchQueryBuilder/tokens/filter/valueCombobox.tsx b/static/app/components/searchQueryBuilder/tokens/filter/valueCombobox.tsx index ca21492a1a957d..e028933cc9ddef 100644 --- a/static/app/components/searchQueryBuilder/tokens/filter/valueCombobox.tsx +++ b/static/app/components/searchQueryBuilder/tokens/filter/valueCombobox.tsx @@ -181,7 +181,7 @@ function getSuggestionDescription(group: SearchGroup | SearchItem) { return undefined; } -function getPredefinedValues({ +export function getPredefinedValues({ fieldDefinition, key, filterValue, diff --git a/static/app/views/dashboards/globalFilter/filterSelector.tsx b/static/app/views/dashboards/globalFilter/filterSelector.tsx index bf0b30df3f2392..bec1f15f45fafe 100644 --- a/static/app/views/dashboards/globalFilter/filterSelector.tsx +++ b/static/app/views/dashboards/globalFilter/filterSelector.tsx @@ -6,6 +6,7 @@ import {Flex} from '@sentry/scraps/layout'; import {Button} from 'sentry/components/core/button'; import {HybridFilter} from 'sentry/components/organizations/hybridFilter'; +import {getPredefinedValues} from 'sentry/components/searchQueryBuilder/tokens/filter/valueCombobox'; import {MutableSearch} from 'sentry/components/searchSyntax/mutableSearch'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; @@ -15,6 +16,10 @@ import usePageFilters from 'sentry/utils/usePageFilters'; import {type SearchBarData} from 'sentry/views/dashboards/datasetConfig/base'; import {getDatasetLabel} from 'sentry/views/dashboards/globalFilter/addFilter'; import FilterSelectorTrigger from 'sentry/views/dashboards/globalFilter/filterSelectorTrigger'; +import { + getFieldDefinitionForDataset, + getFilterToken, +} from 'sentry/views/dashboards/globalFilter/utils'; import type {GlobalFilter} from 'sentry/views/dashboards/types'; type FilterSelectorProps = { @@ -48,6 +53,35 @@ function FilterSelector({ const {dataset, tag} = globalFilter; const {selection} = usePageFilters(); + // Retrieve full tag definition to check if it has predefined values + const datasetFilterKeys = searchBarData.getFilterKeys(); + const fullTag = datasetFilterKeys[tag.key]; + const fieldDefinition = getFieldDefinitionForDataset(tag, dataset); + + const filterToken = useMemo( + () => getFilterToken(globalFilter, fieldDefinition), + [globalFilter, fieldDefinition] + ); + + // Retrieve predefined values if the tag has any + const predefinedValues = useMemo(() => { + if (!filterToken) { + return null; + } + const filterValue = filterToken.value.text; + return getPredefinedValues({ + key: fullTag, + filterValue, + token: filterToken, + fieldDefinition, + }); + }, [fullTag, filterToken, fieldDefinition]); + + // Only fetch values if the tag has no predefined values + const shouldFetchValues = fullTag + ? !fullTag.predefined && predefinedValues === null + : true; + const baseQueryKey = useMemo( () => ['global-dashboard-filters-tag-values', tag, selection, searchQuery], [tag, selection, searchQuery] @@ -63,7 +97,7 @@ function FilterSelector({ return result ?? []; }, placeholderData: keepPreviousData, - enabled: true, + enabled: shouldFetchValues, staleTime: 5 * 60 * 1000, }); @@ -75,6 +109,10 @@ function FilterSelector({ // Filter values fetched using getTagValues fetchedFilterValues?.forEach(addOption); + // Predefined values + predefinedValues?.forEach(suggestionSection => { + suggestionSection.suggestions.forEach(suggestion => addOption(suggestion.value)); + }); // Filter values in the global filter activeFilterValues.forEach(addOption); // Staged filter values inside the filter selector @@ -89,7 +127,13 @@ function FilterSelector({ // and avoid losing their original order from the fetched results // (e.g. without this, all staged values would be grouped at the top of the list) return Array.from(optionMap.values()).reverse(); - }, [fetchedFilterValues, activeFilterValues, stagedFilterValues, searchQuery]); + }, [ + fetchedFilterValues, + predefinedValues, + activeFilterValues, + stagedFilterValues, + searchQuery, + ]); const handleChange = (opts: string[]) => { if (isEqual(opts, activeFilterValues)) { diff --git a/static/app/views/dashboards/globalFilter/utils.tsx b/static/app/views/dashboards/globalFilter/utils.tsx index 7d80c2e0213428..6510ddc6035028 100644 --- a/static/app/views/dashboards/globalFilter/utils.tsx +++ b/static/app/views/dashboards/globalFilter/utils.tsx @@ -4,6 +4,7 @@ import { } from 'sentry/components/searchQueryBuilder/hooks/useQueryBuilderState'; import {getFilterValueType} from 'sentry/components/searchQueryBuilder/tokens/filter/utils'; import {cleanFilterValue} from 'sentry/components/searchQueryBuilder/tokens/filter/valueSuggestions/utils'; +import {getInitialFilterText} from 'sentry/components/searchQueryBuilder/tokens/utils'; import {parseQueryBuilderValue} from 'sentry/components/searchQueryBuilder/utils'; import { TermOperator, @@ -54,6 +55,19 @@ export function parseFilterValue( return parsedResult.filter(token => token.type === Token.FILTER); } +export function getFilterToken( + globalFilter: GlobalFilter, + fieldDefinition: FieldDefinition | null +) { + const {tag, value} = globalFilter; + let filterValue = value; + if (value === '') { + filterValue = getInitialFilterText(tag.key, fieldDefinition, false); + } + const filterTokens = parseFilterValue(filterValue, globalFilter); + return filterTokens[0] ?? null; +} + export function isValidNumericFilterValue( value: string, filterToken: TokenResult, From 03166bade1739239eb195965c2d9924fa9b1a08d Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Fri, 7 Nov 2025 12:44:48 -0500 Subject: [PATCH 2/2] fix filter value sorting --- .../globalFilter/filterSelector.tsx | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/static/app/views/dashboards/globalFilter/filterSelector.tsx b/static/app/views/dashboards/globalFilter/filterSelector.tsx index bec1f15f45fafe..c3ff2fd2f4aa90 100644 --- a/static/app/views/dashboards/globalFilter/filterSelector.tsx +++ b/static/app/views/dashboards/globalFilter/filterSelector.tsx @@ -2,6 +2,7 @@ import {useEffect, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import isEqual from 'lodash/isEqual'; +import type {SelectOption} from '@sentry/scraps/compactSelect'; import {Flex} from '@sentry/scraps/layout'; import {Button} from 'sentry/components/core/button'; @@ -104,29 +105,34 @@ function FilterSelector({ const {data: fetchedFilterValues, isFetching} = queryResult; const options = useMemo(() => { - const optionMap = new Map(); - const addOption = (value: string) => optionMap.set(value, {label: value, value}); + const optionMap = new Map>(); + const fixedOptionMap = new Map>(); + const addOption = (value: string, map: Map>) => + map.set(value, {label: value, value}); + + // Filter values in the global filter + activeFilterValues.forEach(value => addOption(value, optionMap)); - // Filter values fetched using getTagValues - fetchedFilterValues?.forEach(addOption); // Predefined values predefinedValues?.forEach(suggestionSection => { - suggestionSection.suggestions.forEach(suggestion => addOption(suggestion.value)); + suggestionSection.suggestions.forEach(suggestion => + addOption(suggestion.value, optionMap) + ); }); - // Filter values in the global filter - activeFilterValues.forEach(addOption); - // Staged filter values inside the filter selector - stagedFilterValues.forEach(addOption); + // Filter values fetched using getTagValues + fetchedFilterValues?.forEach(value => addOption(value, optionMap)); // Allow setting a custom filter value based on search input - if (searchQuery) { - addOption(searchQuery); + if (searchQuery && !optionMap.has(searchQuery)) { + addOption(searchQuery, fixedOptionMap); } - - // Reversing the order allows effectively deduplicating the values - // and avoid losing their original order from the fetched results - // (e.g. without this, all staged values would be grouped at the top of the list) - return Array.from(optionMap.values()).reverse(); + // Staged filter values inside the filter selector + stagedFilterValues.forEach(value => { + if (!optionMap.has(value)) { + addOption(value, fixedOptionMap); + } + }); + return [...Array.from(fixedOptionMap.values()), ...Array.from(optionMap.values())]; }, [ fetchedFilterValues, predefinedValues, @@ -170,7 +176,8 @@ function FilterSelector({ onStagedValueChange={value => { setStagedFilterValues(value); }} - sizeLimit={10} + sizeLimit={30} + menuWidth={400} onClose={() => { setSearchQuery(''); setStagedFilterValues([]);