Merge pull request #3155 from influxdata/table/fieldnames-from-query-config

Table/Get FieldNames from query config
pull/10616/head
Iris Scholten 2018-04-09 15:59:25 -07:00 committed by GitHub
commit 5b87bea961
11 changed files with 102 additions and 311 deletions

View File

@ -49,7 +49,6 @@ class CellEditorOverlay extends Component {
activeQueryIndex: 0, activeQueryIndex: 0,
isDisplayOptionsTabActive: false, isDisplayOptionsTabActive: false,
staticLegend: IS_STATIC_LEGEND(legend), staticLegend: IS_STATIC_LEGEND(legend),
dataLabels: [],
} }
} }
@ -253,10 +252,6 @@ class CellEditorOverlay extends Component {
this.overlayRef.focus() this.overlayRef.focus()
} }
setDataLabels = dataLabels => {
this.setState({dataLabels})
}
render() { render() {
const { const {
onCancel, onCancel,
@ -271,7 +266,6 @@ class CellEditorOverlay extends Component {
isDisplayOptionsTabActive, isDisplayOptionsTabActive,
queriesWorkingDraft, queriesWorkingDraft,
staticLegend, staticLegend,
dataLabels,
} = this.state } = this.state
const queryActions = { const queryActions = {
@ -304,7 +298,6 @@ class CellEditorOverlay extends Component {
queryConfigs={queriesWorkingDraft} queryConfigs={queriesWorkingDraft}
editQueryStatus={editQueryStatus} editQueryStatus={editQueryStatus}
staticLegend={staticLegend} staticLegend={staticLegend}
setDataLabels={this.setDataLabels}
/> />
<CEOBottom> <CEOBottom>
<OverlayControls <OverlayControls
@ -324,7 +317,6 @@ class CellEditorOverlay extends Component {
onToggleStaticLegend={this.handleToggleStaticLegend} onToggleStaticLegend={this.handleToggleStaticLegend}
staticLegend={staticLegend} staticLegend={staticLegend}
onResetFocus={this.handleResetFocus} onResetFocus={this.handleResetFocus}
dataLabels={dataLabels}
/> />
) : ( ) : (
<QueryMaker <QueryMaker

View File

@ -42,7 +42,7 @@ class DisplayOptions extends Component {
staticLegend, staticLegend,
onToggleStaticLegend, onToggleStaticLegend,
onResetFocus, onResetFocus,
dataLabels, queryConfigs,
} = this.props } = this.props
switch (type) { switch (type) {
case 'gauge': case 'gauge':
@ -51,7 +51,10 @@ class DisplayOptions extends Component {
return <SingleStatOptions onResetFocus={onResetFocus} /> return <SingleStatOptions onResetFocus={onResetFocus} />
case 'table': case 'table':
return ( return (
<TableOptions onResetFocus={onResetFocus} dataLabels={dataLabels} /> <TableOptions
onResetFocus={onResetFocus}
queryConfigs={queryConfigs}
/>
) )
default: default:
return ( return (
@ -90,7 +93,6 @@ DisplayOptions.propTypes = {
onToggleStaticLegend: func.isRequired, onToggleStaticLegend: func.isRequired,
staticLegend: bool, staticLegend: bool,
onResetFocus: func.isRequired, onResetFocus: func.isRequired,
dataLabels: arrayOf(string),
} }
const mapStateToProps = ({cellEditorOverlay: {cell, cell: {axes}}}) => ({ const mapStateToProps = ({cellEditorOverlay: {cell, cell: {axes}}}) => ({

View File

@ -23,16 +23,6 @@ class GraphOptionsCustomizableField extends PureComponent<Props, {}> {
this.handleToggleVisible = this.handleToggleVisible.bind(this) this.handleToggleVisible = this.handleToggleVisible.bind(this)
} }
public handleFieldRename(rename: string) {
const {onFieldUpdate, internalName, visible} = this.props
onFieldUpdate({internalName, displayName: rename, visible})
}
public handleToggleVisible() {
const {onFieldUpdate, internalName, displayName, visible} = this.props
onFieldUpdate({internalName, displayName, visible: !visible})
}
public render() { public render() {
const {internalName, displayName, visible} = this.props const {internalName, displayName, visible} = this.props
@ -65,6 +55,16 @@ class GraphOptionsCustomizableField extends PureComponent<Props, {}> {
</div> </div>
) )
} }
private handleFieldRename(rename: string) {
const {onFieldUpdate, internalName, visible} = this.props
onFieldUpdate({internalName, displayName: rename, visible})
}
private handleToggleVisible() {
const {onFieldUpdate, internalName, displayName, visible} = this.props
onFieldUpdate({internalName, displayName, visible: !visible})
}
} }
export default GraphOptionsCustomizableField export default GraphOptionsCustomizableField

View File

@ -16,6 +16,7 @@ import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeTo
import {updateTableOptions} from 'src/dashboards/actions/cellEditorOverlay' import {updateTableOptions} from 'src/dashboards/actions/cellEditorOverlay'
import {TIME_FIELD_DEFAULT} from 'src/shared/constants/tableGraph' import {TIME_FIELD_DEFAULT} from 'src/shared/constants/tableGraph'
import {QueryConfig} from 'src/types/query'
interface Option { interface Option {
text: string text: string
@ -37,10 +38,10 @@ interface Options {
} }
interface Props { interface Props {
queryConfigs: QueryConfig[]
handleUpdateTableOptions: (options: Options) => void handleUpdateTableOptions: (options: Options) => void
tableOptions: Options tableOptions: Options
onResetFocus: () => void onResetFocus: () => void
dataLabels: string[]
} }
export class TableOptions extends PureComponent<Props, {}> { export class TableOptions extends PureComponent<Props, {}> {
@ -48,79 +49,6 @@ export class TableOptions extends PureComponent<Props, {}> {
super(props) super(props)
} }
get fieldNames() {
const {tableOptions: {fieldNames}} = this.props
return fieldNames || []
}
get timeField() {
return (
this.fieldNames.find(f => f.internalName === 'time') || TIME_FIELD_DEFAULT
)
}
get computedFieldNames() {
const {dataLabels} = this.props
return _.isEmpty(dataLabels)
? [this.timeField]
: dataLabels.map(label => {
const existing = this.fieldNames.find(f => f.internalName === label)
return (
existing || {internalName: label, displayName: '', visible: true}
)
})
}
public handleChooseSortBy = (option: Option) => {
const {tableOptions, handleUpdateTableOptions} = this.props
const sortBy = {
displayName: option.text === option.key ? '' : option.text,
internalName: option.key,
visible: true,
}
handleUpdateTableOptions({...tableOptions, sortBy})
}
public handleTimeFormatChange = timeFormat => {
const {tableOptions, handleUpdateTableOptions} = this.props
handleUpdateTableOptions({...tableOptions, timeFormat})
}
public handleToggleVerticalTimeAxis = verticalTimeAxis => () => {
const {tableOptions, handleUpdateTableOptions} = this.props
handleUpdateTableOptions({...tableOptions, verticalTimeAxis})
}
public handleToggleFixFirstColumn = () => {
const {handleUpdateTableOptions, tableOptions} = this.props
const fixFirstColumn = !tableOptions.fixFirstColumn
handleUpdateTableOptions({...tableOptions, fixFirstColumn})
}
public handleFieldUpdate = field => {
const {handleUpdateTableOptions, tableOptions, dataLabels} = this.props
const {sortBy, fieldNames} = tableOptions
const fields =
fieldNames.length >= dataLabels.length
? fieldNames
: this.computedFieldNames
const updatedFields = fields.map(
f => (f.internalName === field.internalName ? field : f)
)
const updatedSortBy =
sortBy.internalName === field.internalName
? {...sortBy, displayName: field.displayName}
: sortBy
handleUpdateTableOptions({
...tableOptions,
fieldNames: updatedFields,
sortBy: updatedSortBy,
})
}
public componentWillMount() { public componentWillMount() {
const {handleUpdateTableOptions, tableOptions} = this.props const {handleUpdateTableOptions, tableOptions} = this.props
handleUpdateTableOptions({ handleUpdateTableOptions({
@ -130,14 +58,13 @@ export class TableOptions extends PureComponent<Props, {}> {
} }
public shouldComponentUpdate(nextProps) { public shouldComponentUpdate(nextProps) {
const {tableOptions, dataLabels} = this.props const {tableOptions} = this.props
const tableOptionsDifferent = !_.isEqual( const tableOptionsDifferent = !_.isEqual(
tableOptions, tableOptions,
nextProps.tableOptions nextProps.tableOptions
) )
const dataLabelsDifferent = !_.isEqual(dataLabels, nextProps.dataLabels)
return tableOptionsDifferent || dataLabelsDifferent return tableOptionsDifferent
} }
public render() { public render() {
@ -147,13 +74,11 @@ export class TableOptions extends PureComponent<Props, {}> {
tableOptions, tableOptions,
} = this.props } = this.props
const tableSortByOptions = this.computedFieldNames.map(field => ({ const tableSortByOptions = this.fieldNames.map(field => ({
key: field.internalName, key: field.internalName,
text: field.displayName || field.internalName, text: field.displayName || field.internalName,
})) }))
const fields = fieldNames.length > 1 ? fieldNames : this.computedFieldNames
return ( return (
<FancyScrollbar <FancyScrollbar
className="display-options--cell y-axis-controls" className="display-options--cell y-axis-controls"
@ -181,7 +106,7 @@ export class TableOptions extends PureComponent<Props, {}> {
/> />
</div> </div>
<GraphOptionsCustomizeFields <GraphOptionsCustomizeFields
fields={fields} fields={fieldNames}
onFieldUpdate={this.handleFieldUpdate} onFieldUpdate={this.handleFieldUpdate}
/> />
<ThresholdsList showListHeading={true} onResetFocus={onResetFocus} /> <ThresholdsList showListHeading={true} onResetFocus={onResetFocus} />
@ -192,6 +117,79 @@ export class TableOptions extends PureComponent<Props, {}> {
</FancyScrollbar> </FancyScrollbar>
) )
} }
private get fieldNames() {
const {tableOptions: {fieldNames}} = this.props
return fieldNames || []
}
private get timeField() {
return (
this.fieldNames.find(f => f.internalName === 'time') || TIME_FIELD_DEFAULT
)
}
private get computedFieldNames() {
const {queryConfigs} = this.props
const queryFields = _.flatten(
queryConfigs.map(({measurement, fields}) => {
return fields.map(({alias}) => {
const internalName = `${measurement}.${alias}`
const existing = this.fieldNames.find(
c => c.internalName === internalName
)
return existing || {internalName, displayName: '', visible: true}
})
})
)
return [this.timeField, ...queryFields]
}
private handleChooseSortBy = (option: Option) => {
const {tableOptions, handleUpdateTableOptions} = this.props
const sortBy = {
displayName: option.text === option.key ? '' : option.text,
internalName: option.key,
visible: true,
}
handleUpdateTableOptions({...tableOptions, sortBy})
}
private handleTimeFormatChange = timeFormat => {
const {tableOptions, handleUpdateTableOptions} = this.props
handleUpdateTableOptions({...tableOptions, timeFormat})
}
private handleToggleVerticalTimeAxis = verticalTimeAxis => () => {
const {tableOptions, handleUpdateTableOptions} = this.props
handleUpdateTableOptions({...tableOptions, verticalTimeAxis})
}
private handleToggleFixFirstColumn = () => {
const {handleUpdateTableOptions, tableOptions} = this.props
const fixFirstColumn = !tableOptions.fixFirstColumn
handleUpdateTableOptions({...tableOptions, fixFirstColumn})
}
private handleFieldUpdate = field => {
const {handleUpdateTableOptions, tableOptions} = this.props
const {sortBy, fieldNames} = tableOptions
const updatedFields = fieldNames.map(
f => (f.internalName === field.internalName ? field : f)
)
const updatedSortBy =
sortBy.internalName === field.internalName
? {...sortBy, displayName: field.displayName}
: sortBy
handleUpdateTableOptions({
...tableOptions,
fieldNames: updatedFields,
sortBy: updatedSortBy,
})
}
} }
const mapStateToProps = ({cellEditorOverlay: {cell: {tableOptions}}}) => ({ const mapStateToProps = ({cellEditorOverlay: {cell: {tableOptions}}}) => ({

View File

@ -24,7 +24,6 @@ const DashVisualization = (
staticLegend, staticLegend,
thresholdsListColors, thresholdsListColors,
tableOptions, tableOptions,
setDataLabels,
}, },
{source: {links: {proxy}}} {source: {links: {proxy}}}
) => { ) => {
@ -50,7 +49,6 @@ const DashVisualization = (
editQueryStatus={editQueryStatus} editQueryStatus={editQueryStatus}
resizerTopHeight={resizerTopHeight} resizerTopHeight={resizerTopHeight}
staticLegend={staticLegend} staticLegend={staticLegend}
setDataLabels={setDataLabels}
/> />
</div> </div>
</div> </div>
@ -80,7 +78,6 @@ DashVisualization.propTypes = {
gaugeColors: colorsNumberSchema, gaugeColors: colorsNumberSchema,
lineColors: colorsStringSchema, lineColors: colorsStringSchema,
staticLegend: bool, staticLegend: bool,
setDataLabels: func,
} }
DashVisualization.contextTypes = { DashVisualization.contextTypes = {

View File

@ -25,7 +25,6 @@ const RefreshingGraph = ({
cellID, cellID,
queries, queries,
tableOptions, tableOptions,
setDataLabels,
templates, templates,
timeRange, timeRange,
cellHeight, cellHeight,
@ -101,7 +100,6 @@ const RefreshingGraph = ({
hoverTime={hoverTime} hoverTime={hoverTime}
onSetHoverTime={onSetHoverTime} onSetHoverTime={onSetHoverTime}
inView={inView} inView={inView}
setDataLabels={setDataLabels}
/> />
) )
} }
@ -160,7 +158,6 @@ RefreshingGraph.propTypes = {
cellID: string, cellID: string,
inView: bool, inView: bool,
tableOptions: shape({}), tableOptions: shape({}),
setDataLabels: func,
} }
RefreshingGraph.defaultProps = { RefreshingGraph.defaultProps = {

View File

@ -40,7 +40,6 @@ class TableGraph extends Component {
data: [[]], data: [[]],
processedData: [[]], processedData: [[]],
sortedTimeVals: [], sortedTimeVals: [],
labels: [],
hoveredColumnIndex: NULL_ARRAY_INDEX, hoveredColumnIndex: NULL_ARRAY_INDEX,
hoveredRowIndex: NULL_ARRAY_INDEX, hoveredRowIndex: NULL_ARRAY_INDEX,
sortField, sortField,
@ -51,7 +50,7 @@ class TableGraph extends Component {
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const {labels, data} = timeSeriesToTableGraph(nextProps.data) const {data} = timeSeriesToTableGraph(nextProps.data)
if (_.isEmpty(data[0])) { if (_.isEmpty(data[0])) {
return return
} }
@ -64,13 +63,8 @@ class TableGraph extends Component {
verticalTimeAxis, verticalTimeAxis,
timeFormat, timeFormat,
}, },
setDataLabels,
} = nextProps } = nextProps
if (setDataLabels) {
setDataLabels(labels)
}
let direction, sortFieldName let direction, sortFieldName
if ( if (
_.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') === _.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') ===
@ -99,7 +93,6 @@ class TableGraph extends Component {
this.setState({ this.setState({
data, data,
labels,
processedData, processedData,
sortedTimeVals, sortedTimeVals,
sortField: sortFieldName, sortField: sortFieldName,
@ -194,7 +187,7 @@ class TableGraph extends Component {
}) })
} }
calculateColumnWidth = __ => column => { calculateColumnWidth = columnSizerWidth => column => {
const {index} = column const {index} = column
const {tableOptions: {fixFirstColumn}} = this.props const {tableOptions: {fixFirstColumn}} = this.props
const {processedData, columnWidths, totalColumnWidths} = this.state const {processedData, columnWidths, totalColumnWidths} = this.state
@ -205,8 +198,12 @@ class TableGraph extends Component {
const tableWidth = _.get(this, ['gridContainer', 'clientWidth'], 0) const tableWidth = _.get(this, ['gridContainer', 'clientWidth'], 0)
if (tableWidth > totalColumnWidths) { if (tableWidth > totalColumnWidths) {
if (columnCount === 1) {
return columnSizerWidth
}
const difference = tableWidth - totalColumnWidths const difference = tableWidth - totalColumnWidths
const distributeOver = fixFirstColumn ? columnCount - 1 : columnCount const distributeOver =
fixFirstColumn && columnCount > 1 ? columnCount - 1 : columnCount
const increment = difference / distributeOver const increment = difference / distributeOver
adjustedColumnSizerWidth = adjustedColumnSizerWidth =
fixFirstColumn && index === 0 fixFirstColumn && index === 0
@ -412,7 +409,6 @@ TableGraph.propTypes = {
hoverTime: string, hoverTime: string,
onSetHoverTime: func, onSetHoverTime: func,
colors: colorsStringSchema, colors: colorsStringSchema,
setDataLabels: func,
} }
export default TableGraph export default TableGraph

View File

@ -180,7 +180,6 @@ export const timeSeriesToTableGraph = raw => {
const tableData = map(sortedTimeSeries, ({time, values}) => [time, ...values]) const tableData = map(sortedTimeSeries, ({time, values}) => [time, ...values])
const data = tableData.length ? [labels, ...tableData] : [[]] const data = tableData.length ? [labels, ...tableData] : [[]]
return { return {
labels,
data, data,
} }
} }

View File

@ -72,47 +72,4 @@ describe('Dashboards.Components.GraphOptionsCustomizableField', () => {
}) })
}) })
}) })
describe('instance methods', () => {
describe('#handleFieldUpdate', () => {
it('calls onFieldUpdate once with internalName, new name, and visible', () => {
const onFieldUpdate = jest.fn()
const internalName = 'test'
const {instance, props: {visible}} = setup({
internalName,
onFieldUpdate,
})
const rename = 'TEST'
instance.handleFieldRename(rename)
expect(onFieldUpdate).toHaveBeenCalledTimes(1)
expect(onFieldUpdate).toHaveBeenCalledWith({
displayName: rename,
internalName,
visible,
})
})
})
describe('#handleToggleVisible', () => {
it('calls onFieldUpdate once with !visible, internalName, and displayName', () => {
const onFieldUpdate = jest.fn()
const visible = true
const {instance, props: {internalName, displayName}} = setup({
onFieldUpdate,
visible,
})
instance.handleToggleVisible()
expect(onFieldUpdate).toHaveBeenCalledTimes(1)
expect(onFieldUpdate).toHaveBeenCalledWith({
displayName,
internalName,
visible: !visible,
})
})
})
})
}) })

View File

@ -9,12 +9,11 @@ import {TableOptions} from 'src/dashboards/components/TableOptions'
import FancyScrollbar from 'src/shared/components/FancyScrollbar' import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import ThresholdsList from 'src/shared/components/ThresholdsList' import ThresholdsList from 'src/shared/components/ThresholdsList'
import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeToggle' import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeToggle'
import {TIME_FIELD_DEFAULT} from 'src/shared/constants/tableGraph'
const defaultProps = { const defaultProps = {
dataLabels: [],
handleUpdateTableOptions: () => {}, handleUpdateTableOptions: () => {},
onResetFocus: () => {}, onResetFocus: () => {},
queryConfigs: [],
tableOptions: { tableOptions: {
columnNames: [], columnNames: [],
fieldNames: [], fieldNames: [],
@ -39,100 +38,6 @@ const setup = (override = {}) => {
} }
describe('Dashboards.Components.TableOptions', () => { describe('Dashboards.Components.TableOptions', () => {
describe('getters', () => {
describe('fieldNames', () => {
describe('if fieldNames are passed in tableOptions as props', () => {
it('returns fieldNames', () => {
const fieldNames = [
{internalName: 'time', displayName: 'TIME', visible: true},
{internalName: 'foo', displayName: 'BAR', visible: false},
]
const {instance} = setup({tableOptions: {fieldNames}})
expect(instance.fieldNames).toBe(fieldNames)
})
})
describe('if fieldNames are not passed in tableOptions as props', () => {
it('returns empty array', () => {
const {instance} = setup()
expect(instance.fieldNames).toEqual([])
})
})
})
describe('timeField', () => {
describe('if time field in fieldNames', () => {
it('returns time field', () => {
const timeField = {
internalName: 'time',
displayName: 'TIME',
visible: true,
}
const fieldNames = [
timeField,
{internalName: 'foo', displayName: 'BAR', visible: false},
]
const {instance} = setup({tableOptions: {fieldNames}})
expect(instance.timeField).toBe(timeField)
})
})
describe('if time field not in fieldNames', () => {
it('returns default time field', () => {
const fieldNames = [
{internalName: 'foo', displayName: 'BAR', visible: false},
]
const {instance} = setup({tableOptions: {fieldNames}})
expect(instance.timeField).toBe(TIME_FIELD_DEFAULT)
})
})
})
describe('computedFieldNames', () => {
describe('if dataLabels are not passed in', () => {
it('returns an array of the time column', () => {
const {instance} = setup()
expect(instance.computedFieldNames).toEqual([TIME_FIELD_DEFAULT])
})
})
describe('if dataLabels are passed in', () => {
describe('if dataLabel has a matching fieldName', () => {
it('returns array with the matching fieldName', () => {
const fieldNames = [
{internalName: 'foo', displayName: 'bar', visible: true},
]
const dataLabels = ['foo']
const {instance} = setup({tableOptions: {fieldNames}, dataLabels})
expect(instance.computedFieldNames).toEqual(fieldNames)
})
})
describe('if dataLabel does not have a matching fieldName', () => {
it('returns array with a new fieldName created for it', () => {
const fieldNames = [
{internalName: 'time', displayName: 'bar', visible: true},
]
const unmatchedLabel = 'foo'
const dataLabels = ['time', unmatchedLabel]
const {instance} = setup({tableOptions: {fieldNames}, dataLabels})
expect(instance.computedFieldNames).toEqual([
...fieldNames,
{internalName: unmatchedLabel, displayName: '', visible: true},
])
})
})
})
})
})
describe('rendering', () => { describe('rendering', () => {
it('should render all components', () => { it('should render all components', () => {
const {wrapper} = setup() const {wrapper} = setup()

View File

@ -351,58 +351,6 @@ describe('timeSeriesToTableGraph', () => {
expect(actual.data).toEqual(expected) expect(actual.data).toEqual(expected)
}) })
it('returns labels starting with time and then alphabetized', () => {
const influxResponse = [
{
response: {
results: [
{
series: [
{
name: 'mb',
columns: ['time', 'f1'],
values: [[1000, 1], [2000, 2]],
},
],
},
{
series: [
{
name: 'ma',
columns: ['time', 'f1'],
values: [[1000, 1], [2000, 2]],
},
],
},
{
series: [
{
name: 'mc',
columns: ['time', 'f2'],
values: [[2000, 3], [4000, 4]],
},
],
},
{
series: [
{
name: 'mc',
columns: ['time', 'f1'],
values: [[2000, 3], [4000, 4]],
},
],
},
],
},
},
]
const actual = timeSeriesToTableGraph(influxResponse)
const expected = ['time', 'ma.f1', 'mb.f1', 'mc.f1', 'mc.f2']
expect(actual.labels).toEqual(expected)
})
it('parses raw data into a table-readable format with the first row being labels', () => { it('parses raw data into a table-readable format with the first row being labels', () => {
const influxResponse = [ const influxResponse = [
{ {