mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Nested folders: Replace explicit type column with icon in browse view (#70666)
* remove type column and add icon instead * extract translations
This commit is contained in:
parent
9ebede1e18
commit
8ea74333f1
@ -43,7 +43,6 @@ describe('browse-dashboards DashboardsTree', () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(screen.queryByText(dashboard.item.title)).toBeInTheDocument();
|
expect(screen.queryByText(dashboard.item.title)).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Dashboard')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByText(assertIsDefined(dashboard.item.tags)[0])).toBeInTheDocument();
|
expect(screen.queryByText(assertIsDefined(dashboard.item.tags)[0])).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(selectors.pages.BrowseDashbards.table.checkbox(dashboard.item.uid))).toBeInTheDocument();
|
expect(screen.getByTestId(selectors.pages.BrowseDashbards.table.checkbox(dashboard.item.uid))).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@ -84,7 +83,6 @@ describe('browse-dashboards DashboardsTree', () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(screen.queryByText(folder.item.title)).toBeInTheDocument();
|
expect(screen.queryByText(folder.item.title)).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Folder')).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls onFolderClick when a folder button is clicked', async () => {
|
it('calls onFolderClick when a folder button is clicked', async () => {
|
||||||
@ -125,6 +123,5 @@ describe('browse-dashboards DashboardsTree', () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(screen.queryByText('No items')).toBeInTheDocument();
|
expect(screen.queryByText('No items')).toBeInTheDocument();
|
||||||
expect(screen.queryByText(emptyFolderIndicator.item.kind)).not.toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -22,7 +22,6 @@ import CheckboxCell from './CheckboxCell';
|
|||||||
import CheckboxHeaderCell from './CheckboxHeaderCell';
|
import CheckboxHeaderCell from './CheckboxHeaderCell';
|
||||||
import { NameCell } from './NameCell';
|
import { NameCell } from './NameCell';
|
||||||
import { TagsCell } from './TagsCell';
|
import { TagsCell } from './TagsCell';
|
||||||
import { TypeCell } from './TypeCell';
|
|
||||||
import { useCustomFlexLayout } from './customFlexTableLayout';
|
import { useCustomFlexLayout } from './customFlexTableLayout';
|
||||||
|
|
||||||
interface DashboardsTreeProps {
|
interface DashboardsTreeProps {
|
||||||
@ -85,20 +84,13 @@ export function DashboardsTree({
|
|||||||
Cell: (props: DashboardsTreeCellProps) => <NameCell {...props} onFolderClick={onFolderClick} />,
|
Cell: (props: DashboardsTreeCellProps) => <NameCell {...props} onFolderClick={onFolderClick} />,
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeColumn: DashboardsTreeColumn = {
|
|
||||||
id: 'type',
|
|
||||||
width: 1,
|
|
||||||
Header: t('browse-dashboards.dashboards-tree.type-column', 'Type'),
|
|
||||||
Cell: TypeCell,
|
|
||||||
};
|
|
||||||
|
|
||||||
const tagsColumns: DashboardsTreeColumn = {
|
const tagsColumns: DashboardsTreeColumn = {
|
||||||
id: 'tags',
|
id: 'tags',
|
||||||
width: 2,
|
width: 2,
|
||||||
Header: t('browse-dashboards.dashboards-tree.tags-column', 'Tags'),
|
Header: t('browse-dashboards.dashboards-tree.tags-column', 'Tags'),
|
||||||
Cell: TagsCell,
|
Cell: TagsCell,
|
||||||
};
|
};
|
||||||
const columns = [canSelect && checkboxColumn, nameColumn, typeColumn, tagsColumns].filter(isTruthy);
|
const columns = [canSelect && checkboxColumn, nameColumn, tagsColumns].filter(isTruthy);
|
||||||
|
|
||||||
return columns;
|
return columns;
|
||||||
}, [onFolderClick, canSelect]);
|
}, [onFolderClick, canSelect]);
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React from 'react';
|
||||||
import Skeleton from 'react-loading-skeleton';
|
import Skeleton from 'react-loading-skeleton';
|
||||||
import { CellProps } from 'react-table';
|
import { CellProps } from 'react-table';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { reportInteraction } from '@grafana/runtime';
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
import { IconButton, Link, Spinner, useStyles2 } from '@grafana/ui';
|
import { Icon, IconButton, Link, Spinner, useStyles2 } from '@grafana/ui';
|
||||||
import { getSvgSize } from '@grafana/ui/src/components/Icon/utils';
|
import { getSvgSize } from '@grafana/ui/src/components/Icon/utils';
|
||||||
import { Span } from '@grafana/ui/src/unstable';
|
import { Span } from '@grafana/ui/src/unstable';
|
||||||
|
import { getIconForKind } from 'app/features/search/service/utils';
|
||||||
|
|
||||||
import { useChildrenByParentUIDState } from '../state';
|
import { useChildrenByParentUIDState } from '../state';
|
||||||
import { DashboardsTreeItem } from '../types';
|
import { DashboardsTreeItem } from '../types';
|
||||||
@ -15,6 +16,7 @@ import { DashboardsTreeItem } from '../types';
|
|||||||
import { Indent } from './Indent';
|
import { Indent } from './Indent';
|
||||||
|
|
||||||
const CHEVRON_SIZE = 'md';
|
const CHEVRON_SIZE = 'md';
|
||||||
|
const ICON_SIZE = 'sm';
|
||||||
|
|
||||||
type NameCellProps = CellProps<DashboardsTreeItem, unknown> & {
|
type NameCellProps = CellProps<DashboardsTreeItem, unknown> & {
|
||||||
onFolderClick: (uid: string, newOpenState: boolean) => void;
|
onFolderClick: (uid: string, newOpenState: boolean) => void;
|
||||||
@ -24,17 +26,8 @@ export function NameCell({ row: { original: data }, onFolderClick }: NameCellPro
|
|||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const { item, level, isOpen } = data;
|
const { item, level, isOpen } = data;
|
||||||
const childrenByParentUID = useChildrenByParentUIDState();
|
const childrenByParentUID = useChildrenByParentUIDState();
|
||||||
const chevronRef = useRef<HTMLButtonElement>(null);
|
|
||||||
const isLoading = isOpen && !childrenByParentUID[item.uid];
|
const isLoading = isOpen && !childrenByParentUID[item.uid];
|
||||||
const [shouldRestoreFocus, setShouldRestoreFocus] = useState(false);
|
const iconName = getIconForKind(data.item.kind, isOpen);
|
||||||
|
|
||||||
// restore focus back to the original button when loading is complete
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isLoading && chevronRef.current && shouldRestoreFocus) {
|
|
||||||
chevronRef.current.focus();
|
|
||||||
setShouldRestoreFocus(false);
|
|
||||||
}
|
|
||||||
}, [isLoading, shouldRestoreFocus]);
|
|
||||||
|
|
||||||
if (item.kind === 'ui') {
|
if (item.kind === 'ui') {
|
||||||
return (
|
return (
|
||||||
@ -59,43 +52,36 @@ export function NameCell({ row: { original: data }, onFolderClick }: NameCellPro
|
|||||||
<Indent level={level} />
|
<Indent level={level} />
|
||||||
|
|
||||||
{item.kind === 'folder' ? (
|
{item.kind === 'folder' ? (
|
||||||
<>
|
<IconButton
|
||||||
{isLoading ? (
|
size={CHEVRON_SIZE}
|
||||||
<Spinner className={styles.chevron} />
|
className={styles.chevron}
|
||||||
) : (
|
onClick={() => {
|
||||||
<IconButton
|
onFolderClick(item.uid, !isOpen);
|
||||||
size={CHEVRON_SIZE}
|
}}
|
||||||
className={styles.chevron}
|
name={isOpen ? 'angle-down' : 'angle-right'}
|
||||||
ref={chevronRef}
|
aria-label={isOpen ? 'Collapse folder' : 'Expand folder'}
|
||||||
onClick={() => {
|
/>
|
||||||
if (!isOpen && !childrenByParentUID[item.uid]) {
|
|
||||||
setShouldRestoreFocus(true);
|
|
||||||
}
|
|
||||||
onFolderClick(item.uid, !isOpen);
|
|
||||||
}}
|
|
||||||
name={isOpen ? 'angle-down' : 'angle-right'}
|
|
||||||
tooltip={isOpen ? 'Collapse folder' : 'Expand folder'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<span className={styles.folderButtonSpacer} />
|
<span className={styles.folderButtonSpacer} />
|
||||||
)}
|
)}
|
||||||
<Span variant="body" truncate>
|
<div className={styles.iconNameContainer}>
|
||||||
{item.url ? (
|
{isLoading ? <Spinner size={ICON_SIZE} /> : <Icon size={ICON_SIZE} name={iconName} />}
|
||||||
<Link
|
<Span variant="body" truncate>
|
||||||
onClick={() => {
|
{item.url ? (
|
||||||
reportInteraction('manage_dashboards_result_clicked');
|
<Link
|
||||||
}}
|
onClick={() => {
|
||||||
href={item.url}
|
reportInteraction('manage_dashboards_result_clicked');
|
||||||
className={styles.link}
|
}}
|
||||||
>
|
href={item.url}
|
||||||
{item.title}
|
className={styles.link}
|
||||||
</Link>
|
>
|
||||||
) : (
|
{item.title}
|
||||||
item.title
|
</Link>
|
||||||
)}
|
) : (
|
||||||
</Span>
|
item.title
|
||||||
|
)}
|
||||||
|
</Span>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -114,6 +100,12 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
folderButtonSpacer: css({
|
folderButtonSpacer: css({
|
||||||
paddingLeft: `calc(${getSvgSize(CHEVRON_SIZE)}px + ${theme.spacing(1)})`,
|
paddingLeft: `calc(${getSvgSize(CHEVRON_SIZE)}px + ${theme.spacing(1)})`,
|
||||||
}),
|
}),
|
||||||
|
iconNameContainer: css({
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
overflow: 'hidden',
|
||||||
|
}),
|
||||||
link: css({
|
link: css({
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
textDecoration: 'underline',
|
textDecoration: 'underline',
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import React from 'react';
|
|
||||||
import Skeleton from 'react-loading-skeleton';
|
|
||||||
import { CellProps } from 'react-table';
|
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
|
||||||
import { Icon, useStyles2 } from '@grafana/ui';
|
|
||||||
import { Span } from '@grafana/ui/src/unstable';
|
|
||||||
import { Trans } from 'app/core/internationalization';
|
|
||||||
import { getIconForKind } from 'app/features/search/service/utils';
|
|
||||||
|
|
||||||
import { DashboardsTreeItem } from '../types';
|
|
||||||
|
|
||||||
export function TypeCell({ row: { original: data } }: CellProps<DashboardsTreeItem, unknown>) {
|
|
||||||
const iconName = getIconForKind(data.item.kind);
|
|
||||||
const styles = useStyles2(getStyles);
|
|
||||||
|
|
||||||
switch (data.item.kind) {
|
|
||||||
case 'dashboard':
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<Icon name={iconName} />
|
|
||||||
<Span variant="body" color="secondary" truncate>
|
|
||||||
<Trans i18nKey="browse-dashboards.type-cell.dashboard">Dashboard</Trans>
|
|
||||||
</Span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case 'folder':
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<Icon name={iconName} />
|
|
||||||
<Span variant="body" color="secondary" truncate>
|
|
||||||
<Trans i18nKey="browse-dashboards.type-cell.folder">Folder</Trans>
|
|
||||||
</Span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case 'panel':
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<Icon name={iconName} />
|
|
||||||
<Span variant="body" color="secondary" truncate>
|
|
||||||
<Trans i18nKey="browse-dashboards.type-cell.panel">Panel</Trans>
|
|
||||||
</Span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case 'ui':
|
|
||||||
return data.item.uiKind === 'empty-folder' ? null : <Skeleton width={100} />;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
|
||||||
container: css({
|
|
||||||
alignItems: 'center',
|
|
||||||
display: 'flex',
|
|
||||||
flexWrap: 'nowrap',
|
|
||||||
gap: theme.spacing(0.5),
|
|
||||||
// needed for text to truncate correctly
|
|
||||||
overflow: 'hidden',
|
|
||||||
}),
|
|
||||||
});
|
|
@ -38,13 +38,13 @@ function delay(ms: number) {
|
|||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getIconForKind(kind: string): IconName {
|
export function getIconForKind(kind: string, isOpen?: boolean): IconName {
|
||||||
if (kind === 'dashboard') {
|
if (kind === 'dashboard') {
|
||||||
return 'apps';
|
return 'apps';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kind === 'folder') {
|
if (kind === 'folder') {
|
||||||
return 'folder';
|
return isOpen ? 'folder-open' : 'folder';
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'question-circle';
|
return 'question-circle';
|
||||||
|
@ -7,17 +7,11 @@
|
|||||||
},
|
},
|
||||||
"dashboards-tree": {
|
"dashboards-tree": {
|
||||||
"name-column": "",
|
"name-column": "",
|
||||||
"tags-column": "",
|
"tags-column": ""
|
||||||
"type-column": ""
|
|
||||||
},
|
},
|
||||||
"no-results": {
|
"no-results": {
|
||||||
"clear": "",
|
"clear": "",
|
||||||
"text": ""
|
"text": ""
|
||||||
},
|
|
||||||
"type-cell": {
|
|
||||||
"dashboard": "",
|
|
||||||
"folder": "",
|
|
||||||
"panel": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clipboard-button": {
|
"clipboard-button": {
|
||||||
|
@ -7,17 +7,11 @@
|
|||||||
},
|
},
|
||||||
"dashboards-tree": {
|
"dashboards-tree": {
|
||||||
"name-column": "Name",
|
"name-column": "Name",
|
||||||
"tags-column": "Tags",
|
"tags-column": "Tags"
|
||||||
"type-column": "Type"
|
|
||||||
},
|
},
|
||||||
"no-results": {
|
"no-results": {
|
||||||
"clear": "Clear search and filters",
|
"clear": "Clear search and filters",
|
||||||
"text": "No results found for your query."
|
"text": "No results found for your query."
|
||||||
},
|
|
||||||
"type-cell": {
|
|
||||||
"dashboard": "Dashboard",
|
|
||||||
"folder": "Folder",
|
|
||||||
"panel": "Panel"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clipboard-button": {
|
"clipboard-button": {
|
||||||
|
@ -7,17 +7,11 @@
|
|||||||
},
|
},
|
||||||
"dashboards-tree": {
|
"dashboards-tree": {
|
||||||
"name-column": "",
|
"name-column": "",
|
||||||
"tags-column": "",
|
"tags-column": ""
|
||||||
"type-column": ""
|
|
||||||
},
|
},
|
||||||
"no-results": {
|
"no-results": {
|
||||||
"clear": "",
|
"clear": "",
|
||||||
"text": ""
|
"text": ""
|
||||||
},
|
|
||||||
"type-cell": {
|
|
||||||
"dashboard": "",
|
|
||||||
"folder": "",
|
|
||||||
"panel": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clipboard-button": {
|
"clipboard-button": {
|
||||||
|
@ -7,17 +7,11 @@
|
|||||||
},
|
},
|
||||||
"dashboards-tree": {
|
"dashboards-tree": {
|
||||||
"name-column": "",
|
"name-column": "",
|
||||||
"tags-column": "",
|
"tags-column": ""
|
||||||
"type-column": ""
|
|
||||||
},
|
},
|
||||||
"no-results": {
|
"no-results": {
|
||||||
"clear": "",
|
"clear": "",
|
||||||
"text": ""
|
"text": ""
|
||||||
},
|
|
||||||
"type-cell": {
|
|
||||||
"dashboard": "",
|
|
||||||
"folder": "",
|
|
||||||
"panel": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clipboard-button": {
|
"clipboard-button": {
|
||||||
|
@ -7,17 +7,11 @@
|
|||||||
},
|
},
|
||||||
"dashboards-tree": {
|
"dashboards-tree": {
|
||||||
"name-column": "Ńämę",
|
"name-column": "Ńämę",
|
||||||
"tags-column": "Ŧäģş",
|
"tags-column": "Ŧäģş"
|
||||||
"type-column": "Ŧypę"
|
|
||||||
},
|
},
|
||||||
"no-results": {
|
"no-results": {
|
||||||
"clear": "Cľęäř şęäřčĥ äʼnđ ƒįľŧęřş",
|
"clear": "Cľęäř şęäřčĥ äʼnđ ƒįľŧęřş",
|
||||||
"text": "Ńő řęşūľŧş ƒőūʼnđ ƒőř yőūř qūęřy."
|
"text": "Ńő řęşūľŧş ƒőūʼnđ ƒőř yőūř qūęřy."
|
||||||
},
|
|
||||||
"type-cell": {
|
|
||||||
"dashboard": "Đäşĥþőäřđ",
|
|
||||||
"folder": "Főľđęř",
|
|
||||||
"panel": "Päʼnęľ"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clipboard-button": {
|
"clipboard-button": {
|
||||||
|
@ -7,17 +7,11 @@
|
|||||||
},
|
},
|
||||||
"dashboards-tree": {
|
"dashboards-tree": {
|
||||||
"name-column": "",
|
"name-column": "",
|
||||||
"tags-column": "",
|
"tags-column": ""
|
||||||
"type-column": ""
|
|
||||||
},
|
},
|
||||||
"no-results": {
|
"no-results": {
|
||||||
"clear": "",
|
"clear": "",
|
||||||
"text": ""
|
"text": ""
|
||||||
},
|
|
||||||
"type-cell": {
|
|
||||||
"dashboard": "",
|
|
||||||
"folder": "",
|
|
||||||
"panel": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clipboard-button": {
|
"clipboard-button": {
|
||||||
|
Loading…
Reference in New Issue
Block a user