diff --git a/ui/src/dashboards/components/GraphOptionsCustomizableColumn.js b/ui/src/dashboards/components/GraphOptionsCustomizableColumn.js deleted file mode 100644 index e2ad8a95f..000000000 --- a/ui/src/dashboards/components/GraphOptionsCustomizableColumn.js +++ /dev/null @@ -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 ( -
-
- {originalColumnName} -
- -
- ) -} -const {func, string} = PropTypes - -GraphOptionsCustomizableColumn.propTypes = { - originalColumnName: string, - newColumnName: string, - onColumnRename: func, -} - -export default GraphOptionsCustomizableColumn diff --git a/ui/src/dashboards/components/GraphOptionsCustomizableColumn.tsx b/ui/src/dashboards/components/GraphOptionsCustomizableColumn.tsx new file mode 100644 index 000000000..cfed2453a --- /dev/null +++ b/ui/src/dashboards/components/GraphOptionsCustomizableColumn.tsx @@ -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 { + 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 ( +
+
+ {internalName} +
+ +
+ ) + } +} + +export default GraphOptionsCustomizableColumn diff --git a/ui/src/dashboards/components/GraphOptionsCustomizeColumns.js b/ui/src/dashboards/components/GraphOptionsCustomizeColumns.tsx similarity index 54% rename from ui/src/dashboards/components/GraphOptionsCustomizeColumns.js rename to ui/src/dashboards/components/GraphOptionsCustomizeColumns.tsx index 510f9a06a..3d4e44759 100644 --- a/ui/src/dashboards/components/GraphOptionsCustomizeColumns.js +++ b/ui/src/dashboards/components/GraphOptionsCustomizeColumns.tsx @@ -1,10 +1,22 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' import GraphOptionsCustomizableColumn from 'src/dashboards/components/GraphOptionsCustomizableColumn' 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 = ({ + columns, + onColumnRename, +}) => { return (
@@ -12,8 +24,8 @@ const GraphOptionsCustomizeColumns = ({columns, onColumnRename}) => { return ( ) @@ -21,16 +33,5 @@ const GraphOptionsCustomizeColumns = ({columns, onColumnRename}) => {
) } -const {arrayOf, func, shape, string} = PropTypes - -GraphOptionsCustomizeColumns.propTypes = { - columns: arrayOf( - shape({ - name: string, - newName: string, - }) - ), - onColumnRename: func, -} export default GraphOptionsCustomizeColumns diff --git a/ui/src/dashboards/components/TableOptions.tsx b/ui/src/dashboards/components/TableOptions.tsx index d88770de7..65bf0f56a 100644 --- a/ui/src/dashboards/components/TableOptions.tsx +++ b/ui/src/dashboards/components/TableOptions.tsx @@ -18,11 +18,7 @@ import { updateSingleStatColors, updateTableOptions, } from 'src/dashboards/actions/cellEditorOverlay' - -const formatColor = color => { - const {hex, name} = color - return {hex, name} -} +import {TIME_COLUMN_DEFAULT} from 'src/shared/constants/tableGraph' type Color = { type: string @@ -47,10 +43,12 @@ type Options = { type QueryConfig = { measurement: string - fields: [{ - alias: string - value: string - }] + fields: [ + { + alias: string + value: string + } + ] } interface Props { @@ -63,7 +61,42 @@ interface Props { tableOptions: Options } +const formatColor = color => { + const {hex, name} = color + return {hex, name} +} + export class TableOptions extends PureComponent { + 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 = () => {} handleAddThreshold = () => {} @@ -83,7 +116,14 @@ export class TableOptions extends PureComponent { 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 = () => {} @@ -93,22 +133,13 @@ export class TableOptions extends PureComponent { const { singleStatColors, singleStatType, - tableOptions: {timeFormat}, + tableOptions: {timeFormat, columnNames: columns}, } = this.props const disableAddThreshold = singleStatColors.length > MAX_THRESHOLDS const TimeAxis = 'vertical' 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 = [ 'cpu.mean_usage_system', 'cpu.mean_usage_idle', diff --git a/ui/src/shared/components/TableGraph.js b/ui/src/shared/components/TableGraph.js index d3a400b4c..ed31ed787 100644 --- a/ui/src/shared/components/TableGraph.js +++ b/ui/src/shared/components/TableGraph.js @@ -9,6 +9,7 @@ import { NULL_ROW_INDEX, NULL_HOVER_TIME, TIME_FORMAT_DEFAULT, + TIME_COLUMN_DEFAULT, } from 'src/shared/constants/tableGraph' import {MultiGrid} from 'react-virtualized' @@ -71,6 +72,9 @@ class TableGraph extends Component { const timeFormat = tableOptions ? tableOptions.timeFormat : TIME_FORMAT_DEFAULT + const columnNames = tableOptions + ? tableOptions.columnNames + : [TIME_COLUMN_DEFAULT] const isFixedRow = rowIndex === 0 && columnIndex > 0 const isFixedColumn = rowIndex > 0 && columnIndex === 0 @@ -94,6 +98,13 @@ class TableGraph extends Component { '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 (
{isTimeData - ? `${moment(data[rowIndex][columnIndex]).format(timeFormat)}` - : `${data[rowIndex][columnIndex]}`} + ? `${moment(cellData).format(timeFormat)}` + : columnName || `${cellData}`}
) } @@ -141,6 +152,9 @@ class TableGraph extends Component { timeFormat={ tableOptions ? tableOptions.timeFormat : TIME_FORMAT_DEFAULT } + columnNames={ + tableOptions ? tableOptions.columnNames : [TIME_COLUMN_DEFAULT] + } scrollToRow={hoverTimeRow} cellRenderer={this.cellRenderer} hoveredColumnIndex={hoveredColumnIndex} diff --git a/ui/src/shared/constants/tableGraph.js b/ui/src/shared/constants/tableGraph.js index ebf97a769..9c87cc65e 100644 --- a/ui/src/shared/constants/tableGraph.js +++ b/ui/src/shared/constants/tableGraph.js @@ -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_CUSTOM = 'Custom' +export const TIME_COLUMN_DEFAULT = {internalName: 'time', displayName: ''} + export const FORMAT_OPTIONS = [ {text: TIME_FORMAT_DEFAULT}, {text: 'MM/DD/YYYY HH:mm'}, @@ -21,9 +23,9 @@ export const FORMAT_OPTIONS = [ export const DEFAULT_TABLE_OPTIONS = { timeFormat: 'MM/DD/YYYY HH:mm:ss.ss', verticalTimeAxis: true, - sortBy: {internalName: 'time', displayName: ''}, + sortBy: TIME_COLUMN_DEFAULT, wrapping: 'truncate', - columnNames: [{internalName: 'time', displayName: ''}], + columnNames: [TIME_COLUMN_DEFAULT], } export const initializeOptions = cellType => { diff --git a/ui/test/dashboards/components/GraphOptionsCustomizableColumn.test.tsx b/ui/test/dashboards/components/GraphOptionsCustomizableColumn.test.tsx new file mode 100644 index 000000000..477807e29 --- /dev/null +++ b/ui/test/dashboards/components/GraphOptionsCustomizableColumn.test.tsx @@ -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() + 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, + }) + }) + }) + }) +}) diff --git a/ui/test/dashboards/components/GraphOptionsCustomizeColumns.test.tsx b/ui/test/dashboards/components/GraphOptionsCustomizeColumns.test.tsx new file mode 100644 index 000000000..23dee525e --- /dev/null +++ b/ui/test/dashboards/components/GraphOptionsCustomizeColumns.test.tsx @@ -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() + + 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) + }) + }) +})