Search: display sort metadata (#31167)

* Search: display metadata

* Search: update SortPicker icon

* Search: display folder meta data

* Search: reset sort picker on layout change

* Search: align tags in Card component

* Search: replace hyphen with dash

* Search: preserve sort state on layout change

* Search: update tests

* Search: fix tests

* Update pkg/services/search/hits.go

Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>

* Update public/app/features/search/components/SearchItem.tsx

Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>

* Update public/app/features/search/components/SearchItem.tsx

Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>

* Update public/app/features/search/types.ts

Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>

* Search: fix type error

* Search: add General folder name and adjust icon margin

Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>
This commit is contained in:
Alex Khomenko
2021-02-17 14:06:19 +02:00
committed by GitHub
parent c0015034a3
commit c21e45e428
13 changed files with 83 additions and 37 deletions

View File

@@ -1,6 +1,6 @@
import React, { FC } from 'react';
import { useAsync } from 'react-use';
import { Select, Icon } from '@grafana/ui';
import { Select, Icon, IconName } from '@grafana/ui';
import { SelectableValue } from '@grafana/data';
import { DEFAULT_SORT } from 'app/features/search/constants';
import { SearchSrv } from '../../services/search_srv';
@@ -9,7 +9,7 @@ const searchSrv = new SearchSrv();
export interface Props {
onChange: (sortValue: SelectableValue) => void;
value?: SelectableValue | null;
value?: string;
placeholder?: string;
}
@@ -23,14 +23,16 @@ export const SortPicker: FC<Props> = ({ onChange, value, placeholder }) => {
// Using sync Select and manual options fetching here since we need to find the selected option by value
const { loading, value: options } = useAsync<SelectableValue[]>(getSortOptions, []);
const selected = options?.filter((opt) => opt.value === value);
return !loading ? (
<Select
key={value}
width={25}
onChange={onChange}
value={options?.filter((opt) => opt.value === value)}
value={selected?.length ? selected : null}
options={options}
placeholder={placeholder ?? `Sort (Default ${DEFAULT_SORT.label})`}
prefix={<Icon name="sort-amount-down" />}
prefix={<Icon name={(value?.includes('asc') ? 'sort-amount-up' : 'sort-amount-down') as IconName} />}
/>
) : null;
};

View File

@@ -50,6 +50,7 @@ describe('DashboardSearch', () => {
folderIds: [],
layout: SearchLayout.Folders,
sort: undefined,
prevSort: null,
});
});
@@ -71,6 +72,7 @@ describe('DashboardSearch', () => {
folderIds: [],
layout: SearchLayout.Folders,
sort: undefined,
prevSort: null,
});
});
@@ -110,6 +112,7 @@ describe('DashboardSearch', () => {
folderIds: [],
layout: SearchLayout.Folders,
sort: undefined,
prevSort: null,
})
);
});

View File

