diff --git a/.betterer.results b/.betterer.results index b8420d1ceeb..dadf1c314c2 100644 --- a/.betterer.results +++ b/.betterer.results @@ -639,9 +639,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], - "packages/grafana-ui/src/components/Table/Cells/DataLinksCell.tsx:5381": [ - [0, 0, 0, "There should be no empty line within import group", "0"] - ], "packages/grafana-ui/src/components/Table/Cells/TableCell.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], diff --git a/packages/grafana-ui/src/components/Table/Cells/DataLinksCell.tsx b/packages/grafana-ui/src/components/Table/Cells/DataLinksCell.tsx index c4c325682f4..3e95d3946b4 100644 --- a/packages/grafana-ui/src/components/Table/Cells/DataLinksCell.tsx +++ b/packages/grafana-ui/src/components/Table/Cells/DataLinksCell.tsx @@ -1,5 +1,4 @@ import { getCellLinks } from '../../../utils'; - import { TableCellProps } from '../types'; export const DataLinksCell = (props: TableCellProps) => { diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/DataLinksCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/DataLinksCell.tsx new file mode 100644 index 00000000000..15092b85a19 --- /dev/null +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/DataLinksCell.tsx @@ -0,0 +1,50 @@ +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { useStyles2 } from '../../../../themes'; +import { CellNGProps } from '../types'; +import { getCellLinks } from '../utils'; + +export const DataLinksCell = (props: CellNGProps) => { + const { field, rowIdx } = props; + const styles = useStyles2(getStyles); + + const links = getCellLinks(field, rowIdx!); + + return ( +
+ {links && + links.map((link, idx) => { + return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions + + + {link.title} + + + ); + })} +
+ ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + linkCell: css({ + cursor: 'pointer', + overflow: 'hidden', + textOverflow: 'ellipsis', + userSelect: 'text', + whiteSpace: 'nowrap', + color: theme.colors.text.link, + fontWeight: theme.typography.fontWeightMedium, + paddingRight: theme.spacing(1.5), + a: { + color: theme.colors.text.link, + }, + '&:hover': { + textDecoration: 'underline', + color: theme.colors.text.link, + }, + }), +}); diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/TableCellNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/TableCellNG.tsx index 69b7b9a163b..0aba7fa1ee0 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/TableCellNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/TableCellNG.tsx @@ -13,6 +13,7 @@ import { getCellColors } from '../utils'; import AutoCell from './AutoCell'; import { BarGaugeCell } from './BarGaugeCell'; +import { DataLinksCell } from './DataLinksCell'; import { ImageCell } from './ImageCell'; import { JSONCell } from './JSONCell'; import { SparklineCell } from './SparklineCell'; @@ -22,6 +23,7 @@ import { SparklineCell } from './SparklineCell'; // fieldDisplay: any? // } +// eslint-disable-next-line @typescript-eslint/consistent-type-assertions export function TableCellNG(props: any) { const { field, @@ -117,6 +119,9 @@ export function TableCellNG(props: any) { case TableCellDisplayMode.JSONView: cell = ; break; + case TableCellDisplayMode.DataLinks: + cell = ; + break; case TableCellDisplayMode.Auto: default: cell = ( diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index 0f1ad1e8207..33f062b88bf 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -51,6 +51,13 @@ export type FilterType = { }; }; +/** + * getIsNestedTable is a helper function that takes a DataFrame and returns a + * boolean value based on the presence of nested frames + */ +const getIsNestedTable = (dataFrame: DataFrame): boolean => + dataFrame.fields.some(({ type }) => type === FieldType.nestedFrames); + export function TableNG(props: TableNGProps) { const { height, @@ -158,6 +165,7 @@ export function TableNG(props: TableNGProps) { // setSortColumns is still used to trigger re-render const sortColumnsRef = useRef(sortColumns); const [expandedRows, setExpandedRows] = useState([]); + const [isNestedTable, setIsNestedTable] = useState(false); function getDefaultRowHeight(): number { const bodyFontSize = theme.typography.fontSize; @@ -230,12 +238,10 @@ export function TableNG(props: TableNGProps) { const mapFrameToDataGrid = (main: DataFrame, calcsRef: React.MutableRefObject, subTable?: boolean) => { const columns: TableColumn[] = []; - // Check for nestedFrames - const nestedDataField = main.fields.find((f) => f.type === FieldType.nestedFrames); - const hasNestedData = nestedDataField !== undefined; + const hasNestedFrames = getIsNestedTable(main); // If nested frames, add expansion control column - if (hasNestedData) { + if (hasNestedFrames) { const expanderField: Field = { name: '', type: FieldType.other, @@ -546,9 +552,14 @@ export function TableNG(props: TableNGProps) { const columns = useMemo( () => mapFrameToDataGrid(props.data, calcsRef), - [props.data, calcsRef, filter, expandedRows, footerOptions] // eslint-disable-line react-hooks/exhaustive-deps + [props.data, calcsRef, filter, expandedRows, expandedRows.length, footerOptions] // eslint-disable-line react-hooks/exhaustive-deps ); + useEffect(() => { + const hasNestedFrames = getIsNestedTable(props.data); + setIsNestedTable(hasNestedFrames); + }, [props.data]); + // This effect needed to set header cells refs before row height calculation useLayoutEffect(() => { setReadyForRowHeightCalc(Object.keys(headerCellRefs.current).length > 0); @@ -603,7 +614,7 @@ export function TableNG(props: TableNGProps) { sortable: true, resizable: true, }} - rowHeight={textWrap ? calculateRowHeight : defaultRowHeight} + rowHeight={textWrap || isNestedTable ? calculateRowHeight : defaultRowHeight} // TODO: This doesn't follow current table behavior style={{ width, height: height - (enablePagination ? paginationHeight : 0) }} renderers={{ renderRow: myRowRenderer }} diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts index c8aeb2b7567..16d1f06ca55 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts @@ -216,23 +216,14 @@ export function getCellColors( return { textColor, bgColor, bgHoverColor }; } -export const getLinks = (field: Field, rowIdx: number) => { - if (field.getLinks) { - return field.getLinks({ valueRowIndex: rowIdx }); - } - - return []; -}; - /** * @internal - * TODO: unify with the existing getCellLinks */ -export const getCellLinks = (field: Field, rowIndex: number) => { +export const getCellLinks = (field: Field, rowIdx: number) => { let links: Array> | undefined; if (field.getLinks) { links = field.getLinks({ - valueRowIndex: rowIndex, + valueRowIndex: rowIdx, }); } @@ -250,7 +241,7 @@ export const getCellLinks = (field: Field, rowIndex: number) => { event.preventDefault(); origOnClick!(event, { field, - rowIndex: rowIndex, + rowIndex: rowIdx, }); } };