Merge pull request #2996 from influxdata/feature/table-graph-series-renaming
Table Graph Series/Column renamingpull/10616/head
commit
27168d8c14
|
@ -1,34 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import InputClickToEdit from 'shared/components/InputClickToEdit'
|
|
||||||
|
|
||||||
const GraphOptionsCustomizableColumn = ({
|
|
||||||
originalColumnName,
|
|
||||||
newColumnName,
|
|
||||||
onColumnRename,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="column-controls--section">
|
|
||||||
<div className="column-controls--label">
|
|
||||||
{originalColumnName}
|
|
||||||
</div>
|
|
||||||
<InputClickToEdit
|
|
||||||
value={newColumnName}
|
|
||||||
wrapperClass="column-controls-input"
|
|
||||||
onBlur={onColumnRename}
|
|
||||||
placeholder="Rename..."
|
|
||||||
appearAsNormalInput={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const {func, string} = PropTypes
|
|
||||||
|
|
||||||
GraphOptionsCustomizableColumn.propTypes = {
|
|
||||||
originalColumnName: string,
|
|
||||||
newColumnName: string,
|
|
||||||
onColumnRename: func,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GraphOptionsCustomizableColumn
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
|
||||||
|
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
|
||||||
|
|
||||||
|
type Column = {
|
||||||
|
internalName: string
|
||||||
|
displayName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
internalName: string
|
||||||
|
displayName: string
|
||||||
|
onColumnRename: (column: Column) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
class GraphOptionsCustomizableColumn extends PureComponent<Props, {}> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.handleColumnRename = this.handleColumnRename.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleColumnRename(rename) {
|
||||||
|
const {onColumnRename, internalName} = this.props
|
||||||
|
onColumnRename({internalName, displayName: rename})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {internalName, displayName} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="column-controls--section">
|
||||||
|
<div className="column-controls--label">
|
||||||
|
{internalName}
|
||||||
|
</div>
|
||||||
|
<InputClickToEdit
|
||||||
|
value={displayName}
|
||||||
|
wrapperClass="column-controls-input"
|
||||||
|
onBlur={this.handleColumnRename}
|
||||||
|
placeholder="Rename..."
|
||||||
|
appearAsNormalInput={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GraphOptionsCustomizableColumn
|
|
@ -1,10 +1,22 @@
|
||||||
import React from 'react'
|
import React, {SFC} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import GraphOptionsCustomizableColumn from 'src/dashboards/components/GraphOptionsCustomizableColumn'
|
import GraphOptionsCustomizableColumn from 'src/dashboards/components/GraphOptionsCustomizableColumn'
|
||||||
import uuid from 'uuid'
|
import uuid from 'uuid'
|
||||||
|
|
||||||
const GraphOptionsCustomizeColumns = ({columns, onColumnRename}) => {
|
type Column = {
|
||||||
|
internalName: string
|
||||||
|
displayName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
columns: Column[]
|
||||||
|
onColumnRename: (column: Column) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const GraphOptionsCustomizeColumns: SFC<Props> = ({
|
||||||
|
columns,
|
||||||
|
onColumnRename,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="graph-options-group">
|
<div className="graph-options-group">
|
||||||
<label className="form-label">Customize Columns</label>
|
<label className="form-label">Customize Columns</label>
|
||||||
|
@ -12,8 +24,8 @@ const GraphOptionsCustomizeColumns = ({columns, onColumnRename}) => {
|
||||||
return (
|
return (
|
||||||
<GraphOptionsCustomizableColumn
|
<GraphOptionsCustomizableColumn
|
||||||
key={uuid.v4()}
|
key={uuid.v4()}
|
||||||
originalColumnName={col.name}
|
internalName={col.internalName}
|
||||||
newColumnName={col.newName}
|
displayName={col.displayName}
|
||||||
onColumnRename={onColumnRename}
|
onColumnRename={onColumnRename}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -21,16 +33,5 @@ const GraphOptionsCustomizeColumns = ({columns, onColumnRename}) => {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const {arrayOf, func, shape, string} = PropTypes
|
|
||||||
|
|
||||||
GraphOptionsCustomizeColumns.propTypes = {
|
|
||||||
columns: arrayOf(
|
|
||||||
shape({
|
|
||||||
name: string,
|
|
||||||
newName: string,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
onColumnRename: func,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GraphOptionsCustomizeColumns
|
export default GraphOptionsCustomizeColumns
|
|
@ -18,11 +18,7 @@ import {
|
||||||
updateSingleStatColors,
|
updateSingleStatColors,
|
||||||
updateTableOptions,
|
updateTableOptions,
|
||||||
} from 'src/dashboards/actions/cellEditorOverlay'
|
} from 'src/dashboards/actions/cellEditorOverlay'
|
||||||
|
import {TIME_COLUMN_DEFAULT} from 'src/shared/constants/tableGraph'
|
||||||
const formatColor = color => {
|
|
||||||
const {hex, name} = color
|
|
||||||
return {hex, name}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Color = {
|
type Color = {
|
||||||
type: string
|
type: string
|
||||||
|
@ -47,10 +43,12 @@ type Options = {
|
||||||
|
|
||||||
type QueryConfig = {
|
type QueryConfig = {
|
||||||
measurement: string
|
measurement: string
|
||||||
fields: [{
|
fields: [
|
||||||
alias: string
|
{
|
||||||
value: string
|
alias: string
|
||||||
}]
|
value: string
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -63,7 +61,42 @@ interface Props {
|
||||||
tableOptions: Options
|
tableOptions: Options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatColor = color => {
|
||||||
|
const {hex, name} = color
|
||||||
|
return {hex, name}
|
||||||
|
}
|
||||||
|
|
||||||
export class TableOptions extends PureComponent<Props, {}> {
|
export class TableOptions extends PureComponent<Props, {}> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
const {queryConfigs, handleUpdateTableOptions, tableOptions} = this.props
|
||||||
|
const {columnNames} = tableOptions
|
||||||
|
const timeColumn =
|
||||||
|
(columnNames && columnNames.find(c => c.internalName === 'time')) ||
|
||||||
|
TIME_COLUMN_DEFAULT
|
||||||
|
|
||||||
|
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})
|
||||||
|
}
|
||||||
|
|
||||||
handleToggleSingleStatType = () => {}
|
handleToggleSingleStatType = () => {}
|
||||||
|
|
||||||
handleAddThreshold = () => {}
|
handleAddThreshold = () => {}
|
||||||
|
@ -83,7 +116,14 @@ export class TableOptions extends PureComponent<Props, {}> {
|
||||||
|
|
||||||
handleToggleTextWrapping = () => {}
|
handleToggleTextWrapping = () => {}
|
||||||
|
|
||||||
handleColumnRename = () => {}
|
handleColumnRename = column => {
|
||||||
|
const {handleUpdateTableOptions, tableOptions} = this.props
|
||||||
|
const {columnNames} = tableOptions
|
||||||
|
const updatedColumns = columnNames.map(
|
||||||
|
op => (op.internalName === column.internalName ? column : op)
|
||||||
|
)
|
||||||
|
handleUpdateTableOptions({...tableOptions, columnNames: updatedColumns})
|
||||||
|
}
|
||||||
|
|
||||||
handleUpdateColorValue = () => {}
|
handleUpdateColorValue = () => {}
|
||||||
|
|
||||||
|
@ -93,22 +133,13 @@ export class TableOptions extends PureComponent<Props, {}> {
|
||||||
const {
|
const {
|
||||||
singleStatColors,
|
singleStatColors,
|
||||||
singleStatType,
|
singleStatType,
|
||||||
tableOptions: {timeFormat},
|
tableOptions: {timeFormat, columnNames: columns},
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const disableAddThreshold = singleStatColors.length > MAX_THRESHOLDS
|
const disableAddThreshold = singleStatColors.length > MAX_THRESHOLDS
|
||||||
const TimeAxis = 'vertical'
|
const TimeAxis = 'vertical'
|
||||||
const sortedColors = _.sortBy(singleStatColors, color => color.value)
|
const sortedColors = _.sortBy(singleStatColors, color => color.value)
|
||||||
|
|
||||||
const columns = [
|
|
||||||
'cpu.mean_usage_system',
|
|
||||||
'cpu.mean_usage_idle',
|
|
||||||
'cpu.mean_usage_user',
|
|
||||||
].map(col => ({
|
|
||||||
text: col,
|
|
||||||
name: col,
|
|
||||||
newName: '',
|
|
||||||
}))
|
|
||||||
const tableSortByOptions = [
|
const tableSortByOptions = [
|
||||||
'cpu.mean_usage_system',
|
'cpu.mean_usage_system',
|
||||||
'cpu.mean_usage_idle',
|
'cpu.mean_usage_idle',
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
NULL_ROW_INDEX,
|
NULL_ROW_INDEX,
|
||||||
NULL_HOVER_TIME,
|
NULL_HOVER_TIME,
|
||||||
TIME_FORMAT_DEFAULT,
|
TIME_FORMAT_DEFAULT,
|
||||||
|
TIME_COLUMN_DEFAULT,
|
||||||
} from 'src/shared/constants/tableGraph'
|
} from 'src/shared/constants/tableGraph'
|
||||||
|
|
||||||
import {MultiGrid} from 'react-virtualized'
|
import {MultiGrid} from 'react-virtualized'
|
||||||
|
@ -71,6 +72,9 @@ class TableGraph extends Component {
|
||||||
const timeFormat = tableOptions
|
const timeFormat = tableOptions
|
||||||
? tableOptions.timeFormat
|
? tableOptions.timeFormat
|
||||||
: TIME_FORMAT_DEFAULT
|
: TIME_FORMAT_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
|
||||||
|
@ -94,6 +98,13 @@ class TableGraph extends Component {
|
||||||
'table-graph-cell__numerical': dataIsNumerical,
|
'table-graph-cell__numerical': dataIsNumerical,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const cellData = data[rowIndex][columnIndex]
|
||||||
|
const foundColumn = columnNames.find(
|
||||||
|
column => column.internalName === cellData
|
||||||
|
)
|
||||||
|
const columnName =
|
||||||
|
foundColumn && (foundColumn.displayName || foundColumn.internalName)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={key}
|
key={key}
|
||||||
|
@ -102,8 +113,8 @@ class TableGraph extends Component {
|
||||||
onMouseOver={this.handleHover(columnIndex, rowIndex)}
|
onMouseOver={this.handleHover(columnIndex, rowIndex)}
|
||||||
>
|
>
|
||||||
{isTimeData
|
{isTimeData
|
||||||
? `${moment(data[rowIndex][columnIndex]).format(timeFormat)}`
|
? `${moment(cellData).format(timeFormat)}`
|
||||||
: `${data[rowIndex][columnIndex]}`}
|
: columnName || `${cellData}`}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -141,6 +152,9 @@ class TableGraph extends Component {
|
||||||
timeFormat={
|
timeFormat={
|
||||||
tableOptions ? tableOptions.timeFormat : TIME_FORMAT_DEFAULT
|
tableOptions ? tableOptions.timeFormat : TIME_FORMAT_DEFAULT
|
||||||
}
|
}
|
||||||
|
columnNames={
|
||||||
|
tableOptions ? tableOptions.columnNames : [TIME_COLUMN_DEFAULT]
|
||||||
|
}
|
||||||
scrollToRow={hoverTimeRow}
|
scrollToRow={hoverTimeRow}
|
||||||
cellRenderer={this.cellRenderer}
|
cellRenderer={this.cellRenderer}
|
||||||
hoveredColumnIndex={hoveredColumnIndex}
|
hoveredColumnIndex={hoveredColumnIndex}
|
||||||
|
|
|
@ -6,6 +6,8 @@ export const NULL_HOVER_TIME = '0'
|
||||||
export const TIME_FORMAT_DEFAULT = 'MM/DD/YYYY HH:mm:ss.ss'
|
export const TIME_FORMAT_DEFAULT = 'MM/DD/YYYY HH:mm:ss.ss'
|
||||||
export const TIME_FORMAT_CUSTOM = 'Custom'
|
export const TIME_FORMAT_CUSTOM = 'Custom'
|
||||||
|
|
||||||
|
export const TIME_COLUMN_DEFAULT = {internalName: 'time', displayName: ''}
|
||||||
|
|
||||||
export const FORMAT_OPTIONS = [
|
export const FORMAT_OPTIONS = [
|
||||||
{text: TIME_FORMAT_DEFAULT},
|
{text: TIME_FORMAT_DEFAULT},
|
||||||
{text: 'MM/DD/YYYY HH:mm'},
|
{text: 'MM/DD/YYYY HH:mm'},
|
||||||
|
@ -21,9 +23,9 @@ export const FORMAT_OPTIONS = [
|
||||||
export const DEFAULT_TABLE_OPTIONS = {
|
export const DEFAULT_TABLE_OPTIONS = {
|
||||||
timeFormat: 'MM/DD/YYYY HH:mm:ss.ss',
|
timeFormat: 'MM/DD/YYYY HH:mm:ss.ss',
|
||||||
verticalTimeAxis: true,
|
verticalTimeAxis: true,
|
||||||
sortBy: {internalName: 'time', displayName: ''},
|
sortBy: TIME_COLUMN_DEFAULT,
|
||||||
wrapping: 'truncate',
|
wrapping: 'truncate',
|
||||||
columnNames: [{internalName: 'time', displayName: ''}],
|
columnNames: [TIME_COLUMN_DEFAULT],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initializeOptions = cellType => {
|
export const initializeOptions = cellType => {
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import GraphOptionsCustomizableColumn from 'src/dashboards/components/GraphOptionsCustomizableColumn'
|
||||||
|
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
|
||||||
|
|
||||||
|
import {shallow} from 'enzyme'
|
||||||
|
|
||||||
|
const setup = (override = {}) => {
|
||||||
|
const props = {
|
||||||
|
internalName: '',
|
||||||
|
displayName: '',
|
||||||
|
onColumnRename: () => {},
|
||||||
|
...override,
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = shallow(<GraphOptionsCustomizableColumn {...props} />)
|
||||||
|
const instance = wrapper.instance() as GraphOptionsCustomizableColumn
|
||||||
|
|
||||||
|
return {wrapper, props, instance}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Dashboards.Components.GraphOptionsCustomizableColumn', () => {
|
||||||
|
describe('rendering', () => {
|
||||||
|
it('displays both label div and InputClickToEdit', () => {
|
||||||
|
const {wrapper} = setup()
|
||||||
|
const label = wrapper.find('div').last()
|
||||||
|
const input = wrapper.find(InputClickToEdit)
|
||||||
|
|
||||||
|
expect(label.exists()).toBe(true)
|
||||||
|
expect(input.exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when there is an internalName', () => {
|
||||||
|
it('displays the value', () => {
|
||||||
|
const internalName = 'test'
|
||||||
|
const {wrapper} = setup({internalName})
|
||||||
|
const label = wrapper.find('div').last()
|
||||||
|
expect(label.exists()).toBe(true)
|
||||||
|
expect(label.children().contains(internalName)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('instance methods', () => {
|
||||||
|
describe('#handleColumnRename', () => {
|
||||||
|
it('calls onColumnRename once', () => {
|
||||||
|
const onColumnRename = jest.fn()
|
||||||
|
const internalName = 'test'
|
||||||
|
const {instance} = setup({onColumnRename, internalName})
|
||||||
|
const rename = 'TEST'
|
||||||
|
|
||||||
|
instance.handleColumnRename(rename)
|
||||||
|
|
||||||
|
expect(onColumnRename).toHaveBeenCalledTimes(1)
|
||||||
|
expect(onColumnRename).toHaveBeenCalledWith({
|
||||||
|
internalName,
|
||||||
|
displayName: rename,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import GraphOptionsCustomizeColumns from 'src/dashboards/components/GraphOptionsCustomizeColumns'
|
||||||
|
import GraphOptionsCustomizableColumn from 'src/dashboards/components/GraphOptionsCustomizableColumn'
|
||||||
|
import {TIME_COLUMN_DEFAULT} from 'src/shared/constants/tableGraph'
|
||||||
|
|
||||||
|
import {shallow} from 'enzyme'
|
||||||
|
|
||||||
|
const setup = (override = {}) => {
|
||||||
|
const props = {
|
||||||
|
columns: [],
|
||||||
|
onColumnRename: () => {},
|
||||||
|
...override,
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = shallow(<GraphOptionsCustomizeColumns {...props} />)
|
||||||
|
|
||||||
|
return {wrapper, props}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Dashboards.Components.GraphOptionsCustomizeColumns', () => {
|
||||||
|
describe('rendering', () => {
|
||||||
|
it('displays label and all columns passed in', () => {
|
||||||
|
const columns = [TIME_COLUMN_DEFAULT]
|
||||||
|
const {wrapper} = setup({columns})
|
||||||
|
const label = wrapper.find('label')
|
||||||
|
const customizableColumns = wrapper.find(GraphOptionsCustomizableColumn)
|
||||||
|
|
||||||
|
expect(label.exists()).toBe(true)
|
||||||
|
expect(customizableColumns.exists()).toBe(true)
|
||||||
|
expect(customizableColumns.length).toBe(columns.length)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue