mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Table: Add custom cell rendering option (#70999)
This commit is contained in:
@@ -656,6 +656,7 @@ export enum TableCellDisplayMode {
|
||||
ColorBackground = 'color-background',
|
||||
ColorBackgroundSolid = 'color-background-solid',
|
||||
ColorText = 'color-text',
|
||||
Custom = 'custom',
|
||||
Gauge = 'gauge',
|
||||
GradientGauge = 'gradient-gauge',
|
||||
Image = 'image',
|
||||
@@ -883,6 +884,10 @@ export interface TableFieldOptions {
|
||||
displayMode?: TableCellDisplayMode;
|
||||
filterable?: boolean;
|
||||
hidden?: boolean; // ?? default is missing or false ??
|
||||
/**
|
||||
* Hides any header for a column, usefull for columns that show some static content or buttons.
|
||||
*/
|
||||
hideHeader?: boolean;
|
||||
inspect: boolean;
|
||||
minWidth?: number;
|
||||
width?: number;
|
||||
|
||||
@@ -4,7 +4,7 @@ package common
|
||||
// in the table such as colored text, JSON, gauge, etc.
|
||||
// The color-background-solid, gradient-gauge, and lcd-gauge
|
||||
// modes are deprecated in favor of new cell subOptions
|
||||
TableCellDisplayMode: "auto" | "color-text" | "color-background" | "color-background-solid" | "gradient-gauge" | "lcd-gauge" | "json-view" | "basic" | "image" | "gauge" | "sparkline" @cuetsy(kind="enum",memberNames="Auto|ColorText|ColorBackground|ColorBackgroundSolid|GradientGauge|LcdGauge|JSONView|BasicGauge|Image|Gauge|Sparkline")
|
||||
TableCellDisplayMode: "auto" | "color-text" | "color-background" | "color-background-solid" | "gradient-gauge" | "lcd-gauge" | "json-view" | "basic" | "image" | "gauge" | "sparkline"| "custom" @cuetsy(kind="enum",memberNames="Auto|ColorText|ColorBackground|ColorBackgroundSolid|GradientGauge|LcdGauge|JSONView|BasicGauge|Image|Gauge|Sparkline|Custom")
|
||||
|
||||
// Display mode to the "Colored Background" display
|
||||
// mode for table cells. Either displays a solid color (basic mode)
|
||||
@@ -86,5 +86,7 @@ TableFieldOptions: {
|
||||
hidden?: bool // ?? default is missing or false ??
|
||||
inspect: bool | *false
|
||||
filterable?: bool
|
||||
// Hides any header for a column, usefull for columns that show some static content or buttons.
|
||||
hideHeader?: bool
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ import {
|
||||
Field,
|
||||
DisplayValue,
|
||||
} from '@grafana/data';
|
||||
import { BarGaugeDisplayMode, BarGaugeValueMode } from '@grafana/schema';
|
||||
import { BarGaugeDisplayMode, BarGaugeValueMode, TableCellDisplayMode } from '@grafana/schema';
|
||||
|
||||
import { BarGauge } from '../BarGauge/BarGauge';
|
||||
import { DataLinksContextMenu, DataLinksContextMenuApi } from '../DataLinks/DataLinksContextMenu';
|
||||
|
||||
import { TableCellProps, TableCellDisplayMode } from './types';
|
||||
import { TableCellProps } from './types';
|
||||
import { getCellOptions } from './utils';
|
||||
|
||||
const defaultScale: ThresholdsConfig = {
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { ReactElement } from 'react';
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
import { DisplayValue, formattedValueToString } from '@grafana/data';
|
||||
import { TableCellBackgroundDisplayMode, TableCellOptions } from '@grafana/schema';
|
||||
import { TableCellBackgroundDisplayMode, TableCellDisplayMode } from '@grafana/schema';
|
||||
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { getCellLinks, getTextColorForAlphaBackground } from '../../utils';
|
||||
@@ -12,28 +12,33 @@ import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu';
|
||||
|
||||
import { CellActions } from './CellActions';
|
||||
import { TableStyles } from './styles';
|
||||
import { TableCellDisplayMode, TableCellProps, TableFieldOptions } from './types';
|
||||
import { TableCellProps, TableFieldOptions, CustomCellRendererProps, TableCellOptions } from './types';
|
||||
import { getCellOptions } from './utils';
|
||||
|
||||
export const DefaultCell = (props: TableCellProps) => {
|
||||
const { field, cell, tableStyles, row, cellProps } = props;
|
||||
const { field, cell, tableStyles, row, cellProps, frame } = props;
|
||||
|
||||
const inspectEnabled = Boolean((field.config.custom as TableFieldOptions)?.inspect);
|
||||
const displayValue = field.display!(cell.value);
|
||||
|
||||
let value: string | ReactElement;
|
||||
if (React.isValidElement(cell.value)) {
|
||||
value = cell.value;
|
||||
} else {
|
||||
value = formattedValueToString(displayValue);
|
||||
}
|
||||
|
||||
const showFilters = props.onCellFilterAdded && field.config.filterable;
|
||||
const showActions = (showFilters && cell.value !== undefined) || inspectEnabled;
|
||||
const cellOptions = getCellOptions(field);
|
||||
const cellStyle = getCellStyle(tableStyles, cellOptions, displayValue, inspectEnabled);
|
||||
const hasLinks = Boolean(getCellLinks(field, row)?.length);
|
||||
const clearButtonStyle = useStyles2(clearLinkButtonStyles);
|
||||
let value: string | ReactElement;
|
||||
|
||||
if (cellOptions.type === TableCellDisplayMode.Custom) {
|
||||
const CustomCellComponent: React.ComponentType<CustomCellRendererProps> = cellOptions.cellComponent;
|
||||
value = <CustomCellComponent field={field} value={cell.value} rowIndex={row.index} frame={frame} />;
|
||||
} else {
|
||||
if (React.isValidElement(cell.value)) {
|
||||
value = cell.value;
|
||||
} else {
|
||||
value = formattedValueToString(displayValue);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...cellProps} className={cellStyle}>
|
||||
|
||||
@@ -260,12 +260,13 @@ export const Table = memo((props: Props) => {
|
||||
columnIndex={index}
|
||||
columnCount={row.cells.length}
|
||||
timeRange={timeRange}
|
||||
frame={data}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[onCellFilterAdded, page, enablePagination, prepareRow, rows, tableStyles, renderSubTable, timeRange]
|
||||
[onCellFilterAdded, page, enablePagination, prepareRow, rows, tableStyles, renderSubTable, timeRange, data]
|
||||
);
|
||||
|
||||
const onNavigate = useCallback(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Cell } from 'react-table';
|
||||
|
||||
import { TimeRange } from '@grafana/data';
|
||||
import { TimeRange, DataFrame } from '@grafana/data';
|
||||
|
||||
import { TableStyles } from './styles';
|
||||
import { GrafanaTableColumn, TableFilterActionCallback } from './types';
|
||||
@@ -14,9 +14,10 @@ export interface Props {
|
||||
columnCount: number;
|
||||
timeRange?: TimeRange;
|
||||
userProps?: object;
|
||||
frame: DataFrame;
|
||||
}
|
||||
|
||||
export const TableCell = ({ cell, tableStyles, onCellFilterAdded, timeRange, userProps }: Props) => {
|
||||
export const TableCell = ({ cell, tableStyles, onCellFilterAdded, timeRange, userProps, frame }: Props) => {
|
||||
const cellProps = cell.getCellProps();
|
||||
const field = (cell.column as unknown as GrafanaTableColumn).field;
|
||||
|
||||
@@ -39,5 +40,6 @@ export const TableCell = ({ cell, tableStyles, onCellFilterAdded, timeRange, use
|
||||
innerWidth,
|
||||
timeRange,
|
||||
userProps,
|
||||
frame,
|
||||
}) as React.ReactElement;
|
||||
};
|
||||
|
||||
@@ -3,16 +3,11 @@ import { FC } from 'react';
|
||||
import { CellProps, Column, Row, TableState, UseExpandedRowProps } from 'react-table';
|
||||
|
||||
import { DataFrame, Field, KeyValue, SelectableValue, TimeRange } from '@grafana/data';
|
||||
import { TableCellHeight } from '@grafana/schema';
|
||||
import * as schema from '@grafana/schema';
|
||||
|
||||
import { TableStyles } from './styles';
|
||||
|
||||
export {
|
||||
type TableFieldOptions,
|
||||
TableCellDisplayMode,
|
||||
type FieldTextAlignment,
|
||||
TableCellBackgroundDisplayMode,
|
||||
} from '@grafana/schema';
|
||||
export { type FieldTextAlignment, TableCellBackgroundDisplayMode, TableCellDisplayMode } from '@grafana/schema';
|
||||
|
||||
export interface TableRow {
|
||||
[x: string]: any;
|
||||
@@ -37,6 +32,7 @@ export interface TableCellProps extends CellProps<any> {
|
||||
field: Field;
|
||||
onCellFilterAdded?: TableFilterActionCallback;
|
||||
innerWidth: number;
|
||||
frame: DataFrame;
|
||||
}
|
||||
|
||||
export type CellComponent = FC<TableCellProps>;
|
||||
@@ -84,9 +80,38 @@ export interface Props {
|
||||
footerOptions?: TableFooterCalc;
|
||||
footerValues?: FooterItem[];
|
||||
enablePagination?: boolean;
|
||||
cellHeight?: TableCellHeight;
|
||||
cellHeight?: schema.TableCellHeight;
|
||||
/** @alpha */
|
||||
subData?: DataFrame[];
|
||||
/** @alpha Used by SparklineCell when provided */
|
||||
timeRange?: TimeRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* Props that will be passed to the TableCustomCellOptions.cellComponent when rendered.
|
||||
*/
|
||||
export interface CustomCellRendererProps {
|
||||
field: Field;
|
||||
rowIndex: number;
|
||||
frame: DataFrame;
|
||||
// Would be great to have generic type for this but that would need having a generic DataFrame type where the field
|
||||
// types could be propagated here.
|
||||
value: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* Can be used to define completely custom cell contents by providing a custom cellComponent.
|
||||
*/
|
||||
export interface TableCustomCellOptions {
|
||||
cellComponent: FC<CustomCellRendererProps>;
|
||||
type: schema.TableCellDisplayMode.Custom;
|
||||
}
|
||||
|
||||
// As cue/schema cannot define function types (as main point of schema is to be serializable) we have to extend the
|
||||
// types here with the dynamic API. This means right now this is not usable as a table panel option for example.
|
||||
export type TableCellOptions = schema.TableCellOptions | TableCustomCellOptions;
|
||||
export type TableFieldOptions = Omit<schema.TableFieldOptions, 'cellOptions'> & {
|
||||
cellOptions: TableCellOptions;
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
BarGaugeDisplayMode,
|
||||
TableAutoCellOptions,
|
||||
TableCellBackgroundDisplayMode,
|
||||
TableCellOptions,
|
||||
TableCellDisplayMode,
|
||||
} from '@grafana/schema';
|
||||
|
||||
import { BarGaugeCell } from './BarGaugeCell';
|
||||
@@ -34,7 +34,7 @@ import { RowExpander } from './RowExpander';
|
||||
import { SparklineCell } from './SparklineCell';
|
||||
import {
|
||||
CellComponent,
|
||||
TableCellDisplayMode,
|
||||
TableCellOptions,
|
||||
TableFieldOptions,
|
||||
FooterItem,
|
||||
GrafanaTableColumn,
|
||||
@@ -130,7 +130,7 @@ export function getColumns(
|
||||
Cell,
|
||||
id: fieldIndex.toString(),
|
||||
field: field,
|
||||
Header: getFieldDisplayName(field, data),
|
||||
Header: fieldTableOptions.hideHeader ? '' : getFieldDisplayName(field, data),
|
||||
accessor: (_row: any, i: number) => {
|
||||
return field.values[i];
|
||||
},
|
||||
@@ -169,6 +169,7 @@ export function getColumns(
|
||||
|
||||
export function getCellComponent(displayMode: TableCellDisplayMode, field: Field): CellComponent {
|
||||
switch (displayMode) {
|
||||
case TableCellDisplayMode.Custom:
|
||||
case TableCellDisplayMode.ColorText:
|
||||
case TableCellDisplayMode.ColorBackground:
|
||||
return DefaultCell;
|
||||
|
||||
@@ -83,7 +83,9 @@ export { SetInterval } from './SetInterval/SetInterval';
|
||||
|
||||
export { Table } from './Table/Table';
|
||||
export {
|
||||
TableCellDisplayMode,
|
||||
type TableCustomCellOptions,
|
||||
type CustomCellRendererProps,
|
||||
type TableFieldOptions,
|
||||
type TableSortByFieldState,
|
||||
type TableFooterCalc,
|
||||
type AdHocFilterItem,
|
||||
|
||||
@@ -34,7 +34,6 @@ export {
|
||||
LegendDisplayMode,
|
||||
type VizLegendOptions,
|
||||
type OptionsWithLegend,
|
||||
type TableFieldOptions,
|
||||
TableCellDisplayMode,
|
||||
type FieldTextAlignment,
|
||||
type VizTextDisplayOptions,
|
||||
|
||||
@@ -147,13 +147,23 @@ export const SearchResultsTable = React.memo(
|
||||
columnIndex={index}
|
||||
columnCount={row.cells.length}
|
||||
userProps={{ href: url, onClick: onClickItem }}
|
||||
frame={response.view.dataFrame}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[rows, prepareRow, response.view.fields.url?.values, highlightIndex, styles, tableStyles, onClickItem]
|
||||
[
|
||||
rows,
|
||||
prepareRow,
|
||||
response.view.fields.url?.values,
|
||||
highlightIndex,
|
||||
styles,
|
||||
tableStyles,
|
||||
onClickItem,
|
||||
response.view.dataFrame,
|
||||
]
|
||||
);
|
||||
|
||||
if (!rows.length) {
|
||||
|
||||
Reference in New Issue
Block a user