mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Search: Add ManageDashboardNew to integrate new search (#49266)
* Search: created ManageDashboardNew to integrate new search * hide pseudo folders in ManageDashboardNew * ManageDashboardNew - Fix overflow table Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
parent
9e5292d32d
commit
84a8a1aaa6
@ -1,9 +1,10 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
import React, { FC, memo } from 'react';
|
import React, { FC, memo } from 'react';
|
||||||
import { connect, MapStateToProps } from 'react-redux';
|
import { connect, MapStateToProps } from 'react-redux';
|
||||||
import { useAsync } from 'react-use';
|
import { useAsync } from 'react-use';
|
||||||
|
|
||||||
import { NavModel, locationUtil } from '@grafana/data';
|
import { NavModel, locationUtil } from '@grafana/data';
|
||||||
import { locationService } from '@grafana/runtime';
|
import { config, locationService } from '@grafana/runtime';
|
||||||
import Page from 'app/core/components/Page/Page';
|
import Page from 'app/core/components/Page/Page';
|
||||||
import { getNavModel } from 'app/core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
import { FolderDTO, StoreState } from 'app/types';
|
import { FolderDTO, StoreState } from 'app/types';
|
||||||
@ -12,6 +13,7 @@ import { GrafanaRouteComponentProps } from '../../../core/navigation/types';
|
|||||||
import { loadFolderPage } from '../loaders';
|
import { loadFolderPage } from '../loaders';
|
||||||
|
|
||||||
import ManageDashboards from './ManageDashboards';
|
import ManageDashboards from './ManageDashboards';
|
||||||
|
import ManageDashboardsNew from './ManageDashboardsNew';
|
||||||
|
|
||||||
export interface DashboardListPageRouteParams {
|
export interface DashboardListPageRouteParams {
|
||||||
uid?: string;
|
uid?: string;
|
||||||
@ -44,9 +46,23 @@ export const DashboardListPage: FC<Props> = memo(({ navModel, match, location })
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Page navModel={value?.pageNavModel ?? navModel}>
|
<Page navModel={value?.pageNavModel ?? navModel}>
|
||||||
<Page.Contents isLoading={loading}>
|
{/*Todo: remove the false to test, or when we feel confident with thsi approach */}
|
||||||
<ManageDashboards folder={value?.folder} />
|
{false && config.featureToggles.panelTitleSearch ? (
|
||||||
</Page.Contents>
|
<Page.Contents
|
||||||
|
isLoading={loading}
|
||||||
|
className={css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<ManageDashboardsNew folder={value?.folder} />
|
||||||
|
</Page.Contents>
|
||||||
|
) : (
|
||||||
|
<Page.Contents isLoading={loading}>
|
||||||
|
<ManageDashboards folder={value?.folder} />
|
||||||
|
</Page.Contents>
|
||||||
|
)}
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useDebounce } from 'react-use';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { Input, useStyles2, Spinner } from '@grafana/ui';
|
||||||
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
|
import { FolderDTO } from 'app/types';
|
||||||
|
|
||||||
|
import { useSearchQuery } from '../hooks/useSearchQuery';
|
||||||
|
import { SearchView } from '../page/components/SearchView';
|
||||||
|
|
||||||
|
import { DashboardActions } from './DashboardActions';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
folder?: FolderDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ManageDashboardsNew = React.memo(({ folder }: Props) => {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
// since we don't use "query" from use search... it is not actually loaded from the URL!
|
||||||
|
const { query, onQueryChange } = useSearchQuery({});
|
||||||
|
|
||||||
|
// TODO: we need to refactor DashboardActions to use folder.uid instead
|
||||||
|
const folderId = folder?.id;
|
||||||
|
// const folderUid = folder?.uid;
|
||||||
|
const canSave = folder?.canSave;
|
||||||
|
const hasEditPermissionInFolders = folder ? canSave : contextSrv.hasEditPermissionInFolders;
|
||||||
|
|
||||||
|
const { isEditor } = contextSrv;
|
||||||
|
|
||||||
|
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 className="page-action-bar">
|
||||||
|
<div className="gf-form gf-form--grow m-r-2">
|
||||||
|
<Input
|
||||||
|
value={inputValue}
|
||||||
|
onChange={onSearchQueryChange}
|
||||||
|
autoFocus
|
||||||
|
spellCheck={false}
|
||||||
|
placeholder="Search for dashboards and panels"
|
||||||
|
className={styles.searchInput}
|
||||||
|
suffix={false ? <Spinner /> : null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<DashboardActions isEditor={isEditor} canEdit={hasEditPermissionInFolders || canSave} folderId={folderId} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SearchView
|
||||||
|
showManage={isEditor || hasEditPermissionInFolders || canSave}
|
||||||
|
folderDTO={folder}
|
||||||
|
queryText={query.query}
|
||||||
|
hidePseudoFolders={true}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ManageDashboardsNew.displayName = 'ManageDashboardsNew';
|
||||||
|
|
||||||
|
export default ManageDashboardsNew;
|
||||||
|
|
||||||
|
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};
|
||||||
|
`,
|
||||||
|
});
|
@ -21,6 +21,7 @@ export interface DashboardSection {
|
|||||||
selected?: boolean; // not used ? keyboard
|
selected?: boolean; // not used ? keyboard
|
||||||
url?: string;
|
url?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
itemsUIDs?: string[]; // for pseudo folders
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SectionHeaderProps {
|
interface SectionHeaderProps {
|
||||||
@ -57,12 +58,9 @@ export const FolderSection: FC<SectionHeaderProps> = ({
|
|||||||
location: section.uid,
|
location: section.uid,
|
||||||
};
|
};
|
||||||
if (section.title === 'Starred') {
|
if (section.title === 'Starred') {
|
||||||
const stars = await getBackendSrv().get('api/user/stars');
|
query = {
|
||||||
if (stars.length > 0) {
|
uid: section.itemsUIDs, // array of UIDs
|
||||||
query = {
|
};
|
||||||
uid: stars, // array of UIDs
|
|
||||||
};
|
|
||||||
}
|
|
||||||
folderUid = undefined;
|
folderUid = undefined;
|
||||||
folderTitle = undefined;
|
folderTitle = undefined;
|
||||||
} else if (section.title === 'Recent') {
|
} else if (section.title === 'Recent') {
|
||||||
|
@ -4,6 +4,7 @@ import { useAsync } from 'react-use';
|
|||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
import { Spinner, useStyles2 } from '@grafana/ui';
|
import { Spinner, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { GENERAL_FOLDER_UID } from '../../constants';
|
import { GENERAL_FOLDER_UID } from '../../constants';
|
||||||
@ -12,20 +13,28 @@ import { SearchResultsProps } from '../components/SearchResultsTable';
|
|||||||
|
|
||||||
import { DashboardSection, FolderSection } from './FolderSection';
|
import { DashboardSection, FolderSection } from './FolderSection';
|
||||||
|
|
||||||
type Props = Pick<SearchResultsProps, 'selection' | 'selectionToggle' | 'onTagSelected'> & { tags?: string[] };
|
type Props = Pick<SearchResultsProps, 'selection' | 'selectionToggle' | 'onTagSelected'> & {
|
||||||
export const FolderView = ({ selection, selectionToggle, onTagSelected, tags }: Props) => {
|
tags?: string[];
|
||||||
|
hidePseudoFolders?: boolean;
|
||||||
|
};
|
||||||
|
export const FolderView = ({ selection, selectionToggle, onTagSelected, tags, hidePseudoFolders }: Props) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const results = useAsync(async () => {
|
const results = useAsync(async () => {
|
||||||
|
const folders: DashboardSection[] = [];
|
||||||
|
if (!hidePseudoFolders) {
|
||||||
|
const stars = await getBackendSrv().get('api/user/stars');
|
||||||
|
if (stars.length > 0) {
|
||||||
|
folders.push({ title: 'Starred', icon: 'star', kind: 'query-star', uid: '__starred', itemsUIDs: stars });
|
||||||
|
}
|
||||||
|
folders.push({ title: 'Recent', icon: 'clock', kind: 'query-recent', uid: '__recent' });
|
||||||
|
}
|
||||||
|
folders.push({ title: 'General', url: '/dashboards', kind: 'folder', uid: GENERAL_FOLDER_UID });
|
||||||
|
|
||||||
const rsp = await getGrafanaSearcher().search({
|
const rsp = await getGrafanaSearcher().search({
|
||||||
query: '*',
|
query: '*',
|
||||||
kind: ['folder'],
|
kind: ['folder'],
|
||||||
});
|
});
|
||||||
const folders: DashboardSection[] = [
|
|
||||||
{ title: 'Recent', icon: 'clock', kind: 'query-recent', uid: '__recent' },
|
|
||||||
{ title: 'Starred', icon: 'star', kind: 'query-star', uid: '__starred' },
|
|
||||||
{ title: 'General', url: '/dashboards', kind: 'folder', uid: GENERAL_FOLDER_UID }, // not sure why this is not in the index
|
|
||||||
];
|
|
||||||
for (const row of rsp.view) {
|
for (const row of rsp.view) {
|
||||||
folders.push({
|
folders.push({
|
||||||
title: row.name,
|
title: row.name,
|
||||||
|
@ -26,9 +26,10 @@ type SearchViewProps = {
|
|||||||
queryText: string; // odd that it is not from query.query
|
queryText: string; // odd that it is not from query.query
|
||||||
showManage: boolean;
|
showManage: boolean;
|
||||||
folderDTO?: FolderDTO;
|
folderDTO?: FolderDTO;
|
||||||
|
hidePseudoFolders?: boolean; // Recent + starred
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SearchView = ({ showManage, folderDTO, queryText }: SearchViewProps) => {
|
export const SearchView = ({ showManage, folderDTO, queryText, hidePseudoFolders }: SearchViewProps) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const { query, onQueryChange, onTagFilterChange, onTagAdd, onDatasourceChange, onSortChange, onLayoutChange } =
|
const { query, onQueryChange, onTagFilterChange, onTagAdd, onDatasourceChange, onSortChange, onLayoutChange } =
|
||||||
@ -132,7 +133,13 @@ export const SearchView = ({ showManage, folderDTO, queryText }: SearchViewProps
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<FolderView selection={selection} selectionToggle={toggleSelection} tags={query.tag} onTagSelected={onTagAdd} />
|
<FolderView
|
||||||
|
selection={selection}
|
||||||
|
selectionToggle={toggleSelection}
|
||||||
|
tags={query.tag}
|
||||||
|
onTagSelected={onTagAdd}
|
||||||
|
hidePseudoFolders={hidePseudoFolders}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user