Commit cfac1432 by Torkel Ödegaard Committed by GitHub

TemplateSrv: Refactoring out all formats to a formatRegistry (#26607)

parent 0d933b79
import kbn from 'app/core/utils/kbn';
import { Registry, RegistryItem, VariableModel, textUtil, dateTime } from '@grafana/data';
import { map, isArray, replace } from 'lodash';
export interface FormatRegistryItem extends RegistryItem {
formatter(value: any, args: string[], variable: VariableModel): string;
}
export const formatRegistry = new Registry<FormatRegistryItem>(() => {
const formats: FormatRegistryItem[] = [
{
id: 'lucene',
name: 'Lucene',
description: 'Values are lucene escaped and multi-valued variables generate an OR expression',
formatter: value => {
if (typeof value === 'string') {
return luceneEscape(value);
}
if (value instanceof Array && value.length === 0) {
return '__empty__';
}
const quotedValues = map(value, (val: string) => {
return '"' + luceneEscape(val) + '"';
});
return '(' + quotedValues.join(' OR ') + ')';
},
},
{
id: 'regex',
name: 'Regex',
description: 'Values are regex escaped and multi-valued variables generate a (<value>|<value>) expression',
formatter: value => {
if (typeof value === 'string') {
return kbn.regexEscape(value);
}
const escapedValues = map(value, kbn.regexEscape);
if (escapedValues.length === 1) {
return escapedValues[0];
}
return '(' + escapedValues.join('|') + ')';
},
},
{
id: 'pipe',
name: 'Pipe',
description: 'Values are seperated by | character',
formatter: value => {
if (typeof value === 'string') {
return value;
}
return value.join('|');
},
},
{
id: 'distributed',
name: 'Distributed',
description: 'Multiple values are formatted like variable=value',
formatter: (value, args, variable) => {
if (typeof value === 'string') {
return value;
}
value = map(value, (val: any, index: number) => {
if (index !== 0) {
return variable.name + '=' + val;
} else {
return val;
}
});
return value.join(',');
},
},
{
id: 'csv',
name: 'Csv',
description: 'Comma seperated values',
formatter: (value, args, variable) => {
if (isArray(value)) {
return value.join(',');
}
return value;
},
},
{
id: 'html',
name: 'HTML',
description: 'HTML escaping of values',
formatter: (value, args, variable) => {
if (isArray(value)) {
return textUtil.escapeHtml(value.join(', '));
}
return textUtil.escapeHtml(value);
},
},
{
id: 'json',
name: 'JSON',
description: 'JSON stringify valu',
formatter: (value, args, variable) => {
return JSON.stringify(value);
},
},
{
id: 'percentencode',
name: 'Percent encode',
description: 'Useful for url escaping values',
formatter: (value, args, variable) => {
// like glob, but url escaped
if (isArray(value)) {
return encodeURIComponentStrict('{' + value.join(',') + '}');
}
return encodeURIComponentStrict(value);
},
},
{
id: 'singlequote',
name: 'Single quote',
description: 'Single quoted values',
formatter: (value, args, variable) => {
// escape single quotes with backslash
const regExp = new RegExp(`'`, 'g');
if (isArray(value)) {
return map(value, (v: string) => `'${replace(v, regExp, `\\'`)}'`).join(',');
}
return `'${replace(value, regExp, `\\'`)}'`;
},
},
{
id: 'doublequote',
name: 'Double quote',
description: 'Double quoted values',
formatter: (value, args, variable) => {
// escape double quotes with backslash
const regExp = new RegExp('"', 'g');
if (isArray(value)) {
return map(value, (v: string) => `"${replace(v, regExp, '\\"')}"`).join(',');
}
return `"${replace(value, regExp, '\\"')}"`;
},
},
{
id: 'sqlstring',
name: 'SQL string',
description: 'SQL string quoting and commas for use in IN statements and other scenarios',
formatter: (value, args, variable) => {
// escape single quotes by pairing them
const regExp = new RegExp(`'`, 'g');
if (isArray(value)) {
return map(value, v => `'${replace(v, regExp, "''")}'`).join(',');
}
return `'${replace(value, regExp, "''")}'`;
},
},
{
id: 'date',
name: 'Date',
description: 'Format date in different ways',
formatter: (value, args, variable) => {
const arg = args[0] ?? 'iso';
switch (arg) {
case 'ms':
return value;
case 'seconds':
return `${Math.round(parseInt(value, 10)! / 1000)}`;
case 'iso':
return dateTime(parseInt(value, 10)).toISOString();
default:
return dateTime(parseInt(value, 10)).format(arg);
}
},
},
{
id: 'glob',
name: 'Glob',
description: 'Format multi valued variables using glob syntax, example {value1,value2}',
formatter: (value, args, variable) => {
if (isArray(value) && value.length > 1) {
return '{' + value.join(',') + '}';
}
return value;
},
},
];
return formats;
});
function luceneEscape(value: string) {
return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1');
}
/**
* encode string according to RFC 3986; in contrast to encodeURIComponent()
* also the sub-delims "!", "'", "(", ")" and "*" are encoded;
* unicode handling uses UTF-8 as in ECMA-262.
*/
function encodeURIComponentStrict(str: string) {
return encodeURIComponent(str).replace(/[!'()*]/g, c => {
return (
'%' +
c
.charCodeAt(0)
.toString(16)
.toUpperCase()
);
});
}
...@@ -595,11 +595,6 @@ describe('templateSrv', () => { ...@@ -595,11 +595,6 @@ describe('templateSrv', () => {
initTemplateSrv([]); initTemplateSrv([]);
}); });
it('should be possible to fetch value with getBuilInIntervalValue', () => {
const val = _templateSrv.getBuiltInIntervalValue();
expect(val).toBe('1s');
});
it('should replace $__interval_ms with interval milliseconds', () => { it('should replace $__interval_ms with interval milliseconds', () => {
const target = _templateSrv.replace('10 * $__interval_ms', { const target = _templateSrv.replace('10 * $__interval_ms', {
__interval_ms: { text: '100', value: '100' }, __interval_ms: { text: '100', value: '100' },
......
import kbn from 'app/core/utils/kbn';
import _ from 'lodash'; import _ from 'lodash';
import { deprecationWarning, ScopedVars, textUtil, TimeRange, dateTime } from '@grafana/data'; import { deprecationWarning, ScopedVars, TimeRange } from '@grafana/data';
import { getFilteredVariables, getVariables, getVariableWithName } from '../variables/state/selectors'; import { getFilteredVariables, getVariables, getVariableWithName } from '../variables/state/selectors';
import { variableRegex } from '../variables/utils'; import { variableRegex } from '../variables/utils';
import { isAdHoc } from '../variables/guard'; import { isAdHoc } from '../variables/guard';
import { VariableModel } from '../variables/types'; import { VariableModel } from '../variables/types';
import { setTemplateSrv, TemplateSrv as BaseTemplateSrv } from '@grafana/runtime'; import { setTemplateSrv, TemplateSrv as BaseTemplateSrv } from '@grafana/runtime';
import { variableAdapters } from '../variables/adapters'; import { variableAdapters } from '../variables/adapters';
import { formatRegistry } from './formatRegistry';
function luceneEscape(value: string) {
return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1');
}
interface FieldAccessorCache { interface FieldAccessorCache {
[key: string]: (obj: any) => any; [key: string]: (obj: any) => any;
...@@ -33,13 +29,10 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -33,13 +29,10 @@ export class TemplateSrv implements BaseTemplateSrv {
private regex = variableRegex; private regex = variableRegex;
private index: any = {}; private index: any = {};
private grafanaVariables: any = {}; private grafanaVariables: any = {};
private builtIns: any = {};
private timeRange?: TimeRange | null = null; private timeRange?: TimeRange | null = null;
private fieldAccessorCache: FieldAccessorCache = {}; private fieldAccessorCache: FieldAccessorCache = {};
constructor(private dependencies: TemplateSrvDependencies = runtimeDependencies) { constructor(private dependencies: TemplateSrvDependencies = runtimeDependencies) {
this.builtIns['__interval'] = { text: '1s', value: '1s' };
this.builtIns['__interval_ms'] = { text: '100', value: '100' };
this._variables = []; this._variables = [];
} }
...@@ -49,10 +42,6 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -49,10 +42,6 @@ export class TemplateSrv implements BaseTemplateSrv {
this.updateIndex(); this.updateIndex();
} }
getBuiltInIntervalValue() {
return this.builtIns.__interval.value;
}
/** /**
* @deprecated: this instance variable should not be used and will be removed in future releases * @deprecated: this instance variable should not be used and will be removed in future releases
* *
...@@ -118,34 +107,6 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -118,34 +107,6 @@ export class TemplateSrv implements BaseTemplateSrv {
return filters; return filters;
} }
luceneFormat(value: any) {
if (typeof value === 'string') {
return luceneEscape(value);
}
if (value instanceof Array && value.length === 0) {
return '__empty__';
}
const quotedValues = _.map(value, val => {
return '"' + luceneEscape(val) + '"';
});
return '(' + quotedValues.join(' OR ') + ')';
}
// encode string according to RFC 3986; in contrast to encodeURIComponent()
// also the sub-delims "!", "'", "(", ")" and "*" are encoded;
// unicode handling uses UTF-8 as in ECMA-262.
encodeURIComponentStrict(str: string) {
return encodeURIComponent(str).replace(/[!'()*]/g, c => {
return (
'%' +
c
.charCodeAt(0)
.toString(16)
.toUpperCase()
);
});
}
formatValue(value: any, format: any, variable: any) { formatValue(value: any, format: any, variable: any) {
// for some scopedVars there is no variable // for some scopedVars there is no variable
variable = variable || {}; variable = variable || {};
...@@ -167,104 +128,12 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -167,104 +128,12 @@ export class TemplateSrv implements BaseTemplateSrv {
args = []; args = [];
} }
switch (format) { const formatItem = formatRegistry.getIfExists(format);
case 'regex': { if (!formatItem) {
if (typeof value === 'string') { throw new Error(`Variable format ${format} not found`);
return kbn.regexEscape(value);
} }
const escapedValues = _.map(value, kbn.regexEscape); return formatItem.formatter(value, args, variable);
if (escapedValues.length === 1) {
return escapedValues[0];
}
return '(' + escapedValues.join('|') + ')';
}
case 'lucene': {
return this.luceneFormat(value);
}
case 'pipe': {
if (typeof value === 'string') {
return value;
}
return value.join('|');
}
case 'distributed': {
if (typeof value === 'string') {
return value;
}
return this.distributeVariable(value, variable.name);
}
case 'csv': {
if (_.isArray(value)) {
return value.join(',');
}
return value;
}
case 'html': {
if (_.isArray(value)) {
return textUtil.escapeHtml(value.join(', '));
}
return textUtil.escapeHtml(value);
}
case 'json': {
return JSON.stringify(value);
}
case 'percentencode': {
// like glob, but url escaped
if (_.isArray(value)) {
return this.encodeURIComponentStrict('{' + value.join(',') + '}');
}
return this.encodeURIComponentStrict(value);
}
case 'singlequote': {
// escape single quotes with backslash
const regExp = new RegExp(`'`, 'g');
if (_.isArray(value)) {
return _.map(value, v => `'${_.replace(v, regExp, `\\'`)}'`).join(',');
}
return `'${_.replace(value, regExp, `\\'`)}'`;
}
case 'doublequote': {
// escape double quotes with backslash
const regExp = new RegExp('"', 'g');
if (_.isArray(value)) {
return _.map(value, v => `"${_.replace(v, regExp, '\\"')}"`).join(',');
}
return `"${_.replace(value, regExp, '\\"')}"`;
}
case 'sqlstring': {
// escape single quotes by pairing them
const regExp = new RegExp(`'`, 'g');
if (_.isArray(value)) {
return _.map(value, v => `'${_.replace(v, regExp, "''")}'`).join(',');
}
return `'${_.replace(value, regExp, "''")}'`;
}
case 'date': {
return this.formatDate(value, args);
}
case 'glob': {
if (_.isArray(value) && value.length > 1) {
return '{' + value.join(',') + '}';
}
return value;
}
}
}
formatDate(value: any, args: string[]): string {
const arg = args[0] ?? 'iso';
switch (arg) {
case 'ms':
return value;
case 'seconds':
return `${Math.round(parseInt(value, 10)! / 1000)}`;
case 'iso':
return dateTime(parseInt(value, 10)).toISOString();
default:
return dateTime(parseInt(value, 10)).format(arg);
}
} }
setGrafanaVariable(name: string, value: any) { setGrafanaVariable(name: string, value: any) {
...@@ -310,7 +179,7 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -310,7 +179,7 @@ export class TemplateSrv implements BaseTemplateSrv {
str = _.escape(str); str = _.escape(str);
this.regex.lastIndex = 0; this.regex.lastIndex = 0;
return str.replace(this.regex, (match, var1, var2, fmt2, var3) => { return str.replace(this.regex, (match, var1, var2, fmt2, var3) => {
if (this.getVariableAtIndex(var1 || var2 || var3) || this.builtIns[var1 || var2 || var3]) { if (this.getVariableAtIndex(var1 || var2 || var3)) {
return '<span class="template-variable">' + match + '</span>'; return '<span class="template-variable">' + match + '</span>';
} }
return match; return match;
...@@ -448,17 +317,6 @@ export class TemplateSrv implements BaseTemplateSrv { ...@@ -448,17 +317,6 @@ export class TemplateSrv implements BaseTemplateSrv {
}); });
}; };
distributeVariable(value: any, variable: any) {
value = _.map(value, (val: any, index: number) => {
if (index !== 0) {
return variable + '=' + val;
} else {
return val;
}
});
return value.join(',');
}
private getVariableAtIndex(name: string) { private getVariableAtIndex(name: string) {
if (!name) { if (!name) {
return; return;
......
<query-editor-row <query-editor-row query-ctrl="ctrl" can-collapse="false">
query-ctrl="ctrl"
can-collapse="false"
>
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label query-keyword width-9">Service</label> <label class="gf-form-label query-keyword width-9">Service</label>
...@@ -143,7 +140,9 @@ ...@@ -143,7 +140,9 @@
<label class="gf-form-label query-keyword width-9">Dimension</label> <label class="gf-form-label query-keyword width-9">Dimension</label>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<a ng-click="ctrl.azureMonitorAddDimensionFilter()" class="gf-form-label query-part"><icon name="'plus'"></icon></a> <a ng-click="ctrl.azureMonitorAddDimensionFilter()" class="gf-form-label query-part"
><icon name="'plus'"></icon
></a>
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form-label gf-form-label--grow"></div>
...@@ -175,10 +174,14 @@ ...@@ -175,10 +174,14 @@
/> />
</div> </div>
<div class="gf-form"> <div class="gf-form">
<a ng-click="ctrl.azureMonitorRemoveDimensionFilter($index)" class="gf-form-label query-part"><icon name="'minus'"></icon></a> <a ng-click="ctrl.azureMonitorRemoveDimensionFilter($index)" class="gf-form-label query-part"
><icon name="'minus'"></icon
></a>
</div> </div>
<div class="gf-form" ng-if="$last"> <div class="gf-form" ng-if="$last">
<a ng-click="ctrl.azureMonitorAddDimensionFilter()" class="gf-form-label query-part"><icon name="'plus'"></icon></a> <a ng-click="ctrl.azureMonitorAddDimensionFilter()" class="gf-form-label query-part"
><icon name="'plus'"></icon
></a>
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form-label gf-form-label--grow"></div>
...@@ -360,7 +363,6 @@ ...@@ -360,7 +363,6 @@
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form-label gf-form-label--grow"></div>
</div> </div>
</div> </div>
</div> </div>
<div ng-if="ctrl.target.queryType === 'Application Insights'"> <div ng-if="ctrl.target.queryType === 'Application Insights'">
...@@ -396,13 +398,14 @@ ...@@ -396,13 +398,14 @@
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label query-keyword width-9">Group By</label> <label class="gf-form-label query-keyword width-9">Group By</label>
</div> </div>
<div ng-repeat="d in ctrl.target.appInsights.dimension track by $index" <div
ng-repeat="d in ctrl.target.appInsights.dimension track by $index"
class="gf-form" class="gf-form"
ng-click="ctrl.removeGroupBy($index);" ng-click="ctrl.removeGroupBy($index);"
onmouseover="this.style['text-decoration'] = 'line-through';" onmouseover="this.style['text-decoration'] = 'line-through';"
onmouseout="this.style['text-decoration'] = '';"> onmouseout="this.style['text-decoration'] = '';"
<label class="gf-form-label" >
style="cursor: pointer;">{{d}} <icon name="'times'"></icon></label> <label class="gf-form-label" style="cursor: pointer;">{{d}} <icon name="'times'"></icon></label>
</div> </div>
<div> <div>
<gf-form-dropdown <gf-form-dropdown
...@@ -472,8 +475,7 @@ ...@@ -472,8 +475,7 @@
</div> </div>
</div> </div>
<div class="gf-form" ng-hide="ctrl.target.appInsights.timeGrainType !== 'auto'"> <div class="gf-form" ng-hide="ctrl.target.appInsights.timeGrainType !== 'auto'">
<label class="gf-form-label">Auto Interval</label> <label class="gf-form-label">Auto Interval (see query options)</label>
<label class="gf-form-label">{{ctrl.getAppInsightsAutoInterval()}}</label>
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form-label gf-form-label--grow"></div>
......
...@@ -499,7 +499,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -499,7 +499,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
generateAutoUnits(timeGrain: string, timeGrains: Array<{ value: string }>) { generateAutoUnits(timeGrain: string, timeGrains: Array<{ value: string }>) {
if (timeGrain === 'auto') { if (timeGrain === 'auto') {
return TimegrainConverter.findClosestTimeGrain( return TimegrainConverter.findClosestTimeGrain(
this.templateSrv.getBuiltInIntervalValue(), '1m',
_.map(timeGrains, o => TimegrainConverter.createKbnUnitFromISO8601Duration(o.value)) || [ _.map(timeGrains, o => TimegrainConverter.createKbnUnitFromISO8601Duration(o.value)) || [
'1m', '1m',
'5m', '5m',
...@@ -580,16 +580,6 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -580,16 +580,6 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
return this.templateSrv.getVariables().map(t => '$' + t.name); return this.templateSrv.getVariables().map(t => '$' + t.name);
} }
/* Application Insights Section */
getAppInsightsAutoInterval() {
const interval = this.templateSrv.getBuiltInIntervalValue();
if (interval[interval.length - 1] === 's') {
return '1m';
}
return interval;
}
getAppInsightsMetricNames() { getAppInsightsMetricNames() {
if (!this.datasource.appInsightsDatasource.isConfigured()) { if (!this.datasource.appInsightsDatasource.isConfigured()) {
return; return;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment