Skip to content

Commit

Permalink
[TSVB] Refactor Table Visualization
Browse files Browse the repository at this point in the history
- Use partitioning to get all rows
- Add sorting to all columns
- Add pagination controls
- Convert table to use EUI
- Add Kibana config to control the total limit of rows
- Add Kibana config to control the partition size
  • Loading branch information
simianhacker committed Dec 22, 2017
1 parent 1d92807 commit 9f41f7e
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 203 deletions.
4 changes: 3 additions & 1 deletion src/core_plugins/metrics/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export default function (kibana) {
return Joi.object({
enabled: Joi.boolean().default(true),
chartResolution: Joi.number().default(150),
minimumBucketSize: Joi.number().default(10)
minimumBucketSize: Joi.number().default(10),
tablePartitionSize: Joi.number().default(50),
tableTotalSize: Joi.number().default(10000)
}).default();
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,6 @@ class TablePanelConfig extends Component {
onChange={handleTextChange('pivot_label')}
value={model.pivot_label}
/>
<label className="vis_editor__label" htmlFor={htmlId('pivotRowsInput')}>Rows</label>
<input
id={htmlId('pivotRowsInput')}
className="vis_editor__input-number"
type="number"
onChange={handleTextChange('pivot_rows')}
value={model.pivot_rows}
/>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import createTextHandler from '../../lib/create_text_handler';
import createAggRowRender from '../../lib/create_agg_row_render';
import { createUpDownHandler } from '../../lib/sort_keyhandler';

function TopNSeries(props) {
function TableSeries(props) {
const {
model,
onAdd,
Expand Down Expand Up @@ -137,7 +137,7 @@ function TopNSeries(props) {
);
}

TopNSeries.propTypes = {
TableSeries.propTypes = {
className: PropTypes.string,
disableAdd: PropTypes.bool,
disableDelete: PropTypes.bool,
Expand All @@ -161,4 +161,4 @@ TopNSeries.propTypes = {
visible: PropTypes.bool
};

export default TopNSeries;
export default TableSeries;
207 changes: 121 additions & 86 deletions src/core_plugins/metrics/public/components/vis_types/table/vis.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ticFormatter from '../../lib/tick_formatter';
import calculateLabel from '../../../../common/calculate_label';
import { isSortable } from './is_sortable';
import Tooltip from '../../tooltip';
import replaceVars from '../../lib/replace_vars';

const STATE_KEY = 'table.sort';

import {
Pager,
EuiSpacer,
EuiTable,
EuiTableBody,
EuiTableHeader,
EuiTableHeaderCell,
EuiTablePagination,
EuiTableRow,
EuiTableRowCell,
} from '@elastic/eui';

function getColor(rules, colorKey, value) {
let color;
if (rules) {
Expand All @@ -26,8 +38,35 @@ class TableVis extends Component {
constructor(props) {
super(props);
this.renderRow = this.renderRow.bind(this);
this.state = {
itemsPerPage: 10,
sort: props.uiState.get(STATE_KEY, {
column: '_default_',
order: 'asc'
})
};
this.pager = new Pager(props.visData.series.length, this.state.itemsPerPage);
this.state.firstItemIndex = this.pager.getFirstItemIndex();
this.state.lastItemIndex = this.pager.getLastItemIndex();
}

onChangeItemsPerPage = itemsPerPage => {
this.pager.setItemsPerPage(itemsPerPage);
this.setState({
itemsPerPage,
firstItemIndex: this.pager.getFirstItemIndex(),
lastItemIndex: this.pager.getLastItemIndex(),
});
}

onChangePage = pageIndex => {
this.pager.goToPageIndex(pageIndex);
this.setState({
firstItemIndex: this.pager.getFirstItemIndex(),
lastItemIndex: this.pager.getLastItemIndex(),
});
};

renderRow(row) {
const { model } = this.props;
const rowId = row.key;
Expand All @@ -52,137 +91,133 @@ class TableVis extends Component {
}
const style = { color: getColor(column.color_rules, 'text', item.last) };
return (
<td key={`${rowId}-${item.id}`} className="tsvb-table__value" style={style}>
<span className="tsvb-table__value-display">{ value }</span>
<EuiTableRowCell
key={`${rowId}-${item.id}`}
textOnly={false}
align="right"
>
<span style={style} className="tsvb-table__value-display">{ value }</span>
{trend}
</td>
</EuiTableRowCell>
);
});
return (
<tr key={rowId}>
<td className="tsvb-table__fieldName">{rowDisplay}</td>
<EuiTableRow key={rowId}>
<EuiTableRowCell
textOnly={false}
align="left"
>
{rowDisplay}
</EuiTableRowCell>
{columns}
</tr>
</EuiTableRow>
);
}

renderHeader() {
const { model, uiState, onUiState } = this.props;
const stateKey = `${model.type}.sort`;
const sort = uiState.get(stateKey, {
column: '_default_',
order: 'asc'
});
const { model, onUiState } = this.props;
const { sort } = this.state;
const columns = model.series.map(item => {
const metric = _.last(item.metrics);
const label = item.label || calculateLabel(metric, item.metrics);
const handleClick = () => {
if (!isSortable(metric)) return;
let order;
let order = sort.order;
if (sort.column === item.id) {
order = sort.order === 'asc' ? 'desc' : 'asc';
} else {
order = 'asc';
}
onUiState(stateKey, { column: item.id, order });
const nextSort = { column: item.id, order };
this.setState({ sort: nextSort }, () => {
onUiState(STATE_KEY, nextSort);
});
};
let sortComponent;
if (isSortable(metric)) {
let sortIcon;
if (sort.column === item.id) {
sortIcon = sort.order === 'asc' ? 'sort-asc' : 'sort-desc';
} else {
sortIcon = 'sort';
}
sortComponent = (
<i className={`fa fa-${sortIcon}`} />
);
}
let headerContent = (
<span>{label} {sortComponent}</span>
);
if (!isSortable(metric)) {
headerContent = (
<Tooltip text="This Column is Not Sortable">{headerContent}</Tooltip>
);
}

const isSortAscending = sort.order === 'asc';
return (
<th
className="tsvb-table__columnName"
onClick={handleClick}
<EuiTableHeaderCell
key={item.id}
scope="col"
align="right"
onSort={handleClick}
isSorted={sort.column === item.id}
isSortAscending={isSortAscending}
>
{headerContent}
</th>
{label}
</EuiTableHeaderCell>
);
});

const label = model.pivot_label || model.pivot_field || model.pivot_id;
let sortIcon;
if (sort.column === '_default_') {
sortIcon = sort.order === 'asc' ? 'sort-asc' : 'sort-desc';
} else {
sortIcon = 'sort';
}
const sortComponent = (
<i className={`fa fa-${sortIcon}`} />
);
const isSortAscending = sort.order === 'asc';
const handleSortClick = () => {
let order;
let order = sort.order;
if (sort.column === '_default_') {
order = sort.order === 'asc' ? 'desc' : 'asc';
} else {
order = 'asc';
}
onUiState(stateKey, { column: '_default_', order });
const nextSort = { column: '_default_', order };
this.setState({ sort: nextSort }, () => {
onUiState(STATE_KEY, nextSort);
});
};

return (
<tr>
<th scope="col" onClick={handleSortClick}>{label} {sortComponent}</th>
{ columns }
</tr>
<EuiTableHeader>
<EuiTableHeaderCell
align="left"
onSort={handleSortClick}
isSorted={sort.column === '_default_'}
isSortAscending={isSortAscending}
>
{label}
</EuiTableHeaderCell>
{columns}
</EuiTableHeader>
);
}

sortIdentity = (item) => {
const { sort } = this.state;
if (sort.column === '_default_') return item.key;
const column = item.series.find(i => i.id === sort.column);
if (column) return column.last;
}

render() {
const { visData, model } = this.props;
const { firstItemIndex, lastItemIndex, itemsPerPage } = this.state;
const header = this.renderHeader();
let rows;
let reversedClass = '';

if (this.props.reversed) {
reversedClass = 'reversed';
}

let rows;
if (_.isArray(visData.series) && visData.series.length) {
rows = visData.series.map(this.renderRow);
const series = this.state.sort.order === 'asc' ?
_.sortBy(visData.series, this.sortIdentity) :
_.sortBy(visData.series, this.sortIdentity).reverse();
rows = series.slice(firstItemIndex, lastItemIndex).map(this.renderRow);
} else {
let message = 'No results available.';
if (!model.pivot_id) {
message += ' You must choose a group by field for this visualization.';
}
rows = (
<tr>
<td
className="tsvb-table__noResults"
colSpan={model.series.length + 1}
>
{message}
</td>
</tr>
<EuiTableRow colSpan={model.series.length + 1}>
<EuiTableRowCell>{message}</EuiTableRowCell>
</EuiTableRow>
);
}
return(
<div className={`dashboard__visualization ${reversedClass}`}>
<table className="table">
<thead>
{header}
</thead>
<tbody>
<div className="dashboard__tableVisualization">
<EuiTable>
{header}
<EuiTableBody>
{rows}
</tbody>
</table>
</EuiTableBody>
</EuiTable>
<EuiSpacer size="m" />
<EuiTablePagination
activePage={this.pager.getCurrentPageIndex()}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={[5, 10, 20]}
pageCount={this.pager.getTotalPages()}
onChangeItemsPerPage={this.onChangeItemsPerPage}
onChangePage={this.onChangePage}
/>
</div>
);
}
Expand Down
10 changes: 10 additions & 0 deletions src/core_plugins/metrics/public/less/misc.less
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@
left: 0;
}

.dashboard__tableVisualization {
flex: 1 0 auto;
padding: 10px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}

.dashboard__visualization-title {
color: rgba(0,0,0,0.6);
font-weight: 500;
Expand Down
Loading

0 comments on commit 9f41f7e

Please sign in to comment.