@ -1,16 +1,6 @@
import React , { Component } from 'react' ;
import {
Alert ,
Button ,
Col ,
Nav ,
NavItem ,
NavLink ,
Row ,
TabContent ,
TabPane ,
} from 'reactstrap' ;
import { Alert , Button , Col , Nav , NavItem , NavLink , Row , TabContent , TabPane } from 'reactstrap' ;
import moment from 'moment-timezone' ;
@ -32,14 +22,15 @@ interface PanelProps {
interface PanelState {
data : any ; // TODO: Type data.
lastQueryParams : { // TODO: Share these with Graph.tsx in a file.
startTime : number ,
endTime : number ,
resolution : number ,
lastQueryParams : {
// TODO: Share these with Graph.tsx in a file.
startTime : number ;
endTime : number ;
resolution : number ;
} | null ;
loading : boolean ;
error : string | null ;
stats : QueryStats | null ,
stats : QueryStats | null ;
}
export interface PanelOptions {
@ -63,7 +54,7 @@ export const PanelDefaultOptions: PanelOptions = {
endTime : null ,
resolution : null ,
stacked : false ,
}
} ;
class Panel extends Component < PanelProps , PanelState > {
private abortInFlightFetch : ( ( ) = > void ) | null = null ;
@ -83,16 +74,17 @@ class Panel extends Component<PanelProps, PanelState> {
componentDidUpdate ( prevProps : PanelProps , prevState : PanelState ) {
const prevOpts = prevProps . options ;
const opts = this . props . options ;
if ( prevOpts . type !== opts . type ||
prevOpts . range !== opts . range ||
prevOpts . endTime !== opts . endTime ||
prevOpts . resolution !== opts . resolution ) {
if (
prevOpts . type !== opts . type ||
prevOpts . range !== opts . range ||
prevOpts . endTime !== opts . endTime ||
prevOpts . resolution !== opts . resolution
) {
if ( prevOpts . type !== opts . type ) {
// If the other options change, we still want to show the old data until the new
// query completes, but this is not a good idea when we actually change between
// table and graph view, since not all queries work well in both.
this . setState ( { data : null } ) ;
this . setState ( { data : null } ) ;
}
this . executeQuery ( opts . expr ) ;
}
@ -104,9 +96,9 @@ class Panel extends Component<PanelProps, PanelState> {
executeQuery = ( expr : string ) : void = > {
const queryStart = Date . now ( ) ;
this . props . onExecuteQuery ( expr )
this . props . onExecuteQuery ( expr ) ;
if ( this . props . options . expr !== expr ) {
this . setOptions ( { expr : expr } ) ;
this . setOptions ( { expr : expr } ) ;
}
if ( expr === '' ) {
return ;
@ -119,114 +111,114 @@ class Panel extends Component<PanelProps, PanelState> {
const abortController = new AbortController ( ) ;
this . abortInFlightFetch = ( ) = > abortController . abort ( ) ;
this . setState ( { loading : true } ) ;
this . setState ( { loading : true } ) ;
const endTime = this . getEndTime ( ) . valueOf ( ) / 1000 ; // TODO: shouldn't valueof only work when it's a moment?
const startTime = endTime - this . props . options . range ;
const resolution = this . props . options . resolution || Math . max ( Math . floor ( this . props . options . range / 250 ) , 1 ) ;
const url = new URL ( window . location . href ) ;
const params : { [ key : string ] : string } = {
'query' : expr ,
const params : { [ key : string ] : string } = {
query : expr ,
} ;
switch ( this . props . options . type ) {
case 'graph' :
url . pathname = '../../api/v1/query_range'
url . pathname = '../../api/v1/query_range' ;
Object . assign ( params , {
start : startTime ,
end : endTime ,
step : resolution ,
} )
} ) ;
// TODO path prefix here and elsewhere.
break ;
case 'table' :
url . pathname = '../../api/v1/query'
url . pathname = '../../api/v1/query' ;
Object . assign ( params , {
time : endTime ,
} )
} ) ;
break ;
default :
throw new Error ( 'Invalid panel type "' + this . props . options . type + '"' ) ;
}
Object . keys ( params ) . forEach ( key = > url . searchParams . append ( key , params [ key ] ) )
Object . keys ( params ) . forEach ( key = > url . searchParams . append ( key , params [ key ] ) ) ;
fetch ( url . toString ( ) , { cache : 'no-store' , signal : abortController.signal } )
. then ( resp = > resp . json ( ) )
. then ( json = > {
if ( json . status !== 'success' ) {
throw new Error ( json . error || 'invalid response JSON' ) ;
}
fetch ( url . toString ( ) , { cache : 'no-store' , signal : abortController.signal } )
. then ( resp = > resp . json ( ) )
. then ( json = > {
if ( json . status !== 'success' ) {
throw new Error ( json . error || 'invalid response JSON' ) ;
}
let resultSeries = 0 ;
if ( json . data ) {
const { resultType , result } = json . data ;
if ( resultType === "scalar" ) {
resultSeries = 1 ;
} else if ( result && result . length > 0 ) {
resultSeries = result . length ;
let resultSeries = 0 ;
if ( json . data ) {
const { resultType , result } = json . data ;
if ( resultType === 'scalar' ) {
resultSeries = 1 ;
} else if ( result && result . length > 0 ) {
resultSeries = result . length ;
}
}
}
this . setState ( {
error : null ,
data : json.data ,
lastQueryParams : {
startTime ,
endTime ,
resolution ,
} ,
stats : {
loadTime : Date.now ( ) - queryStart ,
resolution ,
resultSeries
} ,
loading : false ,
} ) ;
this . abortInFlightFetch = null ;
} )
. catch ( error = > {
if ( error . name === 'AbortError' ) {
// Aborts are expected, don't show an error for them.
return
}
this . setState ( {
error : 'Error executing query: ' + error . message ,
loading : false ,
this . setState ( {
error : null ,
data : json.data ,
lastQueryParams : {
startTime ,
endTime ,
resolution ,
} ,
stats : {
loadTime : Date.now ( ) - queryStart ,
resolution ,
resultSeries ,
} ,
loading : false ,
} ) ;
this . abortInFlightFetch = null ;
} )
} ) ;
}
. catch ( error = > {
if ( error . name === 'AbortError' ) {
// Aborts are expected, don't show an error for them.
return ;
}
this . setState ( {
error : 'Error executing query: ' + error . message ,
loading : false ,
} ) ;
} ) ;
} ;
setOptions ( opts : object ) : void {
const newOpts = { . . . this . props . options , . . . opts } ;
const newOpts = { . . . this . props . options , . . . opts } ;
this . props . onOptionsChanged ( newOpts ) ;
}
handleExpressionChange = ( expr : string ) : void = > {
this . setOptions ( { expr : expr } ) ;
}
this . setOptions ( { expr : expr } ) ;
} ;
handleChangeRange = ( range : number ) : void = > {
this . setOptions ( { range : range } ) ;
}
this . setOptions ( { range : range } ) ;
} ;
getEndTime = ( ) : number | moment . Moment = > {
if ( this . props . options . endTime === null ) {
return moment ( ) ;
}
return this . props . options . endTime ;
}
} ;
handleChangeEndTime = ( endTime : number | null ) = > {
this . setOptions ( { endTime : endTime } ) ;
}
this . setOptions ( { endTime : endTime } ) ;
} ;
handleChangeResolution = ( resolution : number | null ) = > {
this . setOptions ( { resolution : resolution } ) ;
}
this . setOptions ( { resolution : resolution } ) ;
} ;
handleChangeStacking = ( stacked : boolean ) = > {
this . setOptions ( { stacked : stacked } ) ;
}
this . setOptions ( { stacked : stacked } ) ;
} ;
render() {
const { pastQueries , metricNames , options } = this . props ;
@ -246,9 +238,7 @@ class Panel extends Component<PanelProps, PanelState> {
< / Col >
< / Row >
< Row >
< Col >
{ this . state . error && < Alert color = "danger" > { this . state . error } < / Alert > }
< / Col >
< Col > { this . state . error && < Alert color = "danger" > { this . state . error } < / Alert > } < / Col >
< / Row >
< Row >
< Col >
@ -256,7 +246,9 @@ class Panel extends Component<PanelProps, PanelState> {
< NavItem >
< NavLink
className = { options . type === 'table' ? 'active' : '' }
onClick = { ( ) = > { this . setOptions ( { type : 'table' } ) ; } }
onClick = { ( ) = > {
this . setOptions ( { type : 'table' } ) ;
} }
>
Table
< / NavLink >
@ -264,19 +256,18 @@ class Panel extends Component<PanelProps, PanelState> {
< NavItem >
< NavLink
className = { options . type === 'graph' ? 'active' : '' }
onClick = { ( ) = > { this . setOptions ( { type : 'graph' } ) ; } }
onClick = { ( ) = > {
this . setOptions ( { type : 'graph' } ) ;
} }
>
Graph
< / NavLink >
< / NavItem >
{
( ! this . state . loading && ! this . state . error && this . state . stats ) &&
< QueryStatsView { ...this.state.stats } / >
}
{ ! this . state . loading && ! this . state . error && this . state . stats && < QueryStatsView { ...this.state.stats } / > }
< / Nav >
< TabContent activeTab = { options . type } >
< TabPane tabId = "table" >
{ options . type === 'table' &&
{ options . type === 'table' && (
< >
< div className = "table-controls" >
< TimeInput
@ -288,17 +279,16 @@ class Panel extends Component<PanelProps, PanelState> {
< / div >
< DataTable data = { this . state . data } / >
< / >
}
) }
< / TabPane >
< TabPane tabId = "graph" >
{ this . props . options . type === 'graph' &&
{ this . props . options . type === 'graph' && (
< >
< GraphControls
range = { options . range }
endTime = { options . endTime }
resolution = { options . resolution }
stacked = { options . stacked }
onChangeRange = { this . handleChangeRange }
onChangeEndTime = { this . handleChangeEndTime }
onChangeResolution = { this . handleChangeResolution }
@ -306,14 +296,16 @@ class Panel extends Component<PanelProps, PanelState> {
/ >
< Graph data = { this . state . data } stacked = { options . stacked } queryParams = { this . state . lastQueryParams } / >
< / >
}
) }
< / TabPane >
< / TabContent >
< / Col >
< / Row >
< Row >
< Col >
< Button className = "float-right" color = "link" onClick = { this . props . removePanel } size = "sm" > Remove Panel < / Button >
< Button className = "float-right" color = "link" onClick = { this . props . removePanel } size = "sm" >
Remove Panel
< / Button >
< / Col >
< / Row >
< / div >