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. [#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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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[])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}}) => ({
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {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}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 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) => {
|
||||||
|
|
|
@ -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 = {}) => {
|
||||||
|
|
|
@ -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: [''],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue