From 9771b1c74529ee5fc197ea7ff8e72d564c569a32 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Mon, 27 Apr 2020 15:08:48 +0300 Subject: [PATCH] Search/fix folder sort (#23893) * Search: Move layout to query reducer/hook * Search: Refactor search_srv * Search: Fix types * Search: Move extra layout/sort logic to reducer * Search: Fix Select min-width * Search: Fix filter by starred * Search: Update tests * Search: Simplify query return * Search: Set width to auto on HorizontalGroup * Search: Fix tests --- .../src/components/Select/SelectBase.tsx | 2 +- public/app/core/services/search_srv.ts | 21 +++++++++-------- public/app/core/specs/search_srv.test.ts | 2 +- .../features/search/components/ActionRow.tsx | 20 +++++++++------- .../components/DashboardSearch.test.tsx | 7 ++++++ .../search/components/DashboardSearch.tsx | 14 ++--------- .../search/components/ManageDashboards.tsx | 23 ++++++++----------- .../search/components/SearchResults.test.tsx | 2 ++ .../search/components/SearchResults.tsx | 16 ++++++------- .../components/SearchResultsFilter.test.tsx | 1 + .../search/components/SearchResultsFilter.tsx | 3 --- .../search/hooks/useManageDashboards.ts | 7 +++--- public/app/features/search/hooks/useSearch.ts | 2 +- .../features/search/hooks/useSearchLayout.ts | 20 ---------------- .../features/search/hooks/useSearchQuery.ts | 23 +++++++++++++++---- .../features/search/reducers/actionTypes.ts | 1 + .../search/reducers/dashboardSearch.ts | 6 ++--- .../search/reducers/searchQueryReducer.ts | 22 ++++++++++++++---- public/app/features/search/types.ts | 1 + public/app/features/search/utils.ts | 2 +- 20 files changed, 100 insertions(+), 95 deletions(-) delete mode 100644 public/app/features/search/hooks/useSearchLayout.ts diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index 366b358e7ba..724d43f6649 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -334,7 +334,7 @@ export function SelectBase({ bottom, position, marginBottom: !!bottom ? '10px' : '0', - 'min-width': '100%', + minWidth: '100%', zIndex: theme.zIndex.dropdown, }), container: () => ({ diff --git a/public/app/core/services/search_srv.ts b/public/app/core/services/search_srv.ts index 267ab88a1af..370638a586e 100644 --- a/public/app/core/services/search_srv.ts +++ b/public/app/core/services/search_srv.ts @@ -4,7 +4,7 @@ import impressionSrv from 'app/core/services/impression_srv'; import store from 'app/core/store'; import { contextSrv } from 'app/core/services/context_srv'; import { hasFilters } from 'app/features/search/utils'; -import { DashboardSection, DashboardSearchItemType, DashboardSearchHit } from 'app/features/search/types'; +import { DashboardSection, DashboardSearchItemType, DashboardSearchHit, SearchLayout } from 'app/features/search/types'; import { backendSrv } from './backend_srv'; interface Sections { @@ -43,14 +43,12 @@ export class SearchSrv { return backendSrv.search({ dashboardIds: dashIds }).then(result => { return dashIds - .map(orderId => { - return _.find(result, { id: orderId }); - }) + .map(orderId => result.find(result => result.id === orderId)) .filter(hit => hit && !hit.isStarred) as DashboardSearchHit[]; }); } - private getStarred(sections: DashboardSection) { + private getStarred(sections: DashboardSection): Promise { if (!contextSrv.isSignedIn) { return Promise.resolve(); } @@ -75,6 +73,14 @@ export class SearchSrv { const query = _.clone(options); const filters = hasFilters(options) || query.folderIds?.length > 0; + query.folderIds = query.folderIds || []; + if (!filters) { + query.folderIds = [0]; + } + if (query.layout === SearchLayout.List) { + return backendSrv.search({ ...query, type: DashboardSearchItemType.DashDB }); + } + if (!options.skipRecent && !filters) { promises.push(this.getRecentDashboards(sections)); } @@ -83,11 +89,6 @@ export class SearchSrv { promises.push(this.getStarred(sections)); } - query.folderIds = query.folderIds || []; - if (!filters) { - query.folderIds = [0]; - } - promises.push( backendSrv.search(query).then(results => { return this.handleSearchResult(sections, results); diff --git a/public/app/core/specs/search_srv.test.ts b/public/app/core/specs/search_srv.test.ts index e7dc17bdc56..2a02378decc 100644 --- a/public/app/core/specs/search_srv.test.ts +++ b/public/app/core/specs/search_srv.test.ts @@ -289,7 +289,7 @@ describe('SearchSrv', () => { searchSrv['getStarred'] = () => { getStarredCalled = true; - return Promise.resolve(); + return Promise.resolve({}); }; return searchSrv.search({ skipStarred: true }).then(() => {}); diff --git a/public/app/features/search/components/ActionRow.tsx b/public/app/features/search/components/ActionRow.tsx index 62f313dbcae..02bee317390 100644 --- a/public/app/features/search/components/ActionRow.tsx +++ b/public/app/features/search/components/ActionRow.tsx @@ -1,21 +1,24 @@ -import React, { Dispatch, FC, SetStateAction } from 'react'; +import React, { Dispatch, FC, FormEvent, SetStateAction } from 'react'; import { css } from 'emotion'; import { HorizontalGroup, RadioButtonGroup, stylesFactory, useTheme, Checkbox } from '@grafana/ui'; import { GrafanaTheme, SelectableValue } from '@grafana/data'; import { SortPicker } from 'app/core/components/Select/SortPicker'; import { TagFilter } from 'app/core/components/TagFilter/TagFilter'; import { SearchSrv } from 'app/core/services/search_srv'; -import { layoutOptions } from '../hooks/useSearchLayout'; -import { DashboardQuery } from '../types'; +import { DashboardQuery, SearchLayout } from '../types'; + +export const layoutOptions = [ + { label: 'Folders', value: SearchLayout.Folders, icon: 'folder' }, + { label: 'List', value: SearchLayout.List, icon: 'list-ul' }, +]; const searchSrv = new SearchSrv(); type onSelectChange = (value: SelectableValue) => void; interface Props { - layout: string; onLayoutChange: Dispatch>; onSortChange: onSelectChange; - onStarredFilterChange?: onSelectChange; + onStarredFilterChange?: (event: FormEvent) => void; onTagFilterChange: onSelectChange; query: DashboardQuery; showStarredFilter?: boolean; @@ -23,7 +26,6 @@ interface Props { } export const ActionRow: FC = ({ - layout, onLayoutChange, onSortChange, onStarredFilterChange = () => {}, @@ -37,8 +39,10 @@ export const ActionRow: FC = ({ return (
- - {!hideLayout ? : null} + + {!hideLayout ? ( + + ) : null} diff --git a/public/app/features/search/components/DashboardSearch.test.tsx b/public/app/features/search/components/DashboardSearch.test.tsx index e24e148d5a3..91c50241d84 100644 --- a/public/app/features/search/components/DashboardSearch.test.tsx +++ b/public/app/features/search/components/DashboardSearch.test.tsx @@ -4,6 +4,7 @@ import { act } from 'react-dom/test-utils'; import { mockSearch } from './mocks'; import { DashboardSearch } from './DashboardSearch'; import { searchResults } from '../testData'; +import { SearchLayout } from '../types'; beforeEach(() => { jest.useFakeTimers(); @@ -43,6 +44,8 @@ describe('DashboardSearch', () => { skipStarred: false, starred: false, folderIds: [], + layout: SearchLayout.Folders, + sort: undefined, }); }); @@ -68,6 +71,8 @@ describe('DashboardSearch', () => { tag: [], starred: false, folderIds: [], + layout: SearchLayout.Folders, + sort: undefined, }); }); @@ -108,6 +113,8 @@ describe('DashboardSearch', () => { tag: ['TestTag'], starred: false, folderIds: [], + layout: SearchLayout.Folders, + sort: undefined, }); }); }); diff --git a/public/app/features/search/components/DashboardSearch.tsx b/public/app/features/search/components/DashboardSearch.tsx index d197311cba3..6e8380806ba 100644 --- a/public/app/features/search/components/DashboardSearch.tsx +++ b/public/app/features/search/components/DashboardSearch.tsx @@ -4,7 +4,6 @@ import { useTheme, CustomScrollbar, stylesFactory, IconButton } from '@grafana/u import { GrafanaTheme } from '@grafana/data'; import { useSearchQuery } from '../hooks/useSearchQuery'; import { useDashboardSearch } from '../hooks/useDashboardSearch'; -import { useSearchLayout } from '../hooks/useSearchLayout'; import { SearchField } from './SearchField'; import { SearchResults } from './SearchResults'; import { ActionRow } from './ActionRow'; @@ -16,9 +15,8 @@ export interface Props { export const DashboardSearch: FC = memo(({ onCloseSearch, folder }) => { const payload = folder ? { query: `folder:${folder}` } : {}; - const { query, onQueryChange, onTagFilterChange, onTagAdd, onSortChange } = useSearchQuery(payload); + const { query, onQueryChange, onTagFilterChange, onTagAdd, onSortChange, onLayoutChange } = useSearchQuery(payload); const { results, loading, onToggleSection, onKeyDown } = useDashboardSearch(query, onCloseSearch); - const { layout, setLayout } = useSearchLayout(query); const theme = useTheme(); const styles = getStyles(theme); @@ -28,13 +26,6 @@ export const DashboardSearch: FC = memo(({ onCloseSearch, folder }) => { onCloseSearch(); }; - const onLayoutChange = (layout: string) => { - setLayout(layout); - if (query.sort) { - onSortChange(null); - } - }; - return (
@@ -47,7 +38,6 @@ export const DashboardSearch: FC = memo(({ onCloseSearch, folder }) => {
= memo(({ onCloseSearch, folder }) => { onTagSelected={onTagAdd} editable={false} onToggleSection={onToggleSection} - layout={layout} + layout={query.layout} />
diff --git a/public/app/features/search/components/ManageDashboards.tsx b/public/app/features/search/components/ManageDashboards.tsx index 5ebe8d508f1..7d8340917e4 100644 --- a/public/app/features/search/components/ManageDashboards.tsx +++ b/public/app/features/search/components/ManageDashboards.tsx @@ -11,7 +11,6 @@ import { useManageDashboards } from '../hooks/useManageDashboards'; import { SearchResultsFilter } from './SearchResultsFilter'; import { SearchResults } from './SearchResults'; import { DashboardActions } from './DashboardActions'; -import { useSearchLayout } from '../hooks/useSearchLayout'; import { SearchLayout } from '../types'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; @@ -27,7 +26,13 @@ export const ManageDashboards: FC = memo(({ folderId, folderUid }) => { const styles = getStyles(theme); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isMoveModalOpen, setIsMoveModalOpen] = useState(false); - const queryParams = { skipRecent: true, skipStarred: true, folderIds: folderId ? [folderId] : [] }; + const defaultLayout = folderId ? SearchLayout.List : SearchLayout.Folders; + const queryParams = { + skipRecent: true, + skipStarred: true, + folderIds: folderId ? [folderId] : [], + layout: defaultLayout, + }; const { query, hasFilters, @@ -36,6 +41,7 @@ export const ManageDashboards: FC = memo(({ folderId, folderUid }) => { onStarredFilterChange, onTagAdd, onSortChange, + onLayoutChange, } = useSearchQuery(queryParams); const { @@ -53,9 +59,6 @@ export const ManageDashboards: FC = memo(({ folderId, folderUid }) => { onMoveItems, } = useManageDashboards(query, { hasEditPermissionInFolders: contextSrv.hasEditPermissionInFolders }, folderUid); - const defaultLayout = folderId ? SearchLayout.List : SearchLayout.Folders; - const { layout, setLayout } = useSearchLayout(query, defaultLayout); - const onMoveTo = () => { setIsMoveModalOpen(true); }; @@ -64,13 +67,6 @@ export const ManageDashboards: FC = memo(({ folderId, folderUid }) => { setIsDeleteModalOpen(true); }; - const onLayoutChange = (layout: string) => { - setLayout(layout); - if (query.sort) { - onSortChange(null); - } - }; - if (canSave && folderId && !hasFilters && results.length === 0) { return ( = memo(({ folderId, folderUid }) => { onSortChange={onSortChange} onTagFilterChange={onTagFilterChange} query={query} - layout={layout} hideLayout={!!folderUid} onLayoutChange={onLayoutChange} /> @@ -124,7 +119,7 @@ export const ManageDashboards: FC = memo(({ folderId, folderUid }) => { onTagSelected={onTagAdd} onToggleSection={onToggleSection} onToggleChecked={onToggleChecked} - layout={layout} + layout={query.layout} />
, renderMethod = shallow) => { const props: Props = { @@ -12,6 +13,7 @@ const setup = (propOverrides?: Partial, renderMethod = shallow) => { onFolderExpanding: () => {}, onToggleSelection: () => {}, editable: false, + layout: SearchLayout.Folders, }; Object.assign(props, propOverrides); diff --git a/public/app/features/search/components/SearchResults.tsx b/public/app/features/search/components/SearchResults.tsx index b9441193ea7..0620b590dfc 100644 --- a/public/app/features/search/components/SearchResults.tsx +++ b/public/app/features/search/components/SearchResults.tsx @@ -4,8 +4,7 @@ import { FixedSizeList } from 'react-window'; import AutoSizer from 'react-virtualized-auto-sizer'; import { GrafanaTheme } from '@grafana/data'; import { stylesFactory, useTheme, Spinner } from '@grafana/ui'; -import { DashboardSection, OnToggleChecked, SearchLayout } from '../types'; -import { getVisibleItems } from '../utils'; +import { DashboardSection, OnToggleChecked, SearchLayout, DashboardSearchHit } from '../types'; import { SEARCH_ITEM_HEIGHT, SEARCH_ITEM_MARGIN } from '../constants'; import { SearchItem } from './SearchItem'; import { SectionHeader } from './SectionHeader'; @@ -16,7 +15,7 @@ export interface Props { onTagSelected: (name: string) => any; onToggleChecked?: OnToggleChecked; onToggleSection: (section: DashboardSection) => void; - results: DashboardSection[]; + results: DashboardSearchHit[]; layout?: string; } @@ -32,7 +31,6 @@ export const SearchResults: FC = ({ const theme = useTheme(); const styles = getSectionStyles(theme); const itemProps = { editable, onToggleChecked, onTagSelected }; - const renderFolders = () => { return (
@@ -50,8 +48,6 @@ export const SearchResults: FC = ({ ); }; - const items = getVisibleItems(results); - const renderDashboards = () => { return (
@@ -63,11 +59,11 @@ export const SearchResults: FC = ({ innerElementType="ul" itemSize={SEARCH_ITEM_HEIGHT + SEARCH_ITEM_MARGIN} height={height} - itemCount={items.length} + itemCount={results.length} width="100%" > {({ index, style }) => { - const item = items[index]; + const item = results[index]; // The wrapper div is needed as the inner SearchItem has margin-bottom spacing // And without this wrapper there is no room for that margin return ( @@ -90,7 +86,9 @@ export const SearchResults: FC = ({ } return ( -
{layout !== SearchLayout.List ? renderFolders() : renderDashboards()}
+
+ {layout === SearchLayout.Folders ? renderFolders() : renderDashboards()} +
); }; diff --git a/public/app/features/search/components/SearchResultsFilter.test.tsx b/public/app/features/search/components/SearchResultsFilter.test.tsx index a6db78e17e0..056c8f0387a 100644 --- a/public/app/features/search/components/SearchResultsFilter.test.tsx +++ b/public/app/features/search/components/SearchResultsFilter.test.tsx @@ -62,6 +62,7 @@ describe('SearchResultsFilter', () => { const option = { value: true, label: 'Yes' }; //@ts-ignore const { wrapper } = setup({ onStarredFilterChange: mockFilterStarred }, mount); + //@ts-ignore wrapper .find('Checkbox') .at(1) diff --git a/public/app/features/search/components/SearchResultsFilter.tsx b/public/app/features/search/components/SearchResultsFilter.tsx index 596eb938327..3344ddbc087 100644 --- a/public/app/features/search/components/SearchResultsFilter.tsx +++ b/public/app/features/search/components/SearchResultsFilter.tsx @@ -13,7 +13,6 @@ export interface Props { canMove?: boolean; deleteItem: () => void; hideLayout?: boolean; - layout: string; moveTo: () => void; onLayoutChange: Dispatch>; onSortChange: onSelectChange; @@ -29,7 +28,6 @@ export const SearchResultsFilter: FC = ({ canMove, deleteItem, hideLayout, - layout, moveTo, onLayoutChange, onSortChange, @@ -57,7 +55,6 @@ export const SearchResultsFilter: FC = ({ ) : ( results.some((result: DashboardSection) => result.items.some(item => item.checked)), [ - results, - ]); + const canMove = useMemo( + () => results.some((result: DashboardSection) => result.items && result.items.some(item => item.checked)), + [results] + ); const canDelete = useMemo(() => canMove || results.some((result: DashboardSection) => result.checked), [ canMove, results, diff --git a/public/app/features/search/hooks/useSearch.ts b/public/app/features/search/hooks/useSearch.ts index c4c7ee1e1ec..7be66337856 100644 --- a/public/app/features/search/hooks/useSearch.ts +++ b/public/app/features/search/hooks/useSearch.ts @@ -35,7 +35,7 @@ export const useSearch: UseSearch = (query, reducer, params) => { // Set loading state before debounced search useEffect(() => { dispatch({ type: SEARCH_START }); - }, [query.tag, query.sort, query.starred]); + }, [query.tag, query.sort, query.starred, query.layout]); useDebounce(search, 300, [query, folderUid, queryParsing]); diff --git a/public/app/features/search/hooks/useSearchLayout.ts b/public/app/features/search/hooks/useSearchLayout.ts deleted file mode 100644 index 68a9a6b5187..00000000000 --- a/public/app/features/search/hooks/useSearchLayout.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useEffect, useState } from 'react'; -import { DashboardQuery, SearchLayout } from '../types'; - -export const layoutOptions = [ - { label: 'Folders', value: SearchLayout.Folders, icon: 'folder' }, - { label: 'List', value: SearchLayout.List, icon: 'list-ul' }, -]; - -export const useSearchLayout = (query: DashboardQuery, defaultLayout = SearchLayout.Folders) => { - const [layout, setLayout] = useState(defaultLayout); - - useEffect(() => { - if (query.sort) { - const list = layoutOptions.find(opt => opt.value === SearchLayout.List); - setLayout(list!.value); - } - }, [query]); - - return { layout, setLayout }; -}; diff --git a/public/app/features/search/hooks/useSearchQuery.ts b/public/app/features/search/hooks/useSearchQuery.ts index 385e67e1b45..a7a55957e95 100644 --- a/public/app/features/search/hooks/useSearchQuery.ts +++ b/public/app/features/search/hooks/useSearchQuery.ts @@ -1,8 +1,16 @@ -import { useReducer } from 'react'; +import { FormEvent, useReducer } from 'react'; import { SelectableValue } from '@grafana/data'; import { defaultQuery, queryReducer } from '../reducers/searchQueryReducer'; -import { ADD_TAG, CLEAR_FILTERS, QUERY_CHANGE, SET_TAGS, TOGGLE_SORT, TOGGLE_STARRED } from '../reducers/actionTypes'; -import { DashboardQuery } from '../types'; +import { + ADD_TAG, + CLEAR_FILTERS, + LAYOUT_CHANGE, + QUERY_CHANGE, + SET_TAGS, + TOGGLE_SORT, + TOGGLE_STARRED, +} from '../reducers/actionTypes'; +import { DashboardQuery, SearchLayout } from '../types'; import { hasFilters } from '../utils'; export const useSearchQuery = (queryParams: Partial) => { @@ -25,14 +33,18 @@ export const useSearchQuery = (queryParams: Partial) => { dispatch({ type: CLEAR_FILTERS }); }; - const onStarredFilterChange = (filter: SelectableValue) => { - dispatch({ type: TOGGLE_STARRED, payload: filter.value }); + const onStarredFilterChange = (e: FormEvent) => { + dispatch({ type: TOGGLE_STARRED, payload: (e.target as HTMLInputElement).checked }); }; const onSortChange = (sort: SelectableValue | null) => { dispatch({ type: TOGGLE_SORT, payload: sort }); }; + const onLayoutChange = (layout: SearchLayout) => { + dispatch({ type: LAYOUT_CHANGE, payload: layout }); + }; + return { query, hasFilters: hasFilters(query), @@ -42,5 +54,6 @@ export const useSearchQuery = (queryParams: Partial) => { onStarredFilterChange, onTagAdd, onSortChange, + onLayoutChange, }; }; diff --git a/public/app/features/search/reducers/actionTypes.ts b/public/app/features/search/reducers/actionTypes.ts index 59d2bb30b66..e45368cce31 100644 --- a/public/app/features/search/reducers/actionTypes.ts +++ b/public/app/features/search/reducers/actionTypes.ts @@ -22,3 +22,4 @@ export const CLEAR_FILTERS = 'CLEAR_FILTERS'; export const SET_TAGS = 'SET_TAGS'; export const ADD_TAG = 'ADD_TAG'; export const TOGGLE_SORT = 'TOGGLE_SORT'; +export const LAYOUT_CHANGE = 'LAYOUT_CHANGE'; diff --git a/public/app/features/search/reducers/dashboardSearch.ts b/public/app/features/search/reducers/dashboardSearch.ts index bb7b4eba4ea..967aca821f0 100644 --- a/public/app/features/search/reducers/dashboardSearch.ts +++ b/public/app/features/search/reducers/dashboardSearch.ts @@ -1,4 +1,4 @@ -import { DashboardSection, SearchAction } from '../types'; +import { DashboardSearchHit, DashboardSection, SearchAction } from '../types'; import { getFlattenedSections, getLookupField, markSelected } from '../utils'; import { FETCH_ITEMS, @@ -10,7 +10,7 @@ import { } from './actionTypes'; export interface DashboardsSearchState { - results: DashboardSection[]; + results: DashboardSearchHit[]; loading: boolean; selectedIndex: number; } @@ -31,7 +31,7 @@ export const searchReducer = (state: DashboardsSearchState, action: SearchAction case FETCH_RESULTS: { const results = action.payload; // Highlight the first item ('Starred' folder) - if (results.length) { + if (results.length > 0) { results[0].selected = true; } return { ...state, results, loading: false }; diff --git a/public/app/features/search/reducers/searchQueryReducer.ts b/public/app/features/search/reducers/searchQueryReducer.ts index 18a570467e6..dee371031bb 100644 --- a/public/app/features/search/reducers/searchQueryReducer.ts +++ b/public/app/features/search/reducers/searchQueryReducer.ts @@ -1,13 +1,14 @@ -import { SearchAction, DashboardQuery } from '../types'; +import { DashboardQuery, SearchAction, SearchLayout } from '../types'; import { ADD_TAG, CLEAR_FILTERS, + LAYOUT_CHANGE, QUERY_CHANGE, REMOVE_STARRED, REMOVE_TAG, SET_TAGS, - TOGGLE_STARRED, TOGGLE_SORT, + TOGGLE_STARRED, } from './actionTypes'; export const defaultQuery: DashboardQuery = { @@ -18,6 +19,7 @@ export const defaultQuery: DashboardQuery = { skipStarred: false, folderIds: [], sort: null, + layout: SearchLayout.Folders, }; export const queryReducer = (state: DashboardQuery, action: SearchAction) => { @@ -38,8 +40,20 @@ export const queryReducer = (state: DashboardQuery, action: SearchAction) => { return { ...state, starred: false }; case CLEAR_FILTERS: return { ...state, query: '', tag: [], starred: false, sort: null }; - case TOGGLE_SORT: - return { ...state, sort: action.payload }; + case TOGGLE_SORT: { + const sort = action.payload; + if (state.layout === SearchLayout.Folders) { + return { ...state, sort, layout: SearchLayout.List }; + } + return { ...state, sort }; + } + case LAYOUT_CHANGE: { + const layout = action.payload; + if (state.sort && layout === SearchLayout.Folders) { + return { ...state, layout, sort: null }; + } + return { ...state, layout }; + } default: return state; } diff --git a/public/app/features/search/types.ts b/public/app/features/search/types.ts index 45fc24b29e2..cb14285d79b 100644 --- a/public/app/features/search/types.ts +++ b/public/app/features/search/types.ts @@ -70,6 +70,7 @@ export interface DashboardQuery { skipStarred: boolean; folderIds: number[]; sort: SelectableValue | null; + layout: SearchLayout; } export type SearchReducer = [S, Dispatch]; diff --git a/public/app/features/search/utils.ts b/public/app/features/search/utils.ts index 84eb52c7333..fb32060ad71 100644 --- a/public/app/features/search/utils.ts +++ b/public/app/features/search/utils.ts @@ -136,7 +136,7 @@ export const getCheckedDashboards = (sections: DashboardSection[]): DashboardSec } return sections.reduce((uids, section) => { - return [...uids, ...section.items.filter(item => item.checked)]; + return section.items ? [...uids, ...section.items.filter(item => item.checked)] : uids; }, []); };