Merge pull request #3282 from influxdata/table/group-by

Table/group by
pull/10616/head
Deniz Kusefoglu 2018-04-24 13:43:47 -07:00 committed by GitHub
commit d2af17d1e5
16 changed files with 636 additions and 426 deletions

View File

@ -82,7 +82,10 @@ const createWorkingDraft = (source: string, query: CellQuery): Query => {
return draft return draft
} }
const createWorkingDrafts = (source: string, queries: CellQuery[]): Query[] => const createWorkingDrafts = (
source: string,
queries: CellQuery[] = []
): Query[] =>
_.cloneDeep( _.cloneDeep(
queries.map((query: CellQuery) => createWorkingDraft(source, query)) queries.map((query: CellQuery) => createWorkingDraft(source, query))
) )
@ -179,6 +182,7 @@ class CellEditorOverlay extends Component<Props, State> {
queryConfigs={queriesWorkingDraft} queryConfigs={queriesWorkingDraft}
editQueryStatus={editQueryStatus} editQueryStatus={editQueryStatus}
staticLegend={isStaticLegend} staticLegend={isStaticLegend}
isInCEO={true}
/> />
<CEOBottom> <CEOBottom>
<OverlayControls <OverlayControls

View File

@ -1,4 +1,4 @@
import React, {SFC} from 'react' import React, {PureComponent} from 'react'
import {DragDropContext} from 'react-dnd' import {DragDropContext} from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend' import HTML5Backend from 'react-dnd-html5-backend'
@ -10,35 +10,36 @@ interface RenamableField {
visible: boolean visible: boolean
} }
interface GraphOptionsCustomizeFieldsProps { interface Props {
fields: RenamableField[] fields: RenamableField[]
onFieldUpdate: (field: RenamableField) => void onFieldUpdate: (field: RenamableField) => void
moveField: (dragIndex: number, hoverIndex: number) => void moveField: (dragIndex: number, hoverIndex: number) => void
} }
const GraphOptionsCustomizeFields: SFC<GraphOptionsCustomizeFieldsProps> = ({
fields, class GraphOptionsCustomizeFields extends PureComponent<Props> {
onFieldUpdate, public render() {
moveField, const {fields, onFieldUpdate, moveField} = this.props
}) => {
return ( return (
<div className="graph-options-group"> <div className="graph-options-group">
<label className="form-label">Customize Fields</label> <label className="form-label">Customize Fields</label>
<div> <div>
{fields.map((field, i) => ( {fields.map((field, i) => (
<GraphOptionsCustomizableField <GraphOptionsCustomizableField
key={field.internalName} key={field.internalName}
index={i} index={i}
id={field.internalName} id={field.internalName}
internalName={field.internalName} internalName={field.internalName}
displayName={field.displayName} displayName={field.displayName}
visible={field.visible} visible={field.visible}
onFieldUpdate={onFieldUpdate} onFieldUpdate={onFieldUpdate}
moveField={moveField} moveField={moveField}
/> />
))} ))}
</div>
</div> </div>
</div> )
) }
} }
export default DragDropContext(HTML5Backend)(GraphOptionsCustomizeFields) export default DragDropContext(HTML5Backend)(GraphOptionsCustomizeFields)

View File

@ -4,7 +4,7 @@ import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip'
import { import {
FORMAT_OPTIONS, FORMAT_OPTIONS,
TIME_FORMAT_CUSTOM, TIME_FORMAT_CUSTOM,
TIME_FORMAT_DEFAULT, DEFAULT_TIME_FORMAT,
TIME_FORMAT_TOOLTIP_LINK, TIME_FORMAT_TOOLTIP_LINK,
} from 'src/shared/constants/tableGraph' } from 'src/shared/constants/tableGraph'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
@ -29,7 +29,7 @@ class GraphOptionsTimeFormat extends PureComponent<Props, State> {
super(props) super(props)
this.state = { this.state = {
customFormat: false, customFormat: false,
format: this.props.timeFormat || TIME_FORMAT_DEFAULT, format: this.props.timeFormat || DEFAULT_TIME_FORMAT,
} }
} }

View File

