Updating flux time range re-queries data

pull/4597/head
Brandon Farmer 2018-10-16 11:36:29 -07:00
parent e15a8acef7
commit 2fba2b9721
4 changed files with 179 additions and 160 deletions

View File

@ -16,6 +16,7 @@ import {Source, AlertRule, QueryConfig, Query, TimeRange} from 'src/types'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {setHoverTime as setHoverTimeAction} from 'src/dashboards/actions'
import uuid from 'uuid'
interface Props {
source: Source
@ -56,6 +57,7 @@ class RuleGraph extends PureComponent<Props> {
timeRange={timeRange}
source={source}
queries={this.queries}
uuid={uuid.v1()}
>
{data => {
return (

View File

@ -0,0 +1,60 @@
import uuid from 'uuid'
import {PureComponent} from 'react'
import {AutoRefresher} from 'src/utils/AutoRefresher'
interface Props {
autoRefresh?: AutoRefresher
manualRefresh?: number
children: (uuid: string) => JSX.Element | JSX.Element[]
}
interface State {
id: string
manualRefresh: number
}
export default class AutoRefresh extends PureComponent<Props, State> {
public static getDerivedStateFromProps(nextProps: Props, state: State) {
if (state.manualRefresh !== nextProps.manualRefresh) {
return {
id: uuid.v4(),
manualRefresh: nextProps.manualRefresh,
}
}
return false
}
constructor(props: Props) {
super(props)
this.state = {
manualRefresh: props.manualRefresh,
id: uuid.v4(),
}
}
public render() {
return this.props.children(this.state.id)
}
public componentDidMount() {
const {autoRefresh} = this.props
if (autoRefresh) {
autoRefresh.subscribe(this.update)
}
}
public componentWillUnmount() {
const {autoRefresh} = this.props
if (autoRefresh) {
autoRefresh.unsubscribe(this.update)
}
}
private update = () => {
this.setState({id: uuid.v4()})
}
}

View File

@ -1,5 +1,5 @@
// Libraries
import React, {PureComponent} from 'react'
import React, {Component} from 'react'
import {connect} from 'react-redux'
import _ from 'lodash'
@ -14,6 +14,7 @@ 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'
import AutoRefresh from 'src/shared/components/AutoRefresh'
// Constants
import {emptyGraphCopy} from 'src/shared/copy/cell'
@ -24,7 +25,8 @@ import {
import {DataType} from 'src/shared/constants'
// Utils
import {AutoRefresher} from 'src/utils/AutoRefresher'
import {AutoRefresher, GlobalAutoRefresher} from 'src/utils/AutoRefresher'
import {getDeep} from 'src/utils/wrappers'
// Actions
import {setHoverTime} from 'src/dashboards/actions'
@ -42,6 +44,7 @@ import {
FluxTable,
RemoteDataState,
QueryUpdateState,
QueryType,
} from 'src/types'
import {
TableOptions,
@ -50,7 +53,10 @@ import {
NoteVisibility,
} from 'src/types/dashboards'
import {GrabDataForDownloadHandler} from 'src/types/layout'
import {TimeSeriesServerResponse} from 'src/types/series'
import {
TimeSeriesSuccessfulResult,
TimeSeriesServerResponse,
} from 'src/types/series'
interface TypeAndData {
dataType: DataType
@ -91,25 +97,18 @@ interface Props {
onUpdateFieldOptions?: (fieldOptions: FieldOption[]) => void
}
class RefreshingGraph extends PureComponent<Props> {
class RefreshingGraph extends Component<Props> {
public static defaultProps: Partial<Props> = {
inView: true,
manualRefresh: 0,
staticLegend: false,
timeFormat: DEFAULT_TIME_FORMAT,
decimalPlaces: DEFAULT_DECIMAL_PLACES,
autoRefresher: GlobalAutoRefresher,
}
private timeSeries: React.RefObject<TimeSeries> = React.createRef()
public componentDidUpdate(prevProps) {
if (!this.timeSeries.current) {
return
}
if (this.props.editorLocation && this.haveVisOptionsChanged(prevProps)) {
this.timeSeries.current.forceUpdate()
}
public shouldComponentUpdate(nextProps: Props) {
return this.haveVisOptionsChanged(nextProps)
}
public render() {
@ -144,53 +143,107 @@ class RefreshingGraph extends PureComponent<Props> {
}
return (
<TimeSeries
ref={this.timeSeries}
autoRefresher={autoRefresher}
manualRefresh={manualRefresh}
source={source}
cellType={type}
inView={inView}
queries={this.queries}
timeRange={timeRange}
templates={templates}
editQueryStatus={editQueryStatus}
onNotify={onNotify}
grabDataForDownload={grabDataForDownload}
grabFluxData={grabFluxData}
cellNote={cellNote}
cellNoteVisibility={cellNoteVisibility}
>
{({timeSeriesInfluxQL, timeSeriesFlux, rawFluxData, loading, uuid}) => {
if (showRawFluxData) {
return <RawFluxDataTable csv={rawFluxData} />
}
<AutoRefresh autoRefresh={autoRefresher} manualRefresh={manualRefresh}>
{refreshingUUID => (
<TimeSeries
uuid={refreshingUUID}
source={source}
inView={inView}
queries={this.queries}
timeRange={timeRange}
templates={templates}
editQueryStatus={editQueryStatus}
onNotify={onNotify}
grabDataForDownload={grabDataForDownload}
grabFluxData={grabFluxData}
>
{({
timeSeriesInfluxQL,
timeSeriesFlux,
rawFluxData,
loading,
uuid,
}) => {
const hasValues =
timeSeriesFlux.length ||
_.some(timeSeriesInfluxQL, s => {
const results = getDeep<TimeSeriesSuccessfulResult[]>(
s,
'response.results',
[]
)
const v = _.some(results, r => r.series)
return v
})
switch (type) {
case CellType.SingleStat:
return this.singleStat(timeSeriesInfluxQL, timeSeriesFlux)
case CellType.Table:
return this.table(timeSeriesInfluxQL, timeSeriesFlux, uuid)
case CellType.Gauge:
return this.gauge(timeSeriesInfluxQL, timeSeriesFlux)
default:
return this.lineGraph(timeSeriesInfluxQL, timeSeriesFlux, loading)
}
}}
</TimeSeries>
if (!hasValues) {
if (cellNoteVisibility === NoteVisibility.ShowWhenNoData) {
return <MarkdownCell text={cellNote} />
}
if (
this.isFluxQuery &&
!getDeep<string>(source, 'links.flux', null)
) {
return (
<div className="graph-empty">
<p>The current source does not support flux</p>
</div>
)
}
return (
<div className="graph-empty">
<p>No Results</p>
</div>
)
}
if (showRawFluxData) {
return <RawFluxDataTable csv={rawFluxData} />
}
switch (type) {
case CellType.SingleStat:
return this.singleStat(timeSeriesInfluxQL, timeSeriesFlux)
case CellType.Table:
return this.table(timeSeriesInfluxQL, timeSeriesFlux, uuid)
case CellType.Gauge:
return this.gauge(timeSeriesInfluxQL, timeSeriesFlux)
default:
return this.lineGraph(
timeSeriesInfluxQL,
timeSeriesFlux,
loading
)
}
}}
</TimeSeries>
)}
</AutoRefresh>
)
}
private get isFluxQuery(): boolean {
const {queries} = this.props
return getDeep<string>(queries, '0.type', '') === QueryType.Flux
}
private haveVisOptionsChanged(prevProps: Props): boolean {
const visProps: string[] = [
'axes',
'colors',
'type',
'tableOptions',
'fieldOptions',
'decimalPlaces',
'timeFormat',
'showRawFluxData',
'queries',
'templates',
'manualRefresh',
'timeRange',
]
const prevVisValues = _.pick(prevProps, visProps)

View File

@ -1,8 +1,7 @@
// Library
import React, {Component} from 'react'
import React, {PureComponent} from 'react'
import _ from 'lodash'
import uuid from 'uuid'
// API
import {executeQuery} from 'src/shared/apis/query'
import {
@ -17,7 +16,6 @@ import {
Query,
RemoteDataState,
TimeRange,
CellType,
Status,
FluxTable,
QueryType,
@ -26,8 +24,6 @@ import {TimeSeriesServerResponse} from 'src/types/series'
import {GrabDataForDownloadHandler} from 'src/types/layout'
// Utils
import {GlobalAutoRefresher, AutoRefresher} from 'src/utils/AutoRefresher'
import {NoteVisibility} from 'src/types/dashboards'
import {
extractQueryWarningMessage,
extractQueryErrorMessage,
@ -36,9 +32,6 @@ import {notify} from 'src/shared/actions/notifications'
import {fluxResponseTruncatedError} from 'src/shared/copy/notifications'
import {getDeep} from 'src/utils/wrappers'
// Components
import MarkdownCell from 'src/shared/components/MarkdownCell'
export const DEFAULT_TIME_SERIES = [{response: {results: []}}]
interface RenderProps {
@ -51,19 +44,15 @@ interface RenderProps {
interface Props {
source: Source
cellType?: CellType
manualRefresh?: number
uuid: string
queries: Query[]
timeRange: TimeRange
children: (r: RenderProps) => JSX.Element
autoRefresher?: AutoRefresher
inView?: boolean
templates?: Template[]
editQueryStatus?: (queryID: string, status: Status) => void
grabDataForDownload?: GrabDataForDownloadHandler
grabFluxData?: (data: FluxTable[]) => void
cellNote?: string
cellNoteVisibility?: NoteVisibility
onNotify?: typeof notify
}
@ -85,11 +74,10 @@ const GraphLoadingDots = () => (
</div>
)
class TimeSeries extends Component<Props, State> {
class TimeSeries extends PureComponent<Props, State> {
public static defaultProps = {
inView: true,
templates: [],
autoRefresher: GlobalAutoRefresher,
editQueryStatus: () => ({
type: 'NOOP',
payload: {},
@ -112,8 +100,6 @@ class TimeSeries extends Component<Props, State> {
return null
}
private isComponentMounted: boolean = false
constructor(props: Props) {
super(props)
@ -128,59 +114,25 @@ class TimeSeries extends Component<Props, State> {
}
}
public shouldComponentUpdate(prevProps: Props, prevState: State) {
const propKeys = [
'source',
'queries',
'timeRange',
'inView',
'templates',
'cellType',
'manualRefresh',
]
const stateKeys = ['loading', 'timeSeriesInfluxQL', 'timeSeriesFlux']
const propsUpdated = propKeys.some(
k => !_.isEqual(this.props[k], prevProps[k])
)
const stateUpdated = stateKeys.some(
k => !_.isEqual(this.state[k], prevState[k])
)
return propsUpdated || stateUpdated
}
public async componentDidMount() {
const {autoRefresher} = this.props
this.isComponentMounted = true
this.executeQueries()
autoRefresher.subscribe(this.executeQueries)
}
public componentWillUnmount() {
const {autoRefresher} = this.props
this.isComponentMounted = false
autoRefresher.unsubscribe(this.executeQueries)
}
public async componentDidUpdate(prevProps: Props) {
if (this.props.autoRefresher !== prevProps.autoRefresher) {
prevProps.autoRefresher.unsubscribe(this.executeQueries)
this.props.autoRefresher.subscribe(this.executeQueries)
}
const prevQueries = _.map(prevProps.queries, q => q.text)
const currQueries = _.map(this.props.queries, q => q.text)
const queriesDifferent = !_.isEqual(prevQueries, currQueries)
if (!this.isPropsDifferent(prevProps)) {
return
if (
this.props.uuid !== prevProps.uuid ||
queriesDifferent ||
this.state.isFirstFetch
) {
this.executeQueries()
}
this.executeQueries()
}
public render() {
const {cellNoteVisibility, cellNote, source} = this.props
const {
timeSeriesInfluxQL,
timeSeriesFlux,
@ -194,34 +146,6 @@ class TimeSeries extends Component<Props, State> {
return <div className="graph-empty">{this.spinner}</div>
}
const hasValues =
timeSeriesFlux.length ||
_.some(timeSeriesInfluxQL, s => {
const results = _.get(s, 'response.results', [])
const v = _.some(results, r => r.series)
return v
})
if (!hasValues) {
if (cellNoteVisibility === NoteVisibility.ShowWhenNoData) {
return <MarkdownCell text={cellNote} />
}
if (this.isFluxQuery && !getDeep(source, 'links.flux', null)) {
return (
<div className="graph-empty">
<p>The current source does not support flux</p>
</div>
)
}
return (
<div className="graph-empty">
<p>No Results</p>
</div>
)
}
return (
<>
{this.loadingDots}
@ -285,7 +209,11 @@ class TimeSeries extends Component<Props, State> {
const latestUUID = uuid.v1()
this.setState({loading: RemoteDataState.Loading, latestUUID})
this.setState({
loading: RemoteDataState.Loading,
latestUUID,
isFirstFetch: false,
})
try {
if (this.isFluxQuery) {
@ -299,10 +227,6 @@ class TimeSeries extends Component<Props, State> {
responseUUID = _.get(timeSeriesInfluxQL, '0.response.uuid')
}
if (!this.isComponentMounted) {
return
}
if (responseUUID !== this.state.latestUUID) {
return
}
@ -316,7 +240,6 @@ class TimeSeries extends Component<Props, State> {
timeSeriesInfluxQL,
timeSeriesFlux,
rawFluxData,
isFirstFetch: false,
loading,
})
@ -393,25 +316,6 @@ class TimeSeries extends Component<Props, State> {
throw error
}
}
private isPropsDifferent(prevProps: Props) {
const isSourceDifferent = !_.isEqual(this.props.source, prevProps.source)
return (
this.props.manualRefresh !== prevProps.manualRefresh ||
this.props.inView !== prevProps.inView ||
!!this.queryDifference(this.props.queries, prevProps.queries).length ||
!_.isEqual(this.props.templates, prevProps.templates) ||
isSourceDifferent
)
}
private queryDifference = (left, right) => {
const mapper = q => `${q.text}`
const l = left.map(mapper)
const r = right.map(mapper)
return _.difference(_.union(l, r), _.intersection(l, r))
}
}
export default TimeSeries