From 1b7d35e4204aeacec54dac1ad5427e36cfc0668d Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Wed, 22 Oct 2025 11:11:27 -0400 Subject: [PATCH 1/6] display saved filter selections if not present in fetched values --- .../dashboards/globalFilter/filterSelector.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/static/app/views/dashboards/globalFilter/filterSelector.tsx b/static/app/views/dashboards/globalFilter/filterSelector.tsx index e1400b0e95fc87..2da04fba4ac13c 100644 --- a/static/app/views/dashboards/globalFilter/filterSelector.tsx +++ b/static/app/views/dashboards/globalFilter/filterSelector.tsx @@ -59,7 +59,7 @@ function FilterSelector({ }); const {data, isFetching} = queryResult; - const options = useMemo(() => { + const fetchedOptions = useMemo(() => { if (!data) return []; return data.map(value => ({ label: value, @@ -67,6 +67,17 @@ function FilterSelector({ })); }, [data]); + const savedFilterValueOptions = useMemo(() => { + return activeFilterValues + .map(value => ({ + label: value, + value, + })) + .filter(option => !fetchedOptions.some(o => o.value === option.value)); + }, [activeFilterValues, fetchedOptions]); + + const options = [...savedFilterValueOptions, ...fetchedOptions]; + const handleChange = (opts: string[]) => { if (isEqual(opts, activeFilterValues)) { return; From 4d80e1f92b5c63bd947d90f751e5186c49926138 Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Wed, 22 Oct 2025 11:30:30 -0400 Subject: [PATCH 2/6] only display string and boolean filter keys when adding global filters --- .../dashboards/globalFilter/addFilter.tsx | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/static/app/views/dashboards/globalFilter/addFilter.tsx b/static/app/views/dashboards/globalFilter/addFilter.tsx index 18b3fe47217ff7..618413a11260c6 100644 --- a/static/app/views/dashboards/globalFilter/addFilter.tsx +++ b/static/app/views/dashboards/globalFilter/addFilter.tsx @@ -11,7 +11,12 @@ import {IconAdd, IconArrow} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Tag} from 'sentry/types/group'; -import {FieldKind, getFieldDefinition, prettifyTagKey} from 'sentry/utils/fields'; +import { + FieldKind, + FieldValueType, + getFieldDefinition, + prettifyTagKey, +} from 'sentry/utils/fields'; import type {SearchBarData} from 'sentry/views/dashboards/datasetConfig/base'; import {WidgetType, type GlobalFilter} from 'sentry/views/dashboards/types'; import {shouldExcludeTracingKeys} from 'sentry/views/performance/utils'; @@ -25,19 +30,12 @@ export const DATASET_CHOICES = new Map([ ]); const UNSUPPORTED_FIELD_KINDS = [FieldKind.FUNCTION, FieldKind.MEASUREMENT]; +const SUPPORTED_FIELD_VALUE_TYPES = [FieldValueType.STRING, FieldValueType.BOOLEAN]; export function getDatasetLabel(dataset: WidgetType) { return DATASET_CHOICES.get(dataset) ?? ''; } -function getTagType(tag: Tag, dataset: WidgetType) { - const fieldType = - dataset === WidgetType.SPANS ? 'span' : dataset === WidgetType.LOGS ? 'log' : 'event'; - const fieldDefinition = getFieldDefinition(tag.key, fieldType, tag.kind); - - return ; -} - type AddFilterProps = { getSearchBarData: (widgetType: WidgetType) => SearchBarData; globalFilters: GlobalFilter[]; @@ -70,11 +68,28 @@ function AddFilter({globalFilters, getSearchBarData, onAddFilter}: AddFilterProp // Get filter keys for the selected dataset const filterKeyOptions = selectedDataset - ? Object.entries(filterKeys).map(([_, tag]) => { + ? Object.entries(filterKeys).flatMap(([_, tag]) => { + const fieldType = + selectedDataset === WidgetType.SPANS + ? 'span' + : selectedDataset === WidgetType.LOGS + ? 'log' + : 'event'; + const fieldDefinition = getFieldDefinition(tag.key, fieldType, tag.kind); + const valueType = fieldDefinition?.valueType; + + if (!valueType || !SUPPORTED_FIELD_VALUE_TYPES.includes(valueType)) { + return []; + } + return { value: tag.key, label: prettifyTagKey(tag.key), - trailingItems: {getTagType(tag, selectedDataset)}, + trailingItems: ( + + + + ), disabled: globalFilters.some(filter => filter.tag.key === tag.key), }; }) From 6f6ca63045f198081654317854011cb72aa7b19d Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Wed, 22 Oct 2025 11:34:26 -0400 Subject: [PATCH 3/6] add page filters to debouncer to fetch new tag values upon page filter changes --- .../app/views/dashboards/globalFilter/filterSelector.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/static/app/views/dashboards/globalFilter/filterSelector.tsx b/static/app/views/dashboards/globalFilter/filterSelector.tsx index 2da04fba4ac13c..60a16a01840d51 100644 --- a/static/app/views/dashboards/globalFilter/filterSelector.tsx +++ b/static/app/views/dashboards/globalFilter/filterSelector.tsx @@ -11,6 +11,7 @@ import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {keepPreviousData, useQuery} from 'sentry/utils/queryClient'; import {useDebouncedValue} from 'sentry/utils/useDebouncedValue'; +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'; @@ -42,8 +43,12 @@ function FilterSelector({ }, [initialValues]); const {dataset, tag} = globalFilter; + const pageFilters = usePageFilters(); - const baseQueryKey = useMemo(() => ['global-dashboard-filters-tag-values', tag], [tag]); + const baseQueryKey = useMemo( + () => ['global-dashboard-filters-tag-values', tag, pageFilters.selection], + [tag, pageFilters.selection] + ); const queryKey = useDebouncedValue(baseQueryKey); const queryResult = useQuery({ From 67aff59a8f56203de842b44e1552268cb444728e Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Wed, 22 Oct 2025 12:18:20 -0400 Subject: [PATCH 4/6] fix breaking tests --- .../globalFilter/addFilter.spec.tsx | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/static/app/views/dashboards/globalFilter/addFilter.spec.tsx b/static/app/views/dashboards/globalFilter/addFilter.spec.tsx index f76ed337b2b45c..fd9fe6d70dd698 100644 --- a/static/app/views/dashboards/globalFilter/addFilter.spec.tsx +++ b/static/app/views/dashboards/globalFilter/addFilter.spec.tsx @@ -9,9 +9,9 @@ import {WidgetType} from 'sentry/views/dashboards/types'; describe('AddFilter', () => { // Mock filter keys returned by the search bar data provider const mockFilterKeys: TagCollection = { - browser: { - key: 'browser', - name: 'Browser', + 'browser.name': { + key: 'browser.name', + name: 'Browser Name', kind: FieldKind.FIELD, }, environment: { @@ -19,12 +19,12 @@ describe('AddFilter', () => { name: 'Environment', kind: FieldKind.FIELD, }, - unsupportedFunction: { + 'unsupported.function': { key: 'unsupported.function', name: 'Unsupported Function', kind: FieldKind.FUNCTION, }, - unsupportedMeasurement: { + 'unsupported.measurement': { key: 'unsupported.measurement', name: 'Unsupported Measurement', kind: FieldKind.MEASUREMENT, @@ -64,17 +64,15 @@ describe('AddFilter', () => { await userEvent.click(screen.getByRole('button', {name: 'Add Global Filter'})); // Verify filter keys are shown for each dataset - for (const datasetLabel of DATASET_CHOICES.values()) { - await userEvent.click(screen.getByText(datasetLabel)); + await userEvent.click(screen.getByText('Errors')); - // Should see filter key options for the dataset - expect(screen.getByText('Select Filter Tag')).toBeInTheDocument(); - expect(screen.getByText(mockFilterKeys.browser!.key)).toBeInTheDocument(); - expect(screen.getByText(mockFilterKeys.environment!.key)).toBeInTheDocument(); + // Should see filter key options for the dataset + expect(screen.getByText('Select Filter Tag')).toBeInTheDocument(); + expect(screen.getByText(mockFilterKeys['browser.name']!.key)).toBeInTheDocument(); + expect(screen.getByText(mockFilterKeys.environment!.key)).toBeInTheDocument(); - // Return to dataset selection - await userEvent.click(screen.getByText('Back')); - } + // Return to dataset selection + await userEvent.click(screen.getByText('Back')); }); it('does not render unsupported filter keys', async () => { @@ -92,10 +90,10 @@ describe('AddFilter', () => { // Unsupported filter keys should not be included in the options expect( - screen.queryByText(mockFilterKeys.unsupportedFunction!.key) + screen.queryByText(mockFilterKeys['unsupported.function']!.key) ).not.toBeInTheDocument(); expect( - screen.queryByText(mockFilterKeys.unsupportedMeasurement!.key) + screen.queryByText(mockFilterKeys['unsupported.measurement']!.key) ).not.toBeInTheDocument(); }); @@ -114,14 +112,16 @@ describe('AddFilter', () => { // Select arbitrary dataset and filter key await userEvent.click(screen.getByText('Errors')); - await userEvent.click(screen.getByText(mockFilterKeys.browser!.key)); - await userEvent.click(screen.getByText('Add Filter')); + await userEvent.click( + screen.getByRole('option', {name: mockFilterKeys['browser.name']!.key}) + ); + await userEvent.click(screen.getByRole('button', {name: 'Add Filter'})); // Verify onAddFilter was called with the added global filter object expect(onAddFilter).toHaveBeenCalledTimes(1); expect(onAddFilter).toHaveBeenCalledWith({ dataset: WidgetType.ERRORS, - tag: mockFilterKeys.browser, + tag: mockFilterKeys['browser.name'], value: '', }); }); From 82c7ea136e221c72a88e75db9378978bacb73af5 Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Wed, 22 Oct 2025 13:10:47 -0400 Subject: [PATCH 5/6] check for filter key dataset when disabling used filter keys --- static/app/views/dashboards/globalFilter/addFilter.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/static/app/views/dashboards/globalFilter/addFilter.tsx b/static/app/views/dashboards/globalFilter/addFilter.tsx index 618413a11260c6..bc7864b9523d6c 100644 --- a/static/app/views/dashboards/globalFilter/addFilter.tsx +++ b/static/app/views/dashboards/globalFilter/addFilter.tsx @@ -90,7 +90,9 @@ function AddFilter({globalFilters, getSearchBarData, onAddFilter}: AddFilterProp ), - disabled: globalFilters.some(filter => filter.tag.key === tag.key), + disabled: globalFilters.some( + filter => filter.tag.key === tag.key && filter.dataset === selectedDataset + ), }; }) : []; From ad43d9e64b4746965dcdaf08588b3290f2f92c36 Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Fri, 24 Oct 2025 11:58:12 -0400 Subject: [PATCH 6/6] replace ternary expression with switch case --- .../dashboards/globalFilter/addFilter.tsx | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/static/app/views/dashboards/globalFilter/addFilter.tsx b/static/app/views/dashboards/globalFilter/addFilter.tsx index bc7864b9523d6c..b924ee57c040ea 100644 --- a/static/app/views/dashboards/globalFilter/addFilter.tsx +++ b/static/app/views/dashboards/globalFilter/addFilter.tsx @@ -69,13 +69,21 @@ function AddFilter({globalFilters, getSearchBarData, onAddFilter}: AddFilterProp // Get filter keys for the selected dataset const filterKeyOptions = selectedDataset ? Object.entries(filterKeys).flatMap(([_, tag]) => { - const fieldType = - selectedDataset === WidgetType.SPANS - ? 'span' - : selectedDataset === WidgetType.LOGS - ? 'log' - : 'event'; - const fieldDefinition = getFieldDefinition(tag.key, fieldType, tag.kind); + const fieldType = (datasetType: WidgetType) => { + switch (datasetType) { + case WidgetType.SPANS: + return 'span'; + case WidgetType.LOGS: + return 'log'; + default: + return 'event'; + } + }; + const fieldDefinition = getFieldDefinition( + tag.key, + fieldType(selectedDataset), + tag.kind + ); const valueType = fieldDefinition?.valueType; if (!valueType || !SUPPORTED_FIELD_VALUE_TYPES.includes(valueType)) {