Update Single Stat Vis types to work with Flux data (#4449)

pull/4454/head
Iris Scholten 2018-09-13 16:17:16 -07:00 committed by GitHub
parent 5f138d6b63
commit b251c9350e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 358 additions and 116 deletions

View File

@ -22,6 +22,7 @@
1. [#4422](https://github.com/influxdata/chronograf/pull/4422): Allow deep linking flux script in data explorer 1. [#4422](https://github.com/influxdata/chronograf/pull/4422): Allow deep linking flux script in data explorer
1. [#4410](https://github.com/influxdata/chronograf/pull/4410): Add ability to use line graph visualizations for flux query 1. [#4410](https://github.com/influxdata/chronograf/pull/4410): Add ability to use line graph visualizations for flux query
1. [#4445](https://github.com/influxdata/chronograf/pull/4445): Allow flux dashboard cells to be exported 1. [#4445](https://github.com/influxdata/chronograf/pull/4445): Allow flux dashboard cells to be exported
1. [#4449](https://github.com/influxdata/chronograf/pull/4449): Add ability to use single stat graph visualizations for flux query
### UI Improvements ### UI Improvements

View File

@ -1,6 +1,8 @@
import React, {PureComponent} from 'react' import React, {PureComponent} from 'react'
import _ from 'lodash' import _ from 'lodash'
import {manager} from 'src/worker/JobManager'
import getLastValues from 'src/shared/parsing/lastValues' import getLastValues from 'src/shared/parsing/lastValues'
import Gauge from 'src/shared/components/Gauge' import Gauge from 'src/shared/components/Gauge'
@ -11,9 +13,12 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
import {DecimalPlaces} from 'src/types/dashboards' import {DecimalPlaces} from 'src/types/dashboards'
import {ColorString} from 'src/types/colors' import {ColorString} from 'src/types/colors'
import {TimeSeriesServerResponse} from 'src/types/series' import {TimeSeriesServerResponse} from 'src/types/series'
import {FluxTable} from 'src/types/flux'
import {DataTypes} from 'src/shared/components/RefreshingGraph'
interface Props { interface Props {
data: TimeSeriesServerResponse[] data: TimeSeriesServerResponse[] | FluxTable[]
dataType: DataTypes
decimalPlaces: DecimalPlaces decimalPlaces: DecimalPlaces
cellID: string cellID: string
cellHeight?: number cellHeight?: number
@ -23,12 +28,46 @@ interface Props {
resizerTopHeight?: number resizerTopHeight?: number
} }
interface State {
lastValues?: {
values: number[]
series: string[]
}
}
@ErrorHandling @ErrorHandling
class GaugeChart extends PureComponent<Props> { class GaugeChart extends PureComponent<Props, State> {
public static defaultProps: Partial<Props> = { public static defaultProps: Partial<Props> = {
colors: stringifyColorValues(DEFAULT_GAUGE_COLORS), colors: stringifyColorValues(DEFAULT_GAUGE_COLORS),
} }
private isComponentMounted: boolean
constructor(props: Props) {
super(props)
this.state = {}
}
public async componentDidMount() {
this.isComponentMounted = true
await this.dataToLastValues()
}
public async componentDidUpdate(prevProps: Props) {
const isDataChanged =
prevProps.dataType !== this.props.dataType ||
!_.isEqual(prevProps.data, this.props.data)
if (isDataChanged) {
await this.dataToLastValues()
}
}
public componentWillUnmount() {
this.isComponentMounted = false
}
public render() { public render() {
const {colors, prefix, suffix, decimalPlaces} = this.props const {colors, prefix, suffix, decimalPlaces} = this.props
return ( return (
@ -63,9 +102,8 @@ class GaugeChart extends PureComponent<Props> {
} }
private get lastValueForGauge(): number { private get lastValueForGauge(): number {
const {data} = this.props const {lastValues} = this.state
const {lastValues} = getLastValues(data) const lastValue = _.get(lastValues, 'values.0', 0)
const lastValue = _.get(lastValues, 0, 0)
if (!lastValue) { if (!lastValue) {
return 0 return 0
@ -73,6 +111,27 @@ class GaugeChart extends PureComponent<Props> {
return lastValue return lastValue
} }
private async dataToLastValues() {
const {data, dataType} = this.props
try {
let lastValues
if (dataType === DataTypes.flux) {
lastValues = await manager.fluxTablesToSingleStat(data as FluxTable[])
} else if (dataType === DataTypes.influxQL) {
lastValues = getLastValues(data as TimeSeriesServerResponse[])
}
if (!this.isComponentMounted) {
return
}
this.setState({lastValues})
} catch (err) {
console.error(err)
}
}
} }
export default GaugeChart export default GaugeChart

View File

@ -28,6 +28,7 @@ import {
CellType, CellType,
FluxTable, FluxTable,
} from 'src/types' } from 'src/types'
import {DataTypes} from 'src/shared/components/RefreshingGraph'
interface Props { interface Props {
axes: Axes axes: Axes
@ -37,8 +38,8 @@ interface Props {
colors: ColorString[] colors: ColorString[]
loading: RemoteDataState loading: RemoteDataState
decimalPlaces: DecimalPlaces decimalPlaces: DecimalPlaces
fluxData: FluxTable[] data: TimeSeriesServerResponse[] | FluxTable[]
influxQLData: TimeSeriesServerResponse[] dataType: DataTypes
cellID: string cellID: string
cellHeight: number cellHeight: number
staticLegend: boolean staticLegend: boolean
@ -70,17 +71,20 @@ class LineGraph extends PureComponent<LineGraphProps, State> {
public async componentDidMount() { public async componentDidMount() {
this.isComponentMounted = true this.isComponentMounted = true
const {influxQLData, fluxData} = this.props const {data, dataType} = this.props
await this.parseTimeSeries(influxQLData, fluxData) await this.parseTimeSeries(data, dataType)
} }
public componentWillUnmount() { public componentWillUnmount() {
this.isComponentMounted = false this.isComponentMounted = false
} }
public async parseTimeSeries(data, fluxData) { public async parseTimeSeries(
data: TimeSeriesServerResponse[] | FluxTable[],
dataType: DataTypes
) {
try { try {
const timeSeries = await this.convertToDygraphData(data, fluxData) const timeSeries = await this.convertToDygraphData(data, dataType)
this.isValidData = await manager.validateDygraphData( this.isValidData = await manager.validateDygraphData(
timeSeries.timeSeries timeSeries.timeSeries
@ -97,7 +101,7 @@ class LineGraph extends PureComponent<LineGraphProps, State> {
public componentWillReceiveProps(nextProps: LineGraphProps) { public componentWillReceiveProps(nextProps: LineGraphProps) {
if (nextProps.loading === RemoteDataState.Done) { if (nextProps.loading === RemoteDataState.Done) {
this.parseTimeSeries(nextProps.influxQLData, nextProps.fluxData) this.parseTimeSeries(nextProps.data, nextProps.dataType)
} }
} }
@ -107,7 +111,7 @@ class LineGraph extends PureComponent<LineGraphProps, State> {
} }
const { const {
influxQLData, data,
axes, axes,
type, type,
colors, colors,
@ -115,6 +119,7 @@ class LineGraph extends PureComponent<LineGraphProps, State> {
onZoom, onZoom,
loading, loading,
queries, queries,
dataType,
timeRange, timeRange,
cellHeight, cellHeight,
staticLegend, staticLegend,
@ -165,7 +170,8 @@ class LineGraph extends PureComponent<LineGraphProps, State> {
> >
{type === CellType.LinePlusSingleStat && ( {type === CellType.LinePlusSingleStat && (
<SingleStat <SingleStat
data={influxQLData} data={data}
dataType={dataType}
lineGraph={true} lineGraph={true}
colors={colors} colors={colors}
prefix={this.prefix} prefix={this.prefix}
@ -223,17 +229,20 @@ class LineGraph extends PureComponent<LineGraphProps, State> {
} }
private async convertToDygraphData( private async convertToDygraphData(
data: TimeSeriesServerResponse[], data: TimeSeriesServerResponse[] | FluxTable[],
fluxData: FluxTable[] dataType: DataTypes
): Promise<TimeSeriesToDyGraphReturnType> { ): Promise<TimeSeriesToDyGraphReturnType> {
const {location} = this.props const {location} = this.props
if (data.length) { if (dataType === DataTypes.influxQL) {
return await timeSeriesToDygraph(data, location.pathname) return await timeSeriesToDygraph(
data as TimeSeriesServerResponse[],
location.pathname
)
} }
if (fluxData.length) { if (dataType === DataTypes.flux) {
return await fluxTablesToDygraph(fluxData) return await fluxTablesToDygraph(data as FluxTable[])
} }
} }
} }

View File

@ -49,6 +49,16 @@ import {GrabDataForDownloadHandler} from 'src/types/layout'
import {VisType} from 'src/types/flux' import {VisType} from 'src/types/flux'
import {TimeSeriesServerResponse} from 'src/types/series' import {TimeSeriesServerResponse} from 'src/types/series'
interface TypeAndData {
dataType: DataTypes
data: TimeSeriesServerResponse[] | FluxTable[]
}
export enum DataTypes {
flux = 'flux',
influxQL = 'influxQL',
}
interface Props { interface Props {
axes: Axes axes: Axes
source: Source source: Source
@ -161,11 +171,11 @@ class RefreshingGraph extends PureComponent<Props> {
{({timeSeriesInfluxQL, timeSeriesFlux, loading}) => { {({timeSeriesInfluxQL, timeSeriesFlux, loading}) => {
switch (type) { switch (type) {
case CellType.SingleStat: case CellType.SingleStat:
return this.singleStat(timeSeriesInfluxQL) return this.singleStat(timeSeriesInfluxQL, timeSeriesFlux)
case CellType.Table: case CellType.Table:
return this.table(timeSeriesInfluxQL) return this.table(timeSeriesInfluxQL)
case CellType.Gauge: case CellType.Gauge:
return this.gauge(timeSeriesInfluxQL) return this.gauge(timeSeriesInfluxQL, timeSeriesFlux)
default: default:
return this.lineGraph(timeSeriesInfluxQL, timeSeriesFlux, loading) return this.lineGraph(timeSeriesInfluxQL, timeSeriesFlux, loading)
} }
@ -189,7 +199,10 @@ class RefreshingGraph extends PureComponent<Props> {
return !_.isEqual(prevVisValues, curVisValues) return !_.isEqual(prevVisValues, curVisValues)
} }
private singleStat = (data): JSX.Element => { private singleStat = (
influxQLData: TimeSeriesServerResponse[],
fluxData: FluxTable[]
): JSX.Element => {
const { const {
colors, colors,
cellHeight, cellHeight,
@ -198,8 +211,11 @@ class RefreshingGraph extends PureComponent<Props> {
onUpdateCellColors, onUpdateCellColors,
} = this.props } = this.props
const {dataType, data} = this.getTypeAndData(influxQLData, fluxData)
return ( return (
<SingleStat <SingleStat
dataType={dataType}
data={data} data={data}
colors={colors} colors={colors}
prefix={this.prefix} prefix={this.prefix}
@ -240,7 +256,10 @@ class RefreshingGraph extends PureComponent<Props> {
) )
} }
private gauge = (data): JSX.Element => { private gauge = (
influxQLData: TimeSeriesServerResponse[],
fluxData: FluxTable[]
): JSX.Element => {
const { const {
colors, colors,
cellID, cellID,
@ -250,9 +269,12 @@ class RefreshingGraph extends PureComponent<Props> {
resizerTopHeight, resizerTopHeight,
} = this.props } = this.props
const {dataType, data} = this.getTypeAndData(influxQLData, fluxData)
return ( return (
<GaugeChart <GaugeChart
data={data} data={data}
dataType={dataType}
cellID={cellID} cellID={cellID}
colors={colors} colors={colors}
prefix={this.prefix} prefix={this.prefix}
@ -285,18 +307,20 @@ class RefreshingGraph extends PureComponent<Props> {
handleSetHoverTime, handleSetHoverTime,
} = this.props } = this.props
const {dataType, data} = this.getTypeAndData(influxQLData, fluxData)
return ( return (
<LineGraph <LineGraph
influxQLData={influxQLData} data={data}
type={type} type={type}
axes={axes} axes={axes}
cellID={cellID} cellID={cellID}
colors={colors} colors={colors}
onZoom={onZoom} onZoom={onZoom}
queries={queries} queries={queries}
fluxData={fluxData}
key={manualRefresh}
loading={loading} loading={loading}
dataType={dataType}
key={manualRefresh}
timeRange={timeRange} timeRange={timeRange}
cellHeight={cellHeight} cellHeight={cellHeight}
staticLegend={staticLegend} staticLegend={staticLegend}
@ -329,6 +353,19 @@ class RefreshingGraph extends PureComponent<Props> {
const {axes} = this.props const {axes} = this.props
return _.get(axes, 'y.suffix', '') return _.get(axes, 'y.suffix', '')
} }
private getTypeAndData(
influxQLData: TimeSeriesServerResponse[],
fluxData: FluxTable[]
): TypeAndData {
if (influxQLData.length) {
return {dataType: DataTypes.influxQL, data: influxQLData}
}
if (fluxData.length) {
return {dataType: DataTypes.flux, data: fluxData}
}
}
} }
const mapStateToProps = ({annotations: {mode}}) => ({ const mapStateToProps = ({annotations: {mode}}) => ({

View File

@ -2,6 +2,7 @@ import React, {PureComponent, CSSProperties} from 'react'
import classnames from 'classnames' import classnames from 'classnames'
import getLastValues from 'src/shared/parsing/lastValues' import getLastValues from 'src/shared/parsing/lastValues'
import _ from 'lodash' import _ from 'lodash'
import {manager} from 'src/worker/JobManager'
import {SMALL_CELL_HEIGHT} from 'src/shared/graphs/helpers' import {SMALL_CELL_HEIGHT} from 'src/shared/graphs/helpers'
import {DYGRAPH_CONTAINER_V_MARGIN} from 'src/shared/constants' import {DYGRAPH_CONTAINER_V_MARGIN} from 'src/shared/constants'
@ -10,6 +11,8 @@ import {ColorString} from 'src/types/colors'
import {CellType, DecimalPlaces} from 'src/types/dashboards' import {CellType, DecimalPlaces} from 'src/types/dashboards'
import {TimeSeriesServerResponse} from 'src/types/series' import {TimeSeriesServerResponse} from 'src/types/series'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {FluxTable} from 'src/types'
import {DataTypes} from 'src/shared/components/RefreshingGraph'
interface Props { interface Props {
decimalPlaces: DecimalPlaces decimalPlaces: DecimalPlaces
@ -19,21 +22,60 @@ interface Props {
suffix?: string suffix?: string
lineGraph: boolean lineGraph: boolean
staticLegendHeight?: number staticLegendHeight?: number
data: TimeSeriesServerResponse[] data: TimeSeriesServerResponse[] | FluxTable[]
dataType: DataTypes
onUpdateCellColors?: (bgColor: string, textColor: string) => void onUpdateCellColors?: (bgColor: string, textColor: string) => void
} }
interface State {
lastValues?: {
values: number[]
series: string[]
}
}
const NOOP = () => {} const NOOP = () => {}
@ErrorHandling @ErrorHandling
class SingleStat extends PureComponent<Props> { class SingleStat extends PureComponent<Props, State> {
public static defaultProps: Partial<Props> = { public static defaultProps: Partial<Props> = {
prefix: '', prefix: '',
suffix: '', suffix: '',
onUpdateCellColors: NOOP, onUpdateCellColors: NOOP,
} }
private isComponentMounted: boolean
constructor(props: Props) {
super(props)
this.state = {}
}
public async componentDidMount() {
this.isComponentMounted = true
await this.dataToLastValues()
}
public async componentDidUpdate(prevProps: Props) {
const isDataChanged =
prevProps.dataType !== this.props.dataType ||
!_.isEqual(prevProps.data, this.props.data)
if (isDataChanged) {
await this.dataToLastValues()
}
}
public componentWillUnmount() {
this.isComponentMounted = false
}
public render() { public render() {
if (!this.state.lastValues) {
return <h3 className="graph-spinner" />
}
return ( return (
<div className="single-stat" style={this.containerStyle}> <div className="single-stat" style={this.containerStyle}>
{this.resizerBox} {this.resizerBox}
@ -54,16 +96,19 @@ class SingleStat extends PureComponent<Props> {
} }
private get lastValue(): number { private get lastValue(): number {
const {data} = this.props const {lastValues} = this.state
const {lastValues, series} = getLastValues(data)
const firstAlphabeticalSeriesName = _.sortBy(series)[0]
const firstAlphabeticalIndex = _.indexOf( if (lastValues) {
series, const {values, series} = lastValues
firstAlphabeticalSeriesName const firstAlphabeticalSeriesName = _.sortBy(series)[0]
)
return lastValues[firstAlphabeticalIndex] const firstAlphabeticalIndex = _.indexOf(
series,
firstAlphabeticalSeriesName
)
return values[firstAlphabeticalIndex]
}
} }
private get roundedLastValue(): string { private get roundedLastValue(): string {
@ -108,16 +153,20 @@ class SingleStat extends PureComponent<Props> {
} }
private get coloration(): CSSProperties { private get coloration(): CSSProperties {
const {data, colors, lineGraph, onUpdateCellColors} = this.props const {colors, lineGraph, onUpdateCellColors} = this.props
const {lastValues} = this.state
const {lastValues, series} = getLastValues(data) let lastValue: number = 0
const firstAlphabeticalSeriesName = _.sortBy(series)[0] if (lastValues) {
const {values, series} = lastValues
const firstAlphabeticalSeriesName = _.sortBy(series)[0]
const firstAlphabeticalIndex = _.indexOf( const firstAlphabeticalIndex = _.indexOf(
series, series,
firstAlphabeticalSeriesName firstAlphabeticalSeriesName
) )
const lastValue = lastValues[firstAlphabeticalIndex] lastValue = values[firstAlphabeticalIndex]
}
const {bgColor, textColor} = generateThresholdsListHexs({ const {bgColor, textColor} = generateThresholdsListHexs({
colors, colors,
@ -170,6 +219,27 @@ class SingleStat extends PureComponent<Props> {
</div> </div>
) )
} }
private async dataToLastValues() {
const {data, dataType} = this.props
try {
let lastValues
if (dataType === DataTypes.flux) {
lastValues = await manager.fluxTablesToSingleStat(data as FluxTable[])
} else if (dataType === DataTypes.influxQL) {
lastValues = getLastValues(data as TimeSeriesServerResponse[])
}
if (!this.isComponentMounted) {
return
}
this.setState({lastValues})
} catch (err) {
console.error(err)
}
}
} }
export default SingleStat export default SingleStat

View File

@ -0,0 +1,83 @@
import {FluxTable} from 'src/types'
const COLUMN_BLACKLIST = new Set([
'_time',
'result',
'table',
'_start',
'_stop',
'',
])
const NUMERIC_DATATYPES = ['double', 'long', 'int', 'float']
interface TableByTime {
[time: string]: {[columnName: string]: string}
}
interface ParseTablesByTimeResult {
tablesByTime: TableByTime[]
allColumnNames: string[]
nonNumericColumns: string[]
}
export const parseTablesByTime = (
tables: FluxTable[]
): ParseTablesByTimeResult => {
const allColumnNames = []
const nonNumericColumns = []
const tablesByTime = tables.map(table => {
const header = table.data[0]
const columnNames: {[k: number]: string} = {}
for (let i = 0; i < header.length; i++) {
const columnName = header[i]
const dataType = table.dataTypes[columnName]
if (COLUMN_BLACKLIST.has(columnName)) {
continue
}
if (table.groupKey[columnName]) {
continue
}
if (!NUMERIC_DATATYPES.includes(dataType)) {
nonNumericColumns.push(columnName)
continue
}
const uniqueColumnName = Object.entries(table.groupKey).reduce(
(acc, [k, v]) => acc + `[${k}=${v}]`,
columnName
)
columnNames[i] = uniqueColumnName
allColumnNames.push(uniqueColumnName)
}
const timeIndex = header.indexOf('_time')
if (timeIndex < 0) {
throw new Error('Could not find time index in FluxTable')
}
const result = {}
for (let i = 1; i < table.data.length; i++) {
const row = table.data[i]
const time = row[timeIndex]
result[time] = Object.entries(columnNames).reduce(
(acc, [valueIndex, columnName]) => ({
...acc,
[columnName]: row[valueIndex],
}),
{}
)
}
return result
})
return {nonNumericColumns, tablesByTime, allColumnNames}
}

View File

@ -2,14 +2,14 @@ import _ from 'lodash'
import {Data} from 'src/types/dygraphs' import {Data} from 'src/types/dygraphs'
import {TimeSeriesServerResponse} from 'src/types/series' import {TimeSeriesServerResponse} from 'src/types/series'
interface Result { interface LastValues {
lastValues: number[] values: number[]
series: string[] series: string[]
} }
export default function( export default function(
timeSeriesResponse: TimeSeriesServerResponse[] | Data | null timeSeriesResponse: TimeSeriesServerResponse[] | Data | null
): Result { ): LastValues {
const values = _.get( const values = _.get(
timeSeriesResponse, timeSeriesResponse,
['0', 'response', 'results', '0', 'series', '0', 'values'], ['0', 'response', 'results', '0', 'series', '0', 'values'],
@ -24,5 +24,5 @@ export default function(
const lastValues = values[values.length - 1].slice(1) // remove time with slice 1 const lastValues = values[values.length - 1].slice(1) // remove time with slice 1
return {lastValues, series} return {values: lastValues, series}
} }

View File

@ -8,6 +8,7 @@ import {getBasepath} from 'src/utils/basepath'
import {TimeSeriesToTableGraphReturnType} from 'src/worker/jobs/timeSeriesToTableGraph' import {TimeSeriesToTableGraphReturnType} from 'src/worker/jobs/timeSeriesToTableGraph'
import {TimeSeriesToDyGraphReturnType} from 'src/worker/jobs/timeSeriesToDygraph' import {TimeSeriesToDyGraphReturnType} from 'src/worker/jobs/timeSeriesToDygraph'
import {FluxTablesToDygraphResult} from 'src/worker/jobs/fluxTablesToDygraph' import {FluxTablesToDygraphResult} from 'src/worker/jobs/fluxTablesToDygraph'
import {LastValues} from 'src/worker/jobs/fluxTablesToSingleStat'
interface DecodeFluxRespWithLimitResult { interface DecodeFluxRespWithLimitResult {
body: string body: string
@ -99,6 +100,10 @@ class JobManager {
return this.publishDBJob('FLUXTODYGRAPH', {raw}) return this.publishDBJob('FLUXTODYGRAPH', {raw})
} }
public fluxTablesToSingleStat = (raw: FluxTable[]): Promise<LastValues> => {
return this.publishDBJob('FLUXTOSINGLE', {raw})
}
public validateDygraphData = (ts: DygraphValue[][]) => { public validateDygraphData = (ts: DygraphValue[][]) => {
return this.publishDBJob('VALIDATEDYGRAPHDATA', ts) return this.publishDBJob('VALIDATEDYGRAPHDATA', ts)
} }

View File

@ -3,6 +3,7 @@ import _ from 'lodash'
import {Message} from 'src/worker/types' import {Message} from 'src/worker/types'
import {fetchData} from 'src/worker/utils' import {fetchData} from 'src/worker/utils'
import {FluxTable, DygraphValue} from 'src/types' import {FluxTable, DygraphValue} from 'src/types'
import {parseTablesByTime} from 'src/shared/parsing/flux/parseTablesByTime'
export interface FluxTablesToDygraphResult { export interface FluxTablesToDygraphResult {
labels: string[] labels: string[]
@ -10,75 +11,12 @@ export interface FluxTablesToDygraphResult {
nonNumericColumns: string[] nonNumericColumns: string[]
} }
const COLUMN_BLACKLIST = new Set([
'_time',
'result',
'table',
'_start',
'_stop',
'',
])
const NUMERIC_DATATYPES = ['double', 'long', 'int', 'float']
export const fluxTablesToDygraphWork = ( export const fluxTablesToDygraphWork = (
tables: FluxTable[] tables: FluxTable[]
): FluxTablesToDygraphResult => { ): FluxTablesToDygraphResult => {
const allColumnNames = [] const {tablesByTime, allColumnNames, nonNumericColumns} = parseTablesByTime(
const nonNumericColumns = [] tables
)
const tablesByTime = tables.map(table => {
const header = table.data[0]
const columnNames: {[k: number]: string} = {}
for (let i = 0; i < header.length; i++) {
const columnName = header[i]
const dataType = table.dataTypes[columnName]
if (COLUMN_BLACKLIST.has(columnName)) {
continue
}
if (table.groupKey[columnName]) {
continue
}
if (!NUMERIC_DATATYPES.includes(dataType)) {
nonNumericColumns.push(columnName)
continue
}
const uniqueColmnName = Object.entries(table.groupKey).reduce(
(acc, [k, v]) => acc + `[${k}=${v}]`,
columnName
)
columnNames[i] = uniqueColmnName
allColumnNames.push(uniqueColmnName)
}
const timeIndex = header.indexOf('_time')
if (timeIndex < 0) {
throw new Error('Could not find time index in FluxTable')
}
const result = {}
for (let i = 1; i < table.data.length; i++) {
const row = table.data[i]
const time = row[timeIndex]
result[time] = Object.entries(columnNames).reduce(
(acc, [valueIndex, columnName]) => ({
...acc,
[columnName]: row[valueIndex],
}),
{}
)
}
return result
})
const dygraphValuesByTime: {[k: string]: DygraphValue[]} = {} const dygraphValuesByTime: {[k: string]: DygraphValue[]} = {}
const DATE_INDEX = 0 const DATE_INDEX = 0

View File

@ -0,0 +1,36 @@
import _ from 'lodash'
import {Message} from 'src/worker/types'
import {fetchData} from 'src/worker/utils'
import {FluxTable} from 'src/types'
import {parseTablesByTime} from 'src/shared/parsing/flux/parseTablesByTime'
export interface LastValues {
values: number[]
series: string[]
}
export const fluxTablesToSingleStatWork = (tables: FluxTable[]): LastValues => {
const {tablesByTime} = parseTablesByTime(tables)
const lastValues = _.reduce(
tablesByTime,
(acc, table) => {
const lastTime = _.last(Object.keys(table))
const values = table[lastTime]
_.forEach(values, (value, series) => {
acc.series.push(series)
acc.values.push(value)
})
return acc
},
{values: [], series: []}
)
return lastValues
}
export default async (msg: Message): Promise<LastValues> => {
const {raw} = await fetchData(msg)
return fluxTablesToSingleStatWork(raw)
}

View File

@ -16,6 +16,7 @@ import validateDygraphData from 'src/worker/jobs/validateDygraphData'
import postJSON from 'src/worker/jobs/postJSON' import postJSON from 'src/worker/jobs/postJSON'
import fetchFluxData from 'src/worker/jobs/fetchFluxData' import fetchFluxData from 'src/worker/jobs/fetchFluxData'
import fluxTablesToDygraph from 'src/worker/jobs/fluxTablesToDygraph' import fluxTablesToDygraph from 'src/worker/jobs/fluxTablesToDygraph'
import fluxTablesToSingleStat from 'src/worker/jobs/fluxTablesToSingleStat'
type Job = (msg: Message) => Promise<any> type Job = (msg: Message) => Promise<any>
@ -29,6 +30,7 @@ const jobMapping: {[key: string]: Job} = {
VALIDATEDYGRAPHDATA: validateDygraphData, VALIDATEDYGRAPHDATA: validateDygraphData,
FETCHFLUXDATA: fetchFluxData, FETCHFLUXDATA: fetchFluxData,
FLUXTODYGRAPH: fluxTablesToDygraph, FLUXTODYGRAPH: fluxTablesToDygraph,
FLUXTOSINGLE: fluxTablesToSingleStat,
} }
const errorJob = async (data: Message) => { const errorJob = async (data: Message) => {

View File

@ -2,6 +2,7 @@ import {shallow} from 'enzyme'
import React from 'react' import React from 'react'
import Gauge from 'src/shared/components/Gauge' import Gauge from 'src/shared/components/Gauge'
import GaugeChart from 'src/shared/components/GaugeChart' import GaugeChart from 'src/shared/components/GaugeChart'
import {DataTypes} from 'src/shared/components/RefreshingGraph'
const data = [ const data = [
{ {
@ -30,6 +31,7 @@ const defaultProps = {
digits: 10, digits: 10,
isEnforced: false, isEnforced: false,
}, },
dataType: DataTypes.influxQL,
} }
const setup = (overrides = {}) => { const setup = (overrides = {}) => {

View File

@ -3,7 +3,7 @@ import lastValues from 'src/shared/parsing/lastValues'
describe('lastValues', () => { describe('lastValues', () => {
it('returns the correct value when response is empty', () => { it('returns the correct value when response is empty', () => {
expect(lastValues([])).toEqual({ expect(lastValues([])).toEqual({
lastValues: [''], values: [''],
series: [''], series: [''],
}) })
}) })