import { css } from '@emotion/css'; import React, { useCallback, useMemo, useState } from 'react'; import { useAsync } from 'react-use'; import AutoSizer from 'react-virtualized-auto-sizer'; import { Observable } from 'rxjs'; import { GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; import { useStyles2, Spinner, Button } from '@grafana/ui'; import { TermCount } from 'app/core/components/TagFilter/TagFilter'; import { FolderDTO } from 'app/types'; import { PreviewsSystemRequirements } from '../../components/PreviewsSystemRequirements'; import { useSearchQuery } from '../../hooks/useSearchQuery'; import { getGrafanaSearcher, SearchQuery } from '../../service'; import { SearchLayout } from '../../types'; import { newSearchSelection, updateSearchSelection } from '../selection'; import { ActionRow, getValidQueryLayout } from './ActionRow'; import { FolderSection } from './FolderSection'; import { FolderView } from './FolderView'; import { ManageActions } from './ManageActions'; import { SearchResultsGrid } from './SearchResultsGrid'; import { SearchResultsTable, SearchResultsProps } from './SearchResultsTable'; type SearchViewProps = { queryText: string; // odd that it is not from query.query showManage: boolean; folderDTO?: FolderDTO; hidePseudoFolders?: boolean; // Recent + starred onQueryTextChange: (newQueryText: string) => void; includePanels: boolean; setIncludePanels: (v: boolean) => void; keyboardEvents: Observable; }; export const SearchView = ({ showManage, folderDTO, queryText, hidePseudoFolders, onQueryTextChange, includePanels, setIncludePanels, keyboardEvents, }: SearchViewProps) => { const styles = useStyles2(getStyles); const { query, onTagFilterChange, onTagAdd, onDatasourceChange, onSortChange, onLayoutChange } = useSearchQuery({}); query.query = queryText; // Use the query value passed in from parent rather than from URL const [searchSelection, setSearchSelection] = useState(newSearchSelection()); const layout = getValidQueryLayout(query); const isFolders = layout === SearchLayout.Folders; const [listKey, setListKey] = useState(Date.now()); const searchQuery = useMemo(() => { const q: SearchQuery = { query: queryText, tags: query.tag as string[], ds_uid: query.datasource as string, location: folderDTO?.uid, // This will scope all results to the prefix sort: query.sort?.value, }; // Only dashboards have additional properties if (q.sort?.length && !q.sort.includes('name')) { q.kind = ['dashboard', 'folder']; // skip panels } if (!q.query?.length) { q.query = '*'; if (!q.location) { q.kind = ['dashboard', 'folder']; // skip panels } } if (!includePanels && !q.kind) { q.kind = ['dashboard', 'folder']; // skip panels } if (q.query === '*' && !q.sort?.length) { q.sort = 'name_sort'; } return q; }, [query, queryText, folderDTO, includePanels]); const results = useAsync(() => { return getGrafanaSearcher().search(searchQuery); }, [searchQuery]); const clearSelection = useCallback(() => { searchSelection.items.clear(); setSearchSelection({ ...searchSelection }); }, [searchSelection]); const toggleSelection = useCallback( (kind: string, uid: string) => { const current = searchSelection.isSelected(kind, uid); setSearchSelection(updateSearchSelection(searchSelection, !current, kind, [uid])); }, [searchSelection] ); if (!config.featureToggles.panelTitleSearch) { return
Unsupported
; } // This gets the possible tags from within the query results const getTagOptions = (): Promise => { return getGrafanaSearcher().tags(searchQuery); }; // function to update items when dashboards or folders are moved or deleted const onChangeItemsList = async () => { // clean up search selection clearSelection(); setListKey(Date.now()); // trigger again the search to the backend onQueryTextChange(query.query); }; const renderResults = () => { const value = results.value; if ((!value || !value.totalRows) && !isFolders) { if (results.loading && !value) { return ; } return (
No results found for your query.

); } const selection = showManage ? searchSelection.isSelected : undefined; if (layout === SearchLayout.Folders) { if (folderDTO) { return ( ); } return ( ); } return (
{({ width, height }) => { const props: SearchResultsProps = { response: value!, selection, selectionToggle: toggleSelection, clearSelection, width: width, height: height, onTagSelected: onTagAdd, keyboardEvents, onDatasourceChange: query.datasource ? onDatasourceChange : undefined, }; if (layout === SearchLayout.Grid) { return ; } return ; }}
); }; if (!config.featureToggles.panelTitleSearch) { return
Unsupported
; } return ( <> {Boolean(searchSelection.items.size > 0) ? ( ) : ( { if (v === SearchLayout.Folders) { if (query.query) { onQueryTextChange(''); // parent will clear the sort } } onLayoutChange(v); }} onSortChange={onSortChange} onTagFilterChange={onTagFilterChange} getTagOptions={getTagOptions} getSortOptions={getGrafanaSearcher().getSortOptions} onDatasourceChange={onDatasourceChange} query={query} includePanels={includePanels!} setIncludePanels={setIncludePanels} /> )} {layout === SearchLayout.Grid && ( onLayoutChange(SearchLayout.List)} /> )} {renderResults()} ); }; const getStyles = (theme: GrafanaTheme2) => ({ searchInput: css` margin-bottom: 6px; min-height: ${theme.spacing(4)}; `, unsupported: css` padding: 10px; display: flex; align-items: center; justify-content: center; height: 100%; font-size: 18px; `, noResults: css` padding: ${theme.v1.spacing.md}; background: ${theme.v1.colors.bg2}; font-style: italic; margin-top: ${theme.v1.spacing.md}; `, });