import React, { FC, useReducer, useState } from 'react'; import { useDebounce } from 'react-use'; import { css } from 'emotion'; import { Icon, useTheme, CustomScrollbar, stylesFactory } from '@grafana/ui'; import { getLocationSrv } from '@grafana/runtime'; import { GrafanaTheme } from '@grafana/data'; import { SearchSrv } from 'app/core/services/search_srv'; import { backendSrv } from 'app/core/services/backend_srv'; import { SearchQuery } from 'app/core/components/search/search'; import { TagFilter } from 'app/core/components/TagFilter/TagFilter'; import { contextSrv } from 'app/core/services/context_srv'; import { DashboardSearchItemType, DashboardSection, OpenSearchParams } from '../types'; import { findSelected, hasId, parseQuery } from '../utils'; import { searchReducer, initialState } from '../reducers/dashboardSearch'; import { getDashboardSrv } from '../../dashboard/services/DashboardSrv'; import { FETCH_ITEMS, FETCH_RESULTS, TOGGLE_SECTION, MOVE_SELECTION_DOWN, MOVE_SELECTION_UP, } from '../reducers/actionTypes'; import { SearchField } from './SearchField'; import { SearchResults } from './SearchResults'; const searchSrv = new SearchSrv(); const defaultQuery: SearchQuery = { query: '', parsedQuery: { text: '' }, tags: [], starred: false }; const { isEditor, hasEditPermissionInFolders } = contextSrv; const canEdit = isEditor || hasEditPermissionInFolders; export interface Props { onCloseSearch: () => void; payload?: OpenSearchParams; } export const DashboardSearch: FC = ({ onCloseSearch, payload = {} }) => { const [query, setQuery] = useState({ ...defaultQuery, ...payload, parsedQuery: parseQuery(payload.query) }); const [{ results, loading }, dispatch] = useReducer(searchReducer, initialState); const theme = useTheme(); const styles = getStyles(theme); const search = () => { let folderIds: number[] = []; if (query.parsedQuery.folder === 'current') { const { folderId } = getDashboardSrv().getCurrent().meta; if (folderId) { folderIds.push(folderId); } } searchSrv.search({ ...query, tag: query.tags, query: query.parsedQuery.text, folderIds }).then(results => { dispatch({ type: FETCH_RESULTS, payload: results }); }); }; useDebounce(search, 300, [query]); const onToggleSection = (section: DashboardSection) => { if (hasId(section.title) && !section.items.length) { backendSrv.search({ ...defaultQuery, folderIds: [section.id] }).then(items => { dispatch({ type: FETCH_ITEMS, payload: { section, items } }); dispatch({ type: TOGGLE_SECTION, payload: section }); }); } else { dispatch({ type: TOGGLE_SECTION, payload: section }); } }; const onQueryChange = (searchQuery: string) => { setQuery(q => ({ ...q, parsedQuery: parseQuery(searchQuery), query: searchQuery, })); }; const onKeyDown = (event: React.KeyboardEvent) => { switch (event.key) { case 'Escape': onCloseSearch(); break; case 'ArrowUp': dispatch({ type: MOVE_SELECTION_UP }); break; case 'ArrowDown': dispatch({ type: MOVE_SELECTION_DOWN }); break; case 'Enter': const selectedItem = findSelected(results); if (selectedItem) { if (selectedItem.type === DashboardSearchItemType.DashFolder) { onToggleSection(selectedItem as DashboardSection); } else { getLocationSrv().update({ path: selectedItem.url }); // Delay closing to prevent current page flicker setTimeout(onCloseSearch, 0); } } } }; // The main search input has own keydown handler, also TagFilter uses input, so // clicking Esc when tagFilter is active shouldn't close the whole search overlay const onClose = (e: React.KeyboardEvent) => { const target = e.target as HTMLElement; if ((target.tagName as any) !== 'INPUT' && ['Escape', 'ArrowLeft'].includes(e.key)) { onCloseSearch(); } }; const onTagFiltersChanged = (tags: string[]) => { setQuery(q => ({ ...q, tags })); }; const onTagSelected = (tag: string) => { if (tag && !query.tags.includes(tag)) { setQuery(q => ({ ...q, tags: [...q.tags, tag] })); } }; const onClearSearchFilters = () => { setQuery(q => ({ ...q, tags: [] })); }; return (
Filter by: {query.tags.length > 0 && ( Clear )}
{canEdit && ( )}
); }; const getStyles = stylesFactory((theme: GrafanaTheme) => { return { closeBtn: css` font-size: 22px; margin-top: 14px; margin-right: 6px; &:hover { cursor: pointer; color: ${theme.palette.white}; } @media only screen and (max-width: ${theme.breakpoints.md}) { position: absolute; right: 15px; top: 60px; } `, icon: css` margin-right: ${theme.spacing.sm}; color: ${theme.palette.blue95}; `, filter: css` margin-right: ${theme.spacing.xs}; `, }; });