From fbd289c19c10fc6d8714d1b2c2d6ca1d60cd925a Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 29 Jul 2022 07:34:17 -0700 Subject: [PATCH] Search: add starred filter and swap button order (#52184) Co-authored-by: Alexandra Vargas Co-authored-by: Ivan Ortega Alba --- .../search/components/DashboardActions.tsx | 23 ++++++-- .../features/search/hooks/useSearchQuery.ts | 6 ++ .../search/page/components/ActionRow.tsx | 56 ++++++++++--------- .../search/page/components/SearchView.tsx | 27 ++++++++- public/app/features/search/service/bluge.ts | 17 +++++- public/app/features/search/service/sql.ts | 37 +++++++++++- public/app/features/search/service/types.ts | 2 + 7 files changed, 133 insertions(+), 35 deletions(-) diff --git a/public/app/features/search/components/DashboardActions.tsx b/public/app/features/search/components/DashboardActions.tsx index 945cf39e67d..0419b02368d 100644 --- a/public/app/features/search/components/DashboardActions.tsx +++ b/public/app/features/search/components/DashboardActions.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; -import { HorizontalGroup, LinkButton } from '@grafana/ui'; +import { Menu, Dropdown, Button, Icon } from '@grafana/ui'; export interface Props { folderId?: number; @@ -19,13 +19,24 @@ export const DashboardActions: FC = ({ folderId, canCreateFolders = false return url; }; + const MenuActions = () => { + return ( + + {canCreateDashboards && } + {!folderId && canCreateFolders && } + {canCreateDashboards && } + + ); + }; + return (
- - {canCreateDashboards && New Dashboard} - {!folderId && canCreateFolders && New Folder} - {canCreateDashboards && Import} - + + +
); }; diff --git a/public/app/features/search/hooks/useSearchQuery.ts b/public/app/features/search/hooks/useSearchQuery.ts index fb7d2690703..f7434df559e 100644 --- a/public/app/features/search/hooks/useSearchQuery.ts +++ b/public/app/features/search/hooks/useSearchQuery.ts @@ -64,6 +64,11 @@ export const useSearchQuery = (defaults: Partial) => { updateLocation({ starred: starred || null }); }, []); + const onClearStarred = useCallback(() => { + dispatch({ type: TOGGLE_STARRED, payload: false }); + updateLocation({ starred: null }); + }, []); + const onSortChange = useCallback((sort: SelectableValue | null) => { dispatch({ type: TOGGLE_SORT, payload: sort }); updateLocation({ sort: sort?.value, layout: SearchLayout.List }); @@ -85,6 +90,7 @@ export const useSearchQuery = (defaults: Partial) => { onClearFilters, onTagFilterChange, onStarredFilterChange, + onClearStarred, onTagAdd, onSortChange, onLayoutChange, diff --git a/public/app/features/search/page/components/ActionRow.tsx b/public/app/features/search/page/components/ActionRow.tsx index 462bdae98b4..e7f5ff81785 100644 --- a/public/app/features/search/page/components/ActionRow.tsx +++ b/public/app/features/search/page/components/ActionRow.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/css'; -import React, { FC, FormEvent } from 'react'; +import React, { FC, FormEvent, useEffect } from 'react'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { config } from '@grafana/runtime'; @@ -39,7 +39,7 @@ export function getValidQueryLayout(q: DashboardQuery): SearchLayout { // Folders is not valid when a query exists if (layout === SearchLayout.Folders) { - if (q.query || q.sort) { + if (q.query || q.sort || q.starred) { return SearchLayout.List; } } @@ -75,8 +75,37 @@ export const ActionRow: FC = ({ onLayoutChange(layout); }; + useEffect(() => { + if (includePanels && layout === SearchLayout.Folders) { + setIncludePanels(false); + } + }, [layout, includePanels, setIncludePanels]); + return (
+ + + {config.featureToggles.panelTitleSearch && ( + setIncludePanels(!includePanels)} + label="Include panels" + /> + )} + + {showStarredFilter && ( +
+ +
+ )} + {query.datasource && ( + + )} +
{!hideLayout && ( @@ -90,29 +119,6 @@ export const ActionRow: FC = ({
- - {showStarredFilter && ( -
- -
- )} - {query.datasource && ( - - )} - {config.featureToggles.panelTitleSearch && ( - setIncludePanels(!includePanels)} - label="Include panels" - /> - )} - - -
); }; diff --git a/public/app/features/search/page/components/SearchView.tsx b/public/app/features/search/page/components/SearchView.tsx index 4e406c34809..549fc81294e 100644 --- a/public/app/features/search/page/components/SearchView.tsx +++ b/public/app/features/search/page/components/SearchView.tsx @@ -48,7 +48,16 @@ export const SearchView = ({ }: SearchViewProps) => { const styles = useStyles2(getStyles); - const { query, onTagFilterChange, onTagAdd, onDatasourceChange, onSortChange, onLayoutChange } = useSearchQuery({}); + const { + query, + onTagFilterChange, + onStarredFilterChange, + onTagAdd, + onDatasourceChange, + onSortChange, + onLayoutChange, + onClearStarred, + } = useSearchQuery({}); query.query = queryText; // Use the query value passed in from parent rather than from URL const [searchSelection, setSearchSelection] = useState(newSearchSelection()); @@ -66,6 +75,7 @@ export const SearchView = ({ sort: query.sort?.value, explain: query.explain, withAllowedActions: query.explain, // allowedActions are currently not used for anything on the UI and added only in `explain` mode + starred: query.starred, }; // Only dashboards have additional properties @@ -106,6 +116,9 @@ export const SearchView = ({ ); const results = useAsync(() => { + if (searchQuery.starred) { + return getGrafanaSearcher().starred(searchQuery); + } return getGrafanaSearcher().search(searchQuery); }, [searchQuery]); @@ -136,6 +149,13 @@ export const SearchView = ({ onQueryTextChange(query.query); }; + const getStarredItems = useCallback( + (e) => { + onStarredFilterChange(e); + }, + [onStarredFilterChange] + ); + const renderResults = () => { const value = results.value; @@ -252,9 +272,14 @@ export const SearchView = ({ if (query.query) { onQueryTextChange(''); // parent will clear the sort } + if (query.starred) { + onClearStarred(); + } } onLayoutChange(v); }} + showStarredFilter={hidePseudoFolders} + onStarredFilterChange={!hidePseudoFolders ? undefined : getStarredItems} onSortChange={onSortChange} onTagFilterChange={onTagFilterChange} getTagOptions={getTagOptions} diff --git a/public/app/features/search/service/bluge.ts b/public/app/features/search/service/bluge.ts index 1539619ba5d..cc8769e46b1 100644 --- a/public/app/features/search/service/bluge.ts +++ b/public/app/features/search/service/bluge.ts @@ -1,7 +1,7 @@ import { lastValueFrom } from 'rxjs'; import { ArrayVector, DataFrame, DataFrameView, getDisplayProcessor, SelectableValue } from '@grafana/data'; -import { config } from '@grafana/runtime'; +import { config, getBackendSrv } from '@grafana/runtime'; import { TermCount } from 'app/core/components/TagFilter/TagFilter'; import { getGrafanaDatasource } from 'app/plugins/datasource/grafana/datasource'; import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; @@ -13,11 +13,24 @@ import { DashboardQueryResult, GrafanaSearcher, QueryResponse, SearchQuery, Sear export class BlugeSearcher implements GrafanaSearcher { async search(query: SearchQuery): Promise { if (query.facet?.length) { - throw 'facets not supported!'; + throw new Error('facets not supported!'); } return doSearchQuery(query); } + async starred(query: SearchQuery): Promise { + if (query.facet?.length) { + throw new Error('facets not supported!'); + } + // get the starred dashboards + const starsUIDS = await getBackendSrv().get('api/user/stars'); + const starredQuery = { + uid: starsUIDS, + query: query.query ?? '*', + }; + return doSearchQuery(starredQuery); + } + async tags(query: SearchQuery): Promise { const ds = await getGrafanaDatasource(); const target = { diff --git a/public/app/features/search/service/sql.ts b/public/app/features/search/service/sql.ts index 5ecb3096831..54452b22834 100644 --- a/public/app/features/search/service/sql.ts +++ b/public/app/features/search/service/sql.ts @@ -20,6 +20,7 @@ interface APIQuery { dashboardUID?: string[]; folderIds?: number[]; sort?: string; + starred?: boolean; } // Internal object to hold folderId @@ -39,7 +40,7 @@ export class SQLSearcher implements GrafanaSearcher { async search(query: SearchQuery): Promise { if (query.facet?.length) { - throw 'facets not supported!'; + throw new Error('facets not supported!'); } const q: APIQuery = { limit: query.limit ?? 1000, // default 1k max values @@ -70,6 +71,40 @@ export class SQLSearcher implements GrafanaSearcher { return this.doAPIQuery(q); } + async starred(query: SearchQuery): Promise { + if (query.facet?.length) { + throw new Error('facets not supported!'); + } + const q: APIQuery = { + limit: query.limit ?? 1000, // default 1k max values + tag: query.tags, + sort: query.sort, + starred: query.starred, + }; + + query = await replaceCurrentFolderQuery(query); + if (query.query === '*') { + if (query.kind?.length === 1 && query.kind[0] === 'folder') { + q.type = 'dash-folder'; + } + } else if (query.query?.length) { + q.query = query.query; + } + + if (query.uid) { + q.dashboardUID = query.uid; + } else if (query.location?.length) { + let info = this.locationInfo[query.location]; + if (!info) { + // This will load all folder folders + await this.doAPIQuery({ type: 'dash-folder', limit: 999 }); + info = this.locationInfo[query.location]; + } + q.folderIds = [info.folderId ?? 0]; + } + return this.doAPIQuery(q); + } + // returns the appropriate sorting options async getSortOptions(): Promise { // { diff --git a/public/app/features/search/service/types.ts b/public/app/features/search/service/types.ts index efbaad3d21a..0f0fc9b0a91 100644 --- a/public/app/features/search/service/types.ts +++ b/public/app/features/search/service/types.ts @@ -23,6 +23,7 @@ export interface SearchQuery { hasPreview?: string; // theme limit?: number; from?: number; + starred?: boolean; } export interface DashboardQueryResult { @@ -68,6 +69,7 @@ export interface QueryResponse { export interface GrafanaSearcher { search: (query: SearchQuery) => Promise; + starred: (query: SearchQuery) => Promise; tags: (query: SearchQuery) => Promise; getSortOptions: () => Promise; }