mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 08:35:43 -06:00
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
This commit is contained in:
parent
410e2a8ca0
commit
9771b1c745
@ -334,7 +334,7 @@ export function SelectBase<T>({
|
||||
bottom,
|
||||
position,
|
||||
marginBottom: !!bottom ? '10px' : '0',
|
||||
'min-width': '100%',
|
||||
minWidth: '100%',
|
||||
zIndex: theme.zIndex.dropdown,
|
||||
}),
|
||||
container: () => ({
|
||||
|
@ -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<any> {
|
||||
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);
|
||||
|
@ -289,7 +289,7 @@ describe('SearchSrv', () => {
|
||||
|
||||
searchSrv['getStarred'] = () => {
|
||||
getStarredCalled = true;
|
||||
return Promise.resolve();
|
||||
return Promise.resolve({});
|
||||
};
|
||||
|
||||
return searchSrv.search({ skipStarred: true }).then(() => {});
|
||||
|
@ -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<SetStateAction<string>>;
|
||||
onSortChange: onSelectChange;
|
||||
onStarredFilterChange?: onSelectChange;
|
||||
onStarredFilterChange?: (event: FormEvent<HTMLInputElement>) => void;
|
||||
onTagFilterChange: onSelectChange;
|
||||
query: DashboardQuery;
|
||||
showStarredFilter?: boolean;
|
||||
@ -23,7 +26,6 @@ interface Props {
|
||||
}
|
||||
|
||||
export const ActionRow: FC<Props> = ({
|
||||
layout,
|
||||
onLayoutChange,
|
||||
onSortChange,
|
||||
onStarredFilterChange = () => {},
|
||||
@ -37,8 +39,10 @@ export const ActionRow: FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className={styles.actionRow}>
|
||||
<HorizontalGroup spacing="md">
|
||||
{!hideLayout ? <RadioButtonGroup options={layoutOptions} onChange={onLayoutChange} value={layout} /> : null}
|
||||
<HorizontalGroup spacing="md" width="auto">
|
||||
{!hideLayout ? (
|
||||
<RadioButtonGroup options={layoutOptions} onChange={onLayoutChange} value={query.layout} />
|
||||
) : null}
|
||||
<SortPicker onChange={onSortChange} value={query.sort} />
|
||||
</HorizontalGroup>
|
||||
<HorizontalGroup spacing="md" width="auto">
|
||||
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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<Props> = 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<Props> = memo(({ onCloseSearch, folder }) => {
|
||||
onCloseSearch();
|
||||
};
|
||||
|
||||
const onLayoutChange = (layout: string) => {
|
||||
setLayout(layout);
|
||||
if (query.sort) {
|
||||
onSortChange(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div tabIndex={0} className={styles.overlay}>
|
||||
<div className={styles.container}>
|
||||
@ -47,7 +38,6 @@ export const DashboardSearch: FC<Props> = memo(({ onCloseSearch, folder }) => {
|
||||
<div className={styles.search}>
|
||||
<ActionRow
|
||||
{...{
|
||||
layout,
|
||||
onLayoutChange,
|
||||
onSortChange,
|
||||
onTagFilterChange,
|
||||
@ -61,7 +51,7 @@ export const DashboardSearch: FC<Props> = memo(({ onCloseSearch, folder }) => {
|
||||
onTagSelected={onTagAdd}
|
||||
editable={false}
|
||||
onToggleSection={onToggleSection}
|
||||
layout={layout}
|
||||
layout={query.layout}
|
||||
/>
|
||||
</CustomScrollbar>
|
||||
</div>
|
||||
|
@ -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<Props> = 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<Props> = memo(({ folderId, folderUid }) => {
|
||||
onStarredFilterChange,
|
||||
onTagAdd,
|
||||
onSortChange,
|
||||
onLayoutChange,
|
||||
} = useSearchQuery(queryParams);
|
||||
|
||||
const {
|
||||
@ -53,9 +59,6 @@ export const ManageDashboards: FC<Props> = 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<Props> = memo(({ folderId, folderUid }) => {
|
||||
setIsDeleteModalOpen(true);
|
||||
};
|
||||
|
||||
const onLayoutChange = (layout: string) => {
|
||||
setLayout(layout);
|
||||
if (query.sort) {
|
||||
onSortChange(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (canSave && folderId && !hasFilters && results.length === 0) {
|
||||
return (
|
||||
<EmptyListCTA
|
||||
@ -113,7 +109,6 @@ export const ManageDashboards: FC<Props> = memo(({ folderId, folderUid }) => {
|
||||
onSortChange={onSortChange}
|
||||
onTagFilterChange={onTagFilterChange}
|
||||
query={query}
|
||||
layout={layout}
|
||||
hideLayout={!!folderUid}
|
||||
onLayoutChange={onLayoutChange}
|
||||
/>
|
||||
@ -124,7 +119,7 @@ export const ManageDashboards: FC<Props> = memo(({ folderId, folderUid }) => {
|
||||
onTagSelected={onTagAdd}
|
||||
onToggleSection={onToggleSection}
|
||||
onToggleChecked={onToggleChecked}
|
||||
layout={layout}
|
||||
layout={query.layout}
|
||||
/>
|
||||
</div>
|
||||
<ConfirmDeleteModal
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
import { SearchResults, Props } from './SearchResults';
|
||||
import { searchResults } from '../testData';
|
||||
import { SearchLayout } from '../types';
|
||||
|
||||
const setup = (propOverrides?: Partial<Props>, renderMethod = shallow) => {
|
||||
const props: Props = {
|
||||
@ -12,6 +13,7 @@ const setup = (propOverrides?: Partial<Props>, renderMethod = shallow) => {
|
||||
onFolderExpanding: () => {},
|
||||
onToggleSelection: () => {},
|
||||
editable: false,
|
||||
layout: SearchLayout.Folders,
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
@ -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<Props> = ({
|
||||
const theme = useTheme();
|
||||
const styles = getSectionStyles(theme);
|
||||
const itemProps = { editable, onToggleChecked, onTagSelected };
|
||||
|
||||
const renderFolders = () => {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
@ -50,8 +48,6 @@ export const SearchResults: FC<Props> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const items = getVisibleItems(results);
|
||||
|
||||
const renderDashboards = () => {
|
||||
return (
|
||||
<div className={styles.listModeWrapper}>
|
||||
@ -63,11 +59,11 @@ export const SearchResults: FC<Props> = ({
|
||||
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<Props> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.resultsContainer}>{layout !== SearchLayout.List ? renderFolders() : renderDashboards()}</div>
|
||||
<div className={styles.resultsContainer}>
|
||||
{layout === SearchLayout.Folders ? renderFolders() : renderDashboards()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
|
@ -13,7 +13,6 @@ export interface Props {
|
||||
canMove?: boolean;
|
||||
deleteItem: () => void;
|
||||
hideLayout?: boolean;
|
||||
layout: string;
|
||||
moveTo: () => void;
|
||||
onLayoutChange: Dispatch<SetStateAction<string>>;
|
||||
onSortChange: onSelectChange;
|
||||
@ -29,7 +28,6 @@ export const SearchResultsFilter: FC<Props> = ({
|
||||
canMove,
|
||||
deleteItem,
|
||||
hideLayout,
|
||||
layout,
|
||||
moveTo,
|
||||
onLayoutChange,
|
||||
onSortChange,
|
||||
@ -57,7 +55,6 @@ export const SearchResultsFilter: FC<Props> = ({
|
||||
) : (
|
||||
<ActionRow
|
||||
{...{
|
||||
layout,
|
||||
hideLayout,
|
||||
onLayoutChange,
|
||||
onSortChange,
|
||||
|
@ -55,9 +55,10 @@ export const useManageDashboards = (
|
||||
dispatch({ type: MOVE_ITEMS, payload: { dashboards: selectedDashboards, folder } });
|
||||
};
|
||||
|
||||
const canMove = useMemo(() => 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,
|
||||
|
@ -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]);
|
||||
|
||||
|
@ -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<string>(defaultLayout);
|
||||
|
||||
useEffect(() => {
|
||||
if (query.sort) {
|
||||
const list = layoutOptions.find(opt => opt.value === SearchLayout.List);
|
||||
setLayout(list!.value);
|
||||
}
|
||||
}, [query]);
|
||||
|
||||
return { layout, setLayout };
|
||||
};
|
@ -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<DashboardQuery>) => {
|
||||
@ -25,14 +33,18 @@ export const useSearchQuery = (queryParams: Partial<DashboardQuery>) => {
|
||||
dispatch({ type: CLEAR_FILTERS });
|
||||
};
|
||||
|
||||
const onStarredFilterChange = (filter: SelectableValue) => {
|
||||
dispatch({ type: TOGGLE_STARRED, payload: filter.value });
|
||||
const onStarredFilterChange = (e: FormEvent<HTMLInputElement>) => {
|
||||
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<DashboardQuery>) => {
|
||||
onStarredFilterChange,
|
||||
onTagAdd,
|
||||
onSortChange,
|
||||
onLayoutChange,
|
||||
};
|
||||
};
|
||||
|
@ -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';
|
||||
|
@ -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 };
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ export interface DashboardQuery {
|
||||
skipStarred: boolean;
|
||||
folderIds: number[];
|
||||
sort: SelectableValue | null;
|
||||
layout: SearchLayout;
|
||||
}
|
||||
|
||||
export type SearchReducer<S> = [S, Dispatch<SearchAction>];
|
||||
|
@ -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;
|
||||
}, []);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user