Merge pull request #3090 from influxdata/feature/table-graph-polish
Feature/table graph polishpull/3101/head
commit
f6c5222b51
|
@ -4349,39 +4349,141 @@
|
|||
"format": "uuid4"
|
||||
},
|
||||
"x": {
|
||||
"description": "X-coordinate of Cell in the Layout",
|
||||
"description": "X-coordinate of Cell in the Dashboard",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"y": {
|
||||
"description": "Y-coordinate of Cell in the Layout",
|
||||
"description": "Y-coordinate of Cell in the Dashboard",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"w": {
|
||||
"description": "Width of Cell in the Layout",
|
||||
"description": "Width of Cell in the Dashboard",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"minimum": 1,
|
||||
"default": 4
|
||||
},
|
||||
"h": {
|
||||
"description": "Height of Cell in the Layout",
|
||||
"description": "Height of Cell in the Dashboard",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
"format": "int32",
|
||||
"minimum": 1,
|
||||
"default": 4
|
||||
},
|
||||
"name": {
|
||||
"description": "Cell name",
|
||||
"description": "Title of Cell in the Dashboard",
|
||||
"type": "string"
|
||||
},
|
||||
"queries": {
|
||||
"description": "Time-series data queries for Cell.",
|
||||
"description": "Time-series data queries for Cell",
|
||||
"type": "array",
|
||||
"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": {
|
||||
"description": "Cell visualization type",
|
||||
"type": "string"
|
||||
"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"
|
||||
},
|
||||
"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": {
|
||||
|
@ -4558,154 +4660,9 @@
|
|||
"format": "int64"
|
||||
},
|
||||
"cells": {
|
||||
"description": "a list of dashboard visualizations",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"description": "cell visualization information",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"$ref": "#/definitions/Cell"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
|
|
|
@ -4,7 +4,7 @@ import Dygraph from 'shared/components/Dygraph'
|
|||
import shallowCompare from 'react-addons-shallow-compare'
|
||||
|
||||
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'
|
||||
|
||||
|
|
|
@ -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}
|
||||
},
|
||||
})
|
|
@ -5,8 +5,13 @@ import classnames from 'classnames'
|
|||
|
||||
import {MultiGrid, ColumnSizer} from 'react-virtualized'
|
||||
import moment from 'moment'
|
||||
import {reduce} from 'fast.js'
|
||||
|
||||
import {
|
||||
timeSeriesToTableGraph,
|
||||
processTableData,
|
||||
} from 'src/utils/timeSeriesTransformers'
|
||||
|
||||
import {timeSeriesToTableGraph} from 'src/utils/timeSeriesToDygraph'
|
||||
import {
|
||||
NULL_ARRAY_INDEX,
|
||||
NULL_HOVER_TIME,
|
||||
|
@ -14,51 +19,24 @@ import {
|
|||
TIME_FIELD_DEFAULT,
|
||||
ASCENDING,
|
||||
DESCENDING,
|
||||
DEFAULT_SORT,
|
||||
FIX_FIRST_COLUMN_DEFAULT,
|
||||
VERTICAL_TIME_AXIS_DEFAULT,
|
||||
calculateTimeColumnWidth,
|
||||
calculateLabelsColumnWidth,
|
||||
} from 'src/shared/constants/tableGraph'
|
||||
export const DEFAULT_SORT = ASCENDING
|
||||
|
||||
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 {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const sortField = _.get(
|
||||
this.props,
|
||||
['tableOptions', 'sortBy', 'internalName'],
|
||||
TIME_FIELD_DEFAULT.internalName
|
||||
)
|
||||
this.state = {
|
||||
data: [[]],
|
||||
processedData: [[]],
|
||||
|
@ -68,7 +46,7 @@ class TableGraph extends Component {
|
|||
labelsColumnWidth: calculateLabelsColumnWidth(props.data.labels),
|
||||
hoveredColumnIndex: NULL_ARRAY_INDEX,
|
||||
hoveredRowIndex: NULL_ARRAY_INDEX,
|
||||
sortField: 'time',
|
||||
sortField,
|
||||
sortDirection: DEFAULT_SORT,
|
||||
}
|
||||
}
|
||||
|
@ -103,18 +81,17 @@ class TableGraph extends Component {
|
|||
|
||||
let direction, sortFieldName
|
||||
if (
|
||||
_.isEmpty(sortField) ||
|
||||
_.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') !==
|
||||
_.get(nextProps, ['tableOptions', 'sortBy', 'internalName'], '')
|
||||
_.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') ===
|
||||
internalName
|
||||
) {
|
||||
direction = DEFAULT_SORT
|
||||
sortFieldName = internalName
|
||||
} else {
|
||||
direction = sortDirection
|
||||
sortFieldName = sortField
|
||||
} else {
|
||||
direction = DEFAULT_SORT
|
||||
sortFieldName = internalName
|
||||
}
|
||||
|
||||
const {processedData, sortedTimeVals} = processData(
|
||||
const {processedData, sortedTimeVals} = processTableData(
|
||||
data,
|
||||
sortFieldName,
|
||||
direction,
|
||||
|
@ -126,6 +103,11 @@ class TableGraph extends Component {
|
|||
? processedData[0]
|
||||
: processedData.map(row => row[0])
|
||||
|
||||
const labelsColumnWidth = calculateLabelsColumnWidth(
|
||||
processedLabels,
|
||||
fieldNames
|
||||
)
|
||||
|
||||
this.setState({
|
||||
data,
|
||||
labels,
|
||||
|
@ -133,10 +115,7 @@ class TableGraph extends Component {
|
|||
sortedTimeVals,
|
||||
sortField: sortFieldName,
|
||||
sortDirection: direction,
|
||||
labelsColumnWidth: calculateLabelsColumnWidth(
|
||||
processedLabels,
|
||||
fieldNames
|
||||
),
|
||||
labelsColumnWidth,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -150,7 +129,8 @@ class TableGraph extends Component {
|
|||
}
|
||||
|
||||
const firstDiff = Math.abs(hoverTime - sortedTimeVals[1]) // sortedTimeVals[0] is "time"
|
||||
const hoverTimeFound = sortedTimeVals.reduce(
|
||||
const hoverTimeFound = reduce(
|
||||
sortedTimeVals,
|
||||
(acc, currentTime, index) => {
|
||||
const thisDiff = Math.abs(hoverTime - currentTime)
|
||||
if (thisDiff < acc.diff) {
|
||||
|
@ -208,7 +188,7 @@ class TableGraph extends Component {
|
|||
direction = DEFAULT_SORT
|
||||
}
|
||||
|
||||
const {processedData, sortedTimeVals} = processData(
|
||||
const {processedData, sortedTimeVals} = processTableData(
|
||||
data,
|
||||
fieldName,
|
||||
direction,
|
||||
|
@ -379,6 +359,7 @@ class TableGraph extends Component {
|
|||
const tableWidth = _.get(this, ['gridContainer', 'clientWidth'], 0)
|
||||
const tableHeight = _.get(this, ['gridContainer', 'clientHeight'], 0)
|
||||
const {scrollToColumn, scrollToRow} = this.calcScrollToColRow()
|
||||
|
||||
return (
|
||||
<div
|
||||
className="table-graph-container"
|
||||
|
|
|
@ -5,9 +5,6 @@ export const NULL_ARRAY_INDEX = -1
|
|||
|
||||
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 =
|
||||
'http://momentjs.com/docs/#/parsing/string-format/'
|
||||
|
||||
|
@ -19,20 +16,24 @@ export const TIME_FIELD_DEFAULT = {
|
|||
|
||||
export const ASCENDING = 'asc'
|
||||
export const DESCENDING = 'desc'
|
||||
export const DEFAULT_SORT = ASCENDING
|
||||
|
||||
export const FIX_FIRST_COLUMN_DEFAULT = true
|
||||
export const VERTICAL_TIME_AXIS_DEFAULT = true
|
||||
|
||||
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 = [
|
||||
{text: TIME_FORMAT_DEFAULT},
|
||||
{text: 'MM/DD/YYYY HH:mm'},
|
||||
{text: 'MM/DD/YYYY'},
|
||||
{text: 'h:mm:ss A'},
|
||||
{text: 'h:mm A'},
|
||||
{text: 'MMMM D, YYYY'},
|
||||
{text: 'MMMM D, YYYY h:mm A'},
|
||||
{text: 'dddd, MMMM D, YYYY h:mm A'},
|
||||
{text: 'MM/DD/YYYY HH:mm:ss.SSS'},
|
||||
{text: 'YYYY-MM-DD HH:mm:ss'},
|
||||
{text: 'HH:mm:ss'},
|
||||
{text: 'HH:mm:ss.SSS'},
|
||||
{text: 'MMMM D, YYYY HH:mm:ss'},
|
||||
{text: 'dddd, MMMM D, YYYY HH:mm:ss'},
|
||||
{text: TIME_FORMAT_CUSTOM},
|
||||
]
|
||||
|
||||
|
@ -51,6 +52,7 @@ export const calculateTimeColumnWidth = timeFormat => {
|
|||
timeFormat = _.replace(timeFormat, 'dddd', 'Wednesday')
|
||||
timeFormat = _.replace(timeFormat, 'A', 'AM')
|
||||
timeFormat = _.replace(timeFormat, 'h', '00')
|
||||
timeFormat = _.replace(timeFormat, 'X', '1522286058')
|
||||
|
||||
const {width} = calculateSize(timeFormat, {
|
||||
font: '"RobotoMono", monospace',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import _ from 'lodash'
|
||||
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
|
||||
|
@ -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
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,6 +1,9 @@
|
|||
import timeSeriesToDygraph, {
|
||||
timeSeriesToTableGraph,
|
||||
} from 'src/utils/timeSeriesToDygraph'
|
||||
filterTableColumns,
|
||||
processTableData,
|
||||
} from 'src/utils/timeSeriesTransformers'
|
||||
import {DEFAULT_SORT} from 'src/shared/constants/tableGraph'
|
||||
|
||||
describe('timeSeriesToDygraph', () => {
|
||||
it('parses a raw InfluxDB response into a dygraph friendly data format', () => {
|
||||
|
@ -461,3 +464,201 @@ describe('timeSeriesToTableGraph', () => {
|
|||
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)
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue