diff --git a/ui/src/kapacitor/components/RuleGraph.tsx b/ui/src/kapacitor/components/RuleGraph.tsx index da6d7bc98..afbe5e17f 100644 --- a/ui/src/kapacitor/components/RuleGraph.tsx +++ b/ui/src/kapacitor/components/RuleGraph.tsx @@ -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 { timeRange={timeRange} source={source} queries={this.queries} + uuid={uuid.v1()} > {data => { return ( diff --git a/ui/src/shared/components/AutoRefresh.tsx b/ui/src/shared/components/AutoRefresh.tsx new file mode 100644 index 000000000..295f020e2 --- /dev/null +++ b/ui/src/shared/components/AutoRefresh.tsx @@ -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 { + 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()}) + } +} diff --git a/ui/src/shared/components/RefreshingGraph.tsx b/ui/src/shared/components/RefreshingGraph.tsx index d84aa2054..cd721ba93 100644 --- a/ui/src/shared/components/RefreshingGraph.tsx +++ b/ui/src/shared/components/RefreshingGraph.tsx @@ -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 { +class RefreshingGraph extends Component { public static defaultProps: Partial = { inView: true, manualRefresh: 0, staticLegend: false, timeFormat: DEFAULT_TIME_FORMAT, decimalPlaces: DEFAULT_DECIMAL_PLACES, + autoRefresher: GlobalAutoRefresher, } - private timeSeries: React.RefObject = 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 { } return ( - - {({timeSeriesInfluxQL, timeSeriesFlux, rawFluxData, loading, uuid}) => { - if (showRawFluxData) { - return - } + + {refreshingUUID => ( + + {({ + timeSeriesInfluxQL, + timeSeriesFlux, + rawFluxData, + loading, + uuid, + }) => { + const hasValues = + timeSeriesFlux.length || + _.some(timeSeriesInfluxQL, s => { + const results = getDeep( + 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) - } - }} - + if (!hasValues) { + if (cellNoteVisibility === NoteVisibility.ShowWhenNoData) { + return + } + + if ( + this.isFluxQuery && + !getDeep(source, 'links.flux', null) + ) { + return ( +
+

The current source does not support flux

+
+ ) + } + + return ( +
+

No Results

+
+ ) + } + + if (showRawFluxData) { + return + } + + 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 + ) + } + }} +
+ )} + ) } + private get isFluxQuery(): boolean { + const {queries} = this.props + + return getDeep(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) diff --git a/ui/src/shared/components/time_series/TimeSeries.tsx b/ui/src/shared/components/time_series/TimeSeries.tsx index dbdd358d4..1fffc8716 100644 --- a/ui/src/shared/components/time_series/TimeSeries.tsx +++ b/ui/src/shared/components/time_series/TimeSeries.tsx @@ -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 = () => ( ) -class TimeSeries extends Component { +class TimeSeries extends PureComponent { public static defaultProps = { inView: true, templates: [], - autoRefresher: GlobalAutoRefresher, editQueryStatus: () => ({ type: 'NOOP', payload: {}, @@ -112,8 +100,6 @@ class TimeSeries extends Component { return null } - private isComponentMounted: boolean = false - constructor(props: Props) { super(props) @@ -128,59 +114,25 @@ class TimeSeries extends Component { } } - 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 { return
{this.spinner}
} - 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 - } - - if (this.isFluxQuery && !getDeep(source, 'links.flux', null)) { - return ( -
-

The current source does not support flux

-
- ) - } - - return ( -
-

No Results

-
- ) - } - return ( <> {this.loadingDots} @@ -285,7 +209,11 @@ class TimeSeries extends Component { 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 { responseUUID = _.get(timeSeriesInfluxQL, '0.response.uuid') } - if (!this.isComponentMounted) { - return - } - if (responseUUID !== this.state.latestUUID) { return } @@ -316,7 +240,6 @@ class TimeSeries extends Component { timeSeriesInfluxQL, timeSeriesFlux, rawFluxData, - isFirstFetch: false, loading, }) @@ -393,25 +316,6 @@ class TimeSeries extends Component { 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