From 9a229456b7934e029414b618d37262487496defb Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Thu, 16 Oct 2025 14:46:20 -0400 Subject: [PATCH 01/11] create central hook for all dataset search bar data --- .../dashboards/hooks/useSearchBarData.tsx | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 static/app/views/dashboards/hooks/useSearchBarData.tsx diff --git a/static/app/views/dashboards/hooks/useSearchBarData.tsx b/static/app/views/dashboards/hooks/useSearchBarData.tsx new file mode 100644 index 00000000000000..391fa159cba5bb --- /dev/null +++ b/static/app/views/dashboards/hooks/useSearchBarData.tsx @@ -0,0 +1,46 @@ +import usePageFilters from 'sentry/utils/usePageFilters'; +import { + getDatasetConfig, + type SearchBarData, +} from 'sentry/views/dashboards/datasetConfig/base'; +import {WidgetType} from 'sentry/views/dashboards/types'; + +export type DatasetSearchBarData = { + [WidgetType.ERRORS]: SearchBarData; + [WidgetType.LOGS]: SearchBarData; + [WidgetType.SPANS]: SearchBarData; + [WidgetType.ISSUE]: SearchBarData; + [WidgetType.RELEASE]: SearchBarData; +}; + +export function useDatasetSearchBarData(): DatasetSearchBarData { + const {selection} = usePageFilters(); + + const errorsData = getDatasetConfig(WidgetType.ERRORS).useSearchBarDataProvider!({ + pageFilters: selection, + }); + + const logsData = getDatasetConfig(WidgetType.LOGS).useSearchBarDataProvider!({ + pageFilters: selection, + }); + + const spansData = getDatasetConfig(WidgetType.SPANS).useSearchBarDataProvider!({ + pageFilters: selection, + }); + + const issuesData = getDatasetConfig(WidgetType.ISSUE).useSearchBarDataProvider!({ + pageFilters: selection, + }); + + const releasesData = getDatasetConfig(WidgetType.RELEASE).useSearchBarDataProvider!({ + pageFilters: selection, + }); + + return { + [WidgetType.ERRORS]: errorsData, + [WidgetType.LOGS]: logsData, + [WidgetType.SPANS]: spansData, + [WidgetType.ISSUE]: issuesData, + [WidgetType.RELEASE]: releasesData, + }; +} From 84afeaf6de8854a85c2451fc3960bc5e5616e88f Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Thu, 16 Oct 2025 14:48:08 -0400 Subject: [PATCH 02/11] use centralized searchbar data hook to retrieve filter keys for each dataset --- static/app/views/dashboards/filtersBar.tsx | 4 ++ .../dashboards/globalFilter/addFilter.tsx | 48 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/static/app/views/dashboards/filtersBar.tsx b/static/app/views/dashboards/filtersBar.tsx index 34807ae196ccfd..0f6e41b196dff4 100644 --- a/static/app/views/dashboards/filtersBar.tsx +++ b/static/app/views/dashboards/filtersBar.tsx @@ -20,6 +20,7 @@ import {useUser} from 'sentry/utils/useUser'; import {useUserTeams} from 'sentry/utils/useUserTeams'; import AddFilter from 'sentry/views/dashboards/globalFilter/addFilter'; import {useInvalidateStarredDashboards} from 'sentry/views/dashboards/hooks/useInvalidateStarredDashboards'; +import {useDatasetSearchBarData} from 'sentry/views/dashboards/hooks/useSearchBarData'; import {getDashboardFiltersFromURL} from 'sentry/views/dashboards/utils'; import FilterSelector from './globalFilter/filterSelector'; @@ -59,6 +60,8 @@ export default function FiltersBar({ const organization = useOrganization(); const currentUser = useUser(); const {teams: userTeams} = useUserTeams(); + const datasetSearchBarData = useDatasetSearchBarData(); + const hasEditAccess = checkUserHasEditAccess( currentUser, userTeams, @@ -162,6 +165,7 @@ export default function FiltersBar({ /> ))} { updateGlobalFilters([...activeGlobalFilters, newFilter]); }} diff --git a/static/app/views/dashboards/globalFilter/addFilter.tsx b/static/app/views/dashboards/globalFilter/addFilter.tsx index 22f739b55f44f6..3dd827913ba9b4 100644 --- a/static/app/views/dashboards/globalFilter/addFilter.tsx +++ b/static/app/views/dashboards/globalFilter/addFilter.tsx @@ -10,14 +10,20 @@ import {ValueType} from 'sentry/components/searchQueryBuilder/tokens/filterKeyLi import {IconAdd, IconArrow} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; -import type {Tag, TagCollection} from 'sentry/types/group'; +import type {Tag} from 'sentry/types/group'; import {FieldKind, getFieldDefinition, prettifyTagKey} from 'sentry/utils/fields'; -import usePageFilters from 'sentry/utils/usePageFilters'; -import {getDatasetConfig} from 'sentry/views/dashboards/datasetConfig/base'; +import type {DatasetSearchBarData} from 'sentry/views/dashboards/hooks/useSearchBarData'; import {WidgetType, type GlobalFilter} from 'sentry/views/dashboards/types'; import {shouldExcludeTracingKeys} from 'sentry/views/performance/utils'; -export const DATASET_CHOICES = new Map([ +type SupportedDataset = + | WidgetType.ERRORS + | WidgetType.SPANS + | WidgetType.LOGS + | WidgetType.RELEASE + | WidgetType.ISSUE; + +export const DATASET_CHOICES = new Map([ [WidgetType.ERRORS, t('Errors')], [WidgetType.SPANS, t('Spans')], [WidgetType.LOGS, t('Logs')], @@ -27,11 +33,11 @@ export const DATASET_CHOICES = new Map([ const UNSUPPORTED_FIELD_KINDS = [FieldKind.FUNCTION, FieldKind.MEASUREMENT]; -export function getDatasetLabel(dataset: WidgetType) { - return DATASET_CHOICES.get(dataset) ?? ''; +export function getDatasetLabel(dataset: SupportedDataset) { + return DATASET_CHOICES.get(dataset); } -function getTagType(tag: Tag, dataset: WidgetType | null) { +function getTagType(tag: Tag, dataset: SupportedDataset | null) { const fieldType = dataset === WidgetType.SPANS ? 'span' : dataset === WidgetType.LOGS ? 'log' : 'event'; const fieldDefinition = getFieldDefinition(tag.key, fieldType, tag.kind); @@ -40,14 +46,14 @@ function getTagType(tag: Tag, dataset: WidgetType | null) { } type AddFilterProps = { + datasetSearchBarData: DatasetSearchBarData; onAddFilter: (filter: GlobalFilter) => void; }; -function AddFilter({onAddFilter}: AddFilterProps) { - const [selectedDataset, setSelectedDataset] = useState(null); +function AddFilter({datasetSearchBarData, onAddFilter}: AddFilterProps) { + const [selectedDataset, setSelectedDataset] = useState(null); const [selectedFilterKey, setSelectedFilterKey] = useState(null); const [isSelectingFilterKey, setIsSelectingFilterKey] = useState(false); - const {selection} = usePageFilters(); // Dataset selection before showing filter keys const datasetOptions = useMemo(() => { @@ -58,27 +64,17 @@ function AddFilter({onAddFilter}: AddFilterProps) { })); }, []); - const datasetFilterKeysMap = new Map(); - - DATASET_CHOICES.forEach((_, widgetType) => { - const datasetConfig = getDatasetConfig(widgetType); - if (datasetConfig.useSearchBarDataProvider) { - const dataProvider = datasetConfig.useSearchBarDataProvider({ - pageFilters: selection, - }); - const filterKeys = Object.fromEntries( - Object.entries(dataProvider.getFilterKeys()).filter( + const filterKeys: Record = selectedDataset + ? Object.fromEntries( + Object.entries(datasetSearchBarData[selectedDataset].getFilterKeys()).filter( ([key, value]) => !shouldExcludeTracingKeys(key) && (!value.kind || !UNSUPPORTED_FIELD_KINDS.includes(value.kind)) ) - ); - datasetFilterKeysMap.set(widgetType, filterKeys); - } - }); + ) + : {}; // Get filter keys for the selected dataset - const filterKeys = (selectedDataset && datasetFilterKeysMap.get(selectedDataset)) || {}; const filterKeyOptions = Object.entries(filterKeys).map(([_, tag]) => { return { value: tag.key, @@ -136,7 +132,7 @@ function AddFilter({onAddFilter}: AddFilterProps) { setSelectedFilterKey(filterKeys[option.value] ?? null); return; } - setSelectedDataset(option.value as WidgetType); + setSelectedDataset(option.value as SupportedDataset); setSelectedFilterKey(null); setIsSelectingFilterKey(true); }} From cf4385c07e5ee8b6897a12f8f42cb0ce4652895d Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Thu, 16 Oct 2025 15:13:26 -0400 Subject: [PATCH 03/11] change dataset label argument back to WidgetType to avoid type assertion in other component --- static/app/views/dashboards/globalFilter/addFilter.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/app/views/dashboards/globalFilter/addFilter.tsx b/static/app/views/dashboards/globalFilter/addFilter.tsx index 3dd827913ba9b4..234b35b34b9d4b 100644 --- a/static/app/views/dashboards/globalFilter/addFilter.tsx +++ b/static/app/views/dashboards/globalFilter/addFilter.tsx @@ -33,8 +33,8 @@ export const DATASET_CHOICES = new Map([ const UNSUPPORTED_FIELD_KINDS = [FieldKind.FUNCTION, FieldKind.MEASUREMENT]; -export function getDatasetLabel(dataset: SupportedDataset) { - return DATASET_CHOICES.get(dataset); +export function getDatasetLabel(dataset: WidgetType) { + return DATASET_CHOICES.get(dataset as SupportedDataset) ?? ''; } function getTagType(tag: Tag, dataset: SupportedDataset | null) { From f42f8508d9e9c8f2689eb0d401993d385febf51a Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Thu, 16 Oct 2025 15:39:06 -0400 Subject: [PATCH 04/11] update addFilter tests to use new centralized search bar data hook --- .../globalFilter/addFilter.spec.tsx | 60 ++++++++++++------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/static/app/views/dashboards/globalFilter/addFilter.spec.tsx b/static/app/views/dashboards/globalFilter/addFilter.spec.tsx index db5a04e0e9d2ba..88029036a40199 100644 --- a/static/app/views/dashboards/globalFilter/addFilter.spec.tsx +++ b/static/app/views/dashboards/globalFilter/addFilter.spec.tsx @@ -2,8 +2,15 @@ import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import type {TagCollection} from 'sentry/types/group'; import {FieldKind} from 'sentry/utils/fields'; -import {getDatasetConfig} from 'sentry/views/dashboards/datasetConfig/base'; +import { + getDatasetConfig, + type SearchBarData, +} from 'sentry/views/dashboards/datasetConfig/base'; import AddFilter, {DATASET_CHOICES} from 'sentry/views/dashboards/globalFilter/addFilter'; +import { + useDatasetSearchBarData, + type DatasetSearchBarData, +} from 'sentry/views/dashboards/hooks/useSearchBarData'; import {WidgetType} from 'sentry/views/dashboards/types'; // Mock getDatasetConfig @@ -34,17 +41,22 @@ describe('AddFilter', () => { }, }; - const mockUseSearchBarDataProvider = jest.fn(() => ({ - getFilterKeys: () => mockFilterKeys, - })); + const createMockSearchBarData = (): SearchBarData => ({ + getFilterKeys: jest.fn(() => mockFilterKeys), + getFilterKeySections: jest.fn(() => []), + getTagValues: jest.fn(() => Promise.resolve([])), + }); + + const mockDatasetSearchBarData: DatasetSearchBarData = { + [WidgetType.ERRORS]: createMockSearchBarData(), + [WidgetType.LOGS]: createMockSearchBarData(), + [WidgetType.SPANS]: createMockSearchBarData(), + [WidgetType.ISSUE]: createMockSearchBarData(), + [WidgetType.RELEASE]: createMockSearchBarData(), + }; beforeEach(() => { MockApiClient.clearMockResponses(); - - // Mock getDatasetConfig that returns a config with a mock search bar data provider - jest.mocked(getDatasetConfig).mockReturnValue({ - useSearchBarDataProvider: mockUseSearchBarDataProvider, - } as any); }); afterEach(() => { @@ -52,7 +64,9 @@ describe('AddFilter', () => { }); it('renders all dataset options', async () => { - render( {}} />); + render( + {}} /> + ); await userEvent.click(screen.getByRole('button', {name: 'Add Global Filter'})); for (const dataset of DATASET_CHOICES.values()) { expect(screen.getByText(dataset)).toBeInTheDocument(); @@ -60,21 +74,20 @@ describe('AddFilter', () => { }); it('retrieves filter keys for each dataset', async () => { - render( {}} />); + render( + {}} /> + ); // Open the add global filter drop down await userEvent.click(screen.getByRole('button', {name: 'Add Global Filter'})); - // Verify search bar data provider was called for each dataset type - for (const [widgetType] of DATASET_CHOICES.entries()) { - expect(getDatasetConfig).toHaveBeenCalledWith(widgetType); - } - expect(mockUseSearchBarDataProvider).toHaveBeenCalledTimes(DATASET_CHOICES.size); - // Verify filter keys are shown for each dataset - for (const datasetLabel of DATASET_CHOICES.values()) { + for (const [widgetType, datasetLabel] of DATASET_CHOICES.entries()) { await userEvent.click(screen.getByText(datasetLabel)); + // Verify corresponding dataset filter key getter was called once + expect(mockDatasetSearchBarData[widgetType].getFilterKeys).toHaveBeenCalledTimes(1); + // Should see filter key options for the dataset expect(screen.getByText('Select Filter Tag')).toBeInTheDocument(); expect(screen.getByText(mockFilterKeys.browser!.key)).toBeInTheDocument(); @@ -86,7 +99,9 @@ describe('AddFilter', () => { }); it('does not render unsupported filter keys', async () => { - render( {}} />); + render( + {}} /> + ); // Open the dropdown and select an arbitrary dataset await userEvent.click(screen.getByRole('button', {name: 'Add Global Filter'})); @@ -103,7 +118,12 @@ describe('AddFilter', () => { it('calls onAddFilter with expected global filter object', async () => { const onAddFilter = jest.fn(); - render(); + render( + + ); // Open add global filter drop down await userEvent.click(screen.getByRole('button', {name: 'Add Global Filter'})); From 486bb35f5cd1e5cc1973f99e7cdab3a1ba65e4c6 Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Thu, 16 Oct 2025 15:47:07 -0400 Subject: [PATCH 05/11] remove unused import --- .../views/dashboards/globalFilter/addFilter.spec.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/static/app/views/dashboards/globalFilter/addFilter.spec.tsx b/static/app/views/dashboards/globalFilter/addFilter.spec.tsx index 88029036a40199..ab9d93002eb947 100644 --- a/static/app/views/dashboards/globalFilter/addFilter.spec.tsx +++ b/static/app/views/dashboards/globalFilter/addFilter.spec.tsx @@ -2,15 +2,9 @@ import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import type {TagCollection} from 'sentry/types/group'; import {FieldKind} from 'sentry/utils/fields'; -import { - getDatasetConfig, - type SearchBarData, -} from 'sentry/views/dashboards/datasetConfig/base'; +import {type SearchBarData} from 'sentry/views/dashboards/datasetConfig/base'; import AddFilter, {DATASET_CHOICES} from 'sentry/views/dashboards/globalFilter/addFilter'; -import { - useDatasetSearchBarData, - type DatasetSearchBarData, -} from 'sentry/views/dashboards/hooks/useSearchBarData'; +import {type DatasetSearchBarData} from 'sentry/views/dashboards/hooks/useSearchBarData'; import {WidgetType} from 'sentry/views/dashboards/types'; // Mock getDatasetConfig From 4e0741f9b243cbfa4bb3f9f854b6873c5d61e107 Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Thu, 16 Oct 2025 15:55:21 -0400 Subject: [PATCH 06/11] use centralized search bar data hook in the filter selector component --- static/app/views/dashboards/filtersBar.tsx | 5 ++++- .../app/views/dashboards/globalFilter/addFilter.tsx | 2 +- .../views/dashboards/globalFilter/filterSelector.tsx | 11 ++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/static/app/views/dashboards/filtersBar.tsx b/static/app/views/dashboards/filtersBar.tsx index 0f6e41b196dff4..8174846a065acc 100644 --- a/static/app/views/dashboards/filtersBar.tsx +++ b/static/app/views/dashboards/filtersBar.tsx @@ -18,7 +18,9 @@ import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import {useUser} from 'sentry/utils/useUser'; import {useUserTeams} from 'sentry/utils/useUserTeams'; -import AddFilter from 'sentry/views/dashboards/globalFilter/addFilter'; +import AddFilter, { + type SupportedDataset, +} from 'sentry/views/dashboards/globalFilter/addFilter'; import {useInvalidateStarredDashboards} from 'sentry/views/dashboards/hooks/useInvalidateStarredDashboards'; import {useDatasetSearchBarData} from 'sentry/views/dashboards/hooks/useSearchBarData'; import {getDashboardFiltersFromURL} from 'sentry/views/dashboards/utils'; @@ -150,6 +152,7 @@ export default function FiltersBar({ { updateGlobalFilters( activeGlobalFilters.map(f => diff --git a/static/app/views/dashboards/globalFilter/addFilter.tsx b/static/app/views/dashboards/globalFilter/addFilter.tsx index 234b35b34b9d4b..24ab9cd9c60ed4 100644 --- a/static/app/views/dashboards/globalFilter/addFilter.tsx +++ b/static/app/views/dashboards/globalFilter/addFilter.tsx @@ -16,7 +16,7 @@ import type {DatasetSearchBarData} from 'sentry/views/dashboards/hooks/useSearch import {WidgetType, type GlobalFilter} from 'sentry/views/dashboards/types'; import {shouldExcludeTracingKeys} from 'sentry/views/performance/utils'; -type SupportedDataset = +export type SupportedDataset = | WidgetType.ERRORS | WidgetType.SPANS | WidgetType.LOGS diff --git a/static/app/views/dashboards/globalFilter/filterSelector.tsx b/static/app/views/dashboards/globalFilter/filterSelector.tsx index fa0ad81ca4a4cf..a804ca34682ef3 100644 --- a/static/app/views/dashboards/globalFilter/filterSelector.tsx +++ b/static/app/views/dashboards/globalFilter/filterSelector.tsx @@ -7,8 +7,7 @@ import {MutableSearch} from 'sentry/components/searchSyntax/mutableSearch'; import {t} from 'sentry/locale'; import {keepPreviousData, useQuery} from 'sentry/utils/queryClient'; import {useDebouncedValue} from 'sentry/utils/useDebouncedValue'; -import usePageFilters from 'sentry/utils/usePageFilters'; -import {getDatasetConfig} from 'sentry/views/dashboards/datasetConfig/base'; +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 type {GlobalFilter} from 'sentry/views/dashboards/types'; @@ -17,10 +16,12 @@ type FilterSelectorProps = { globalFilter: GlobalFilter; onRemoveFilter: (filter: GlobalFilter) => void; onUpdateFilter: (filter: GlobalFilter) => void; + searchBarData: SearchBarData; }; function FilterSelector({ globalFilter, + searchBarData, onRemoveFilter, onUpdateFilter, }: FilterSelectorProps) { @@ -37,10 +38,6 @@ function FilterSelector({ }, [initialValues]); const {dataset, tag} = globalFilter; - const {selection} = usePageFilters(); - const dataProvider = getDatasetConfig(dataset).useSearchBarDataProvider!({ - pageFilters: selection, - }); const baseQueryKey = useMemo(() => ['global-dashboard-filters-tag-values', tag], [tag]); const queryKey = useDebouncedValue(baseQueryKey); @@ -50,7 +47,7 @@ function FilterSelector({ // eslint-disable-next-line @tanstack/query/exhaustive-deps queryKey, queryFn: async () => { - const result = await dataProvider?.getTagValues(tag, ''); + const result = await searchBarData.getTagValues(tag, ''); return result ?? []; }, placeholderData: keepPreviousData, From 286186eeee6b8c15f3c2d969862b4e44ca304d9e Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Thu, 16 Oct 2025 16:14:22 -0400 Subject: [PATCH 07/11] rename dataset search bar data hook file --- static/app/views/dashboards/filtersBar.tsx | 2 +- static/app/views/dashboards/globalFilter/addFilter.spec.tsx | 2 +- static/app/views/dashboards/globalFilter/addFilter.tsx | 2 +- .../hooks/{useSearchBarData.tsx => useDatasetSearchBarData.tsx} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename static/app/views/dashboards/hooks/{useSearchBarData.tsx => useDatasetSearchBarData.tsx} (100%) diff --git a/static/app/views/dashboards/filtersBar.tsx b/static/app/views/dashboards/filtersBar.tsx index 8174846a065acc..ecf1556a270c51 100644 --- a/static/app/views/dashboards/filtersBar.tsx +++ b/static/app/views/dashboards/filtersBar.tsx @@ -21,8 +21,8 @@ import {useUserTeams} from 'sentry/utils/useUserTeams'; import AddFilter, { type SupportedDataset, } from 'sentry/views/dashboards/globalFilter/addFilter'; +import {useDatasetSearchBarData} from 'sentry/views/dashboards/hooks/useDatasetSearchBarData'; import {useInvalidateStarredDashboards} from 'sentry/views/dashboards/hooks/useInvalidateStarredDashboards'; -import {useDatasetSearchBarData} from 'sentry/views/dashboards/hooks/useSearchBarData'; import {getDashboardFiltersFromURL} from 'sentry/views/dashboards/utils'; import FilterSelector from './globalFilter/filterSelector'; diff --git a/static/app/views/dashboards/globalFilter/addFilter.spec.tsx b/static/app/views/dashboards/globalFilter/addFilter.spec.tsx index ab9d93002eb947..76aea71e303ce8 100644 --- a/static/app/views/dashboards/globalFilter/addFilter.spec.tsx +++ b/static/app/views/dashboards/globalFilter/addFilter.spec.tsx @@ -4,7 +4,7 @@ import type {TagCollection} from 'sentry/types/group'; import {FieldKind} from 'sentry/utils/fields'; import {type SearchBarData} from 'sentry/views/dashboards/datasetConfig/base'; import AddFilter, {DATASET_CHOICES} from 'sentry/views/dashboards/globalFilter/addFilter'; -import {type DatasetSearchBarData} from 'sentry/views/dashboards/hooks/useSearchBarData'; +import {type DatasetSearchBarData} from 'sentry/views/dashboards/hooks/useDatasetSearchBarData'; import {WidgetType} from 'sentry/views/dashboards/types'; // Mock getDatasetConfig diff --git a/static/app/views/dashboards/globalFilter/addFilter.tsx b/static/app/views/dashboards/globalFilter/addFilter.tsx index 24ab9cd9c60ed4..c6407227ea87bb 100644 --- a/static/app/views/dashboards/globalFilter/addFilter.tsx +++ b/static/app/views/dashboards/globalFilter/addFilter.tsx @@ -12,7 +12,7 @@ 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 type {DatasetSearchBarData} from 'sentry/views/dashboards/hooks/useSearchBarData'; +import type {DatasetSearchBarData} from 'sentry/views/dashboards/hooks/useDatasetSearchBarData'; import {WidgetType, type GlobalFilter} from 'sentry/views/dashboards/types'; import {shouldExcludeTracingKeys} from 'sentry/views/performance/utils'; diff --git a/static/app/views/dashboards/hooks/useSearchBarData.tsx b/static/app/views/dashboards/hooks/useDatasetSearchBarData.tsx similarity index 100% rename from static/app/views/dashboards/hooks/useSearchBarData.tsx rename to static/app/views/dashboards/hooks/useDatasetSearchBarData.tsx From 7b74c4101b3a1e302a5ce18dbfb16cead9592fe9 Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Thu, 16 Oct 2025 16:15:03 -0400 Subject: [PATCH 08/11] update global filter selector tests to use mock search bar data --- .../globalFilter/filterSelector.spec.tsx | 38 +++++-------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/static/app/views/dashboards/globalFilter/filterSelector.spec.tsx b/static/app/views/dashboards/globalFilter/filterSelector.spec.tsx index 5078d95e88c7d4..4a8024fa52469e 100644 --- a/static/app/views/dashboards/globalFilter/filterSelector.spec.tsx +++ b/static/app/views/dashboards/globalFilter/filterSelector.spec.tsx @@ -1,6 +1,7 @@ import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import {FieldKind} from 'sentry/utils/fields'; +import type {SearchBarData} from 'sentry/views/dashboards/datasetConfig/base'; import FilterSelector from 'sentry/views/dashboards/globalFilter/filterSelector'; import {WidgetType, type GlobalFilter} from 'sentry/views/dashboards/types'; @@ -17,40 +18,18 @@ describe('FilterSelector', () => { }, value: '', }; - beforeEach(() => { - MockApiClient.clearMockResponses(); - // Mock tags endpoint - MockApiClient.addMockResponse({ - url: '/organizations/org-slug/tags/', - body: [], - }); - - // Mock custom measurements endpoint - MockApiClient.addMockResponse({ - url: '/organizations/org-slug/measurements-meta/', - body: {}, - }); - - // Mock tag values endpoint - MockApiClient.addMockResponse({ - url: `/organizations/org-slug/tags/${mockGlobalFilter.tag.key}/values/`, - body: [ - {name: 'chrome', value: 'chrome', count: 100}, - {name: 'firefox', value: 'firefox', count: 50}, - {name: 'safari', value: 'safari', count: 25}, - ], - }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); + const mockSearchBarData: SearchBarData = { + getFilterKeySections: () => [], + getFilterKeys: () => ({}), + getTagValues: () => Promise.resolve(['chrome', 'firefox', 'safari']), + }; it('renders all filter values', async () => { render( @@ -68,6 +47,7 @@ describe('FilterSelector', () => { render( @@ -98,6 +78,7 @@ describe('FilterSelector', () => { render( @@ -114,6 +95,7 @@ describe('FilterSelector', () => { render( From 90c6a7539e933bb66b0bcb9b1dc59a43f58689e3 Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Thu, 16 Oct 2025 16:24:59 -0400 Subject: [PATCH 09/11] add mock response for cusom measurements endpoint since it is now being called by the search bar data hook in the filtersBar --- static/app/views/dashboards/detail.spec.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static/app/views/dashboards/detail.spec.tsx b/static/app/views/dashboards/detail.spec.tsx index 726f050ddd1cd6..a8da89b4830899 100644 --- a/static/app/views/dashboards/detail.spec.tsx +++ b/static/app/views/dashboards/detail.spec.tsx @@ -149,6 +149,10 @@ describe('Dashboards > Detail', () => { url: '/organizations/org-slug/releases/stats/', body: [], }); + MockApiClient.addMockResponse({ + url: '/organizations/org-slug/measurements-meta/', + body: [], + }); }); afterEach(() => { From bd70ebdaf1667bc5d8dacf53502d6a6fd88bb7fb Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Fri, 17 Oct 2025 14:49:52 -0400 Subject: [PATCH 10/11] refactor centralized search bar data hook --- static/app/views/dashboards/filtersBar.tsx | 10 ++--- .../dashboards/globalFilter/addFilter.tsx | 41 ++++++++----------- .../hooks/useDatasetSearchBarData.tsx | 37 ++++++++++------- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/static/app/views/dashboards/filtersBar.tsx b/static/app/views/dashboards/filtersBar.tsx index ecf1556a270c51..04a060fd9df43c 100644 --- a/static/app/views/dashboards/filtersBar.tsx +++ b/static/app/views/dashboards/filtersBar.tsx @@ -18,9 +18,7 @@ import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import {useUser} from 'sentry/utils/useUser'; import {useUserTeams} from 'sentry/utils/useUserTeams'; -import AddFilter, { - type SupportedDataset, -} from 'sentry/views/dashboards/globalFilter/addFilter'; +import AddFilter from 'sentry/views/dashboards/globalFilter/addFilter'; import {useDatasetSearchBarData} from 'sentry/views/dashboards/hooks/useDatasetSearchBarData'; import {useInvalidateStarredDashboards} from 'sentry/views/dashboards/hooks/useInvalidateStarredDashboards'; import {getDashboardFiltersFromURL} from 'sentry/views/dashboards/utils'; @@ -62,7 +60,7 @@ export default function FiltersBar({ const organization = useOrganization(); const currentUser = useUser(); const {teams: userTeams} = useUserTeams(); - const datasetSearchBarData = useDatasetSearchBarData(); + const getSearchBarData = useDatasetSearchBarData(); const hasEditAccess = checkUserHasEditAccess( currentUser, @@ -152,7 +150,7 @@ export default function FiltersBar({ { updateGlobalFilters( activeGlobalFilters.map(f => @@ -168,7 +166,7 @@ export default function FiltersBar({ /> ))} { updateGlobalFilters([...activeGlobalFilters, newFilter]); }} diff --git a/static/app/views/dashboards/globalFilter/addFilter.tsx b/static/app/views/dashboards/globalFilter/addFilter.tsx index c6407227ea87bb..949208618bf41b 100644 --- a/static/app/views/dashboards/globalFilter/addFilter.tsx +++ b/static/app/views/dashboards/globalFilter/addFilter.tsx @@ -12,18 +12,11 @@ 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 type {DatasetSearchBarData} from 'sentry/views/dashboards/hooks/useDatasetSearchBarData'; +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'; -export type SupportedDataset = - | WidgetType.ERRORS - | WidgetType.SPANS - | WidgetType.LOGS - | WidgetType.RELEASE - | WidgetType.ISSUE; - -export const DATASET_CHOICES = new Map([ +export const DATASET_CHOICES = new Map([ [WidgetType.ERRORS, t('Errors')], [WidgetType.SPANS, t('Spans')], [WidgetType.LOGS, t('Logs')], @@ -34,10 +27,10 @@ export const DATASET_CHOICES = new Map([ const UNSUPPORTED_FIELD_KINDS = [FieldKind.FUNCTION, FieldKind.MEASUREMENT]; export function getDatasetLabel(dataset: WidgetType) { - return DATASET_CHOICES.get(dataset as SupportedDataset) ?? ''; + return DATASET_CHOICES.get(dataset) ?? ''; } -function getTagType(tag: Tag, dataset: SupportedDataset | null) { +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); @@ -46,12 +39,12 @@ function getTagType(tag: Tag, dataset: SupportedDataset | null) { } type AddFilterProps = { - datasetSearchBarData: DatasetSearchBarData; + getSearchBarData: (widgetType: WidgetType) => SearchBarData; onAddFilter: (filter: GlobalFilter) => void; }; -function AddFilter({datasetSearchBarData, onAddFilter}: AddFilterProps) { - const [selectedDataset, setSelectedDataset] = useState(null); +function AddFilter({getSearchBarData, onAddFilter}: AddFilterProps) { + const [selectedDataset, setSelectedDataset] = useState(null); const [selectedFilterKey, setSelectedFilterKey] = useState(null); const [isSelectingFilterKey, setIsSelectingFilterKey] = useState(false); @@ -66,7 +59,7 @@ function AddFilter({datasetSearchBarData, onAddFilter}: AddFilterProps) { const filterKeys: Record = selectedDataset ? Object.fromEntries( - Object.entries(datasetSearchBarData[selectedDataset].getFilterKeys()).filter( + Object.entries(getSearchBarData(selectedDataset).getFilterKeys()).filter( ([key, value]) => !shouldExcludeTracingKeys(key) && (!value.kind || !UNSUPPORTED_FIELD_KINDS.includes(value.kind)) @@ -75,13 +68,15 @@ function AddFilter({datasetSearchBarData, onAddFilter}: AddFilterProps) { : {}; // Get filter keys for the selected dataset - const filterKeyOptions = Object.entries(filterKeys).map(([_, tag]) => { - return { - value: tag.key, - label: prettifyTagKey(tag.key), - trailingItems: {getTagType(tag, selectedDataset)}, - }; - }); + const filterKeyOptions = selectedDataset + ? Object.entries(filterKeys).map(([_, tag]) => { + return { + value: tag.key, + label: prettifyTagKey(tag.key), + trailingItems: {getTagType(tag, selectedDataset)}, + }; + }) + : []; // Footer for filter key selection for adding filters and returning to dataset selection const filterOptionsMenuFooter = ({closeOverlay}: {closeOverlay: () => void}) => ( @@ -132,7 +127,7 @@ function AddFilter({datasetSearchBarData, onAddFilter}: AddFilterProps) { setSelectedFilterKey(filterKeys[option.value] ?? null); return; } - setSelectedDataset(option.value as SupportedDataset); + setSelectedDataset(option.value as WidgetType); setSelectedFilterKey(null); setIsSelectingFilterKey(true); }} diff --git a/static/app/views/dashboards/hooks/useDatasetSearchBarData.tsx b/static/app/views/dashboards/hooks/useDatasetSearchBarData.tsx index 391fa159cba5bb..8ef7d82404a7e7 100644 --- a/static/app/views/dashboards/hooks/useDatasetSearchBarData.tsx +++ b/static/app/views/dashboards/hooks/useDatasetSearchBarData.tsx @@ -5,15 +5,7 @@ import { } from 'sentry/views/dashboards/datasetConfig/base'; import {WidgetType} from 'sentry/views/dashboards/types'; -export type DatasetSearchBarData = { - [WidgetType.ERRORS]: SearchBarData; - [WidgetType.LOGS]: SearchBarData; - [WidgetType.SPANS]: SearchBarData; - [WidgetType.ISSUE]: SearchBarData; - [WidgetType.RELEASE]: SearchBarData; -}; - -export function useDatasetSearchBarData(): DatasetSearchBarData { +export function useDatasetSearchBarData(): (widgetType: WidgetType) => SearchBarData { const {selection} = usePageFilters(); const errorsData = getDatasetConfig(WidgetType.ERRORS).useSearchBarDataProvider!({ @@ -36,11 +28,26 @@ export function useDatasetSearchBarData(): DatasetSearchBarData { pageFilters: selection, }); - return { - [WidgetType.ERRORS]: errorsData, - [WidgetType.LOGS]: logsData, - [WidgetType.SPANS]: spansData, - [WidgetType.ISSUE]: issuesData, - [WidgetType.RELEASE]: releasesData, + const getSearchBarData = (widgetType: WidgetType): SearchBarData => { + switch (widgetType) { + case WidgetType.ERRORS: + return errorsData; + case WidgetType.LOGS: + return logsData; + case WidgetType.SPANS: + return spansData; + case WidgetType.ISSUE: + return issuesData; + case WidgetType.RELEASE: + return releasesData; + default: + return { + getFilterKeySections: () => [], + getFilterKeys: () => ({}), + getTagValues: () => Promise.resolve([]), + }; + } }; + + return getSearchBarData; } From d7cc771a4761ef44caf1f926d00a6d0fc2df7ae9 Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Fri, 17 Oct 2025 15:03:58 -0400 Subject: [PATCH 11/11] fix breaking test after search bar data hook change --- .../globalFilter/addFilter.spec.tsx | 52 ++++--------------- 1 file changed, 9 insertions(+), 43 deletions(-) diff --git a/static/app/views/dashboards/globalFilter/addFilter.spec.tsx b/static/app/views/dashboards/globalFilter/addFilter.spec.tsx index 76aea71e303ce8..82cca789c53665 100644 --- a/static/app/views/dashboards/globalFilter/addFilter.spec.tsx +++ b/static/app/views/dashboards/globalFilter/addFilter.spec.tsx @@ -4,12 +4,8 @@ import type {TagCollection} from 'sentry/types/group'; import {FieldKind} from 'sentry/utils/fields'; import {type SearchBarData} from 'sentry/views/dashboards/datasetConfig/base'; import AddFilter, {DATASET_CHOICES} from 'sentry/views/dashboards/globalFilter/addFilter'; -import {type DatasetSearchBarData} from 'sentry/views/dashboards/hooks/useDatasetSearchBarData'; import {WidgetType} from 'sentry/views/dashboards/types'; -// Mock getDatasetConfig -jest.mock('sentry/views/dashboards/datasetConfig/base'); - describe('AddFilter', () => { // Mock filter keys returned by the search bar data provider const mockFilterKeys: TagCollection = { @@ -35,32 +31,14 @@ describe('AddFilter', () => { }, }; - const createMockSearchBarData = (): SearchBarData => ({ - getFilterKeys: jest.fn(() => mockFilterKeys), - getFilterKeySections: jest.fn(() => []), - getTagValues: jest.fn(() => Promise.resolve([])), - }); - - const mockDatasetSearchBarData: DatasetSearchBarData = { - [WidgetType.ERRORS]: createMockSearchBarData(), - [WidgetType.LOGS]: createMockSearchBarData(), - [WidgetType.SPANS]: createMockSearchBarData(), - [WidgetType.ISSUE]: createMockSearchBarData(), - [WidgetType.RELEASE]: createMockSearchBarData(), - }; - - beforeEach(() => { - MockApiClient.clearMockResponses(); - }); - - afterEach(() => { - jest.clearAllMocks(); + const getSearchBarData = (_: WidgetType): SearchBarData => ({ + getFilterKeys: () => mockFilterKeys, + getFilterKeySections: () => [], + getTagValues: () => Promise.resolve([]), }); it('renders all dataset options', async () => { - render( - {}} /> - ); + render( {}} />); await userEvent.click(screen.getByRole('button', {name: 'Add Global Filter'})); for (const dataset of DATASET_CHOICES.values()) { expect(screen.getByText(dataset)).toBeInTheDocument(); @@ -68,20 +46,15 @@ describe('AddFilter', () => { }); it('retrieves filter keys for each dataset', async () => { - render( - {}} /> - ); + render( {}} />); // Open the add global filter drop down await userEvent.click(screen.getByRole('button', {name: 'Add Global Filter'})); // Verify filter keys are shown for each dataset - for (const [widgetType, datasetLabel] of DATASET_CHOICES.entries()) { + for (const datasetLabel of DATASET_CHOICES.values()) { await userEvent.click(screen.getByText(datasetLabel)); - // Verify corresponding dataset filter key getter was called once - expect(mockDatasetSearchBarData[widgetType].getFilterKeys).toHaveBeenCalledTimes(1); - // Should see filter key options for the dataset expect(screen.getByText('Select Filter Tag')).toBeInTheDocument(); expect(screen.getByText(mockFilterKeys.browser!.key)).toBeInTheDocument(); @@ -93,9 +66,7 @@ describe('AddFilter', () => { }); it('does not render unsupported filter keys', async () => { - render( - {}} /> - ); + render( {}} />); // Open the dropdown and select an arbitrary dataset await userEvent.click(screen.getByRole('button', {name: 'Add Global Filter'})); @@ -112,12 +83,7 @@ describe('AddFilter', () => { it('calls onAddFilter with expected global filter object', async () => { const onAddFilter = jest.fn(); - render( - - ); + render(); // Open add global filter drop down await userEvent.click(screen.getByRole('button', {name: 'Add Global Filter'}));