Commit 30eef761 by Peter Holmberg Committed by GitHub

Chore: Add react-table typings to Table (#21418)

* add typings

* introduce tyings and refactor accordingly

* extract setting celltype

* update tests to reflect changes

* removing unused things

* renaming getCellType -> getCellDisplayType

* fix width type error

* remove caret

* move cell back to utils, fix story

* remove unused import

* rename type
parent b6c75b10
......@@ -30,6 +30,7 @@
"@torkelo/react-select": "2.1.1",
"@types/react-color": "2.17.0",
"@types/react-select": "2.0.15",
"@types/react-table": "7.0.2",
"@types/slate": "0.47.1",
"@types/slate-react": "0.22.5",
"bizcharts": "^3.5.5",
......
import React, { CSSProperties, FC } from 'react';
import { TableCellProps } from './types';
import tinycolor from 'tinycolor2';
import { formattedValueToString } from '@grafana/data';
export const BackgroundColoredCell: FC<TableCellProps> = props => {
const { cell, tableStyles, field } = props;
if (!field.display) {
return null;
}
const themeFactor = tableStyles.theme.isDark ? 1 : -0.7;
const displayValue = field.display(cell.value);
const bgColor2 = tinycolor(displayValue.color)
.darken(10 * themeFactor)
.spin(5)
.toRgbString();
const styles: CSSProperties = {
background: `linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`,
borderRadius: '0px',
color: 'white',
height: tableStyles.cellHeight,
padding: tableStyles.cellPadding,
};
return <div style={styles}>{formattedValueToString(displayValue)}</div>;
};
import React, { FC } from 'react';
import { ReactTableCellProps, TableCellDisplayMode } from './types';
import { BarGauge, BarGaugeDisplayMode } from '../BarGauge/BarGauge';
import { ThresholdsConfig, ThresholdsMode, VizOrientation } from '@grafana/data';
import { BarGauge, BarGaugeDisplayMode } from '../BarGauge/BarGauge';
import { TableCellProps, TableCellDisplayMode } from './types';
const defaultScale: ThresholdsConfig = {
mode: ThresholdsMode.Absolute,
......@@ -17,9 +17,8 @@ const defaultScale: ThresholdsConfig = {
],
};
export const BarGaugeCell: FC<ReactTableCellProps> = props => {
const { column, tableStyles, cell } = props;
const { field } = column;
export const BarGaugeCell: FC<TableCellProps> = props => {
const { field, column, tableStyles, cell } = props;
if (!field.display) {
return null;
......@@ -40,10 +39,17 @@ export const BarGaugeCell: FC<ReactTableCellProps> = props => {
barGaugeMode = BarGaugeDisplayMode.Lcd;
}
let width;
if (column.width) {
width = (column.width as number) - tableStyles.cellPadding * 2;
} else {
width = tableStyles.cellPadding * 2;
}
return (
<div className={tableStyles.tableCell}>
<BarGauge
width={column.width - tableStyles.cellPadding * 2}
width={width}
height={tableStyles.cellHeightInner}
field={config}
value={displayValue}
......
import React, { FC, CSSProperties } from 'react';
import { ReactTableCellProps } from './types';
import React, { FC } from 'react';
import { TableCellProps } from './types';
import { formattedValueToString } from '@grafana/data';
import tinycolor from 'tinycolor2';
export const DefaultCell: FC<ReactTableCellProps> = props => {
const { column, cell, tableStyles } = props;
export const DefaultCell: FC<TableCellProps> = props => {
const { field, cell, tableStyles } = props;
if (!column.field.display) {
if (!field.display) {
return null;
}
const displayValue = column.field.display(cell.value);
const displayValue = field.display(cell.value);
return <div className={tableStyles.tableCell}>{formattedValueToString(displayValue)}</div>;
};
export const BackgroundColoredCell: FC<ReactTableCellProps> = props => {
const { column, cell, tableStyles } = props;
if (!column.field.display) {
return null;
}
const themeFactor = tableStyles.theme.isDark ? 1 : -0.7;
const displayValue = column.field.display(cell.value);
const bgColor2 = tinycolor(displayValue.color)
.darken(10 * themeFactor)
.spin(5)
.toRgbString();
const styles: CSSProperties = {
background: `linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`,
borderRadius: '0px',
color: 'white',
height: tableStyles.cellHeight,
padding: tableStyles.cellPadding,
};
return <div style={styles}>{formattedValueToString(displayValue)}</div>;
};
......@@ -5,13 +5,15 @@ import { number } from '@storybook/addon-knobs';
import { useTheme } from '../../themes';
import mdx from './Table.mdx';
import {
applyFieldOverrides,
ConfigOverrideRule,
DataFrame,
MutableDataFrame,
FieldMatcherID,
FieldType,
GrafanaTheme,
applyFieldOverrides,
FieldMatcherID,
ConfigOverrideRule,
MutableDataFrame,
ThresholdsConfig,
ThresholdsMode,
} from '@grafana/data';
export default {
......@@ -56,7 +58,7 @@ function buildData(theme: GrafanaTheme, overrides: ConfigOverrideRule[]): DataFr
config: {
unit: 'percent',
custom: {
width: 50,
width: 100,
},
},
},
......@@ -118,7 +120,8 @@ export const BarGaugeCell = () => {
);
};
const defaultThresholds = [
const defaultThresholds: ThresholdsConfig = {
steps: [
{
color: 'blue',
value: -Infinity,
......@@ -127,7 +130,9 @@ const defaultThresholds = [
color: 'green',
value: 20,
},
];
],
mode: ThresholdsMode.Absolute,
};
export const ColoredCells = () => {
const theme = useTheme();
......
import React, { useMemo, CSSProperties } from 'react';
import React, { useMemo } from 'react';
import { DataFrame } from '@grafana/data';
// @ts-ignore
import { useSortBy, useTable, useBlockLayout } from 'react-table';
import { useSortBy, useTable, useBlockLayout, Cell } from 'react-table';
import { FixedSizeList } from 'react-window';
import { getTableStyles } from './styles';
import { getColumns, getTableRows } from './utils';
import { TableColumn } from './types';
import { useTheme } from '../../themes';
import { TableFilterActionCallback } from './types';
import { getTableStyles } from './styles';
import { TableCell } from './TableCell';
export interface Props {
data: DataFrame;
......@@ -15,17 +15,14 @@ export interface Props {
onCellClick?: TableFilterActionCallback;
}
type TableFilterActionCallback = (key: string, value: string) => void;
export const Table = ({ data, height, onCellClick, width }: Props) => {
const theme = useTheme();
const tableStyles = getTableStyles(theme);
const { getTableProps, headerGroups, rows, prepareRow } = useTable(
{
columns: useMemo(() => getColumns(data, width, theme), [data]),
columns: useMemo(() => getColumns(data, width), [data]),
data: useMemo(() => getTableRows(data), [data]),
tableStyles,
},
useSortBy,
useBlockLayout
......@@ -37,7 +34,15 @@ export const Table = ({ data, height, onCellClick, width }: Props) => {
prepareRow(row);
return (
<div {...row.getRowProps({ style })} className={tableStyles.row}>
{row.cells.map((cell: RenderCellProps) => renderCell(cell, onCellClick))}
{row.cells.map((cell: Cell, index: number) => (
<TableCell
key={index}
field={data.fields[cell.column.index]}
tableStyles={tableStyles}
cell={cell}
onCellClick={onCellClick}
/>
))}
</div>
);
},
......@@ -60,34 +65,6 @@ export const Table = ({ data, height, onCellClick, width }: Props) => {
);
};
interface RenderCellProps {
column: TableColumn;
value: any;
getCellProps: () => { style: CSSProperties };
render: (component: string) => React.ReactNode;
}
function renderCell(cell: RenderCellProps, onCellClick?: TableFilterActionCallback) {
const filterable = cell.column.field.config.filterable;
const cellProps = cell.getCellProps();
let onClick: ((event: React.SyntheticEvent) => void) | undefined = undefined;
if (filterable && onCellClick) {
cellProps.style.cursor = 'pointer';
onClick = () => onCellClick(cell.column.Header, cell.value);
}
if (cell.column.textAlign) {
cellProps.style.textAlign = cell.column.textAlign;
}
return (
<div {...cellProps} onClick={onClick}>
{cell.render('Cell')}
</div>
);
}
function renderHeaderCell(column: any, className: string) {
const headerProps = column.getHeaderProps(column.getSortByToggleProps());
......
import React, { FC } from 'react';
import { Cell } from 'react-table';
import { Field } from '@grafana/data';
import { getTextAlign } from './utils';
import { TableFilterActionCallback } from './types';
import { TableStyles } from './styles';
interface Props {
cell: Cell;
field: Field;
tableStyles: TableStyles;
onCellClick?: TableFilterActionCallback;
}
export const TableCell: FC<Props> = ({ cell, field, tableStyles, onCellClick }) => {
const filterable = field.config.filterable;
const cellProps = cell.getCellProps();
let onClick: ((event: React.SyntheticEvent) => void) | undefined = undefined;
if (filterable && onCellClick) {
if (cellProps.style) {
cellProps.style.cursor = 'pointer';
}
onClick = () => onCellClick(cell.column.Header as string, cell.value);
}
const fieldTextAlign = getTextAlign(field);
if (fieldTextAlign && cellProps.style) {
cellProps.style.textAlign = fieldTextAlign;
}
return (
<div {...cellProps} onClick={onClick}>
{cell.render('Cell', { field, tableStyles })}
</div>
);
};
import { TextAlignProperty } from 'csstype';
import { ComponentType } from 'react';
import { CellProps } from 'react-table';
import { Field } from '@grafana/data';
import { TableStyles } from './styles';
......@@ -19,27 +18,13 @@ export enum TableCellDisplayMode {
export type FieldTextAlignment = 'auto' | 'left' | 'right' | 'center';
export interface TableColumn {
// React table props
Header: string;
accessor: string | Function;
Cell: ComponentType<ReactTableCellProps>;
// Grafana additions
field: Field;
width: number;
textAlign: TextAlignProperty;
}
export interface TableRow {
[x: string]: any;
}
export interface ReactTableCellProps {
cell: ReactTableCell;
column: TableColumn;
tableStyles: TableStyles;
}
export type TableFilterActionCallback = (key: string, value: string) => void;
export interface ReactTableCell {
value: any;
export interface TableCellProps extends CellProps<any> {
tableStyles: TableStyles;
field: Field;
}
import { MutableDataFrame, GrafanaThemeType, FieldType } from '@grafana/data';
import { getColumns } from './utils';
import { getTheme } from '../../themes';
import { MutableDataFrame, FieldType } from '@grafana/data';
import { getColumns, getTextAlign } from './utils';
function getData() {
const data = new MutableDataFrame({
......@@ -34,33 +33,31 @@ function getData() {
describe('Table utils', () => {
describe('getColumns', () => {
it('Should build columns from DataFrame', () => {
const theme = getTheme(GrafanaThemeType.Dark);
const columns = getColumns(getData(), 1000, theme);
const columns = getColumns(getData(), 1000);
expect(columns[0].Header).toBe('Time');
expect(columns[1].Header).toBe('Value');
});
it('Should distribute width and use field config width', () => {
const theme = getTheme(GrafanaThemeType.Dark);
const columns = getColumns(getData(), 1000, theme);
const columns = getColumns(getData(), 1000);
expect(columns[0].width).toBe(450);
expect(columns[1].width).toBe(100);
});
});
describe('getTextAlign', () => {
it('Should use textAlign from custom', () => {
const theme = getTheme(GrafanaThemeType.Dark);
const columns = getColumns(getData(), 1000, theme);
const data = getData();
const textAlign = getTextAlign(data.fields[2]);
expect(columns[2].textAlign).toBe('center');
expect(textAlign).toBe('center');
});
it('Should set textAlign to right for number values', () => {
const theme = getTheme(GrafanaThemeType.Dark);
const columns = getColumns(getData(), 1000, theme);
expect(columns[1].textAlign).toBe('right');
const data = getData();
const textAlign = getTextAlign(data.fields[1]);
expect(textAlign).toBe('right');
});
});
});
import { TextAlignProperty } from 'csstype';
import { DataFrame, Field, GrafanaTheme, FieldType } from '@grafana/data';
import { TableColumn, TableRow, TableFieldOptions, TableCellDisplayMode } from './types';
import { DataFrame, Field, FieldType } from '@grafana/data';
import { Column } from 'react-table';
import { DefaultCell } from './DefaultCell';
import { BarGaugeCell } from './BarGaugeCell';
import { DefaultCell, BackgroundColoredCell } from './DefaultCell';
import { BackgroundColoredCell } from './BackgroundColorCell';
import { TableRow, TableFieldOptions, TableCellDisplayMode } from './types';
export function getTableRows(data: DataFrame): TableRow[] {
const tableData = [];
......@@ -19,7 +21,7 @@ export function getTableRows(data: DataFrame): TableRow[] {
return tableData;
}
function getTextAlign(field: Field): TextAlignProperty {
export function getTextAlign(field: Field): TextAlignProperty {
if (field.config.custom) {
const custom = field.config.custom as TableFieldOptions;
......@@ -40,36 +42,21 @@ function getTextAlign(field: Field): TextAlignProperty {
return 'left';
}
export function getColumns(data: DataFrame, availableWidth: number, theme: GrafanaTheme): TableColumn[] {
const cols: TableColumn[] = [];
export function getColumns(data: DataFrame, availableWidth: number): Column[] {
const columns: Column[] = [];
let fieldCountWithoutWidth = data.fields.length;
for (const field of data.fields) {
const fieldTableOptions = (field.config.custom || {}) as TableFieldOptions;
if (fieldTableOptions.width) {
availableWidth -= fieldTableOptions.width;
fieldCountWithoutWidth -= 1;
}
let Cell = DefaultCell;
let textAlign = getTextAlign(field);
switch (fieldTableOptions.displayMode) {
case TableCellDisplayMode.ColorBackground:
Cell = BackgroundColoredCell;
break;
case TableCellDisplayMode.LcdGauge:
case TableCellDisplayMode.GradientGauge:
Cell = BarGaugeCell;
textAlign = 'center';
break;
}
const Cell = getCellComponent(fieldTableOptions.displayMode);
cols.push({
field,
columns.push({
Cell,
textAlign,
Header: field.name,
accessor: field.name,
width: fieldTableOptions.width,
......@@ -78,11 +65,23 @@ export function getColumns(data: DataFrame, availableWidth: number, theme: Grafa
// divide up the rest of the space
const sharedWidth = availableWidth / fieldCountWithoutWidth;
for (const column of cols) {
for (const column of columns) {
if (!column.width) {
column.width = sharedWidth;
}
}
return cols;
return columns;
}
function getCellComponent(displayMode: TableCellDisplayMode) {
switch (displayMode) {
case TableCellDisplayMode.ColorBackground:
return BackgroundColoredCell;
case TableCellDisplayMode.LcdGauge:
case TableCellDisplayMode.GradientGauge:
return BarGaugeCell;
default:
return DefaultCell;
}
}
......@@ -3778,43 +3778,6 @@
"@types/d3-interpolate" "*"
"@types/d3-selection" "*"
"@types/d3@5.7.1":
version "5.7.1"
resolved "https://registry.yarnpkg.com/@types/d3/-/d3-5.7.1.tgz#99e6b3a558816264a674947822600d3aba8b84b0"
integrity sha512-1TNamlKYTdpRzFjDIcgiRqpNqYz9VVvNOisVqCqvYsxXyysgbfTKxdOurrVcW+SxyURTPwAD68KV04BL5RKNcQ==
dependencies:
"@types/d3-array" "*"
"@types/d3-axis" "*"
"@types/d3-brush" "*"
"@types/d3-chord" "*"
"@types/d3-collection" "*"
"@types/d3-color" "*"
"@types/d3-contour" "*"
"@types/d3-dispatch" "*"
"@types/d3-drag" "*"
"@types/d3-dsv" "*"
"@types/d3-ease" "*"
"@types/d3-fetch" "*"
"@types/d3-force" "*"
"@types/d3-format" "*"
"@types/d3-geo" "*"
"@types/d3-hierarchy" "*"
"@types/d3-interpolate" "*"
"@types/d3-path" "*"
"@types/d3-polygon" "*"
"@types/d3-quadtree" "*"
"@types/d3-random" "*"
"@types/d3-scale" "*"
"@types/d3-scale-chromatic" "*"
"@types/d3-selection" "*"
"@types/d3-shape" "*"
"@types/d3-time" "*"
"@types/d3-time-format" "*"
"@types/d3-timer" "*"
"@types/d3-transition" "*"
"@types/d3-voronoi" "*"
"@types/d3-zoom" "*"
"@types/d3@5.7.2":
version "5.7.2"
resolved "https://registry.yarnpkg.com/@types/d3/-/d3-5.7.2.tgz#52235eb71a1d3ca171d6dca52a58f5ccbe0254cc"
......@@ -4290,6 +4253,13 @@
dependencies:
"@types/react" "*"
"@types/react-table@7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.0.2.tgz#184de5ad5a7c5aced08b49812002a4d2e8918cc0"
integrity sha512-sxvjV0JCk/ijCzENejXth99cFMnmucATaC31gz1bMk8iQwUDE2VYaw2QQTcDrzBxzastBQGdcLpcFIN61RvgIA==
dependencies:
"@types/react" "*"
"@types/react-test-renderer@*":
version "16.9.1"
resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.1.tgz#9d432c46c515ebe50c45fa92c6fb5acdc22e39c4"
......@@ -8245,43 +8215,6 @@ d3@5.15.0:
d3-voronoi "1"
d3-zoom "1"
d3@5.9.1:
version "5.9.1"
resolved "https://registry.yarnpkg.com/d3/-/d3-5.9.1.tgz#fde73fa9af7281d2ff0d2a32aa8f306e93a6d1cd"
integrity sha512-JceuBn5VVWySPQc9EA0gfq0xQVgEQXGokHhe+359bmgGeUITLK2r2b9idMzquQne9DKxb7JDCE1gDRXe9OIF2Q==
dependencies:
d3-array "1"
d3-axis "1"
d3-brush "1"
d3-chord "1"
d3-collection "1"
d3-color "1"
d3-contour "1"
d3-dispatch "1"
d3-drag "1"
d3-dsv "1"
d3-ease "1"
d3-fetch "1"
d3-force "1"
d3-format "1"
d3-geo "1"
d3-hierarchy "1"
d3-interpolate "1"
d3-path "1"
d3-polygon "1"
d3-quadtree "1"
d3-random "1"
d3-scale "2"
d3-scale-chromatic "1"
d3-selection "1"
d3-shape "1"
d3-time "1"
d3-time-format "2"
d3-timer "1"
d3-transition "1"
d3-voronoi "1"
d3-zoom "1"
d@1, d@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment