Merge pull request #4550 from influxdata/flux/table-graph-update
Table graph update timepull/4563/head
commit
5e9e859a3a
|
@ -20,7 +20,7 @@ const calculateSize = (message: string): number => {
|
||||||
return message.length * 7
|
return message.length * 7
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ColumnWidths {
|
export interface ColumnWidths {
|
||||||
totalWidths: number
|
totalWidths: number
|
||||||
widths: {[x: string]: number}
|
widths: {[x: string]: number}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,20 @@ interface TransformTableDataReturnType {
|
||||||
columnWidths: ColumnWidths
|
columnWidths: ColumnWidths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ErrorTypes {
|
||||||
|
MetaQueryCombo = 'MetaQueryCombo',
|
||||||
|
GeneralError = 'Error',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getInvalidDataMessage = (errorType: ErrorTypes): string => {
|
||||||
|
switch (errorType) {
|
||||||
|
case ErrorTypes.MetaQueryCombo:
|
||||||
|
return 'Cannot display data for meta queries mixed with data queries'
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const calculateTimeColumnWidth = (timeFormat: string): number => {
|
const calculateTimeColumnWidth = (timeFormat: string): number => {
|
||||||
// Force usage of longest format names for ideal measurement
|
// Force usage of longest format names for ideal measurement
|
||||||
timeFormat = _.replace(timeFormat, 'MMMM', 'September')
|
timeFormat = _.replace(timeFormat, 'MMMM', 'September')
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Libraries
|
// Libraries
|
||||||
import React, {PureComponent} from 'react'
|
import React, {PureComponent} from 'react'
|
||||||
|
import uuid from 'uuid'
|
||||||
import memoizeOne from 'memoize-one'
|
import memoizeOne from 'memoize-one'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
@ -7,6 +8,8 @@ import TableSidebar from 'src/flux/components/TableSidebar'
|
||||||
import {FluxTable} from 'src/types'
|
import {FluxTable} from 'src/types'
|
||||||
import NoResults from 'src/flux/components/NoResults'
|
import NoResults from 'src/flux/components/NoResults'
|
||||||
import TableGraph from 'src/shared/components/TableGraph'
|
import TableGraph from 'src/shared/components/TableGraph'
|
||||||
|
import TableGraphTransform from 'src/shared/components/TableGraphTransform'
|
||||||
|
import TableGraphFormat from 'src/shared/components/TableGraphFormat'
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import {getDeep} from 'src/utils/wrappers'
|
import {getDeep} from 'src/utils/wrappers'
|
||||||
|
@ -19,6 +22,7 @@ import {QueryUpdateState} from 'src/types'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: FluxTable[]
|
data: FluxTable[]
|
||||||
|
uuid: string
|
||||||
dataType: DataType
|
dataType: DataType
|
||||||
tableOptions: TableOptions
|
tableOptions: TableOptions
|
||||||
timeFormat: string
|
timeFormat: string
|
||||||
|
@ -91,18 +95,40 @@ class TimeMachineTables extends PureComponent<Props, State> {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{this.shouldShowTable && (
|
{this.shouldShowTable && (
|
||||||
<TableGraph
|
<TableGraphTransform
|
||||||
data={this.selectedResult}
|
data={this.selectedResult}
|
||||||
dataType={dataType}
|
dataType={dataType}
|
||||||
colors={colors}
|
uuid={uuid.v4()}
|
||||||
tableOptions={tableOptions}
|
>
|
||||||
fieldOptions={this.fieldOptions}
|
{(transformedData, nextUUID) => (
|
||||||
timeFormat={timeFormat}
|
<TableGraphFormat
|
||||||
decimalPlaces={decimalPlaces}
|
data={transformedData}
|
||||||
editorLocation={editorLocation}
|
uuid={nextUUID}
|
||||||
handleSetHoverTime={handleSetHoverTime}
|
dataType={dataType}
|
||||||
onUpdateFieldOptions={onUpdateFieldOptions}
|
tableOptions={tableOptions}
|
||||||
/>
|
timeFormat={timeFormat}
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
fieldOptions={this.fieldOptions}
|
||||||
|
>
|
||||||
|
{(formattedData, sort, computedFieldOptions, onSort) => (
|
||||||
|
<TableGraph
|
||||||
|
data={formattedData}
|
||||||
|
sort={sort}
|
||||||
|
onSort={onSort}
|
||||||
|
dataType={dataType}
|
||||||
|
colors={colors}
|
||||||
|
tableOptions={tableOptions}
|
||||||
|
fieldOptions={computedFieldOptions}
|
||||||
|
timeFormat={timeFormat}
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
editorLocation={editorLocation}
|
||||||
|
handleSetHoverTime={handleSetHoverTime}
|
||||||
|
onUpdateFieldOptions={onUpdateFieldOptions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableGraphFormat>
|
||||||
|
)}
|
||||||
|
</TableGraphTransform>
|
||||||
)}
|
)}
|
||||||
{!this.hasResults && <NoResults />}
|
{!this.hasResults && <NoResults />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -87,9 +87,10 @@ class YieldFuncNode extends PureComponent<Props, State> {
|
||||||
<div className="yield-node">
|
<div className="yield-node">
|
||||||
<div className="func-node--connector" />
|
<div className="func-node--connector" />
|
||||||
<TimeSeries source={source} queries={queries} timeRange={timeRange}>
|
<TimeSeries source={source} queries={queries} timeRange={timeRange}>
|
||||||
{({timeSeriesFlux}) => (
|
{({timeSeriesFlux, uuid}) => (
|
||||||
<YieldNodeVis
|
<YieldNodeVis
|
||||||
data={timeSeriesFlux}
|
data={timeSeriesFlux}
|
||||||
|
uuid={uuid}
|
||||||
yieldName={yieldName}
|
yieldName={yieldName}
|
||||||
axes={axes}
|
axes={axes}
|
||||||
tableOptions={tableOptions}
|
tableOptions={tableOptions}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {ColorNumber, ColorString} from 'src/types/colors'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: FluxTable[]
|
data: FluxTable[]
|
||||||
|
uuid: string
|
||||||
yieldName: string
|
yieldName: string
|
||||||
axes: Axes | null
|
axes: Axes | null
|
||||||
tableOptions: TableOptions
|
tableOptions: TableOptions
|
||||||
|
@ -86,6 +87,7 @@ class YieldNodeVis extends PureComponent<Props, State> {
|
||||||
const {visType} = this.state
|
const {visType} = this.state
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
uuid,
|
||||||
tableOptions,
|
tableOptions,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
decimalPlaces,
|
decimalPlaces,
|
||||||
|
@ -113,6 +115,7 @@ class YieldNodeVis extends PureComponent<Props, State> {
|
||||||
return (
|
return (
|
||||||
<TimeMachineTables
|
<TimeMachineTables
|
||||||
data={data}
|
data={data}
|
||||||
|
uuid={uuid}
|
||||||
dataType={DataType.flux}
|
dataType={DataType.flux}
|
||||||
tableOptions={tableOptions}
|
tableOptions={tableOptions}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
|
|
|
@ -12,6 +12,8 @@ import MarkdownCell from 'src/shared/components/MarkdownCell'
|
||||||
import TimeSeries from 'src/shared/components/time_series/TimeSeries'
|
import TimeSeries from 'src/shared/components/time_series/TimeSeries'
|
||||||
import TimeMachineTables from 'src/flux/components/TimeMachineTables'
|
import TimeMachineTables from 'src/flux/components/TimeMachineTables'
|
||||||
import RawFluxDataTable from 'src/shared/components/TimeMachine/RawFluxDataTable'
|
import RawFluxDataTable from 'src/shared/components/TimeMachine/RawFluxDataTable'
|
||||||
|
import TableGraphTransform from 'src/shared/components/TableGraphTransform'
|
||||||
|
import TableGraphFormat from 'src/shared/components/TableGraphFormat'
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
import {emptyGraphCopy} from 'src/shared/copy/cell'
|
import {emptyGraphCopy} from 'src/shared/copy/cell'
|
||||||
|
@ -159,7 +161,7 @@ class RefreshingGraph extends PureComponent<Props> {
|
||||||
cellNote={cellNote}
|
cellNote={cellNote}
|
||||||
cellNoteVisibility={cellNoteVisibility}
|
cellNoteVisibility={cellNoteVisibility}
|
||||||
>
|
>
|
||||||
{({timeSeriesInfluxQL, timeSeriesFlux, rawFluxData, loading}) => {
|
{({timeSeriesInfluxQL, timeSeriesFlux, rawFluxData, loading, uuid}) => {
|
||||||
if (showRawFluxData) {
|
if (showRawFluxData) {
|
||||||
return <RawFluxDataTable csv={rawFluxData} />
|
return <RawFluxDataTable csv={rawFluxData} />
|
||||||
}
|
}
|
||||||
|
@ -168,7 +170,7 @@ class RefreshingGraph extends PureComponent<Props> {
|
||||||
case CellType.SingleStat:
|
case CellType.SingleStat:
|
||||||
return this.singleStat(timeSeriesInfluxQL, timeSeriesFlux)
|
return this.singleStat(timeSeriesInfluxQL, timeSeriesFlux)
|
||||||
case CellType.Table:
|
case CellType.Table:
|
||||||
return this.table(timeSeriesInfluxQL, timeSeriesFlux)
|
return this.table(timeSeriesInfluxQL, timeSeriesFlux, uuid)
|
||||||
case CellType.Gauge:
|
case CellType.Gauge:
|
||||||
return this.gauge(timeSeriesInfluxQL, timeSeriesFlux)
|
return this.gauge(timeSeriesInfluxQL, timeSeriesFlux)
|
||||||
default:
|
default:
|
||||||
|
@ -228,7 +230,8 @@ class RefreshingGraph extends PureComponent<Props> {
|
||||||
|
|
||||||
private table = (
|
private table = (
|
||||||
influxQLData: TimeSeriesServerResponse[],
|
influxQLData: TimeSeriesServerResponse[],
|
||||||
fluxData: FluxTable[]
|
fluxData: FluxTable[],
|
||||||
|
uuid: string
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
const {
|
const {
|
||||||
colors,
|
colors,
|
||||||
|
@ -247,6 +250,7 @@ class RefreshingGraph extends PureComponent<Props> {
|
||||||
return (
|
return (
|
||||||
<TimeMachineTables
|
<TimeMachineTables
|
||||||
data={data as FluxTable[]}
|
data={data as FluxTable[]}
|
||||||
|
uuid={uuid}
|
||||||
dataType={dataType}
|
dataType={dataType}
|
||||||
colors={colors}
|
colors={colors}
|
||||||
key={manualRefresh}
|
key={manualRefresh}
|
||||||
|
@ -262,19 +266,41 @@ class RefreshingGraph extends PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableGraph
|
<TableGraphTransform
|
||||||
data={data}
|
data={data as TimeSeriesServerResponse[]}
|
||||||
|
uuid={uuid}
|
||||||
dataType={dataType}
|
dataType={dataType}
|
||||||
colors={colors}
|
>
|
||||||
key={manualRefresh}
|
{(transformedData, nextUUID) => (
|
||||||
tableOptions={tableOptions}
|
<TableGraphFormat
|
||||||
fieldOptions={fieldOptions}
|
data={transformedData}
|
||||||
timeFormat={timeFormat}
|
uuid={nextUUID}
|
||||||
decimalPlaces={decimalPlaces}
|
dataType={dataType}
|
||||||
editorLocation={editorLocation}
|
tableOptions={tableOptions}
|
||||||
handleSetHoverTime={handleSetHoverTime}
|
fieldOptions={fieldOptions}
|
||||||
onUpdateFieldOptions={onUpdateFieldOptions}
|
timeFormat={timeFormat}
|
||||||
/>
|
decimalPlaces={decimalPlaces}
|
||||||
|
>
|
||||||
|
{(formattedData, sort, computedFieldOptions, onSort) => (
|
||||||
|
<TableGraph
|
||||||
|
data={formattedData}
|
||||||
|
sort={sort}
|
||||||
|
onSort={onSort}
|
||||||
|
dataType={dataType}
|
||||||
|
colors={colors}
|
||||||
|
key={manualRefresh}
|
||||||
|
tableOptions={tableOptions}
|
||||||
|
fieldOptions={computedFieldOptions}
|
||||||
|
timeFormat={timeFormat}
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
editorLocation={editorLocation}
|
||||||
|
handleSetHoverTime={handleSetHoverTime}
|
||||||
|
onUpdateFieldOptions={onUpdateFieldOptions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableGraphFormat>
|
||||||
|
)}
|
||||||
|
</TableGraphTransform>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,38 +8,25 @@ import {ColumnSizer, SizedColumnProps, AutoSizer} from 'react-virtualized'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import {MultiGrid, PropsMultiGrid} from 'src/shared/components/MultiGrid'
|
import {MultiGrid, PropsMultiGrid} from 'src/shared/components/MultiGrid'
|
||||||
import InvalidData from 'src/shared/components/InvalidData'
|
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import {fastReduce} from 'src/utils/fast'
|
import {fastReduce} from 'src/utils/fast'
|
||||||
import {timeSeriesToTableGraph} from 'src/utils/timeSeriesTransformers'
|
|
||||||
import {
|
|
||||||
computeFieldOptions,
|
|
||||||
getDefaultTimeField,
|
|
||||||
} from 'src/dashboards/utils/tableGraph'
|
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
import {manager} from 'src/worker/JobManager'
|
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
|
import {getDefaultTimeField} from 'src/dashboards/utils/tableGraph'
|
||||||
import {
|
import {
|
||||||
ASCENDING,
|
ASCENDING,
|
||||||
DESCENDING,
|
|
||||||
NULL_HOVER_TIME,
|
NULL_HOVER_TIME,
|
||||||
NULL_ARRAY_INDEX,
|
NULL_ARRAY_INDEX,
|
||||||
DEFAULT_FIX_FIRST_COLUMN,
|
DEFAULT_FIX_FIRST_COLUMN,
|
||||||
DEFAULT_VERTICAL_TIME_AXIS,
|
DEFAULT_VERTICAL_TIME_AXIS,
|
||||||
DEFAULT_SORT_DIRECTION,
|
|
||||||
} from 'src/shared/constants/tableGraph'
|
} from 'src/shared/constants/tableGraph'
|
||||||
import {generateThresholdsListHexs} from 'src/shared/constants/colorOperations'
|
import {generateThresholdsListHexs} from 'src/shared/constants/colorOperations'
|
||||||
import {DataType} from 'src/shared/constants'
|
import {DataType} from 'src/shared/constants'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import {
|
import {TimeSeriesValue} from 'src/types/series'
|
||||||
TimeSeriesServerResponse,
|
|
||||||
TimeSeriesValue,
|
|
||||||
TimeSeriesToTableGraphReturnType,
|
|
||||||
InfluxQLQueryType,
|
|
||||||
} from 'src/types/series'
|
|
||||||
import {ColorString} from 'src/types/colors'
|
import {ColorString} from 'src/types/colors'
|
||||||
import {
|
import {
|
||||||
TableOptions,
|
TableOptions,
|
||||||
|
@ -47,7 +34,9 @@ import {
|
||||||
DecimalPlaces,
|
DecimalPlaces,
|
||||||
Sort,
|
Sort,
|
||||||
} from 'src/types/dashboards'
|
} from 'src/types/dashboards'
|
||||||
import {FluxTable, QueryUpdateState} from 'src/types'
|
import {QueryUpdateState} from 'src/types'
|
||||||
|
|
||||||
|
import {FormattedTableData} from 'src/shared/components/TableGraphFormat'
|
||||||
|
|
||||||
const COLUMN_MIN_WIDTH = 100
|
const COLUMN_MIN_WIDTH = 100
|
||||||
const ROW_HEIGHT = 30
|
const ROW_HEIGHT = 30
|
||||||
|
@ -66,13 +55,10 @@ interface CellRendererProps {
|
||||||
style: React.CSSProperties
|
style: React.CSSProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ErrorTypes {
|
|
||||||
MetaQueryCombo = 'MetaQueryCombo',
|
|
||||||
GeneralError = 'Error',
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: TimeSeriesServerResponse[] | FluxTable
|
data: FormattedTableData
|
||||||
|
onSort: (fieldName: string) => void
|
||||||
|
sort: Sort
|
||||||
dataType: DataType
|
dataType: DataType
|
||||||
tableOptions: TableOptions
|
tableOptions: TableOptions
|
||||||
timeFormat: string
|
timeFormat: string
|
||||||
|
@ -86,67 +72,44 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
data: TimeSeriesValue[][]
|
|
||||||
transformedData: TimeSeriesValue[][]
|
|
||||||
sortedTimeVals: TimeSeriesValue[]
|
sortedTimeVals: TimeSeriesValue[]
|
||||||
sortedLabels: Label[]
|
sortedLabels: Label[]
|
||||||
influxQLQueryType: InfluxQLQueryType
|
|
||||||
hoveredColumnIndex: number
|
hoveredColumnIndex: number
|
||||||
hoveredRowIndex: number
|
hoveredRowIndex: number
|
||||||
timeColumnWidth: number
|
timeColumnWidth: number
|
||||||
sort: Sort
|
|
||||||
columnWidths: {[x: string]: number}
|
|
||||||
totalColumnWidths: number
|
|
||||||
isTimeVisible: boolean
|
isTimeVisible: boolean
|
||||||
shouldResize: boolean
|
shouldResize: boolean
|
||||||
invalidDataError: ErrorTypes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ErrorHandling
|
@ErrorHandling
|
||||||
class TableGraph extends PureComponent<Props, State> {
|
class TableGraph extends PureComponent<Props, State> {
|
||||||
private gridContainer: HTMLDivElement
|
private gridContainer: HTMLDivElement
|
||||||
private multiGrid?: MultiGrid
|
private multiGrid?: MultiGrid
|
||||||
private isComponentMounted: boolean = false
|
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
const sortField: string = _.get(
|
|
||||||
this.props,
|
|
||||||
'tableOptions.sortBy.internalName',
|
|
||||||
''
|
|
||||||
)
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
shouldResize: false,
|
shouldResize: false,
|
||||||
data: [[]],
|
|
||||||
transformedData: [[]],
|
|
||||||
sortedTimeVals: [],
|
sortedTimeVals: [],
|
||||||
sortedLabels: [],
|
sortedLabels: [],
|
||||||
influxQLQueryType: InfluxQLQueryType.DataQuery,
|
|
||||||
hoveredColumnIndex: NULL_ARRAY_INDEX,
|
hoveredColumnIndex: NULL_ARRAY_INDEX,
|
||||||
hoveredRowIndex: NULL_ARRAY_INDEX,
|
hoveredRowIndex: NULL_ARRAY_INDEX,
|
||||||
sort: {field: sortField, direction: DEFAULT_SORT_DIRECTION},
|
|
||||||
columnWidths: {},
|
|
||||||
totalColumnWidths: 0,
|
|
||||||
isTimeVisible: true,
|
isTimeVisible: true,
|
||||||
timeColumnWidth: 0,
|
timeColumnWidth: 0,
|
||||||
invalidDataError: null,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {transformedData} = this.state
|
const {
|
||||||
|
data: {transformedData},
|
||||||
|
} = this.props
|
||||||
|
|
||||||
const columnCount = _.get(transformedData, ['0', 'length'], 0)
|
const columnCount = _.get(transformedData, ['0', 'length'], 0)
|
||||||
const rowCount = columnCount === 0 ? 0 : transformedData.length
|
const rowCount = columnCount === 0 ? 0 : transformedData.length
|
||||||
const fixedColumnCount = this.fixFirstColumn && columnCount > 1 ? 1 : 0
|
const fixedColumnCount = this.fixFirstColumn && columnCount > 1 ? 1 : 0
|
||||||
const {scrollToColumn, scrollToRow} = this.scrollToColRow
|
const {scrollToColumn, scrollToRow} = this.scrollToColRow
|
||||||
|
|
||||||
if (this.state.invalidDataError) {
|
|
||||||
return <InvalidData message={this.invalidDataMessage} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={this.tableContainerClassName}
|
className={this.tableContainerClassName}
|
||||||
|
@ -197,7 +160,6 @@ class TableGraph extends PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
this.isComponentMounted = false
|
|
||||||
window.removeEventListener('resize', this.handleResize)
|
window.removeEventListener('resize', this.handleResize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,221 +174,57 @@ class TableGraph extends PureComponent<Props, State> {
|
||||||
|
|
||||||
public async componentDidMount() {
|
public async componentDidMount() {
|
||||||
const {
|
const {
|
||||||
data,
|
data: {sortedTimeVals},
|
||||||
dataType,
|
|
||||||
timeFormat,
|
|
||||||
tableOptions,
|
|
||||||
fieldOptions,
|
fieldOptions,
|
||||||
decimalPlaces,
|
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
this.isComponentMounted = true
|
|
||||||
window.addEventListener('resize', this.handleResize)
|
window.addEventListener('resize', this.handleResize)
|
||||||
|
|
||||||
let sortField: string = _.get(
|
this.handleUpdateFieldOptions(fieldOptions)
|
||||||
this.props,
|
|
||||||
['tableOptions', 'sortBy', 'internalName'],
|
|
||||||
''
|
|
||||||
)
|
|
||||||
const isValidSortField = !!fieldOptions.find(
|
|
||||||
f => f.internalName === sortField
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!isValidSortField) {
|
const isTimeVisible = _.get(this.timeField, 'visible', false)
|
||||||
sortField = _.get(
|
|
||||||
this.defaultTimeField,
|
|
||||||
'internalName',
|
|
||||||
_.get(fieldOptions, '0.internalName', '')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const sort: Sort = {field: sortField, direction: DEFAULT_SORT_DIRECTION}
|
this.setState(
|
||||||
|
{
|
||||||
try {
|
|
||||||
const {
|
|
||||||
data: resultData,
|
|
||||||
sortedLabels,
|
|
||||||
influxQLQueryType,
|
|
||||||
} = await this.getTableGraphData(data, dataType)
|
|
||||||
|
|
||||||
const computedFieldOptions = computeFieldOptions(
|
|
||||||
fieldOptions,
|
|
||||||
sortedLabels,
|
|
||||||
dataType,
|
|
||||||
influxQLQueryType
|
|
||||||
)
|
|
||||||
|
|
||||||
this.handleUpdateFieldOptions(computedFieldOptions)
|
|
||||||
|
|
||||||
const {
|
|
||||||
transformedData,
|
|
||||||
sortedTimeVals,
|
sortedTimeVals,
|
||||||
columnWidths,
|
hoveredColumnIndex: NULL_ARRAY_INDEX,
|
||||||
} = await manager.tableTransform(
|
hoveredRowIndex: NULL_ARRAY_INDEX,
|
||||||
resultData,
|
isTimeVisible,
|
||||||
sort,
|
},
|
||||||
computedFieldOptions,
|
() => {
|
||||||
tableOptions,
|
window.setTimeout(() => {
|
||||||
timeFormat,
|
this.forceUpdate()
|
||||||
decimalPlaces
|
}, 0)
|
||||||
)
|
}
|
||||||
|
)
|
||||||
const isTimeVisible = _.get(this.timeField, 'visible', false)
|
|
||||||
|
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
transformedData,
|
|
||||||
sortedTimeVals,
|
|
||||||
columnWidths: columnWidths.widths,
|
|
||||||
data: resultData,
|
|
||||||
sortedLabels,
|
|
||||||
totalColumnWidths: columnWidths.totalWidths,
|
|
||||||
hoveredColumnIndex: NULL_ARRAY_INDEX,
|
|
||||||
hoveredRowIndex: NULL_ARRAY_INDEX,
|
|
||||||
sort,
|
|
||||||
isTimeVisible,
|
|
||||||
invalidDataError: null,
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
window.setTimeout(() => {
|
|
||||||
this.forceUpdate()
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} catch (e) {
|
|
||||||
this.handleError(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async componentWillReceiveProps(nextProps: Props) {
|
public async componentWillReceiveProps(nextProps: Props) {
|
||||||
const {sort} = this.state
|
const {dataType} = nextProps
|
||||||
|
|
||||||
let result: TimeSeriesToTableGraphReturnType
|
const defaultTimeField = getDefaultTimeField(dataType)
|
||||||
const hasDataChanged = this.hasDataChanged(nextProps.data)
|
const timeField = _.find(nextProps.fieldOptions, f => {
|
||||||
|
return f.internalName === defaultTimeField.internalName
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
const isTimeVisible = _.get(timeField, 'visible', this.state.isTimeVisible)
|
||||||
if (hasDataChanged) {
|
|
||||||
result = await this.getTableGraphData(
|
|
||||||
nextProps.data,
|
|
||||||
nextProps.dataType
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const data = _.get(result, 'data', this.state.data)
|
|
||||||
const influxQLQueryType = _.get(
|
|
||||||
result,
|
|
||||||
'influxQLQueryType',
|
|
||||||
this.state.influxQLQueryType
|
|
||||||
)
|
|
||||||
|
|
||||||
if (_.isEmpty(data[0])) {
|
const updatedProps = _.keys(_.omit(nextProps, 'data')).filter(
|
||||||
return
|
k => !_.isEqual(this.props[k], nextProps[k])
|
||||||
}
|
)
|
||||||
|
|
||||||
const updatedProps = _.keys(_.omit(nextProps, 'data')).filter(
|
const shouldResize =
|
||||||
k => !_.isEqual(this.props[k], nextProps[k])
|
_.includes(updatedProps, 'tableOptions') ||
|
||||||
)
|
_.includes(updatedProps, 'fieldOptions') ||
|
||||||
|
_.includes(updatedProps, 'timeFormat')
|
||||||
|
|
||||||
const {
|
this.setState({
|
||||||
tableOptions,
|
isTimeVisible,
|
||||||
fieldOptions,
|
shouldResize,
|
||||||
timeFormat,
|
})
|
||||||
decimalPlaces,
|
|
||||||
dataType,
|
|
||||||
} = nextProps
|
|
||||||
|
|
||||||
const sortedLabels = _.get(
|
|
||||||
result,
|
|
||||||
'sortedLabels',
|
|
||||||
this.state.sortedLabels
|
|
||||||
)
|
|
||||||
const computedFieldOptions = computeFieldOptions(
|
|
||||||
fieldOptions,
|
|
||||||
sortedLabels,
|
|
||||||
dataType,
|
|
||||||
influxQLQueryType
|
|
||||||
)
|
|
||||||
|
|
||||||
if (hasDataChanged) {
|
|
||||||
this.handleUpdateFieldOptions(computedFieldOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
let sortField = _.get(tableOptions, 'sortBy.internalName', '')
|
|
||||||
|
|
||||||
const isValidSortField = !!fieldOptions.find(
|
|
||||||
f => f.internalName === sortField
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultTimeField = getDefaultTimeField(dataType)
|
|
||||||
|
|
||||||
if (!isValidSortField) {
|
|
||||||
const timeField = fieldOptions.find(
|
|
||||||
f => f.internalName === defaultTimeField.internalName
|
|
||||||
)
|
|
||||||
sortField = _.get(
|
|
||||||
timeField,
|
|
||||||
'internalName',
|
|
||||||
_.get(fieldOptions, '0.internalName', '')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
_.get(this.props, 'tableOptions.sortBy.internalName', '') !== sortField
|
|
||||||
) {
|
|
||||||
sort.direction = DEFAULT_SORT_DIRECTION
|
|
||||||
sort.field = sortField
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
hasDataChanged ||
|
|
||||||
_.includes(updatedProps, 'tableOptions') ||
|
|
||||||
_.includes(updatedProps, 'fieldOptions') ||
|
|
||||||
_.includes(updatedProps, 'timeFormat')
|
|
||||||
) {
|
|
||||||
const {
|
|
||||||
transformedData,
|
|
||||||
sortedTimeVals,
|
|
||||||
columnWidths,
|
|
||||||
} = await manager.tableTransform(
|
|
||||||
data,
|
|
||||||
sort,
|
|
||||||
computedFieldOptions,
|
|
||||||
tableOptions,
|
|
||||||
timeFormat,
|
|
||||||
decimalPlaces
|
|
||||||
)
|
|
||||||
|
|
||||||
let isTimeVisible = this.state.isTimeVisible
|
|
||||||
if (_.includes(updatedProps, 'fieldOptions')) {
|
|
||||||
const timeField = _.find(nextProps.fieldOptions, f => {
|
|
||||||
return f.internalName === defaultTimeField.internalName
|
|
||||||
})
|
|
||||||
isTimeVisible = _.get(timeField, 'visible', false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isComponentMounted) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
data,
|
|
||||||
sortedLabels,
|
|
||||||
influxQLQueryType,
|
|
||||||
transformedData,
|
|
||||||
sortedTimeVals,
|
|
||||||
sort,
|
|
||||||
columnWidths: columnWidths.widths,
|
|
||||||
totalColumnWidths: columnWidths.totalWidths,
|
|
||||||
isTimeVisible,
|
|
||||||
shouldResize: true,
|
|
||||||
invalidDataError: null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.handleError(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate() {
|
public componentDidUpdate(prevProps: Props) {
|
||||||
if (this.state.shouldResize) {
|
if (this.state.shouldResize) {
|
||||||
if (this.multiGrid) {
|
if (this.multiGrid) {
|
||||||
this.multiGrid.recomputeGridSize()
|
this.multiGrid.recomputeGridSize()
|
||||||
|
@ -434,6 +232,12 @@ class TableGraph extends PureComponent<Props, State> {
|
||||||
|
|
||||||
this.setState({shouldResize: false})
|
this.setState({shouldResize: false})
|
||||||
}
|
}
|
||||||
|
if (this.multiGrid) {
|
||||||
|
this.multiGrid.forceUpdate()
|
||||||
|
}
|
||||||
|
if (!_.isEqual(this.props.fieldOptions, prevProps.fieldOptions)) {
|
||||||
|
this.handleUpdateFieldOptions(this.props.fieldOptions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get tableContainerClassName(): string {
|
private get tableContainerClassName(): string {
|
||||||
|
@ -446,38 +250,6 @@ class TableGraph extends PureComponent<Props, State> {
|
||||||
return 'table-graph-container'
|
return 'table-graph-container'
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasDataChanged(data): boolean {
|
|
||||||
const newUUID =
|
|
||||||
_.get(data, '0.response.uuid', null) || _.get(data, 'id', null)
|
|
||||||
const oldUUID =
|
|
||||||
_.get(this.props.data, '0.response.uuid', null) ||
|
|
||||||
_.get(this.props.data, 'id', null)
|
|
||||||
|
|
||||||
return newUUID !== oldUUID || !!this.props.editorLocation
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleError(e: Error): void {
|
|
||||||
let invalidDataError: ErrorTypes
|
|
||||||
switch (e.toString()) {
|
|
||||||
case 'Error: Cannot display meta and data query':
|
|
||||||
invalidDataError = ErrorTypes.MetaQueryCombo
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
invalidDataError = ErrorTypes.GeneralError
|
|
||||||
break
|
|
||||||
}
|
|
||||||
this.setState({invalidDataError})
|
|
||||||
}
|
|
||||||
|
|
||||||
private get invalidDataMessage(): string {
|
|
||||||
switch (this.state.invalidDataError) {
|
|
||||||
case ErrorTypes.MetaQueryCombo:
|
|
||||||
return 'Cannot display data for meta queries mixed with data queries'
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private get fixFirstColumn(): boolean {
|
private get fixFirstColumn(): boolean {
|
||||||
const {tableOptions, fieldOptions} = this.props
|
const {tableOptions, fieldOptions} = this.props
|
||||||
const {fixFirstColumn = DEFAULT_FIX_FIRST_COLUMN} = tableOptions
|
const {fixFirstColumn = DEFAULT_FIX_FIRST_COLUMN} = tableOptions
|
||||||
|
@ -501,7 +273,9 @@ class TableGraph extends PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private get columnCount(): number {
|
private get columnCount(): number {
|
||||||
const {transformedData} = this.state
|
const {
|
||||||
|
data: {transformedData},
|
||||||
|
} = this.props
|
||||||
return _.get(transformedData, ['0', 'length'], 0)
|
return _.get(transformedData, ['0', 'length'], 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -532,8 +306,10 @@ class TableGraph extends PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private get isEmpty(): boolean {
|
private get isEmpty(): boolean {
|
||||||
const {data} = this.state
|
const {
|
||||||
return _.isEmpty(data[0])
|
data: {transformedData},
|
||||||
|
} = this.props
|
||||||
|
return _.isEmpty(transformedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
private get scrollToColRow(): {
|
private get scrollToColRow(): {
|
||||||
|
@ -614,30 +390,7 @@ class TableGraph extends PureComponent<Props, State> {
|
||||||
private handleClickFieldName = (
|
private handleClickFieldName = (
|
||||||
clickedFieldName: string
|
clickedFieldName: string
|
||||||
) => async (): Promise<void> => {
|
) => async (): Promise<void> => {
|
||||||
const {tableOptions, fieldOptions, timeFormat, decimalPlaces} = this.props
|
this.props.onSort(clickedFieldName)
|
||||||
const {data, sort} = this.state
|
|
||||||
|
|
||||||
if (clickedFieldName === sort.field) {
|
|
||||||
sort.direction = sort.direction === ASCENDING ? DESCENDING : ASCENDING
|
|
||||||
} else {
|
|
||||||
sort.field = clickedFieldName
|
|
||||||
sort.direction = DEFAULT_SORT_DIRECTION
|
|
||||||
}
|
|
||||||
|
|
||||||
const {transformedData, sortedTimeVals} = await manager.tableTransform(
|
|
||||||
data,
|
|
||||||
sort,
|
|
||||||
fieldOptions,
|
|
||||||
tableOptions,
|
|
||||||
timeFormat,
|
|
||||||
decimalPlaces
|
|
||||||
)
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
transformedData,
|
|
||||||
sortedTimeVals,
|
|
||||||
sort,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateColumnWidth = (columnSizerWidth: number) => (column: {
|
private calculateColumnWidth = (columnSizerWidth: number) => (column: {
|
||||||
|
@ -645,7 +398,13 @@ class TableGraph extends PureComponent<Props, State> {
|
||||||
}): number => {
|
}): number => {
|
||||||
const {index} = column
|
const {index} = column
|
||||||
|
|
||||||
const {transformedData, columnWidths, totalColumnWidths} = this.state
|
const {
|
||||||
|
data: {
|
||||||
|
transformedData,
|
||||||
|
columnWidths: {widths: columnWidths, totalWidths: totalColumnWidths},
|
||||||
|
},
|
||||||
|
} = this.props
|
||||||
|
|
||||||
const columnLabel = transformedData[0][index]
|
const columnLabel = transformedData[0][index]
|
||||||
|
|
||||||
const original = columnWidths[columnLabel]
|
const original = columnWidths[columnLabel]
|
||||||
|
@ -683,10 +442,13 @@ class TableGraph extends PureComponent<Props, State> {
|
||||||
return _.defaultTo(fieldName, '').toString()
|
return _.defaultTo(fieldName, '').toString()
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
_.isNumber(cellData) &&
|
(_.isNumber(cellData) || parseFloat(cellData)) &&
|
||||||
decimalPlaces.isEnforced &&
|
decimalPlaces.isEnforced &&
|
||||||
decimalPlaces.digits < 100
|
decimalPlaces.digits < 100
|
||||||
) {
|
) {
|
||||||
|
if (_.isString(cellData)) {
|
||||||
|
return parseFloat(cellData).toFixed(decimalPlaces.digits)
|
||||||
|
}
|
||||||
return cellData.toFixed(decimalPlaces.digits)
|
return cellData.toFixed(decimalPlaces.digits)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -724,13 +486,11 @@ class TableGraph extends PureComponent<Props, State> {
|
||||||
parent,
|
parent,
|
||||||
style,
|
style,
|
||||||
}: CellRendererProps) => {
|
}: CellRendererProps) => {
|
||||||
|
const {hoveredColumnIndex, hoveredRowIndex, isTimeVisible} = this.state
|
||||||
const {
|
const {
|
||||||
hoveredColumnIndex,
|
data: {transformedData},
|
||||||
hoveredRowIndex,
|
|
||||||
transformedData,
|
|
||||||
sort,
|
sort,
|
||||||
isTimeVisible,
|
} = this.props
|
||||||
} = this.state
|
|
||||||
|
|
||||||
const {fieldOptions = [this.defaultTimeField], colors} = this.props
|
const {fieldOptions = [this.defaultTimeField], colors} = this.props
|
||||||
const cellData = transformedData[rowIndex][columnIndex]
|
const cellData = transformedData[rowIndex][columnIndex]
|
||||||
|
@ -818,32 +578,6 @@ class TableGraph extends PureComponent<Props, State> {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getTableGraphData(
|
|
||||||
data: TimeSeriesServerResponse[] | FluxTable,
|
|
||||||
dataType: DataType
|
|
||||||
): Promise<TimeSeriesToTableGraphReturnType> {
|
|
||||||
if (dataType === DataType.influxQL) {
|
|
||||||
const result = await timeSeriesToTableGraph(
|
|
||||||
data as TimeSeriesServerResponse[]
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
|
||||||
} else {
|
|
||||||
const resultData = (data as FluxTable).data
|
|
||||||
const sortedLabels = _.get(resultData, '0', []).map(label => ({
|
|
||||||
label,
|
|
||||||
seriesIndex: 0,
|
|
||||||
responseIndex: 0,
|
|
||||||
}))
|
|
||||||
|
|
||||||
return {data: resultData, sortedLabels} as {
|
|
||||||
data: TimeSeriesValue[][]
|
|
||||||
sortedLabels: Label[]
|
|
||||||
influxQLQueryType: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mstp = ({dashboardUI}) => ({
|
const mstp = ({dashboardUI}) => ({
|
||||||
|
|
|
@ -0,0 +1,216 @@
|
||||||
|
// Libraries
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import {manager} from 'src/worker/JobManager'
|
||||||
|
import {
|
||||||
|
ErrorTypes,
|
||||||
|
getInvalidDataMessage,
|
||||||
|
computeFieldOptions,
|
||||||
|
getDefaultTimeField,
|
||||||
|
} from 'src/dashboards/utils/tableGraph'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import InvalidData from 'src/shared/components/InvalidData'
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import {
|
||||||
|
DEFAULT_SORT_DIRECTION,
|
||||||
|
ASCENDING,
|
||||||
|
DESCENDING,
|
||||||
|
} from 'src/shared/constants/tableGraph'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import {
|
||||||
|
TimeSeriesValue,
|
||||||
|
TimeSeriesToTableGraphReturnType,
|
||||||
|
} from 'src/types/series'
|
||||||
|
import {
|
||||||
|
TableOptions,
|
||||||
|
FieldOption,
|
||||||
|
DecimalPlaces,
|
||||||
|
Sort,
|
||||||
|
} from 'src/types/dashboards'
|
||||||
|
import {DataType} from 'src/shared/constants'
|
||||||
|
import {ColumnWidths} from 'src/dashboards/utils/tableGraph'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: TimeSeriesToTableGraphReturnType
|
||||||
|
dataType: DataType
|
||||||
|
tableOptions: TableOptions
|
||||||
|
timeFormat: string
|
||||||
|
decimalPlaces: DecimalPlaces
|
||||||
|
fieldOptions: FieldOption[]
|
||||||
|
uuid: string
|
||||||
|
children: (
|
||||||
|
data: FormattedTableData,
|
||||||
|
sort: Sort,
|
||||||
|
computedFieldOptions: FieldOption[],
|
||||||
|
resortData: (fieldName: string) => void
|
||||||
|
) => JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormattedTableData {
|
||||||
|
transformedData: TimeSeriesValue[][]
|
||||||
|
sortedTimeVals: TimeSeriesValue[]
|
||||||
|
columnWidths: ColumnWidths
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
formattedData: FormattedTableData
|
||||||
|
sort: Sort
|
||||||
|
computedFieldOptions: FieldOption[]
|
||||||
|
invalidDataError: ErrorTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
class TableGraphFormat extends PureComponent<Props, State> {
|
||||||
|
private isComponentMounted: boolean
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
const sortField: string = _.get(
|
||||||
|
this.props,
|
||||||
|
'tableOptions.sortBy.internalName',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
formattedData: null,
|
||||||
|
sort: {field: sortField, direction: DEFAULT_SORT_DIRECTION},
|
||||||
|
computedFieldOptions: props.fieldOptions,
|
||||||
|
invalidDataError: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
if (this.state.invalidDataError) {
|
||||||
|
return (
|
||||||
|
<InvalidData
|
||||||
|
message={getInvalidDataMessage(this.state.invalidDataError)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.state.formattedData) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children(
|
||||||
|
this.state.formattedData,
|
||||||
|
this.state.sort,
|
||||||
|
this.state.computedFieldOptions,
|
||||||
|
this.formatData
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
this.isComponentMounted = true
|
||||||
|
|
||||||
|
this.formatData()
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentWillUnmount() {
|
||||||
|
this.isComponentMounted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidUpdate(prevProps: Props) {
|
||||||
|
const updatedProps = _.keys(_.omit(prevProps, 'data')).filter(
|
||||||
|
k => !_.isEqual(this.props[k], prevProps[k])
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.props.uuid !== prevProps.uuid ||
|
||||||
|
_.includes(updatedProps, 'tableOptions') ||
|
||||||
|
_.includes(updatedProps, 'fieldOptions') ||
|
||||||
|
_.includes(updatedProps, 'timeFormat')
|
||||||
|
) {
|
||||||
|
this.formatData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatData = async (sortField?: string) => {
|
||||||
|
const {
|
||||||
|
fieldOptions,
|
||||||
|
data: {sortedLabels, influxQLQueryType},
|
||||||
|
dataType,
|
||||||
|
tableOptions,
|
||||||
|
timeFormat,
|
||||||
|
decimalPlaces,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const {sort} = this.state
|
||||||
|
|
||||||
|
if (sortField === sort.field) {
|
||||||
|
sort.direction = sort.direction === ASCENDING ? DESCENDING : ASCENDING
|
||||||
|
} else {
|
||||||
|
sort.field = sortField || this.sortField
|
||||||
|
sort.direction = DEFAULT_SORT_DIRECTION
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestUUID = this.props.uuid
|
||||||
|
|
||||||
|
const computedFieldOptions = computeFieldOptions(
|
||||||
|
fieldOptions,
|
||||||
|
sortedLabels,
|
||||||
|
dataType,
|
||||||
|
influxQLQueryType
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formattedData = await manager.tableTransform(
|
||||||
|
this.props.data.data,
|
||||||
|
sort,
|
||||||
|
computedFieldOptions,
|
||||||
|
tableOptions,
|
||||||
|
timeFormat,
|
||||||
|
decimalPlaces
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!this.isComponentMounted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.uuid === latestUUID) {
|
||||||
|
this.setState({
|
||||||
|
formattedData,
|
||||||
|
sort,
|
||||||
|
computedFieldOptions,
|
||||||
|
invalidDataError: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (!this.isComponentMounted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({invalidDataError: ErrorTypes.GeneralError})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get sortField(): string {
|
||||||
|
const {fieldOptions, dataType} = this.props
|
||||||
|
|
||||||
|
let sortField: string = _.get(
|
||||||
|
this.props,
|
||||||
|
['tableOptions', 'sortBy', 'internalName'],
|
||||||
|
''
|
||||||
|
)
|
||||||
|
const isValidSortField = !!fieldOptions.find(
|
||||||
|
f => f.internalName === sortField
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!isValidSortField) {
|
||||||
|
sortField = _.get(
|
||||||
|
getDefaultTimeField(dataType),
|
||||||
|
'internalName',
|
||||||
|
_.get(fieldOptions, '0.internalName', '')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableGraphFormat
|
|
@ -0,0 +1,136 @@
|
||||||
|
// Libraries
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import uuid from 'uuid'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import InvalidData from 'src/shared/components/InvalidData'
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import {timeSeriesToTableGraph} from 'src/utils/timeSeriesTransformers'
|
||||||
|
import {
|
||||||
|
ErrorTypes,
|
||||||
|
getInvalidDataMessage,
|
||||||
|
} from 'src/dashboards/utils/tableGraph'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import {
|
||||||
|
Label,
|
||||||
|
TimeSeriesValue,
|
||||||
|
TimeSeriesServerResponse,
|
||||||
|
TimeSeriesToTableGraphReturnType,
|
||||||
|
} from 'src/types/series'
|
||||||
|
import {FluxTable} from 'src/types'
|
||||||
|
import {DataType} from 'src/shared/constants'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: TimeSeriesServerResponse[] | FluxTable
|
||||||
|
uuid: string
|
||||||
|
dataType: DataType
|
||||||
|
children: (
|
||||||
|
data: TimeSeriesToTableGraphReturnType,
|
||||||
|
uuid: string
|
||||||
|
) => JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
transformedData: TimeSeriesToTableGraphReturnType
|
||||||
|
invalidDataError: ErrorTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
class TableGraphTransform extends PureComponent<Props, State> {
|
||||||
|
private isComponentMounted: boolean
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {transformedData: null, invalidDataError: null}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
if (this.state.invalidDataError) {
|
||||||
|
return (
|
||||||
|
<InvalidData
|
||||||
|
message={getInvalidDataMessage(this.state.invalidDataError)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.state.transformedData) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children(this.state.transformedData, uuid.v4())
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
this.isComponentMounted = true
|
||||||
|
this.transformData()
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentWillUnmount() {
|
||||||
|
this.isComponentMounted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidUpdate(prevProps: Props) {
|
||||||
|
if (prevProps.uuid !== this.props.uuid) {
|
||||||
|
this.transformData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async transformData() {
|
||||||
|
const {dataType, data} = this.props
|
||||||
|
|
||||||
|
if (dataType === DataType.influxQL) {
|
||||||
|
try {
|
||||||
|
const influxQLData = await timeSeriesToTableGraph(
|
||||||
|
data as TimeSeriesServerResponse[]
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!this.isComponentMounted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({transformedData: influxQLData, invalidDataError: null})
|
||||||
|
} catch (err) {
|
||||||
|
let invalidDataError: ErrorTypes
|
||||||
|
switch (err.toString()) {
|
||||||
|
case 'Error: Cannot display meta and data query':
|
||||||
|
invalidDataError = ErrorTypes.MetaQueryCombo
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
invalidDataError = ErrorTypes.GeneralError
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isComponentMounted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.setState({invalidDataError})
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultData = (data as FluxTable).data
|
||||||
|
const sortedLabels = _.get(resultData, '0', []).map(label => ({
|
||||||
|
label,
|
||||||
|
seriesIndex: 0,
|
||||||
|
responseIndex: 0,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const fluxData = {data: resultData, sortedLabels} as {
|
||||||
|
data: TimeSeriesValue[][]
|
||||||
|
sortedLabels: Label[]
|
||||||
|
influxQLQueryType: null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isComponentMounted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({transformedData: fluxData})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableGraphTransform
|
|
@ -46,6 +46,7 @@ interface RenderProps {
|
||||||
timeSeriesFlux: FluxTable[]
|
timeSeriesFlux: FluxTable[]
|
||||||
rawFluxData: string
|
rawFluxData: string
|
||||||
loading: RemoteDataState
|
loading: RemoteDataState
|
||||||
|
uuid: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -73,6 +74,7 @@ interface State {
|
||||||
rawFluxData: string
|
rawFluxData: string
|
||||||
timeSeriesInfluxQL: TimeSeriesServerResponse[]
|
timeSeriesInfluxQL: TimeSeriesServerResponse[]
|
||||||
timeSeriesFlux: FluxTable[]
|
timeSeriesFlux: FluxTable[]
|
||||||
|
latestUUID: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const GraphLoadingDots = () => (
|
const GraphLoadingDots = () => (
|
||||||
|
@ -110,7 +112,6 @@ class TimeSeries extends Component<Props, State> {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private latestUUID: string = uuid.v1()
|
|
||||||
private isComponentMounted: boolean = false
|
private isComponentMounted: boolean = false
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
|
@ -123,6 +124,7 @@ class TimeSeries extends Component<Props, State> {
|
||||||
isFirstFetch: true,
|
isFirstFetch: true,
|
||||||
timeSeriesFlux: [],
|
timeSeriesFlux: [],
|
||||||
rawFluxData: '',
|
rawFluxData: '',
|
||||||
|
latestUUID: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,8 +187,13 @@ class TimeSeries extends Component<Props, State> {
|
||||||
rawFluxData,
|
rawFluxData,
|
||||||
loading,
|
loading,
|
||||||
isFirstFetch,
|
isFirstFetch,
|
||||||
|
latestUUID,
|
||||||
} = this.state
|
} = this.state
|
||||||
|
|
||||||
|
if (isFirstFetch && loading === RemoteDataState.Loading) {
|
||||||
|
return <div className="graph-empty">{this.spinner}</div>
|
||||||
|
}
|
||||||
|
|
||||||
const hasValues =
|
const hasValues =
|
||||||
timeSeriesFlux.length ||
|
timeSeriesFlux.length ||
|
||||||
_.some(timeSeriesInfluxQL, s => {
|
_.some(timeSeriesInfluxQL, s => {
|
||||||
|
@ -195,10 +202,6 @@ class TimeSeries extends Component<Props, State> {
|
||||||
return v
|
return v
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isFirstFetch && loading === RemoteDataState.Loading) {
|
|
||||||
return <div className="graph-empty">{this.spinner}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasValues) {
|
if (!hasValues) {
|
||||||
if (cellNoteVisibility === NoteVisibility.ShowWhenNoData) {
|
if (cellNoteVisibility === NoteVisibility.ShowWhenNoData) {
|
||||||
return <MarkdownCell text={cellNote} />
|
return <MarkdownCell text={cellNote} />
|
||||||
|
@ -227,6 +230,7 @@ class TimeSeries extends Component<Props, State> {
|
||||||
timeSeriesFlux,
|
timeSeriesFlux,
|
||||||
rawFluxData,
|
rawFluxData,
|
||||||
loading,
|
loading,
|
||||||
|
uuid: latestUUID,
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -277,19 +281,20 @@ class TimeSeries extends Component<Props, State> {
|
||||||
let timeSeriesFlux: FluxTable[] = []
|
let timeSeriesFlux: FluxTable[] = []
|
||||||
let rawFluxData = ''
|
let rawFluxData = ''
|
||||||
let responseUUID: string
|
let responseUUID: string
|
||||||
|
let loading: RemoteDataState = null
|
||||||
|
|
||||||
this.setState({loading: RemoteDataState.Loading})
|
this.setState({loading: RemoteDataState.Loading})
|
||||||
this.latestUUID = uuid.v1()
|
const latestUUID = uuid.v1()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.isFluxQuery) {
|
if (this.isFluxQuery) {
|
||||||
const results = await this.executeFluxQuery()
|
const results = await this.executeFluxQuery(latestUUID)
|
||||||
|
|
||||||
timeSeriesFlux = results.tables
|
timeSeriesFlux = results.tables
|
||||||
rawFluxData = results.csv
|
rawFluxData = results.csv
|
||||||
responseUUID = results.uuid
|
responseUUID = results.uuid
|
||||||
} else {
|
} else {
|
||||||
timeSeriesInfluxQL = await this.executeInfluxQLQueries()
|
timeSeriesInfluxQL = await this.executeInfluxQLQueries(latestUUID)
|
||||||
responseUUID = _.get(timeSeriesInfluxQL, '0.response.uuid')
|
responseUUID = _.get(timeSeriesInfluxQL, '0.response.uuid')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,13 +302,13 @@ class TimeSeries extends Component<Props, State> {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseUUID !== this.latestUUID) {
|
if (responseUUID !== latestUUID) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({loading: RemoteDataState.Done})
|
loading = RemoteDataState.Done
|
||||||
} catch {
|
} catch {
|
||||||
this.setState({loading: RemoteDataState.Error})
|
loading = RemoteDataState.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -311,6 +316,8 @@ class TimeSeries extends Component<Props, State> {
|
||||||
timeSeriesFlux,
|
timeSeriesFlux,
|
||||||
rawFluxData,
|
rawFluxData,
|
||||||
isFirstFetch: false,
|
isFirstFetch: false,
|
||||||
|
loading,
|
||||||
|
latestUUID,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (grabDataForDownload) {
|
if (grabDataForDownload) {
|
||||||
|
@ -322,7 +329,9 @@ class TimeSeries extends Component<Props, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private executeFluxQuery = async (): Promise<GetTimeSeriesResult> => {
|
private executeFluxQuery = async (
|
||||||
|
latestUUID: string
|
||||||
|
): Promise<GetTimeSeriesResult> => {
|
||||||
const {queries, onNotify, source, timeRange} = this.props
|
const {queries, onNotify, source, timeRange} = this.props
|
||||||
|
|
||||||
const script: string = _.get(queries, '0.text', '')
|
const script: string = _.get(queries, '0.text', '')
|
||||||
|
@ -330,7 +339,7 @@ class TimeSeries extends Component<Props, State> {
|
||||||
source,
|
source,
|
||||||
script,
|
script,
|
||||||
timeRange,
|
timeRange,
|
||||||
this.latestUUID
|
latestUUID
|
||||||
)
|
)
|
||||||
|
|
||||||
if (results.didTruncate && onNotify) {
|
if (results.didTruncate && onNotify) {
|
||||||
|
@ -340,19 +349,20 @@ class TimeSeries extends Component<Props, State> {
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
private executeInfluxQLQueries = async (): Promise<
|
private executeInfluxQLQueries = async (
|
||||||
TimeSeriesServerResponse[]
|
latestUUID: string
|
||||||
> => {
|
): Promise<TimeSeriesServerResponse[]> => {
|
||||||
const {queries} = this.props
|
const {queries} = this.props
|
||||||
const timeSeriesInfluxQL = await Promise.all(
|
const timeSeriesInfluxQL = await Promise.all(
|
||||||
queries.map(this.executeInfluxQLQuery)
|
queries.map(query => this.executeInfluxQLQuery(query, latestUUID))
|
||||||
)
|
)
|
||||||
|
|
||||||
return timeSeriesInfluxQL
|
return timeSeriesInfluxQL
|
||||||
}
|
}
|
||||||
|
|
||||||
private executeInfluxQLQuery = async (
|
private executeInfluxQLQuery = async (
|
||||||
query: Query
|
query: Query,
|
||||||
|
latestUUID: string
|
||||||
): Promise<TimeSeriesServerResponse> => {
|
): Promise<TimeSeriesServerResponse> => {
|
||||||
const {source, templates, editQueryStatus} = this.props
|
const {source, templates, editQueryStatus} = this.props
|
||||||
const TEMP_RES = 300 // FIXME
|
const TEMP_RES = 300 // FIXME
|
||||||
|
@ -365,7 +375,7 @@ class TimeSeries extends Component<Props, State> {
|
||||||
query,
|
query,
|
||||||
templates,
|
templates,
|
||||||
TEMP_RES,
|
TEMP_RES,
|
||||||
this.latestUUID
|
latestUUID
|
||||||
)
|
)
|
||||||
|
|
||||||
const warningMessage = extractQueryWarningMessage(response)
|
const warningMessage = extractQueryWarningMessage(response)
|
||||||
|
|
Loading…
Reference in New Issue