diff --git a/public/app/features/templating/formatRegistry.ts b/public/app/features/templating/formatRegistry.ts new file mode 100644 index 00000000000..519ef2d46ca --- /dev/null +++ b/public/app/features/templating/formatRegistry.ts @@ -0,0 +1,212 @@ +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(() => { + 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 (|) 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() + ); + }); +} diff --git a/public/app/features/templating/template_srv.test.ts b/public/app/features/templating/template_srv.test.ts index 5d9ed63ccc8..0f3ba563e5b 100644 --- a/public/app/features/templating/template_srv.test.ts +++ b/public/app/features/templating/template_srv.test.ts @@ -595,11 +595,6 @@ describe('templateSrv', () => { 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', () => { const target = _templateSrv.replace('10 * $__interval_ms', { __interval_ms: { text: '100', value: '100' }, diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index aba469fde18..379b2952370 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -1,16 +1,12 @@ -import kbn from 'app/core/utils/kbn'; 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 { variableRegex } from '../variables/utils'; import { isAdHoc } from '../variables/guard'; import { VariableModel } from '../variables/types'; import { setTemplateSrv, TemplateSrv as BaseTemplateSrv } from '@grafana/runtime'; import { variableAdapters } from '../variables/adapters'; - -function luceneEscape(value: string) { - return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1'); -} +import { formatRegistry } from './formatRegistry'; interface FieldAccessorCache { [key: string]: (obj: any) => any; @@ -33,13 +29,10 @@ export class TemplateSrv implements BaseTemplateSrv { private regex = variableRegex; private index: any = {}; private grafanaVariables: any = {}; - private builtIns: any = {}; private timeRange?: TimeRange | null = null; private fieldAccessorCache: FieldAccessorCache = {}; constructor(private dependencies: TemplateSrvDependencies = runtimeDependencies) { - this.builtIns['__interval'] = { text: '1s', value: '1s' }; - this.builtIns['__interval_ms'] = { text: '100', value: '100' }; this._variables = []; } @@ -49,10 +42,6 @@ export class TemplateSrv implements BaseTemplateSrv { this.updateIndex(); } - getBuiltInIntervalValue() { - return this.builtIns.__interval.value; - } - /** * @deprecated: this instance variable should not be used and will be removed in future releases * @@ -118,34 +107,6 @@ export class TemplateSrv implements BaseTemplateSrv { 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) { // for some scopedVars there is no variable variable = variable || {}; @@ -167,104 +128,12 @@ export class TemplateSrv implements BaseTemplateSrv { args = []; } - switch (format) { - case 'regex': { - if (typeof value === 'string') { - return kbn.regexEscape(value); - } - - const escapedValues = _.map(value, kbn.regexEscape); - 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; - } + const formatItem = formatRegistry.getIfExists(format); + if (!formatItem) { + throw new Error(`Variable format ${format} not found`); } - } - 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); - } + return formatItem.formatter(value, args, variable); } setGrafanaVariable(name: string, value: any) { @@ -310,7 +179,7 @@ export class TemplateSrv implements BaseTemplateSrv { str = _.escape(str); this.regex.lastIndex = 0; 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 '' + match + ''; } return match; @@ -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) { if (!name) { return; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html index 8c18df0dbb2..3cea41dba8a 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html @@ -1,7 +1,4 @@ - +
@@ -143,7 +140,9 @@
- +
@@ -175,10 +174,14 @@ />
- +
- +
@@ -332,17 +335,17 @@
-
- -
+
+ +
@@ -360,141 +363,140 @@
-
-
-
- - - -
-
- -
- -
-
-
-
-
-
-
-
- -
-
- -
-
- - -
-
-
- - -
-
-
-
-
-
-
-
- -
- -
-
-
+
+ + + +
+
+ +
+ +
+
+
+
+
+
+
+
+ +
+
+ +
+
+ + +
+
+
+ -
-
-
- -
-
-
- - -
-
-
-
-
-
-
- -
-
-
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+ + +
+
+
+
+
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts index 3ff8cdcb59e..bd6ccc88f24 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts @@ -499,7 +499,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { generateAutoUnits(timeGrain: string, timeGrains: Array<{ value: string }>) { if (timeGrain === 'auto') { return TimegrainConverter.findClosestTimeGrain( - this.templateSrv.getBuiltInIntervalValue(), + '1m', _.map(timeGrains, o => TimegrainConverter.createKbnUnitFromISO8601Duration(o.value)) || [ '1m', '5m', @@ -580,16 +580,6 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { 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() { if (!this.datasource.appInsightsDatasource.isConfigured()) { return;