Merge pull request #3090 from influxdata/feature/table-graph-polish

Feature/table graph polish
pull/3101/head
Deniz Kusefoglu 2018-03-30 01:52:12 -04:00 committed by GitHub
commit f6c5222b51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 521 additions and 441 deletions

View File

@ -4349,39 +4349,141 @@
"format": "uuid4" "format": "uuid4"
}, },
"x": { "x": {
"description": "X-coordinate of Cell in the Layout", "description": "X-coordinate of Cell in the Dashboard",
"type": "integer", "type": "integer",
"format": "int32" "format": "int32"
}, },
"y": { "y": {
"description": "Y-coordinate of Cell in the Layout", "description": "Y-coordinate of Cell in the Dashboard",
"type": "integer", "type": "integer",
"format": "int32" "format": "int32"
}, },
"w": { "w": {
"description": "Width of Cell in the Layout", "description": "Width of Cell in the Dashboard",
"type": "integer", "type": "integer",
"format": "int32" "format": "int32",
"minimum": 1,
"default": 4
}, },
"h": { "h": {
"description": "Height of Cell in the Layout", "description": "Height of Cell in the Dashboard",
"type": "integer", "type": "integer",
"format": "int32" "format": "int32",
"minimum": 1,
"default": 4
}, },
"name": { "name": {
"description": "Cell name", "description": "Title of Cell in the Dashboard",
"type": "string" "type": "string"
}, },
"queries": { "queries": {
"description": "Time-series data queries for Cell.", "description": "Time-series data queries for Cell",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/LayoutQuery" "$ref": "#/definitions/DashboardQuery"
}
},
"axes": {
"description": "The viewport for a Cell's visualizations",
"type": "object",
"properties": {
"x": {
"$ref": "#/definitions/Axis"
},
"y": {
"$ref": "#/definitions/Axis"
},
"y2": {
"$ref": "#/definitions/Axis"
}
} }
}, },
"type": { "type": {
"description": "Cell visualization type", "description": "Cell visualization type",
"type": "string",
"enum": [
"single-stat",
"line",
"line-plus-single-stat",
"line-stacked",
"line-stepplot",
"bar",
"gauge",
"table"
],
"default": "line"
},
"colors": {
"description": "Colors define encoding data into a visualization",
"type": "array",
"items": {
"$ref": "#/definitions/DashboardColor"
}
},
"legend": {
"description":
"Legend define encoding of the data into a cell's legend",
"type": "object",
"properties": {
"type": {
"description": "type is the style of the legend",
"type": "string",
"enum": ["static"]
},
"orientation": {
"description":
"orientation is the location of the legend with respect to the cell graph",
"type": "string",
"enum": ["top", "bottom", "left", "right"]
}
}
},
"tableOptions": {
"timeFormat": {
"description":
"timeFormat describes the display format for time values according to moment.js date formatting",
"type": "string" "type": "string"
},
"verticalTimeAxis": {
"description":
"verticalTimeAxis describes the orientation of the table by indicating whether the time axis will be displayed vertically",
"type": "boolean"
},
"sortBy": {
"description":
"sortBy contains the name of the series that is used for sorting the table",
"type": "object",
"$ref": "#/definitions/RenamableField"
},
"wrapping": {
"description":
"wrapping describes the text wrapping style to be used in table cells",
"type": "string",
"enum": ["truncate", "wrap", "single-line"]
},
"fieldNames": {
"description":
"fieldNames represent the fields retrieved by the query with customization options",
"type": "array",
"items": {
"$ref": "#/definitions/RenamableField"
}
},
"fixFirstColumn": {
"description":
"fixFirstColumn indicates whether the first column of the table should be locked",
"type": "boolean"
}
},
"links": {
"type": "object",
"properties": {
"self": {
"type": "string",
"description": "Self link mapping to this resource",
"format": "url"
}
}
} }
}, },
"example": { "example": {
@ -4558,154 +4660,9 @@
"format": "int64" "format": "int64"
}, },
"cells": { "cells": {
"description": "a list of dashboard visualizations",
"type": "array", "type": "array",
"items": { "items": {
"description": "cell visualization information", "$ref": "#/definitions/Cell"
"type": "object",
"properties": {
"x": {
"description": "X-coordinate of Cell in the Dashboard",
"type": "integer",
"format": "int32"
},
"y": {
"description": "Y-coordinate of Cell in the Dashboard",
"type": "integer",
"format": "int32"
},
"w": {
"description": "Width of Cell in the Dashboard",
"type": "integer",
"format": "int32",
"minimum": 1,
"default": 4
},
"h": {
"description": "Height of Cell in the Dashboard",
"type": "integer",
"format": "int32",
"minimum": 1,
"default": 4
},
"name": {
"description": "Name of Cell in the Dashboard",
"type": "string"
},
"queries": {
"description": "Time-series data queries for Cell.",
"type": "array",
"items": {
"$ref": "#/definitions/DashboardQuery"
}
},
"axes": {
"description": "The viewport for a cell's visualizations",
"type": "object",
"properties": {
"x": {
"$ref": "#/definitions/Axis"
},
"y": {
"$ref": "#/definitions/Axis"
},
"y2": {
"$ref": "#/definitions/Axis"
}
}
},
"type": {
"description": "Cell visualization type",
"type": "string",
"enum": [
"single-stat",
"line",
"line-plus-single-stat",
"line-stacked",
"line-stepplot",
"bar"
],
"default": "line"
},
"colors": {
"description":
"Colors define encoding data into a visualization",
"type": "array",
"items": {
"$ref": "#/definitions/DashboardColor"
}
},
"legend": {
"description":
"Legend define encoding of the data into a cell's legend",
"type": "object",
"properties": {
"type": {
"description": "type is the style of the legend",
"type": "string",
"enum": ["static"]
},
"orientation": {
"description":
"orientation is the location of the legend with respect to the cell graph",
"type": "string",
"enum": ["top", "bottom", "left", "right"]
}
}
},
"tableOptions": {
"description":
"visualization options for a cell with table type",
"type": "object",
"properties": {
"timeFormat": {
"description":
"timeFormat describes the display format for time values according to moment.js date formatting",
"type": "string"
},
"verticalTimeAxis": {
"description":
"verticalTimeAxis describes the orientation of the table by indicating whether the time axis will be displayed vertically",
"type": "boolean"
},
"sortBy": {
"description":
"sortBy contains the name of the series that is used for sorting the table",
"type": "object",
"$ref": "#/definitions/RenamableField"
},
"wrapping": {
"description":
"wrapping describes the text wrapping style to be used in table cells",
"type": "string",
"enum": ["truncate", "wrap", "single-line"]
},
"fieldNames": {
"description":
"fieldNames represent the fields retrieved by the query with customization options",
"type": "array",
"items": {
"$ref": "#/definitions/RenamableField"
}
},
"fixFirstColumn": {
"description":
"fixFirstColumn indicates whether this field should be visible on the table",
"type": "boolean"
}
}
},
"links": {
"type": "object",
"properties": {
"self": {
"type": "string",
"description": "Self link mapping to this resource",
"format": "url"
}
}
}
}
} }
}, },
"name": { "name": {

View File

@ -4,7 +4,7 @@ import Dygraph from 'shared/components/Dygraph'
import shallowCompare from 'react-addons-shallow-compare' import shallowCompare from 'react-addons-shallow-compare'
import SingleStat from 'src/shared/components/SingleStat' import SingleStat from 'src/shared/components/SingleStat'
import timeSeriesToDygraph from 'utils/timeSeriesToDygraph' import timeSeriesToDygraph from 'utils/timeSeriesTransformers'
import {SINGLE_STAT_LINE_COLORS} from 'src/shared/graphs/helpers' import {SINGLE_STAT_LINE_COLORS} from 'src/shared/graphs/helpers'

View File

@ -0,0 +1,129 @@
import React from 'react'
import PropTypes from 'prop-types'
import Dygraph from './Dygraph'
import shallowCompare from 'react-addons-shallow-compare'
import timeSeriesToDygraph from 'utils/timeSeriesTransformers'
export default React.createClass({
displayName: 'MiniGraph',
propTypes: {
data: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
title: PropTypes.string,
queryDescription: PropTypes.string,
yRange: PropTypes.arrayOf(PropTypes.number.isRequired),
options: PropTypes.shape({
combineSeries: PropTypes.bool,
}),
},
getDefaultProps() {
return {
options: {},
}
},
shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState)
},
render() {
const results = timeSeriesToDygraph(this.props.data)
const {fields, timeSeries} = this.props.options.combineSeries
? this.combineSeries(results)
: results
if (!timeSeries.length) {
return null
}
const options = {
labels: fields,
showLabelsOnHighlight: false,
fillGraph: false,
connectSeparatedPoints: true,
axisLineColor: '#23232C',
gridLineColor: '#2E2E38',
gridLineWidth: 1,
strokeWidth: 1.5,
highlightCircleSize: 0,
highlightSeriesOpts: {
strokeWidth: 0,
highlightCircleSize: 0,
},
highlightCallback() {},
legend: 'never',
axes: {
x: {
drawGrid: false,
drawAxis: false,
},
y: {
drawGrid: false,
drawAxis: false,
},
},
title: this.props.title,
rightGap: 0,
yRangePad: 10,
interactionModel: {},
}
const truncPrecision = 100000
const latestValue = timeSeries[timeSeries.length - 1][1]
const truncated = Math.round(latestValue * truncPrecision) / truncPrecision
const statText = (
<div className="cluster-stat--label">
<span>{this.props.queryDescription}</span>
<span>
<strong>{truncated}</strong>
</span>
</div>
)
return (
<div className="cluster-stat">
<Dygraph
containerStyle={{width: '100%', height: '30px'}}
timeSeries={timeSeries}
fields={fields}
options={options}
yRange={this.props.yRange}
/>
{statText}
</div>
)
},
/**
* If we have a series with multiple points, sometimes we want to sum all
* values into a single value (e.g. on the overview page, where we might
* calculate active writes per node, but also across the entire cluster.
*
* [<timestamp>, 5, 10] => [<timestamp>, 15]
*/
combineSeries(results) {
const fields = results.fields.slice(0, 2) // Hack, but good enough for now for the sparklines (which have no labels).
const timeSeries = results.timeSeries
.filter(point => {
// Filter out any points that don't report results for *all* of the series
// we're trying to combine..
//
// e.g. [<timestamp>, null, null, 5] would be removed.
//
// We use `combineSeries` when we want to combine the values for multiple series
// into a single series. It makes sense to only report points where all
// series are represented, so we can accurately take the sum.
return point.slice(1).every(v => v !== null)
})
.map(point => {
const timestamp = point[0]
const total = point.slice(1).reduce((sum, n) => {
return n ? sum + n : sum
}, 0)
return [timestamp, total]
})
return {fields, timeSeries}
},
})

