commit
d2af17d1e5
|
@ -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
|
||||||
|
|
|
@ -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,16 +10,16 @@ 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>
|
||||||
|
@ -39,6 +39,7 @@ const GraphOptionsCustomizeFields: SFC<GraphOptionsCustomizeFieldsProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DragDropContext(HTML5Backend)(GraphOptionsCustomizeFields)
|
export default DragDropContext(HTML5Backend)(GraphOptionsCustomizeFields)
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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,71 +44,88 @@ 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
_.includes(updatedProps, 'data') ||
|
||||||
|
_.includes(updatedProps, 'tableOptions')
|
||||||
|
) {
|
||||||
const {
|
const {
|
||||||
processedData,
|
transformedData,
|
||||||
sortedTimeVals,
|
sortedTimeVals,
|
||||||
columnWidths,
|
columnWidths,
|
||||||
totalWidths,
|
totalWidths,
|
||||||
} = processTableData(
|
} = transformTableData(data, sort, fieldNames, tableOptions)
|
||||||
data,
|
|
||||||
sortFieldName,
|
|
||||||
direction,
|
|
||||||
verticalTimeAxis,
|
|
||||||
fieldNames,
|
|
||||||
timeFormat
|
|
||||||
)
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
data,
|
data,
|
||||||
processedData,
|
sortedLabels,
|
||||||
|
transformedData,
|
||||||
sortedTimeVals,
|
sortedTimeVals,
|
||||||
sortField: sortFieldName,
|
sort,
|
||||||
sortDirection: direction,
|
|
||||||
columnWidths,
|
columnWidths,
|
||||||
totalColumnWidths: totalWidths,
|
totalColumnWidths: totalWidths,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
calcScrollToColRow = () => {
|
calcScrollToColRow = () => {
|
||||||
const {data, sortedTimeVals, hoveredColumnIndex} = this.state
|
const {data, sortedTimeVals, hoveredColumnIndex} = this.state
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ const defaultProps = {
|
||||||
timeFormat: '',
|
timeFormat: '',
|
||||||
verticalTimeAxis: true,
|
verticalTimeAxis: true,
|
||||||
},
|
},
|
||||||
|
queryASTs: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
const setup = (override = {}) => {
|
const setup = (override = {}) => {
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue