Sort table graphs in cell editor overlay
parent
0587c94345
commit
1c08717148
|
@ -1,29 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import Dropdown from 'shared/components/Dropdown'
|
|
||||||
|
|
||||||
const GraphOptionsSortBy = ({sortByOptions, onChooseSortBy}) =>
|
|
||||||
<div className="form-group col-xs-6">
|
|
||||||
<label>Sort By</label>
|
|
||||||
<Dropdown
|
|
||||||
items={sortByOptions}
|
|
||||||
selected={sortByOptions[0].text}
|
|
||||||
buttonColor="btn-default"
|
|
||||||
buttonSize="btn-sm"
|
|
||||||
className="dropdown-stretch"
|
|
||||||
onChoose={onChooseSortBy}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
const {arrayOf, func, shape, string} = PropTypes
|
|
||||||
|
|
||||||
GraphOptionsSortBy.propTypes = {
|
|
||||||
sortByOptions: arrayOf(
|
|
||||||
shape({
|
|
||||||
text: string.isRequired,
|
|
||||||
}).isRequired
|
|
||||||
),
|
|
||||||
onChooseSortBy: func,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GraphOptionsSortBy
|
|
|
@ -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) => (
|
||||||
|
<div className="form-group col-xs-6">
|
||||||
|
<label>Sort By</label>
|
||||||
|
<Dropdown
|
||||||
|
items={sortByOptions}
|
||||||
|
selected={selected}
|
||||||
|
buttonColor="btn-default"
|
||||||
|
buttonSize="btn-sm"
|
||||||
|
className="dropdown-stretch"
|
||||||
|
onChoose={onChooseSortBy}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default GraphOptionsSortBy
|
|
@ -29,14 +29,12 @@ type Options = {
|
||||||
columnNames: TableColumn[]
|
columnNames: TableColumn[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryConfig = {
|
interface QueryConfig {
|
||||||
measurement: string
|
measurement: string
|
||||||
fields: [
|
fields: {
|
||||||
{
|
alias: string
|
||||||
alias: string
|
value: string
|
||||||
value: string
|
}[]
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -51,33 +49,52 @@ export class TableOptions extends PureComponent<Props, {}> {
|
||||||
super(props)
|
super(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
get columnNames() {
|
||||||
const {queryConfigs, handleUpdateTableOptions, tableOptions} = this.props
|
const {tableOptions: {columnNames}} = this.props
|
||||||
const {columnNames} = tableOptions
|
|
||||||
const timeColumn =
|
|
||||||
(columnNames && columnNames.find(c => c.internalName === 'time')) ||
|
|
||||||
TIME_COLUMN_DEFAULT
|
|
||||||
|
|
||||||
const columns = [
|
return columnNames || []
|
||||||
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})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 => {
|
handleTimeFormatChange = timeFormat => {
|
||||||
const {tableOptions, handleUpdateTableOptions} = this.props
|
const {tableOptions, handleUpdateTableOptions} = this.props
|
||||||
|
@ -101,15 +118,15 @@ export class TableOptions extends PureComponent<Props, {}> {
|
||||||
const {
|
const {
|
||||||
tableOptions: {timeFormat, columnNames: columns},
|
tableOptions: {timeFormat, columnNames: columns},
|
||||||
onResetFocus,
|
onResetFocus,
|
||||||
|
tableOptions,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const TimeAxis = 'vertical'
|
const TimeAxis = 'vertical'
|
||||||
|
|
||||||
const tableSortByOptions = [
|
const tableSortByOptions = this.computedColumnNames.map(col => ({
|
||||||
'cpu.mean_usage_system',
|
text: col.displayName || col.internalName,
|
||||||
'cpu.mean_usage_idle',
|
key: col.internalName,
|
||||||
'cpu.mean_usage_user',
|
}))
|
||||||
].map(col => ({text: col}))
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FancyScrollbar
|
<FancyScrollbar
|
||||||
|
@ -128,6 +145,7 @@ export class TableOptions extends PureComponent<Props, {}> {
|
||||||
onToggleTimeAxis={this.handleToggleTimeAxis}
|
onToggleTimeAxis={this.handleToggleTimeAxis}
|
||||||
/>
|
/>
|
||||||
<GraphOptionsSortBy
|
<GraphOptionsSortBy
|
||||||
|
selected={tableOptions.sortBy.internalName || TIME_COLUMN_DEFAULT.internalName}
|
||||||
sortByOptions={tableSortByOptions}
|
sortByOptions={tableSortByOptions}
|
||||||
onChooseSortBy={this.handleChooseSortBy}
|
onChooseSortBy={this.handleChooseSortBy}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -546,9 +546,9 @@ export const GRAPH_TYPES = [
|
||||||
graphic: GRAPH_SVGS.gauge,
|
graphic: GRAPH_SVGS.gauge,
|
||||||
},
|
},
|
||||||
// FEATURE FLAG for Table-Graph
|
// FEATURE FLAG for Table-Graph
|
||||||
// {
|
{
|
||||||
// type: 'table',
|
type: 'table',
|
||||||
// menuOption: 'Table',
|
menuOption: 'Table',
|
||||||
// graphic: GRAPH_SVGS.table,
|
graphic: GRAPH_SVGS.table,
|
||||||
// },
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React, {Component} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
import isEmpty from 'lodash/isEmpty'
|
||||||
|
|
||||||
import {MultiGrid} from 'react-virtualized'
|
import {MultiGrid} from 'react-virtualized'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
@ -16,24 +17,25 @@ import {
|
||||||
} from 'src/shared/constants/tableGraph'
|
} from 'src/shared/constants/tableGraph'
|
||||||
import {generateThresholdsListHexs} from 'shared/constants/colorOperations'
|
import {generateThresholdsListHexs} from 'shared/constants/colorOperations'
|
||||||
|
|
||||||
const isEmpty = data => data.length <= 1
|
|
||||||
|
|
||||||
class TableGraph extends Component {
|
class TableGraph extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
|
data: [[]],
|
||||||
hoveredColumnIndex: NULL_COLUMN_INDEX,
|
hoveredColumnIndex: NULL_COLUMN_INDEX,
|
||||||
hoveredRowIndex: NULL_ROW_INDEX,
|
hoveredRowIndex: NULL_ROW_INDEX,
|
||||||
|
columnIndex: -1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillReceiveProps(nextProps) {
|
||||||
this._data = [[]]
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUpdate(nextProps) {
|
|
||||||
const {data} = timeSeriesToTableGraph(nextProps.data)
|
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) =>
|
calcHoverTimeRow = (data, hoverTime) =>
|
||||||
|
@ -45,7 +47,8 @@ class TableGraph extends Component {
|
||||||
|
|
||||||
handleHover = (columnIndex, rowIndex) => () => {
|
handleHover = (columnIndex, rowIndex) => () => {
|
||||||
if (this.props.onSetHoverTime) {
|
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({
|
this.setState({
|
||||||
hoveredColumnIndex: columnIndex,
|
hoveredColumnIndex: columnIndex,
|
||||||
hoveredRowIndex: rowIndex,
|
hoveredRowIndex: rowIndex,
|
||||||
|
@ -63,20 +66,17 @@ class TableGraph extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cellRenderer = ({columnIndex, rowIndex, key, parent, style}) => {
|
cellRenderer = ({columnIndex, rowIndex, key, style, parent}) => {
|
||||||
const data = this._data
|
const {hoveredColumnIndex, hoveredRowIndex, data} = this.state
|
||||||
const {hoveredColumnIndex, hoveredRowIndex} = this.state
|
|
||||||
const {colors} = this.props
|
const {colors} = this.props
|
||||||
|
|
||||||
const columnCount = _.get(data, ['0', 'length'], 0)
|
const columnCount = _.get(data, ['0', 'length'], 0)
|
||||||
const rowCount = data.length
|
const rowCount = data.length
|
||||||
const {tableOptions} = this.props
|
const {tableOptions} = this.props
|
||||||
const timeFormat = tableOptions
|
const timeFormat = _.get(tableOptions, 'timeFormat', TIME_FORMAT_DEFAULT)
|
||||||
? tableOptions.timeFormat
|
const columnNames = _.get(tableOptions, 'columnNames', [
|
||||||
: TIME_FORMAT_DEFAULT
|
TIME_COLUMN_DEFAULT,
|
||||||
const columnNames = tableOptions
|
])
|
||||||
? tableOptions.columnNames
|
|
||||||
: [TIME_COLUMN_DEFAULT]
|
|
||||||
|
|
||||||
const isFixedRow = rowIndex === 0 && columnIndex > 0
|
const isFixedRow = rowIndex === 0 && columnIndex > 0
|
||||||
const isFixedColumn = rowIndex > 0 && columnIndex === 0
|
const isFixedColumn = rowIndex > 0 && columnIndex === 0
|
||||||
|
@ -139,14 +139,17 @@ class TableGraph extends Component {
|
||||||
render() {
|
render() {
|
||||||
const {hoveredColumnIndex, hoveredRowIndex} = this.state
|
const {hoveredColumnIndex, hoveredRowIndex} = this.state
|
||||||
const {hoverTime, tableOptions, colors} = this.props
|
const {hoverTime, tableOptions, colors} = this.props
|
||||||
const data = this._data
|
const {data} = this.state
|
||||||
const columnCount = _.get(data, ['0', 'length'], 0)
|
const columnCount = _.get(data, ['0', 'length'], 0)
|
||||||
const rowCount = data.length
|
const rowCount = data.length
|
||||||
const COLUMN_WIDTH = 300
|
const COLUMN_WIDTH = 300
|
||||||
const ROW_HEIGHT = 30
|
const ROW_HEIGHT = 30
|
||||||
const tableWidth = this.gridContainer ? this.gridContainer.clientWidth : 0
|
const tableWidth = this.gridContainer ? this.gridContainer.clientWidth : 0
|
||||||
const tableHeight = this.gridContainer ? this.gridContainer.clientHeight : 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 (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -172,6 +175,7 @@ class TableGraph extends Component {
|
||||||
columnNames={
|
columnNames={
|
||||||
tableOptions ? tableOptions.columnNames : [TIME_COLUMN_DEFAULT]
|
tableOptions ? tableOptions.columnNames : [TIME_COLUMN_DEFAULT]
|
||||||
}
|
}
|
||||||
|
columnIndex={columnIndex}
|
||||||
scrollToRow={hoverTimeRow}
|
scrollToRow={hoverTimeRow}
|
||||||
cellRenderer={this.cellRenderer}
|
cellRenderer={this.cellRenderer}
|
||||||
hoveredColumnIndex={hoveredColumnIndex}
|
hoveredColumnIndex={hoveredColumnIndex}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {shallow} from 'enzyme'
|
||||||
|
|
||||||
|
import GraphOptionsSortBy from 'src/dashboards/components/GraphOptionsSortBy'
|
||||||
|
import Dropdown from 'src/shared/components/Dropdown'
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
sortByOptions: [],
|
||||||
|
onChooseSortBy: () => {},
|
||||||
|
selected: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const setup = (override = {}) => {
|
||||||
|
const props = {...defaultProps, ...override}
|
||||||
|
const wrapper = shallow(<GraphOptionsSortBy {...props} />)
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -13,18 +13,51 @@ import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeTo
|
||||||
|
|
||||||
import {shallow} from 'enzyme'
|
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 setup = (override = {}) => {
|
||||||
const props = {
|
const props = {
|
||||||
queryConfigs: [],
|
...defaultProps,
|
||||||
handleUpdateTableOptions: () => {},
|
|
||||||
tableOptions: {
|
|
||||||
timeFormat: '',
|
|
||||||
verticalTimeAxis: true,
|
|
||||||
sortBy: {internalName: '', displayName: ''},
|
|
||||||
wrapping: '',
|
|
||||||
columnNames: [],
|
|
||||||
},
|
|
||||||
onResetFocus: () => {},
|
|
||||||
...override,
|
...override,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,9 +67,65 @@ const setup = (override = {}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Dashboards.Components.TableOptions', () => {
|
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', () => {
|
describe('rendering', () => {
|
||||||
it('should render all components', () => {
|
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 fancyScrollbar = wrapper.find(FancyScrollbar)
|
||||||
const graphOptionsTimeFormat = wrapper.find(GraphOptionsTimeFormat)
|
const graphOptionsTimeFormat = wrapper.find(GraphOptionsTimeFormat)
|
||||||
const graphOptionsTimeAxis = wrapper.find(GraphOptionsTimeAxis)
|
const graphOptionsTimeAxis = wrapper.find(GraphOptionsTimeAxis)
|
||||||
|
@ -48,6 +137,8 @@ describe('Dashboards.Components.TableOptions', () => {
|
||||||
const thresholdsList = wrapper.find(ThresholdsList)
|
const thresholdsList = wrapper.find(ThresholdsList)
|
||||||
const thresholdsListTypeToggle = wrapper.find(ThresholdsListTypeToggle)
|
const thresholdsListTypeToggle = wrapper.find(ThresholdsListTypeToggle)
|
||||||
|
|
||||||
|
expect(graphOptionsSortBy.props().sortByOptions).toEqual(expectedSortOptions)
|
||||||
|
|
||||||
expect(fancyScrollbar.exists()).toBe(true)
|
expect(fancyScrollbar.exists()).toBe(true)
|
||||||
expect(graphOptionsTimeFormat.exists()).toBe(true)
|
expect(graphOptionsTimeFormat.exists()).toBe(true)
|
||||||
expect(graphOptionsTimeAxis.exists()).toBe(true)
|
expect(graphOptionsTimeAxis.exists()).toBe(true)
|
||||||
|
|
Loading…
Reference in New Issue