D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
usr
/
share
/
grafana
/
public
/
app
/
features
/
search
/
state
/
Filename :
SearchStateManager.ts
back
Copy
import { debounce } from 'lodash'; import { FormEvent } from 'react'; import { locationService } from '@grafana/runtime'; import { TermCount } from 'app/core/components/TagFilter/TagFilter'; import { StateManagerBase } from 'app/core/services/StateManagerBase'; import store from 'app/core/store'; import { SEARCH_PANELS_LOCAL_STORAGE_KEY, SEARCH_SELECTED_LAYOUT, SEARCH_SELECTED_SORT } from '../constants'; import { reportDashboardListViewed, reportSearchFailedQueryInteraction, reportSearchQueryInteraction, reportSearchResultInteraction, } from '../page/reporting'; import { getGrafanaSearcher, SearchQuery } from '../service'; import { SearchLayout, SearchQueryParams, SearchState } from '../types'; import { parseRouteParams } from '../utils'; export const initialState: SearchState = { query: '', tag: [], starred: false, layout: SearchLayout.Folders, sort: undefined, prevSort: undefined, eventTrackingNamespace: 'dashboard_search', }; export const defaultQueryParams: SearchQueryParams = { sort: null, starred: null, query: null, tag: null, layout: null, }; export class SearchStateManager extends StateManagerBase<SearchState> { updateLocation = debounce((query) => locationService.partial(query, true), 300); doSearchWithDebounce = debounce(() => this.doSearch(), 300); lastQuery?: SearchQuery; lastSearchTimestamp = 0; initStateFromUrl(folderUid?: string, doInitialSearch = true) { const stateFromUrl = parseRouteParams(locationService.getSearchObject()); // Force list view when conditions are specified from the URL if (stateFromUrl.query || stateFromUrl.datasource || stateFromUrl.panel_type) { stateFromUrl.layout = SearchLayout.List; } stateManager.setState({ ...initialState, ...stateFromUrl, folderUid: folderUid, eventTrackingNamespace: folderUid ? 'manage_dashboards' : 'dashboard_search', }); if (doInitialSearch) { this.doSearch(); } } /** * Updates internal and url state, then triggers a new search */ setStateAndDoSearch(state: Partial<SearchState>) { const sort = state.sort || this.state.sort || localStorage.getItem(SEARCH_SELECTED_SORT) || undefined; // Set internal state this.setState({ sort, ...state }); // Update url state this.updateLocation({ query: this.state.query.length === 0 ? null : this.state.query, tag: this.state.tag, datasource: this.state.datasource, panel_type: this.state.panel_type, starred: this.state.starred ? this.state.starred : null, sort: this.state.sort, }); // issue new search query this.doSearchWithDebounce(); } onCloseSearch = () => { this.updateLocation({ search: null, folder: null, ...defaultQueryParams, }); }; onClearSearchAndFilters = () => { this.setStateAndDoSearch({ query: '', datasource: undefined, tag: [], panel_type: undefined, starred: undefined, sort: undefined, }); }; onQueryChange = (query: string) => { this.setStateAndDoSearch({ query }); }; onRemoveTag = (tagToRemove: string) => { this.setStateAndDoSearch({ tag: this.state.tag.filter((tag) => tag !== tagToRemove) }); }; onTagFilterChange = (tags: string[]) => { this.setStateAndDoSearch({ tag: tags }); }; onAddTag = (newTag: string) => { if (this.state.tag && this.state.tag.includes(newTag)) { return; } this.setStateAndDoSearch({ tag: [...this.state.tag, newTag] }); }; onDatasourceChange = (datasource: string | undefined) => { this.setStateAndDoSearch({ datasource }); }; onPanelTypeChange = (panel_type?: string) => { this.setStateAndDoSearch({ panel_type }); }; onStarredFilterChange = (e: FormEvent<HTMLInputElement>) => { const starred = e.currentTarget.checked; this.setStateAndDoSearch({ starred }); }; onClearStarred = () => { this.setStateAndDoSearch({ starred: false }); }; onSortChange = (sort: string | undefined) => { if (sort) { localStorage.setItem(SEARCH_SELECTED_SORT, sort); } else { localStorage.removeItem(SEARCH_SELECTED_SORT); } if (this.state.layout === SearchLayout.Folders) { this.setStateAndDoSearch({ sort, layout: SearchLayout.List }); } else { this.setStateAndDoSearch({ sort }); } }; onLayoutChange = (layout: SearchLayout) => { localStorage.setItem(SEARCH_SELECTED_LAYOUT, layout); if (this.state.sort && layout === SearchLayout.Folders) { this.setStateAndDoSearch({ layout, prevSort: this.state.sort, sort: undefined }); } else { this.setStateAndDoSearch({ layout, sort: this.state.prevSort }); } }; onSetIncludePanels = (includePanels: boolean) => { this.setStateAndDoSearch({ includePanels }); store.set(SEARCH_PANELS_LOCAL_STORAGE_KEY, includePanels); }; hasSearchFilters() { return this.state.query || this.state.tag.length || this.state.starred || this.state.panel_type || this.state.sort; } getSearchQuery() { const q: SearchQuery = { query: this.state.query, tags: this.state.tag, ds_uid: this.state.datasource, panel_type: this.state.panel_type, location: this.state.folderUid, // This will scope all results to the prefix sort: this.state.sort, explain: this.state.explain, withAllowedActions: this.state.explain, // allowedActions are currently not used for anything on the UI and added only in `explain` mode starred: this.state.starred, }; // 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 (!this.state.includePanels && !q.kind) { q.kind = ['dashboard', 'folder']; // skip panels } if (q.panel_type?.length) { q.kind = ['panel']; } return q; } private doSearch() { const trackingInfo = { layout: this.state.layout, starred: this.state.starred, sortValue: this.state.sort, query: this.state.query, tagCount: this.state.tag?.length, includePanels: this.state.includePanels, }; reportSearchQueryInteraction(this.state.eventTrackingNamespace, trackingInfo); this.lastQuery = this.getSearchQuery(); this.setState({ loading: true }); const searcher = getGrafanaSearcher(); const searchTimestamp = Date.now(); const searchPromise = this.state.starred ? searcher.starred(this.lastQuery) : searcher.search(this.lastQuery); searchPromise .then((result) => { // Only keep the results if it's was issued after the most recently resolved search. // This prevents results showing out of order if first request is slower than later ones if (searchTimestamp > this.lastSearchTimestamp) { this.setState({ result, loading: false }); this.lastSearchTimestamp = searchTimestamp; } }) .catch((error) => { reportSearchFailedQueryInteraction(this.state.eventTrackingNamespace, { ...trackingInfo, error: error?.message, }); this.setState({ loading: false }); }); } // This gets the possible tags from within the query results getTagOptions = (): Promise<TermCount[]> => { const query = this.lastQuery ?? { kind: ['dashboard', 'folder'], query: '*', }; return getGrafanaSearcher().tags(query); }; /** * When item is selected clear some filters and report interaction */ onSearchItemClicked = (e: React.MouseEvent<HTMLElement>) => { reportSearchResultInteraction(this.state.eventTrackingNamespace, { layout: this.state.layout, starred: this.state.starred, sortValue: this.state.sort, query: this.state.query, tagCount: this.state.tag?.length, includePanels: this.state.includePanels, }); }; /** * Caller should handle debounce */ onReportSearchUsage = () => { reportDashboardListViewed(this.state.eventTrackingNamespace, { layout: this.state.layout, starred: this.state.starred, sortValue: this.state.sort, query: this.state.query, tagCount: this.state.tag?.length, includePanels: this.state.includePanels, }); }; } let stateManager: SearchStateManager; export function getSearchStateManager() { if (!stateManager) { const selectedLayout = localStorage.getItem(SEARCH_SELECTED_LAYOUT) as SearchLayout; const layout = selectedLayout ?? initialState.layout; let includePanels = store.getBool(SEARCH_PANELS_LOCAL_STORAGE_KEY, true); if (includePanels) { includePanels = false; } stateManager = new SearchStateManager({ ...initialState, layout, includePanels }); } return stateManager; } export function useSearchStateManager() { const stateManager = getSearchStateManager(); const state = stateManager.useState(); return [state, stateManager] as const; }