Skip to content

Commit 7614cc0

Browse files
authored
feat(dashboards): Include predefined values in the global filter selector (#102822)
Some tags have predefined values. This PR adds a check for predefined values and adds them to the global filter selector options if there are any. If predefined values exist, they should be used and tag values should not be fetched. Fixes [DAIN-1008](https://linear.app/getsentry/issue/DAIN-1008/assigned-assigned-or-suggested-etc-dont-work-as-global-filters)
1 parent f6ec844 commit 7614cc0

File tree

3 files changed

+83
-18
lines changed

3 files changed

+83
-18
lines changed

static/app/components/searchQueryBuilder/tokens/filter/valueCombobox.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ function getSuggestionDescription(group: SearchGroup | SearchItem) {
181181
return undefined;
182182
}
183183

184-
function getPredefinedValues({
184+
export function getPredefinedValues({
185185
fieldDefinition,
186186
key,
187187
filterValue,

static/app/views/dashboards/globalFilter/filterSelector.tsx

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import {useEffect, useMemo, useState} from 'react';
22
import styled from '@emotion/styled';
33
import isEqual from 'lodash/isEqual';
44

5+
import type {SelectOption} from '@sentry/scraps/compactSelect';
56
import {Flex} from '@sentry/scraps/layout';
67

78
import {Button} from 'sentry/components/core/button';
89
import {HybridFilter} from 'sentry/components/organizations/hybridFilter';
10+
import {getPredefinedValues} from 'sentry/components/searchQueryBuilder/tokens/filter/valueCombobox';
911
import {MutableSearch} from 'sentry/components/searchSyntax/mutableSearch';
1012
import {t} from 'sentry/locale';
1113
import {space} from 'sentry/styles/space';
@@ -15,6 +17,10 @@ import usePageFilters from 'sentry/utils/usePageFilters';
1517
import {type SearchBarData} from 'sentry/views/dashboards/datasetConfig/base';
1618
import {getDatasetLabel} from 'sentry/views/dashboards/globalFilter/addFilter';
1719
import FilterSelectorTrigger from 'sentry/views/dashboards/globalFilter/filterSelectorTrigger';
20+
import {
21+
getFieldDefinitionForDataset,
22+
getFilterToken,
23+
} from 'sentry/views/dashboards/globalFilter/utils';
1824
import type {GlobalFilter} from 'sentry/views/dashboards/types';
1925

2026
type FilterSelectorProps = {
@@ -48,6 +54,35 @@ function FilterSelector({
4854
const {dataset, tag} = globalFilter;
4955
const {selection} = usePageFilters();
5056

57+
// Retrieve full tag definition to check if it has predefined values
58+
const datasetFilterKeys = searchBarData.getFilterKeys();
59+
const fullTag = datasetFilterKeys[tag.key];
60+
const fieldDefinition = getFieldDefinitionForDataset(tag, dataset);
61+
62+
const filterToken = useMemo(
63+
() => getFilterToken(globalFilter, fieldDefinition),
64+
[globalFilter, fieldDefinition]
65+
);
66+
67+
// Retrieve predefined values if the tag has any
68+
const predefinedValues = useMemo(() => {
69+
if (!filterToken) {
70+
return null;
71+
}
72+
const filterValue = filterToken.value.text;
73+
return getPredefinedValues({
74+
key: fullTag,
75+
filterValue,
76+
token: filterToken,
77+
fieldDefinition,
78+
});
79+
}, [fullTag, filterToken, fieldDefinition]);
80+
81+
// Only fetch values if the tag has no predefined values
82+
const shouldFetchValues = fullTag
83+
? !fullTag.predefined && predefinedValues === null
84+
: true;
85+
5186
const baseQueryKey = useMemo(
5287
() => ['global-dashboard-filters-tag-values', tag, selection, searchQuery],
5388
[tag, selection, searchQuery]
@@ -63,33 +98,48 @@ function FilterSelector({
6398
return result ?? [];
6499
},
65100
placeholderData: keepPreviousData,
66-
enabled: true,
101+
enabled: shouldFetchValues,
67102
staleTime: 5 * 60 * 1000,
68103
});
69104

70105
const {data: fetchedFilterValues, isFetching} = queryResult;
71106

72107
const options = useMemo(() => {
73-
const optionMap = new Map<string, {label: string; value: string}>();
74-
const addOption = (value: string) => optionMap.set(value, {label: value, value});
108+
const optionMap = new Map<string, SelectOption<string>>();
109+
const fixedOptionMap = new Map<string, SelectOption<string>>();
110+
const addOption = (value: string, map: Map<string, SelectOption<string>>) =>
111+
map.set(value, {label: value, value});
75112

76-
// Filter values fetched using getTagValues
77-
fetchedFilterValues?.forEach(addOption);
78113
// Filter values in the global filter
79-
activeFilterValues.forEach(addOption);
80-
// Staged filter values inside the filter selector
81-
stagedFilterValues.forEach(addOption);
114+
activeFilterValues.forEach(value => addOption(value, optionMap));
115+
116+
// Predefined values
117+
predefinedValues?.forEach(suggestionSection => {
118+
suggestionSection.suggestions.forEach(suggestion =>
119+
addOption(suggestion.value, optionMap)
120+
);
121+
});
122+
// Filter values fetched using getTagValues
123+
fetchedFilterValues?.forEach(value => addOption(value, optionMap));
82124

83125
// Allow setting a custom filter value based on search input
84-
if (searchQuery) {
85-
addOption(searchQuery);
126+
if (searchQuery && !optionMap.has(searchQuery)) {
127+
addOption(searchQuery, fixedOptionMap);
86128
}
87-
88-
// Reversing the order allows effectively deduplicating the values
89-
// and avoid losing their original order from the fetched results
90-
// (e.g. without this, all staged values would be grouped at the top of the list)
91-
return Array.from(optionMap.values()).reverse();
92-
}, [fetchedFilterValues, activeFilterValues, stagedFilterValues, searchQuery]);
129+
// Staged filter values inside the filter selector
130+
stagedFilterValues.forEach(value => {
131+
if (!optionMap.has(value)) {
132+
addOption(value, fixedOptionMap);
133+
}
134+
});
135+
return [...Array.from(fixedOptionMap.values()), ...Array.from(optionMap.values())];
136+
}, [
137+
fetchedFilterValues,
138+
predefinedValues,
139+
activeFilterValues,
140+
stagedFilterValues,
141+
searchQuery,
142+
]);
93143

94144
const handleChange = (opts: string[]) => {
95145
if (isEqual(opts, activeFilterValues)) {
@@ -126,7 +176,8 @@ function FilterSelector({
126176
onStagedValueChange={value => {
127177
setStagedFilterValues(value);
128178
}}
129-
sizeLimit={10}
179+
sizeLimit={30}
180+
menuWidth={400}
130181
onClose={() => {
131182
setSearchQuery('');
132183
setStagedFilterValues([]);

static/app/views/dashboards/globalFilter/utils.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
} from 'sentry/components/searchQueryBuilder/hooks/useQueryBuilderState';
55
import {getFilterValueType} from 'sentry/components/searchQueryBuilder/tokens/filter/utils';
66
import {cleanFilterValue} from 'sentry/components/searchQueryBuilder/tokens/filter/valueSuggestions/utils';
7+
import {getInitialFilterText} from 'sentry/components/searchQueryBuilder/tokens/utils';
78
import {parseQueryBuilderValue} from 'sentry/components/searchQueryBuilder/utils';
89
import {
910
TermOperator,
@@ -54,6 +55,19 @@ export function parseFilterValue(
5455
return parsedResult.filter(token => token.type === Token.FILTER);
5556
}
5657

58+
export function getFilterToken(
59+
globalFilter: GlobalFilter,
60+
fieldDefinition: FieldDefinition | null
61+
) {
62+
const {tag, value} = globalFilter;
63+
let filterValue = value;
64+
if (value === '') {
65+
filterValue = getInitialFilterText(tag.key, fieldDefinition, false);
66+
}
67+
const filterTokens = parseFilterValue(filterValue, globalFilter);
68+
return filterTokens[0] ?? null;
69+
}
70+
5771
export function isValidNumericFilterValue(
5872
value: string,
5973
filterToken: TokenResult<Token.FILTER>,

0 commit comments

Comments
 (0)