Update Single Stat Vis types to work with Flux data (#4449)
parent
5f138d6b63
commit
b251c9350e
|
@ -22,6 +22,7 @@
|
|||
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. [#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
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import {manager} from 'src/worker/JobManager'
|
||||
|
||||
import getLastValues from 'src/shared/parsing/lastValues'
|
||||
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 {ColorString} from 'src/types/colors'
|
||||
import {TimeSeriesServerResponse} from 'src/types/series'
|
||||
import {FluxTable} from 'src/types/flux'
|
||||
import {DataTypes} from 'src/shared/components/RefreshingGraph'
|
||||
|
||||
interface Props {
|
||||
data: TimeSeriesServerResponse[]
|
||||
data: TimeSeriesServerResponse[] | FluxTable[]
|
||||
dataType: DataTypes
|
||||
decimalPlaces: DecimalPlaces
|
||||
cellID: string
|
||||
cellHeight?: number
|
||||
|
@ -23,12 +28,46 @@ interface Props {
|
|||
resizerTopHeight?: number
|
||||
}
|
||||
|
||||
interface State {
|
||||
lastValues?: {
|
||||
values: number[]
|
||||
series: string[]
|
||||
}
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class GaugeChart extends PureComponent<Props> {
|
||||
class GaugeChart extends PureComponent<Props, State> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
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() {
|
||||
const {colors, prefix, suffix, decimalPlaces} = this.props
|
||||
return (
|
||||
|
@ -63,9 +102,8 @@ class GaugeChart extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
private get lastValueForGauge(): number {
|
||||
const {data} = this.props
|
||||
const {lastValues} = getLastValues(data)
|
||||
const lastValue = _.get(lastValues, 0, 0)
|
||||
const {lastValues} = this.state
|
||||
const lastValue = _.get(lastValues, 'values.0', 0)
|
||||
|
||||
if (!lastValue) {
|
||||
return 0
|
||||
|
@ -73,6 +111,27 @@ class GaugeChart extends PureComponent<Props> {
|
|||
|
||||
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
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
CellType,
|
||||
FluxTable,
|
||||
} from 'src/types'
|
||||
import {DataTypes} from 'src/shared/components/RefreshingGraph'
|
||||
|
||||
interface Props {
|
||||
axes: Axes
|
||||
|
@ -37,8 +38,8 @@ interface Props {
|
|||
colors: ColorString[]
|
||||
loading: RemoteDataState
|
||||
decimalPlaces: DecimalPlaces
|
||||
fluxData: FluxTable[]
|
||||
influxQLData: TimeSeriesServerResponse[]
|
||||
data: TimeSeriesServerResponse[] | FluxTable[]
|
||||
dataType: DataTypes
|
||||
cellID: string
|
||||
cellHeight: number
|
||||
staticLegend: boolean
|
||||
|
@ -70,17 +71,20 @@ class LineGraph extends PureComponent<LineGraphProps, State> {
|
|||
|
||||
public async componentDidMount() {
|
||||
this.isComponentMounted = true
|
||||
const {influxQLData, fluxData} = this.props
|
||||
await this.parseTimeSeries(influxQLData, fluxData)
|
||||
const {data, dataType} = this.props
|
||||
await this.parseTimeSeries(data, dataType)
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.isComponentMounted = false
|
||||
}
|
||||
|
||||
public async parseTimeSeries(data, fluxData) {
|
||||
public async parseTimeSeries(
|
||||
data: TimeSeriesServerResponse[] | FluxTable[],
|
||||
dataType: DataTypes
|
||||
) {
|
||||
try {
|
||||
const timeSeries = await this.convertToDygraphData(data, fluxData)
|
||||
const timeSeries = await this.convertToDygraphData(data, dataType)
|
||||
|
||||
this.isValidData = await manager.validateDygraphData(
|
||||
timeSeries.timeSeries
|
||||
|
@ -97,7 +101,7 @@ class LineGraph extends PureComponent<LineGraphProps, State> {
|
|||
|
||||
public componentWillReceiveProps(nextProps: LineGraphProps) {
|
||||
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 {
|
||||
influxQLData,
|
||||
data,
|
||||
axes,
|
||||
type,
|
||||
colors,
|
||||
|
@ -115,6 +119,7 @@ class LineGraph extends PureComponent<LineGraphProps, State> {
|
|||
onZoom,
|
||||
loading,
|
||||
queries,
|
||||
dataType,
|
||||
timeRange,
|
||||
cellHeight,
|
||||
staticLegend,
|
||||
|
@ -165,7 +170,8 @@ class LineGraph extends PureComponent<LineGraphProps, State> {
|
|||
>
|
||||
{type === CellType.LinePlusSingleStat && (
|
||||
<SingleStat
|
||||
data={influxQLData}
|
||||
data={data}
|
||||
dataType={dataType}
|
||||
lineGraph={true}
|
||||
colors={colors}
|
||||
prefix={this.prefix}
|
||||
|
@ -223,17 +229,20 @@ class LineGraph extends PureComponent<LineGraphProps, State> {
|
|||
}
|
||||
|
||||
private async convertToDygraphData(
|
||||
data: TimeSeriesServerResponse[],
|
||||
fluxData: FluxTable[]
|
||||
data: TimeSeriesServerResponse[] | FluxTable[],
|
||||
dataType: DataTypes
|
||||
): Promise<TimeSeriesToDyGraphReturnType> {
|
||||
const {location} = this.props
|
||||
|
||||
if (data.length) {
|
||||
return await timeSeriesToDygraph(data, location.pathname)
|
||||
if (dataType === DataTypes.influxQL) {
|
||||
return await timeSeriesToDygraph(
|
||||
data as TimeSeriesServerResponse[],
|
||||
location.pathname
|
||||
)
|
||||
}
|
||||
|
||||
if (fluxData.length) {
|
||||
return await fluxTablesToDygraph(fluxData)
|
||||
if (dataType === DataTypes.flux) {
|
||||
return await fluxTablesToDygraph(data as FluxTable[])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,16 @@ import {GrabDataForDownloadHandler} from 'src/types/layout'
|
|||
import {VisType} from 'src/types/flux'
|
||||
import {TimeSeriesServerResponse} from 'src/types/series'
|
||||
|
||||
interface TypeAndData {
|
||||
dataType: DataTypes
|
||||
data: TimeSeriesServerResponse[] | FluxTable[]
|
||||
}
|
||||
|
||||
export enum DataTypes {
|
||||
flux = 'flux',
|
||||
influxQL = 'influxQL',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
axes: Axes
|
||||
source: Source
|
||||
|
@ -161,11 +171,11 @@ class RefreshingGraph extends PureComponent<Props> {
|
|||
{({timeSeriesInfluxQL, timeSeriesFlux, loading}) => {
|
||||
switch (type) {
|
||||
case CellType.SingleStat:
|
||||
return this.singleStat(timeSeriesInfluxQL)
|
||||
return this.singleStat(timeSeriesInfluxQL, timeSeriesFlux)
|
||||
case CellType.Table:
|
||||
return this.table(timeSeriesInfluxQL)
|
||||
case CellType.Gauge:
|
||||
return this.gauge(timeSeriesInfluxQL)
|
||||
return this.gauge(timeSeriesInfluxQL, timeSeriesFlux)
|
||||
default:
|
||||
return this.lineGraph(timeSeriesInfluxQL, timeSeriesFlux, loading)
|
||||
}
|
||||
|
@ -189,7 +199,10 @@ class RefreshingGraph extends PureComponent<Props> {
|
|||
return !_.isEqual(prevVisValues, curVisValues)
|
||||
}
|
||||
|
||||
private singleStat = (data): JSX.Element => {
|
||||
private singleStat = (
|
||||
influxQLData: TimeSeriesServerResponse[],
|
||||
fluxData: FluxTable[]
|
||||
): JSX.Element => {
|
||||
const {
|
||||
colors,
|
||||
cellHeight,
|
||||
|
@ -198,8 +211,11 @@ class RefreshingGraph extends PureComponent<Props> {
|
|||
onUpdateCellColors,
|
||||
} = this.props
|
||||
|
||||
const {dataType, data} = this.getTypeAndData(influxQLData, fluxData)
|
||||
|
||||
return (
|
||||
<SingleStat
|
||||
dataType={dataType}
|
||||
data={data}
|
||||
colors={colors}
|
||||
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 {
|
||||
colors,
|
||||
cellID,
|
||||
|
@ -250,9 +269,12 @@ class RefreshingGraph extends PureComponent<Props> {
|
|||
resizerTopHeight,
|
||||
} = this.props
|
||||
|
||||
const {dataType, data} = this.getTypeAndData(influxQLData, fluxData)
|
||||
|
||||
return (
|
||||
<GaugeChart
|
||||
data={data}
|
||||
dataType={dataType}
|
||||
cellID={cellID}
|
||||
colors={colors}
|
||||
prefix={this.prefix}
|
||||
|
@ -285,18 +307,20 @@ class RefreshingGraph extends PureComponent<Props> {
|
|||
handleSetHoverTime,
|
||||
} = this.props
|
||||
|
||||
const {dataType, data} = this.getTypeAndData(influxQLData, fluxData)
|
||||
|
||||
return (
|
||||
<LineGraph
|
||||
influxQLData={influxQLData}
|
||||
data={data}
|
||||
type={type}
|
||||
axes={axes}
|
||||
cellID={cellID}
|
||||
colors={colors}
|
||||
onZoom={onZoom}
|
||||
queries={queries}
|
||||
fluxData={fluxData}
|
||||
key={manualRefresh}
|
||||
loading={loading}
|
||||
dataType={dataType}
|
||||
key={manualRefresh}
|
||||
timeRange={timeRange}
|
||||
cellHeight={cellHeight}
|
||||
staticLegend={staticLegend}
|
||||
|
@ -329,6 +353,19 @@ class RefreshingGraph extends PureComponent<Props> {
|
|||
const {axes} = this.props
|
||||
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}}) => ({
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, {PureComponent, CSSProperties} from 'react'
|
|||
import classnames from 'classnames'
|
||||
import getLastValues from 'src/shared/parsing/lastValues'
|
||||
import _ from 'lodash'
|
||||
import {manager} from 'src/worker/JobManager'
|
||||
|
||||
import {SMALL_CELL_HEIGHT} from 'src/shared/graphs/helpers'
|
||||
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 {TimeSeriesServerResponse} from 'src/types/series'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {FluxTable} from 'src/types'
|
||||
import {DataTypes} from 'src/shared/components/RefreshingGraph'
|
||||
|
||||
interface Props {
|
||||
decimalPlaces: DecimalPlaces
|
||||
|
@ -19,21 +22,60 @@ interface Props {
|
|||
suffix?: string
|
||||
lineGraph: boolean
|
||||
staticLegendHeight?: number
|
||||
data: TimeSeriesServerResponse[]
|
||||
data: TimeSeriesServerResponse[] | FluxTable[]
|
||||
dataType: DataTypes
|
||||
onUpdateCellColors?: (bgColor: string, textColor: string) => void
|
||||
}
|
||||
|
||||
interface State {
|
||||
lastValues?: {
|
||||
values: number[]
|
||||
series: string[]
|
||||
}
|
||||
}
|
||||
|
||||
const NOOP = () => {}
|
||||
|
||||
@ErrorHandling
|
||||
class SingleStat extends PureComponent<Props> {
|
||||
class SingleStat extends PureComponent<Props, State> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
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() {
|
||||
if (!this.state.lastValues) {
|
||||
return <h3 className="graph-spinner" />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="single-stat" style={this.containerStyle}>
|
||||
{this.resizerBox}
|
||||
|
@ -54,16 +96,19 @@ class SingleStat extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
private get lastValue(): number {
|
||||
const {data} = this.props
|
||||
const {lastValues, series} = getLastValues(data)
|
||||
const firstAlphabeticalSeriesName = _.sortBy(series)[0]
|
||||
const {lastValues} = this.state
|
||||
|
||||
const firstAlphabeticalIndex = _.indexOf(
|
||||
series,
|
||||
firstAlphabeticalSeriesName
|
||||
)
|
||||
if (lastValues) {
|
||||
const {values, series} = lastValues
|
||||
const firstAlphabeticalSeriesName = _.sortBy(series)[0]
|
||||
|
||||
return lastValues[firstAlphabeticalIndex]
|
||||
const firstAlphabeticalIndex = _.indexOf(
|
||||
series,
|
||||
firstAlphabeticalSeriesName
|
||||
)
|
||||
|
||||
return values[firstAlphabeticalIndex]
|
||||
}
|
||||
}
|
||||
|
||||
private get roundedLastValue(): string {
|
||||
|
@ -108,16 +153,20 @@ class SingleStat extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
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)
|
||||
const firstAlphabeticalSeriesName = _.sortBy(series)[0]
|
||||
let lastValue: number = 0
|
||||
if (lastValues) {
|
||||
const {values, series} = lastValues
|
||||
const firstAlphabeticalSeriesName = _.sortBy(series)[0]
|
||||
|
||||
const firstAlphabeticalIndex = _.indexOf(
|
||||
series,
|
||||
firstAlphabeticalSeriesName
|
||||
)
|
||||
const lastValue = lastValues[firstAlphabeticalIndex]
|
||||
const firstAlphabeticalIndex = _.indexOf(
|
||||
series,
|
||||
firstAlphabeticalSeriesName
|
||||
)
|
||||
lastValue = values[firstAlphabeticalIndex]
|
||||
}
|
||||
|
||||
const {bgColor, textColor} = generateThresholdsListHexs({
|
||||
colors,
|
||||
|
@ -170,6 +219,27 @@ class SingleStat extends PureComponent<Props> {
|
|||
</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
|
||||
|
|
|
@ -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}
|
||||
}
|
|
@ -2,14 +2,14 @@ import _ from 'lodash'
|
|||
import {Data} from 'src/types/dygraphs'
|
||||
import {TimeSeriesServerResponse} from 'src/types/series'
|
||||
|
||||
interface Result {
|
||||
lastValues: number[]
|
||||
interface LastValues {
|
||||
values: number[]
|
||||
series: string[]
|
||||
}
|
||||
|
||||
export default function(
|
||||
timeSeriesResponse: TimeSeriesServerResponse[] | Data | null
|
||||
): Result {
|
||||
): LastValues {
|
||||
const values = _.get(
|
||||
timeSeriesResponse,
|
||||
['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
|
||||
|
||||
return {lastValues, series}
|
||||
return {values: lastValues, series}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {getBasepath} from 'src/utils/basepath'
|
|||
import {TimeSeriesToTableGraphReturnType} from 'src/worker/jobs/timeSeriesToTableGraph'
|
||||
import {TimeSeriesToDyGraphReturnType} from 'src/worker/jobs/timeSeriesToDygraph'
|
||||
import {FluxTablesToDygraphResult} from 'src/worker/jobs/fluxTablesToDygraph'
|
||||
import {LastValues} from 'src/worker/jobs/fluxTablesToSingleStat'
|
||||
|
||||
interface DecodeFluxRespWithLimitResult {
|
||||
body: string
|
||||
|
@ -99,6 +100,10 @@ class JobManager {
|
|||
return this.publishDBJob('FLUXTODYGRAPH', {raw})
|
||||
}
|
||||
|
||||
public fluxTablesToSingleStat = (raw: FluxTable[]): Promise<LastValues> => {
|
||||
return this.publishDBJob('FLUXTOSINGLE', {raw})
|
||||
}
|
||||
|
||||
public validateDygraphData = (ts: DygraphValue[][]) => {
|
||||
return this.publishDBJob('VALIDATEDYGRAPHDATA', ts)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import _ from 'lodash'
|
|||
import {Message} from 'src/worker/types'
|
||||
import {fetchData} from 'src/worker/utils'
|
||||
import {FluxTable, DygraphValue} from 'src/types'
|
||||
import {parseTablesByTime} from 'src/shared/parsing/flux/parseTablesByTime'
|
||||
|
||||
export interface FluxTablesToDygraphResult {
|
||||
labels: string[]
|
||||
|
@ -10,75 +11,12 @@ export interface FluxTablesToDygraphResult {
|
|||
nonNumericColumns: string[]
|
||||
}
|
||||
|
||||
const COLUMN_BLACKLIST = new Set([
|
||||
'_time',
|
||||
'result',
|
||||
'table',
|
||||
'_start',
|
||||
'_stop',
|
||||
'',
|
||||
])
|
||||
|
||||
const NUMERIC_DATATYPES = ['double', 'long', 'int', 'float']
|
||||
|
||||
export const fluxTablesToDygraphWork = (
|
||||
tables: FluxTable[]
|
||||
): FluxTablesToDygraphResult => {
|
||||
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 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 {tablesByTime, allColumnNames, nonNumericColumns} = parseTablesByTime(
|
||||
tables
|
||||
)
|
||||
|
||||
const dygraphValuesByTime: {[k: string]: DygraphValue[]} = {}
|
||||
const DATE_INDEX = 0
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -16,6 +16,7 @@ import validateDygraphData from 'src/worker/jobs/validateDygraphData'
|
|||
import postJSON from 'src/worker/jobs/postJSON'
|
||||
import fetchFluxData from 'src/worker/jobs/fetchFluxData'
|
||||
import fluxTablesToDygraph from 'src/worker/jobs/fluxTablesToDygraph'
|
||||
import fluxTablesToSingleStat from 'src/worker/jobs/fluxTablesToSingleStat'
|
||||
|
||||
type Job = (msg: Message) => Promise<any>
|
||||
|
||||
|
@ -29,6 +30,7 @@ const jobMapping: {[key: string]: Job} = {
|
|||
VALIDATEDYGRAPHDATA: validateDygraphData,
|
||||
FETCHFLUXDATA: fetchFluxData,
|
||||
FLUXTODYGRAPH: fluxTablesToDygraph,
|
||||
FLUXTOSINGLE: fluxTablesToSingleStat,
|
||||
}
|
||||
|
||||
const errorJob = async (data: Message) => {
|
||||
|
|
|
@ -2,6 +2,7 @@ import {shallow} from 'enzyme'
|
|||
import React from 'react'
|
||||
import Gauge from 'src/shared/components/Gauge'
|
||||
import GaugeChart from 'src/shared/components/GaugeChart'
|
||||
import {DataTypes} from 'src/shared/components/RefreshingGraph'
|
||||
|
||||
const data = [
|
||||
{
|
||||
|
@ -30,6 +31,7 @@ const defaultProps = {
|
|||
digits: 10,
|
||||
isEnforced: false,
|
||||
},
|
||||
dataType: DataTypes.influxQL,
|
||||
}
|
||||
|
||||
const setup = (overrides = {}) => {
|
||||
|
|
|
@ -3,7 +3,7 @@ import lastValues from 'src/shared/parsing/lastValues'
|
|||
describe('lastValues', () => {
|
||||
it('returns the correct value when response is empty', () => {
|
||||
expect(lastValues([])).toEqual({
|
||||
lastValues: [''],
|
||||
values: [''],
|
||||
series: [''],
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue