template_srv.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. import { escape, isString, property } from 'lodash';
  2. import { deprecationWarning, ScopedVars, TimeRange } from '@grafana/data';
  3. import { getDataSourceSrv, setTemplateSrv, TemplateSrv as BaseTemplateSrv } from '@grafana/runtime';
  4. import { variableAdapters } from '../variables/adapters';
  5. import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../variables/constants';
  6. import { isAdHoc } from '../variables/guard';
  7. import { getFilteredVariables, getVariables, getVariableWithName } from '../variables/state/selectors';
  8. import { AdHocVariableFilter, AdHocVariableModel, VariableModel } from '../variables/types';
  9. import { variableRegex } from '../variables/utils';
  10. import { FormatOptions, formatRegistry, FormatRegistryID } from './formatRegistry';
  11. interface FieldAccessorCache {
  12. [key: string]: (obj: any) => any;
  13. }
  14. export interface TemplateSrvDependencies {
  15. getFilteredVariables: typeof getFilteredVariables;
  16. getVariables: typeof getVariables;
  17. getVariableWithName: typeof getVariableWithName;
  18. }
  19. const runtimeDependencies: TemplateSrvDependencies = {
  20. getFilteredVariables,
  21. getVariables,
  22. getVariableWithName,
  23. };
  24. export class TemplateSrv implements BaseTemplateSrv {
  25. private _variables: any[];
  26. private regex = variableRegex;
  27. private index: any = {};
  28. private grafanaVariables: any = {};
  29. private timeRange?: TimeRange | null = null;
  30. private fieldAccessorCache: FieldAccessorCache = {};
  31. constructor(private dependencies: TemplateSrvDependencies = runtimeDependencies) {
  32. this._variables = [];
  33. }
  34. init(variables: any, timeRange?: TimeRange) {
  35. this._variables = variables;
  36. this.timeRange = timeRange;
  37. this.updateIndex();
  38. }
  39. /**
  40. * @deprecated: this instance variable should not be used and will be removed in future releases
  41. *
  42. * Use getVariables function instead
  43. */
  44. get variables(): any[] {
  45. deprecationWarning('template_srv.ts', 'variables', 'getVariables');
  46. return this.getVariables();
  47. }
  48. getVariables(): VariableModel[] {
  49. return this.dependencies.getVariables();
  50. }
  51. updateIndex() {
  52. const existsOrEmpty = (value: any) => value || value === '';
  53. this.index = this._variables.reduce((acc, currentValue) => {
  54. if (currentValue.current && (currentValue.current.isNone || existsOrEmpty(currentValue.current.value))) {
  55. acc[currentValue.name] = currentValue;
  56. }
  57. return acc;
  58. }, {});
  59. if (this.timeRange) {
  60. const from = this.timeRange.from.valueOf().toString();
  61. const to = this.timeRange.to.valueOf().toString();
  62. this.index = {
  63. ...this.index,
  64. ['__from']: {
  65. current: { value: from, text: from },
  66. },
  67. ['__to']: {
  68. current: { value: to, text: to },
  69. },
  70. };
  71. }
  72. }
  73. updateTimeRange(timeRange: TimeRange) {
  74. this.timeRange = timeRange;
  75. this.updateIndex();
  76. }
  77. variableInitialized(variable: any) {
  78. this.index[variable.name] = variable;
  79. }
  80. getAdhocFilters(datasourceName: string): AdHocVariableFilter[] {
  81. let filters: any = [];
  82. let ds = getDataSourceSrv().getInstanceSettings(datasourceName);
  83. if (!ds) {
  84. return [];
  85. }
  86. for (const variable of this.getAdHocVariables()) {
  87. const variableUid = variable.datasource?.uid;
  88. if (variableUid === ds.uid) {
  89. filters = filters.concat(variable.filters);
  90. } else if (variableUid?.indexOf('$') === 0) {
  91. if (this.replace(variableUid) === datasourceName) {
  92. filters = filters.concat(variable.filters);
  93. }
  94. }
  95. }
  96. return filters;
  97. }
  98. formatValue(value: any, format: any, variable: any, text?: string): string {
  99. // for some scopedVars there is no variable
  100. variable = variable || {};
  101. if (value === null || value === undefined) {
  102. return '';
  103. }
  104. if (isAdHoc(variable) && format !== FormatRegistryID.queryParam) {
  105. return '';
  106. }
  107. // if it's an object transform value to string
  108. if (!Array.isArray(value) && typeof value === 'object') {
  109. value = `${value}`;
  110. }
  111. if (typeof format === 'function') {
  112. return format(value, variable, this.formatValue);
  113. }
  114. if (!format) {
  115. format = FormatRegistryID.glob;
  116. }
  117. // some formats have arguments that come after ':' character
  118. let args = format.split(':');
  119. if (args.length > 1) {
  120. format = args[0];
  121. args = args.slice(1);
  122. } else {
  123. args = [];
  124. }
  125. let formatItem = formatRegistry.getIfExists(format);
  126. if (!formatItem) {
  127. console.error(`Variable format ${format} not found. Using glob format as fallback.`);
  128. formatItem = formatRegistry.get(FormatRegistryID.glob);
  129. }
  130. const options: FormatOptions = { value, args, text: text ?? value };
  131. return formatItem.formatter(options, variable);
  132. }
  133. setGrafanaVariable(name: string, value: any) {
  134. this.grafanaVariables[name] = value;
  135. }
  136. /**
  137. * @deprecated: setGlobalVariable function should not be used and will be removed in future releases
  138. *
  139. * Use addVariable action to add variables to Redux instead
  140. */
  141. setGlobalVariable(name: string, variable: any) {
  142. deprecationWarning('template_srv.ts', 'setGlobalVariable', '');
  143. this.index = {
  144. ...this.index,
  145. [name]: {
  146. current: variable,
  147. },
  148. };
  149. }
  150. getVariableName(expression: string) {
  151. this.regex.lastIndex = 0;
  152. const match = this.regex.exec(expression);
  153. if (!match) {
  154. return null;
  155. }
  156. const variableName = match.slice(1).find((match) => match !== undefined);
  157. return variableName;
  158. }
  159. containsTemplate(target: string | undefined): boolean {
  160. if (!target) {
  161. return false;
  162. }
  163. const name = this.getVariableName(target);
  164. const variable = name && this.getVariableAtIndex(name);
  165. return variable !== null && variable !== undefined;
  166. }
  167. variableExists(expression: string): boolean {
  168. deprecationWarning('template_srv.ts', 'variableExists', 'containsTemplate');
  169. return this.containsTemplate(expression);
  170. }
  171. highlightVariablesAsHtml(str: string) {
  172. if (!str || !isString(str)) {
  173. return str;
  174. }
  175. str = escape(str);
  176. this.regex.lastIndex = 0;
  177. return str.replace(this.regex, (match, var1, var2, fmt2, var3) => {
  178. if (this.getVariableAtIndex(var1 || var2 || var3)) {
  179. return '<span class="template-variable">' + match + '</span>';
  180. }
  181. return match;
  182. });
  183. }
  184. getAllValue(variable: any) {
  185. if (variable.allValue) {
  186. return variable.allValue;
  187. }
  188. const values = [];
  189. for (let i = 1; i < variable.options.length; i++) {
  190. values.push(variable.options[i].value);
  191. }
  192. return values;
  193. }
  194. private getFieldAccessor(fieldPath: string) {
  195. const accessor = this.fieldAccessorCache[fieldPath];
  196. if (accessor) {
  197. return accessor;
  198. }
  199. return (this.fieldAccessorCache[fieldPath] = property(fieldPath));
  200. }
  201. private getVariableValue(variableName: string, fieldPath: string | undefined, scopedVars: ScopedVars) {
  202. const scopedVar = scopedVars[variableName];
  203. if (!scopedVar) {
  204. return null;
  205. }
  206. if (fieldPath) {
  207. return this.getFieldAccessor(fieldPath)(scopedVar.value);
  208. }
  209. return scopedVar.value;
  210. }
  211. private getVariableText(variableName: string, value: any, scopedVars: ScopedVars) {
  212. const scopedVar = scopedVars[variableName];
  213. if (!scopedVar) {
  214. return null;
  215. }
  216. if (scopedVar.value === value || typeof value !== 'string') {
  217. return scopedVar.text;
  218. }
  219. return value;
  220. }
  221. replace(target?: string, scopedVars?: ScopedVars, format?: string | Function): string {
  222. if (!target) {
  223. return target ?? '';
  224. }
  225. this.regex.lastIndex = 0;
  226. return target.replace(this.regex, (match, var1, var2, fmt2, var3, fieldPath, fmt3) => {
  227. const variableName = var1 || var2 || var3;
  228. const variable = this.getVariableAtIndex(variableName);
  229. const fmt = fmt2 || fmt3 || format;
  230. if (scopedVars) {
  231. const value = this.getVariableValue(variableName, fieldPath, scopedVars);
  232. const text = this.getVariableText(variableName, value, scopedVars);
  233. if (value !== null && value !== undefined) {
  234. return this.formatValue(value, fmt, variable, text);
  235. }
  236. }
  237. if (!variable) {
  238. return match;
  239. }
  240. if (fmt === FormatRegistryID.queryParam || isAdHoc(variable)) {
  241. const value = variableAdapters.get(variable.type).getValueForUrl(variable);
  242. const text = isAdHoc(variable) ? variable.id : variable.current.text;
  243. return this.formatValue(value, fmt, variable, text);
  244. }
  245. const systemValue = this.grafanaVariables[variable.current.value];
  246. if (systemValue) {
  247. return this.formatValue(systemValue, fmt, variable);
  248. }
  249. let value = variable.current.value;
  250. let text = variable.current.text;
  251. if (this.isAllValue(value)) {
  252. value = this.getAllValue(variable);
  253. text = ALL_VARIABLE_TEXT;
  254. // skip formatting of custom all values
  255. if (variable.allValue && fmt !== FormatRegistryID.text) {
  256. return this.replace(value);
  257. }
  258. }
  259. if (fieldPath) {
  260. const fieldValue = this.getVariableValue(variableName, fieldPath, {
  261. [variableName]: { value, text },
  262. });
  263. if (fieldValue !== null && fieldValue !== undefined) {
  264. return this.formatValue(fieldValue, fmt, variable, text);
  265. }
  266. }
  267. const res = this.formatValue(value, fmt, variable, text);
  268. return res;
  269. });
  270. }
  271. isAllValue(value: any) {
  272. return value === ALL_VARIABLE_VALUE || (Array.isArray(value) && value[0] === ALL_VARIABLE_VALUE);
  273. }
  274. replaceWithText(target: string, scopedVars?: ScopedVars) {
  275. deprecationWarning('template_srv.ts', 'replaceWithText()', 'replace(), and specify the :text format');
  276. return this.replace(target, scopedVars, 'text');
  277. }
  278. private getVariableAtIndex(name: string) {
  279. if (!name) {
  280. return;
  281. }
  282. if (!this.index[name]) {
  283. return this.dependencies.getVariableWithName(name);
  284. }
  285. return this.index[name];
  286. }
  287. private getAdHocVariables(): AdHocVariableModel[] {
  288. return this.dependencies.getFilteredVariables(isAdHoc) as AdHocVariableModel[];
  289. }
  290. }
  291. // Expose the template srv
  292. const srv = new TemplateSrv();
  293. setTemplateSrv(srv);
  294. export const getTemplateSrv = () => srv;