2023-03-28 22:04:56 +08:00
import { gte , SemVer } from 'semver' ;
2022-04-22 21:33:13 +08:00
2023-02-04 01:04:12 +08:00
import { isMetricAggregationWithField } from './components/QueryEditor/MetricAggregationsEditor/aggregations' ;
2020-12-04 22:29:40 +08:00
import { metricAggregationConfig } from './components/QueryEditor/MetricAggregationsEditor/utils' ;
2025-07-07 15:23:27 +08:00
import { ElasticsearchDataQuery , MetricAggregation , MetricAggregationWithInlineScript } from './dataquery.gen' ;
2020-12-04 22:29:40 +08:00
export const describeMetric = ( metric : MetricAggregation ) = > {
if ( ! isMetricAggregationWithField ( metric ) ) {
return metricAggregationConfig [ metric . type ] . label ;
}
// TODO: field might be undefined
return ` ${ metricAggregationConfig [ metric . type ] . label } ${ metric . field } ` ;
} ;
/ * *
* Utility function to clean up aggregations settings objects .
* It removes nullish values and empty strings , array and objects
* recursing over nested objects ( not arrays ) .
* @param obj
* /
2022-09-09 23:17:58 +08:00
export const removeEmpty = < T extends {} > ( obj : T ) : Partial < T > = >
2020-12-04 22:29:40 +08:00
Object . entries ( obj ) . reduce ( ( acc , [ key , value ] ) = > {
// Removing nullish values (null & undefined)
if ( value == null ) {
return { . . . acc } ;
}
// Removing empty arrays (This won't recurse the array)
if ( Array . isArray ( value ) && value . length === 0 ) {
return { . . . acc } ;
}
// Removing empty strings
2022-09-09 23:17:58 +08:00
if ( typeof value === 'string' && value . length === 0 ) {
2020-12-04 22:29:40 +08:00
return { . . . acc } ;
}
// Recursing over nested objects
if ( ! Array . isArray ( value ) && typeof value === 'object' ) {
const cleanObj = removeEmpty ( value ) ;
if ( Object . keys ( cleanObj ) . length === 0 ) {
return { . . . acc } ;
}
return { . . . acc , [ key ] : cleanObj } ;
}
return {
. . . acc ,
[ key ] : value ,
} ;
} , { } ) ;
2021-01-15 19:10:16 +08:00
/ * *
* This function converts an order by string to the correct metric id For example ,
* if the user uses the standard deviation extended stat for the order by ,
* the value would be "1[std_deviation]" and this would return "1"
* /
export const convertOrderByToMetricId = ( orderBy : string ) : string | undefined = > {
const metricIdMatches = orderBy . match ( /^(\d+)/ ) ;
return metricIdMatches ? metricIdMatches [ 1 ] : void 0 ;
} ;
2021-03-05 20:48:45 +08:00
/ * * G e t s t h e a c t u a l s c r i p t v a l u e f o r m e t r i c s t h a t s u p p o r t i n l i n e s c r i p t s .
*
* This is needed because the ` script ` is a bit polymorphic .
* when creating a query with Grafana < 7.4 it was stored as :
* ` ` ` json
* {
* "settings" : {
* "script" : {
* "inline" : "value"
* }
* }
* }
* ` ` `
*
* while from 7.4 it ' s stored as
* ` ` ` json
* {
* "settings" : {
* "script" : "value"
* }
* }
* ` ` `
*
* This allows us to access both formats and support both queries created before 7.4 and after .
* /
export const getScriptValue = ( metric : MetricAggregationWithInlineScript ) = >
( typeof metric . settings ? . script === 'object' ? metric.settings?.script?.inline : metric.settings?.script ) || '' ;
2021-05-11 16:44:00 +08:00
2023-03-28 14:59:39 +08:00
export const isSupportedVersion = ( version : SemVer ) : boolean = > {
2023-04-21 19:37:04 +08:00
if ( gte ( version , '7.16.0' ) ) {
2022-05-05 22:16:34 +08:00
return true ;
2022-05-02 23:08:47 +08:00
}
2022-05-05 22:16:34 +08:00
return false ;
2022-05-02 23:08:47 +08:00
} ;
2023-03-28 14:59:39 +08:00
export const unsupportedVersionMessage =
2023-04-21 19:37:04 +08:00
'Support for Elasticsearch versions after their end-of-life (currently versions < 7.16) was removed. Using unsupported version of Elasticsearch may lead to unexpected and incorrect results.' ;
2023-06-22 19:53:05 +08:00
// To be considered a time series query, the last bucked aggregation must be a Date Histogram
2025-07-07 15:23:27 +08:00
export const isTimeSeriesQuery = ( query : ElasticsearchDataQuery ) : boolean = > {
2023-06-22 19:53:05 +08:00
return query ? . bucketAggs ? . slice ( - 1 ) [ 0 ] ? . type === 'date_histogram' ;
} ;
2024-02-09 20:11:08 +08:00
/ *
* This regex matches 3 types of variable reference with an optional format specifier
* There are 6 capture groups that replace will return
* \ $ ( \ w + ) $var1
* \ [ \ [ ( \ w + ? ) ( ? : : ( \ w + ) ) ? \ ] \ ] [ [ var2 ] ] or [ [ var2 :fmt2 ] ]
* \ $ { ( \ w + ) ( ? : \ . ( [ ^ : ^ \ } ] + ) ) ? ( ? : : ( [ ^ \ } ] + ) ) ? } $ { var3 } or $ { var3 . fieldPath } or $ { var3 :fmt3 } ( or $ { var3.fieldPath :fmt3 } but that is not a separate capture group )
* /
export const variableRegex = /\$(\w+)|\[\[(\w+?)(?::(\w+))?\]\]|\${(\w+)(?:\.([^:^\}]+))?(?::([^\}]+))?}/g ;
// Copyright (c) 2014, Hugh Kennedy
// Based on code from https://github.com/hughsk/flat/blob/master/index.js
//
export function flattenObject (
target : Record < string , unknown > ,
opts ? : { delimiter? : string ; maxDepth? : number ; safe? : boolean }
) : Record < string , unknown > {
opts = opts || { } ;
const delimiter = opts . delimiter || '.' ;
let maxDepth = opts . maxDepth || 3 ;
let currentDepth = 1 ;
const output : Record < string , unknown > = { } ;
function step ( object : Record < string , unknown > , prev : string | null ) {
Object . keys ( object ) . forEach ( ( key ) = > {
const value = object [ key ] ;
const isarray = opts ? . safe && Array . isArray ( value ) ;
const type = Object . prototype . toString . call ( value ) ;
const isobject = type === '[object Object]' ;
const newKey = prev ? prev + delimiter + key : key ;
if ( ! opts ? . maxDepth ) {
maxDepth = currentDepth + 1 ;
}
if ( ! isarray && isobject && value && Object . keys ( value ) . length && currentDepth < maxDepth ) {
++ currentDepth ;
return step ( { . . . value } , newKey ) ;
}
output [ newKey ] = value ;
} ) ;
}
step ( target , null ) ;
return output ;
}