Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ function getSuggestionDescription(group: SearchGroup | SearchItem) {
return undefined;
}

function getPredefinedValues({
export function getPredefinedValues({
fieldDefinition,
key,
filterValue,
Expand Down
85 changes: 68 additions & 17 deletions static/app/views/dashboards/globalFilter/filterSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ 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';
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';
Expand All @@ -15,6 +17,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 = {
Expand Down Expand Up @@ -48,6 +54,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]
Expand All @@ -63,33 +98,48 @@ function FilterSelector({
return result ?? [];
},
placeholderData: keepPreviousData,
enabled: true,
enabled: shouldFetchValues,
staleTime: 5 * 60 * 1000,
});

const {data: fetchedFilterValues, isFetching} = queryResult;

const options = useMemo(() => {
const optionMap = new Map<string, {label: string; value: string}>();
const addOption = (value: string) => optionMap.set(value, {label: value, value});
const optionMap = new Map<string, SelectOption<string>>();
const fixedOptionMap = new Map<string, SelectOption<string>>();
const addOption = (value: string, map: Map<string, SelectOption<string>>) =>
map.set(value, {label: value, value});

// Filter values fetched using getTagValues
fetchedFilterValues?.forEach(addOption);
// Filter values in the global filter
activeFilterValues.forEach(addOption);
// Staged filter values inside the filter selector
stagedFilterValues.forEach(addOption);
activeFilterValues.forEach(value => addOption(value, optionMap));

// Predefined values
predefinedValues?.forEach(suggestionSection => {
suggestionSection.suggestions.forEach(suggestion =>
addOption(suggestion.value, optionMap)
);
});
// 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();
}, [fetchedFilterValues, activeFilterValues, stagedFilterValues, searchQuery]);
// 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,
activeFilterValues,
stagedFilterValues,
searchQuery,
]);

const handleChange = (opts: string[]) => {
if (isEqual(opts, activeFilterValues)) {
Expand Down Expand Up @@ -126,7 +176,8 @@ function FilterSelector({
onStagedValueChange={value => {
setStagedFilterValues(value);
}}
sizeLimit={10}
sizeLimit={30}
menuWidth={400}
onClose={() => {
setSearchQuery('');
setStagedFilterValues([]);
Expand Down
14 changes: 14 additions & 0 deletions static/app/views/dashboards/globalFilter/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<Token.FILTER>,
Expand Down
Loading