import QueryOptions from './interfaces/query-options' ; import AnnotationOptions from './interfaces/annotation-options' ; import GTS from './gts' ; import { Table } from './table' ; import { isGeoJson } from './geo' ; import Query from './query' ; export default class Warp10Datasource { constructor ( private instanceSettings , private $q , private backendSrv , private templateSrv , private $log ) { } param options return { Promise < any > } query ( opts ) { let queries = [ ] ; let wsHeader = this . computeTimeVars ( opts ) + this . computeGrafanaContext ( ) + this . computePanelRepeatVars ( opts ) ; opts . targets . forEach ( queryRef => { let query = Object . assign ( { } , queryRef ) ; if ( query . friendlyQuery ) query . friendlyQuery = Object . assign ( new Query ( ) , query . friendlyQuery ) ; if ( query . expr || query . friendlyQuery ) { if ( query . advancedMode === undefined ) query . advancedMode = false ; query . ws = `${ wsHeader }\n${ query . advancedMode ? query . expr : query . friendlyQuery . warpScript }` ; queries . push ( query ) ; console . debug ( 'New Query: ' , query . ws ) ; } } ) ; if ( queries . length === 0 ) { let d = this . $q . defer ( ) ; d . resolve ( { data : [ ] } ) ; return d . promise ; } queries = queries . map ( this . executeExec . bind ( this ) ) ; return this . $q . all ( queries ) . then ( ( responses ) => { let data = [ ] ; responses . forEach ( ( response , i ) => { const res = response . result ; if ( res . data . type === 'error' ) { console . error ( res . data . value ) ; return ; } if ( res . data . length === 1 && res . data [ 0 ] && Table . isTable ( res . data [ 0 ] ) ) { const t = res . data [ 0 ] ; t . type = 'table' ; data . push ( t ) ; return ; } if ( res . data . length === 1 && res . data [ 0 ] && isGeoJson ( res . data [ 0 ] ) ) { const t = res . data [ 0 ] ; t . type = 'table' ; data = t ; return ; } GTS . stackFilter ( res . data ) . forEach ( gts => { let grafanaGts = { target : ( opts . targets [ i ] . hideLabels ) ? gts . c : gts . nameWithLabels , datapoints : [ ] , refId : ( response . query || { } ) . refId } ; if ( opts . targets [ i ] . hideAttributes !== undefined && ! opts . targets [ i ] . hideAttributes ) { grafanaGts . target += gts . formatedAttributes ; } gts . v . forEach ( dp => { grafanaGts . datapoints . push ( [ dp [ dp . length - 1 ] , dp [ 0 ] / 1000 ] ) ; } ) ; data . push ( grafanaGts ) ; } ) ; } ) ; return { data } ; } ) . catch ( ( err ) => { let errorline = - 1 ; let errorMessage = "Unable to read x-warp10-error-line and x-warp10-error-line headers in server answer" ; let headers = err . headers ? err . headers : new Headers ( { } ) ; if ( headers . has ( 'x-warp10-error-line' ) && headers . has ( 'x-warp10-error-message' ) ) { const wsHeadersOffset = wsHeader . split ( '\n' ) . length ; errorline = Number . parseInt ( headers . get ( 'x-warp10-error-line' ) ) - wsHeadersOffset ; errorMessage = headers . get ( 'x-warp10-error-message' ) ; errorMessage = errorMessage . replace ( /\[Line #(\d+)\]/g , ( match , group1 ) => '[Line #' + ( Number . parseInt ( group1 ) - wsHeadersOffset ) . toString ( ) + ']' ) ; } console . warn ( '[Warp 10] Failed to execute query' , err ) ; let d = this . $q . defer ( ) ; throw { message : `WarpScript Failure on Line ${ errorline }, ${ errorMessage }` } ; } ) ; } return { Promise < any > } testDatasource ( ) { return this . executeExec ( { ws : '1 2 +' } ) . then ( response => { const res = response . result ; if ( res . data [ 0 ] !== 3 ) { return { status : 'error' , message : JSON . parse ( res . data ) || res . data , title : 'Failed to execute basic WarpScript™' } ; } else { return { status : 'success' , message : 'Datasource is working' , title : 'Success' } ; } } ) . catch ( ( res ) => { console . debug ( 'Error' , res ) ; let errorMessage = 'could not be loaded, see details in the console' ; if ( res . statusText != undefined ) { errorMessage = res . statusText ; } return { status : 'error' , message : `Error message: ${ errorMessage }` , title : 'Failed to contact Warp 10 Platform' } ; } ) ; } param options return { Promise < any > } annotationQuery ( opts ) { let ws = this . computeTimeVars ( opts ) + this . computeGrafanaContext ( ) + opts . annotation . query ; return this . executeExec ( { ws } ) . then ( ( response ) => { const res = response . result ; const annotations = [ ] ; for ( let gts of GTS . stackFilter ( res . data ) ) { let tags = [ ] ; for ( let label in gts . l ) { tags . push ( `${ label }:${ gts . l [ label ] }` ) ; } gts . v . forEach ( dp => { annotations . push ( { annotation : { name : opts . annotation . name , enabled : true , datasource : this . instanceSettings . name , } , title : gts . c , time : Math . trunc ( dp [ 0 ] / ( 1000 ) ) , text : dp [ dp . length - 1 ] , tags : tags } ) ; } ) ; } return annotations ; } ) ; } param options return { Promise < any > } metricFindQuery ( ws ) { return this . executeExec ( { ws : this . computeGrafanaContext ( ) + ws } ) . then ( response => { const res = response . result ; if ( ! Array . isArray ( res . data ) ) { throw new Error ( 'Warp 10 expects the response to be a stack (an array), it isn\'t' ) ; } let entries = [ ] ; if ( 1 == res . data . length && Array . isArray ( res . data [ 0 ] ) ) { res . data [ 0 ] . forEach ( elt => { if ( typeof elt === 'string' || elt instanceof String || typeof elt === 'number' ) { entries . push ( { text : elt . toString ( ) , value : elt . toString ( ) } ) ; } } ) ; } else if ( res . data . length === 1 && typeof res . data [ 0 ] === 'object' ) { Object . keys ( res . data [ 0 ] ) . forEach ( key => { const value = res . data [ 0 ] [ key ] ; if ( typeof value === 'string' || value instanceof String || typeof value === 'number' ) { entries . push ( { text : key . toString ( ) , value : value . toString ( ) } ) ; } } ) ; } else { res . data . forEach ( elt => { if ( typeof elt === 'string' || elt instanceof String || typeof elt === 'number' ) { entries . push ( { text : elt . toString ( ) , value : elt . toString ( ) } ) ; } } ) ; } return entries ; } ) ; } param ws return { Promise < any > } private executeExec ( query ) { let endpoint = this . instanceSettings . url ; if ( ( query . backend !== undefined ) && ( query . backend . length > 0 ) ) { endpoint = query . backend ; } if ( endpoint . charAt ( endpoint . length - 1 ) === '/' ) { endpoint = endpoint . substr ( 0 , endpoint . length - 1 ) ; } return fetch ( endpoint + '/api/v0/exec' , { method : 'post' , body : query . ws , headers : { 'Accept' : undefined , 'Content-Type' : 'text/plain; charset=UTF-8' } , } ) . then ( res => { if ( res . status == 200 ) { return res . json ( ) . then ( data => { return { result : { data : data } , query : query } ; } ) ; } else { throw ( res ) ; } } ) ; } param selector return { Promise < any > } private executeFind ( selector ) { return this . backendSrv . datasourceRequest ( { method : 'GET' , url : `${ this . instanceSettings . url }/api/v0/find?selector=${ selector }` , headers : { 'Accept' : undefined , 'Content-Type' : undefined } } ) ; } return { string } private computeGrafanaContext ( ) { let wsHeader = '' ; for ( let myVar in this . instanceSettings . jsonData ) { let value = this . instanceSettings . jsonData [ myVar ] ; if ( typeof value === 'string' ) value = value . replace ( /'/g , '"' ) ; if ( typeof value === 'string' && ! value . startsWith ( '<%' ) && ! value . endsWith ( '%>' ) ) value = `'${ value }'` ; wsHeader += `${ value || 'NULL' } '${ myVar }' STORE\n` ; } for ( let myVar of this . templateSrv . variables ) { const value = myVar . current . value ; if ( ( ( Array . isArray ( value ) && ( value . length == 1 && value [ 0 ] === '$__all' ) ) || value === "$__all" ) ) { if ( myVar . allValue && myVar . allValue !== "" ) { const customValue = myVar . allValue ; wsHeader += `[ '${ customValue }' ] '${ myVar . name }_list' STORE\n` ; wsHeader += ` '${ customValue }' '${ myVar . name }' STORE\n` ; } else { const allValues = myVar . options . filter ( o => o . value !== "$__all" ) . map ( o => o . value ) ; wsHeader += `[ ${ allValues . map ( s => `'${ s }'` ) . join ( " " ) } ] '${ myVar . name }_list' STORE\n` ; wsHeader += ` '~' $${ myVar . name }_list REOPTALT + '${ myVar . name }' STORE\n` ; } } else if ( Array . isArray ( value ) ) { wsHeader += `[ ${ value . map ( s => `'${ s }'` ) . join ( " " ) } ] '${ myVar . name }_list' STORE\n` ; if ( 1 == value . length ) { wsHeader += ` '${ value [ 0 ] }' '${ myVar . name }' STORE\n` ; } else { wsHeader += ` '~' $${ myVar . name }_list REOPTALT + '${ myVar . name }' STORE\n` ; } } else { wsHeader += `[ '${ value }' ] '${ myVar . name }_list' STORE\n` ; wsHeader += `'${ value }' '${ myVar . name }' STORE\n` ; } } wsHeader += "LINEON\n" ; return wsHeader ; } private computeTimeVars ( opts ) { let vars = { start : opts . range . from . toDate ( ) . getTime ( ) * 1000 , startISO : opts . range . from . toISOString ( ) , end : opts . range . to . toDate ( ) . getTime ( ) * 1000 , endISO : opts . range . to . toISOString ( ) , } ; vars . interval = vars . end - vars . start ; vars . __interval = Math . floor ( vars . interval / ( opts . maxDataPoints || 1 ) ) ; vars . __interval_ms = Math . floor ( vars . __interval / 1000 ) ; let str = '' ; for ( let gVar in vars ) { str += `${ isNaN ( vars [ gVar ] ) ? `'${ vars [ gVar ] }'` : vars [ gVar ] } '${ gVar }' STORE ` ; } return str ; } private computePanelRepeatVars ( opts ) { let str = '' ; if ( opts . scopedVars ) { for ( let k in opts . scopedVars ) { let v = opts . scopedVars [ k ] ; if ( v . selected || this . scopedVarIsAll ( k ) ) { str += `'${ v . value }' '${ k }' STORE ` ; } } } return str ; } param name return private scopedVarIsAll ( name ) { for ( let i = 0 ; i < this . templateSrv . variables . length ; i ++ ) { const v = this . templateSrv . variables [ i ] ; if ( v . name === name && v . current . value . length === 1 && v . current . value [ 0 ] === '$__all' ) { return true ; } } return false ; } }