mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Search: extract a reusable view from the search playground (#49132)
Co-authored-by: Alexandra Vargas <alexa1866@gmail.com>
This commit is contained in:
parent
7251115457
commit
f0496955e3
@ -11,7 +11,7 @@ import * as SearchSrv from 'app/core/services/search_srv';
|
||||
import { searchResults } from '../testData';
|
||||
import { SearchLayout } from '../types';
|
||||
|
||||
import { DashboardSearch, Props } from './DashboardSearch';
|
||||
import { DashboardSearchOLD as DashboardSearch, Props } from './DashboardSearch';
|
||||
|
||||
jest.mock('app/core/services/search_srv');
|
||||
// Typecast the mock search so the mock import is correctly recognised by TS
|
||||
|
@ -1,11 +1,14 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { FC, memo } from 'react';
|
||||
import React, { FC, memo, useState } from 'react';
|
||||
import { useDebounce } from 'react-use';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { CustomScrollbar, IconButton, stylesFactory, useTheme2 } from '@grafana/ui';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { CustomScrollbar, IconButton, stylesFactory, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
|
||||
import { useDashboardSearch } from '../hooks/useDashboardSearch';
|
||||
import { useSearchQuery } from '../hooks/useSearchQuery';
|
||||
import { SearchView } from '../page/components/SearchView';
|
||||
|
||||
import { ActionRow } from './ActionRow';
|
||||
import { PreviewsSystemRequirements } from './PreviewsSystemRequirements';
|
||||
@ -16,7 +19,55 @@ export interface Props {
|
||||
onCloseSearch: () => void;
|
||||
}
|
||||
|
||||
export const DashboardSearch: FC<Props> = memo(({ onCloseSearch }) => {
|
||||
export default function DashboardSearch({ onCloseSearch }: Props) {
|
||||
if (false && config.featureToggles.panelTitleSearch) {
|
||||
// TODO: "folder:current" ????
|
||||
return <DashbaordSearchNEW onCloseSearch={onCloseSearch} />;
|
||||
}
|
||||
return <DashboardSearchOLD onCloseSearch={onCloseSearch} />;
|
||||
}
|
||||
|
||||
function DashbaordSearchNEW({ onCloseSearch }: Props) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const { query, onQueryChange } = useSearchQuery({});
|
||||
|
||||
const [inputValue, setInputValue] = useState(query.query ?? '');
|
||||
const onSearchQueryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
e.preventDefault();
|
||||
setInputValue(e.currentTarget.value);
|
||||
};
|
||||
useDebounce(() => onQueryChange(inputValue), 200, [inputValue]);
|
||||
|
||||
return (
|
||||
<div tabIndex={0} className={styles.overlay}>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.searchField}>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search dashboards by name"
|
||||
value={inputValue}
|
||||
onChange={onSearchQueryChange}
|
||||
tabIndex={0}
|
||||
spellCheck={false}
|
||||
className={styles.input}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.closeBtn}>
|
||||
<IconButton name="times" surface="panel" onClick={onCloseSearch} size="xxl" tooltip="Close search" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.search}>
|
||||
<SearchView showManage={false} queryText={query.query} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardSearchOLD: FC<Props> = memo(({ onCloseSearch }) => {
|
||||
const { query, onQueryChange, onTagFilterChange, onTagAdd, onSortChange, onLayoutChange } = useSearchQuery({});
|
||||
const { results, loading, onToggleSection, onKeyDown, showPreviews, setShowPreviews } = useDashboardSearch(
|
||||
query,
|
||||
@ -67,9 +118,7 @@ export const DashboardSearch: FC<Props> = memo(({ onCloseSearch }) => {
|
||||
);
|
||||
});
|
||||
|
||||
DashboardSearch.displayName = 'DashboardSearch';
|
||||
|
||||
export default DashboardSearch;
|
||||
DashboardSearchOLD.displayName = 'DashboardSearchOLD';
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme2) => {
|
||||
return {
|
||||
@ -113,5 +162,19 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => {
|
||||
height: 100%;
|
||||
padding-bottom: ${theme.spacing(3)};
|
||||
`,
|
||||
input: css`
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
background: transparent;
|
||||
border-bottom: 2px solid ${theme.v1.colors.border1};
|
||||
font-size: 20px;
|
||||
line-height: 38px;
|
||||
width: 100%;
|
||||
|
||||
&::placeholder {
|
||||
color: ${theme.v1.colors.textWeak};
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
@ -1,26 +1,17 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useAsync, useDebounce } from 'react-use';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Input, useStyles2, Spinner, InlineSwitch, InlineFieldRow, InlineField, Button, Select } from '@grafana/ui';
|
||||
import { Input, useStyles2, Spinner, InlineSwitch, InlineFieldRow, InlineField, Select } from '@grafana/ui';
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import { TermCount } from 'app/core/components/TagFilter/TagFilter';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { FolderDTO } from 'app/types';
|
||||
|
||||
import { PreviewsSystemRequirements } from '../components/PreviewsSystemRequirements';
|
||||
import { useSearchQuery } from '../hooks/useSearchQuery';
|
||||
import { getGrafanaSearcher, SearchQuery } from '../service';
|
||||
import { SearchLayout } from '../types';
|
||||
import { getGrafanaSearcher } from '../service';
|
||||
|
||||
import { ActionRow, getValidQueryLayout } from './components/ActionRow';
|
||||
import { FolderSection } from './components/FolderSection';
|
||||
import { FolderView } from './components/FolderView';
|
||||
import { ManageActions } from './components/ManageActions';
|
||||
import { SearchResultsGrid } from './components/SearchResultsGrid';
|
||||
import { SearchResultsTable, SearchResultsProps } from './components/SearchResultsTable';
|
||||
import { newSearchSelection, updateSearchSelection } from './selection';
|
||||
import { SearchView } from './components/SearchView';
|
||||
|
||||
const node: NavModelItem = {
|
||||
id: 'search',
|
||||
@ -32,11 +23,9 @@ const node: NavModelItem = {
|
||||
|
||||
export default function SearchPage() {
|
||||
const styles = useStyles2(getStyles);
|
||||
const { query, onQueryChange, onTagFilterChange, onTagAdd, onDatasourceChange, onSortChange, onLayoutChange } =
|
||||
useSearchQuery({});
|
||||
|
||||
const [showManage, setShowManage] = useState(false); // grid vs list view
|
||||
const [folder, setFolder] = useState<string>(); // grid vs list view
|
||||
const [folderDTO, setFolderDTO] = useState<FolderDTO>(); // grid vs list view
|
||||
const folders = useAsync(async () => {
|
||||
const rsp = await getGrafanaSearcher().search({
|
||||
query: '*',
|
||||
@ -44,139 +33,25 @@ export default function SearchPage() {
|
||||
});
|
||||
return rsp.view.map((v) => ({ value: v.uid, label: v.name }));
|
||||
}, []);
|
||||
|
||||
const [searchSelection, setSearchSelection] = useState(newSearchSelection());
|
||||
const layout = getValidQueryLayout(query);
|
||||
const isFolders = layout === SearchLayout.Folders;
|
||||
|
||||
const results = useAsync(() => {
|
||||
let qstr = query.query as string;
|
||||
if (!qstr?.length) {
|
||||
qstr = '*';
|
||||
const setFolder = async (uid?: string) => {
|
||||
if (uid?.length) {
|
||||
const dto = await backendSrv.getFolderByUid(uid);
|
||||
setFolderDTO(dto);
|
||||
} else {
|
||||
setFolderDTO(undefined);
|
||||
}
|
||||
const q: SearchQuery = {
|
||||
query: qstr,
|
||||
tags: query.tag as string[],
|
||||
ds_uid: query.datasource as string,
|
||||
location: folder, // This will scope all results to the prefix
|
||||
};
|
||||
return getGrafanaSearcher().search(q);
|
||||
}, [query, layout, folder]);
|
||||
};
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
// since we don't use "query" from use search... it is not actually loaded from the URL!
|
||||
const { query, onQueryChange } = useSearchQuery({});
|
||||
|
||||
const [inputValue, setInputValue] = useState(query.query ?? '');
|
||||
const onSearchQueryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
e.preventDefault();
|
||||
setInputValue(e.currentTarget.value);
|
||||
};
|
||||
|
||||
useDebounce(() => onQueryChange(inputValue), 200, [inputValue]);
|
||||
|
||||
const toggleSelection = useCallback(
|
||||
(kind: string, uid: string) => {
|
||||
const current = searchSelection.isSelected(kind, uid);
|
||||
if (kind === 'folder') {
|
||||
// ??? also select all children?
|
||||
}
|
||||
setSearchSelection(updateSearchSelection(searchSelection, !current, kind, [uid]));
|
||||
},
|
||||
[searchSelection]
|
||||
);
|
||||
|
||||
if (!config.featureToggles.panelTitleSearch) {
|
||||
return <div className={styles.unsupported}>Unsupported</div>;
|
||||
}
|
||||
|
||||
// This gets the possible tags from within the query results
|
||||
const getTagOptions = (): Promise<TermCount[]> => {
|
||||
const q: SearchQuery = {
|
||||
query: query.query?.length ? query.query : '*',
|
||||
tags: query.tag,
|
||||
ds_uid: query.datasource,
|
||||
};
|
||||
return getGrafanaSearcher().tags(q);
|
||||
};
|
||||
|
||||
// function to update items when dashboards or folders are moved or deleted
|
||||
const onChangeItemsList = async () => {
|
||||
// clean up search selection
|
||||
setSearchSelection(newSearchSelection());
|
||||
// trigger again the search to the backend
|
||||
onQueryChange(inputValue);
|
||||
};
|
||||
|
||||
const renderResults = () => {
|
||||
const value = results.value;
|
||||
|
||||
if ((!value || !value.totalRows) && !isFolders) {
|
||||
if (results.loading && !value) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.noResults}>
|
||||
<div>No results found for your query.</div>
|
||||
<br />
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
if (query.query) {
|
||||
onQueryChange('');
|
||||
}
|
||||
if (query.tag?.length) {
|
||||
onTagFilterChange([]);
|
||||
}
|
||||
if (query.datasource) {
|
||||
onDatasourceChange(undefined);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Remove search constraints
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const selection = showManage ? searchSelection.isSelected : undefined;
|
||||
if (layout === SearchLayout.Folders) {
|
||||
if (folder) {
|
||||
return (
|
||||
<FolderSection
|
||||
section={{ uid: folder, kind: 'folder', title: folder }}
|
||||
selection={selection}
|
||||
selectionToggle={toggleSelection}
|
||||
onTagSelected={onTagAdd}
|
||||
renderStandaloneBody={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <FolderView selection={selection} selectionToggle={toggleSelection} onTagSelected={onTagAdd} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%', width: '100%' }}>
|
||||
<AutoSizer>
|
||||
{({ width, height }) => {
|
||||
const props: SearchResultsProps = {
|
||||
response: value!,
|
||||
selection,
|
||||
selectionToggle: toggleSelection,
|
||||
width: width,
|
||||
height: height,
|
||||
onTagSelected: onTagAdd,
|
||||
onDatasourceChange: query.datasource ? onDatasourceChange : undefined,
|
||||
};
|
||||
|
||||
if (layout === SearchLayout.Grid) {
|
||||
return <SearchResultsGrid {...props} />;
|
||||
}
|
||||
|
||||
return <SearchResultsTable {...props} />;
|
||||
}}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Page navModel={{ node: node, main: node }}>
|
||||
<Page.Contents
|
||||
@ -193,7 +68,7 @@ export default function SearchPage() {
|
||||
spellCheck={false}
|
||||
placeholder="Search for dashboards and panels"
|
||||
className={styles.searchInput}
|
||||
suffix={results.loading ? <Spinner /> : null}
|
||||
suffix={false ? <Spinner /> : null}
|
||||
/>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Show manage options">
|
||||
@ -209,34 +84,7 @@ export default function SearchPage() {
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
|
||||
{Boolean(searchSelection.items.size > 0) ? (
|
||||
<ManageActions items={searchSelection.items} onChange={onChangeItemsList} />
|
||||
) : (
|
||||
<ActionRow
|
||||
onLayoutChange={(v) => {
|
||||
if (v === SearchLayout.Folders) {
|
||||
if (query.query) {
|
||||
onQueryChange(''); // parent will clear the sort
|
||||
}
|
||||
}
|
||||
onLayoutChange(v);
|
||||
}}
|
||||
onSortChange={onSortChange}
|
||||
onTagFilterChange={onTagFilterChange}
|
||||
getTagOptions={getTagOptions}
|
||||
onDatasourceChange={onDatasourceChange}
|
||||
query={query}
|
||||
/>
|
||||
)}
|
||||
|
||||
{layout === SearchLayout.Grid && (
|
||||
<PreviewsSystemRequirements
|
||||
bottomSpacing={3}
|
||||
showPreviews={true}
|
||||
onRemove={() => onLayoutChange(SearchLayout.List)}
|
||||
/>
|
||||
)}
|
||||
{renderResults()}
|
||||
<SearchView showManage={showManage} folderDTO={folderDTO} queryText={query.query} />
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
);
|
||||
|
@ -58,13 +58,22 @@ export const ActionRow: FC<Props> = ({
|
||||
hideLayout,
|
||||
}) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const layout = getValidQueryLayout(query);
|
||||
|
||||
// Disabled folder layout option when query is present
|
||||
const disabledOptions = query.sort || query.query ? [SearchLayout.Folders] : [];
|
||||
|
||||
return (
|
||||
<div className={styles.actionRow}>
|
||||
<div className={styles.rowContainer}>
|
||||
<HorizontalGroup spacing="md" width="auto">
|
||||
{!hideLayout && (
|
||||
<RadioButtonGroup options={layoutOptions} onChange={onLayoutChange} value={getValidQueryLayout(query)} />
|
||||
<RadioButtonGroup
|
||||
options={layoutOptions}
|
||||
disabledOptions={disabledOptions}
|
||||
onChange={onLayoutChange}
|
||||
value={layout}
|
||||
/>
|
||||
)}
|
||||
<SortPicker onChange={onSortChange} value={query.sort?.value} />
|
||||
</HorizontalGroup>
|
||||
|
@ -4,7 +4,7 @@ import { useAsync, useLocalStorage } from 'react-use';
|
||||
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { Checkbox, CollapsableSection, Icon, stylesFactory, useTheme } from '@grafana/ui';
|
||||
import { Card, Checkbox, CollapsableSection, Icon, Spinner, stylesFactory, useTheme } from '@grafana/ui';
|
||||
import impressionSrv from 'app/core/services/impression_srv';
|
||||
import { getSectionStorageKey } from 'app/features/search/utils';
|
||||
import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId';
|
||||
@ -128,7 +128,15 @@ export const FolderSection: FC<SectionHeaderProps> = ({
|
||||
|
||||
const renderResults = () => {
|
||||
if (!results.value?.length) {
|
||||
return <div>No items found</div>;
|
||||
if (results.loading) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Card.Heading>No results found</Card.Heading>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return results.value.map((v) => {
|
||||
|
@ -83,6 +83,8 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
> ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
border: solid 1px ${theme.v1.colors.border2};
|
||||
`,
|
||||
section: css`
|
||||
display: flex;
|
||||
|
219
public/app/features/search/page/components/SearchView.tsx
Normal file
219
public/app/features/search/page/components/SearchView.tsx
Normal file
@ -0,0 +1,219 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { useStyles2, Spinner, Button } from '@grafana/ui';
|
||||
import { TermCount } from 'app/core/components/TagFilter/TagFilter';
|
||||
import { FolderDTO } from 'app/types';
|
||||
|
||||
import { PreviewsSystemRequirements } from '../../components/PreviewsSystemRequirements';
|
||||
import { useSearchQuery } from '../../hooks/useSearchQuery';
|
||||
import { getGrafanaSearcher, SearchQuery } from '../../service';
|
||||
import { SearchLayout } from '../../types';
|
||||
import { newSearchSelection, updateSearchSelection } from '../selection';
|
||||
|
||||
import { ActionRow, getValidQueryLayout } from './ActionRow';
|
||||
import { FolderSection } from './FolderSection';
|
||||
import { FolderView } from './FolderView';
|
||||
import { ManageActions } from './ManageActions';
|
||||
import { SearchResultsGrid } from './SearchResultsGrid';
|
||||
import { SearchResultsTable, SearchResultsProps } from './SearchResultsTable';
|
||||
|
||||
type SearchViewProps = {
|
||||
queryText: string; // odd that it is not from query.query
|
||||
showManage: boolean;
|
||||
folderDTO?: FolderDTO;
|
||||
};
|
||||
|
||||
export const SearchView = ({ showManage, folderDTO, queryText }: SearchViewProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const { query, onQueryChange, onTagFilterChange, onTagAdd, onDatasourceChange, onSortChange, onLayoutChange } =
|
||||
useSearchQuery({});
|
||||
query.query = queryText; // Use the query value passed in from parent rather than from URL
|
||||
|
||||
const [searchSelection, setSearchSelection] = useState(newSearchSelection());
|
||||
const layout = getValidQueryLayout(query);
|
||||
const isFolders = layout === SearchLayout.Folders;
|
||||
|
||||
const results = useAsync(() => {
|
||||
let qstr = queryText;
|
||||
if (!qstr?.length) {
|
||||
qstr = '*';
|
||||
}
|
||||
const q: SearchQuery = {
|
||||
query: qstr,
|
||||
tags: query.tag as string[],
|
||||
ds_uid: query.datasource as string,
|
||||
location: folderDTO?.uid, // This will scope all results to the prefix
|
||||
};
|
||||
return getGrafanaSearcher().search(q);
|
||||
}, [query, layout, queryText, folderDTO]);
|
||||
|
||||
const toggleSelection = useCallback(
|
||||
(kind: string, uid: string) => {
|
||||
const current = searchSelection.isSelected(kind, uid);
|
||||
if (kind === 'folder') {
|
||||
// ??? also select all children?
|
||||
}
|
||||
setSearchSelection(updateSearchSelection(searchSelection, !current, kind, [uid]));
|
||||
},
|
||||
[searchSelection]
|
||||
);
|
||||
|
||||
if (!config.featureToggles.panelTitleSearch) {
|
||||
return <div className={styles.unsupported}>Unsupported</div>;
|
||||
}
|
||||
|
||||
// This gets the possible tags from within the query results
|
||||
const getTagOptions = (): Promise<TermCount[]> => {
|
||||
const q: SearchQuery = {
|
||||
query: query.query?.length ? query.query : '*',
|
||||
tags: query.tag,
|
||||
ds_uid: query.datasource,
|
||||
};
|
||||
return getGrafanaSearcher().tags(q);
|
||||
};
|
||||
|
||||
// function to update items when dashboards or folders are moved or deleted
|
||||
const onChangeItemsList = async () => {
|
||||
// clean up search selection
|
||||
setSearchSelection(newSearchSelection());
|
||||
// trigger again the search to the backend
|
||||
onQueryChange(query.query);
|
||||
};
|
||||
|
||||
const renderResults = () => {
|
||||
const value = results.value;
|
||||
|
||||
if ((!value || !value.totalRows) && !isFolders) {
|
||||
if (results.loading && !value) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.noResults}>
|
||||
<div>No results found for your query.</div>
|
||||
<br />
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
if (query.query) {
|
||||
onQueryChange('');
|
||||
}
|
||||
if (query.tag?.length) {
|
||||
onTagFilterChange([]);
|
||||
}
|
||||
if (query.datasource) {
|
||||
onDatasourceChange(undefined);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Remove search constraints
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const selection = showManage ? searchSelection.isSelected : undefined;
|
||||
if (layout === SearchLayout.Folders) {
|
||||
if (folderDTO) {
|
||||
return (
|
||||
<FolderSection
|
||||
section={{ uid: folderDTO.uid, kind: 'folder', title: folderDTO.title }}
|
||||
selection={selection}
|
||||
selectionToggle={toggleSelection}
|
||||
onTagSelected={onTagAdd}
|
||||
renderStandaloneBody={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <FolderView selection={selection} selectionToggle={toggleSelection} onTagSelected={onTagAdd} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%', width: '100%' }}>
|
||||
<AutoSizer>
|
||||
{({ width, height }) => {
|
||||
const props: SearchResultsProps = {
|
||||
response: value!,
|
||||
selection,
|
||||
selectionToggle: toggleSelection,
|
||||
width: width,
|
||||
height: height,
|
||||
onTagSelected: onTagAdd,
|
||||
onDatasourceChange: query.datasource ? onDatasourceChange : undefined,
|
||||
};
|
||||
|
||||
if (layout === SearchLayout.Grid) {
|
||||
return <SearchResultsGrid {...props} />;
|
||||
}
|
||||
|
||||
return <SearchResultsTable {...props} />;
|
||||
}}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (!config.featureToggles.panelTitleSearch) {
|
||||
return <div className={styles.unsupported}>Unsupported</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{Boolean(searchSelection.items.size > 0) ? (
|
||||
<ManageActions items={searchSelection.items} onChange={onChangeItemsList} />
|
||||
) : (
|
||||
<ActionRow
|
||||
onLayoutChange={(v) => {
|
||||
if (v === SearchLayout.Folders) {
|
||||
if (query.query) {
|
||||
onQueryChange(''); // parent will clear the sort
|
||||
}
|
||||
}
|
||||
onLayoutChange(v);
|
||||
}}
|
||||
onSortChange={onSortChange}
|
||||
onTagFilterChange={onTagFilterChange}
|
||||
getTagOptions={getTagOptions}
|
||||
onDatasourceChange={onDatasourceChange}
|
||||
query={query}
|
||||
/>
|
||||
)}
|
||||
|
||||
{layout === SearchLayout.Grid && (
|
||||
<PreviewsSystemRequirements
|
||||
bottomSpacing={3}
|
||||
showPreviews={true}
|
||||
onRemove={() => onLayoutChange(SearchLayout.List)}
|
||||
/>
|
||||
)}
|
||||
{renderResults()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
searchInput: css`
|
||||
margin-bottom: 6px;
|
||||
min-height: ${theme.spacing(4)};
|
||||
`,
|
||||
unsupported: css`
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
font-size: 18px;
|
||||
`,
|
||||
noResults: css`
|
||||
padding: ${theme.v1.spacing.md};
|
||||
background: ${theme.v1.colors.bg2};
|
||||
font-style: italic;
|
||||
margin-top: ${theme.v1.spacing.md};
|
||||
`,
|
||||
});
|
Loading…
Reference in New Issue
Block a user