Merge pull request #4550 from influxdata/flux/table-graph-update

Table graph update time
pull/4563/head
Iris Scholten 2018-10-05 17:28:51 -07:00 committed by GitHub
commit 5e9e859a3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 550 additions and 384 deletions

View File

@ -20,7 +20,7 @@ const calculateSize = (message: string): number => {
return message.length * 7
}
interface ColumnWidths {
export interface ColumnWidths {
totalWidths: number
widths: {[x: string]: number}
}
@ -37,6 +37,20 @@ interface TransformTableDataReturnType {
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 => {
// Force usage of longest format names for ideal measurement
timeFormat = _.replace(timeFormat, 'MMMM', 'September')

View File

@ -1,5 +1,6 @@
// Libraries
import React, {PureComponent} from 'react'
import uuid from 'uuid'
import memoizeOne from 'memoize-one'
// Components
@ -7,6 +8,8 @@ import TableSidebar from 'src/flux/components/TableSidebar'
import {FluxTable} from 'src/types'
import NoResults from 'src/flux/components/NoResults'
import TableGraph from 'src/shared/components/TableGraph'
import TableGraphTransform from 'src/shared/components/TableGraphTransform'
import TableGraphFormat from 'src/shared/components/TableGraphFormat'
// Utils
import {getDeep} from 'src/utils/wrappers'
@ -19,6 +22,7 @@ import {QueryUpdateState} from 'src/types'
interface Props {
data: FluxTable[]
uuid: string
dataType: DataType
tableOptions: TableOptions
timeFormat: string
@ -91,18 +95,40 @@ class TimeMachineTables extends PureComponent<Props, State> {
/>
)}
{this.shouldShowTable && (
<TableGraph
<TableGraphTransform
data={this.selectedResult}
dataType={dataType}
colors={colors}
tableOptions={tableOptions}
fieldOptions={this.fieldOptions}
timeFormat={timeFormat}
decimalPlaces={decimalPlaces}
editorLocation={editorLocation}
handleSetHoverTime={handleSetHoverTime}
onUpdateFieldOptions={onUpdateFieldOptions}
/>
uuid={uuid.v4()}
>
{(transformedData, nextUUID) => (
<TableGraphFormat
data={transformedData}
uuid={nextUUID}
dataType={dataType}
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 />}
</div>

View File

@ -87,9 +87,10 @@ class YieldFuncNode extends PureComponent<Props, State> {
<div className="yield-node">
<div className="func-node--connector" />
<TimeSeries source={source} queries={queries} timeRange={timeRange}>
{({timeSeriesFlux}) => (
{({timeSeriesFlux, uuid}) => (
<YieldNodeVis
data={timeSeriesFlux}
uuid={uuid}
yieldName={yieldName}
axes={axes}
tableOptions={tableOptions}

View File

@ -20,6 +20,7 @@ import {ColorNumber, ColorString} from 'src/types/colors'
interface Props {
data: FluxTable[]
uuid: string
yieldName: string
axes: Axes | null
tableOptions: TableOptions
@ -86,6 +87,7 @@ class YieldNodeVis extends PureComponent<Props, State> {
const {visType} = this.state
const {
data,
uuid,
tableOptions,
timeFormat,
decimalPlaces,
@ -113,6 +115,7 @@ class YieldNodeVis extends PureComponent<Props, State> {
return (
<TimeMachineTables
data={data}
uuid={uuid}
dataType={DataType.flux}
tableOptions={tableOptions}
timeFormat={timeFormat}

View File

@ -12,6 +12,8 @@ import MarkdownCell from 'src/shared/components/MarkdownCell'
import TimeSeries from 'src/shared/components/time_series/TimeSeries'
import TimeMachineTables from 'src/flux/components/TimeMachineTables'
import RawFluxDataTable from 'src/shared/components/TimeMachine/RawFluxDataTable'
import TableGraphTransform from 'src/shared/components/TableGraphTransform'
import TableGraphFormat from 'src/shared/components/TableGraphFormat'
// Constants
import {emptyGraphCopy} from 'src/shared/copy/cell'
@ -159,7 +161,7 @@ class RefreshingGraph extends PureComponent<Props> {
cellNote={cellNote}
cellNoteVisibility={cellNoteVisibility}
>
{({timeSeriesInfluxQL, timeSeriesFlux, rawFluxData, loading}) => {
{({timeSeriesInfluxQL, timeSeriesFlux, rawFluxData, loading, uuid}) => {
if (showRawFluxData) {
return <RawFluxDataTable csv={rawFluxData} />
}
@ -168,7 +170,7 @@ class RefreshingGraph extends PureComponent<Props> {
case CellType.SingleStat:
return this.singleStat(timeSeriesInfluxQL, timeSeriesFlux)
case CellType.Table:
return this.table(timeSeriesInfluxQL, timeSeriesFlux)
return this.table(timeSeriesInfluxQL, timeSeriesFlux, uuid)
case CellType.Gauge:
return this.gauge(timeSeriesInfluxQL, timeSeriesFlux)
default:
@ -228,7 +230,8 @@ class RefreshingGraph extends PureComponent<Props> {
private table = (
influxQLData: TimeSeriesServerResponse[],
fluxData: FluxTable[]
fluxData: FluxTable[],
uuid: string
): JSX.Element => {
const {
colors,
@ -247,6 +250,7 @@ class RefreshingGraph extends PureComponent<Props> {
return (
<TimeMachineTables
data={data as FluxTable[]}
uuid={uuid}
dataType={dataType}
colors={colors}
key={manualRefresh}
@ -262,19 +266,41 @@ class RefreshingGraph extends PureComponent<Props> {
}
return (
<TableGraph
data={data}
<TableGraphTransform
data={data as TimeSeriesServerResponse[]}
uuid={uuid}
dataType={dataType}
colors={colors}
key={manualRefresh}
tableOptions={tableOptions}
fieldOptions={fieldOptions}
timeFormat={timeFormat}
decimalPlaces={decimalPlaces}
editorLocation={editorLocation}
handleSetHoverTime={handleSetHoverTime}
onUpdateFieldOptions={onUpdateFieldOptions}
/>
>
{(transformedData, nextUUID) => (
<TableGraphFormat
data={transformedData}
uuid={nextUUID}
dataType={dataType}
tableOptions={tableOptions}
fieldOptions={fieldOptions}
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>
)
}

View File

@ -8,38 +8,25 @@ import {ColumnSizer, SizedColumnProps, AutoSizer} from 'react-virtualized'
// Components
import {MultiGrid, PropsMultiGrid} from 'src/shared/components/MultiGrid'
import InvalidData from 'src/shared/components/InvalidData'
// Utils
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 {manager} from 'src/worker/JobManager'
// Constants
import {getDefaultTimeField} from 'src/dashboards/utils/tableGraph'
import {
ASCENDING,
DESCENDING,
NULL_HOVER_TIME,
NULL_ARRAY_INDEX,
DEFAULT_FIX_FIRST_COLUMN,
DEFAULT_VERTICAL_TIME_AXIS,
DEFAULT_SORT_DIRECTION,
} from 'src/shared/constants/tableGraph'
import {generateThresholdsListHexs} from 'src/shared/constants/colorOperations'
import {DataType} from 'src/shared/constants'
// Types
import {
TimeSeriesServerResponse,
TimeSeriesValue,
TimeSeriesToTableGraphReturnType,
InfluxQLQueryType,
} from 'src/types/series'
import {TimeSeriesValue} from 'src/types/series'
import {ColorString} from 'src/types/colors'
import {
TableOptions,
@ -47,7 +34,9 @@ import {
DecimalPlaces,
Sort,
} 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 ROW_HEIGHT = 30
@ -66,13 +55,10 @@ interface CellRendererProps {
style: React.CSSProperties
}
enum ErrorTypes {
MetaQueryCombo = 'MetaQueryCombo',
GeneralError = 'Error',
}
interface Props {
data: TimeSeriesServerResponse[] | FluxTable
data: FormattedTableData
onSort: (fieldName: string) => void
sort: Sort
dataType: DataType
tableOptions: TableOptions
timeFormat: string
@ -86,67 +72,44 @@ interface Props {
}
interface State {
data: TimeSeriesValue[][]
transformedData: TimeSeriesValue[][]
sortedTimeVals: TimeSeriesValue[]
sortedLabels: Label[]
influxQLQueryType: InfluxQLQueryType
hoveredColumnIndex: number
hoveredRowIndex: number
timeColumnWidth: number
sort: Sort
columnWidths: {[x: string]: number}
totalColumnWidths: number
isTimeVisible: boolean
shouldResize: boolean
invalidDataError: ErrorTypes
}
@ErrorHandling
class TableGraph extends PureComponent<Props, State> {
private gridContainer: HTMLDivElement
private multiGrid?: MultiGrid
private isComponentMounted: boolean = false
constructor(props: Props) {
super(props)
const sortField: string = _.get(
this.props,
'tableOptions.sortBy.internalName',
''
)
this.state = {
shouldResize: false,
data: [[]],
transformedData: [[]],
sortedTimeVals: [],
sortedLabels: [],
influxQLQueryType: InfluxQLQueryType.DataQuery,
hoveredColumnIndex: NULL_ARRAY_INDEX,
hoveredRowIndex: NULL_ARRAY_INDEX,
sort: {field: sortField, direction: DEFAULT_SORT_DIRECTION},
columnWidths: {},
totalColumnWidths: 0,
isTimeVisible: true,
timeColumnWidth: 0,
invalidDataError: null,
}
}
public render() {
const {transformedData} = this.state
const {
data: {transformedData},
} = this.props
const columnCount = _.get(transformedData, ['0', 'length'], 0)
const rowCount = columnCount === 0 ? 0 : transformedData.length
const fixedColumnCount = this.fixFirstColumn && columnCount > 1 ? 1 : 0
const {scrollToColumn, scrollToRow} = this.scrollToColRow
if (this.state.invalidDataError) {
return <InvalidData message={this.invalidDataMessage} />
}
return (
<div
className={this.tableContainerClassName}
@ -197,7 +160,6 @@ class TableGraph extends PureComponent<Props, State> {
}
public componentWillUnmount() {
this.isComponentMounted = false
window.removeEventListener('resize', this.handleResize)
}
@ -212,221 +174,57 @@ class TableGraph extends PureComponent<Props, State> {
public async componentDidMount() {
const {
data,
dataType,
timeFormat,
tableOptions,
data: {sortedTimeVals},
fieldOptions,
decimalPlaces,
} = this.props
this.isComponentMounted = true
window.addEventListener('resize', this.handleResize)
let sortField: string = _.get(
this.props,
['tableOptions', 'sortBy', 'internalName'],
''
)
const isValidSortField = !!fieldOptions.find(
f => f.internalName === sortField
)
this.handleUpdateFieldOptions(fieldOptions)
if (!isValidSortField) {
sortField = _.get(
this.defaultTimeField,
'internalName',
_.get(fieldOptions, '0.internalName', '')
)
}
const isTimeVisible = _.get(this.timeField, 'visible', false)
const sort: Sort = {field: sortField, direction: DEFAULT_SORT_DIRECTION}
try {
const {
data: resultData,
sortedLabels,
influxQLQueryType,
} = await this.getTableGraphData(data, dataType)
const computedFieldOptions = computeFieldOptions(
fieldOptions,
sortedLabels,
dataType,
influxQLQueryType
)
this.handleUpdateFieldOptions(computedFieldOptions)
const {
transformedData,
this.setState(
{
sortedTimeVals,
columnWidths,
} = await manager.tableTransform(
resultData,
sort,
computedFieldOptions,
tableOptions,
timeFormat,
decimalPlaces
)
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)
}
hoveredColumnIndex: NULL_ARRAY_INDEX,
hoveredRowIndex: NULL_ARRAY_INDEX,
isTimeVisible,
},
() => {
window.setTimeout(() => {
this.forceUpdate()
}, 0)
}
)
}
public async componentWillReceiveProps(nextProps: Props) {
const {sort} = this.state
const {dataType} = nextProps
let result: TimeSeriesToTableGraphReturnType
const hasDataChanged = this.hasDataChanged(nextProps.data)
const defaultTimeField = getDefaultTimeField(dataType)
const timeField = _.find(nextProps.fieldOptions, f => {
return f.internalName === defaultTimeField.internalName
})
try {
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
)
const isTimeVisible = _.get(timeField, 'visible', this.state.isTimeVisible)
if (_.isEmpty(data[0])) {
return
}
const updatedProps = _.keys(_.omit(nextProps, 'data')).filter(
k => !_.isEqual(this.props[k], nextProps[k])
)
const updatedProps = _.keys(_.omit(nextProps, 'data')).filter(
k => !_.isEqual(this.props[k], nextProps[k])
)
const shouldResize =
_.includes(updatedProps, 'tableOptions') ||
_.includes(updatedProps, 'fieldOptions') ||
_.includes(updatedProps, 'timeFormat')
const {
tableOptions,
fieldOptions,
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)
}
this.setState({
isTimeVisible,
shouldResize,
})
}
public componentDidUpdate() {
public componentDidUpdate(prevProps: Props) {
if (this.state.shouldResize) {
if (this.multiGrid) {
this.multiGrid.recomputeGridSize()
@ -434,6 +232,12 @@ class TableGraph extends PureComponent<Props, State> {
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 {
@ -446,38 +250,6 @@ class TableGraph extends PureComponent<Props, State> {
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 {
const {tableOptions, fieldOptions} = this.props
const {fixFirstColumn = DEFAULT_FIX_FIRST_COLUMN} = tableOptions
@ -501,7 +273,9 @@ class TableGraph extends PureComponent<Props, State> {
}
private get columnCount(): number {
const {transformedData} = this.state
const {
data: {transformedData},
} = this.props
return _.get(transformedData, ['0', 'length'], 0)
}
@ -532,8 +306,10 @@ class TableGraph extends PureComponent<Props, State> {
}
private get isEmpty(): boolean {
const {data} = this.state
return _.isEmpty(data[0])
const {
data: {transformedData},
} = this.props
return _.isEmpty(transformedData)
}
private get scrollToColRow(): {
@ -614,30 +390,7 @@ class TableGraph extends PureComponent<Props, State> {
private handleClickFieldName = (
clickedFieldName: string
) => async (): Promise<void> => {
const {tableOptions, fieldOptions, timeFormat, decimalPlaces} = this.props
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,
})
this.props.onSort(clickedFieldName)
}
private calculateColumnWidth = (columnSizerWidth: number) => (column: {
@ -645,7 +398,13 @@ class TableGraph extends PureComponent<Props, State> {
}): number => {
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 original = columnWidths[columnLabel]
@ -683,10 +442,13 @@ class TableGraph extends PureComponent<Props, State> {
return _.defaultTo(fieldName, '').toString()
}
if (
_.isNumber(cellData) &&
(_.isNumber(cellData) || parseFloat(cellData)) &&
decimalPlaces.isEnforced &&
decimalPlaces.digits < 100
) {
if (_.isString(cellData)) {
return parseFloat(cellData).toFixed(decimalPlaces.digits)
}
return cellData.toFixed(decimalPlaces.digits)
}
@ -724,13 +486,11 @@ class TableGraph extends PureComponent<Props, State> {
parent,
style,
}: CellRendererProps) => {
const {hoveredColumnIndex, hoveredRowIndex, isTimeVisible} = this.state
const {
hoveredColumnIndex,
hoveredRowIndex,
transformedData,
data: {transformedData},
sort,
isTimeVisible,
} = this.state
} = this.props
const {fieldOptions = [this.defaultTimeField], colors} = this.props
const cellData = transformedData[rowIndex][columnIndex]
@ -818,32 +578,6 @@ class TableGraph extends PureComponent<Props, State> {
</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}) => ({

View File

@ -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

View File

@ -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

View File

@ -46,6 +46,7 @@ interface RenderProps {
timeSeriesFlux: FluxTable[]
rawFluxData: string
loading: RemoteDataState
uuid: string
}
interface Props {
@ -73,6 +74,7 @@ interface State {
rawFluxData: string
timeSeriesInfluxQL: TimeSeriesServerResponse[]
timeSeriesFlux: FluxTable[]
latestUUID: string
}
const GraphLoadingDots = () => (
@ -110,7 +112,6 @@ class TimeSeries extends Component<Props, State> {
return null
}
private latestUUID: string = uuid.v1()
private isComponentMounted: boolean = false
constructor(props: Props) {
@ -123,6 +124,7 @@ class TimeSeries extends Component<Props, State> {
isFirstFetch: true,
timeSeriesFlux: [],
rawFluxData: '',
latestUUID: null,
}
}
@ -185,8 +187,13 @@ class TimeSeries extends Component<Props, State> {
rawFluxData,
loading,
isFirstFetch,
latestUUID,
} = this.state
if (isFirstFetch && loading === RemoteDataState.Loading) {
return <div className="graph-empty">{this.spinner}</div>
}
const hasValues =
timeSeriesFlux.length ||
_.some(timeSeriesInfluxQL, s => {
@ -195,10 +202,6 @@ class TimeSeries extends Component<Props, State> {
return v
})
if (isFirstFetch && loading === RemoteDataState.Loading) {
return <div className="graph-empty">{this.spinner}</div>
}
if (!hasValues) {
if (cellNoteVisibility === NoteVisibility.ShowWhenNoData) {
return <MarkdownCell text={cellNote} />
@ -227,6 +230,7 @@ class TimeSeries extends Component<Props, State> {
timeSeriesFlux,
rawFluxData,
loading,
uuid: latestUUID,
})}
</>
)
@ -277,19 +281,20 @@ class TimeSeries extends Component<Props, State> {
let timeSeriesFlux: FluxTable[] = []
let rawFluxData = ''
let responseUUID: string
let loading: RemoteDataState = null
this.setState({loading: RemoteDataState.Loading})
this.latestUUID = uuid.v1()
const latestUUID = uuid.v1()
try {
if (this.isFluxQuery) {
const results = await this.executeFluxQuery()
const results = await this.executeFluxQuery(latestUUID)
timeSeriesFlux = results.tables
rawFluxData = results.csv
responseUUID = results.uuid
} else {
timeSeriesInfluxQL = await this.executeInfluxQLQueries()
timeSeriesInfluxQL = await this.executeInfluxQLQueries(latestUUID)
responseUUID = _.get(timeSeriesInfluxQL, '0.response.uuid')
}
@ -297,13 +302,13 @@ class TimeSeries extends Component<Props, State> {
return
}
if (responseUUID !== this.latestUUID) {
if (responseUUID !== latestUUID) {
return
}
this.setState({loading: RemoteDataState.Done})
loading = RemoteDataState.Done
} catch {
this.setState({loading: RemoteDataState.Error})
loading = RemoteDataState.Error
}
this.setState({
@ -311,6 +316,8 @@ class TimeSeries extends Component<Props, State> {
timeSeriesFlux,
rawFluxData,
isFirstFetch: false,
loading,
latestUUID,
})
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 script: string = _.get(queries, '0.text', '')
@ -330,7 +339,7 @@ class TimeSeries extends Component<Props, State> {
source,
script,
timeRange,
this.latestUUID
latestUUID
)
if (results.didTruncate && onNotify) {
@ -340,19 +349,20 @@ class TimeSeries extends Component<Props, State> {
return results
}
private executeInfluxQLQueries = async (): Promise<
TimeSeriesServerResponse[]
> => {
private executeInfluxQLQueries = async (
latestUUID: string
): Promise<TimeSeriesServerResponse[]> => {
const {queries} = this.props
const timeSeriesInfluxQL = await Promise.all(
queries.map(this.executeInfluxQLQuery)
queries.map(query => this.executeInfluxQLQuery(query, latestUUID))
)
return timeSeriesInfluxQL
}
private executeInfluxQLQuery = async (
query: Query
query: Query,
latestUUID: string
): Promise<TimeSeriesServerResponse> => {
const {source, templates, editQueryStatus} = this.props
const TEMP_RES = 300 // FIXME
@ -365,7 +375,7 @@ class TimeSeries extends Component<Props, State> {
query,
templates,
TEMP_RES,
this.latestUUID
latestUUID
)
const warningMessage = extractQueryWarningMessage(response)