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:
Ashley Harrison 2023-06-26 11:58:26 +01:00 committed by GitHub
parent 9ebede1e18
commit 8ea74333f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 47 additions and 164 deletions

View File

@ -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();
});
});

View File

@ -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]);

View File

@ -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',

View File

@ -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',
}),
});

View File

@ -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';

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {