mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Command palette: Enable folder searching (#62663)
search folders as well as dashboards in the command palette
This commit is contained in:
parent
d3bc39be8f
commit
36c090416a
@ -21,7 +21,7 @@ import { t } from 'app/core/internationalization';
|
|||||||
|
|
||||||
import { KBarResults } from './KBarResults';
|
import { KBarResults } from './KBarResults';
|
||||||
import { ResultItem } from './ResultItem';
|
import { ResultItem } from './ResultItem';
|
||||||
import { useDashboardResults } from './actions/dashboardActions';
|
import { useSearchResults } from './actions/dashboardActions';
|
||||||
import useActions from './actions/useActions';
|
import useActions from './actions/useActions';
|
||||||
import { CommandPaletteAction } from './types';
|
import { CommandPaletteAction } from './types';
|
||||||
import { useMatches } from './useMatches';
|
import { useMatches } from './useMatches';
|
||||||
@ -36,7 +36,7 @@ export function CommandPalette() {
|
|||||||
|
|
||||||
const actions = useActions(searchQuery);
|
const actions = useActions(searchQuery);
|
||||||
useRegisterActions(actions, [actions]);
|
useRegisterActions(actions, [actions]);
|
||||||
const { dashboardResults, isFetchingDashboardResults } = useDashboardResults(searchQuery, showing);
|
const { searchResults, isFetchingSearchResults } = useSearchResults(searchQuery, showing);
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const { overlayProps } = useOverlay(
|
const { overlayProps } = useOverlay(
|
||||||
@ -58,14 +58,14 @@ export function CommandPalette() {
|
|||||||
<FocusScope contain autoFocus restoreFocus>
|
<FocusScope contain autoFocus restoreFocus>
|
||||||
<div {...overlayProps} {...dialogProps}>
|
<div {...overlayProps} {...dialogProps}>
|
||||||
<div className={styles.searchContainer}>
|
<div className={styles.searchContainer}>
|
||||||
{isFetchingDashboardResults ? <Spinner className={styles.spinner} /> : <Icon name="search" size="md" />}
|
{isFetchingSearchResults ? <Spinner className={styles.spinner} /> : <Icon name="search" size="md" />}
|
||||||
<KBarSearch
|
<KBarSearch
|
||||||
defaultPlaceholder={t('command-palette.search-box.placeholder', 'Search or jump to...')}
|
defaultPlaceholder={t('command-palette.search-box.placeholder', 'Search or jump to...')}
|
||||||
className={styles.search}
|
className={styles.search}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.resultsContainer}>
|
<div className={styles.resultsContainer}>
|
||||||
<RenderResults dashboardResults={dashboardResults} />
|
<RenderResults searchResults={searchResults} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FocusScope>
|
</FocusScope>
|
||||||
@ -76,24 +76,43 @@ export function CommandPalette() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface RenderResultsProps {
|
interface RenderResultsProps {
|
||||||
dashboardResults: CommandPaletteAction[];
|
searchResults: CommandPaletteAction[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const RenderResults = ({ dashboardResults }: RenderResultsProps) => {
|
const RenderResults = ({ searchResults }: RenderResultsProps) => {
|
||||||
const { results, rootActionId } = useMatches();
|
const { results: kbarResults, rootActionId } = useMatches();
|
||||||
const styles = useStyles2(getSearchStyles);
|
const styles = useStyles2(getSearchStyles);
|
||||||
const dashboardsSectionTitle = t('command-palette.section.dashboard-search-results', 'Dashboards');
|
const dashboardsSectionTitle = t('command-palette.section.dashboard-search-results', 'Dashboards');
|
||||||
|
const foldersSectionTitle = t('command-palette.section.folder-search-results', 'Folders');
|
||||||
// because dashboard search results aren't registered as actions, we need to manually
|
// because dashboard search results aren't registered as actions, we need to manually
|
||||||
// convert them to ActionImpls before passing them as items to KBarResults
|
// convert them to ActionImpls before passing them as items to KBarResults
|
||||||
const dashboardResultItems = useMemo(
|
const dashboardResultItems = useMemo(
|
||||||
() => dashboardResults.map((dashboard) => new ActionImpl(dashboard, { store: {} })),
|
() =>
|
||||||
[dashboardResults]
|
searchResults
|
||||||
|
.filter((item) => item.id.startsWith('go/dashboard'))
|
||||||
|
.map((dashboard) => new ActionImpl(dashboard, { store: {} })),
|
||||||
|
[searchResults]
|
||||||
|
);
|
||||||
|
const folderResultItems = useMemo(
|
||||||
|
() =>
|
||||||
|
searchResults
|
||||||
|
.filter((item) => item.id.startsWith('go/folder'))
|
||||||
|
.map((folder) => new ActionImpl(folder, { store: {} })),
|
||||||
|
[searchResults]
|
||||||
);
|
);
|
||||||
|
|
||||||
const items = useMemo(
|
const items = useMemo(() => {
|
||||||
() => (dashboardResultItems.length > 0 ? [...results, dashboardsSectionTitle, ...dashboardResultItems] : results),
|
const results = [...kbarResults];
|
||||||
[results, dashboardsSectionTitle, dashboardResultItems]
|
if (folderResultItems.length > 0) {
|
||||||
);
|
results.push(foldersSectionTitle);
|
||||||
|
results.push(...folderResultItems);
|
||||||
|
}
|
||||||
|
if (dashboardResultItems.length > 0) {
|
||||||
|
results.push(dashboardsSectionTitle);
|
||||||
|
results.push(...dashboardResultItems);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}, [kbarResults, dashboardsSectionTitle, dashboardResultItems, foldersSectionTitle, folderResultItems]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KBarResults
|
<KBarResults
|
||||||
|
@ -12,7 +12,7 @@ import { RECENT_DASHBOARDS_PRORITY, SEARCH_RESULTS_PRORITY } from '../values';
|
|||||||
const MAX_SEARCH_RESULTS = 100;
|
const MAX_SEARCH_RESULTS = 100;
|
||||||
const MAX_RECENT_DASHBOARDS = 5;
|
const MAX_RECENT_DASHBOARDS = 5;
|
||||||
|
|
||||||
const debouncedDashboardSearch = debounce(getDashboardSearchResultActions, 200);
|
const debouncedSearch = debounce(getSearchResultActions, 200);
|
||||||
|
|
||||||
export async function getRecentDashboardActions(): Promise<CommandPaletteAction[]> {
|
export async function getRecentDashboardActions(): Promise<CommandPaletteAction[]> {
|
||||||
const recentUids = (await impressionSrv.getDashboardOpened()).slice(0, MAX_RECENT_DASHBOARDS);
|
const recentUids = (await impressionSrv.getDashboardOpened()).slice(0, MAX_RECENT_DASHBOARDS);
|
||||||
@ -44,51 +44,54 @@ export async function getRecentDashboardActions(): Promise<CommandPaletteAction[
|
|||||||
return recentDashboardActions;
|
return recentDashboardActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDashboardSearchResultActions(searchQuery: string): Promise<CommandPaletteAction[]> {
|
export async function getSearchResultActions(searchQuery: string): Promise<CommandPaletteAction[]> {
|
||||||
// Empty strings should not come through to here
|
// Empty strings should not come through to here
|
||||||
if (searchQuery.length === 0) {
|
if (searchQuery.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await getGrafanaSearcher().search({
|
const data = await getGrafanaSearcher().search({
|
||||||
kind: ['dashboard'],
|
kind: ['dashboard', 'folder'],
|
||||||
query: searchQuery,
|
query: searchQuery,
|
||||||
limit: MAX_SEARCH_RESULTS,
|
limit: MAX_SEARCH_RESULTS,
|
||||||
});
|
});
|
||||||
|
|
||||||
const goToDashboardActions: CommandPaletteAction[] = data.view.map((item) => {
|
const goToSearchResultActions: CommandPaletteAction[] = data.view.map((item) => {
|
||||||
const { url, name } = item; // items are backed by DataFrameView, so must hold the url in a closure
|
const { url, name, kind } = item; // items are backed by DataFrameView, so must hold the url in a closure
|
||||||
return {
|
return {
|
||||||
id: `go/dashboard${url}`,
|
id: `go/${kind}${url}`,
|
||||||
name: `${name}`,
|
name: `${name}`,
|
||||||
section: t('command-palette.section.dashboard-search-results', 'Dashboards'),
|
section:
|
||||||
|
kind === 'dashboard'
|
||||||
|
? t('command-palette.section.dashboard-search-results', 'Dashboards')
|
||||||
|
: t('command-palette.section.folder-search-results', 'Folders'),
|
||||||
priority: SEARCH_RESULTS_PRORITY,
|
priority: SEARCH_RESULTS_PRORITY,
|
||||||
url: locationUtil.stripBaseFromUrl(url),
|
url: locationUtil.stripBaseFromUrl(url),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return goToDashboardActions;
|
return goToSearchResultActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDashboardResults(searchQuery: string, isShowing: boolean) {
|
export function useSearchResults(searchQuery: string, isShowing: boolean) {
|
||||||
const [dashboardResults, setDashboardResults] = useState<CommandPaletteAction[]>([]);
|
const [searchResults, setSearchResults] = useState<CommandPaletteAction[]>([]);
|
||||||
const [isFetchingDashboardResults, setIsFetchingDashboardResults] = useState(false);
|
const [isFetchingSearchResults, setIsFetchingSearchResults] = useState(false);
|
||||||
|
|
||||||
// Hit dashboards API
|
// Hit dashboards API
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isShowing && searchQuery.length > 0) {
|
if (isShowing && searchQuery.length > 0) {
|
||||||
setIsFetchingDashboardResults(true);
|
setIsFetchingSearchResults(true);
|
||||||
debouncedDashboardSearch(searchQuery).then((resultActions) => {
|
debouncedSearch(searchQuery).then((resultActions) => {
|
||||||
setDashboardResults(resultActions);
|
setSearchResults(resultActions);
|
||||||
setIsFetchingDashboardResults(false);
|
setIsFetchingSearchResults(false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setDashboardResults([]);
|
setSearchResults([]);
|
||||||
}
|
}
|
||||||
}, [isShowing, searchQuery]);
|
}, [isShowing, searchQuery]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dashboardResults,
|
searchResults,
|
||||||
isFetchingDashboardResults,
|
isFetchingSearchResults,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user