import { PrometheusDriver , QueryResult } from 'prometheus-query' ; import { Chart , ChartDataSets , PluginServiceGlobalRegistration , PluginServiceRegistrationOptions } from 'chart.js' ; import datasource from './datasource' ; import { ChartDatasourcePrometheusPluginOptions , PrometheusQuery } from './options' ; import { setTimeAxesOptions , fillGaps , } from './axes' ; import { selectLabel , selectBackGroundColor , selectBorderColor , } from './serie' ; class ChartDatasourcePrometheusPluginInternals { loading : boolean = false ; updateInterval : any | null = null ; error : string | null = null ; } export class ChartDatasourcePrometheusPlugin implements PluginServiceGlobalRegistration , PluginServiceRegistrationOptions { id = 'datasource-prometheus' ; public beforeInit ( chart , options ) { chart [ 'datasource-prometheus' ] = new ChartDatasourcePrometheusPluginInternals ( ) ; } public afterInit ( chart , _options ) { if ( chart . config . type != 'line' ) throw 'ChartDatasourcePrometheusPlugin is already compatible with Line chart\nFeel free to contribute for more!' ; if ( ! _options ) throw 'ChartDatasourcePrometheusPlugin.options is undefined' ; const options = Object . assign ( new ChartDatasourcePrometheusPluginOptions ( ) , _options ) ; options . assertPluginOptions ( ) ; if ( ! ! options && ! ! options . timeRange ) { if ( ! ! options . timeRange . msUpdateInterval ) chart [ 'datasource-prometheus' ] . updateInterval = setInterval ( ( ) => { chart . update ( ) ; } , options . timeRange . msUpdateInterval ) ; else chart . update ( ) ; } } public beforeUpdate ( chart , _options ) { if ( ! ! chart [ 'datasource-prometheus' ] && chart [ 'datasource-prometheus' ] . loading == true ) return ; const options = Object . assign ( new ChartDatasourcePrometheusPluginOptions ( ) , _options ) ; const prometheus = options . prometheus ; const queries = options . getQueries ( ) ; const { start , end } = datasource . getStartAndEndDates ( options . timeRange ) ; const expectedStep = options . timeRange . step || datasource . getPrometheusStepAuto ( start , end , chart . width ) ; const minStep = ( options . timeRange . minStep || expectedStep ) ; const step = minStep >= expectedStep ? minStep : expectedStep ; if ( ! ! chart [ 'datasource-prometheus' ] && chart [ 'datasource-prometheus' ] . step == step && chart [ 'datasource-prometheus' ] . start == start && chart [ 'datasource-prometheus' ] . end == end ) return ; chart [ 'datasource-prometheus' ] . step = step ; chart [ 'datasource-prometheus' ] . start = start ; chart [ 'datasource-prometheus' ] . end = end ; chart [ 'datasource-prometheus' ] . error = null ; const reqs = datasource . executeQueries ( prometheus , queries , start , end , step ) ; let isHiddenMap = { } ; if ( chart . data . datasets . length > 0 ) { for ( let oldDataSetIndex in chart . data . datasets ) { const oldDataSet = chart . data . datasets [ oldDataSetIndex ] ; let metaIndex = 0 ; for ( let id in oldDataSet [ '_meta' ] ) { metaIndex = id as any as number ; } isHiddenMap [ oldDataSet . label ] = ! chart . isDatasetVisible ( oldDataSet [ '_meta' ] [ metaIndex ] . index ) ; } } const yAxes = chart . config . options . scales . yAxes ; Promise . all ( reqs ) . then ( ( results ) => { const datasets = results . reduce ( ( datasets , result , queryIndex ) => { if ( result . result . length == 0 ) return datasets ; const seriesCount = datasets . length ; const data = result . result . map ( ( serie , i ) => { return { yAxisID : ! ! yAxes && yAxes . length > 0 ? yAxes [ queryIndex % yAxes . length ] . id : null , tension : options . tension , cubicInterpolationMode : options . cubicInterpolationMode || 'default' , stepped : options . stepped , fill : options . fill || false , label : selectLabel ( options , serie , seriesCount + i ) , data : serie . values . map ( ( v , j ) => { return { t : v . time , y : v . value , } ; } ) , backgroundColor : selectBackGroundColor ( options , serie , seriesCount + i ) , borderColor : selectBorderColor ( options , serie , seriesCount + i ) , borderWidth : options . borderWidth , hidden : isHiddenMap [ selectLabel ( options , serie , seriesCount + i ) ] || false , } as ChartDataSets ; } ) ; return datasets . concat ( ... data ) ; } , [ ] ) ; chart . data . datasets = datasets ; if ( chart . data . datasets . length > 0 ) { if ( options . fillGaps ) { fillGaps ( chart , start , end , step , options ) ; } if ( options . dataSetHook ) { chart . data . datasets = options . dataSetHook ( chart . data . datasets ) ; } setTimeAxesOptions ( chart ) ; chart [ 'datasource-prometheus' ] . loading = true ; chart . update ( ) ; chart [ 'datasource-prometheus' ] . loading = false ; } } ) . catch ( ( err ) => { chart . data . datasets = [ ] ; setTimeAxesOptions ( chart ) ; chart [ 'datasource-prometheus' ] . error = 'Failed to fetch data' ; throw err ; } ) ; } public beforeRender ( chart , _options ) { const options = Object . assign ( new ChartDatasourcePrometheusPluginOptions ( ) , _options ) ; if ( chart [ 'datasource-prometheus' ] . error != null ) { const ctx = chart . ctx ; const width = chart . width ; const height = chart . height ; chart . clear ( ) ; ctx . save ( ) ; ctx . direction = options . errorMsg . direction ; ctx . textAlign = options . errorMsg . textAlign ; ctx . textBaseline = options . errorMsg . textBaseline ; ctx . font = "16px normal 'Helvetica Nueue'" ; ctx . fillText ( options . errorMsg ?. message || chart [ 'datasource-prometheus' ] . error , width / 2 , height / 2 ) ; ctx . restore ( ) ; return ; } else if ( chart . data . datasets . length == 0 ) { const ctx = chart . ctx ; const width = chart . width ; const height = chart . height ; chart . clear ( ) ; ctx . save ( ) ; ctx . direction = options . noDataMsg . direction ; ctx . textAlign = options . noDataMsg . textAlign ; ctx . textBaseline = options . noDataMsg . textBaseline ; ctx . font = options . noDataMsg . font ; ctx . fillText ( options . noDataMsg . message , width / 2 , height / 2 ) ; ctx . restore ( ) ; return ; } } public destroy ( chart ) { if ( ! ! chart [ 'datasource-prometheus' ] . updateInterval ) clearInterval ( chart [ 'datasource-prometheus' ] . updateInterval ) ; } } ;