diff --git a/packages/grafana-ui/src/components/Table/DefaultCell.tsx b/packages/grafana-ui/src/components/Table/DefaultCell.tsx index 7f9fcd54b2f..503dbcd4da3 100644 --- a/packages/grafana-ui/src/components/Table/DefaultCell.tsx +++ b/packages/grafana-ui/src/components/Table/DefaultCell.tsx @@ -5,10 +5,10 @@ import { TableCellDisplayMode, TableCellProps } from './types'; import tinycolor from 'tinycolor2'; import { TableStyles } from './styles'; import { FilterActions } from './FilterActions'; -import { getTextColorForBackground } from '../../utils'; +import { getTextColorForBackground, getCellLinks } from '../../utils'; export const DefaultCell: FC = (props) => { - const { field, cell, tableStyles, cellProps } = props; + const { field, cell, tableStyles, row, cellProps } = props; const displayValue = field.display!(cell.value); @@ -22,9 +22,16 @@ export const DefaultCell: FC = (props) => { const cellStyle = getCellStyle(tableStyles, field, displayValue); const showFilters = field.config.filterable; + const { link, onClick } = getCellLinks(field, row); + return (
-
{value}
+ {!link &&
{value}
} + {link && ( + + {value} + + )} {showFilters && cell.value !== undefined && }
); diff --git a/packages/grafana-ui/src/components/Table/ImageCell.tsx b/packages/grafana-ui/src/components/Table/ImageCell.tsx index f855150babf..1a11d3eede7 100644 --- a/packages/grafana-ui/src/components/Table/ImageCell.tsx +++ b/packages/grafana-ui/src/components/Table/ImageCell.tsx @@ -1,14 +1,28 @@ import React, { FC } from 'react'; +import { getCellLinks } from '../../utils'; import { TableCellProps } from './types'; export const ImageCell: FC = (props) => { - const { field, cell, tableStyles, cellProps } = props; + const { field, cell, tableStyles, row, cellProps } = props; const displayValue = field.display!(cell.value); + const { link, onClick } = getCellLinks(field, row); + return (
- + {!link && } + {link && ( + + + + )}
); }; diff --git a/packages/grafana-ui/src/components/Table/Table.test.tsx b/packages/grafana-ui/src/components/Table/Table.test.tsx index d4f537c54df..bbe2d544c4b 100644 --- a/packages/grafana-ui/src/components/Table/Table.test.tsx +++ b/packages/grafana-ui/src/components/Table/Table.test.tsx @@ -98,6 +98,10 @@ function getColumnHeader(name: string | RegExp): HTMLElement { return within(getTable()).getByRole('columnheader', { name }); } +function getLinks(row: HTMLElement): HTMLElement[] { + return within(row).getAllByRole('link'); +} + describe('Table', () => { describe('when mounted without data', () => { it('then no data to show should be displayed', () => { @@ -118,20 +122,27 @@ describe('Table', () => { expect(getColumnHeader(/img/)).toBeInTheDocument(); const rows = within(getTable()).getAllByRole('row'); + const rowOneLink = () => getLinks(rows[1])[0]; + const rowTwoLink = () => getLinks(rows[2])[0]; + const rowThreeLink = () => getLinks(rows[3])[0]; + expect(rows).toHaveLength(4); - expect(within(rows[1]).getByRole('cell', { name: '2021-01-01 00:00:00' })).toBeInTheDocument(); - expect(within(rows[1]).getByRole('cell', { name: '10' })).toBeInTheDocument(); - expect(within(rows[2]).getByRole('cell', { name: '2021-01-01 01:00:00' })).toBeInTheDocument(); - expect(within(rows[2]).getByRole('cell', { name: '11' })).toBeInTheDocument(); - expect(within(rows[3]).getByRole('cell', { name: '2021-01-01 02:00:00' })).toBeInTheDocument(); - expect(within(rows[3]).getByRole('cell', { name: '12' })).toBeInTheDocument(); - expect(within(rows[1]).getByRole('cell', { name: '10' }).closest('a')).toHaveAttribute('href', '10'); - expect(within(rows[2]).getByRole('cell', { name: '11' }).closest('a')).toHaveAttribute('href', '11'); - expect(within(rows[3]).getByRole('cell', { name: '12' }).closest('a')).toHaveAttribute('href', '12'); + expect(within(rows[1]).getByText('2021-01-01 00:00:00')).toBeInTheDocument(); + expect(getLinks(rows[1])).toHaveLength(2); + expect(within(rows[2]).getByText('2021-01-01 01:00:00')).toBeInTheDocument(); + expect(getLinks(rows[2])).toHaveLength(2); + expect(within(rows[3]).getByText('2021-01-01 02:00:00')).toBeInTheDocument(); + expect(getLinks(rows[3])).toHaveLength(2); + expect(rowOneLink()).toHaveTextContent('10'); + expect(rowOneLink()).toHaveAttribute('href', '10'); + expect(rowTwoLink()).toHaveTextContent('11'); + expect(rowTwoLink()).toHaveAttribute('href', '11'); + expect(rowThreeLink()).toHaveTextContent('12'); + expect(rowThreeLink()).toHaveAttribute('href', '12'); }); }); - describe('when sorting with columnheader', () => { + describe('when sorting with column header', () => { it('then correct rows should be rendered', () => { getTestContext(); @@ -140,26 +151,19 @@ describe('Table', () => { const rows = within(getTable()).getAllByRole('row'); expect(rows).toHaveLength(4); - expect(within(rows[1]).getByRole('cell', { name: '2021-01-01 02:00:00' })).toBeInTheDocument(); - expect(within(rows[1]).getByRole('cell', { name: '12' })).toBeInTheDocument(); - expect(within(rows[2]).getByRole('cell', { name: '2021-01-01 01:00:00' })).toBeInTheDocument(); - expect(within(rows[2]).getByRole('cell', { name: '11' })).toBeInTheDocument(); - expect(within(rows[3]).getByRole('cell', { name: '2021-01-01 00:00:00' })).toBeInTheDocument(); - expect(within(rows[3]).getByRole('cell', { name: '10' })).toBeInTheDocument(); - }); + const rowOneLink = () => getLinks(rows[1])[0]; + const rowTwoLink = () => getLinks(rows[2])[0]; + const rowThreeLink = () => getLinks(rows[3])[0]; - describe('and clicking on links', () => { - it('then correct row data should be in link', () => { - getTestContext(); - - userEvent.click(within(getColumnHeader(/temperature/)).getByText(/temperature/i)); - userEvent.click(within(getColumnHeader(/temperature/)).getByText(/temperature/i)); - - const rows = within(getTable()).getAllByRole('row'); - expect(within(rows[1]).getByRole('cell', { name: '12' }).closest('a')).toHaveAttribute('href', '12'); - expect(within(rows[2]).getByRole('cell', { name: '11' }).closest('a')).toHaveAttribute('href', '11'); - expect(within(rows[3]).getByRole('cell', { name: '10' }).closest('a')).toHaveAttribute('href', '10'); - }); + expect(within(rows[1]).getByText('2021-01-01 02:00:00')).toBeInTheDocument(); + expect(rowOneLink()).toHaveTextContent('12'); + expect(rowOneLink()).toHaveAttribute('href', '12'); + expect(within(rows[2]).getByText('2021-01-01 01:00:00')).toBeInTheDocument(); + expect(rowTwoLink()).toHaveTextContent('11'); + expect(rowTwoLink()).toHaveAttribute('href', '11'); + expect(within(rows[3]).getByText('2021-01-01 00:00:00')).toBeInTheDocument(); + expect(rowThreeLink()).toHaveTextContent('10'); + expect(rowThreeLink()).toHaveAttribute('href', '10'); }); }); }); diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index 654e9fccfbb..1aef90e1b6a 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -185,7 +185,6 @@ export const Table: FC = memo((props: Props) => { onCellFilterAdded={onCellFilterAdded} columnIndex={index} columnCount={row.cells.length} - dataRowIndex={row.index} /> ))} diff --git a/packages/grafana-ui/src/components/Table/TableCell.tsx b/packages/grafana-ui/src/components/Table/TableCell.tsx index 8a45d367668..97bc8a071c3 100644 --- a/packages/grafana-ui/src/components/Table/TableCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableCell.tsx @@ -1,6 +1,6 @@ -import React, { FC, MouseEventHandler } from 'react'; +import React, { FC } from 'react'; import { Cell } from 'react-table'; -import { Field, LinkModel } from '@grafana/data'; +import { Field } from '@grafana/data'; import { TableFilterActionCallback } from './types'; import { TableStyles } from './styles'; @@ -11,19 +11,9 @@ export interface Props { onCellFilterAdded?: TableFilterActionCallback; columnIndex: number; columnCount: number; - /** Index before table sort */ - dataRowIndex: number; } -export const TableCell: FC = ({ - cell, - field, - tableStyles, - onCellFilterAdded, - columnIndex, - columnCount, - dataRowIndex, -}) => { +export const TableCell: FC = ({ cell, field, tableStyles, onCellFilterAdded, columnIndex, columnCount }) => { const cellProps = cell.getCellProps(); if (!field.display) { @@ -42,33 +32,11 @@ export const TableCell: FC = ({ innerWidth -= tableStyles.lastChildExtraPadding; } - const link: LinkModel | undefined = field.getLinks?.({ - valueRowIndex: dataRowIndex, - })[0]; - - let onClick: MouseEventHandler | undefined; - if (link?.onClick) { - onClick = (event) => { - // Allow opening in new tab - if (!(event.ctrlKey || event.metaKey || event.shiftKey) && link!.onClick) { - event.preventDefault(); - link!.onClick(event); - } - }; - } - - const renderedCell = cell.render('Cell', { + return cell.render('Cell', { field, tableStyles, onCellFilterAdded, cellProps, innerWidth, - }); - return link ? ( - - {renderedCell} - - ) : ( - <>{renderedCell} - ); + }) as React.ReactElement; }; diff --git a/packages/grafana-ui/src/components/Table/styles.ts b/packages/grafana-ui/src/components/Table/styles.ts index 43eab0087fb..a1bf46b85f7 100644 --- a/packages/grafana-ui/src/components/Table/styles.ts +++ b/packages/grafana-ui/src/components/Table/styles.ts @@ -103,6 +103,12 @@ export const getTableStyles = (theme: GrafanaTheme2) => { white-space: nowrap; text-decoration: underline; `, + imageCellLink: css` + cursor: pointer; + overflow: hidden; + width: 100%; + height: 100%; + `, headerFilter: css` label: headerFilter; cursor: pointer; diff --git a/packages/grafana-ui/src/utils/index.ts b/packages/grafana-ui/src/utils/index.ts index 98c851c5c14..a4ff32608f6 100644 --- a/packages/grafana-ui/src/utils/index.ts +++ b/packages/grafana-ui/src/utils/index.ts @@ -4,6 +4,7 @@ export * from './slate'; export * from './dataLinks'; export * from './tags'; export * from './scrollbar'; +export * from './table'; export * from './measureText'; export * from './useForceUpdate'; export { SearchFunctionType } from './searchFunctions'; diff --git a/packages/grafana-ui/src/utils/table.ts b/packages/grafana-ui/src/utils/table.ts new file mode 100644 index 00000000000..8668ce7b189 --- /dev/null +++ b/packages/grafana-ui/src/utils/table.ts @@ -0,0 +1,30 @@ +import { Field, LinkModel } from '@grafana/data'; +import { MouseEventHandler } from 'react'; +import { Row } from 'react-table'; + +/** + * @internal + */ +export const getCellLinks = (field: Field, row: Row) => { + let link: LinkModel | undefined; + let onClick: MouseEventHandler | undefined; + if (field.getLinks) { + link = field.getLinks({ + valueRowIndex: row.index, + })[0]; + } + + if (link && link.onClick) { + onClick = (event) => { + // Allow opening in new tab + if (!(event.ctrlKey || event.metaKey || event.shiftKey) && link!.onClick) { + event.preventDefault(); + link!.onClick(event); + } + }; + } + return { + link, + onClick, + }; +};