diff --git a/packages/grafana-ui/src/components/Table/Table.story.tsx b/packages/grafana-ui/src/components/Table/Table.story.tsx
index 8cd97dcefa9..1aa618d73d7 100644
--- a/packages/grafana-ui/src/components/Table/Table.story.tsx
+++ b/packages/grafana-ui/src/components/Table/Table.story.tsx
@@ -5,24 +5,32 @@ import { Table } from './Table';
import { migratedTestTable, migratedTestStyles, simpleTable } from './examples';
import { ScopedVars, TableData } from '../../types/index';
import { withFullSizeStory } from '../../utils/storybook/withFullSizeStory';
+import { number, boolean } from '@storybook/addon-knobs';
-const replaceVariables = (value: any, scopedVars: ScopedVars | undefined) => {
- // if (scopedVars) {
- // // For testing variables replacement in link
- // _.each(scopedVars, (val, key) => {
- // value = value.replace('$' + key, val.value);
- // });
- // }
+const replaceVariables = (value: string, scopedVars?: ScopedVars) => {
+ if (scopedVars) {
+ // For testing variables replacement in link
+ for (const key in scopedVars) {
+ const val = scopedVars[key];
+ value = value.replace('$' + key, val.value);
+ }
+ }
return value;
};
-storiesOf('UI - Alpha/Table', module)
+storiesOf('UI/Table', module)
.add('basic', () => {
+ const showHeader = boolean('Show Header', true);
+ const fixedRowCount = number('Fixed Rows', 1);
+ const fixedColumnCount = number('Fixed Columns', 1);
+
return withFullSizeStory(Table, {
styles: [],
data: simpleTable,
replaceVariables,
- showHeader: true,
+ fixedRowCount,
+ fixedColumnCount,
+ showHeader,
});
})
.add('Test Configuration', () => {
diff --git a/packages/grafana-ui/src/components/Table/Table.test.ts b/packages/grafana-ui/src/components/Table/Table.test.ts
deleted file mode 100644
index e53df39750e..00000000000
--- a/packages/grafana-ui/src/components/Table/Table.test.ts
+++ /dev/null
@@ -1,240 +0,0 @@
-import _ from 'lodash';
-
-import { getColorDefinitionByName } from '@grafana/ui';
-import { ScopedVars } from '@grafana/ui/src/types';
-import { getTheme } from '../../themes';
-
-import { migratedTestTable, migratedTestStyles } from './examples';
-import TableXXXX from './TableXXXX';
-
-// TODO: this is commented out with *x* describe!
-// Essentially all the elements need to replace the
with
-xdescribe('when rendering table', () => {
- const SemiDarkOrange = getColorDefinitionByName('semi-dark-orange');
-
- describe('given 13 columns', () => {
- // const sanitize = value => {
- // return 'sanitized';
- // };
-
- const replaceVariables = (value: any, scopedVars: ScopedVars | undefined) => {
- if (scopedVars) {
- // For testing variables replacement in link
- _.each(scopedVars, (val, key) => {
- value = value.replace('$' + key, val.value);
- });
- }
- return value;
- };
-
- const table = migratedTestTable;
- const renderer = new TableXXXX({
- styles: migratedTestStyles,
- data: migratedTestTable,
- replaceVariables,
- showHeader: true,
- width: 100,
- height: 100,
- theme: getTheme(),
- });
-
- it('time column should be formated', () => {
- const html = renderer.renderCell(0, 0, 1388556366666);
- expect(html).toBe(' 2014-01-01T06:06:06Z | ');
- });
-
- it('time column with epoch as string should be formatted', () => {
- const html = renderer.renderCell(0, 0, '1388556366666');
- expect(html).toBe(' 2014-01-01T06:06:06Z | ');
- });
-
- it('time column with RFC2822 date as string should be formatted', () => {
- const html = renderer.renderCell(0, 0, 'Sat, 01 Dec 2018 01:00:00 GMT');
- expect(html).toBe(' 2018-12-01T01:00:00Z | ');
- });
-
- it('time column with ISO date as string should be formatted', () => {
- const html = renderer.renderCell(0, 0, '2018-12-01T01:00:00Z');
- expect(html).toBe(' 2018-12-01T01:00:00Z | ');
- });
-
- it('undefined time column should be rendered as -', () => {
- const html = renderer.renderCell(0, 0, undefined);
- expect(html).toBe(' - | ');
- });
-
- it('null time column should be rendered as -', () => {
- const html = renderer.renderCell(0, 0, null);
- expect(html).toBe(' - | ');
- });
-
- it('number column with unit specified should ignore style unit', () => {
- const html = renderer.renderCell(5, 0, 1230);
- expect(html).toBe(' 1.23 kbps | ');
- });
-
- it('number column should be formated', () => {
- const html = renderer.renderCell(1, 0, 1230);
- expect(html).toBe(' 1.230 s | ');
- });
-
- it('number style should ignore string values', () => {
- const html = renderer.renderCell(1, 0, 'asd');
- expect(html).toBe(' asd | ');
- });
-
- it('colored cell should have style (handles HEX color values)', () => {
- const html = renderer.renderCell(2, 0, 40);
- expect(html).toBe(' 40.0 | ');
- });
-
- it('colored cell should have style (handles named color values', () => {
- const html = renderer.renderCell(2, 0, 55);
- expect(html).toBe(` 55.0 | `);
- });
-
- it('colored cell should have style handles(rgb color values)', () => {
- const html = renderer.renderCell(2, 0, 85);
- expect(html).toBe(' 85.0 | ');
- });
-
- it('unformated undefined should be rendered as string', () => {
- const html = renderer.renderCell(3, 0, 'value');
- expect(html).toBe(' value | ');
- });
-
- it('string style with escape html should return escaped html', () => {
- const html = renderer.renderCell(4, 0, '&breaking the row');
- expect(html).toBe(' &breaking <br /> the <br /> row | ');
- });
-
- it('undefined formater should return escaped html', () => {
- const html = renderer.renderCell(3, 0, '&breaking the row');
- expect(html).toBe(' &breaking <br /> the <br /> row | ');
- });
-
- it('undefined value should render as -', () => {
- const html = renderer.renderCell(3, 0, undefined);
- expect(html).toBe(' | ');
- });
-
- it('sanitized value should render as', () => {
- const html = renderer.renderCell(6, 0, 'text link');
- expect(html).toBe(' sanitized | ');
- });
-
- it('Time column title should be Timestamp', () => {
- expect(table.columns[0].title).toBe('Timestamp');
- });
-
- it('Value column title should be Val', () => {
- expect(table.columns[1].title).toBe('Val');
- });
-
- it('Colored column title should be Colored', () => {
- expect(table.columns[2].title).toBe('Colored');
- });
-
- it('link should render as', () => {
- const html = renderer.renderCell(7, 0, 'host1');
- const expectedHtml = `
-
-
- host1
-
- |
- `;
- expect(normalize(html + '')).toBe(normalize(expectedHtml));
- });
-
- it('Array column should not use number as formatter', () => {
- const html = renderer.renderCell(8, 0, ['value1', 'value2']);
- expect(html).toBe(' value1, value2 | ');
- });
-
- it('numeric value should be mapped to text', () => {
- const html = renderer.renderCell(9, 0, 1);
- expect(html).toBe(' on | ');
- });
-
- it('string numeric value should be mapped to text', () => {
- const html = renderer.renderCell(9, 0, '0');
- expect(html).toBe(' off | ');
- });
-
- it('string value should be mapped to text', () => {
- const html = renderer.renderCell(9, 0, 'HELLO WORLD');
- expect(html).toBe(' HELLO GRAFANA | ');
- });
-
- it('array column value should be mapped to text', () => {
- const html = renderer.renderCell(9, 0, ['value1', 'value2']);
- expect(html).toBe(' value3, value4 | ');
- });
-
- it('value should be mapped to text (range)', () => {
- const html = renderer.renderCell(10, 0, 2);
- expect(html).toBe(' on | ');
- });
-
- it('value should be mapped to text (range)', () => {
- const html = renderer.renderCell(10, 0, 5);
- expect(html).toBe(' off | ');
- });
-
- it('array column value should not be mapped to text', () => {
- const html = renderer.renderCell(10, 0, ['value1', 'value2']);
- expect(html).toBe(' value1, value2 | ');
- });
-
- it('value should be mapped to text and colored cell should have style', () => {
- const html = renderer.renderCell(11, 0, 1);
- expect(html).toBe(` on | `);
- });
-
- it('value should be mapped to text and colored cell should have style', () => {
- const html = renderer.renderCell(11, 0, '1');
- expect(html).toBe(` on | `);
- });
-
- it('value should be mapped to text and colored cell should have style', () => {
- const html = renderer.renderCell(11, 0, 0);
- expect(html).toBe(' off | ');
- });
-
- it('value should be mapped to text and colored cell should have style', () => {
- const html = renderer.renderCell(11, 0, '0');
- expect(html).toBe(' off | ');
- });
-
- it('value should be mapped to text and colored cell should have style', () => {
- const html = renderer.renderCell(11, 0, '2.1');
- expect(html).toBe(' 2.1 | ');
- });
-
- it('value should be mapped to text (range) and colored cell should have style', () => {
- const html = renderer.renderCell(12, 0, 0);
- expect(html).toBe(' 0 | ');
- });
-
- it('value should be mapped to text (range) and colored cell should have style', () => {
- const html = renderer.renderCell(12, 0, 1);
- expect(html).toBe(' on | ');
- });
-
- it('value should be mapped to text (range) and colored cell should have style', () => {
- const html = renderer.renderCell(12, 0, 4);
- expect(html).toBe(` off | `);
- });
-
- it('value should be mapped to text (range) and colored cell should have style', () => {
- const html = renderer.renderCell(12, 0, '7.1');
- expect(html).toBe(' 7.1 | ');
- });
- });
-});
-
-function normalize(str: string) {
- return str.replace(/\s+/gm, ' ').trim();
-}
diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx
index cbc0b50ebc0..2a75c33224e 100644
--- a/packages/grafana-ui/src/components/Table/Table.tsx
+++ b/packages/grafana-ui/src/components/Table/Table.tsx
@@ -1,6 +1,6 @@
// Libraries
import _ from 'lodash';
-import React, { Component, ReactNode } from 'react';
+import React, { Component, ReactElement } from 'react';
import {
SortDirectionType,
SortIndicator,
@@ -14,49 +14,16 @@ import { Themeable } from '../../types/theme';
import { sortTableData } from '../../utils/processTimeSeries';
import { TableData, InterpolateFunction } from '@grafana/ui';
-import { ColumnStyle } from './Table';
-
-// APP Imports!!!
-// import kbn from 'app/core/utils/kbn';
-
-// Made to match the existing (untyped) settings in the angular table
-export interface ColumnStyle {
- pattern?: string;
-
- alias?: string;
- colorMode?: 'cell' | 'value';
- colors?: any[];
- decimals?: number;
- thresholds?: any[];
- type?: 'date' | 'number' | 'string' | 'hidden';
- unit?: string;
- dateFormat?: string;
- sanitize?: boolean; // not used in react
- mappingType?: any;
- valueMaps?: any;
- rangeMaps?: any;
-
- link?: any;
- linkUrl?: any;
- linkTooltip?: any;
- linkTargetBlank?: boolean;
-
- preserveFormat?: boolean;
-}
-
-type CellFormatter = (v: any, style?: ColumnStyle) => ReactNode;
+import { TableCellBuilder, ColumnStyle, getCellBuilder, TableCellBuilderOptions } from './TableCellBuilder';
interface ColumnInfo {
+ index: number;
header: string;
- accessor: string; // the field name
- style?: ColumnStyle;
- hidden?: boolean;
- formatter: CellFormatter;
- filterable?: boolean;
+ builder: TableCellBuilder;
}
-interface Props extends Themeable {
- data?: TableData;
+export interface Props extends Themeable {
+ data: TableData;
showHeader: boolean;
fixedColumnCount: number;
fixedRowCount: number;
@@ -70,14 +37,12 @@ interface Props extends Themeable {
interface State {
sortBy?: number;
sortDirection?: SortDirectionType;
- data?: TableData;
+ data: TableData;
}
export class Table extends Component {
- columns: ColumnInfo[] = [];
- colorState: any;
-
- _cache: CellMeasurerCache;
+ columns: ColumnInfo[];
+ measurer: CellMeasurerCache;
static defaultProps = {
showHeader: true,
@@ -92,12 +57,11 @@ export class Table extends Component {
data: props.data,
};
- this._cache = new CellMeasurerCache({
+ this.columns = this.initColumns(props);
+ this.measurer = new CellMeasurerCache({
defaultHeight: 30,
defaultWidth: 150,
});
-
- this.initRenderer();
}
componentDidUpdate(prevProps: Props, prevState: State) {
@@ -105,9 +69,14 @@ export class Table extends Component {
const { sortBy, sortDirection } = this.state;
const dataChanged = data !== prevProps.data;
+ // Reset the size cache
+ if (dataChanged) {
+ this.measurer.clearAll();
+ }
+
// Update the renderer if options change
if (dataChanged || styles !== prevProps.styles) {
- this.initRenderer();
+ this.columns = this.initColumns(this.props);
}
// Update the data when data or sort changes
@@ -117,7 +86,32 @@ export class Table extends Component {
}
}
- initRenderer() {}
+ initColumns(props: Props): ColumnInfo[] {
+ const { styles, data } = props;
+ return data.columns.map((col, index) => {
+ let title = col.text;
+ let style: ColumnStyle | null = null; // ColumnStyle
+
+ // Find the style based on the text
+ for (let i = 0; i < styles.length; i++) {
+ const s = styles[i];
+ const regex = 'XXX'; //kbn.stringToJsRegex(s.pattern);
+ if (title.match(regex)) {
+ style = s;
+ if (s.alias) {
+ title = title.replace(regex, s.alias);
+ }
+ break;
+ }
+ }
+
+ return {
+ index,
+ header: title,
+ builder: getCellBuilder(col, style, this.props),
+ };
+ });
+ }
//----------------------------------------------------------------------
//----------------------------------------------------------------------
@@ -136,7 +130,7 @@ export class Table extends Component {
this.setState({ sortBy: sort, sortDirection: dir });
};
- handelClick = (rowIndex: number, columnIndex: number) => {
+ handleCellClick = (rowIndex: number, columnIndex: number) => {
const { showHeader } = this.props;
const { data } = this.state;
const realRowIndex = rowIndex - (showHeader ? 1 : 0);
@@ -149,14 +143,16 @@ export class Table extends Component {
}
};
- headerRenderer = (columnIndex: number): ReactNode => {
+ headerBuilder = (cell: TableCellBuilderOptions): ReactElement<'div'> => {
const { data, sortBy, sortDirection } = this.state;
+ const { columnIndex, rowIndex, style } = cell.props;
+
const col = data!.columns[columnIndex];
const sorting = sortBy === columnIndex;
return (
-
- {col.text}{' '}
+ this.handleCellClick(rowIndex, columnIndex)}>
+ {col.text}
{sorting && (
{sortDirection}
@@ -168,43 +164,22 @@ export class Table extends Component {
};
cellRenderer = (props: GridCellProps): React.ReactNode => {
- const { rowIndex, columnIndex, key, parent, style } = props;
+ const { rowIndex, columnIndex, key, parent } = props;
const { showHeader } = this.props;
const { data } = this.state;
if (!data) {
- return ? ;
+ return ?? ;
}
const realRowIndex = rowIndex - (showHeader ? 1 : 0);
-
- let classNames = 'gf-table-cell';
- let content = null;
-
- if (realRowIndex < 0) {
- content = this.headerRenderer(columnIndex);
- classNames = 'gf-table-header';
- } else {
- const row = data.rows[realRowIndex];
- const value = row[columnIndex];
- content = (
-
- {rowIndex}/{columnIndex}: {value}
-
- );
- }
+ const isHeader = realRowIndex < 0;
+ const row = isHeader ? (data.columns as any[]) : data.rows[realRowIndex];
+ const value = row[columnIndex];
+ const builder = isHeader ? this.headerBuilder : this.columns[columnIndex].builder;
return (
-
- this.handelClick(rowIndex, columnIndex)}
- className={classNames}
- style={{
- ...style,
- whiteSpace: 'nowrap',
- }}
- >
- {content}
-
+
+ {builder({ value, row, table: this, props })}
);
};
@@ -218,16 +193,16 @@ export class Table extends Component {
return (
ReactElement<'div'>;
+
+/** Simplest cell that just spits out the value */
+export const simpleCellBuilder: TableCellBuilder = (cell: TableCellBuilderOptions) => {
+ const { props, value, className } = cell;
+ const { style } = props;
+
+ return (
+
+ {value}
+
+ );
+};
+
+// ***************************************************************************
+// HERE BE DRAGONS!!!
+// ***************************************************************************
+//
+// The following code has been migrated blindy two times from the angular
+// table panel. I don't understand all the options nor do I know if they
+// are correct!
+//
+// ***************************************************************************
+
+// APP Imports!!!
+// import kbn from 'app/core/utils/kbn';
+
+// Made to match the existing (untyped) settings in the angular table
+export interface ColumnStyle {
+ pattern?: string;
+
+ alias?: string;
+ colorMode?: 'cell' | 'value';
+ colors?: any[];
+ decimals?: number;
+ thresholds?: any[];
+ type?: 'date' | 'number' | 'string' | 'hidden';
+ unit?: string;
+ dateFormat?: string;
+ sanitize?: boolean; // not used in react
+ mappingType?: any;
+ valueMaps?: any;
+ rangeMaps?: any;
+
+ link?: any;
+ linkUrl?: any;
+ linkTooltip?: any;
+ linkTargetBlank?: boolean;
+
+ preserveFormat?: boolean;
+}
+
+// private mapper:ValueMapper,
+// private style:ColumnStyle,
+// private theme:GrafanaTheme,
+// private column:Column,
+// private replaceVariables: InterpolateFunction,
+// private fmt?:ValueFormatter) {
+
+export function getCellBuilder(schema: Column, style: ColumnStyle | null, props: Props): TableCellBuilder {
+ if (!style) {
+ return simpleCellBuilder;
+ }
+
+ if (style.type === 'hidden') {
+ // TODO -- for hidden, we either need to:
+ // 1. process the Table and remove hidden fields
+ // 2. do special math to pick the right column skipping hidden fields
+ throw new Error('hidden not supported!');
+ }
+
+ if (style.type === 'date') {
+ return new CellBuilderWithStyle(
+ (v: any) => {
+ if (v === undefined || v === null) {
+ return '-';
+ }
+
+ if (_.isArray(v)) {
+ v = v[0];
+ }
+ let date = moment(v);
+ if (false) {
+ // TODO?????? this.props.isUTC) {
+ date = date.utc();
+ }
+ return date.format(style.dateFormat);
+ },
+ style,
+ props.theme,
+ schema,
+ props.replaceVariables
+ ).build;
+ }
+
+ if (style.type === 'string') {
+ return new CellBuilderWithStyle(
+ (v: any) => {
+ if (_.isArray(v)) {
+ v = v.join(', ');
+ }
+ return v;
+ },
+ style,
+ props.theme,
+ schema,
+ props.replaceVariables
+ ).build;
+ // TODO!!!! all the mapping stuff!!!!
+ }
+
+ if (style.type === 'number') {
+ const valueFormatter = getValueFormat(style.unit || schema.unit || 'none');
+ return new CellBuilderWithStyle(
+ (v: any) => {
+ if (v === null || v === void 0) {
+ return '-';
+ }
+ return v;
+ },
+ style,
+ props.theme,
+ schema,
+ props.replaceVariables,
+ valueFormatter
+ ).build;
+ }
+
+ return simpleCellBuilder;
+}
+
+type ValueMapper = (value: any) => any;
+
+// Runs the value through a formatter and adds colors to the cell properties
+class CellBuilderWithStyle {
+ constructor(
+ private mapper: ValueMapper,
+ private style: ColumnStyle,
+ private theme: GrafanaTheme,
+ private column: Column,
+ private replaceVariables: InterpolateFunction,
+ private fmt?: ValueFormatter
+ ) {
+ //
+ }
+
+ getColorForValue = (value: any): string | null => {
+ const { thresholds, colors } = this.style;
+ if (!thresholds || !colors) {
+ return null;
+ }
+
+ for (let i = thresholds.length; i > 0; i--) {
+ if (value >= thresholds[i - 1]) {
+ return getColorFromHexRgbOrName(colors[i], this.theme.type);
+ }
+ }
+ return getColorFromHexRgbOrName(_.first(colors), this.theme.type);
+ };
+
+ build = (cell: TableCellBuilderOptions) => {
+ let { props } = cell;
+ let value = this.mapper(cell.value);
+
+ if (_.isNumber(value)) {
+ if (this.fmt) {
+ value = this.fmt(value, this.style.decimals);
+ }
+
+ // For numeric values set the color
+ const { colorMode } = this.style;
+ if (colorMode) {
+ const color = this.getColorForValue(Number(value));
+ if (color) {
+ if (colorMode === 'cell') {
+ props = {
+ ...props,
+ style: {
+ ...props.style,
+ backgroundColor: color,
+ color: 'white',
+ },
+ };
+ } else if (colorMode === 'value') {
+ props = {
+ ...props,
+ style: {
+ ...props.style,
+ color: color,
+ },
+ };
+ }
+ }
+ }
+ }
+
+ const cellClasses = [];
+ if (this.style.preserveFormat) {
+ cellClasses.push('table-panel-cell-pre');
+ }
+
+ if (this.style.link) {
+ // Render cell as link
+ const { row } = cell;
+
+ const scopedVars: any = {};
+ if (row) {
+ for (let i = 0; i < row.length; i++) {
+ scopedVars[`__cell_${i}`] = { value: row[i] };
+ }
+ }
+ scopedVars['__cell'] = { value: value };
+
+ const cellLink = this.replaceVariables(this.style.linkUrl, scopedVars, encodeURIComponent);
+ const cellLinkTooltip = this.replaceVariables(this.style.linkTooltip, scopedVars);
+ const cellTarget = this.style.linkTargetBlank ? '_blank' : '';
+
+ cellClasses.push('table-panel-cell-link');
+ value = (
+
+ {value}
+
+ );
+ }
+
+ // ??? I don't think this will still work!
+ if (this.column.filterable) {
+ cellClasses.push('table-panel-cell-filterable');
+ value = (
+ <>
+ {value}
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+
+ let className;
+ if (cellClasses.length) {
+ className = cellClasses.join(' ');
+ }
+
+ return simpleCellBuilder({ value, props, className });
+ };
+}
diff --git a/packages/grafana-ui/src/components/Table/TableXXXX.tsx b/packages/grafana-ui/src/components/Table/TableXXXX.tsx
deleted file mode 100644
index 4c78c3b336b..00000000000
--- a/packages/grafana-ui/src/components/Table/TableXXXX.tsx
+++ /dev/null
@@ -1,456 +0,0 @@
-// Libraries
-import _ from 'lodash';
-import React, { Component, CSSProperties, ReactNode } from 'react';
-import {
- Table as RVTable,
- SortDirectionType,
- SortIndicator,
- Column as RVColumn,
- TableHeaderProps,
- TableCellProps,
-} from 'react-virtualized';
-import { Themeable } from '../../types/theme';
-
-import { sortTableData } from '../../utils/processTimeSeries';
-
-import moment from 'moment';
-
-import { getValueFormat, TableData, getColorFromHexRgbOrName, InterpolateFunction, Column } from '@grafana/ui';
-import { Index } from 'react-virtualized';
-import { ColumnStyle } from './Table';
-
-type CellFormatter = (v: any, style?: ColumnStyle) => ReactNode;
-
-interface ColumnInfo {
- header: string;
- accessor: string; // the field name
- style?: ColumnStyle;
- hidden?: boolean;
- formatter: CellFormatter;
- filterable?: boolean;
-}
-
-interface Props extends Themeable {
- data?: TableData;
- showHeader: boolean;
- styles: ColumnStyle[];
- replaceVariables: InterpolateFunction;
- width: number;
- height: number;
- isUTC?: boolean;
-}
-
-interface State {
- sortBy?: number;
- sortDirection?: SortDirectionType;
- data?: TableData;
-}
-
-export class TableXXXX extends Component {
- columns: ColumnInfo[] = [];
- colorState: any;
-
- static defaultProps = {
- showHeader: true,
- };
-
- constructor(props: Props) {
- super(props);
-
- this.state = {
- data: props.data,
- };
-
- this.initRenderer();
- }
-
- componentDidUpdate(prevProps: Props, prevState: State) {
- const { data, styles } = this.props;
- const { sortBy, sortDirection } = this.state;
- const dataChanged = data !== prevProps.data;
-
- // Update the renderer if options change
- if (dataChanged || styles !== prevProps.styles) {
- this.initRenderer();
- }
-
- // Update the data when data or sort changes
- if (dataChanged || sortBy !== prevState.sortBy || sortDirection !== prevState.sortDirection) {
- const sorted = data ? sortTableData(data, sortBy, sortDirection === 'DESC') : data;
- this.setState({ data: sorted });
- }
- }
-
- initRenderer() {
- const { styles } = this.props;
- const { data } = this.state;
- this.colorState = {};
- if (!data || !data.columns) {
- this.columns = [];
- return;
- }
- this.columns = data.columns.map((col, index) => {
- let title = col.text;
- let style; // ColumnStyle
-
- // Find the style based on the text
- for (let i = 0; i < styles.length; i++) {
- const s = styles[i];
- const regex = 'XXX'; //kbn.stringToJsRegex(s.pattern);
- if (title.match(regex)) {
- style = s;
- if (s.alias) {
- title = title.replace(regex, s.alias);
- }
- break;
- }
- }
-
- return {
- header: title,
- accessor: col.text, // unique?
- style: style,
- formatter: this.createColumnFormatter(col, style),
- };
- });
- }
-
- //----------------------------------------------------------------------
- // renderer.ts copy (taken from angular version!!!)
- //----------------------------------------------------------------------
-
- getColorForValue(value: any, style: ColumnStyle) {
- if (!style.thresholds || !style.colors) {
- return null;
- }
- const { theme } = this.props;
-
- for (let i = style.thresholds.length; i > 0; i--) {
- if (value >= style.thresholds[i - 1]) {
- return getColorFromHexRgbOrName(style.colors[i], theme.type);
- }
- }
- return getColorFromHexRgbOrName(_.first(style.colors), theme.type);
- }
-
- defaultCellFormatter(v: any, style?: ColumnStyle): string {
- if (v === null || v === void 0 || v === undefined) {
- return '';
- }
-
- if (_.isArray(v)) {
- v = v.join(', ');
- }
-
- return v; // react will sanitize
- }
-
- createColumnFormatter(schema: Column, style?: ColumnStyle): CellFormatter {
- if (!style) {
- return this.defaultCellFormatter;
- }
-
- if (style.type === 'hidden') {
- return v => {
- return undefined;
- };
- }
-
- if (style.type === 'date') {
- return v => {
- if (v === undefined || v === null) {
- return '-';
- }
-
- if (_.isArray(v)) {
- v = v[0];
- }
- let date = moment(v);
- if (this.props.isUTC) {
- date = date.utc();
- }
- return date.format(style.dateFormat);
- };
- }
-
- if (style.type === 'string') {
- return v => {
- if (_.isArray(v)) {
- v = v.join(', ');
- }
-
- const mappingType = style.mappingType || 0;
-
- if (mappingType === 1 && style.valueMaps) {
- for (let i = 0; i < style.valueMaps.length; i++) {
- const map = style.valueMaps[i];
-
- if (v === null) {
- if (map.value === 'null') {
- return map.text;
- }
- continue;
- }
-
- // Allow both numeric and string values to be mapped
- if ((!_.isString(v) && Number(map.value) === Number(v)) || map.value === v) {
- this.setColorState(v, style);
- return this.defaultCellFormatter(map.text, style);
- }
- }
- }
-
- if (mappingType === 2 && style.rangeMaps) {
- for (let i = 0; i < style.rangeMaps.length; i++) {
- const map = style.rangeMaps[i];
-
- if (v === null) {
- if (map.from === 'null' && map.to === 'null') {
- return map.text;
- }
- continue;
- }
-
- if (Number(map.from) <= Number(v) && Number(map.to) >= Number(v)) {
- this.setColorState(v, style);
- return this.defaultCellFormatter(map.text, style);
- }
- }
- }
-
- if (v === null || v === void 0) {
- return '-';
- }
-
- this.setColorState(v, style);
- return this.defaultCellFormatter(v, style);
- };
- }
-
- if (style.type === 'number') {
- const valueFormatter = getValueFormat(style.unit || schema.unit || 'none');
-
- return v => {
- if (v === null || v === void 0) {
- return '-';
- }
-
- if (_.isString(v) || _.isArray(v)) {
- return this.defaultCellFormatter(v, style);
- }
-
- this.setColorState(v, style);
- return valueFormatter(v, style.decimals, null);
- };
- }
-
- return value => {
- return this.defaultCellFormatter(value, style);
- };
- }
-
- setColorState(value: any, style: ColumnStyle) {
- if (!style.colorMode) {
- return;
- }
-
- if (value === null || value === void 0 || _.isArray(value)) {
- return;
- }
-
- if (_.isNaN(value)) {
- return;
- }
- const numericValue = Number(value);
- this.colorState[style.colorMode] = this.getColorForValue(numericValue, style);
- }
-
- renderRowVariables(rowIndex: number) {
- const scopedVars: any = {};
- const row = this.rowGetter({ index: rowIndex });
- for (let i = 0; i < row.length; i++) {
- scopedVars[`__cell_${i}`] = { value: row[i] };
- }
- return scopedVars;
- }
-
- renderCell(columnIndex: number, rowIndex: number, value: any): ReactNode {
- const column = this.columns[columnIndex];
- if (column.formatter) {
- value = column.formatter(value, column.style);
- }
-
- const style: CSSProperties = {};
- const cellClasses = [];
- let cellClass = '';
-
- if (this.colorState.cell) {
- style.backgroundColor = this.colorState.cell;
- style.color = 'white';
- this.colorState.cell = null;
- } else if (this.colorState.value) {
- style.color = this.colorState.value;
- this.colorState.value = null;
- }
-
- if (value === undefined) {
- style.display = 'none';
- column.hidden = true;
- } else {
- column.hidden = false;
- }
-
- if (column.style && column.style.preserveFormat) {
- cellClasses.push('table-panel-cell-pre');
- }
-
- let columnHtml: JSX.Element;
- if (column.style && column.style.link) {
- // Render cell as link
- const { replaceVariables } = this.props;
- const scopedVars = this.renderRowVariables(rowIndex);
- scopedVars['__cell'] = { value: value };
-
- const cellLink = replaceVariables(column.style.linkUrl, scopedVars, encodeURIComponent);
- const cellLinkTooltip = replaceVariables(column.style.linkTooltip, scopedVars);
- const cellTarget = column.style.linkTargetBlank ? '_blank' : '';
-
- cellClasses.push('table-panel-cell-link');
- columnHtml = (
-
- {value}
-
- );
- } else {
- columnHtml = {value};
- }
-
- let filterLink: JSX.Element | null = null;
- if (column.filterable) {
- cellClasses.push('table-panel-cell-filterable');
- filterLink = (
-
-
-
-
-
-
-
-
- );
- }
-
- if (cellClasses.length) {
- cellClass = cellClasses.join(' ');
- }
-
- style.width = '100%';
- style.height = '100%';
- columnHtml = (
-
- {columnHtml}
- {filterLink}
-
- );
- return columnHtml;
- }
-
- //----------------------------------------------------------------------
- //----------------------------------------------------------------------
-
- rowGetter = ({ index }: Index) => {
- return this.state.data!.rows[index];
- };
-
- doSort = (info: any) => {
- let dir = info.sortDirection;
- let sort = info.sortBy;
- if (sort !== this.state.sortBy) {
- dir = 'DESC';
- } else if (dir === 'DESC') {
- dir = 'ASC';
- } else {
- sort = null;
- }
- this.setState({ sortBy: sort, sortDirection: dir });
- };
-
- headerRenderer = (header: TableHeaderProps): ReactNode => {
- const dataKey = header.dataKey as any; // types say string, but it is number!
- const { data, sortBy, sortDirection } = this.state;
- const col = data!.columns[dataKey];
-
- return (
-
- {col.text} {sortBy === dataKey && }
-
- );
- };
-
- cellRenderer = (cell: TableCellProps) => {
- const { columnIndex, rowIndex } = cell;
- const row = this.state.data!.rows[rowIndex];
- const val = row[columnIndex];
- return this.renderCell(columnIndex, rowIndex, val);
- };
-
- render() {
- const { width, height, showHeader } = this.props;
- const { data } = this.props;
- if (!data) {
- return NO Data ;
- }
-
- return (
-
- {data.columns.map((col, index) => {
- return (
-
- );
- })}
-
- );
- }
-}
-
-export default TableXXXX;
diff --git a/packages/grafana-ui/src/components/Table/_Table.scss b/packages/grafana-ui/src/components/Table/_Table.scss
index f9cb0271561..22170ecf913 100644
--- a/packages/grafana-ui/src/components/Table/_Table.scss
+++ b/packages/grafana-ui/src/components/Table/_Table.scss
@@ -59,6 +59,7 @@
border-bottom: 2px solid $body-bg;
cursor: pointer;
+ white-space: nowrap;
color: $blue;
}
diff --git a/packages/grafana-ui/src/components/Table/examples.ts b/packages/grafana-ui/src/components/Table/examples.ts
index 026c5446f6b..9f05488d839 100644
--- a/packages/grafana-ui/src/components/Table/examples.ts
+++ b/packages/grafana-ui/src/components/Table/examples.ts
@@ -1,5 +1,5 @@
import { TableData } from '../../types/data';
-import { ColumnStyle } from './Table';
+import { ColumnStyle } from './TableCellBuilder';
import { getColorDefinitionByName } from '@grafana/ui';
diff --git a/packages/grafana-ui/src/utils/storybook/withFullSizeStory.tsx b/packages/grafana-ui/src/utils/storybook/withFullSizeStory.tsx
index c6efbee462f..98c3ccb2e7f 100644
--- a/packages/grafana-ui/src/utils/storybook/withFullSizeStory.tsx
+++ b/packages/grafana-ui/src/utils/storybook/withFullSizeStory.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { AutoSizer } from 'react-virtualized';
+/** This will add full size with & height properties */
export const withFullSizeStory = (component: React.ComponentType, props: any) => (
|