2023-04-05 11:10:33 +02:00
import { escape , isString } from 'lodash' ;
2022-04-22 14:33:13 +01:00
2022-08-02 10:15:25 +01:00
import {
deprecationWarning ,
ScopedVars ,
TimeRange ,
AdHocVariableFilter ,
AdHocVariableModel ,
TypedVariableModel ,
2023-03-28 19:22:34 +02:00
ScopedVar ,
2022-08-02 10:15:25 +01:00
} from '@grafana/data' ;
2023-03-28 16:19:27 +02:00
import {
getDataSourceSrv ,
setTemplateSrv ,
TemplateSrv as BaseTemplateSrv ,
VariableInterpolation ,
} from '@grafana/runtime' ;
2023-03-31 09:59:06 +02:00
import { sceneGraph , VariableCustomFormatterFn } from '@grafana/scenes' ;
import { VariableFormatID } from '@grafana/schema' ;
2022-04-22 14:33:13 +01:00
2023-11-13 13:13:27 +01:00
import { getVariablesCompatibility } from '../dashboard-scene/utils/getVariablesCompatibility' ;
2021-12-06 12:44:28 +01:00
import { variableAdapters } from '../variables/adapters' ;
2022-01-17 12:48:26 +01:00
import { ALL_VARIABLE_TEXT , ALL_VARIABLE_VALUE } from '../variables/constants' ;
2022-04-22 14:33:13 +01:00
import { isAdHoc } from '../variables/guard' ;
import { getFilteredVariables , getVariables , getVariableWithName } from '../variables/state/selectors' ;
import { variableRegex } from '../variables/utils' ;
2023-04-05 11:10:33 +02:00
import { getFieldAccessor } from './fieldAccessorCache' ;
2023-03-28 19:22:34 +02:00
import { formatVariableValue } from './formatVariableValue' ;
import { macroRegistry } from './macroRegistry' ;
2017-10-22 07:03:26 +02:00
2023-03-28 16:19:27 +02:00
/ * *
* Internal regex replace function
* /
type ReplaceFunction = ( fullMatch : string , variableName : string , fieldPath : string , format : string ) = > string ;
2020-04-03 09:38:14 +02:00
export interface TemplateSrvDependencies {
getFilteredVariables : typeof getFilteredVariables ;
getVariables : typeof getVariables ;
getVariableWithName : typeof getVariableWithName ;
}
const runtimeDependencies : TemplateSrvDependencies = {
getFilteredVariables ,
getVariables ,
getVariableWithName ,
} ;
2020-04-05 21:44:49 -07:00
export class TemplateSrv implements BaseTemplateSrv {
2020-03-24 16:03:53 +01:00
private _variables : any [ ] ;
2018-10-24 12:06:09 +02:00
private regex = variableRegex ;
2019-07-11 17:05:45 +02:00
private index : any = { } ;
2022-11-16 14:55:31 +01:00
private grafanaVariables = new Map < string , any > ( ) ;
2020-03-10 08:53:41 +01:00
private timeRange? : TimeRange | null = null ;
2023-09-28 16:28:58 +02:00
private _adhocFiltersDeprecationWarningLogged = new Map < string , boolean > ( ) ;
2017-10-22 07:03:26 +02:00
2020-04-03 09:38:14 +02:00
constructor ( private dependencies : TemplateSrvDependencies = runtimeDependencies ) {
2020-03-24 16:03:53 +01:00
this . _variables = [ ] ;
2017-10-22 07:03:26 +02:00
}
2019-07-11 17:05:45 +02:00
init ( variables : any , timeRange? : TimeRange ) {
2020-03-24 16:03:53 +01:00
this . _variables = variables ;
2019-01-29 15:05:28 +01:00
this . timeRange = timeRange ;
2019-01-29 20:49:54 +01:00
this . updateIndex ( ) ;
2017-10-22 07:03:26 +02:00
}
2020-03-24 16:03:53 +01:00
/ * *
* @deprecated : this instance variable should not be used and will be removed in future releases
*
* Use getVariables function instead
* /
2024-01-09 10:00:00 +00:00
get variables ( ) : TypedVariableModel [ ] {
2020-03-24 16:03:53 +01:00
deprecationWarning ( 'template_srv.ts' , 'variables' , 'getVariables' ) ;
return this . getVariables ( ) ;
}
2022-08-02 10:15:25 +01:00
getVariables ( ) : TypedVariableModel [ ] {
2023-11-13 13:13:27 +01:00
// For scenes we have this backward compatiblity translation
if ( window . __grafanaSceneContext ) {
return getVariablesCompatibility ( window . __grafanaSceneContext ) ;
}
2022-08-03 12:38:59 +01:00
return this . dependencies . getVariables ( ) ;
2020-03-24 16:03:53 +01:00
}
2019-01-29 20:49:54 +01:00
updateIndex() {
2024-01-09 10:00:00 +00:00
const existsOrEmpty = ( value : unknown ) = > value || value === '' ;
2017-10-22 07:03:26 +02:00
2020-03-24 16:03:53 +01:00
this . index = this . _variables . reduce ( ( acc , currentValue ) = > {
2018-11-08 16:23:40 +01:00
if ( currentValue . current && ( currentValue . current . isNone || existsOrEmpty ( currentValue . current . value ) ) ) {
2018-10-23 14:05:10 +02:00
acc [ currentValue . name ] = currentValue ;
2017-10-22 07:03:26 +02:00
}
2018-10-23 14:05:10 +02:00
return acc ;
} , { } ) ;
2019-01-29 15:05:28 +01:00
if ( this . timeRange ) {
const from = this . timeRange . from . valueOf ( ) . toString ( ) ;
const to = this . timeRange . to . valueOf ( ) . toString ( ) ;
this . index = {
. . . this . index ,
[ '__from' ] : {
current : { value : from , text : from } ,
} ,
[ '__to' ] : {
current : { value : to , text : to } ,
} ,
} ;
}
}
2019-01-29 20:49:54 +01:00
updateTimeRange ( timeRange : TimeRange ) {
2019-01-29 15:05:28 +01:00
this . timeRange = timeRange ;
2019-01-29 20:49:54 +01:00
this . updateIndex ( ) ;
2017-10-22 07:03:26 +02:00
}
2019-07-11 17:05:45 +02:00
variableInitialized ( variable : any ) {
2017-10-22 07:03:26 +02:00
this . index [ variable . name ] = variable ;
}
2023-09-28 16:28:58 +02:00
/ * *
* @deprecated
* Use filters property on the request ( DataQueryRequest ) or if this is called from
* interpolateVariablesInQueries or applyTemplateVariables it is passed as a new argument
* * /
2023-11-20 20:22:01 +01:00
getAdhocFilters ( datasourceName : string , skipDeprecationWarning? : boolean ) : AdHocVariableFilter [ ] {
2024-05-10 17:28:44 +01:00
let filters : AdHocVariableFilter [ ] = [ ] ;
2021-10-29 10:57:24 -07:00
let ds = getDataSourceSrv ( ) . getInstanceSettings ( datasourceName ) ;
if ( ! ds ) {
return [ ] ;
}
2017-10-22 07:03:26 +02:00
2023-11-20 20:22:01 +01:00
if ( ! skipDeprecationWarning && ! this . _adhocFiltersDeprecationWarningLogged . get ( ds . type ) ) {
2023-09-28 16:28:58 +02:00
if ( process . env . NODE_ENV !== 'test' ) {
deprecationWarning (
` DataSource ${ ds . type } ` ,
'templateSrv.getAdhocFilters' ,
'filters property on the request (DataQueryRequest). Or if this is called from interpolateVariablesInQueries or applyTemplateVariables it is passed as a new argument'
) ;
}
this . _adhocFiltersDeprecationWarningLogged . set ( ds . type , true ) ;
}
2020-03-23 09:00:36 +01:00
for ( const variable of this . getAdHocVariables ( ) ) {
2021-10-29 10:57:24 -07:00
const variableUid = variable . datasource ? . uid ;
2022-03-08 08:56:12 +01:00
if ( variableUid === ds . uid ) {
2020-03-23 09:00:36 +01:00
filters = filters . concat ( variable . filters ) ;
2021-10-29 10:57:24 -07:00
} else if ( variableUid ? . indexOf ( '$' ) === 0 ) {
2023-10-05 11:18:38 +01:00
if ( this . replace ( variableUid ) === ds . uid ) {
2017-10-22 07:03:26 +02:00
filters = filters . concat ( variable . filters ) ;
}
}
}
return filters ;
}
2019-07-11 17:05:45 +02:00
setGrafanaVariable ( name : string , value : any ) {
2022-11-16 14:55:31 +01:00
this . grafanaVariables . set ( name , value ) ;
2017-10-22 07:03:26 +02:00
}
2020-06-25 07:38:55 +02:00
/ * *
* @deprecated : setGlobalVariable function should not be used and will be removed in future releases
*
* Use addVariable action to add variables to Redux instead
* /
2020-01-21 16:06:04 +01:00
setGlobalVariable ( name : string , variable : any ) {
2020-06-25 07:38:55 +02:00
deprecationWarning ( 'template_srv.ts' , 'setGlobalVariable' , '' ) ;
2020-01-21 16:06:04 +01:00
this . index = {
. . . this . index ,
[ name ] : {
current : variable ,
} ,
} ;
}
2019-07-11 17:05:45 +02:00
getVariableName ( expression : string ) {
2017-10-22 07:03:26 +02:00
this . regex . lastIndex = 0 ;
2018-08-26 21:52:57 +02:00
const match = this . regex . exec ( expression ) ;
2017-10-22 07:03:26 +02:00
if ( ! match ) {
return null ;
}
2021-01-20 07:59:48 +01:00
const variableName = match . slice ( 1 ) . find ( ( match ) = > match !== undefined ) ;
2018-10-24 13:27:09 +02:00
return variableName ;
2017-10-22 07:03:26 +02:00
}
2022-02-15 08:53:42 +01:00
containsTemplate ( target : string | undefined ) : boolean {
if ( ! target ) {
return false ;
}
const name = this . getVariableName ( target ) ;
2020-07-20 11:31:51 +02:00
const variable = name && this . getVariableAtIndex ( name ) ;
return variable !== null && variable !== undefined ;
2017-10-22 07:03:26 +02:00
}
2022-02-15 13:22:13 +01:00
variableExists ( expression : string ) : boolean {
deprecationWarning ( 'template_srv.ts' , 'variableExists' , 'containsTemplate' ) ;
return this . containsTemplate ( expression ) ;
}
2019-07-11 17:05:45 +02:00
highlightVariablesAsHtml ( str : string ) {
2021-04-21 08:38:00 +01:00
if ( ! str || ! isString ( str ) ) {
2017-12-19 16:06:54 +01:00
return str ;
}
2017-10-22 07:03:26 +02:00
2021-04-21 08:38:00 +01:00
str = escape ( str ) ;
2023-03-28 16:19:27 +02:00
return this . _replaceWithVariableRegex ( str , undefined , ( match , variableName ) = > {
if ( this . getVariableAtIndex ( variableName ) ) {
2017-12-20 12:33:33 +01:00
return '<span class="template-variable">' + match + '</span>' ;
2017-10-22 07:03:26 +02:00
}
return match ;
} ) ;
}
2019-07-11 17:05:45 +02:00
getAllValue ( variable : any ) {
2017-10-22 07:03:26 +02:00
if ( variable . allValue ) {
return variable . allValue ;
}
2018-08-26 21:52:57 +02:00
const values = [ ] ;
2018-08-30 08:58:43 +02:00
for ( let i = 1 ; i < variable . options . length ; i ++ ) {
2017-10-22 07:03:26 +02:00
values . push ( variable . options [ i ] . value ) ;
}
return values ;
}
2023-03-28 19:22:34 +02:00
private getVariableValue ( scopedVar : ScopedVar , fieldPath : string | undefined ) {
2019-09-13 16:38:21 +02:00
if ( fieldPath ) {
2023-04-05 11:10:33 +02:00
return getFieldAccessor ( fieldPath ) ( scopedVar . value ) ;
2019-09-13 16:38:21 +02:00
}
return scopedVar . value ;
}
2023-03-28 19:22:34 +02:00
private getVariableText ( scopedVar : ScopedVar , value : any ) {
2020-09-14 08:05:51 +02:00
if ( scopedVar . value === value || typeof value !== 'string' ) {
return scopedVar . text ;
}
return value ;
}
2023-03-28 16:19:27 +02:00
replace (
target? : string ,
scopedVars? : ScopedVars ,
2023-03-28 19:22:34 +02:00
format? : string | Function | undefined ,
2023-03-28 16:19:27 +02:00
interpolations? : VariableInterpolation [ ]
) : string {
2023-10-04 13:21:01 +02:00
// Scenes compatability (primary method) is via SceneObject inside scopedVars. This way we get a much more accurate "local" scope for the evaluation
2023-01-16 18:01:31 +01:00
if ( scopedVars && scopedVars . __sceneObject ) {
2022-12-12 04:01:27 -08:00
return sceneGraph . interpolate (
scopedVars . __sceneObject . value ,
target ,
scopedVars ,
2024-04-26 16:44:36 +01:00
format as string | VariableCustomFormatterFn | undefined ,
interpolations
2022-12-12 04:01:27 -08:00
) ;
}
2023-10-04 13:21:01 +02:00
// Scenes compatability: (secondary method) is using the current active scene as the scope for evaluation.
if ( window . __grafanaSceneContext && window . __grafanaSceneContext . isActive ) {
return sceneGraph . interpolate (
window . __grafanaSceneContext ,
target ,
scopedVars ,
2024-04-26 16:44:36 +01:00
format as string | VariableCustomFormatterFn | undefined ,
interpolations
2023-10-04 13:21:01 +02:00
) ;
}
2017-12-19 16:06:54 +01:00
if ( ! target ) {
2020-07-08 11:05:20 +02:00
return target ? ? '' ;
2017-12-19 16:06:54 +01:00
}
2017-10-22 07:03:26 +02:00
this . regex . lastIndex = 0 ;
2023-03-28 16:19:27 +02:00
return this . _replaceWithVariableRegex ( target , format , ( match , variableName , fieldPath , fmt ) = > {
const value = this . _evaluateVariableExpression ( match , variableName , fieldPath , fmt , scopedVars ) ;
2019-09-13 16:38:21 +02:00
2023-03-28 16:19:27 +02:00
// If we get passed this interpolations map we will also record all the expressions that were replaced
if ( interpolations ) {
interpolations . push ( { match , variableName , fieldPath , format : fmt , value , found : value !== match } ) ;
}
2020-09-14 08:05:51 +02:00
2023-03-28 16:19:27 +02:00
return value ;
} ) ;
}
2023-03-22 18:56:18 +01:00
2023-03-28 16:19:27 +02:00
private _evaluateVariableExpression (
match : string ,
variableName : string ,
fieldPath : string ,
2023-03-28 19:22:34 +02:00
format : string | VariableCustomFormatterFn | undefined ,
2023-03-28 16:19:27 +02:00
scopedVars : ScopedVars | undefined
) {
const variable = this . getVariableAtIndex ( variableName ) ;
2023-03-28 19:22:34 +02:00
const scopedVar = scopedVars ? . [ variableName ] ;
2023-03-28 16:19:27 +02:00
2023-03-28 19:22:34 +02:00
if ( scopedVar ) {
const value = this . getVariableValue ( scopedVar , fieldPath ) ;
const text = this . getVariableText ( scopedVar , value ) ;
2023-03-28 16:19:27 +02:00
if ( value !== null && value !== undefined ) {
2023-03-28 19:22:34 +02:00
return formatVariableValue ( value , format , variable , text ) ;
2017-10-22 07:03:26 +02:00
}
2023-03-28 16:19:27 +02:00
}
2017-10-22 07:03:26 +02:00
2023-03-28 16:19:27 +02:00
if ( ! variable ) {
2023-04-05 11:10:33 +02:00
const macro = macroRegistry [ variableName ] ;
if ( macro ) {
return macro ( match , fieldPath , scopedVars , format ) ;
2023-03-28 19:22:34 +02:00
}
2023-03-28 16:19:27 +02:00
return match ;
}
2021-09-23 11:56:33 +02:00
2023-03-31 09:59:06 +02:00
if ( format === VariableFormatID . QueryParam || isAdHoc ( variable ) ) {
2023-03-28 16:19:27 +02:00
const value = variableAdapters . get ( variable . type ) . getValueForUrl ( variable ) ;
const text = isAdHoc ( variable ) ? variable.id : variable.current.text ;
2021-09-23 11:56:33 +02:00
2023-03-28 19:22:34 +02:00
return formatVariableValue ( value , format , variable , text ) ;
2023-03-28 16:19:27 +02:00
}
2017-10-22 07:03:26 +02:00
2023-03-28 16:19:27 +02:00
const systemValue = this . grafanaVariables . get ( variable . current . value ) ;
if ( systemValue ) {
2023-03-28 19:22:34 +02:00
return formatVariableValue ( systemValue , format , variable ) ;
2023-03-28 16:19:27 +02:00
}
2020-09-14 08:05:51 +02:00
2023-03-28 16:19:27 +02:00
let value = variable . current . value ;
let text = variable . current . text ;
if ( this . isAllValue ( value ) ) {
value = this . getAllValue ( variable ) ;
text = ALL_VARIABLE_TEXT ;
// skip formatting of custom all values unless format set to text or percentencode
2023-03-31 09:59:06 +02:00
if ( variable . allValue && format !== VariableFormatID . Text && format !== VariableFormatID . PercentEncode ) {
2023-03-28 16:19:27 +02:00
return this . replace ( value ) ;
2017-10-22 07:03:26 +02:00
}
2023-03-28 16:19:27 +02:00
}
2017-10-22 07:03:26 +02:00
2023-03-28 16:19:27 +02:00
if ( fieldPath ) {
2023-03-28 19:22:34 +02:00
const fieldValue = this . getVariableValue ( { value , text } , fieldPath ) ;
2023-03-28 16:19:27 +02:00
if ( fieldValue !== null && fieldValue !== undefined ) {
2023-03-28 19:22:34 +02:00
return formatVariableValue ( fieldValue , format , variable , text ) ;
2020-01-21 16:06:04 +01:00
}
2023-03-28 16:19:27 +02:00
}
2020-01-21 16:06:04 +01:00
2023-03-28 19:22:34 +02:00
return formatVariableValue ( value , format , variable , text ) ;
2023-03-28 16:19:27 +02:00
}
/ * *
* Tries to unify the different variable format capture groups into a simpler replacer function
* /
private _replaceWithVariableRegex ( text : string , format : string | Function | undefined , replace : ReplaceFunction ) {
this . regex . lastIndex = 0 ;
return text . replace ( this . regex , ( match , var1 , var2 , fmt2 , var3 , fieldPath , fmt3 ) = > {
const variableName = var1 || var2 || var3 ;
const fmt = fmt2 || fmt3 || format ;
return replace ( match , variableName , fieldPath , fmt ) ;
2017-10-22 07:03:26 +02:00
} ) ;
}
2024-01-09 10:00:00 +00:00
isAllValue ( value : unknown ) {
2021-01-29 09:24:25 +01:00
return value === ALL_VARIABLE_VALUE || ( Array . isArray ( value ) && value [ 0 ] === ALL_VARIABLE_VALUE ) ;
2017-10-22 07:03:26 +02:00
}
2020-02-17 07:25:27 +01:00
replaceWithText ( target : string , scopedVars? : ScopedVars ) {
2020-09-14 08:05:51 +02:00
deprecationWarning ( 'template_srv.ts' , 'replaceWithText()' , 'replace(), and specify the :text format' ) ;
return this . replace ( target , scopedVars , 'text' ) ;
2017-10-22 07:03:26 +02:00
}
2020-07-20 11:31:51 +02:00
private getVariableAtIndex ( name : string ) {
2020-03-10 08:53:41 +01:00
if ( ! name ) {
return ;
}
2020-06-04 13:44:48 +02:00
if ( ! this . index [ name ] ) {
2020-04-03 09:38:14 +02:00
return this . dependencies . getVariableWithName ( name ) ;
2020-03-10 08:53:41 +01:00
}
return this . index [ name ] ;
2020-07-20 11:31:51 +02:00
}
2020-03-23 09:00:36 +01:00
2021-10-29 10:57:24 -07:00
private getAdHocVariables ( ) : AdHocVariableModel [ ] {
return this . dependencies . getFilteredVariables ( isAdHoc ) as AdHocVariableModel [ ] ;
2020-07-20 11:31:51 +02:00
}
2017-10-22 07:03:26 +02:00
}
2017-12-14 12:31:57 +01:00
2020-04-05 21:44:49 -07:00
// Expose the template srv
const srv = new TemplateSrv ( ) ;
2021-04-01 06:33:11 +02:00
2020-04-05 21:44:49 -07:00
setTemplateSrv ( srv ) ;
2021-04-01 06:33:11 +02:00
2020-10-01 18:51:23 +01:00
export const getTemplateSrv = ( ) = > srv ;