2025-08-08 07:53:52 +08:00
|
|
|
import { clsx } from 'clsx';
|
2025-08-21 05:12:32 +08:00
|
|
|
import { memo, MemoExoticComponent } from 'react';
|
2025-06-30 20:18:23 +08:00
|
|
|
|
2025-08-08 07:53:52 +08:00
|
|
|
import { Field, FieldType, GrafanaTheme2, isDataFrame, isTimeSeriesFrame } from '@grafana/data';
|
2025-06-30 20:18:23 +08:00
|
|
|
|
|
|
|
import { TableCellDisplayMode, TableCellOptions, TableCustomCellOptions } from '../../types';
|
2025-08-21 05:12:32 +08:00
|
|
|
import { TableCellRenderer, TableCellRendererProps, TableCellStyleOptions, TableCellStyles } from '../types';
|
2025-08-18 22:11:43 +08:00
|
|
|
import { getCellOptions } from '../utils';
|
2025-06-30 20:18:23 +08:00
|
|
|
|
2025-08-08 07:53:52 +08:00
|
|
|
import { ActionsCell, getStyles as getActionsCellStyles } from './ActionsCell';
|
|
|
|
import { AutoCell, getStyles as getAutoCellStyles, getJsonCellStyles } from './AutoCell';
|
2025-06-30 20:18:23 +08:00
|
|
|
import { BarGaugeCell } from './BarGaugeCell';
|
2025-08-08 07:53:52 +08:00
|
|
|
import { DataLinksCell, getStyles as getDataLinksStyles } from './DataLinksCell';
|
|
|
|
import { GeoCell, getStyles as getGeoCellStyles } from './GeoCell';
|
|
|
|
import { ImageCell, getStyles as getImageStyles } from './ImageCell';
|
|
|
|
import { MarkdownCell, getStyles as getMarkdownCellStyles } from './MarkdownCell';
|
|
|
|
import { PillCell, getStyles as getPillStyles } from './PillCell';
|
|
|
|
import { SparklineCell, getStyles as getSparklineCellStyles } from './SparklineCell';
|
2025-06-30 20:18:23 +08:00
|
|
|
|
2025-08-28 04:37:02 +08:00
|
|
|
export const AutoCellRenderer = memo((props: TableCellRendererProps) => (
|
2025-07-16 03:42:06 +08:00
|
|
|
<AutoCell value={props.value} field={props.field} rowIdx={props.rowIdx} />
|
2025-08-21 05:12:32 +08:00
|
|
|
));
|
2025-08-21 18:59:41 +08:00
|
|
|
AutoCellRenderer.displayName = 'AutoCellRenderer';
|
2025-07-08 23:56:39 +08:00
|
|
|
|
2025-06-30 20:18:23 +08:00
|
|
|
function isCustomCellOptions(options: TableCellOptions): options is TableCustomCellOptions {
|
|
|
|
return options.type === TableCellDisplayMode.Custom;
|
|
|
|
}
|
|
|
|
|
2025-08-08 07:53:52 +08:00
|
|
|
function mixinAutoCellStyles(fn: TableCellStyles): TableCellStyles {
|
|
|
|
return (theme, options) => {
|
|
|
|
const styles = fn(theme, options);
|
|
|
|
return clsx(styles, getAutoCellStyles(theme, options));
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2025-08-21 05:12:32 +08:00
|
|
|
interface CellRegistryEntry {
|
|
|
|
renderer: MemoExoticComponent<TableCellRenderer>;
|
|
|
|
getStyles?: TableCellStyles;
|
|
|
|
testField?: (field: Field) => boolean;
|
|
|
|
}
|
2025-06-30 20:18:23 +08:00
|
|
|
|
2025-08-21 05:12:32 +08:00
|
|
|
const CELL_REGISTRY: Record<TableCellOptions['type'], CellRegistryEntry> = {
|
2025-08-08 07:53:52 +08:00
|
|
|
[TableCellDisplayMode.Auto]: {
|
2025-08-21 05:12:32 +08:00
|
|
|
renderer: AutoCellRenderer,
|
2025-08-08 07:53:52 +08:00
|
|
|
getStyles: getAutoCellStyles,
|
|
|
|
},
|
|
|
|
[TableCellDisplayMode.ColorBackground]: {
|
2025-08-21 05:12:32 +08:00
|
|
|
renderer: AutoCellRenderer,
|
2025-08-08 07:53:52 +08:00
|
|
|
getStyles: getAutoCellStyles,
|
|
|
|
},
|
|
|
|
[TableCellDisplayMode.ColorText]: {
|
2025-08-21 05:12:32 +08:00
|
|
|
renderer: AutoCellRenderer,
|
2025-08-08 07:53:52 +08:00
|
|
|
getStyles: getAutoCellStyles,
|
|
|
|
},
|
2025-08-21 05:12:32 +08:00
|
|
|
[TableCellDisplayMode.JSONView]: {
|
|
|
|
renderer: AutoCellRenderer,
|
|
|
|
getStyles: mixinAutoCellStyles(getJsonCellStyles),
|
|
|
|
},
|
|
|
|
[TableCellDisplayMode.Actions]: {
|
2025-08-21 18:59:41 +08:00
|
|
|
// eslint-disable-next-line react/display-name
|
2025-08-21 05:12:32 +08:00
|
|
|
renderer: memo((props: TableCellRendererProps) => (
|
|
|
|
<ActionsCell field={props.field} rowIdx={props.rowIdx} getActions={props.getActions ?? (() => [])} />
|
|
|
|
)),
|
|
|
|
getStyles: getActionsCellStyles,
|
2025-08-08 07:53:52 +08:00
|
|
|
},
|
|
|
|
[TableCellDisplayMode.DataLinks]: {
|
2025-08-21 18:59:41 +08:00
|
|
|
// eslint-disable-next-line react/display-name
|
2025-08-21 05:12:32 +08:00
|
|
|
renderer: memo((props: TableCellRendererProps) => <DataLinksCell field={props.field} rowIdx={props.rowIdx} />),
|
2025-08-08 07:53:52 +08:00
|
|
|
getStyles: getDataLinksStyles,
|
|
|
|
},
|
|
|
|
[TableCellDisplayMode.Gauge]: {
|
2025-08-21 18:59:41 +08:00
|
|
|
// eslint-disable-next-line react/display-name
|
2025-08-21 05:12:32 +08:00
|
|
|
renderer: memo((props: TableCellRendererProps) => (
|
|
|
|
<BarGaugeCell
|
|
|
|
field={props.field}
|
|
|
|
value={props.value}
|
|
|
|
theme={props.theme}
|
|
|
|
height={props.height}
|
|
|
|
width={props.width}
|
|
|
|
rowIdx={props.rowIdx}
|
|
|
|
/>
|
|
|
|
)),
|
|
|
|
},
|
|
|
|
[TableCellDisplayMode.Sparkline]: {
|
2025-08-21 18:59:41 +08:00
|
|
|
// eslint-disable-next-line react/display-name
|
2025-08-21 05:12:32 +08:00
|
|
|
renderer: memo((props: TableCellRendererProps) => (
|
|
|
|
<SparklineCell
|
|
|
|
value={props.value}
|
|
|
|
field={props.field}
|
|
|
|
timeRange={props.timeRange}
|
|
|
|
rowIdx={props.rowIdx}
|
|
|
|
theme={props.theme}
|
|
|
|
width={props.width}
|
|
|
|
/>
|
|
|
|
)),
|
|
|
|
getStyles: getSparklineCellStyles,
|
2025-08-08 07:53:52 +08:00
|
|
|
},
|
|
|
|
[TableCellDisplayMode.Geo]: {
|
2025-08-21 18:59:41 +08:00
|
|
|
// eslint-disable-next-line react/display-name
|
2025-08-21 05:12:32 +08:00
|
|
|
renderer: memo((props: TableCellRendererProps) => <GeoCell value={props.value} height={props.height} />),
|
2025-08-08 07:53:52 +08:00
|
|
|
getStyles: getGeoCellStyles,
|
|
|
|
},
|
|
|
|
[TableCellDisplayMode.Image]: {
|
2025-08-21 18:59:41 +08:00
|
|
|
// eslint-disable-next-line react/display-name
|
2025-08-21 05:12:32 +08:00
|
|
|
renderer: memo((props: TableCellRendererProps) => (
|
|
|
|
<ImageCell cellOptions={props.cellOptions} field={props.field} value={props.value} rowIdx={props.rowIdx} />
|
|
|
|
)),
|
2025-08-08 07:53:52 +08:00
|
|
|
getStyles: getImageStyles,
|
|
|
|
},
|
|
|
|
[TableCellDisplayMode.Pill]: {
|
2025-08-21 18:59:41 +08:00
|
|
|
// eslint-disable-next-line react/display-name
|
2025-08-21 05:12:32 +08:00
|
|
|
renderer: memo((props: TableCellRendererProps) => (
|
2025-08-26 03:47:11 +08:00
|
|
|
<PillCell
|
|
|
|
rowIdx={props.rowIdx}
|
|
|
|
field={props.field}
|
|
|
|
theme={props.theme}
|
|
|
|
getTextColorForBackground={props.getTextColorForBackground}
|
|
|
|
/>
|
2025-08-21 05:12:32 +08:00
|
|
|
)),
|
2025-08-08 07:53:52 +08:00
|
|
|
getStyles: getPillStyles,
|
2025-10-07 23:21:56 +08:00
|
|
|
testField: (field: Field) =>
|
|
|
|
field.type === FieldType.string ||
|
|
|
|
(field.type === FieldType.other && field.values.some((val) => Array.isArray(val))),
|
2025-08-08 07:53:52 +08:00
|
|
|
},
|
|
|
|
[TableCellDisplayMode.Markdown]: {
|
2025-08-21 18:59:41 +08:00
|
|
|
// eslint-disable-next-line react/display-name
|
2025-08-21 05:12:32 +08:00
|
|
|
renderer: memo((props: TableCellRendererProps) => (
|
|
|
|
<MarkdownCell field={props.field} rowIdx={props.rowIdx} disableSanitizeHtml={props.disableSanitizeHtml} />
|
|
|
|
)),
|
2025-08-08 07:53:52 +08:00
|
|
|
getStyles: getMarkdownCellStyles,
|
2025-08-21 05:12:32 +08:00
|
|
|
testField: (field: Field) => field.type === FieldType.string,
|
|
|
|
},
|
|
|
|
[TableCellDisplayMode.Custom]: {
|
2025-08-21 18:59:41 +08:00
|
|
|
// eslint-disable-next-line react/display-name
|
2025-08-21 05:12:32 +08:00
|
|
|
renderer: memo((props: TableCellRendererProps) => {
|
|
|
|
if (!isCustomCellOptions(props.cellOptions) || !props.cellOptions.cellComponent) {
|
|
|
|
return null; // nonsensical case, but better to typeguard it than throw.
|
|
|
|
}
|
|
|
|
const CustomCellComponent = props.cellOptions.cellComponent;
|
|
|
|
return (
|
|
|
|
<CustomCellComponent field={props.field} rowIndex={props.rowIdx} frame={props.frame} value={props.value} />
|
|
|
|
);
|
|
|
|
}),
|
2025-08-08 07:53:52 +08:00
|
|
|
},
|
2025-06-30 20:18:23 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/** @internal */
|
2025-08-18 22:11:43 +08:00
|
|
|
export function getCellRenderer(
|
|
|
|
field: Field,
|
|
|
|
cellOptions: TableCellOptions = getCellOptions(field)
|
|
|
|
): TableCellRenderer {
|
2025-06-30 20:18:23 +08:00
|
|
|
const cellType = cellOptions?.type ?? TableCellDisplayMode.Auto;
|
|
|
|
if (cellType === TableCellDisplayMode.Auto) {
|
2025-08-21 05:12:32 +08:00
|
|
|
return CELL_REGISTRY[getAutoRendererDisplayMode(field)].renderer;
|
2025-06-30 20:18:23 +08:00
|
|
|
}
|
2025-07-12 10:28:06 +08:00
|
|
|
|
2025-08-21 05:12:32 +08:00
|
|
|
// if the field fails the test for a specific renderer, fallback to Auto
|
|
|
|
if (CELL_REGISTRY[cellType]?.testField && CELL_REGISTRY[cellType].testField(field) !== true) {
|
|
|
|
return AutoCellRenderer;
|
2025-07-12 10:28:06 +08:00
|
|
|
}
|
|
|
|
|
2025-08-08 07:53:52 +08:00
|
|
|
// cautious fallback to Auto renderer in case some garbage cell type has been provided.
|
2025-08-21 05:12:32 +08:00
|
|
|
return CELL_REGISTRY[cellType]?.renderer ?? AutoCellRenderer;
|
2025-08-08 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @internal */
|
|
|
|
export function getCellSpecificStyles(
|
|
|
|
cellType: TableCellOptions['type'],
|
|
|
|
field: Field,
|
|
|
|
theme: GrafanaTheme2,
|
|
|
|
options: TableCellStyleOptions
|
|
|
|
): string | undefined {
|
|
|
|
if (cellType === TableCellDisplayMode.Auto) {
|
|
|
|
return getAutoRendererStyles(theme, options, field);
|
|
|
|
}
|
2025-08-21 05:12:32 +08:00
|
|
|
return CELL_REGISTRY[cellType]?.getStyles?.(theme, options);
|
2025-08-08 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @internal */
|
|
|
|
export function getAutoRendererStyles(
|
|
|
|
theme: GrafanaTheme2,
|
|
|
|
options: TableCellStyleOptions,
|
|
|
|
field: Field
|
|
|
|
): string | undefined {
|
|
|
|
const impliedDisplayMode = getAutoRendererDisplayMode(field);
|
|
|
|
if (impliedDisplayMode !== TableCellDisplayMode.Auto) {
|
2025-08-21 05:12:32 +08:00
|
|
|
return CELL_REGISTRY[impliedDisplayMode]?.getStyles?.(theme, options);
|
2025-08-08 07:53:52 +08:00
|
|
|
}
|
|
|
|
return getAutoCellStyles(theme, options);
|
2025-06-30 20:18:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @internal */
|
2025-08-08 07:53:52 +08:00
|
|
|
export function getAutoRendererDisplayMode(field: Field): TableCellOptions['type'] {
|
2025-06-30 20:18:23 +08:00
|
|
|
if (field.type === FieldType.geo) {
|
2025-08-08 07:53:52 +08:00
|
|
|
return TableCellDisplayMode.Geo;
|
2025-06-30 20:18:23 +08:00
|
|
|
}
|
|
|
|
if (field.type === FieldType.frame) {
|
|
|
|
const firstValue = field.values[0];
|
|
|
|
if (isDataFrame(firstValue) && isTimeSeriesFrame(firstValue)) {
|
2025-08-08 07:53:52 +08:00
|
|
|
return TableCellDisplayMode.Sparkline;
|
2025-06-30 20:18:23 +08:00
|
|
|
}
|
|
|
|
}
|
2025-08-08 07:53:52 +08:00
|
|
|
return TableCellDisplayMode.Auto;
|
2025-06-30 20:18:23 +08:00
|
|
|
}
|