TableNG: Add support for datalinks (#100769)

Co-authored-by: drew08t <drew08@gmail.com>
pull/102064/head
Adela Almasan 4 months ago committed by GitHub
parent 020ad6c8cb
commit e199d67e66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 63
      devenv/dev-dashboards/panel-table/table_tests_new.json
  2. 77
      packages/grafana-ui/src/components/Table/TableNG/Cells/AutoCell.tsx
  3. 17
      packages/grafana-ui/src/components/Table/TableNG/Cells/BarGaugeCell.tsx
  4. 40
      packages/grafana-ui/src/components/Table/TableNG/Cells/ImageCell.tsx
  5. 34
      packages/grafana-ui/src/components/Table/TableNG/Cells/JSONCell.tsx
  6. 13
      packages/grafana-ui/src/components/Table/TableNG/Cells/TableCellNG.tsx
  7. 7
      packages/grafana-ui/src/components/Table/TableNG/types.ts

@ -18,14 +18,11 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 89,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
@ -33,15 +30,7 @@
"y": 0
},
"id": 7,
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"refId": "A"
}
],
"panels": [],
"title": "Cell styles",
"type": "row"
},
@ -164,7 +153,7 @@
}
]
},
"pluginVersion": "9.5.0-pre",
"pluginVersion": "11.6.0-pre",
"targets": [
{
"datasource": {
@ -286,6 +275,7 @@
},
"id": 2,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
@ -303,7 +293,7 @@
}
]
},
"pluginVersion": "9.5.0-pre",
"pluginVersion": "11.6.0-pre",
"targets": [
{
"datasource": {
@ -376,7 +366,7 @@
{
"matcher": {
"id": "byName",
"options": "rate"
"options": "Trend #A"
},
"properties": [
{
@ -448,7 +438,7 @@
"showRowNums": false,
"sortBy": []
},
"pluginVersion": "9.5.0-pre",
"pluginVersion": "11.6.0-pre",
"targets": [
{
"datasource": {
@ -625,7 +615,7 @@
"showHeader": true,
"showRowNums": false
},
"pluginVersion": "9.5.0-pre",
"pluginVersion": "11.6.0-pre",
"targets": [
{
"datasource": {
@ -683,10 +673,7 @@
"type": "table"
},
{
"datasource": {
"type": "testdata",
"uid": "gdev-testdata"
},
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
@ -694,15 +681,7 @@
"y": 24
},
"id": 9,
"targets": [
{
"datasource": {
"type": "testdata",
"uid": "gdev-testdata"
},
"refId": "A"
}
],
"panels": [],
"title": "Data links",
"type": "row"
},
@ -752,18 +731,19 @@
{
"matcher": {
"id": "byName",
"options": "Time"
"options": "time"
},
"properties": [
{
"id": "custom.align"
"id": "custom.align",
"value": "center"
}
]
},
{
"matcher": {
"id": "byName",
"options": "{name=\"S1\", server=\"A\"}"
"options": "S1 A"
},
"properties": [
{
@ -799,7 +779,7 @@
"showHeader": true,
"showRowNums": false
},
"pluginVersion": "9.5.0-pre",
"pluginVersion": "11.6.0-pre",
"targets": [
{
"alias": "S1",
@ -916,7 +896,7 @@
}
]
},
"pluginVersion": "9.5.0-pre",
"pluginVersion": "11.6.0-pre",
"targets": [
{
"datasource": {
@ -1005,7 +985,7 @@
"showHeader": true,
"showRowNums": false
},
"pluginVersion": "9.5.0-pre",
"pluginVersion": "11.6.0-pre",
"targets": [
{
"datasource": {
@ -1019,9 +999,9 @@
"type": "table"
}
],
"preload": false,
"refresh": "",
"revision": 1,
"schemaVersion": 38,
"schemaVersion": 41,
"tags": [
"gdev",
"panel-tests"
@ -1049,6 +1029,5 @@
"timezone": "",
"title": "Panel Tests - React Table",
"uid": "U_bZIMRMk",
"version": 7,
"weekStart": ""
"version": 17
}

@ -1,23 +1,94 @@
import { css } from '@emotion/css';
import { css, cx } from '@emotion/css';
import { Property } from 'csstype';
import { GrafanaTheme2, formattedValueToString } from '@grafana/data';
import { TableCellDisplayMode, TableCellOptions } from '@grafana/schema';
import { useStyles2 } from '../../../../themes';
import { clearLinkButtonStyles } from '../../../Button';
import { DataLinksContextMenu } from '../../../DataLinks/DataLinksContextMenu';
import { AutoCellProps } from '../types';
import { getCellLinks } from '../utils';
export default function AutoCell({ value, field, justifyContent }: AutoCellProps) {
export default function AutoCell({ value, field, justifyContent, rowIdx, cellOptions }: AutoCellProps) {
const styles = useStyles2(getStyles, justifyContent);
const displayValue = field.display!(value);
const formattedValue = formattedValueToString(displayValue);
const hasLinks = Boolean(getCellLinks(field, rowIdx)?.length);
const clearButtonStyle = useStyles2(clearLinkButtonStyles);
return <div className={styles.cell}>{formattedValue}</div>;
return (
<div className={styles.cell}>
{hasLinks ? (
<DataLinksContextMenu links={() => getCellLinks(field, rowIdx) || []}>
{(api) => {
if (api.openMenu) {
return (
<button
className={cx(clearButtonStyle, getLinkStyle(styles, cellOptions, api.targetClassName))}
onClick={api.openMenu}
>
{formattedValue}
</button>
);
} else {
return <div className={getLinkStyle(styles, cellOptions, api.targetClassName)}>{formattedValue}</div>;
}
}}
</DataLinksContextMenu>
) : (
formattedValue
)}
</div>
);
}
const getLinkStyle = (
styles: ReturnType<typeof getStyles>,
cellOptions: TableCellOptions,
targetClassName: string | undefined
) => {
if (cellOptions.type === TableCellDisplayMode.Auto) {
return cx(styles.linkCell, targetClassName);
}
return cx(styles.cellLinkForColoredCell, targetClassName);
};
const getStyles = (theme: GrafanaTheme2, justifyContent: Property.JustifyContent | undefined) => ({
cell: css({
display: 'flex',
justifyContent: justifyContent,
a: {
color: 'inherit',
},
}),
cellLinkForColoredCell: css({
cursor: 'pointer',
overflow: 'hidden',
textOverflow: 'ellipsis',
userSelect: 'text',
whiteSpace: 'nowrap',
fontWeight: theme.typography.fontWeightMedium,
textDecoration: 'underline',
}),
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,
},
}),
});

@ -6,7 +6,7 @@ import { BarGaugeDisplayMode, BarGaugeValueMode, TableCellDisplayMode } from '@g
import { BarGauge } from '../../../BarGauge/BarGauge';
import { DataLinksContextMenu, DataLinksContextMenuApi } from '../../../DataLinks/DataLinksContextMenu';
import { BarGaugeCellProps } from '../types';
import { extractPixelValue, getCellOptions, getAlignmentFactor } from '../utils';
import { extractPixelValue, getCellOptions, getAlignmentFactor, getCellLinks } from '../utils';
const defaultScale: ThresholdsConfig = {
mode: ThresholdsMode.Absolute,
@ -46,15 +46,7 @@ export const BarGaugeCell = ({ value, field, theme, height, width, rowIdx }: Bar
cellOptions.valueDisplayMode !== undefined ? cellOptions.valueDisplayMode : BarGaugeValueMode.Text;
}
const getLinks = () => {
if (!isFunction(field.getLinks)) {
return [];
}
return field.getLinks({ valueRowIndex: rowIdx });
};
const hasLinks = Boolean(getLinks().length);
const hasLinks = Boolean(getCellLinks(field, rowIdx)?.length);
const alignmentFactors = getAlignmentFactor(field, displayValue, rowIdx!);
@ -85,7 +77,10 @@ export const BarGaugeCell = ({ value, field, theme, height, width, rowIdx }: Bar
return (
<>
{hasLinks ? (
<DataLinksContextMenu links={getLinks} style={{ display: 'flex', width: '100%' }}>
<DataLinksContextMenu
links={() => getCellLinks(field, rowIdx) || []}
style={{ display: 'flex', width: '100%' }}
>
{(api) => renderComponent(api)}
</DataLinksContextMenu>
) : (

@ -1,17 +1,21 @@
import { css } from '@emotion/css';
import { Property } from 'csstype';
import * as React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { TableCellDisplayMode } from '@grafana/schema';
import { useStyles2 } from '../../../../themes';
import { DataLinksContextMenu } from '../../../DataLinks/DataLinksContextMenu';
import { ImageCellProps } from '../types';
import { getCellLinks } from '../utils';
const DATALINKS_HEIGHT_OFFSET = 10;
export const ImageCell = ({ cellOptions, field, height, justifyContent, value }: ImageCellProps) => {
export const ImageCell = ({ cellOptions, field, height, justifyContent, value, rowIdx }: ImageCellProps) => {
const calculatedHeight = height - DATALINKS_HEIGHT_OFFSET;
const styles = useStyles2(getStyles, calculatedHeight, justifyContent);
const hasLinks = Boolean(getCellLinks(field, rowIdx)?.length);
const { text } = field.display!(value);
const { alt, title } =
@ -19,8 +23,38 @@ export const ImageCell = ({ cellOptions, field, height, justifyContent, value }:
const img = <img alt={alt} src={text} className={styles.image} title={title} />;
// TODO: Implement DataLinksContextMenu + actions
return <div className={styles.imageContainer}>{img}</div>;
// TODO: Implement actions
return (
<div className={styles.imageContainer}>
{hasLinks ? (
<DataLinksContextMenu links={() => getCellLinks(field, rowIdx) || []}>
{(api) => {
if (api.openMenu) {
return (
<div
onClick={api.openMenu}
role="button"
tabIndex={0}
onKeyDown={(e: React.KeyboardEvent) => {
if (e.key === 'Enter' && api.openMenu) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions
api.openMenu(e as any);
}
}}
>
{img}
</div>
);
} else {
return img;
}
}}
</DataLinksContextMenu>
) : (
img
)}
</div>
);
};
const getStyles = (theme: GrafanaTheme2, height: number, justifyContent: Property.JustifyContent) => ({

@ -1,13 +1,17 @@
import { css } from '@emotion/css';
import { css, cx } from '@emotion/css';
import { Property } from 'csstype';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '../../../../themes';
import { Button, clearLinkButtonStyles } from '../../../Button';
import { DataLinksContextMenu } from '../../../DataLinks/DataLinksContextMenu';
import { JSONCellProps } from '../types';
import { getCellLinks } from '../utils';
export const JSONCell = ({ value, justifyContent }: JSONCellProps) => {
export const JSONCell = ({ value, justifyContent, field, rowIdx }: JSONCellProps) => {
const styles = useStyles2(getStyles, justifyContent);
const clearButtonStyle = useStyles2(clearLinkButtonStyles);
let displayValue = value;
@ -29,8 +33,30 @@ export const JSONCell = ({ value, justifyContent }: JSONCellProps) => {
}
}
// TODO: Implement DataLinksContextMenu + actions
return <div className={styles.jsonText}>{displayValue}</div>;
const hasLinks = Boolean(getCellLinks(field, rowIdx)?.length);
// TODO: Implement actions
return (
<div className={styles.jsonText}>
{hasLinks ? (
<DataLinksContextMenu links={() => getCellLinks(field, rowIdx) || []}>
{(api) => {
if (api.openMenu) {
return (
<Button className={cx(clearButtonStyle)} onClick={api.openMenu}>
{displayValue}
</Button>
);
} else {
return <>{displayValue}</>;
}
}}
</DataLinksContextMenu>
) : (
displayValue
)}
</div>
);
};
const getStyles = (theme: GrafanaTheme2, justifyContent: Property.JustifyContent) => ({

@ -101,11 +101,12 @@ export function TableCellNG(props: TableCellNGProps) {
height={height}
justifyContent={justifyContent}
value={value}
rowIdx={rowIdx}
/>
);
break;
case TableCellDisplayMode.JSONView:
cell = <JSONCell value={value} justifyContent={justifyContent} />;
cell = <JSONCell value={value} justifyContent={justifyContent} field={field} rowIdx={rowIdx} />;
break;
case TableCellDisplayMode.DataLinks:
cell = <DataLinksCell field={field} rowIdx={rowIdx} />;
@ -115,7 +116,15 @@ export function TableCellNG(props: TableCellNGProps) {
break;
case TableCellDisplayMode.Auto:
default:
cell = <AutoCell value={value} field={field} justifyContent={justifyContent} />;
cell = (
<AutoCell
value={value}
field={field}
justifyContent={justifyContent}
rowIdx={rowIdx}
cellOptions={cellOptions}
/>
);
}
const handleMouseEnter = () => {

@ -180,11 +180,14 @@ export interface ImageCellProps {
height: number;
justifyContent: Property.JustifyContent;
value: TableCellValue;
rowIdx: number;
}
export interface JSONCellProps {
justifyContent: Property.JustifyContent;
value: TableCellValue;
field: Field;
rowIdx: number;
}
export interface DataLinksCellProps {
@ -193,7 +196,7 @@ export interface DataLinksCellProps {
}
export interface ActionCellProps {
actions: ActionModel[];
actions?: ActionModel[];
}
export interface CellColors {
@ -206,6 +209,8 @@ export interface AutoCellProps {
value: TableCellValue;
field: Field;
justifyContent: Property.JustifyContent;
rowIdx: number;
cellOptions: TableCellOptions;
}
// Comparator for sorting table values

Loading…
Cancel
Save