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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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