View File

@ -5,8 +5,13 @@ import classnames from 'classnames'
import {MultiGrid, ColumnSizer} from 'react-virtualized' import {MultiGrid, ColumnSizer} from 'react-virtualized'
import moment from 'moment' import moment from 'moment'
import {reduce} from 'fast.js'
import {
timeSeriesToTableGraph,
processTableData,
} from 'src/utils/timeSeriesTransformers'
import {timeSeriesToTableGraph} from 'src/utils/timeSeriesToDygraph'
import { import {
NULL_ARRAY_INDEX, NULL_ARRAY_INDEX,
NULL_HOVER_TIME, NULL_HOVER_TIME,
@ -14,51 +19,24 @@ import {
TIME_FIELD_DEFAULT, TIME_FIELD_DEFAULT,
ASCENDING, ASCENDING,
DESCENDING, DESCENDING,
DEFAULT_SORT,
FIX_FIRST_COLUMN_DEFAULT, FIX_FIRST_COLUMN_DEFAULT,
VERTICAL_TIME_AXIS_DEFAULT, VERTICAL_TIME_AXIS_DEFAULT,
calculateTimeColumnWidth, calculateTimeColumnWidth,
calculateLabelsColumnWidth, calculateLabelsColumnWidth,
} from 'src/shared/constants/tableGraph' } from 'src/shared/constants/tableGraph'
export const DEFAULT_SORT = ASCENDING
import {generateThresholdsListHexs} from 'shared/constants/colorOperations' import {generateThresholdsListHexs} from 'shared/constants/colorOperations'
export const filterInvisibleColumns = (data, fieldNames) => {
const visibility = {}
const filteredData = data.map((row, i) => {
return row.filter((col, j) => {
if (i === 0) {
const foundField = fieldNames.find(field => field.internalName === col)
visibility[j] = foundField ? foundField.visible : true
}
return visibility[j]
})
})
return filteredData[0].length ? filteredData : [[]]
}
export const processData = (
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
) => {
const sortIndex = _.indexOf(data[0], sortFieldName)
const sortedData = [
data[0],
..._.orderBy(_.drop(data, 1), sortIndex, [direction]),
]
const sortedTimeVals = sortedData.map(r => r[0])
const filteredData = filterInvisibleColumns(sortedData, fieldNames)
const processedData = verticalTimeAxis ? filteredData : _.unzip(filteredData)
return {processedData, sortedTimeVals}
}
class TableGraph extends Component { class TableGraph extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
const sortField = _.get(
this.props,
['tableOptions', 'sortBy', 'internalName'],
TIME_FIELD_DEFAULT.internalName
)
this.state = { this.state = {
data: [[]], data: [[]],
processedData: [[]], processedData: [[]],
@ -68,7 +46,7 @@ class TableGraph extends Component {
labelsColumnWidth: calculateLabelsColumnWidth(props.data.labels), labelsColumnWidth: calculateLabelsColumnWidth(props.data.labels),
hoveredColumnIndex: NULL_ARRAY_INDEX, hoveredColumnIndex: NULL_ARRAY_INDEX,
hoveredRowIndex: NULL_ARRAY_INDEX, hoveredRowIndex: NULL_ARRAY_INDEX,
sortField: 'time', sortField,
sortDirection: DEFAULT_SORT, sortDirection: DEFAULT_SORT,
} }
} }
@ -103,18 +81,17 @@ class TableGraph extends Component {
let direction, sortFieldName let direction, sortFieldName
if ( if (
_.isEmpty(sortField) || _.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') ===
_.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') !== internalName
_.get(nextProps, ['tableOptions', 'sortBy', 'internalName'], '')
) { ) {
direction = DEFAULT_SORT
sortFieldName = internalName
} else {
direction = sortDirection direction = sortDirection
sortFieldName = sortField sortFieldName = sortField
} else {
direction = DEFAULT_SORT
sortFieldName = internalName
} }
const {processedData, sortedTimeVals} = processData( const {processedData, sortedTimeVals} = processTableData(
data, data,
sortFieldName, sortFieldName,
direction, direction,
@ -126,6 +103,11 @@ class TableGraph extends Component {
? processedData[0] ? processedData[0]
: processedData.map(row => row[0]) : processedData.map(row => row[0])
const labelsColumnWidth = calculateLabelsColumnWidth(
processedLabels,
fieldNames
)
this.setState({ this.setState({
data, data,
labels, labels,
@ -133,10 +115,7 @@ class TableGraph extends Component {
sortedTimeVals, sortedTimeVals,
sortField: sortFieldName, sortField: sortFieldName,
sortDirection: direction, sortDirection: direction,
labelsColumnWidth: calculateLabelsColumnWidth( labelsColumnWidth,
processedLabels,
fieldNames
),
}) })
} }
@ -150,7 +129,8 @@ class TableGraph extends Component {
} }
const firstDiff = Math.abs(hoverTime - sortedTimeVals[1]) // sortedTimeVals[0] is "time" const firstDiff = Math.abs(hoverTime - sortedTimeVals[1]) // sortedTimeVals[0] is "time"
const hoverTimeFound = sortedTimeVals.reduce( const hoverTimeFound = reduce(
sortedTimeVals,
(acc, currentTime, index) => { (acc, currentTime, index) => {
const thisDiff = Math.abs(hoverTime - currentTime) const thisDiff = Math.abs(hoverTime - currentTime)
if (thisDiff < acc.diff) { if (thisDiff < acc.diff) {
@ -208,7 +188,7 @@ class TableGraph extends Component {
direction = DEFAULT_SORT direction = DEFAULT_SORT
} }
const {processedData, sortedTimeVals} = processData( const {processedData, sortedTimeVals} = processTableData(
data, data,
fieldName, fieldName,
direction, direction,
@ -379,6 +359,7 @@ class TableGraph extends Component {
const tableWidth = _.get(this, ['gridContainer', 'clientWidth'], 0) const tableWidth = _.get(this, ['gridContainer', 'clientWidth'], 0)
const tableHeight = _.get(this, ['gridContainer', 'clientHeight'], 0) const tableHeight = _.get(this, ['gridContainer', 'clientHeight'], 0)
const {scrollToColumn, scrollToRow} = this.calcScrollToColRow() const {scrollToColumn, scrollToRow} = this.calcScrollToColRow()
return ( return (
<div <div
className="table-graph-container" className="table-graph-container"

View File

@ -5,9 +5,6 @@ export const NULL_ARRAY_INDEX = -1
export const NULL_HOVER_TIME = '0' 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_FORMAT_TOOLTIP_LINK = export const TIME_FORMAT_TOOLTIP_LINK =
'http://momentjs.com/docs/#/parsing/string-format/' 'http://momentjs.com/docs/#/parsing/string-format/'
@ -19,20 +16,24 @@ export const TIME_FIELD_DEFAULT = {
export const ASCENDING = 'asc' export const ASCENDING = 'asc'
export const DESCENDING = 'desc' export const DESCENDING = 'desc'
export const DEFAULT_SORT = ASCENDING
export const FIX_FIRST_COLUMN_DEFAULT = true export const FIX_FIRST_COLUMN_DEFAULT = true
export const VERTICAL_TIME_AXIS_DEFAULT = true export const VERTICAL_TIME_AXIS_DEFAULT = true
export const CELL_HORIZONTAL_PADDING = 18 export const CELL_HORIZONTAL_PADDING = 18
export const TIME_FORMAT_DEFAULT = 'MM/DD/YYYY HH:mm:ss'
export const TIME_FORMAT_CUSTOM = 'Custom'
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:ss.SSS'},
{text: 'MM/DD/YYYY'}, {text: 'YYYY-MM-DD HH:mm:ss'},
{text: 'h:mm:ss A'}, {text: 'HH:mm:ss'},
{text: 'h:mm A'}, {text: 'HH:mm:ss.SSS'},
{text: 'MMMM D, YYYY'}, {text: 'MMMM D, YYYY HH:mm:ss'},
{text: 'MMMM D, YYYY h:mm A'}, {text: 'dddd, MMMM D, YYYY HH:mm:ss'},
{text: 'dddd, MMMM D, YYYY h:mm A'},
{text: TIME_FORMAT_CUSTOM}, {text: TIME_FORMAT_CUSTOM},
] ]
@ -51,6 +52,7 @@ export const calculateTimeColumnWidth = timeFormat => {
timeFormat = _.replace(timeFormat, 'dddd', 'Wednesday') timeFormat = _.replace(timeFormat, 'dddd', 'Wednesday')
timeFormat = _.replace(timeFormat, 'A', 'AM') timeFormat = _.replace(timeFormat, 'A', 'AM')
timeFormat = _.replace(timeFormat, 'h', '00') timeFormat = _.replace(timeFormat, 'h', '00')
timeFormat = _.replace(timeFormat, 'X', '1522286058')
const {width} = calculateSize(timeFormat, { const {width} = calculateSize(timeFormat, {
font: '"RobotoMono", monospace', font: '"RobotoMono", monospace',

View File

@ -1,6 +1,6 @@
import _ from 'lodash' import _ from 'lodash'
import {shiftDate} from 'shared/query/helpers' import {shiftDate} from 'shared/query/helpers'
import {map, reduce, forEach, concat, clone} from 'fast.js' import {map, reduce, filter, forEach, concat, clone} from 'fast.js'
/** /**
* Accepts an array of raw influxdb responses and returns a format * Accepts an array of raw influxdb responses and returns a format
@ -184,4 +184,37 @@ export const timeSeriesToTableGraph = raw => {
} }
} }
export const filterTableColumns = (data, fieldNames) => {
const visibility = {}
const filteredData = map(data, (row, i) => {
return filter(row, (col, j) => {
if (i === 0) {
const foundField = fieldNames.find(field => field.internalName === col)
visibility[j] = foundField ? foundField.visible : true
}
return visibility[j]
})
})
return filteredData[0].length ? filteredData : [[]]
}
export const processTableData = (
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
) => {
const sortIndex = _.indexOf(data[0], sortFieldName)
const sortedData = [
data[0],
..._.orderBy(_.drop(data, 1), sortIndex, [direction]),
]
const sortedTimeVals = map(sortedData, r => r[0])
const filteredData = filterTableColumns(sortedData, fieldNames)
const processedData = verticalTimeAxis ? filteredData : _.unzip(filteredData)
return {processedData, sortedTimeVals}
}
export default timeSeriesToDygraph export default timeSeriesToDygraph

View File

@ -1,223 +0,0 @@
import React from 'react'
import {shallow} from 'enzyme'
import TableGraph, {
filterInvisibleColumns,
processData,
DEFAULT_SORT,
} from 'src/shared/components/TableGraph'
const setup = (override = []) => {
const props = {
data: [],
tableOptions: {
timeFormat: '',
verticalTimeAxis: true,
sortBy: {
internalName: '',
displayName: '',
visible: true,
},
wrapping: '',
fieldNames: [],
fixFirstColumn: true,
},
hoverTime: '',
onSetHoverTime: () => {},
colors: [],
setDataLabels: () => {},
...override,
}
const data = [
['time', 'f1', 'f2'],
[1000, 3000, 2000],
[2000, 1000, 3000],
[3000, 2000, 1000],
]
const wrapper = shallow(<TableGraph {...props} />)
const instance = wrapper.instance() as TableGraph
return {wrapper, instance, props, data}
}
describe('Components.Shared.TableGraph', () => {
describe('functions', () => {
describe('filterInvisibleColumns', () => {
it("returns a nested array of that only include columns whose corresponding fieldName's visibility is true", () => {
const {data} = setup()
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: false},
]
const actual = filterInvisibleColumns(data, fieldNames)
const expected = [['time'], [1000], [2000], [3000]]
expect(actual).toEqual(expected)
})
it('returns an array of an empty array if all fieldNames are not visible', () => {
const {data} = setup()
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: false},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: false},
]
const actual = filterInvisibleColumns(data, fieldNames)
const expected = [[]]
expect(actual).toEqual(expected)
})
})
describe('processData', () => {
it('sorts the data based on the provided sortFieldName', () => {
const {data} = setup()
const sortFieldName = 'f1'
const direction = DEFAULT_SORT
const verticalTimeAxis = true
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: true},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = processData(
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [
['time', 'f1', 'f2'],
[2000, 1000, 3000],
[3000, 2000, 1000],
[1000, 3000, 2000],
]
expect(actual.processedData).toEqual(expected)
})
it('filters out invisible columns', () => {
const {data} = setup()
const sortFieldName = 'time'
const direction = DEFAULT_SORT
const verticalTimeAxis = true
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = processData(
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [
['time', 'f2'],
[1000, 2000],
[2000, 3000],
[3000, 1000],
]
expect(actual.processedData).toEqual(expected)
})
it('filters out invisible columns after sorting', () => {
const {data} = setup()
const sortFieldName = 'f1'
const direction = DEFAULT_SORT
const verticalTimeAxis = true
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = processData(
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [
['time', 'f2'],
[2000, 3000],
[3000, 1000],
[1000, 2000],
]
expect(actual.processedData).toEqual(expected)
})
describe('if verticalTimeAxis is false', () => {
it('transforms data', () => {
const {data} = setup()
const sortFieldName = 'time'
const direction = DEFAULT_SORT
const verticalTimeAxis = false
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: true},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = processData(
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [
['time', 1000, 2000, 3000],
['f1', 3000, 1000, 2000],
['f2', 2000, 3000, 1000],
]
expect(actual.processedData).toEqual(expected)
})
it('transforms data after filtering out invisible columns', () => {
const {data} = setup()
const sortFieldName = 'f1'
const direction = DEFAULT_SORT
const verticalTimeAxis = false
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = processData(
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [
['time', 2000, 3000, 1000],
['f2', 3000, 1000, 2000],
]
expect(actual.processedData).toEqual(expected)
})
})
})
})
})

View File

@ -1,6 +1,9 @@
import timeSeriesToDygraph, { import timeSeriesToDygraph, {
timeSeriesToTableGraph, timeSeriesToTableGraph,
} from 'src/utils/timeSeriesToDygraph' filterTableColumns,
processTableData,
} from 'src/utils/timeSeriesTransformers'
import {DEFAULT_SORT} from 'src/shared/constants/tableGraph'
describe('timeSeriesToDygraph', () => { describe('timeSeriesToDygraph', () => {
it('parses a raw InfluxDB response into a dygraph friendly data format', () => { it('parses a raw InfluxDB response into a dygraph friendly data format', () => {
@ -461,3 +464,201 @@ describe('timeSeriesToTableGraph', () => {
expect(actual.data).toEqual(expected) expect(actual.data).toEqual(expected)
}) })
}) })
describe('filterTableColumns', () => {
it("returns a nested array of fieldnamesthat only include columns whose corresponding fieldName's visibility is true", () => {
const data = [
['time', 'f1', 'f2'],
[1000, 3000, 2000],
[2000, 1000, 3000],
[3000, 2000, 1000],
]
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: false},
]
const actual = filterTableColumns(data, fieldNames)
const expected = [['time'], [1000], [2000], [3000]]
expect(actual).toEqual(expected)
})
it('returns an array of an empty array if all fieldNames are not visible', () => {
const data = [
['time', 'f1', 'f2'],
[1000, 3000, 2000],
[2000, 1000, 3000],
[3000, 2000, 1000],
]
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: false},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: false},
]
const actual = filterTableColumns(data, fieldNames)
const expected = [[]]
expect(actual).toEqual(expected)
})
})
describe('processTableData', () => {
it('sorts the data based on the provided sortFieldName', () => {
const data = [
['time', 'f1', 'f2'],
[1000, 3000, 2000],
[2000, 1000, 3000],
[3000, 2000, 1000],
]
const sortFieldName = 'f1'
const direction = DEFAULT_SORT
const verticalTimeAxis = true
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: true},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = processTableData(
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [
['time', 'f1', 'f2'],
[2000, 1000, 3000],
[3000, 2000, 1000],
[1000, 3000, 2000],
]
expect(actual.processedData).toEqual(expected)
})
it('filters out columns that should not be visible', () => {
const data = [
['time', 'f1', 'f2'],
[1000, 3000, 2000],
[2000, 1000, 3000],
[3000, 2000, 1000],
]
const sortFieldName = 'time'
const direction = DEFAULT_SORT
const verticalTimeAxis = true
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = processTableData(
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [['time', 'f2'], [1000, 2000], [2000, 3000], [3000, 1000]]
expect(actual.processedData).toEqual(expected)
})
it('filters out invisible columns after sorting', () => {
const data = [
['time', 'f1', 'f2'],
[1000, 3000, 2000],
[2000, 1000, 3000],
[3000, 2000, 1000],
]
const sortFieldName = 'f1'
const direction = DEFAULT_SORT
const verticalTimeAxis = true
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = processTableData(
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [['time', 'f2'], [2000, 3000], [3000, 1000], [1000, 2000]]
expect(actual.processedData).toEqual(expected)
})
})
describe('if verticalTimeAxis is false', () => {
it('transforms data', () => {
const data = [
['time', 'f1', 'f2'],
[1000, 3000, 2000],
[2000, 1000, 3000],
[3000, 2000, 1000],
]
const sortFieldName = 'time'
const direction = DEFAULT_SORT
const verticalTimeAxis = false
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: true},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = processTableData(
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [
['time', 1000, 2000, 3000],
['f1', 3000, 1000, 2000],
['f2', 2000, 3000, 1000],
]
expect(actual.processedData).toEqual(expected)
})
it('transforms data after filtering out invisible columns', () => {
const data = [
['time', 'f1', 'f2'],
[1000, 3000, 2000],
[2000, 1000, 3000],
[3000, 2000, 1000],
]
const sortFieldName = 'f1'
const direction = DEFAULT_SORT
const verticalTimeAxis = false
const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = processTableData(
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [['time', 2000, 3000, 1000], ['f2', 3000, 1000, 2000]]
expect(actual.processedData).toEqual(expected)
})
})