2019-12-05 03:04:03 -06:00
import angular from 'angular' ;
2017-12-20 05:33:33 -06:00
import _ from 'lodash' ;
2019-12-11 10:40:56 -06:00
import {
DataSourceApi ,
DataSourceInstanceSettings ,
DataQueryRequest ,
DataQueryResponse ,
DataFrame ,
2020-01-24 02:50:09 -06:00
ScopedVars ,
2020-07-01 02:45:21 -05:00
DataLink ,
2020-09-08 05:34:11 -05:00
PluginMeta ,
DataQuery ,
2019-12-11 10:40:56 -06:00
} from '@grafana/data' ;
2020-09-08 05:34:11 -05:00
import LanguageProvider from './language_provider' ;
2017-12-20 05:33:33 -06:00
import { ElasticResponse } from './elastic_response' ;
2019-03-26 10:15:23 -05:00
import { IndexPattern } from './index_pattern' ;
import { ElasticQueryBuilder } from './query_builder' ;
2019-07-06 01:05:53 -05:00
import { toUtc } from '@grafana/data' ;
2019-06-24 15:15:03 -05:00
import * as queryDef from './query_def' ;
2020-12-01 12:10:23 -06:00
import { getBackendSrv , getDataSourceSrv } from '@grafana/runtime' ;
2020-10-01 12:51:23 -05:00
import { getTemplateSrv , TemplateSrv } from 'app/features/templating/template_srv' ;
import { getTimeSrv , TimeSrv } from 'app/features/dashboard/services/TimeSrv' ;
2019-12-11 10:40:56 -06:00
import { DataLinkConfig , ElasticsearchOptions , ElasticsearchQuery } from './types' ;
2017-09-28 05:52:39 -05:00
2020-09-15 03:14:47 -05:00
// Those are metadata fields as defined in https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-fields.html#_identity_metadata_fields.
// custom fields can start with underscores, therefore is not safe to exclude anything that starts with one.
const ELASTIC_META_FIELDS = [
'_index' ,
'_type' ,
'_id' ,
'_source' ,
'_size' ,
'_field_names' ,
'_ignored' ,
'_routing' ,
'_meta' ,
] ;
2019-06-24 15:15:03 -05:00
export class ElasticDatasource extends DataSourceApi < ElasticsearchQuery , ElasticsearchOptions > {
2020-07-08 04:05:20 -05:00
basicAuth? : string ;
withCredentials? : boolean ;
2017-09-28 05:52:39 -05:00
url : string ;
name : string ;
index : string ;
timeField : string ;
esVersion : number ;
interval : string ;
2020-07-08 04:05:20 -05:00
maxConcurrentShardRequests? : number ;
2017-09-28 05:52:39 -05:00
queryBuilder : ElasticQueryBuilder ;
indexPattern : IndexPattern ;
2019-06-24 15:15:03 -05:00
logMessageField? : string ;
logLevelField? : string ;
2019-12-11 10:40:56 -06:00
dataLinks : DataLinkConfig [ ] ;
2020-09-08 05:34:11 -05:00
languageProvider : LanguageProvider ;
2017-09-28 05:52:39 -05:00
2019-06-24 15:15:03 -05:00
constructor (
instanceSettings : DataSourceInstanceSettings < ElasticsearchOptions > ,
2020-10-01 12:51:23 -05:00
private readonly templateSrv : TemplateSrv = getTemplateSrv ( ) ,
private readonly timeSrv : TimeSrv = getTimeSrv ( )
2019-06-24 15:15:03 -05:00
) {
super ( instanceSettings ) ;
2017-09-28 05:52:39 -05:00
this . basicAuth = instanceSettings . basicAuth ;
this . withCredentials = instanceSettings . withCredentials ;
2020-07-08 04:05:20 -05:00
this . url = instanceSettings . url ! ;
2017-09-28 05:52:39 -05:00
this . name = instanceSettings . name ;
2020-07-08 04:05:20 -05:00
this . index = instanceSettings . database ? ? '' ;
2019-06-24 15:15:03 -05:00
const settingsData = instanceSettings . jsonData || ( { } as ElasticsearchOptions ) ;
this . timeField = settingsData . timeField ;
this . esVersion = settingsData . esVersion ;
this . indexPattern = new IndexPattern ( this . index , settingsData . interval ) ;
this . interval = settingsData . timeInterval ;
this . maxConcurrentShardRequests = settingsData . maxConcurrentShardRequests ;
2017-09-28 05:52:39 -05:00
this . queryBuilder = new ElasticQueryBuilder ( {
timeField : this.timeField ,
2017-12-20 05:33:33 -06:00
esVersion : this.esVersion ,
2017-09-28 05:52:39 -05:00
} ) ;
2019-06-24 15:15:03 -05:00
this . logMessageField = settingsData . logMessageField || '' ;
this . logLevelField = settingsData . logLevelField || '' ;
2019-12-11 10:40:56 -06:00
this . dataLinks = settingsData . dataLinks || [ ] ;
2019-06-24 15:15:03 -05:00
if ( this . logMessageField === '' ) {
2020-07-08 04:05:20 -05:00
this . logMessageField = undefined ;
2019-06-24 15:15:03 -05:00
}
if ( this . logLevelField === '' ) {
2020-07-08 04:05:20 -05:00
this . logLevelField = undefined ;
2019-06-24 15:15:03 -05:00
}
2020-09-08 05:34:11 -05:00
this . languageProvider = new LanguageProvider ( this ) ;
2017-09-28 05:52:39 -05:00
}
2019-07-11 10:05:45 -05:00
private request ( method : string , url : string , data? : undefined ) {
2018-08-29 07:27:29 -05:00
const options : any = {
2017-12-20 05:33:33 -06:00
url : this.url + '/' + url ,
2017-09-28 05:52:39 -05:00
method : method ,
2017-12-20 05:33:33 -06:00
data : data ,
2017-09-28 05:52:39 -05:00
} ;
if ( this . basicAuth || this . withCredentials ) {
options . withCredentials = true ;
}
if ( this . basicAuth ) {
options . headers = {
2017-12-20 05:33:33 -06:00
Authorization : this.basicAuth ,
2017-09-28 05:52:39 -05:00
} ;
}
2020-06-09 10:32:47 -05:00
return getBackendSrv ( )
. datasourceRequest ( options )
. catch ( ( err : any ) = > {
if ( err . data && err . data . error ) {
throw {
message : 'Elasticsearch error: ' + err . data . error . reason ,
error : err.data.error ,
} ;
}
throw err ;
} ) ;
2017-09-28 05:52:39 -05:00
}
2020-09-08 05:34:11 -05:00
async importQueries ( queries : DataQuery [ ] , originMeta : PluginMeta ) : Promise < ElasticsearchQuery [ ] > {
return this . languageProvider . importQueries ( queries , originMeta . id ) ;
}
2020-03-02 03:45:31 -06:00
/ * *
* Sends a GET request to the specified url on the newest matching and available index .
*
* When multiple indices span the provided time range , the request is sent starting from the newest index ,
* and then going backwards until an index is found .
*
* @param url the url to query the index on , for example ` /_mapping ` .
* /
2019-07-11 10:05:45 -05:00
private get ( url : string ) {
2018-08-29 07:27:29 -05:00
const range = this . timeSrv . timeRange ( ) ;
2018-09-03 04:00:46 -05:00
const indexList = this . indexPattern . getIndexList ( range . from . valueOf ( ) , range . to . valueOf ( ) ) ;
if ( _ . isArray ( indexList ) && indexList . length ) {
2020-03-02 03:45:31 -06:00
return this . requestAllIndices ( indexList , url ) . then ( ( results : any ) = > {
2017-09-28 05:52:39 -05:00
results . data . $ $config = results . config ;
return results . data ;
} ) ;
} else {
2019-07-11 10:05:45 -05:00
return this . request ( 'GET' , this . indexPattern . getIndexForToday ( ) + url ) . then ( ( results : any ) = > {
2017-09-28 05:52:39 -05:00
results . data . $ $config = results . config ;
return results . data ;
} ) ;
}
}
2020-03-02 03:45:31 -06:00
private async requestAllIndices ( indexList : string [ ] , url : string ) : Promise < any > {
const maxTraversals = 7 ; // do not go beyond one week (for a daily pattern)
const listLen = indexList . length ;
for ( let i = 0 ; i < Math . min ( listLen , maxTraversals ) ; i ++ ) {
try {
return await this . request ( 'GET' , indexList [ listLen - i - 1 ] + url ) ;
} catch ( err ) {
if ( err . status !== 404 || i === maxTraversals - 1 ) {
throw err ;
}
}
}
}
2019-07-11 10:05:45 -05:00
private post ( url : string , data : any ) {
2020-06-09 10:32:47 -05:00
return this . request ( 'POST' , url , data ) . then ( ( results : any ) = > {
results . data . $ $config = results . config ;
return results . data ;
} ) ;
2017-09-28 05:52:39 -05:00
}
2020-01-21 03:08:07 -06:00
annotationQuery ( options : any ) : Promise < any > {
2018-08-29 07:27:29 -05:00
const annotation = options . annotation ;
const timeField = annotation . timeField || '@timestamp' ;
2019-10-15 06:16:08 -05:00
const timeEndField = annotation . timeEndField || null ;
2018-08-29 07:27:29 -05:00
const queryString = annotation . query || '*' ;
const tagsField = annotation . tagsField || 'tags' ;
const textField = annotation . textField || null ;
2017-09-28 05:52:39 -05:00
2019-10-15 06:16:08 -05:00
const dateRanges = [ ] ;
const rangeStart : any = { } ;
rangeStart [ timeField ] = {
2017-09-28 05:52:39 -05:00
from : options . range . from . valueOf ( ) ,
to : options.range.to.valueOf ( ) ,
2017-12-20 05:33:33 -06:00
format : 'epoch_millis' ,
2017-09-28 05:52:39 -05:00
} ;
2019-10-15 06:16:08 -05:00
dateRanges . push ( { range : rangeStart } ) ;
if ( timeEndField ) {
const rangeEnd : any = { } ;
rangeEnd [ timeEndField ] = {
from : options . range . from . valueOf ( ) ,
to : options.range.to.valueOf ( ) ,
format : 'epoch_millis' ,
} ;
dateRanges . push ( { range : rangeEnd } ) ;
}
2017-09-28 05:52:39 -05:00
2018-08-29 07:27:29 -05:00
const queryInterpolated = this . templateSrv . replace ( queryString , { } , 'lucene' ) ;
const query = {
2017-12-19 09:06:54 -06:00
bool : {
filter : [
2019-10-15 06:16:08 -05:00
{
bool : {
should : dateRanges ,
minimum_should_match : 1 ,
} ,
} ,
2017-09-28 05:52:39 -05:00
{
2017-12-19 09:06:54 -06:00
query_string : {
2017-12-20 05:33:33 -06:00
query : queryInterpolated ,
} ,
} ,
] ,
} ,
2017-09-28 05:52:39 -05:00
} ;
2019-07-11 10:05:45 -05:00
const data : any = {
query ,
2017-12-20 05:33:33 -06:00
size : 10000 ,
2017-09-28 05:52:39 -05:00
} ;
// fields field not supported on ES 5.x
if ( this . esVersion < 5 ) {
2017-12-20 05:33:33 -06:00
data [ 'fields' ] = [ timeField , '_source' ] ;
2017-09-28 05:52:39 -05:00
}
2018-08-29 07:27:29 -05:00
const header : any = {
2017-12-20 05:33:33 -06:00
search_type : 'query_then_fetch' ,
ignore_unavailable : true ,
2017-12-19 09:06:54 -06:00
} ;
2017-09-28 05:52:39 -05:00
// old elastic annotations had index specified on them
if ( annotation . index ) {
header . index = annotation . index ;
} else {
2017-12-21 01:39:31 -06:00
header . index = this . indexPattern . getIndexList ( options . range . from , options . range . to ) ;
2017-09-28 05:52:39 -05:00
}
2018-08-29 07:27:29 -05:00
const payload = angular . toJson ( header ) + '\n' + angular . toJson ( data ) + '\n' ;
2017-09-28 05:52:39 -05:00
2019-07-11 10:05:45 -05:00
return this . post ( '_msearch' , payload ) . then ( ( res : any ) = > {
2018-08-29 07:27:29 -05:00
const list = [ ] ;
const hits = res . responses [ 0 ] . hits . hits ;
2017-09-28 05:52:39 -05:00
2019-07-11 10:05:45 -05:00
const getFieldFromSource = ( source : any , fieldName : any ) = > {
2017-12-19 09:06:54 -06:00
if ( ! fieldName ) {
return ;
}
2017-09-28 05:52:39 -05:00
2018-08-29 07:27:29 -05:00
const fieldNames = fieldName . split ( '.' ) ;
2018-08-30 02:03:11 -05:00
let fieldValue = source ;
2017-09-28 05:52:39 -05:00
2018-08-30 02:03:11 -05:00
for ( let i = 0 ; i < fieldNames . length ; i ++ ) {
2017-09-28 05:52:39 -05:00
fieldValue = fieldValue [ fieldNames [ i ] ] ;
if ( ! fieldValue ) {
2017-12-20 05:33:33 -06:00
console . log ( 'could not find field in annotation: ' , fieldName ) ;
return '' ;
2017-09-28 05:52:39 -05:00
}
}
return fieldValue ;
} ;
2018-08-30 02:03:11 -05:00
for ( let i = 0 ; i < hits . length ; i ++ ) {
2018-08-29 07:27:29 -05:00
const source = hits [ i ] . _source ;
2018-08-30 02:03:11 -05:00
let time = getFieldFromSource ( source , timeField ) ;
2017-12-20 05:33:33 -06:00
if ( typeof hits [ i ] . fields !== 'undefined' ) {
2018-08-29 07:27:29 -05:00
const fields = hits [ i ] . fields ;
2017-09-28 05:52:39 -05:00
if ( _ . isString ( fields [ timeField ] ) || _ . isNumber ( fields [ timeField ] ) ) {
time = fields [ timeField ] ;
}
}
2019-10-15 06:16:08 -05:00
const event : {
annotation : any ;
time : number ;
timeEnd? : number ;
text : string ;
tags : string | string [ ] ;
} = {
2017-09-28 05:52:39 -05:00
annotation : annotation ,
2019-05-08 06:51:44 -05:00
time : toUtc ( time ) . valueOf ( ) ,
2017-10-07 03:31:39 -05:00
text : getFieldFromSource ( source , textField ) ,
2017-12-20 05:33:33 -06:00
tags : getFieldFromSource ( source , tagsField ) ,
2017-09-28 05:52:39 -05:00
} ;
2019-10-15 06:16:08 -05:00
if ( timeEndField ) {
const timeEnd = getFieldFromSource ( source , timeEndField ) ;
if ( timeEnd ) {
event . timeEnd = toUtc ( timeEnd ) . valueOf ( ) ;
}
}
2017-10-07 03:31:39 -05:00
// legacy support for title tield
if ( annotation . titleField ) {
const title = getFieldFromSource ( source , annotation . titleField ) ;
if ( title ) {
2017-12-20 05:33:33 -06:00
event . text = title + '\n' + event . text ;
2017-10-07 03:31:39 -05:00
}
}
2017-12-20 05:33:33 -06:00
if ( typeof event . tags === 'string' ) {
event . tags = event . tags . split ( ',' ) ;
2017-10-07 03:31:39 -05:00
}
2017-09-28 05:52:39 -05:00
list . push ( event ) ;
}
return list ;
} ) ;
2017-10-07 03:31:39 -05:00
}
2017-09-28 05:52:39 -05:00
2020-11-11 06:56:43 -06:00
private interpolateLuceneQuery ( queryString : string , scopedVars : ScopedVars ) {
// Elasticsearch queryString should always be '*' if empty string
return this . templateSrv . replace ( queryString , scopedVars , 'lucene' ) || '*' ;
}
2020-01-24 02:50:09 -06:00
interpolateVariablesInQueries ( queries : ElasticsearchQuery [ ] , scopedVars : ScopedVars ) : ElasticsearchQuery [ ] {
2019-10-08 10:01:20 -05:00
let expandedQueries = queries ;
if ( queries && queries . length > 0 ) {
expandedQueries = queries . map ( query = > {
const expandedQuery = {
. . . query ,
datasource : this.name ,
2020-11-11 06:56:43 -06:00
query : this.interpolateLuceneQuery ( query . query || '' , scopedVars ) ,
2019-10-08 10:01:20 -05:00
} ;
2020-11-11 06:56:43 -06:00
for ( let bucketAgg of query . bucketAggs || [ ] ) {
if ( bucketAgg . type === 'filters' ) {
for ( let filter of bucketAgg . settings . filters ) {
filter . query = this . interpolateLuceneQuery ( filter . query , scopedVars ) ;
}
}
}
2019-10-08 10:01:20 -05:00
return expandedQuery ;
} ) ;
}
return expandedQueries ;
}
2017-09-28 05:52:39 -05:00
testDatasource() {
// validate that the index exist and has date field
2017-12-20 05:33:33 -06:00
return this . getFields ( { type : 'date' } ) . then (
2019-07-11 10:05:45 -05:00
( dateFields : any ) = > {
2019-04-15 05:11:52 -05:00
const timeField : any = _ . find ( dateFields , { text : this.timeField } ) ;
2017-12-19 09:06:54 -06:00
if ( ! timeField ) {
return {
2017-12-20 05:33:33 -06:00
status : 'error' ,
message : 'No date field named ' + this . timeField + ' found' ,
2017-12-19 09:06:54 -06:00
} ;
}
2017-12-20 05:33:33 -06:00
return { status : 'success' , message : 'Index OK. Time field name OK.' } ;
2018-08-30 03:49:18 -05:00
} ,
2019-07-11 10:05:45 -05:00
( err : any ) = > {
2020-07-10 09:07:04 -05:00
console . error ( err ) ;
2020-10-23 09:44:21 -05:00
if ( err . message ) {
return { status : 'error' , message : err.message } ;
2017-12-19 09:06:54 -06:00
} else {
2017-12-20 05:33:33 -06:00
return { status : 'error' , message : err.status } ;
2017-09-28 05:52:39 -05:00
}
}
2017-12-19 09:06:54 -06:00
) ;
2017-09-28 05:52:39 -05:00
}
2019-07-11 10:05:45 -05:00
getQueryHeader ( searchType : any , timeFrom : any , timeTo : any ) {
2018-09-03 04:00:46 -05:00
const queryHeader : any = {
2017-12-19 09:06:54 -06:00
search_type : searchType ,
ignore_unavailable : true ,
2017-12-20 05:33:33 -06:00
index : this.indexPattern.getIndexList ( timeFrom , timeTo ) ,
2017-12-19 09:06:54 -06:00
} ;
2020-07-08 04:05:20 -05:00
2019-04-25 02:41:13 -05:00
if ( this . esVersion >= 56 && this . esVersion < 70 ) {
2018-09-03 04:00:46 -05:00
queryHeader [ 'max_concurrent_shard_requests' ] = this . maxConcurrentShardRequests ;
2017-11-21 03:56:34 -06:00
}
2020-07-08 04:05:20 -05:00
2018-09-03 04:00:46 -05:00
return angular . toJson ( queryHeader ) ;
2017-09-28 05:52:39 -05:00
}
2019-06-24 15:15:03 -05:00
query ( options : DataQueryRequest < ElasticsearchQuery > ) : Promise < DataQueryResponse > {
2018-08-30 02:03:11 -05:00
let payload = '' ;
2020-11-11 06:56:43 -06:00
const targets = this . interpolateVariablesInQueries ( _ . cloneDeep ( options . targets ) , options . scopedVars ) ;
2019-06-24 15:15:03 -05:00
const sentTargets : ElasticsearchQuery [ ] = [ ] ;
2017-09-28 05:52:39 -05:00
// add global adhoc filters to timeFilter
2018-08-29 07:27:29 -05:00
const adhocFilters = this . templateSrv . getAdhocFilters ( this . name ) ;
2017-09-28 05:52:39 -05:00
2019-03-26 10:15:23 -05:00
for ( const target of targets ) {
2017-12-19 09:06:54 -06:00
if ( target . hide ) {
continue ;
}
2017-09-28 05:52:39 -05:00
2019-06-24 15:15:03 -05:00
let queryObj ;
2019-11-07 06:13:24 -06:00
if ( target . isLogsQuery || queryDef . hasMetricOfType ( target , 'logs' ) ) {
2019-06-24 15:15:03 -05:00
target . bucketAggs = [ queryDef . defaultBucketAgg ( ) ] ;
2020-07-09 09:14:55 -05:00
target . metrics = [ ] ;
2019-11-07 06:13:24 -06:00
// Setting this for metrics queries that are typed as logs
target . isLogsQuery = true ;
2020-11-11 06:56:43 -06:00
queryObj = this . queryBuilder . getLogsQuery ( target , adhocFilters , target . query ) ;
2019-06-24 15:15:03 -05:00
} else {
if ( target . alias ) {
target . alias = this . templateSrv . replace ( target . alias , options . scopedVars , 'lucene' ) ;
}
2020-11-11 06:56:43 -06:00
queryObj = this . queryBuilder . build ( target , adhocFilters , target . query ) ;
2019-06-24 15:15:03 -05:00
}
2018-08-29 07:27:29 -05:00
const esQuery = angular . toJson ( queryObj ) ;
2017-09-28 05:52:39 -05:00
2018-08-29 07:27:29 -05:00
const searchType = queryObj . size === 0 && this . esVersion < 5 ? 'count' : 'query_then_fetch' ;
const header = this . getQueryHeader ( searchType , options . range . from , options . range . to ) ;
2017-12-20 05:33:33 -06:00
payload += header + '\n' ;
2017-12-19 09:06:54 -06:00
2017-12-20 05:33:33 -06:00
payload += esQuery + '\n' ;
2019-06-24 15:15:03 -05:00
2017-09-28 05:52:39 -05:00
sentTargets . push ( target ) ;
}
if ( sentTargets . length === 0 ) {
2019-06-24 15:15:03 -05:00
return Promise . resolve ( { data : [ ] } ) ;
2017-09-28 05:52:39 -05:00
}
2020-02-13 09:00:01 -06:00
// We replace the range here for actual values. We need to replace it together with enclosing "" so that we replace
// it as an integer not as string with digits. This is because elastic will convert the string only if the time
// field is specified as type date (which probably should) but can also be specified as integer (millisecond epoch)
// and then sending string will error out.
payload = payload . replace ( /"\$timeFrom"/g , options . range . from . valueOf ( ) . toString ( ) ) ;
payload = payload . replace ( /"\$timeTo"/g , options . range . to . valueOf ( ) . toString ( ) ) ;
2017-09-28 05:52:39 -05:00
payload = this . templateSrv . replace ( payload , options . scopedVars ) ;
2019-04-25 02:41:13 -05:00
const url = this . getMultiSearchUrl ( ) ;
2019-07-11 10:05:45 -05:00
return this . post ( url , payload ) . then ( ( res : any ) = > {
2019-06-24 15:15:03 -05:00
const er = new ElasticResponse ( sentTargets , res ) ;
2020-07-08 04:05:20 -05:00
2019-06-24 15:15:03 -05:00
if ( sentTargets . some ( target = > target . isLogsQuery ) ) {
2019-12-11 10:40:56 -06:00
const response = er . getLogs ( this . logMessageField , this . logLevelField ) ;
for ( const dataFrame of response . data ) {
2020-07-01 02:45:21 -05:00
enhanceDataFrame ( dataFrame , this . dataLinks ) ;
2019-12-11 10:40:56 -06:00
}
return response ;
2019-06-24 15:15:03 -05:00
}
return er . getTimeSeries ( ) ;
2017-09-28 05:52:39 -05:00
} ) ;
2017-10-07 03:31:39 -05:00
}
2017-09-28 05:52:39 -05:00
2020-09-15 03:14:47 -05:00
isMetadataField ( fieldName : string ) {
return ELASTIC_META_FIELDS . includes ( fieldName ) ;
}
2019-07-11 10:05:45 -05:00
getFields ( query : any ) {
2019-04-25 02:41:13 -05:00
const configuredEsVersion = this . esVersion ;
2019-07-11 10:05:45 -05:00
return this . get ( '/_mapping' ) . then ( ( result : any ) = > {
const typeMap : any = {
2017-12-20 05:33:33 -06:00
float : 'number' ,
double : 'number' ,
integer : 'number' ,
long : 'number' ,
date : 'date' ,
2020-09-17 01:16:58 -05:00
date_nanos : 'date' ,
2017-12-20 05:33:33 -06:00
string : 'string' ,
text : 'string' ,
scaled_float : 'number' ,
nested : 'nested' ,
2017-09-28 05:52:39 -05:00
} ;
2020-09-15 03:14:47 -05:00
const shouldAddField = ( obj : any , key : string , query : any ) = > {
if ( this . isMetadataField ( key ) ) {
2017-09-28 05:52:39 -05:00
return false ;
}
if ( ! query . type ) {
return true ;
}
// equal query type filter, or via typemap translation
return query . type === obj . type || query . type === typeMap [ obj . type ] ;
2020-09-15 03:14:47 -05:00
} ;
2017-09-28 05:52:39 -05:00
// Store subfield names: [system, process, cpu, total] -> system.process.cpu.total
2019-07-11 10:05:45 -05:00
const fieldNameParts : any = [ ] ;
const fields : any = { } ;
2017-09-28 05:52:39 -05:00
2019-07-11 10:05:45 -05:00
function getFieldsRecursively ( obj : any ) {
2018-08-29 07:27:29 -05:00
for ( const key in obj ) {
const subObj = obj [ key ] ;
2017-09-28 05:52:39 -05:00
// Check mapping field for nested fields
if ( _ . isObject ( subObj . properties ) ) {
fieldNameParts . push ( key ) ;
getFieldsRecursively ( subObj . properties ) ;
}
if ( _ . isObject ( subObj . fields ) ) {
fieldNameParts . push ( key ) ;
getFieldsRecursively ( subObj . fields ) ;
}
if ( _ . isString ( subObj . type ) ) {
2018-08-29 07:27:29 -05:00
const fieldName = fieldNameParts . concat ( key ) . join ( '.' ) ;
2017-09-28 05:52:39 -05:00
// Hide meta-fields and check field type
if ( shouldAddField ( subObj , key , query ) ) {
fields [ fieldName ] = {
text : fieldName ,
2017-12-20 05:33:33 -06:00
type : subObj . type ,
2017-09-28 05:52:39 -05:00
} ;
}
}
}
fieldNameParts . pop ( ) ;
}
2018-08-29 07:27:29 -05:00
for ( const indexName in result ) {
const index = result [ indexName ] ;
2017-09-28 05:52:39 -05:00
if ( index && index . mappings ) {
2018-08-29 07:27:29 -05:00
const mappings = index . mappings ;
2019-04-25 02:41:13 -05:00
if ( configuredEsVersion < 70 ) {
for ( const typeName in mappings ) {
const properties = mappings [ typeName ] . properties ;
getFieldsRecursively ( properties ) ;
}
} else {
const properties = mappings . properties ;
2017-09-28 05:52:39 -05:00
getFieldsRecursively ( properties ) ;
}
}
}
// transform to array
2018-09-04 07:27:03 -05:00
return _ . map ( fields , value = > {
2017-09-28 05:52:39 -05:00
return value ;
} ) ;
} ) ;
}
2019-07-11 10:05:45 -05:00
getTerms ( queryDef : any ) {
2018-08-29 07:27:29 -05:00
const range = this . timeSrv . timeRange ( ) ;
const searchType = this . esVersion >= 5 ? 'query_then_fetch' : 'count' ;
const header = this . getQueryHeader ( searchType , range . from , range . to ) ;
2018-08-30 02:03:11 -05:00
let esQuery = angular . toJson ( this . queryBuilder . getTermsQuery ( queryDef ) ) ;
2017-09-28 05:52:39 -05:00
2019-06-24 15:15:03 -05:00
esQuery = esQuery . replace ( /\$timeFrom/g , range . from . valueOf ( ) . toString ( ) ) ;
esQuery = esQuery . replace ( /\$timeTo/g , range . to . valueOf ( ) . toString ( ) ) ;
2017-12-20 05:33:33 -06:00
esQuery = header + '\n' + esQuery + '\n' ;
2017-09-28 05:52:39 -05:00
2019-04-25 02:41:13 -05:00
const url = this . getMultiSearchUrl ( ) ;
2019-07-11 10:05:45 -05:00
return this . post ( url , esQuery ) . then ( ( res : any ) = > {
2017-12-21 01:39:31 -06:00
if ( ! res . responses [ 0 ] . aggregations ) {
return [ ] ;
2017-12-19 09:06:54 -06:00
}
2017-12-21 01:39:31 -06:00
2018-08-29 07:27:29 -05:00
const buckets = res . responses [ 0 ] . aggregations [ '1' ] . buckets ;
2018-09-04 07:27:03 -05:00
return _ . map ( buckets , bucket = > {
2017-12-21 01:39:31 -06:00
return {
text : bucket.key_as_string || bucket . key ,
value : bucket.key ,
} ;
} ) ;
} ) ;
2017-09-28 05:52:39 -05:00
}
2019-04-25 02:41:13 -05:00
getMultiSearchUrl() {
if ( this . esVersion >= 70 && this . maxConcurrentShardRequests ) {
2020-09-23 06:24:46 -05:00
return ` _msearch?max_concurrent_shard_requests= ${ this . maxConcurrentShardRequests } ` ;
2019-04-25 02:41:13 -05:00
}
2020-09-23 06:24:46 -05:00
return '_msearch' ;
2019-04-25 02:41:13 -05:00
}
2019-07-11 10:05:45 -05:00
metricFindQuery ( query : any ) {
2017-09-28 05:52:39 -05:00
query = angular . fromJson ( query ) ;
2020-01-21 03:08:07 -06:00
if ( query ) {
if ( query . find === 'fields' ) {
query . field = this . templateSrv . replace ( query . field , { } , 'lucene' ) ;
return this . getFields ( query ) ;
}
2017-09-28 05:52:39 -05:00
2020-01-21 03:08:07 -06:00
if ( query . find === 'terms' ) {
query . field = this . templateSrv . replace ( query . field , { } , 'lucene' ) ;
query . query = this . templateSrv . replace ( query . query || '*' , { } , 'lucene' ) ;
return this . getTerms ( query ) ;
}
2017-09-28 05:52:39 -05:00
}
2020-01-21 03:08:07 -06:00
return Promise . resolve ( [ ] ) ;
2017-09-28 05:52:39 -05:00
}
getTagKeys() {
return this . getFields ( { } ) ;
}
2019-07-11 10:05:45 -05:00
getTagValues ( options : any ) {
2017-12-20 05:33:33 -06:00
return this . getTerms ( { field : options.key , query : '*' } ) ;
2017-09-28 05:52:39 -05:00
}
2018-05-28 12:45:18 -05:00
2019-07-11 10:05:45 -05:00
targetContainsTemplate ( target : any ) {
2018-05-28 12:45:18 -05:00
if ( this . templateSrv . variableExists ( target . query ) || this . templateSrv . variableExists ( target . alias ) ) {
return true ;
}
2018-08-26 10:14:40 -05:00
for ( const bucketAgg of target . bucketAggs ) {
2018-05-28 12:45:18 -05:00
if ( this . templateSrv . variableExists ( bucketAgg . field ) || this . objectContainsTemplate ( bucketAgg . settings ) ) {
return true ;
}
}
2018-08-26 10:14:40 -05:00
for ( const metric of target . metrics ) {
2018-05-28 12:45:18 -05:00
if (
this . templateSrv . variableExists ( metric . field ) ||
this . objectContainsTemplate ( metric . settings ) ||
this . objectContainsTemplate ( metric . meta )
) {
return true ;
}
}
return false ;
}
2019-07-11 10:05:45 -05:00
private isPrimitive ( obj : any ) {
2018-05-28 12:45:18 -05:00
if ( obj === null || obj === undefined ) {
return true ;
}
if ( [ 'string' , 'number' , 'boolean' ] . some ( type = > type === typeof true ) ) {
return true ;
}
return false ;
}
2019-07-11 10:05:45 -05:00
private objectContainsTemplate ( obj : any ) {
2018-05-28 12:45:18 -05:00
if ( ! obj ) {
return false ;
}
2018-08-26 10:14:40 -05:00
for ( const key of Object . keys ( obj ) ) {
2018-05-28 12:45:18 -05:00
if ( this . isPrimitive ( obj [ key ] ) ) {
if ( this . templateSrv . variableExists ( obj [ key ] ) ) {
return true ;
}
} else if ( Array . isArray ( obj [ key ] ) ) {
2018-08-26 10:14:40 -05:00
for ( const item of obj [ key ] ) {
2018-05-28 12:45:18 -05:00
if ( this . objectContainsTemplate ( item ) ) {
return true ;
}
}
} else {
if ( this . objectContainsTemplate ( obj [ key ] ) ) {
return true ;
}
}
}
return false ;
}
2017-09-28 05:52:39 -05:00
}
2020-07-01 02:45:21 -05:00
/ * *
* Modifies dataframe and adds dataLinks from the config .
* Exported for tests .
* /
export function enhanceDataFrame ( dataFrame : DataFrame , dataLinks : DataLinkConfig [ ] ) {
2020-12-01 12:10:23 -06:00
const dataSourceSrv = getDataSourceSrv ( ) ;
if ( ! dataLinks . length ) {
return ;
}
for ( const field of dataFrame . fields ) {
const dataLinkConfig = dataLinks . find ( dataLink = > field . name && field . name . match ( dataLink . field ) ) ;
if ( ! dataLinkConfig ) {
continue ;
2020-07-01 02:45:21 -05:00
}
2020-12-01 12:10:23 -06:00
let link : DataLink ;
if ( dataLinkConfig . datasourceUid ) {
const dsSettings = dataSourceSrv . getInstanceSettings ( dataLinkConfig . datasourceUid ) ;
link = {
title : '' ,
url : '' ,
internal : {
query : { query : dataLinkConfig.url } ,
datasourceUid : dataLinkConfig.datasourceUid ,
datasourceName : dsSettings?.name ? ? 'Data source not found' ,
} ,
} ;
} else {
link = {
title : '' ,
url : dataLinkConfig.url ,
} ;
}
field . config = field . config || { } ;
field . config . links = [ . . . ( field . config . links || [ ] ) , link ] ;
2020-07-01 02:45:21 -05:00
}
}