mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 10:20:29 -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')).toBeInTheDocument();
|
||||
expect(screen.queryByText(assertIsDefined(dashboard.item.tags)[0])).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')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
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(emptyFolderIndicator.item.kind)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -22,7 +22,6 @@ import CheckboxCell from './CheckboxCell';
|
||||
import CheckboxHeaderCell from './CheckboxHeaderCell';
|
||||
import { NameCell } from './NameCell';
|
||||
import { TagsCell } from './TagsCell';
|
||||
import { TypeCell } from './TypeCell';
|
||||
import { useCustomFlexLayout } from './customFlexTableLayout';
|
||||
|
||||
interface DashboardsTreeProps {
|
||||
@ -85,20 +84,13 @@ export function DashboardsTree({
|
||||
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 = {
|
||||
id: 'tags',
|
||||
width: 2,
|
||||
Header: t('browse-dashboards.dashboards-tree.tags-column', 'Tags'),
|
||||
Cell: TagsCell,
|
||||
};
|
||||
const columns = [canSelect && checkboxColumn, nameColumn, typeColumn, tagsColumns].filter(isTruthy);
|
||||
const columns = [canSelect && checkboxColumn, nameColumn, tagsColumns].filter(isTruthy);
|
||||
|
||||
return columns;
|
||||
}, [onFolderClick, canSelect]);
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React from 'react';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { CellProps } from 'react-table';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
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 { Span } from '@grafana/ui/src/unstable';
|
||||
import { getIconForKind } from 'app/features/search/service/utils';
|
||||
|
||||
import { useChildrenByParentUIDState } from '../state';
|
||||
import { DashboardsTreeItem } from '../types';
|
||||
@ -15,6 +16,7 @@ import { DashboardsTreeItem } from '../types';
|
||||
import { Indent } from './Indent';
|
||||
|
||||
const CHEVRON_SIZE = 'md';
|
||||
const ICON_SIZE = 'sm';
|
||||
|
||||
type NameCellProps = CellProps<DashboardsTreeItem, unknown> & {
|
||||
onFolderClick: (uid: string, newOpenState: boolean) => void;
|
||||
@ -24,17 +26,8 @@ export function NameCell({ row: { original: data }, onFolderClick }: NameCellPro
|
||||
const styles = useStyles2(getStyles);
|
||||
const { item, level, isOpen } = data;
|
||||
const childrenByParentUID = useChildrenByParentUIDState();
|
||||
const chevronRef = useRef<HTMLButtonElement>(null);
|
||||
const isLoading = isOpen && !childrenByParentUID[item.uid];
|
||||
const [shouldRestoreFocus, setShouldRestoreFocus] = useState(false);
|
||||
|
||||
// restore focus back to the original button when loading is complete
|
||||
useEffect(() => {
|
||||
if (!isLoading && chevronRef.current && shouldRestoreFocus) {
|
||||
chevronRef.current.focus();
|
||||
setShouldRestoreFocus(false);
|
||||
}
|
||||
}, [isLoading, shouldRestoreFocus]);
|
||||
const iconName = getIconForKind(data.item.kind, isOpen);
|
||||
|
||||
if (item.kind === 'ui') {
|
||||
return (
|
||||
@ -59,43 +52,36 @@ export function NameCell({ row: { original: data }, onFolderClick }: NameCellPro
|
||||
<Indent level={level} />
|
||||
|
||||
{item.kind === 'folder' ? (
|
||||
<>
|
||||
{isLoading ? (
|
||||
<Spinner className={styles.chevron} />
|
||||
) : (
|
||||
<IconButton
|
||||
size={CHEVRON_SIZE}
|
||||
className={styles.chevron}
|
||||
ref={chevronRef}
|
||||
onClick={() => {
|
||||
if (!isOpen && !childrenByParentUID[item.uid]) {
|
||||
setShouldRestoreFocus(true);
|
||||
}
|
||||
onFolderClick(item.uid, !isOpen);
|
||||
}}
|
||||
name={isOpen ? 'angle-down' : 'angle-right'}
|
||||
tooltip={isOpen ? 'Collapse folder' : 'Expand folder'}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<IconButton
|
||||
size={CHEVRON_SIZE}
|
||||
className={styles.chevron}
|
||||
onClick={() => {
|
||||
onFolderClick(item.uid, !isOpen);
|
||||
}}
|
||||
name={isOpen ? 'angle-down' : 'angle-right'}
|
||||
aria-label={isOpen ? 'Collapse folder' : 'Expand folder'}
|
||||
/>
|
||||
) : (
|
||||
<span className={styles.folderButtonSpacer} />
|
||||
)}
|
||||
<Span variant="body" truncate>
|
||||
{item.url ? (
|
||||
<Link
|
||||
onClick={() => {
|
||||
reportInteraction('manage_dashboards_result_clicked');
|
||||
}}
|
||||
href={item.url}
|
||||
className={styles.link}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
) : (
|
||||
item.title
|
||||
)}
|
||||
</Span>
|
||||
<div className={styles.iconNameContainer}>
|
||||
{isLoading ? <Spinner size={ICON_SIZE} /> : <Icon size={ICON_SIZE} name={iconName} />}
|
||||
<Span variant="body" truncate>
|
||||
{item.url ? (
|
||||
<Link
|
||||
onClick={() => {
|
||||
reportInteraction('manage_dashboards_result_clicked');
|
||||
}}
|
||||
href={item.url}
|
||||
className={styles.link}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
) : (
|
||||
item.title
|
||||
)}
|
||||
</Span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -114,6 +100,12 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
folderButtonSpacer: css({
|
||||
paddingLeft: `calc(${getSvgSize(CHEVRON_SIZE)}px + ${theme.spacing(1)})`,
|
||||
}),
|
||||
iconNameContainer: css({
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
gap: theme.spacing(1),
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
link: css({
|
||||
'&:hover': {
|
||||
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));
|
||||
}
|
||||
|
||||
export function getIconForKind(kind: string): IconName {
|
||||
export function getIconForKind(kind: string, isOpen?: boolean): IconName {
|
||||
if (kind === 'dashboard') {
|
||||
return 'apps';
|
||||
}
|
||||
|
||||
if (kind === 'folder') {
|
||||
return 'folder';
|
||||
return isOpen ? 'folder-open' : 'folder';
|
||||
}
|
||||
|
||||
return 'question-circle';
|
||||
|
@ -7,17 +7,11 @@
|
||||
},
|
||||
"dashboards-tree": {
|
||||
"name-column": "",
|
||||
"tags-column": "",
|
||||
"type-column": ""
|
||||
"tags-column": ""
|
||||
},
|
||||
"no-results": {
|
||||
"clear": "",
|
||||
"text": ""
|
||||
},
|
||||
"type-cell": {
|
||||
"dashboard": "",
|
||||
"folder": "",
|
||||
"panel": ""
|
||||
}
|
||||
},
|
||||
"clipboard-button": {
|
||||
|
@ -7,17 +7,11 @@
|
||||
},
|
||||
"dashboards-tree": {
|
||||
"name-column": "Name",
|
||||
"tags-column": "Tags",
|
||||
"type-column": "Type"
|
||||
"tags-column": "Tags"
|
||||
},
|
||||
"no-results": {
|
||||
"clear": "Clear search and filters",
|
||||
"text": "No results found for your query."
|
||||
},
|
||||
"type-cell": {
|
||||
"dashboard": "Dashboard",
|
||||
"folder": "Folder",
|
||||
"panel": "Panel"
|
||||
}
|
||||
},
|
||||
"clipboard-button": {
|
||||
|
@ -7,17 +7,11 @@
|
||||
},
|
||||
"dashboards-tree": {
|
||||
"name-column": "",
|
||||
"tags-column": "",
|
||||
"type-column": ""
|
||||
"tags-column": ""
|
||||
},
|
||||
"no-results": {
|
||||
"clear": "",
|
||||
"text": ""
|
||||
},
|
||||
"type-cell": {
|
||||
"dashboard": "",
|
||||
"folder": "",
|
||||
"panel": ""
|
||||
}
|
||||
},
|
||||
"clipboard-button": {
|
||||
|
@ -7,17 +7,11 @@
|
||||
},
|
||||
"dashboards-tree": {
|
||||
"name-column": "",
|
||||
"tags-column": "",
|
||||
"type-column": ""
|
||||
"tags-column": ""
|
||||
},
|
||||
"no-results": {
|
||||
"clear": "",
|
||||
"text": ""
|
||||
},
|
||||
"type-cell": {
|
||||
"dashboard": "",
|
||||
"folder": "",
|
||||
"panel": ""
|
||||
}
|
||||
},
|
||||
"clipboard-button": {
|
||||
|
@ -7,17 +7,11 @@
|
||||
},
|
||||
"dashboards-tree": {
|
||||
"name-column": "Ńämę",
|
||||
"tags-column": "Ŧäģş",
|
||||
"type-column": "Ŧypę"
|
||||
"tags-column": "Ŧäģş"
|
||||
},
|
||||
"no-results": {
|
||||
"clear": "Cľęäř şęäřčĥ äʼnđ ƒįľŧęřş",
|
||||
"text": "Ńő řęşūľŧş ƒőūʼnđ ƒőř yőūř qūęřy."
|
||||
},
|
||||
"type-cell": {
|
||||
"dashboard": "Đäşĥþőäřđ",
|
||||
"folder": "Főľđęř",
|
||||
"panel": "Päʼnęľ"
|
||||
}
|
||||
},
|
||||
"clipboard-button": {
|
||||
|
@ -7,17 +7,11 @@
|
||||
},
|
||||
"dashboards-tree": {
|
||||
"name-column": "",
|
||||
"tags-column": "",
|
||||
"type-column": ""
|
||||
"tags-column": ""
|
||||
},
|
||||
"no-results": {
|
||||
"clear": "",
|
||||
"text": ""
|
||||
},
|
||||
"type-cell": {
|
||||
"dashboard": "",
|
||||
"folder": "",
|
||||
"panel": ""
|
||||
}
|
||||
},
|
||||
"clipboard-button": {
|
||||
|
Loading…
Reference in New Issue
Block a user