diff --git a/packages/scenes/src/variables/adhoc/AdHocFiltersCombobox/AdHocFiltersCombobox.tsx b/packages/scenes/src/variables/adhoc/AdHocFiltersCombobox/AdHocFiltersCombobox.tsx index 9e73e6b7a..db22ebd9c 100644 --- a/packages/scenes/src/variables/adhoc/AdHocFiltersCombobox/AdHocFiltersCombobox.tsx +++ b/packages/scenes/src/variables/adhoc/AdHocFiltersCombobox/AdHocFiltersCombobox.tsx @@ -25,7 +25,7 @@ import { import { ERROR_STATE_DROPDOWN_WIDTH, flattenOptionGroups, - fuzzySearchOptions, + searchOptions, generateFilterUpdatePayload, generatePlaceholder, populateInputValueOnInputTypeSwitch, @@ -81,7 +81,7 @@ export const AdHocCombobox = forwardRef(function AdHocCombobox( const disabledIndicesRef = useRef([]); const filterInputTypeRef = useRef(!isAlwaysWip ? 'value' : 'key'); - const optionsSearcher = useMemo(() => fuzzySearchOptions(options), [options]); + const optionsSearcher = useMemo(() => searchOptions(options), [options]); const isLastFilter = useMemo(() => { if (isAlwaysWip) { diff --git a/packages/scenes/src/variables/adhoc/AdHocFiltersCombobox/utils.ts b/packages/scenes/src/variables/adhoc/AdHocFiltersCombobox/utils.ts index 78f0078d9..643a45843 100644 --- a/packages/scenes/src/variables/adhoc/AdHocFiltersCombobox/utils.ts +++ b/packages/scenes/src/variables/adhoc/AdHocFiltersCombobox/utils.ts @@ -11,11 +11,19 @@ export const VIRTUAL_LIST_ITEM_HEIGHT = 38; export const VIRTUAL_LIST_ITEM_HEIGHT_WITH_DESCRIPTION = 60; export const ERROR_STATE_DROPDOWN_WIDTH = 366; -export function fuzzySearchOptions(options: Array>) { +// https://catonmat.net/my-favorite-regex :) +const REGEXP_NON_ASCII = /[^ -~]/m; + +export function searchOptions(options: Array>) { const haystack = options.map((o) => o.label ?? o.value!); const fuzzySearch = getFuzzySearcher(haystack); return (search: string, filterInputType: AdHocInputType) => { + // fall back to substring matching for non-latin chars + if (REGEXP_NON_ASCII.test(search)) { + return options.filter((o) => o.label?.includes(search) || o.value?.includes(search) || false); + } + if (filterInputType === 'operator' && search !== '') { // uFuzzy can only match non-alphanum chars if quoted search = `"${search}"`; diff --git a/packages/scenes/src/variables/utils.test.ts b/packages/scenes/src/variables/utils.test.ts index dab074e39..e6566d6e8 100644 --- a/packages/scenes/src/variables/utils.test.ts +++ b/packages/scenes/src/variables/utils.test.ts @@ -8,6 +8,8 @@ import { getFuzzySearcher, getQueriesForVariables } from './utils'; import { SceneVariableSet } from './sets/SceneVariableSet'; import { DataSourceVariable } from './variants/DataSourceVariable'; import { GetDataSourceListFilters } from '@grafana/runtime'; +import { searchOptions } from './adhoc/AdHocFiltersCombobox/utils'; +import { SelectableValue } from '@grafana/data'; describe('getQueriesForVariables', () => { it('should resolve queries', () => { @@ -289,6 +291,24 @@ describe('getFuzzySearcher orders by match quality with case-sensitivity', () => }); }); +describe('searchOptions falls back to substring matching for non-latin needles', () => { + it('Can filter options by search query', async () => { + const options: SelectableValue[] = [ + '台灣省', + '台中市', + '台北市', + '台南市', + '南投縣', + '高雄市', + '台中第一高級中學', + ].map((v) => ({ label: v, value: v })); + + const searcher = searchOptions(options); + + expect(searcher('南', 'key').map((o) => o.label!)).toEqual(['台南市', '南投縣']); + }); +}); + interface TestObjectState extends SceneObjectState { datasource: DataSourceRef | null; }