mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NestedFolders: Show Tags in Browse Dashboards (#67029)
* NestedFolders: Show tags in Browse Dashboards view * tests
This commit is contained in:
@@ -2,6 +2,7 @@ import { render as rtlRender, screen } from '@testing-library/react';
|
|||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TestProvider } from 'test/helpers/TestProvider';
|
import { TestProvider } from 'test/helpers/TestProvider';
|
||||||
|
import { assertIsDefined } from 'test/helpers/asserts';
|
||||||
|
|
||||||
import { wellFormedDashboard, wellFormedEmptyFolder, wellFormedFolder } from '../fixtures/dashboardsTreeItem.fixture';
|
import { wellFormedDashboard, wellFormedEmptyFolder, wellFormedFolder } from '../fixtures/dashboardsTreeItem.fixture';
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ 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('Dashboard')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(assertIsDefined(dashboard.item.tags)[0])).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a folder item', () => {
|
it('renders a folder item', () => {
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import { DashboardViewItem, DashboardViewItemKind } from 'app/features/search/ty
|
|||||||
import { DashboardsTreeItem, DashboardTreeSelection, INDENT_AMOUNT_CSS_VAR } from '../types';
|
import { DashboardsTreeItem, DashboardTreeSelection, INDENT_AMOUNT_CSS_VAR } from '../types';
|
||||||
|
|
||||||
import { NameCell } from './NameCell';
|
import { NameCell } from './NameCell';
|
||||||
|
import { TagsCell } from './TagsCell';
|
||||||
import { TypeCell } from './TypeCell';
|
import { TypeCell } from './TypeCell';
|
||||||
|
import { useCustomFlexLayout } from './customFlexTableLayout';
|
||||||
|
|
||||||
interface DashboardsTreeProps {
|
interface DashboardsTreeProps {
|
||||||
items: DashboardsTreeItem[];
|
items: DashboardsTreeItem[];
|
||||||
@@ -45,6 +47,7 @@ export function DashboardsTree({
|
|||||||
const tableColumns = useMemo(() => {
|
const tableColumns = useMemo(() => {
|
||||||
const checkboxColumn: DashboardsTreeColumn = {
|
const checkboxColumn: DashboardsTreeColumn = {
|
||||||
id: 'checkbox',
|
id: 'checkbox',
|
||||||
|
width: 0,
|
||||||
Header: () => <Checkbox value={false} />,
|
Header: () => <Checkbox value={false} />,
|
||||||
Cell: ({ row: { original: row }, selectedItems }: DashboardsTreeCellProps) => {
|
Cell: ({ row: { original: row }, selectedItems }: DashboardsTreeCellProps) => {
|
||||||
const item = row.item;
|
const item = row.item;
|
||||||
@@ -65,20 +68,29 @@ export function DashboardsTree({
|
|||||||
|
|
||||||
const nameColumn: DashboardsTreeColumn = {
|
const nameColumn: DashboardsTreeColumn = {
|
||||||
id: 'name',
|
id: 'name',
|
||||||
|
width: 3,
|
||||||
Header: <span style={{ paddingLeft: 20 }}>Name</span>,
|
Header: <span style={{ paddingLeft: 20 }}>Name</span>,
|
||||||
Cell: (props: DashboardsTreeCellProps) => <NameCell {...props} onFolderClick={onFolderClick} />,
|
Cell: (props: DashboardsTreeCellProps) => <NameCell {...props} onFolderClick={onFolderClick} />,
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeColumn: DashboardsTreeColumn = {
|
const typeColumn: DashboardsTreeColumn = {
|
||||||
id: 'type',
|
id: 'type',
|
||||||
|
width: 1,
|
||||||
Header: 'Type',
|
Header: 'Type',
|
||||||
Cell: TypeCell,
|
Cell: TypeCell,
|
||||||
};
|
};
|
||||||
|
|
||||||
return [checkboxColumn, nameColumn, typeColumn];
|
const tagsColumns: DashboardsTreeColumn = {
|
||||||
|
id: 'tags',
|
||||||
|
width: 2,
|
||||||
|
Header: 'Tags',
|
||||||
|
Cell: TagsCell,
|
||||||
|
};
|
||||||
|
|
||||||
|
return [checkboxColumn, nameColumn, typeColumn, tagsColumns];
|
||||||
}, [onItemSelectionChange, onFolderClick]);
|
}, [onItemSelectionChange, onFolderClick]);
|
||||||
|
|
||||||
const table = useTable({ columns: tableColumns, data: items });
|
const table = useTable({ columns: tableColumns, data: items }, useCustomFlexLayout);
|
||||||
const { getTableProps, getTableBodyProps, headerGroups } = table;
|
const { getTableProps, getTableBodyProps, headerGroups } = table;
|
||||||
|
|
||||||
const virtualData = useMemo(() => {
|
const virtualData = useMemo(() => {
|
||||||
@@ -112,7 +124,6 @@ export function DashboardsTree({
|
|||||||
|
|
||||||
<div {...getTableBodyProps()}>
|
<div {...getTableBodyProps()}>
|
||||||
<List
|
<List
|
||||||
className="virtual list"
|
|
||||||
height={height - HEADER_HEIGHT}
|
height={height - HEADER_HEIGHT}
|
||||||
width={width}
|
width={width}
|
||||||
itemCount={items.length}
|
itemCount={items.length}
|
||||||
@@ -163,8 +174,6 @@ function VirtualListRow({ index, style, data }: VirtualListRowProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => {
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
const columnSizing = 'auto 2fr 1fr';
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tableRoot: css({
|
tableRoot: css({
|
||||||
// Responsively
|
// Responsively
|
||||||
@@ -175,17 +184,10 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
cell: css({
|
// Column flex properties (cell sizing) are set by customFlexTableLayout.ts
|
||||||
padding: theme.spacing(1),
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
}),
|
|
||||||
|
|
||||||
row: css({
|
row: css({
|
||||||
display: 'grid',
|
gap: theme.spacing(1),
|
||||||
gridTemplateColumns: columnSizing,
|
|
||||||
alignItems: 'center',
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
headerRow: css({
|
headerRow: css({
|
||||||
@@ -201,6 +203,13 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
cell: css({
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
}),
|
||||||
|
|
||||||
link: css({
|
link: css({
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
textDecoration: 'underline',
|
textDecoration: 'underline',
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
import { CellProps } from 'react-table';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { TagList, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { DashboardsTreeItem } from '../types';
|
||||||
|
|
||||||
|
export function TagsCell({ row: { original: data } }: CellProps<DashboardsTreeItem, unknown>) {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const item = data.item;
|
||||||
|
if (item.kind === 'ui-empty-folder') {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <TagList className={styles.tagList} tags={item.tags ?? []} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStyles(theme: GrafanaTheme2) {
|
||||||
|
return {
|
||||||
|
// TagList is annoying and has weird default alignment
|
||||||
|
tagList: css({
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Hooks, UseTableColumnProps } from 'react-table';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplified flex layout module for react-table.
|
||||||
|
* Uses the width of the column as the flex grow amount - the ratio of width between all columns
|
||||||
|
*
|
||||||
|
* Width of 0 for 'auto' width - useful for columns of fixed with that should shrink to the size
|
||||||
|
* of content
|
||||||
|
*
|
||||||
|
* Originally based on https://github.com/TanStack/table/blob/v7/src/plugin-hooks/useFlexLayout.js
|
||||||
|
*/
|
||||||
|
export function useCustomFlexLayout<D extends object>(hooks: Hooks<D>) {
|
||||||
|
hooks.getRowProps.push((props) => [props, getRowStyles()]);
|
||||||
|
hooks.getHeaderGroupProps.push((props) => [props, getRowStyles()]);
|
||||||
|
hooks.getFooterGroupProps.push((props) => [props, getRowStyles()]);
|
||||||
|
hooks.getHeaderProps.push((props, { column }) => [props, getColumnStyleProps(column)]);
|
||||||
|
hooks.getCellProps.push((props, { cell }) => [props, getColumnStyleProps(cell.column)]);
|
||||||
|
hooks.getFooterProps.push((props, { column }) => [props, getColumnStyleProps(column)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
useCustomFlexLayout.pluginName = 'useCustomFlexLayout';
|
||||||
|
|
||||||
|
function getColumnStyleProps<D extends object>(column: UseTableColumnProps<D>) {
|
||||||
|
return {
|
||||||
|
style: {
|
||||||
|
flex:
|
||||||
|
column.totalWidth === 0
|
||||||
|
? // if width: 0, prevent the column from growing (or shrinking), and set basis to auto to
|
||||||
|
// fit column to the width of its content
|
||||||
|
'0 0 auto'
|
||||||
|
: // Otherwise, grow the content to a size in proportion to the other column widths
|
||||||
|
`${column.totalWidth} 0 0`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRowStyles() {
|
||||||
|
return {
|
||||||
|
style: {
|
||||||
|
display: 'flex',
|
||||||
|
flex: '1 0 auto',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user