sortable class

This commit is contained in:
ryan
2019-03-06 14:39:04 -08:00
parent f7c1842a2b
commit 0d8384e05d
6 changed files with 150 additions and 81 deletions
+85 -49
View File
@@ -3,18 +3,19 @@ import _ from 'lodash';
import React, { Component, ReactNode } from 'react'; import React, { Component, ReactNode } from 'react';
// Types // Types
import { PanelProps } from '@grafana/ui/src/types'; import { PanelProps, ThemeContext } from '@grafana/ui';
import { Options } from './types'; import { Options } from './types';
import { Table, SortDirectionType, SortIndicator, Column, TableHeaderProps, TableCellProps } from 'react-virtualized'; import { Table, SortDirectionType, SortIndicator, Column, TableHeaderProps, TableCellProps } from 'react-virtualized';
import { TableRenderer } from './renderer'; import { TableRenderer } from './renderer';
import { SortedTableData } from './sortable';
interface Props extends PanelProps<Options> {} interface Props extends PanelProps<Options> {}
interface State { interface State {
sortBy: string; sortBy?: number; // but really is a number!
sortDirection: SortDirectionType; sortDirection?: SortDirectionType;
sortedList: any[]; data: SortedTableData;
} }
export class TablePanel extends Component<Props, State> { export class TablePanel extends Component<Props, State> {
@@ -22,55 +23,87 @@ export class TablePanel extends Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = {
sortBy: 'XX',
sortDirection: 'ASC',
sortedList: [],
};
const { panelData, options, replaceVariables } = this.props; const { panelData, options, replaceVariables } = this.props;
const theme = null; // TODO?
this.renderer = new TableRenderer(panelData.tableData, options, replaceVariables, theme); this.state = {
data: new SortedTableData(panelData.tableData),
};
this.renderer = new TableRenderer(options.styles, this.state.data, this._rowGetter, replaceVariables);
}
componentDidUpdate(prevProps: Props, prevState: State) {
const { panelData, options } = this.props;
const { sortBy, sortDirection } = this.state;
console.log('componentDidUpdate', this.props);
// Update the renderer if options change
if (options !== prevProps.options) {
console.log('Options Changed, update renderer', options);
this.renderer = new TableRenderer(options.styles, this.state.data, this._rowGetter, this.props.replaceVariables);
}
// Update the data when data or sort changes
if (panelData !== prevProps.panelData || sortBy !== prevState.sortBy || sortDirection !== prevState.sortDirection) {
const data = new SortedTableData(panelData.tableData, sortBy, sortDirection === 'DESC');
this.setState({ data });
console.log('Data Changed, update', data);
}
} }
_rowGetter = ({ index }) => { _rowGetter = ({ index }) => {
return this.props.panelData.tableData.rows[index]; return this.state.data.getRow(index);
}; };
_sort = ({ sortBy, sortDirection }) => { _sort = ({ sortBy }) => {
// const sortedList = this._sortList({sortBy, sortDirection}); let sortDirection = this.state.sortDirection;
if (sortBy !== this.state.sortBy) {
sortDirection = 'DESC';
} else if (sortDirection === 'DESC') {
sortDirection = 'ASC';
} else {
sortBy = null;
}
// this.setState({sortBy, sortDirection, sortedList}); // This will trigger sort via properties
console.log('TODO, sort!', sortBy, sortDirection); console.log('SORT', sortBy, typeof sortBy, sortDirection);
this.setState({ sortBy, sortDirection });
}; };
_headerRenderer = (header: TableHeaderProps): ReactNode => { _headerRenderer = (header: TableHeaderProps): ReactNode => {
const { sortBy, dataKey, sortDirection } = header; const dataKey = header.dataKey as any; // types say string, but it is number?
const tableData = this.props.panelData.tableData!; const { data, sortBy, sortDirection } = this.state;
const col = tableData.columns[dataKey];
const col = data.getInfo()[dataKey];
if (!col) { if (!col) {
return <div>??{dataKey}??</div>; return <div>??{dataKey}??</div>;
} }
const isSorted = sortBy === dataKey;
console.log('header SORT', sortBy, isSorted);
return ( return (
<div> <div>
{col.text} {sortBy === dataKey && <SortIndicator sortDirection={sortDirection} />} {col.text} {isSorted && <SortIndicator sortDirection={sortDirection} />}
</div> </div>
); );
}; };
_cellRenderer = (cell: TableCellProps) => { _cellRenderer = (cell: TableCellProps) => {
const { columnIndex, rowIndex } = cell; const { columnIndex, rowIndex } = cell;
const tableData = this.props.panelData.tableData!; const row = this.state.data.getRow(rowIndex);
const val = tableData.rows[rowIndex][columnIndex]; const val = row[columnIndex];
return this.renderer.renderCell(columnIndex, rowIndex, val); return this.renderer.renderCell(columnIndex, rowIndex, val);
}; };
render() { render() {
const { panelData, width, height, options } = this.props; const { panelData, width, height, options } = this.props;
const { showHeader } = options; const { showHeader } = options;
const { sortBy, sortDirection } = this.state; // const { sortBy, sortDirection } = this.state;
const { tableData } = panelData; const { tableData } = panelData;
if (!tableData || tableData.rows.length < 1) { if (!tableData || tableData.rows.length < 1) {
@@ -78,32 +111,35 @@ export class TablePanel extends Component<Props, State> {
} }
return ( return (
<Table <ThemeContext.Consumer>
disableHeader={!showHeader} {(
headerHeight={30} theme // ??? { this.renderer.setTheme(theme) }
height={height} ) => (
overscanRowCount={10} <Table
rowHeight={30} disableHeader={!showHeader}
rowGetter={this._rowGetter} headerHeight={30}
rowCount={tableData.rows.length} height={height}
sort={this._sort} overscanRowCount={10}
sortBy={sortBy} rowHeight={30}
sortDirection={sortDirection} rowGetter={this._rowGetter}
width={width} rowCount={tableData.rows.length}
> sort={this._sort}
{tableData.columns.map((col, index) => { width={width}
return ( >
<Column {tableData.columns.map((col, index) => {
key={index} return (
dataKey={index} <Column
headerRenderer={this._headerRenderer} key={index}
cellRenderer={this._cellRenderer} dataKey={index}
flexGrow={1} headerRenderer={this._headerRenderer}
width={60} cellRenderer={this._cellRenderer}
/> width={300}
); />
})} );
</Table> })}
</Table>
)}
</ThemeContext.Consumer>
); );
} }
} }
+15 -15
View File
@@ -7,14 +7,10 @@ import { sanitize } from 'app/core/utils/text';
// Types // Types
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import { import { getValueFormat, getColorFromHexRgbOrName, GrafanaThemeType, InterpolateFunction } from '@grafana/ui';
getValueFormat, import { Style } from './types';
getColorFromHexRgbOrName, import { SortedTableData } from './sortable';
GrafanaThemeType, import { Index } from 'react-virtualized';
InterpolateFunction,
TableData,
} from '@grafana/ui';
import { Options, Style } from './types';
type CellFormatter = (v: any, style: Style) => string; type CellFormatter = (v: any, style: Style) => string;
@@ -32,12 +28,13 @@ export class TableRenderer {
columns: ColumnInfo[]; columns: ColumnInfo[];
colorState: any; colorState: any;
theme?: GrafanaThemeType;
constructor( constructor(
private data: TableData, styles: Style[],
options: Options, data: SortedTableData,
private replaceVariables: InterpolateFunction, private rowGetter: (info: Index) => any[], // matches the table rowGetter
private theme?: GrafanaThemeType private replaceVariables: InterpolateFunction
) { ) {
this.colorState = {}; this.colorState = {};
@@ -45,9 +42,8 @@ export class TableRenderer {
this.columns = []; this.columns = [];
return; return;
} }
const { styles } = options;
this.columns = data.columns.map((col, index) => { this.columns = data.getInfo().map((col, index) => {
let title = col.text; let title = col.text;
let style: Style = null; let style: Style = null;
@@ -72,6 +68,10 @@ export class TableRenderer {
}); });
} }
setTheme(theme: GrafanaThemeType) {
this.theme = theme;
}
getColorForValue(value, style: Style) { getColorForValue(value, style: Style) {
if (!style.thresholds) { if (!style.thresholds) {
return null; return null;
@@ -222,7 +222,7 @@ export class TableRenderer {
renderRowVariables(rowIndex: number) { renderRowVariables(rowIndex: number) {
const scopedVars = {}; const scopedVars = {};
const row = this.data.rows[rowIndex]; const row = this.rowGetter({ index: rowIndex });
for (let i = 0; i < row.length; i++) { for (let i = 0; i < row.length; i++) {
scopedVars[`__cell_${i}`] = { value: row[i] }; scopedVars[`__cell_${i}`] = { value: row[i] };
} }
@@ -0,0 +1,41 @@
// Libraries
import _ from 'lodash';
import { TableData } from '@grafana/ui';
export class SortedTableData {
rows: any[];
constructor(private data: TableData, sortIndex?: number, reverse?: boolean) {
if (_.isNumber(sortIndex)) {
// Make a copy of all the rows
this.rows = this.data.rows.map((row, index) => {
return row;
});
this.rows.sort((a, b) => {
a = a[sortIndex];
b = b[sortIndex];
// Sort null or undefined separately from comparable values
return +(a == null) - +(b == null) || +(a > b) || -(a < b);
});
if (reverse) {
this.rows.reverse();
}
} else {
this.rows = data.rows;
}
}
getInfo(): any[] {
return this.data.columns;
}
getRow(index: number): any[] {
return this.rows[index];
}
getCount(): number {
return this.rows.length;
}
}
@@ -6,6 +6,7 @@ import { Options } from '../types';
import { PanelProps, LoadingState } from '@grafana/ui/src/types'; import { PanelProps, LoadingState } from '@grafana/ui/src/types';
import moment from 'moment'; import moment from 'moment';
import { TableRenderer } from '../renderer'; import { TableRenderer } from '../renderer';
import { SortedTableData } from '../sortable';
// TODO: this is commented out with *x* describe! // TODO: this is commented out with *x* describe!
// Essentially all the elements need to replace the <td> with <div> // Essentially all the elements need to replace the <td> with <div>
@@ -203,8 +204,10 @@ xdescribe('when rendering table', () => {
renderCounter: 1, renderCounter: 1,
options: panel, options: panel,
}; };
const theme = null; const data = new SortedTableData(table);
const renderer = new TableRenderer(table, panel, props.replaceVariables, theme); //panel, table, 'utc', sanitize, templateSrv); const rowGetter = ({ index }) => data.getRow(index);
const renderer = new TableRenderer(panel.styles, data, rowGetter, props.replaceVariables);
renderer.setTheme(null);
it('time column should be formated', () => { it('time column should be formated', () => {
const html = renderer.renderCell(0, 0, 1388556366666); const html = renderer.renderCell(0, 0, 1388556366666);
-11
View File
@@ -22,17 +22,6 @@ export interface Style {
preserveFormat?: boolean; preserveFormat?: boolean;
} }
export type CellFormatter = (v: any, style: Style) => string;
export interface ColumnInfo {
header: string;
accessor: string; // the field name
style?: Style;
hidden?: boolean;
formatter: CellFormatter;
filterable?: boolean;
}
export interface Options { export interface Options {
showHeader: boolean; showHeader: boolean;
styles: Style[]; styles: Style[];
+4 -4
View File
@@ -1,8 +1,8 @@
.ReactVirtualized__Table { // .ReactVirtualized__Table {
} // }
.ReactVirtualized__Table__Grid { // .ReactVirtualized__Table__Grid {
} // }
.ReactVirtualized__Table__headerRow { .ReactVirtualized__Table__headerRow {
font-weight: 700; font-weight: 700;