From 1c08717148dbe27521cf91b20ad6ea5a4b5db893 Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Fri, 16 Mar 2018 17:41:28 -0700 Subject: [PATCH] Sort table graphs in cell editor overlay --- .../components/GraphOptionsSortBy.js | 29 ----- .../components/GraphOptionsSortBy.tsx | 24 ++++ ui/src/dashboards/components/TableOptions.tsx | 90 ++++++++------ ui/src/dashboards/graphics/graph.js | 10 +- ui/src/shared/components/TableGraph.js | 44 +++---- .../components/GraphOptionsSortBy.test.tsx | 32 +++++ .../components/TableOptions.test.tsx | 113 ++++++++++++++++-- 7 files changed, 241 insertions(+), 101 deletions(-) delete mode 100644 ui/src/dashboards/components/GraphOptionsSortBy.js create mode 100644 ui/src/dashboards/components/GraphOptionsSortBy.tsx create mode 100644 ui/test/dashboards/components/GraphOptionsSortBy.test.tsx diff --git a/ui/src/dashboards/components/GraphOptionsSortBy.js b/ui/src/dashboards/components/GraphOptionsSortBy.js deleted file mode 100644 index d58a2adfb..000000000 --- a/ui/src/dashboards/components/GraphOptionsSortBy.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import Dropdown from 'shared/components/Dropdown' - -const GraphOptionsSortBy = ({sortByOptions, onChooseSortBy}) => -
- - -
- -const {arrayOf, func, shape, string} = PropTypes - -GraphOptionsSortBy.propTypes = { - sortByOptions: arrayOf( - shape({ - text: string.isRequired, - }).isRequired - ), - onChooseSortBy: func, -} - -export default GraphOptionsSortBy diff --git a/ui/src/dashboards/components/GraphOptionsSortBy.tsx b/ui/src/dashboards/components/GraphOptionsSortBy.tsx new file mode 100644 index 000000000..1b6089e97 --- /dev/null +++ b/ui/src/dashboards/components/GraphOptionsSortBy.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import Dropdown from 'src/shared/components/Dropdown' + +interface Props { + sortByOptions: any[] + onChooseSortBy: (any) => void + selected: string +} + +const GraphOptionsSortBy = ({sortByOptions, onChooseSortBy, selected} : Props) => ( +
+ + +
+) + +export default GraphOptionsSortBy diff --git a/ui/src/dashboards/components/TableOptions.tsx b/ui/src/dashboards/components/TableOptions.tsx index 1d83eb0ed..2d611f658 100644 --- a/ui/src/dashboards/components/TableOptions.tsx +++ b/ui/src/dashboards/components/TableOptions.tsx @@ -29,14 +29,12 @@ type Options = { columnNames: TableColumn[] } -type QueryConfig = { +interface QueryConfig { measurement: string - fields: [ - { - alias: string - value: string - } - ] + fields: { + alias: string + value: string + }[] } interface Props { @@ -51,33 +49,52 @@ export class TableOptions extends PureComponent { super(props) } - componentWillMount() { - const {queryConfigs, handleUpdateTableOptions, tableOptions} = this.props - const {columnNames} = tableOptions - const timeColumn = - (columnNames && columnNames.find(c => c.internalName === 'time')) || - TIME_COLUMN_DEFAULT + get columnNames() { + const {tableOptions: {columnNames}} = this.props - const columns = [ - timeColumn, - ..._.flatten( - queryConfigs.map(qc => { - const {measurement, fields} = qc - return fields.map(f => { - const internalName = `${measurement}.${f.alias}` - const existing = columnNames.find( - c => c.internalName === internalName - ) - return existing || {internalName, displayName: ''} - }) - }) - ), - ] - - handleUpdateTableOptions({...tableOptions, columnNames: columns}) + return columnNames || [] } - handleChooseSortBy = () => {} + get timeColumn() { + return (this.columnNames.find(c => c.internalName === 'time')) || TIME_COLUMN_DEFAULT + } + + get computedColumnNames() { + const {queryConfigs} = this.props + + const queryFields = _.flatten( + queryConfigs.map(({measurement, fields}) => { + return fields.map(({alias}) => { + const internalName = `${measurement}.${alias}` + const existing = this.columnNames.find( + c => c.internalName === internalName + ) + return existing || {internalName, displayName: ''} + }) + })) + + return [this.timeColumn, ...queryFields] + } + + componentWillMount() { + const {handleUpdateTableOptions, tableOptions} = this.props + handleUpdateTableOptions({...tableOptions, columnNames: this.computedColumnNames}) + } + + handleToggleSingleStatType = () => {} + + handleAddThreshold = () => {} + + handleDeleteThreshold = () => () => {} + + handleChooseColor = () => () => {} + + handleChooseSortBy = option => { + const {tableOptions, handleUpdateTableOptions} = this.props + const sortBy = {displayName: option.text, internalName: option.key} + + handleUpdateTableOptions({...tableOptions, sortBy}) + } handleTimeFormatChange = timeFormat => { const {tableOptions, handleUpdateTableOptions} = this.props @@ -101,15 +118,15 @@ export class TableOptions extends PureComponent { const { tableOptions: {timeFormat, columnNames: columns}, onResetFocus, + tableOptions, } = this.props const TimeAxis = 'vertical' - const tableSortByOptions = [ - 'cpu.mean_usage_system', - 'cpu.mean_usage_idle', - 'cpu.mean_usage_user', - ].map(col => ({text: col})) + const tableSortByOptions = this.computedColumnNames.map(col => ({ + text: col.displayName || col.internalName, + key: col.internalName, + })) return ( { onToggleTimeAxis={this.handleToggleTimeAxis} /> diff --git a/ui/src/dashboards/graphics/graph.js b/ui/src/dashboards/graphics/graph.js index 4038517bb..0247b6ed4 100644 --- a/ui/src/dashboards/graphics/graph.js +++ b/ui/src/dashboards/graphics/graph.js @@ -546,9 +546,9 @@ export const GRAPH_TYPES = [ graphic: GRAPH_SVGS.gauge, }, // FEATURE FLAG for Table-Graph - // { - // type: 'table', - // menuOption: 'Table', - // graphic: GRAPH_SVGS.table, - // }, + { + type: 'table', + menuOption: 'Table', + graphic: GRAPH_SVGS.table, + }, ] diff --git a/ui/src/shared/components/TableGraph.js b/ui/src/shared/components/TableGraph.js index 32203cd27..090931227 100644 --- a/ui/src/shared/components/TableGraph.js +++ b/ui/src/shared/components/TableGraph.js @@ -2,6 +2,7 @@ import React, {Component} from 'react' import PropTypes from 'prop-types' import _ from 'lodash' import classnames from 'classnames' +import isEmpty from 'lodash/isEmpty' import {MultiGrid} from 'react-virtualized' import moment from 'moment' @@ -16,24 +17,25 @@ import { } from 'src/shared/constants/tableGraph' import {generateThresholdsListHexs} from 'shared/constants/colorOperations' -const isEmpty = data => data.length <= 1 - class TableGraph extends Component { constructor(props) { super(props) this.state = { + data: [[]], hoveredColumnIndex: NULL_COLUMN_INDEX, hoveredRowIndex: NULL_ROW_INDEX, + columnIndex: -1, } } - componentWillMount() { - this._data = [[]] - } - - componentWillUpdate(nextProps) { + componentWillReceiveProps(nextProps) { const {data} = timeSeriesToTableGraph(nextProps.data) - this._data = data + + const {tableOptions: {sortBy: {internalName}}} = nextProps + const columnIndex = _.indexOf(data[0], internalName) + + const sortedData = _.sortBy(_.drop(data, 1), columnIndex) + this.setState({data: [data[0], ...sortedData], columnIndex}) } calcHoverTimeRow = (data, hoverTime) => @@ -45,7 +47,8 @@ class TableGraph extends Component { handleHover = (columnIndex, rowIndex) => () => { if (this.props.onSetHoverTime) { - this.props.onSetHoverTime(this._data[rowIndex][0].toString()) + const {data} = this.state + this.props.onSetHoverTime(data[rowIndex][0].toString()) this.setState({ hoveredColumnIndex: columnIndex, hoveredRowIndex: rowIndex, @@ -63,20 +66,17 @@ class TableGraph extends Component { } } - cellRenderer = ({columnIndex, rowIndex, key, parent, style}) => { - const data = this._data - const {hoveredColumnIndex, hoveredRowIndex} = this.state + cellRenderer = ({columnIndex, rowIndex, key, style, parent}) => { + const {hoveredColumnIndex, hoveredRowIndex, data} = this.state const {colors} = this.props const columnCount = _.get(data, ['0', 'length'], 0) const rowCount = data.length const {tableOptions} = this.props - const timeFormat = tableOptions - ? tableOptions.timeFormat - : TIME_FORMAT_DEFAULT - const columnNames = tableOptions - ? tableOptions.columnNames - : [TIME_COLUMN_DEFAULT] + const timeFormat = _.get(tableOptions, 'timeFormat', TIME_FORMAT_DEFAULT) + const columnNames = _.get(tableOptions, 'columnNames', [ + TIME_COLUMN_DEFAULT, + ]) const isFixedRow = rowIndex === 0 && columnIndex > 0 const isFixedColumn = rowIndex > 0 && columnIndex === 0 @@ -139,14 +139,17 @@ class TableGraph extends Component { render() { const {hoveredColumnIndex, hoveredRowIndex} = this.state const {hoverTime, tableOptions, colors} = this.props - const data = this._data + const {data} = this.state const columnCount = _.get(data, ['0', 'length'], 0) const rowCount = data.length const COLUMN_WIDTH = 300 const ROW_HEIGHT = 30 const tableWidth = this.gridContainer ? this.gridContainer.clientWidth : 0 const tableHeight = this.gridContainer ? this.gridContainer.clientHeight : 0 - const hoverTimeRow = this.calcHoverTimeRow(data, hoverTime) + const hoverTimeRow = + hoveredRowIndex === NULL_ROW_INDEX + ? this.calcHoverTimeRow(data, hoverTime) + : hoveredRowIndex return (
{}, + selected: '' +} + +const setup = (override = {}) => { + const props = {...defaultProps, ...override} + const wrapper = shallow() + + return {wrapper, props} +} + +describe('Dashboards.Components.GraphOptionsSortBy', () => { + describe('rendering', () => { + it('renders component', () => { + const {wrapper} = setup() + + const dropdown = wrapper.find(Dropdown) + const label = wrapper.find('label') + + expect(dropdown.exists()).toBe(true) + expect(label.exists()).toBe(true) + }) + }) +}) diff --git a/ui/test/dashboards/components/TableOptions.test.tsx b/ui/test/dashboards/components/TableOptions.test.tsx index c9242616c..f9d70a91f 100644 --- a/ui/test/dashboards/components/TableOptions.test.tsx +++ b/ui/test/dashboards/components/TableOptions.test.tsx @@ -13,18 +13,51 @@ import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeTo import {shallow} from 'enzyme' +const queryConfigs = [ + { + measurement: "dev", + fields: [ + { + alias: "boom", + value: "test" + }, + { + alias: "again", + value: "again" + }, + ] + }, + { + measurement: "prod", + fields: [ + { + alias: "boom", + value: "test" + }, + { + alias: "again", + value: "again" + }, + ] + } +] + +const defaultProps = { + queryConfigs: queryConfigs, + handleUpdateTableOptions: () => {}, + tableOptions: { + timeFormat: '', + verticalTimeAxis: true, + sortBy: {internalName: '', displayName: ''}, + wrapping: '', + columnNames: [], + }, + onResetFocus: () => {}, +} + const setup = (override = {}) => { const props = { - queryConfigs: [], - handleUpdateTableOptions: () => {}, - tableOptions: { - timeFormat: '', - verticalTimeAxis: true, - sortBy: {internalName: '', displayName: ''}, - wrapping: '', - columnNames: [], - }, - onResetFocus: () => {}, + ...defaultProps, ...override, } @@ -34,9 +67,65 @@ const setup = (override = {}) => { } describe('Dashboards.Components.TableOptions', () => { + describe('getters', () => { + describe('computedColumnNames', () => { + it('returns the correct column names', () => { + const instance = new TableOptions(defaultProps) + + const expected = [ + { + displayName: '', + internalName: 'time', + }, + { + displayName: '', + internalName: 'dev.boom', + }, + { + displayName: '', + internalName: 'dev.again', + }, + { + displayName: '', + internalName: 'prod.boom', + }, + { + displayName: '', + internalName: 'prod.again', + }, + ] + + expect(instance.computedColumnNames).toEqual(expected) + }) + }) + }) + describe('rendering', () => { it('should render all components', () => { - const {wrapper} = setup() + const queryConfigs = [ + { + measurement: "dev", + fields: [ + { + alias: "boom", + value: "test" + }, + ] + } + ] + + const expectedSortOptions = [ + { + key: 'time', + text: 'time', + }, + { + key: 'dev.boom', + text: 'dev.boom', + }, + ] + + const {wrapper} = setup({ queryConfigs }) const fancyScrollbar = wrapper.find(FancyScrollbar) const graphOptionsTimeFormat = wrapper.find(GraphOptionsTimeFormat) const graphOptionsTimeAxis = wrapper.find(GraphOptionsTimeAxis) @@ -48,6 +137,8 @@ describe('Dashboards.Components.TableOptions', () => { const thresholdsList = wrapper.find(ThresholdsList) const thresholdsListTypeToggle = wrapper.find(ThresholdsListTypeToggle) + expect(graphOptionsSortBy.props().sortByOptions).toEqual(expectedSortOptions) + expect(fancyScrollbar.exists()).toBe(true) expect(graphOptionsTimeFormat.exists()).toBe(true) expect(graphOptionsTimeAxis.exists()).toBe(true)