@@ -1,7 +1,7 @@
import React, { FC, useCallback } from 'react';
import { css } from 'emotion';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
import { TagList, Card, useStyles } from '@grafana/ui';
import { TagList, Card, useStyles, Icon, IconName } from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data';
import { DashboardSectionItem, OnToggleChecked } from '../types';
import { SearchCheckbox } from './SearchCheckbox';
@@ -16,6 +16,15 @@ export interface Props {
const selectors = e2eSelectors.pages.Dashboards;
const getIconFromMeta = (meta = ''): IconName => {
const metaIconMap = new Map<string, IconName>([
['errors', 'info-circle'],
['views', 'eye'],
]);
return metaIconMap.has(meta) ? metaIconMap.get(meta)! : 'sort-amount-down';
};
export const SearchItem: FC<Props> = ({ item, editable, onToggleChecked, onTagSelected }) => {
const styles = useStyles(getStyles);
const tagSelected = useCallback((tag: string, event: React.MouseEvent<HTMLElement>) => {
@@ -32,6 +41,7 @@ export const SearchItem: FC<Props> = ({ item, editable, onToggleChecked, onTagSe
[item]
);
const folderTitle = item.folderTitle || 'General';
return (
<Card
aria-label={selectors.dashboards(item.title)}
@@ -43,7 +53,18 @@ export const SearchItem: FC<Props> = ({ item, editable, onToggleChecked, onTagSe
<Card.Figure align={'center'}>
<SearchCheckbox editable={editable} checked={item.checked} onClick={toggleItem} />
</Card.Figure>
{item.folderTitle && <Card.Meta>{item.folderTitle}</Card.Meta>}
<Card.Meta separator={''}>
<span className={styles.metaContainer}>
<Icon name={'folder'} />
{folderTitle}
</span>
{item.sortMetaName && (
<span className={styles.metaContainer}>
<Icon name={getIconFromMeta(item.sortMetaName)} />
{item.sortMeta} {item.sortMetaName}
</span>
)}
</Card.Meta>
<Card.Tags>
<TagList tags={item.tags} onClick={tagSelected} />
</Card.Tags>
@@ -56,5 +77,15 @@ const getStyles = (theme: GrafanaTheme) => {
container: css`
padding: ${theme.spacing.sm} ${theme.spacing.md};
`,
metaContainer: css`
display: flex;
align-items: center;
margin-right: ${theme.spacing.sm};
svg {
margin-right: ${theme.spacing.xs};
margin-bottom: 0;
}
`,
};
};

View File

@@ -15,6 +15,7 @@ beforeEach(() => {
const searchQuery = {
starred: false,
sort: null,
prevSort: null,
tag: ['tag'],
query: '',
skipRecent: true,

View File

@@ -60,7 +60,6 @@ export const SectionHeader: FC<SectionHeaderProps> = ({
</a>
)}
</div>
{section.itemsFetching ? <Spinner /> : <Icon name={section.expanded ? 'angle-down' : 'angle-right'} />}
</div>
);

View File

@@ -2,6 +2,6 @@ export const NO_ID_SECTIONS = ['Recent', 'Starred'];
// Height of the search result item
export const SEARCH_ITEM_HEIGHT = 62;
export const SEARCH_ITEM_MARGIN = 8;
export const DEFAULT_SORT = { label: 'A-Z', value: 'alpha-asc' };
export const DEFAULT_SORT = { label: 'A\u2013Z', value: 'alpha-asc' };
export const SECTION_STORAGE_KEY = 'search.sections';
export const GENERAL_FOLDER_ID = 0;

View File

@@ -51,6 +51,10 @@ export const useSearchQuery = (queryParams: Partial<DashboardQuery>, updateLocat
const onLayoutChange = (layout: SearchLayout) => {
dispatch({ type: LAYOUT_CHANGE, payload: layout });
if (layout === SearchLayout.Folders) {
updateLocationQuery({ layout, sort: null });
return;
}
updateLocationQuery({ layout });
};

View File

@@ -20,6 +20,7 @@ export const defaultQuery: DashboardQuery = {
folderIds: [],
sort: null,
layout: SearchLayout.Folders,
prevSort: null,
};
export const defaultQueryParams: RouteParams = {
@@ -58,9 +59,9 @@ export const queryReducer = (state: DashboardQuery, action: SearchAction) => {
case LAYOUT_CHANGE: {
const layout = action.payload;
if (state.sort && layout === SearchLayout.Folders) {
return { ...state, layout, sort: null };
return { ...state, layout, sort: null, prevSort: state.sort };
}
return { ...state, layout };
return { ...state, layout, sort: state.prevSort };
}
default:
return state;

View File

@@ -41,6 +41,8 @@ export interface DashboardSectionItem {
uid?: string;
uri: string;
url: string;
sortMeta?: number;
sortMetaName?: string;
}
export interface DashboardSearchHit extends DashboardSectionItem, DashboardSection {}
@@ -67,6 +69,8 @@ export interface DashboardQuery {
skipStarred: boolean;
folderIds: number[];
sort: SelectableValue | null;
// Save sorting data between layouts
prevSort: SelectableValue | null;
layout: SearchLayout;
}