diff --git a/public/app/features/templating/specs/template_srv.jest.ts b/public/app/features/templating/specs/template_srv.jest.ts index 577669a41dd..37e2f5e4fe5 100644 --- a/public/app/features/templating/specs/template_srv.jest.ts +++ b/public/app/features/templating/specs/template_srv.jest.ts @@ -31,12 +31,40 @@ describe('templateSrv', function() { expect(target).toBe('this.mupp.filters'); }); + it('should replace ${test} with scoped value', function() { + var target = _templateSrv.replace('this.${test}.filters', { + test: { value: 'mupp', text: 'asd' }, + }); + expect(target).toBe('this.mupp.filters'); + }); + + it('should replace ${test:glob} with scoped value', function() { + var target = _templateSrv.replace('this.${test:glob}.filters', { + test: { value: 'mupp', text: 'asd' }, + }); + expect(target).toBe('this.mupp.filters'); + }); + it('should replace $test with scoped text', function() { var target = _templateSrv.replaceWithText('this.$test.filters', { test: { value: 'mupp', text: 'asd' }, }); expect(target).toBe('this.asd.filters'); }); + + it('should replace ${test} with scoped text', function() { + var target = _templateSrv.replaceWithText('this.${test}.filters', { + test: { value: 'mupp', text: 'asd' }, + }); + expect(target).toBe('this.asd.filters'); + }); + + it('should replace ${test:glob} with scoped text', function() { + var target = _templateSrv.replaceWithText('this.${test:glob}.filters', { + test: { value: 'mupp', text: 'asd' }, + }); + expect(target).toBe('this.asd.filters'); + }); }); describe('getAdhocFilters', function() { @@ -79,18 +107,34 @@ describe('templateSrv', function() { ]); }); + it('should replace $test with globbed value', function() { var target = _templateSrv.replace('this.$test.filters', {}, 'glob'); expect(target).toBe('this.{value1,value2}.filters'); }); + it('should replace ${test} with globbed value', function() { + var target = _templateSrv.replace('this.${test}.filters', {}, 'glob'); + expect(target).toBe('this.{value1,value2}.filters'); + }); + + it('should replace ${test:glob} with globbed value', function() { + var target = _templateSrv.replace('this.${test:glob}.filters', {}); + expect(target).toBe('this.{value1,value2}.filters'); + }); + it('should replace $test with piped value', function() { var target = _templateSrv.replace('this=$test', {}, 'pipe'); expect(target).toBe('this=value1|value2'); }); - it('should replace $test with piped value', function() { - var target = _templateSrv.replace('this=$test', {}, 'pipe'); + it('should replace ${test} with piped value', function() { + var target = _templateSrv.replace('this=${test}', {}, 'pipe'); + expect(target).toBe('this=value1|value2'); + }); + + it('should replace ${test:pipe} with piped value', function() { + var target = _templateSrv.replace('this=${test:pipe}', {}); expect(target).toBe('this=value1|value2'); }); }); @@ -111,6 +155,16 @@ describe('templateSrv', function() { var target = _templateSrv.replace('this.$test.filters', {}, 'glob'); expect(target).toBe('this.{value1,value2}.filters'); }); + + it('should replace ${test} with formatted all value', function() { + var target = _templateSrv.replace('this.${test}.filters', {}, 'glob'); + expect(target).toBe('this.{value1,value2}.filters'); + }); + + it('should replace ${test:glob} with formatted all value', function() { + var target = _templateSrv.replace('this.${test:glob}.filters', {}); + expect(target).toBe('this.{value1,value2}.filters'); + }); }); describe('variable with all option and custom value', function() { @@ -131,6 +185,16 @@ describe('templateSrv', function() { expect(target).toBe('this.*.filters'); }); + it('should replace ${test} with formatted all value', function() { + var target = _templateSrv.replace('this.${test}.filters', {}, 'glob'); + expect(target).toBe('this.*.filters'); + }); + + it('should replace ${test:glob} with formatted all value', function() { + var target = _templateSrv.replace('this.${test:glob}.filters', {}); + expect(target).toBe('this.*.filters'); + }); + it('should not escape custom all value', function() { var target = _templateSrv.replace('this.$test', {}, 'regex'); expect(target).toBe('this.*'); @@ -143,6 +207,18 @@ describe('templateSrv', function() { var target = _templateSrv.replace('this:$test', {}, 'lucene'); expect(target).toBe('this:value\\/4'); }); + + it('should properly escape ${test} with lucene escape sequences', function() { + initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'value/4' } }]); + var target = _templateSrv.replace('this:${test}', {}, 'lucene'); + expect(target).toBe('this:value\\/4'); + }); + + it('should properly escape ${test:lucene} with lucene escape sequences', function() { + initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'value/4' } }]); + var target = _templateSrv.replace('this:${test:lucene}', {}); + expect(target).toBe('this:value\\/4'); + }); }); describe('format variable to string values', function() { diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index fa5cabd847a..40f119ea10b 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -8,7 +8,13 @@ function luceneEscape(value) { export class TemplateSrv { variables: any[]; - private regex = /\$(\w+)|\[\[([\s\S]+?)\]\]/g; + /* + * This regex matches 3 types of variable reference with an optional format specifier + * \$(\w+) $var1 + * \[\[([\s\S]+?)(?::(\w+))?\]\] [[var2]] or [[var2:fmt2]] + * \${(\w+)(?::(\w+))?} ${var3} or ${var3:fmt3} + */ + private regex = /\$(\w+)|\[\[([\s\S]+?)(?::(\w+))?\]\]|\${(\w+)(?::(\w+))?}/g; private index = {}; private grafanaVariables = {}; private builtIns = {}; @@ -89,6 +95,9 @@ export class TemplateSrv { } var escapedValues = _.map(value, kbn.regexEscape); + if (escapedValues.length === 1) { + return escapedValues[0]; + } return '(' + escapedValues.join('|') + ')'; } case 'lucene': { @@ -140,8 +149,8 @@ export class TemplateSrv { str = _.escape(str); this.regex.lastIndex = 0; - return str.replace(this.regex, (match, g1, g2) => { - if (this.index[g1 || g2] || this.builtIns[g1 || g2]) { + return str.replace(this.regex, (match, var1, var2, fmt2, var3) => { + if (this.index[var1 || var2 || var3] || this.builtIns[var1 || var2 || var3]) { return '' + match + ''; } return match; @@ -167,11 +176,11 @@ export class TemplateSrv { var variable, systemValue, value; this.regex.lastIndex = 0; - return target.replace(this.regex, (match, g1, g2) => { - variable = this.index[g1 || g2]; - + return target.replace(this.regex, (match, var1, var2, fmt2, var3, fmt3) => { + variable = this.index[var1 || var2 || var3]; + format = fmt2 || fmt3 || format; if (scopedVars) { - value = scopedVars[g1 || g2]; + value = scopedVars[var1 || var2 || var3]; if (value) { return this.formatValue(value.value, format, variable); } @@ -212,15 +221,15 @@ export class TemplateSrv { var variable; this.regex.lastIndex = 0; - return target.replace(this.regex, (match, g1, g2) => { + return target.replace(this.regex, (match, var1, var2, fmt2, var3) => { if (scopedVars) { - var option = scopedVars[g1 || g2]; + var option = scopedVars[var1 || var2 || var3]; if (option) { return option.text; } } - variable = this.index[g1 || g2]; + variable = this.index[var1 || var2 || var3]; if (!variable) { return match; } diff --git a/public/app/plugins/datasource/graphite/query_ctrl.ts b/public/app/plugins/datasource/graphite/query_ctrl.ts index 29ff29b7ae3..0563de61705 100644 --- a/public/app/plugins/datasource/graphite/query_ctrl.ts +++ b/public/app/plugins/datasource/graphite/query_ctrl.ts @@ -348,6 +348,10 @@ export class GraphiteQueryCtrl extends QueryCtrl { let tagKey = tag.key; return this.datasource.getTagValuesAutoComplete(tagExpressions, tagKey, valuePrefix).then(values => { let altValues = _.map(values, 'text'); + // Add template variables as additional values + _.eachRight(this.templateSrv.variables, variable => { + altValues.push('${' + variable.name + ':regex}'); + }); return mapToDropdownOptions(altValues); }); }