@ -52,24 +52,6 @@ export class TableOptions extends Component<Props, {}> {
this.moveField = this.moveField.bind(this) this.moveField = this.moveField.bind(this)
} }
public componentWillMount() {
const {handleUpdateTableOptions, tableOptions} = this.props
handleUpdateTableOptions({
...tableOptions,
fieldNames: this.computedFieldNames,
})
}
public shouldComponentUpdate(nextProps) {
const {tableOptions} = this.props
const tableOptionsDifferent = !_.isEqual(
tableOptions,
nextProps.tableOptions
)
return tableOptionsDifferent
}
public render() { public render() {
const { const {
tableOptions: {timeFormat, fieldNames, verticalTimeAxis, fixFirstColumn}, tableOptions: {timeFormat, fieldNames, verticalTimeAxis, fixFirstColumn},
@ -122,28 +104,14 @@ export class TableOptions extends Component<Props, {}> {
) )
} }
private get fieldNames() {
const {
tableOptions: {fieldNames},
} = this.props
return fieldNames || []
}
private get timeField() {
return (
this.fieldNames.find(f => f.internalName === 'time') || TIME_FIELD_DEFAULT
)
}
private moveField(dragIndex, hoverIndex) { private moveField(dragIndex, hoverIndex) {
const {handleUpdateTableOptions, tableOptions} = this.props const {handleUpdateTableOptions, tableOptions} = this.props
const {fieldNames} = tableOptions const {fieldNames} = tableOptions
const fields = fieldNames.length > 1 ? fieldNames : this.computedFieldNames
const dragField = fields[dragIndex] const dragField = fieldNames[dragIndex]
const removedFields = _.concat( const removedFields = _.concat(
_.slice(fields, 0, dragIndex), _.slice(fieldNames, 0, dragIndex),
_.slice(fields, dragIndex + 1) _.slice(fieldNames, dragIndex + 1)
) )
const addedFields = _.concat( const addedFields = _.concat(
_.slice(removedFields, 0, hoverIndex), _.slice(removedFields, 0, hoverIndex),
@ -156,23 +124,6 @@ export class TableOptions extends Component<Props, {}> {
}) })
} }
private get computedFieldNames() {
const {queryConfigs} = this.props
const queryFields = _.flatten(
queryConfigs.map(({measurement, fields}) => {
return fields.map(({alias}) => {
const internalName = `${measurement}.${alias}`
const existing = this.fieldNames.find(
c => c.internalName === internalName
)
return existing || {internalName, displayName: '', visible: true}
})
})
)
return [this.timeField, ...queryFields]
}
private handleChooseSortBy = (option: Option) => { private handleChooseSortBy = (option: Option) => {
const {tableOptions, handleUpdateTableOptions} = this.props const {tableOptions, handleUpdateTableOptions} = this.props
const sortBy = { const sortBy = {

View File

@ -24,6 +24,7 @@ const DashVisualization = (
staticLegend, staticLegend,
thresholdsListColors, thresholdsListColors,
tableOptions, tableOptions,
isInCEO,
}, },
{ {
source: { source: {
@ -53,6 +54,7 @@ const DashVisualization = (
editQueryStatus={editQueryStatus} editQueryStatus={editQueryStatus}
resizerTopHeight={resizerTopHeight} resizerTopHeight={resizerTopHeight}
staticLegend={staticLegend} staticLegend={staticLegend}
isInCEO={isInCEO}
/> />
</div> </div>
</div> </div>
@ -82,6 +84,7 @@ DashVisualization.propTypes = {
gaugeColors: colorsNumberSchema, gaugeColors: colorsNumberSchema,
lineColors: colorsStringSchema, lineColors: colorsStringSchema,
staticLegend: bool, staticLegend: bool,
isInCEO: bool,
} }
DashVisualization.contextTypes = { DashVisualization.contextTypes = {

View File

@ -363,7 +363,6 @@ class DashboardPage extends Component {
name: d.name, name: d.name,
link: `/sources/${sourceID}/dashboards/${d.id}`, link: `/sources/${sourceID}/dashboards/${d.id}`,
})) }))
return ( return (
<div className="page dashboard-page"> <div className="page dashboard-page">
{isTemplating ? ( {isTemplating ? (
@ -566,6 +565,7 @@ const mapStateToProps = (state, {params: {dashboardID}}) => {
const dashboard = dashboards.find( const dashboard = dashboards.find(
d => d.id === idNormalizer(TYPE_ID, dashboardID) d => d.id === idNormalizer(TYPE_ID, dashboardID)
) )
const selectedCell = cell const selectedCell = cell
return { return {

View File

@ -1,11 +1,11 @@
import calculateSize from 'calculate-size' import calculateSize from 'calculate-size'
import _ from 'lodash' import _ from 'lodash'
import {reduce} from 'fast.js' import {map, reduce, filter} from 'fast.js'
import { import {
CELL_HORIZONTAL_PADDING, CELL_HORIZONTAL_PADDING,
TIME_FIELD_DEFAULT, TIME_FIELD_DEFAULT,
TIME_FORMAT_DEFAULT, DEFAULT_TIME_FORMAT,
} from 'src/shared/constants/tableGraph' } from 'src/shared/constants/tableGraph'
const calculateTimeColumnWidth = timeFormat => { const calculateTimeColumnWidth = timeFormat => {
@ -77,6 +77,28 @@ const updateMaxWidths = (
) )
} }
export const computeFieldNames = (existingFieldNames, sortedLabels) => {
const timeField =
existingFieldNames.find(f => f.internalName === 'time') ||
TIME_FIELD_DEFAULT
let astNames = [timeField]
sortedLabels.forEach(({label}) => {
const field = {internalName: label, displayName: '', visible: true}
astNames = [...astNames, field]
})
const intersection = existingFieldNames.filter(f => {
return astNames.find(a => a.internalName === f.internalName)
})
const newFields = astNames.filter(a => {
return !existingFieldNames.find(f => f.internalName === a.internalName)
})
return [...intersection, ...newFields]
}
export const calculateColumnWidths = ( export const calculateColumnWidths = (
data, data,
fieldNames, fieldNames,
@ -84,7 +106,7 @@ export const calculateColumnWidths = (
verticalTimeAxis verticalTimeAxis
) => { ) => {
const timeFormatWidth = calculateTimeColumnWidth( const timeFormatWidth = calculateTimeColumnWidth(
timeFormat === '' ? TIME_FORMAT_DEFAULT : timeFormat timeFormat === '' ? DEFAULT_TIME_FORMAT : timeFormat
) )
return reduce( return reduce(
data, data,
@ -102,3 +124,50 @@ export const calculateColumnWidths = (
{widths: {}, totalWidths: 0} {widths: {}, totalWidths: 0}
) )
} }
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 orderTableColumns = (data, fieldNames) => {
const fieldsSortOrder = fieldNames.map(fieldName => {
return _.findIndex(data[0], dataLabel => {
return dataLabel === fieldName.internalName
})
})
const filteredFieldSortOrder = filter(fieldsSortOrder, f => f !== -1)
const orderedData = map(data, row => {
return row.map((v, j, arr) => arr[filteredFieldSortOrder[j]] || v)
})
return orderedData[0].length ? orderedData : [[]]
}
export const transformTableData = (data, sort, fieldNames, tableOptions) => {
const {verticalTimeAxis, timeFormat} = tableOptions
const sortIndex = _.indexOf(data[0], sort.field)
const sortedData = [
data[0],
..._.orderBy(_.drop(data, 1), sortIndex, [sort.direction]),
]
const sortedTimeVals = map(sortedData, r => r[0])
const filteredData = filterTableColumns(sortedData, fieldNames)
const orderedData = orderTableColumns(filteredData, fieldNames)
const transformedData = verticalTimeAxis ? orderedData : _.unzip(orderedData)
const {widths: columnWidths, totalWidths} = calculateColumnWidths(
transformedData,
fieldNames,
timeFormat,
verticalTimeAxis
)
return {transformedData, sortedTimeVals, columnWidths, totalWidths}
}

View File

@ -5,6 +5,7 @@ import _ from 'lodash'
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries' import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
import {removeUnselectedTemplateValues} from 'src/dashboards/constants' import {removeUnselectedTemplateValues} from 'src/dashboards/constants'
import {intervalValuesPoints} from 'src/shared/constants' import {intervalValuesPoints} from 'src/shared/constants'
import {getQueryConfig} from 'shared/apis'
const AutoRefresh = ComposedComponent => { const AutoRefresh = ComposedComponent => {
class wrapper extends Component { class wrapper extends Component {
@ -14,12 +15,17 @@ const AutoRefresh = ComposedComponent => {
lastQuerySuccessful: true, lastQuerySuccessful: true,
timeSeries: [], timeSeries: [],
resolution: null, resolution: null,
queryASTs: [],
} }
} }
componentDidMount() { async componentDidMount() {
const {queries, templates, autoRefresh} = this.props const {queries, templates, autoRefresh, type} = this.props
this.executeQueries(queries, templates) this.executeQueries(queries, templates)
if (type === 'table') {
const queryASTs = await this.getQueryASTs(queries, templates)
this.setState({queryASTs})
}
if (autoRefresh) { if (autoRefresh) {
this.intervalID = setInterval( this.intervalID = setInterval(
() => this.executeQueries(queries, templates), () => this.executeQueries(queries, templates),
@ -28,7 +34,19 @@ const AutoRefresh = ComposedComponent => {
} }
} }
componentWillReceiveProps(nextProps) { getQueryASTs = async (queries, templates) => {
return await Promise.all(
queries.map(async q => {
const host = _.isArray(q.host) ? q.host[0] : q.host
const url = host.replace('proxy', 'queries')
const text = q.text
const {data} = await getQueryConfig(url, [{query: text}], templates)
return data.queries[0].queryAST
})
)
}
async componentWillReceiveProps(nextProps) {
const inViewDidUpdate = this.props.inView !== nextProps.inView const inViewDidUpdate = this.props.inView !== nextProps.inView
const queriesDidUpdate = this.queryDifference( const queriesDidUpdate = this.queryDifference(
@ -45,6 +63,14 @@ const AutoRefresh = ComposedComponent => {
queriesDidUpdate || tempVarsDidUpdate || inViewDidUpdate queriesDidUpdate || tempVarsDidUpdate || inViewDidUpdate
if (shouldRefetch) { if (shouldRefetch) {
if (this.props.type === 'table') {
const queryASTs = await this.getQueryASTs(
nextProps.queries,
nextProps.templates
)
this.setState({queryASTs})
}
this.executeQueries( this.executeQueries(
nextProps.queries, nextProps.queries,
nextProps.templates, nextProps.templates,
@ -169,8 +195,7 @@ const AutoRefresh = ComposedComponent => {
} }
render() { render() {
const {timeSeries} = this.state const {timeSeries, queryASTs} = this.state
if (this.state.isFetching && this.state.lastQuerySuccessful) { if (this.state.isFetching && this.state.lastQuerySuccessful) {
return ( return (
<ComposedComponent <ComposedComponent
@ -179,6 +204,7 @@ const AutoRefresh = ComposedComponent => {
setResolution={this.setResolution} setResolution={this.setResolution}
isFetchingInitially={false} isFetchingInitially={false}
isRefreshing={true} isRefreshing={true}
queryASTs={queryASTs}
/> />
) )
} }
@ -188,6 +214,7 @@ const AutoRefresh = ComposedComponent => {
{...this.props} {...this.props}
data={timeSeries} data={timeSeries}
setResolution={this.setResolution} setResolution={this.setResolution}
queryASTs={queryASTs}
/> />
) )
} }
@ -221,6 +248,7 @@ const AutoRefresh = ComposedComponent => {
} = PropTypes } = PropTypes
wrapper.propTypes = { wrapper.propTypes = {
type: string.isRequired,
children: element, children: element,
autoRefresh: number.isRequired, autoRefresh: number.isRequired,
inView: bool, inView: bool,

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import Dygraph from 'shared/components/Dygraph' import Dygraph from 'shared/components/Dygraph'
import SingleStat from 'src/shared/components/SingleStat' import SingleStat from 'src/shared/components/SingleStat'
import timeSeriesToDygraph from 'utils/timeSeriesTransformers' import {timeSeriesToDygraph} from 'utils/timeSeriesTransformers'
import {colorsStringSchema} from 'shared/schemas' import {colorsStringSchema} from 'shared/schemas'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'

View File

@ -40,6 +40,7 @@ const RefreshingGraph = ({
editQueryStatus, editQueryStatus,
handleSetHoverTime, handleSetHoverTime,
grabDataForDownload, grabDataForDownload,
isInCEO,
}) => { }) => {
const prefix = (axes && axes.y.prefix) || '' const prefix = (axes && axes.y.prefix) || ''
const suffix = (axes && axes.y.suffix) || '' const suffix = (axes && axes.y.suffix) || ''
@ -55,6 +56,7 @@ const RefreshingGraph = ({
if (type === 'single-stat') { if (type === 'single-stat') {
return ( return (
<RefreshingSingleStat <RefreshingSingleStat
type={type}
colors={colors} colors={colors}
key={manualRefresh} key={manualRefresh}
queries={[queries[0]]} queries={[queries[0]]}
@ -71,6 +73,7 @@ const RefreshingGraph = ({
if (type === 'gauge') { if (type === 'gauge') {
return ( return (
<RefreshingGaugeChart <RefreshingGaugeChart
type={type}
colors={colors} colors={colors}
key={manualRefresh} key={manualRefresh}
queries={[queries[0]]} queries={[queries[0]]}
@ -90,6 +93,7 @@ const RefreshingGraph = ({
if (type === 'table') { if (type === 'table') {
return ( return (
<RefreshingTableGraph <RefreshingTableGraph
type={type}
cellID={cellID} cellID={cellID}
colors={colors} colors={colors}
inView={inView} inView={inView}
@ -103,6 +107,7 @@ const RefreshingGraph = ({
tableOptions={tableOptions} tableOptions={tableOptions}
resizerTopHeight={resizerTopHeight} resizerTopHeight={resizerTopHeight}
handleSetHoverTime={handleSetHoverTime} handleSetHoverTime={handleSetHoverTime}
isInCEO={isInCEO}
/> />
) )
} }
@ -114,6 +119,7 @@ const RefreshingGraph = ({
return ( return (
<RefreshingLineGraph <RefreshingLineGraph
type={type}
axes={axes} axes={axes}
cellID={cellID} cellID={cellID}
colors={colors} colors={colors}
@ -161,6 +167,7 @@ RefreshingGraph.propTypes = {
tableOptions: shape({}), tableOptions: shape({}),
hoverTime: string.isRequired, hoverTime: string.isRequired,
handleSetHoverTime: func.isRequired, handleSetHoverTime: func.isRequired,
isInCEO: bool,
} }
RefreshingGraph.defaultProps = { RefreshingGraph.defaultProps = {

View File

@ -2,26 +2,30 @@ import React, {Component} from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import _ from 'lodash' import _ from 'lodash'
import classnames from 'classnames' import classnames from 'classnames'
import {connect} from 'react-redux'
import {MultiGrid, ColumnSizer} from 'react-virtualized' import {MultiGrid, ColumnSizer} from 'react-virtualized'
import {bindActionCreators} from 'redux'
import moment from 'moment' import moment from 'moment'
import {reduce} from 'fast.js' import {reduce} from 'fast.js'
const {arrayOf, bool, shape, string, func} = PropTypes const {arrayOf, bool, shape, string, func} = PropTypes
import {timeSeriesToTableGraph} from 'src/utils/timeSeriesTransformers'
import { import {
timeSeriesToTableGraph, computeFieldNames,
processTableData, transformTableData,
} from 'src/utils/timeSeriesTransformers' } from 'src/dashboards/utils/tableGraph'
import {updateTableOptions} from 'src/dashboards/actions/cellEditorOverlay'
import { import {
NULL_ARRAY_INDEX, NULL_ARRAY_INDEX,
NULL_HOVER_TIME, NULL_HOVER_TIME,
TIME_FORMAT_DEFAULT, DEFAULT_TIME_FORMAT,
TIME_FIELD_DEFAULT, TIME_FIELD_DEFAULT,
ASCENDING, ASCENDING,
DESCENDING, DESCENDING,
DEFAULT_SORT, DEFAULT_SORT_DIRECTION,
FIX_FIRST_COLUMN_DEFAULT, FIX_FIRST_COLUMN_DEFAULT,
VERTICAL_TIME_AXIS_DEFAULT, VERTICAL_TIME_AXIS_DEFAULT,
} from 'src/shared/constants/tableGraph' } from 'src/shared/constants/tableGraph'
@ -40,70 +44,87 @@ class TableGraph extends Component {
['tableOptions', 'sortBy', 'internalName'], ['tableOptions', 'sortBy', 'internalName'],
TIME_FIELD_DEFAULT.internalName TIME_FIELD_DEFAULT.internalName
) )
this.state = { this.state = {
data: [[]], data: [[]],
processedData: [[]], transformedData: [[]],
sortedTimeVals: [], sortedTimeVals: [],
sortedLabels: [],
hoveredColumnIndex: NULL_ARRAY_INDEX, hoveredColumnIndex: NULL_ARRAY_INDEX,
hoveredRowIndex: NULL_ARRAY_INDEX, hoveredRowIndex: NULL_ARRAY_INDEX,
sortField, sort: {field: sortField, direction: DEFAULT_SORT_DIRECTION},
sortDirection: DEFAULT_SORT,
columnWidths: {}, columnWidths: {},
totalColumnWidths: 0, totalColumnWidths: 0,
} }
} }
handleUpdateTableOptions = (fieldNames, tableOptions) => {
const {isInCEO} = this.props
if (!isInCEO) {
return
}
this.props.handleUpdateTableOptions({...tableOptions, fieldNames})
}
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const {data} = timeSeriesToTableGraph(nextProps.data) const updatedProps = _.keys(nextProps).filter(
k => !_.isEqual(this.props[k], nextProps[k])
)
const {tableOptions} = nextProps
let result = {}
if (_.includes(updatedProps, 'data')) {
result = timeSeriesToTableGraph(nextProps.data, nextProps.queryASTs)
}
const data = _.get(result, 'data', this.state.data)
const sortedLabels = _.get(result, 'sortedLabels', this.state.sortedLabels)
const fieldNames = computeFieldNames(tableOptions.fieldNames, sortedLabels)
if (_.includes(updatedProps, 'data')) {
this.handleUpdateTableOptions(fieldNames, tableOptions)
}
if (_.isEmpty(data[0])) { if (_.isEmpty(data[0])) {
return return
} }
const {sortField, sortDirection} = this.state const {sort} = this.state
const { const internalName = _.get(
tableOptions: { nextProps,
sortBy: {internalName}, ['tableOptions', 'sortBy', 'internalName'],
fieldNames, ''
verticalTimeAxis, )
timeFormat,
},
} = nextProps
let direction, sortFieldName
if ( if (
_.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') === !_.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') ===
internalName internalName
) { ) {
direction = sortDirection sort.direction = DEFAULT_SORT_DIRECTION
sortFieldName = sortField sort.field = internalName
} else {
direction = DEFAULT_SORT
sortFieldName = internalName
} }
const { if (
processedData, _.includes(updatedProps, 'data') ||
sortedTimeVals, _.includes(updatedProps, 'tableOptions')
columnWidths, ) {
totalWidths, const {
} = processTableData( transformedData,
data, sortedTimeVals,
sortFieldName, columnWidths,
direction, totalWidths,
verticalTimeAxis, } = transformTableData(data, sort, fieldNames, tableOptions)
fieldNames,
timeFormat
)
this.setState({ this.setState({
data, data,
processedData, sortedLabels,
sortedTimeVals, transformedData,
sortField: sortFieldName, sortedTimeVals,
sortDirection: direction, sort,
columnWidths, columnWidths,
totalColumnWidths: totalWidths, totalColumnWidths: totalWidths,
}) })
}
} }
calcScrollToColRow = () => { calcScrollToColRow = () => {
@ -167,6 +188,7 @@ class TableGraph extends Component {
handleClickFieldName = fieldName => () => { handleClickFieldName = fieldName => () => {
const {tableOptions} = this.props const {tableOptions} = this.props
const {timeFormat} = tableOptions
const {data, sortField, sortDirection} = this.state const {data, sortField, sortDirection} = this.state
const verticalTimeAxis = _.get(tableOptions, 'verticalTimeAxis', true) const verticalTimeAxis = _.get(tableOptions, 'verticalTimeAxis', true)
const fieldNames = _.get(tableOptions, 'fieldNames', [TIME_FIELD_DEFAULT]) const fieldNames = _.get(tableOptions, 'fieldNames', [TIME_FIELD_DEFAULT])
@ -175,19 +197,20 @@ class TableGraph extends Component {
if (fieldName === sortField) { if (fieldName === sortField) {
direction = sortDirection === ASCENDING ? DESCENDING : ASCENDING direction = sortDirection === ASCENDING ? DESCENDING : ASCENDING
} else { } else {
direction = DEFAULT_SORT direction = DEFAULT_SORT_DIRECTION
} }
const {processedData, sortedTimeVals} = processTableData( const {transformedData, sortedTimeVals} = transformTableData(
data, data,
fieldName, fieldName,
direction, direction,
verticalTimeAxis, verticalTimeAxis,
fieldNames fieldNames,
timeFormat
) )
this.setState({ this.setState({
processedData, transformedData,
sortedTimeVals, sortedTimeVals,
sortField: fieldName, sortField: fieldName,
sortDirection: direction, sortDirection: direction,
@ -199,9 +222,9 @@ class TableGraph extends Component {
const { const {
tableOptions: {fixFirstColumn}, tableOptions: {fixFirstColumn},
} = this.props } = this.props
const {processedData, columnWidths, totalColumnWidths} = this.state const {transformedData, columnWidths, totalColumnWidths} = this.state
const columnCount = _.get(processedData, ['0', 'length'], 0) const columnCount = _.get(transformedData, ['0', 'length'], 0)
const columnLabel = processedData[0][index] const columnLabel = transformedData[0][index]
let adjustedColumnSizerWidth = columnWidths[columnLabel] let adjustedColumnSizerWidth = columnWidths[columnLabel]
@ -227,20 +250,20 @@ class TableGraph extends Component {
const { const {
hoveredColumnIndex, hoveredColumnIndex,
hoveredRowIndex, hoveredRowIndex,
processedData, transformedData,
sortField, sortField,
sortDirection, sortDirection,
} = this.state } = this.state
const {tableOptions, colors} = parent.props const {tableOptions, colors} = parent.props
const { const {
timeFormat = TIME_FORMAT_DEFAULT, timeFormat = DEFAULT_TIME_FORMAT,
verticalTimeAxis = VERTICAL_TIME_AXIS_DEFAULT, verticalTimeAxis = VERTICAL_TIME_AXIS_DEFAULT,
fixFirstColumn = FIX_FIRST_COLUMN_DEFAULT, fixFirstColumn = FIX_FIRST_COLUMN_DEFAULT,
fieldNames = [TIME_FIELD_DEFAULT], fieldNames = [TIME_FIELD_DEFAULT],
} = tableOptions } = tableOptions
const cellData = processedData[rowIndex][columnIndex] const cellData = transformedData[rowIndex][columnIndex]
const timeFieldIndex = fieldNames.findIndex( const timeFieldIndex = fieldNames.findIndex(
field => field.internalName === TIME_FIELD_DEFAULT.internalName field => field.internalName === TIME_FIELD_DEFAULT.internalName
@ -302,7 +325,7 @@ class TableGraph extends Component {
const cellContents = isTimeData const cellContents = isTimeData
? `${moment(cellData).format( ? `${moment(cellData).format(
timeFormat === '' ? TIME_FORMAT_DEFAULT : timeFormat timeFormat === '' ? DEFAULT_TIME_FORMAT : timeFormat
)}` )}`
: fieldName || `${cellData}` : fieldName || `${cellData}`
@ -332,12 +355,12 @@ class TableGraph extends Component {
timeColumnWidth, timeColumnWidth,
sortField, sortField,
sortDirection, sortDirection,
processedData, transformedData,
} = this.state } = this.state
const {hoverTime, tableOptions, colors} = this.props const {hoverTime, tableOptions, colors} = this.props
const {fixFirstColumn = FIX_FIRST_COLUMN_DEFAULT} = tableOptions const {fixFirstColumn = FIX_FIRST_COLUMN_DEFAULT} = tableOptions
const columnCount = _.get(processedData, ['0', 'length'], 0) const columnCount = _.get(transformedData, ['0', 'length'], 0)
const rowCount = columnCount === 0 ? 0 : processedData.length const rowCount = columnCount === 0 ? 0 : transformedData.length
const COLUMN_MIN_WIDTH = 100 const COLUMN_MIN_WIDTH = 100
const COLUMN_MAX_WIDTH = 1000 const COLUMN_MAX_WIDTH = 1000
@ -348,7 +371,6 @@ 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"
@ -417,8 +439,15 @@ TableGraph.propTypes = {
fixFirstColumn: bool, fixFirstColumn: bool,
}), }),
hoverTime: string, hoverTime: string,
handleUpdateTableOptions: func,
handleSetHoverTime: func, handleSetHoverTime: func,
colors: colorsStringSchema, colors: colorsStringSchema,
queryASTs: arrayOf(shape()),
isInCEO: bool,
} }
export default TableGraph const mapDispatchToProps = dispatch => ({
handleUpdateTableOptions: bindActionCreators(updateTableOptions, dispatch),
})
export default connect(null, mapDispatchToProps)(TableGraph)

View File

@ -13,18 +13,18 @@ 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 DEFAULT_SORT_DIRECTION = 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 = 30 export const CELL_HORIZONTAL_PADDING = 30
export const TIME_FORMAT_DEFAULT = 'MM/DD/YYYY HH:mm:ss' export const DEFAULT_TIME_FORMAT = 'MM/DD/YYYY HH:mm:ss'
export const TIME_FORMAT_CUSTOM = 'Custom' export const TIME_FORMAT_CUSTOM = 'Custom'
export const FORMAT_OPTIONS = [ export const FORMAT_OPTIONS = [
{text: TIME_FORMAT_DEFAULT}, {text: DEFAULT_TIME_FORMAT},
{text: 'MM/DD/YYYY HH:mm:ss.SSS'}, {text: 'MM/DD/YYYY HH:mm:ss.SSS'},
{text: 'YYYY-MM-DD HH:mm:ss'}, {text: 'YYYY-MM-DD HH:mm:ss'},
{text: 'HH:mm:ss'}, {text: 'HH:mm:ss'},
@ -36,7 +36,7 @@ export const FORMAT_OPTIONS = [
export const DEFAULT_TABLE_OPTIONS = { export const DEFAULT_TABLE_OPTIONS = {
verticalTimeAxis: VERTICAL_TIME_AXIS_DEFAULT, verticalTimeAxis: VERTICAL_TIME_AXIS_DEFAULT,
timeFormat: TIME_FORMAT_DEFAULT, timeFormat: DEFAULT_TIME_FORMAT,
sortBy: TIME_FIELD_DEFAULT, sortBy: TIME_FIELD_DEFAULT,
wrapping: 'truncate', wrapping: 'truncate',
fieldNames: [TIME_FIELD_DEFAULT], fieldNames: [TIME_FIELD_DEFAULT],

242
ui/src/utils/groupBy.js Normal file
View File

@ -0,0 +1,242 @@
import _ from 'lodash'
import {shiftDate} from 'shared/query/helpers'
import {map, reduce, forEach, concat, clone} from 'fast.js'
const groupByMap = (responses, responseIndex, groupByColumns) => {
const firstColumns = _.get(responses, [0, 'series', 0, 'columns'])
const accum = [
{
responseIndex,
series: [
{
columns: [
firstColumns[0],
...groupByColumns,
...firstColumns.slice(1),
],
groupByColumns,
name: _.get(responses, [0, 'series', 0, 'name']),
values: [],
},
],
},
]
const seriesArray = _.get(responses, [0, 'series'])
seriesArray.forEach(s => {
const prevValues = accum[0].series[0].values
const tagsToAdd = groupByColumns.map(gb => s.tags[gb])
const newValues = s.values.map(v => [v[0], ...tagsToAdd, ...v.slice(1)])
accum[0].series[0].values = [...prevValues, ...newValues]
})
return accum
}
const constructResults = (raw, groupBys) => {
return _.flatten(
map(raw, (response, index) => {
const responses = _.get(response, 'response.results', [])
if (groupBys[index]) {
return groupByMap(responses, index, groupBys[index])
}
return map(responses, r => ({...r, responseIndex: index}))
})
)
}
const constructSerieses = results => {
return reduce(
results,
(acc, {series = [], responseIndex}) => {
return [
...acc,
...map(series, (item, index) => ({
...item,
responseIndex,
seriesIndex: index,
})),
]
},
[]
)
}
const constructCells = serieses => {
let cellIndex = 0
let labels = []
const seriesLabels = []
const cells = {
label: [],
value: [],
time: [],
seriesIndex: [],
responseIndex: [],
}
forEach(
serieses,
(
{
name: measurement,
columns,
groupByColumns,
values,
seriesIndex,
responseIndex,
tags = {},
},
ind
) => {
const rows = map(values || [], vals => ({
vals,
}))
const unsortedLabels = map(columns.slice(1), (field, i) => ({
label:
groupByColumns && i <= groupByColumns.length - 1
? `${field}`
: `${measurement}.${field}`,
responseIndex,
seriesIndex,
}))
seriesLabels[ind] = unsortedLabels
labels = concat(labels, unsortedLabels)
forEach(rows, ({vals}) => {
const [time, ...rowValues] = vals
forEach(rowValues, (value, i) => {
cells.label[cellIndex] = unsortedLabels[i].label
cells.value[cellIndex] = value
cells.time[cellIndex] = time
cells.seriesIndex[cellIndex] = seriesIndex
cells.responseIndex[cellIndex] = responseIndex
cellIndex++ // eslint-disable-line no-plusplus
})
})
}
)
const sortedLabels = _.sortBy(labels, 'label')
return {cells, sortedLabels, seriesLabels}
}
const insertGroupByValues = (
serieses,
groupBys,
seriesLabels,
labelsToValueIndex,
sortedLabels
) => {
const dashArray = Array(sortedLabels.length).fill('-')
const timeSeries = []
let existingRowIndex
forEach(serieses, (s, sind) => {
if (groupBys[s.responseIndex]) {
forEach(s.values, vs => {
timeSeries.push({time: vs[0], values: clone(dashArray)})
existingRowIndex = timeSeries.length - 1
forEach(vs.slice(1), (v, i) => {
const label = seriesLabels[sind][i].label
timeSeries[existingRowIndex].values[
labelsToValueIndex[label + s.responseIndex + s.seriesIndex]
] = v
})
})
}
})
return timeSeries
}
const constructTimeSeries = (
serieses,
cells,
sortedLabels,
groupBys,
seriesLabels
) => {
const nullArray = Array(sortedLabels.length).fill(null)
const labelsToValueIndex = reduce(
sortedLabels,
(acc, {label, responseIndex, seriesIndex}, i) => {
// adding series index prevents overwriting of two distinct labels that have the same field and measurements
acc[label + responseIndex + seriesIndex] = i
return acc
},
{}
)
const tsMemo = {}
const timeSeries = insertGroupByValues(
serieses,
groupBys,
seriesLabels,
labelsToValueIndex,
sortedLabels
)
let existingRowIndex
for (let i = 0; i < _.get(cells, ['value', 'length'], 0); i++) {
let time
time = cells.time[i]
const value = cells.value[i]
const label = cells.label[i]
const seriesIndex = cells.seriesIndex[i]
const responseIndex = cells.responseIndex[i]
if (groupBys[cells.responseIndex[i]]) {
// we've already inserted GroupByValues
continue
}
if (label.includes('_shifted__')) {
const [, quantity, duration] = label.split('__')
time = +shiftDate(time, quantity, duration).format('x')
}
existingRowIndex = tsMemo[time]
if (existingRowIndex === undefined) {
timeSeries.push({
time,
values: clone(nullArray),
})
existingRowIndex = timeSeries.length - 1
tsMemo[time] = existingRowIndex
}
timeSeries[existingRowIndex].values[
labelsToValueIndex[label + responseIndex + seriesIndex]
] = value
}
return _.sortBy(timeSeries, 'time')
}
export const groupByTimeSeriesTransform = (raw, groupBys) => {
if (!groupBys) {
groupBys = Array(raw.length).fill(false)
}
const results = constructResults(raw, groupBys)
const serieses = constructSerieses(results)
const {cells, sortedLabels, seriesLabels} = constructCells(serieses)
const sortedTimeSeries = constructTimeSeries(
serieses,
cells,
sortedLabels,
groupBys,
seriesLabels
)
return {
sortedLabels,
sortedTimeSeries,
}
}

View File

@ -1,153 +1,9 @@
import _ from 'lodash' import _ from 'lodash'
import {shiftDate} from 'shared/query/helpers' import {map, reduce} from 'fast.js'
import {map, reduce, filter, forEach, concat, clone} from 'fast.js' import {groupByTimeSeriesTransform} from 'src/utils/groupBy.js'
import {calculateColumnWidths} from 'src/dashboards/utils/tableGraph'
/**
* Accepts an array of raw influxdb responses and returns a format
* that Dygraph understands.
**/
const DEFAULT_SIZE = 0
const cells = {
label: new Array(DEFAULT_SIZE),
value: new Array(DEFAULT_SIZE),
time: new Array(DEFAULT_SIZE),
seriesIndex: new Array(DEFAULT_SIZE),
responseIndex: new Array(DEFAULT_SIZE),
}
const timeSeriesTransform = (raw = []) => {
// collect results from each influx response
const results = reduce(
raw,
(acc, rawResponse, responseIndex) => {
const responses = _.get(rawResponse, 'response.results', [])
const indexedResponses = map(responses, response => ({
...response,
responseIndex,
}))
return [...acc, ...indexedResponses]
},
[]
)
// collect each series
const serieses = reduce(
results,
(acc, {series = [], responseIndex}, index) => {
return [...acc, ...map(series, item => ({...item, responseIndex, index}))]
},
[]
)
const size = reduce(
serieses,
(acc, {columns, values}) => {
if (columns.length && (values && values.length)) {
return acc + (columns.length - 1) * values.length
}
return acc
},
0
)
// convert series into cells with rows and columns
let cellIndex = 0
let labels = []
forEach(
serieses,
({
name: measurement,
columns,
values,
index: seriesIndex,
responseIndex,
tags = {},
}) => {
const rows = map(values || [], vals => ({
vals,
}))
// tagSet is each tag key and value for a series
const tagSet = map(Object.keys(tags), tag => `[${tag}=${tags[tag]}]`)
.sort()
.join('')
const unsortedLabels = map(columns.slice(1), field => ({
label: `${measurement}.${field}${tagSet}`,
responseIndex,
seriesIndex,
}))
labels = concat(labels, unsortedLabels)
forEach(rows, ({vals}) => {
const [time, ...rowValues] = vals
forEach(rowValues, (value, i) => {
cells.label[cellIndex] = unsortedLabels[i].label
cells.value[cellIndex] = value
cells.time[cellIndex] = time
cells.seriesIndex[cellIndex] = seriesIndex
cells.responseIndex[cellIndex] = responseIndex
cellIndex++ // eslint-disable-line no-plusplus
})
})
}
)
const sortedLabels = _.sortBy(labels, 'label')
const tsMemo = {}
const nullArray = Array(sortedLabels.length).fill(null)
const labelsToValueIndex = reduce(
sortedLabels,
(acc, {label, seriesIndex}, i) => {
// adding series index prevents overwriting of two distinct labels that have the same field and measurements
acc[label + seriesIndex] = i
return acc
},
{}
)
const timeSeries = []
for (let i = 0; i < size; i++) {
let time = cells.time[i]
const value = cells.value[i]
const label = cells.label[i]
const seriesIndex = cells.seriesIndex[i]
if (label.includes('_shifted__')) {
const [, quantity, duration] = label.split('__')
time = +shiftDate(time, quantity, duration).format('x')
}
let existingRowIndex = tsMemo[time]
if (existingRowIndex === undefined) {
timeSeries.push({
time,
values: clone(nullArray),
})
existingRowIndex = timeSeries.length - 1
tsMemo[time] = existingRowIndex
}
timeSeries[existingRowIndex].values[
labelsToValueIndex[label + seriesIndex]
] = value
}
const sortedTimeSeries = _.sortBy(timeSeries, 'time')
return {
sortedLabels,
sortedTimeSeries,
}
}
export const timeSeriesToDygraph = (raw = [], isInDataExplorer) => { export const timeSeriesToDygraph = (raw = [], isInDataExplorer) => {
const {sortedLabels, sortedTimeSeries} = timeSeriesTransform(raw) const {sortedLabels, sortedTimeSeries} = groupByTimeSeriesTransform(raw)
const dygraphSeries = reduce( const dygraphSeries = reduce(
sortedLabels, sortedLabels,
@ -172,8 +28,17 @@ export const timeSeriesToDygraph = (raw = [], isInDataExplorer) => {
} }
} }
export const timeSeriesToTableGraph = raw => { const computeGroupBys = queryASTs => {
const {sortedLabels, sortedTimeSeries} = timeSeriesTransform(raw) return queryASTs.map(queryAST => {
return _.get(queryAST, ['groupBy', 'tags'], false)
})
}
export const timeSeriesToTableGraph = (raw, queryASTs) => {
const {sortedLabels, sortedTimeSeries} = groupByTimeSeriesTransform(
raw,
computeGroupBys(queryASTs)
)
const labels = ['time', ...map(sortedLabels, ({label}) => label)] const labels = ['time', ...map(sortedLabels, ({label}) => label)]
@ -181,60 +46,6 @@ export const timeSeriesToTableGraph = raw => {
const data = tableData.length ? [labels, ...tableData] : [[]] const data = tableData.length ? [labels, ...tableData] : [[]]
return { return {
data, data,
sortedLabels,
} }
} }
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 orderTableColumns = (data, fieldNames) => {
const fieldsSortOrder = fieldNames.map(fieldName => {
return _.findIndex(data[0], dataLabel => {
return dataLabel === fieldName.internalName
})
})
const filteredFieldSortOrder = filter(fieldsSortOrder, f => f !== -1)
const orderedData = map(data, row => {
return row.map((v, j, arr) => arr[filteredFieldSortOrder[j]])
})
return orderedData[0].length ? orderedData : [[]]
}
export const processTableData = (
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames,
timeFormat
) => {
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 orderedData = orderTableColumns(filteredData, fieldNames)
const processedData = verticalTimeAxis ? orderedData : _.unzip(orderedData)
const {widths: columnWidths, totalWidths} = calculateColumnWidths(
processedData,
fieldNames,
timeFormat,
verticalTimeAxis
)
return {processedData, sortedTimeVals, columnWidths, totalWidths}
}
export default timeSeriesToDygraph

View File

@ -22,6 +22,7 @@ const defaultProps = {
timeFormat: '', timeFormat: '',
verticalTimeAxis: true, verticalTimeAxis: true,
}, },
queryASTs: [],
} }
const setup = (override = {}) => { const setup = (override = {}) => {

View File

@ -1,9 +1,17 @@
import timeSeriesToDygraph, { import {
timeSeriesToDygraph,
timeSeriesToTableGraph, timeSeriesToTableGraph,
filterTableColumns,
processTableData,
} from 'src/utils/timeSeriesTransformers' } from 'src/utils/timeSeriesTransformers'
import {DEFAULT_SORT} from 'src/shared/constants/tableGraph'
import {
filterTableColumns,
transformTableData,
} from 'src/dashboards/utils/tableGraph'
import {
DEFAULT_SORT_DIRECTION,
DEFAULT_TIME_FORMAT,
} 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', () => {
@ -178,7 +186,6 @@ describe('timeSeriesToDygraph', () => {
}, },
}, },
] ]
const actual = timeSeriesToDygraph(influxResponse) const actual = timeSeriesToDygraph(influxResponse)
const expected = { const expected = {
@ -341,13 +348,45 @@ describe('timeSeriesToTableGraph', () => {
}, },
] ]
const actual = timeSeriesToTableGraph(influxResponse) const qASTs = [
{
groupBy: {
time: {
interval: '2s',
},
},
},
{
groupBy: {
time: {
interval: '2s',
},
},
},
{
groupBy: {
time: {
interval: '2s',
},
},
},
{
groupBy: {
time: {
interval: '2s',
},
},
},
]
const actual = timeSeriesToTableGraph(influxResponse, qASTs)
const expected = [ const expected = [
['time', 'ma.f1', 'mb.f1', 'mc.f1', 'mc.f2'], ['time', 'ma.f1', 'mb.f1', 'mc.f1', 'mc.f2'],
[1000, 1, 1, null, null], [1000, 1, 1, null, null],
[2000, 2, 2, 3, 3], [2000, 2, 2, 3, 3],
[4000, null, null, 4, 4], [4000, null, null, 4, 4],
] ]
expect(actual.data).toEqual(expected) expect(actual.data).toEqual(expected)
}) })
@ -397,7 +436,38 @@ describe('timeSeriesToTableGraph', () => {
}, },
] ]
const actual = timeSeriesToTableGraph(influxResponse) const qASTs = [
{
groupBy: {
time: {
interval: '2s',
},
},
},
{
groupBy: {
time: {
interval: '2s',
},
},
},
{
groupBy: {
time: {
interval: '2s',
},
},
},
{
groupBy: {
time: {
interval: '2s',
},
},
},
]
const actual = timeSeriesToTableGraph(influxResponse, qASTs)
const expected = ['time', 'ma.f1', 'mb.f1', 'mc.f1', 'mc.f2'] const expected = ['time', 'ma.f1', 'mb.f1', 'mc.f1', 'mc.f2']
expect(actual.data[0]).toEqual(expected) expect(actual.data[0]).toEqual(expected)
@ -406,7 +476,9 @@ describe('timeSeriesToTableGraph', () => {
it('returns an array of an empty array if there is an empty response', () => { it('returns an array of an empty array if there is an empty response', () => {
const influxResponse = [] const influxResponse = []
const actual = timeSeriesToTableGraph(influxResponse) const qASTs = []
const actual = timeSeriesToTableGraph(influxResponse, qASTs)
const expected = [[]] const expected = [[]]
expect(actual.data).toEqual(expected) expect(actual.data).toEqual(expected)
@ -453,7 +525,7 @@ describe('filterTableColumns', () => {
}) })
}) })
describe('processTableData', () => { describe('transformTableData', () => {
it('sorts the data based on the provided sortFieldName', () => { it('sorts the data based on the provided sortFieldName', () => {
const data = [ const data = [
['time', 'f1', 'f2'], ['time', 'f1', 'f2'],
@ -461,9 +533,11 @@ describe('processTableData', () => {
[2000, 1000, 3000], [2000, 1000, 3000],
[3000, 2000, 1000], [3000, 2000, 1000],
] ]
const sortFieldName = 'f1' const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
const direction = DEFAULT_SORT const tableOptions = {
const verticalTimeAxis = true verticalTimeAxis: true,
timeFormat: DEFAULT_TIME_FORMAT,
}
const fieldNames = [ const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true}, {internalName: 'time', displayName: 'Time', visible: true},
@ -471,13 +545,7 @@ describe('processTableData', () => {
{internalName: 'f2', displayName: 'F2', visible: true}, {internalName: 'f2', displayName: 'F2', visible: true},
] ]
const actual = processTableData( const actual = transformTableData(data, sort, fieldNames, tableOptions)
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [ const expected = [
['time', 'f1', 'f2'], ['time', 'f1', 'f2'],
[2000, 1000, 3000], [2000, 1000, 3000],
@ -485,7 +553,7 @@ describe('processTableData', () => {
[1000, 3000, 2000], [1000, 3000, 2000],
] ]
expect(actual.processedData).toEqual(expected) expect(actual.transformedData).toEqual(expected)
}) })
it('filters out columns that should not be visible', () => { it('filters out columns that should not be visible', () => {
@ -495,9 +563,13 @@ describe('processTableData', () => {
[2000, 1000, 3000], [2000, 1000, 3000],
[3000, 2000, 1000], [3000, 2000, 1000],
] ]
const sortFieldName = 'time'
const direction = DEFAULT_SORT const sort = {field: 'time', direction: DEFAULT_SORT_DIRECTION}
const verticalTimeAxis = true
const tableOptions = {
verticalTimeAxis: true,
timeFormat: DEFAULT_TIME_FORMAT,
}
const fieldNames = [ const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true}, {internalName: 'time', displayName: 'Time', visible: true},
@ -505,16 +577,11 @@ describe('processTableData', () => {
{internalName: 'f2', displayName: 'F2', visible: true}, {internalName: 'f2', displayName: 'F2', visible: true},
] ]
const actual = processTableData( const actual = transformTableData(data, sort, fieldNames, tableOptions)
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [['time', 'f2'], [1000, 2000], [2000, 3000], [3000, 1000]] const expected = [['time', 'f2'], [1000, 2000], [2000, 3000], [3000, 1000]]
expect(actual.processedData).toEqual(expected) expect(actual.transformedData).toEqual(expected)
}) })
it('filters out invisible columns after sorting', () => { it('filters out invisible columns after sorting', () => {
@ -524,9 +591,13 @@ describe('processTableData', () => {
[2000, 1000, 3000], [2000, 1000, 3000],
[3000, 2000, 1000], [3000, 2000, 1000],
] ]
const sortFieldName = 'f1'
const direction = DEFAULT_SORT const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
const verticalTimeAxis = true
const tableOptions = {
verticalTimeAxis: true,
timeFormat: DEFAULT_TIME_FORMAT,
}
const fieldNames = [ const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true}, {internalName: 'time', displayName: 'Time', visible: true},
@ -534,16 +605,11 @@ describe('processTableData', () => {
{internalName: 'f2', displayName: 'F2', visible: true}, {internalName: 'f2', displayName: 'F2', visible: true},
] ]
const actual = processTableData( const actual = transformTableData(data, sort, fieldNames, tableOptions)
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [['time', 'f2'], [2000, 3000], [3000, 1000], [1000, 2000]] const expected = [['time', 'f2'], [2000, 3000], [3000, 1000], [1000, 2000]]
expect(actual.processedData).toEqual(expected) expect(actual.transformedData).toEqual(expected)
}) })
}) })
@ -555,9 +621,13 @@ describe('if verticalTimeAxis is false', () => {
[2000, 1000, 3000], [2000, 1000, 3000],
[3000, 2000, 1000], [3000, 2000, 1000],
] ]
const sortFieldName = 'time'
const direction = DEFAULT_SORT const sort = {field: 'time', direction: DEFAULT_SORT_DIRECTION}
const verticalTimeAxis = false
const tableOptions = {
verticalTimeAxis: false,
timeFormat: DEFAULT_TIME_FORMAT,
}
const fieldNames = [ const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true}, {internalName: 'time', displayName: 'Time', visible: true},
@ -565,20 +635,15 @@ describe('if verticalTimeAxis is false', () => {
{internalName: 'f2', displayName: 'F2', visible: true}, {internalName: 'f2', displayName: 'F2', visible: true},
] ]
const actual = processTableData( const actual = transformTableData(data, sort, fieldNames, tableOptions)
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [ const expected = [
['time', 1000, 2000, 3000], ['time', 1000, 2000, 3000],
['f1', 3000, 1000, 2000], ['f1', 3000, 1000, 2000],
['f2', 2000, 3000, 1000], ['f2', 2000, 3000, 1000],
] ]
expect(actual.processedData).toEqual(expected) expect(actual.transformedData).toEqual(expected)
}) })
it('transforms data after filtering out invisible columns', () => { it('transforms data after filtering out invisible columns', () => {
@ -588,9 +653,13 @@ describe('if verticalTimeAxis is false', () => {
[2000, 1000, 3000], [2000, 1000, 3000],
[3000, 2000, 1000], [3000, 2000, 1000],
] ]
const sortFieldName = 'f1'
const direction = DEFAULT_SORT const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
const verticalTimeAxis = false
const tableOptions = {
verticalTimeAxis: false,
timeFormat: DEFAULT_TIME_FORMAT,
}
const fieldNames = [ const fieldNames = [
{internalName: 'time', displayName: 'Time', visible: true}, {internalName: 'time', displayName: 'Time', visible: true},
@ -598,15 +667,10 @@ describe('if verticalTimeAxis is false', () => {
{internalName: 'f2', displayName: 'F2', visible: true}, {internalName: 'f2', displayName: 'F2', visible: true},
] ]
const actual = processTableData( const actual = transformTableData(data, sort, fieldNames, tableOptions)
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
const expected = [['time', 2000, 3000, 1000], ['f2', 3000, 1000, 2000]] const expected = [['time', 2000, 3000, 1000], ['f2', 3000, 1000, 2000]]
expect(actual.processedData).toEqual(expected) expect(actual.transformedData).toEqual(expected)
}) })
}) })