diff --git a/server/swagger.json b/server/swagger.json index 2e6394b48..508ee2be0 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -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": { diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 7cb2ca484..25905e317 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -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' diff --git a/ui/src/shared/components/MiniGraph.js b/ui/src/shared/components/MiniGraph.js new file mode 100644 index 000000000..2b25a1547 --- /dev/null +++ b/ui/src/shared/components/MiniGraph.js @@ -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 = ( +
+ {this.props.queryDescription} + + {truncated} + +
+ ) + + return ( +
+ + {statText} +
+ ) + }, + + /** + * 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. + * + * [, 5, 10] => [, 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. [, 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} + }, +}) diff --git a/ui/src/shared/components/TableGraph.js b/ui/src/shared/components/TableGraph.js index f2b8d98c9..60edc76fc 100644 --- a/ui/src/shared/components/TableGraph.js +++ b/ui/src/shared/components/TableGraph.js @@ -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 (
{ 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', diff --git a/ui/src/utils/timeSeriesToDygraph.js b/ui/src/utils/timeSeriesTransformers.js similarity index 82% rename from ui/src/utils/timeSeriesToDygraph.js rename to ui/src/utils/timeSeriesTransformers.js index 7426a79bc..0a5db50ba 100644 --- a/ui/src/utils/timeSeriesToDygraph.js +++ b/ui/src/utils/timeSeriesTransformers.js @@ -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 diff --git a/ui/test/shared/components/TableGraph.test.tsx b/ui/test/shared/components/TableGraph.test.tsx deleted file mode 100644 index f8547f85f..000000000 --- a/ui/test/shared/components/TableGraph.test.tsx +++ /dev/null @@ -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() - 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) - }) - }) - }) - }) -}) diff --git a/ui/test/utils/timeSeriesToDygraph.test.js b/ui/test/utils/timeSeriesTransformers.test.js similarity index 66% rename from ui/test/utils/timeSeriesToDygraph.test.js rename to ui/test/utils/timeSeriesTransformers.test.js index c5df3a879..f55c75f7b 100644 --- a/ui/test/utils/timeSeriesToDygraph.test.js +++ b/ui/test/utils/timeSeriesTransformers.test.js @@ -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) + }) +})