Dashboards: Fix folder picker not showing correct results when typing too fast (#50303)

* Use callback form for loadOptions in folder picker

* clean up

* fix other tests

* clarify comment
This commit is contained in:
Josh Hunt 2022-06-10 15:54:21 +01:00 committed by GitHub
parent 307a0d4538
commit 32d4f6ac60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 20 additions and 6 deletions

View File

@ -8,6 +8,7 @@ export type ActionMeta = SelectActionMeta<{}>;
export type InputActionMeta = { export type InputActionMeta = {
action: 'set-value' | 'input-change' | 'input-blur' | 'menu-close'; action: 'set-value' | 'input-change' | 'input-blur' | 'menu-close';
}; };
export type LoadOptionsCallback<T> = (options: Array<SelectableValue<T>>) => void;
export interface SelectCommonProps<T> { export interface SelectCommonProps<T> {
/** Aria label applied to the input field */ /** Aria label applied to the input field */
@ -87,8 +88,10 @@ export interface SelectCommonProps<T> {
export interface SelectAsyncProps<T> { export interface SelectAsyncProps<T> {
/** When specified as boolean the loadOptions will execute when component is mounted */ /** When specified as boolean the loadOptions will execute when component is mounted */
defaultOptions?: boolean | Array<SelectableValue<T>>; defaultOptions?: boolean | Array<SelectableValue<T>>;
/** Asynchronously load select options */ /** Asynchronously load select options */
loadOptions?: (query: string) => Promise<Array<SelectableValue<T>>>; loadOptions?: (query: string, cb?: LoadOptionsCallback<T>) => Promise<Array<SelectableValue<T>>> | void;
/** If cacheOptions is true, then the loaded data will be cached. The cache will remain until cacheOptions changes value. */ /** If cacheOptions is true, then the loaded data will be cached. The cache will remain until cacheOptions changes value. */
cacheOptions?: boolean; cacheOptions?: boolean;
/** Message to display when options are loading */ /** Message to display when options are loading */

View File

@ -3,7 +3,7 @@ import React, { PureComponent } from 'react';
import { AppEvents, SelectableValue } from '@grafana/data'; import { AppEvents, SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { ActionMeta, AsyncSelect } from '@grafana/ui'; import { ActionMeta, AsyncSelect, LoadOptionsCallback } from '@grafana/ui';
import { contextSrv } from 'app/core/services/context_srv'; import { contextSrv } from 'app/core/services/context_srv';
import { createFolder, getFolderById, searchFolders } from 'app/features/manage-dashboards/state/actions'; import { createFolder, getFolderById, searchFolders } from 'app/features/manage-dashboards/state/actions';
import { DashboardSearchHit } from 'app/features/search/types'; import { DashboardSearchHit } from 'app/features/search/types';
@ -52,7 +52,7 @@ export class FolderPicker extends PureComponent<Props, State> {
folder: null, folder: null,
}; };
this.debouncedSearch = debounce(this.getOptions, 300, { this.debouncedSearch = debounce(this.loadOptions, 300, {
leading: true, leading: true,
trailing: true, trailing: true,
}); });
@ -82,7 +82,13 @@ export class FolderPicker extends PureComponent<Props, State> {
await this.loadInitialValue(); await this.loadInitialValue();
}; };
getOptions = async (query: string) => { // when debouncing, we must use the callback form of react-select's loadOptions so we don't
// drop results for user input. This must not return a promise/use await.
loadOptions = (query: string, callback: LoadOptionsCallback<number>): void => {
this.searchFolders(query).then(callback);
};
private searchFolders = async (query: string) => {
const { const {
rootName, rootName,
enableReset, enableReset,
@ -159,7 +165,7 @@ export class FolderPicker extends PureComponent<Props, State> {
const resetFolder: SelectableValue<number> = { label: initialTitle, value: undefined }; const resetFolder: SelectableValue<number> = { label: initialTitle, value: undefined };
const rootFolder: SelectableValue<number> = { label: rootName, value: 0 }; const rootFolder: SelectableValue<number> = { label: rootName, value: 0 };
const options = await this.getOptions(''); const options = await this.searchFolders('');
let folder: SelectableValue<number> | null = null; let folder: SelectableValue<number> | null = null;

View File

@ -19,7 +19,7 @@ jest.mock('@grafana/runtime', () => ({
})); }));
setBackendSrv({ setBackendSrv({
get: jest.fn().mockResolvedValue({}), get: jest.fn().mockResolvedValue([]),
} as any); } as any);
describe('DashboardSettings', () => { describe('DashboardSettings', () => {

View File

@ -5,11 +5,16 @@ import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
import { byRole } from 'testing-library-selector'; import { byRole } from 'testing-library-selector';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { setBackendSrv } from '@grafana/runtime';
import { DashboardModel } from '../../state'; import { DashboardModel } from '../../state';
import { GeneralSettingsUnconnected as GeneralSettings, Props } from './GeneralSettings'; import { GeneralSettingsUnconnected as GeneralSettings, Props } from './GeneralSettings';
setBackendSrv({
get: jest.fn().mockResolvedValue([]),
} as any);
const setupTestContext = (options: Partial<Props>) => { const setupTestContext = (options: Partial<Props>) => {
const defaults: Props = { const defaults: Props = {
dashboard: { dashboard: {