mirror of https://github.com/grafana/grafana.git
NestedFolderPicker: Debounce search correctly (#80956)
* debounce nested folder picker search * readd logic when no search string
This commit is contained in:
parent
5b4a984b78
commit
e84ee33c8b
|
@ -1,7 +1,7 @@
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { autoUpdate, flip, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react';
|
import { autoUpdate, flip, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react';
|
||||||
import React, { useCallback, useId, useMemo, useState } from 'react';
|
import debounce from 'debounce-promise';
|
||||||
import { useAsync } from 'react-use';
|
import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
|
@ -19,7 +19,7 @@ import {
|
||||||
} from 'app/features/browse-dashboards/state';
|
} from 'app/features/browse-dashboards/state';
|
||||||
import { getPaginationPlaceholders } from 'app/features/browse-dashboards/state/utils';
|
import { getPaginationPlaceholders } from 'app/features/browse-dashboards/state/utils';
|
||||||
import { DashboardViewItemCollection } from 'app/features/browse-dashboards/types';
|
import { DashboardViewItemCollection } from 'app/features/browse-dashboards/types';
|
||||||
import { getGrafanaSearcher } from 'app/features/search/service';
|
import { QueryResponse, getGrafanaSearcher } from 'app/features/search/service';
|
||||||
import { queryResultToViewItem } from 'app/features/search/service/utils';
|
import { queryResultToViewItem } from 'app/features/search/service/utils';
|
||||||
import { DashboardViewItem } from 'app/features/search/types';
|
import { DashboardViewItem } from 'app/features/search/types';
|
||||||
import { useDispatch, useSelector } from 'app/types/store';
|
import { useDispatch, useSelector } from 'app/types/store';
|
||||||
|
@ -47,6 +47,19 @@ export interface NestedFolderPickerProps {
|
||||||
|
|
||||||
const EXCLUDED_KINDS = ['empty-folder' as const, 'dashboard' as const];
|
const EXCLUDED_KINDS = ['empty-folder' as const, 'dashboard' as const];
|
||||||
|
|
||||||
|
const debouncedSearch = debounce(getSearchResults, 300);
|
||||||
|
|
||||||
|
async function getSearchResults(searchQuery: string) {
|
||||||
|
const queryResponse = await getGrafanaSearcher().search({
|
||||||
|
query: searchQuery,
|
||||||
|
kind: ['folder'],
|
||||||
|
limit: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = queryResponse.view.map((v) => queryResultToViewItem(v, queryResponse.view));
|
||||||
|
return { ...queryResponse, items };
|
||||||
|
}
|
||||||
|
|
||||||
export function NestedFolderPicker({
|
export function NestedFolderPicker({
|
||||||
value,
|
value,
|
||||||
invalid,
|
invalid,
|
||||||
|
@ -62,26 +75,34 @@ export function NestedFolderPicker({
|
||||||
const nestedFoldersEnabled = Boolean(config.featureToggles.nestedFolders);
|
const nestedFoldersEnabled = Boolean(config.featureToggles.nestedFolders);
|
||||||
|
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
const [searchResults, setSearchResults] = useState<(QueryResponse & { items: DashboardViewItem[] }) | null>(null);
|
||||||
|
const [isFetchingSearchResults, setIsFetchingSearchResults] = useState(false);
|
||||||
const [autoFocusButton, setAutoFocusButton] = useState(false);
|
const [autoFocusButton, setAutoFocusButton] = useState(false);
|
||||||
const [overlayOpen, setOverlayOpen] = useState(false);
|
const [overlayOpen, setOverlayOpen] = useState(false);
|
||||||
const [folderOpenState, setFolderOpenState] = useState<Record<string, boolean>>({});
|
const [folderOpenState, setFolderOpenState] = useState<Record<string, boolean>>({});
|
||||||
const overlayId = useId();
|
const overlayId = useId();
|
||||||
const [error] = useState<Error | undefined>(undefined); // TODO: error not populated anymore
|
const [error] = useState<Error | undefined>(undefined); // TODO: error not populated anymore
|
||||||
|
const lastSearchTimestamp = useRef<number>(0);
|
||||||
|
|
||||||
const searchState = useAsync(async () => {
|
useEffect(() => {
|
||||||
if (!search) {
|
if (!search) {
|
||||||
return undefined;
|
setSearchResults(null);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const searcher = getGrafanaSearcher();
|
const timestamp = Date.now();
|
||||||
const queryResponse = await searcher.search({
|
setIsFetchingSearchResults(true);
|
||||||
query: search,
|
debouncedSearch(search).then((queryResponse) => {
|
||||||
kind: ['folder'],
|
// Only keep the results if it's was issued after the most recently resolved search.
|
||||||
limit: 100,
|
// This prevents results showing out of order if first request is slower than later ones.
|
||||||
|
// We don't need to worry about clearing the isFetching state either - if there's a later
|
||||||
|
// request in progress, this will clear it for us
|
||||||
|
if (timestamp > lastSearchTimestamp.current) {
|
||||||
|
const items = queryResponse.view.map((v) => queryResultToViewItem(v, queryResponse.view));
|
||||||
|
setSearchResults({ ...queryResponse, items });
|
||||||
|
setIsFetchingSearchResults(false);
|
||||||
|
lastSearchTimestamp.current = timestamp;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const items = queryResponse.view.map((v) => queryResultToViewItem(v, queryResponse.view));
|
|
||||||
|
|
||||||
return { ...queryResponse, items };
|
|
||||||
}, [search]);
|
}, [search]);
|
||||||
|
|
||||||
const rootCollection = useSelector(rootItemsSelector);
|
const rootCollection = useSelector(rootItemsSelector);
|
||||||
|
@ -152,9 +173,7 @@ export function NestedFolderPicker({
|
||||||
);
|
);
|
||||||
|
|
||||||
const flatTree = useMemo(() => {
|
const flatTree = useMemo(() => {
|
||||||
const searchResults = search && searchState.value;
|
if (search && searchResults) {
|
||||||
|
|
||||||
if (searchResults) {
|
|
||||||
const searchCollection: DashboardViewItemCollection = {
|
const searchCollection: DashboardViewItemCollection = {
|
||||||
isFullyLoaded: true, //searchResults.items.length === searchResults.totalRows,
|
isFullyLoaded: true, //searchResults.items.length === searchResults.totalRows,
|
||||||
lastKindHasMoreItems: false, // TODO: paginate search
|
lastKindHasMoreItems: false, // TODO: paginate search
|
||||||
|
@ -203,7 +222,7 @@ export function NestedFolderPicker({
|
||||||
}
|
}
|
||||||
|
|
||||||
return flatTree;
|
return flatTree;
|
||||||
}, [search, searchState.value, rootCollection, childrenCollections, folderOpenState, excludeUIDs, showRootFolder]);
|
}, [search, searchResults, rootCollection, childrenCollections, folderOpenState, excludeUIDs, showRootFolder]);
|
||||||
|
|
||||||
const isItemLoaded = useCallback(
|
const isItemLoaded = useCallback(
|
||||||
(itemIndex: number) => {
|
(itemIndex: number) => {
|
||||||
|
@ -219,7 +238,7 @@ export function NestedFolderPicker({
|
||||||
[flatTree]
|
[flatTree]
|
||||||
);
|
);
|
||||||
|
|
||||||
const isLoading = rootStatus === 'pending' || searchState.loading;
|
const isLoading = rootStatus === 'pending' || isFetchingSearchResults;
|
||||||
|
|
||||||
const { focusedItemIndex, handleKeyDown } = useTreeInteractions({
|
const { focusedItemIndex, handleKeyDown } = useTreeInteractions({
|
||||||
tree: flatTree,
|
tree: flatTree,
|
||||||
|
@ -311,7 +330,7 @@ export function NestedFolderPicker({
|
||||||
onFolderExpand={handleFolderExpand}
|
onFolderExpand={handleFolderExpand}
|
||||||
onFolderSelect={handleFolderSelect}
|
onFolderSelect={handleFolderSelect}
|
||||||
idPrefix={overlayId}
|
idPrefix={overlayId}
|
||||||
foldersAreOpenable={nestedFoldersEnabled && !(search && searchState.value)}
|
foldersAreOpenable={nestedFoldersEnabled && !(search && searchResults)}
|
||||||
isItemLoaded={isItemLoaded}
|
isItemLoaded={isItemLoaded}
|
||||||
requestLoadMore={handleLoadMore}
|
requestLoadMore={handleLoadMore}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in New Issue