diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js index 00c0cb3e127..4b7281c55e2 100644 --- a/public/app/core/utils/kbn.js +++ b/public/app/core/utils/kbn.js @@ -10,7 +10,7 @@ function($, _, moment) { kbn.valueFormats = {}; kbn.regexEscape = function(value) { - return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&'); + return value.replace(/[\\^$*+?.()|[\]{}\/]/g, '\\$&') }; ///// HELPER FUNCTIONS ///// diff --git a/public/app/features/templating/all.ts b/public/app/features/templating/all.ts index 208212c4034..d72a76f5a65 100644 --- a/public/app/features/templating/all.ts +++ b/public/app/features/templating/all.ts @@ -5,9 +5,13 @@ import './editorCtrl'; import {VariableSrv} from './variable_srv'; import {IntervalVariable} from './interval_variable'; import {QueryVariable} from './query_variable'; +import {DatasourceVariable} from './datasource_variable'; +import {CustomVariable} from './custom_variable'; export { VariableSrv, IntervalVariable, QueryVariable, + DatasourceVariable, + CustomVariable, } diff --git a/public/app/features/templating/custom_variable.ts b/public/app/features/templating/custom_variable.ts new file mode 100644 index 00000000000..99dd278f899 --- /dev/null +++ b/public/app/features/templating/custom_variable.ts @@ -0,0 +1,41 @@ +/// + +import _ from 'lodash'; +import kbn from 'app/core/utils/kbn'; +import {Variable} from './variable'; +import {VariableSrv, variableConstructorMap} from './variable_srv'; + +export class CustomVariable implements Variable { + query: string; + options: any; + includeAll: boolean; + + /** @ngInject */ + constructor(private model, private timeSrv, private templateSrv) { + _.extend(this, model); + } + + setValue(option) { + } + + updateOptions() { + // extract options in comma separated string + this.options = _.map(this.query.split(/[,]+/), function(text) { + return { text: text.trim(), value: text.trim() }; + }); + + if (this.includeAll) { + this.addAllOption(); + } + } + + addAllOption() { + this.options.unshift({text: 'All', value: "$__all"}); + } + + dependsOn(variableName) { + return false; + } +} + +variableConstructorMap['custom'] = CustomVariable; diff --git a/public/app/features/templating/datasource_variable.ts b/public/app/features/templating/datasource_variable.ts new file mode 100644 index 00000000000..bd13c43f6e4 --- /dev/null +++ b/public/app/features/templating/datasource_variable.ts @@ -0,0 +1,56 @@ +/// + +import _ from 'lodash'; +import kbn from 'app/core/utils/kbn'; +import {Variable} from './variable'; +import {VariableSrv, variableConstructorMap} from './variable_srv'; + +export class DatasourceVariable implements Variable { + regex: any; + query: string; + options: any; + + /** @ngInject */ + constructor(private model, private datasourceSrv) { + _.extend(this, model); + } + + setValue(option) { + } + + updateOptions() { + var options = []; + var sources = this.datasourceSrv.getMetricSources({skipVariables: true}); + var regex; + + if (this.regex) { + regex = kbn.stringToJsRegex(this.regex); + } + + for (var i = 0; i < sources.length; i++) { + var source = sources[i]; + // must match on type + if (source.meta.id !== this.query) { + continue; + } + + if (regex && !regex.exec(source.name)) { + continue; + } + + options.push({text: source.name, value: source.name}); + } + + if (options.length === 0) { + options.push({text: 'No data sources found', value: ''}); + } + + this.options = options; + } + + dependsOn(variableName) { + return false; + } +} + +variableConstructorMap['datasource'] = DatasourceVariable; diff --git a/public/app/features/templating/interval_variable.ts b/public/app/features/templating/interval_variable.ts index e7177982e07..681b088ed0b 100644 --- a/public/app/features/templating/interval_variable.ts +++ b/public/app/features/templating/interval_variable.ts @@ -43,6 +43,10 @@ export class IntervalVariable implements Variable { this.updateAutoValue(); } } + + dependsOn(variableName) { + return false; + } } variableConstructorMap['interval'] = IntervalVariable; diff --git a/public/app/features/templating/query_variable.ts b/public/app/features/templating/query_variable.ts index 2bcd89460d3..784b401a52a 100644 --- a/public/app/features/templating/query_variable.ts +++ b/public/app/features/templating/query_variable.ts @@ -2,7 +2,7 @@ import _ from 'lodash'; import kbn from 'app/core/utils/kbn'; -import {Variable} from './variable'; +import {Variable, containsVariable} from './variable'; import {VariableSrv, variableConstructorMap} from './variable_srv'; function getNoneOption() { @@ -17,8 +17,9 @@ export class QueryVariable implements Variable { options: any; current: any; includeAll: boolean; + refresh: number; - constructor(private model, private datasourceSrv, private templateSrv, private variableSrv) { + constructor(private model, private datasourceSrv, private templateSrv, private variableSrv, private $q) { _.extend(this, model); } @@ -33,6 +34,23 @@ export class QueryVariable implements Variable { return this.variableSrv.variableUpdated(this); } + setValueFromUrl(urlValue) { + var promise = this.$q.when(); + + if (this.refresh) { + promise = this.updateOptions(); + } + + return promise.then(() => { + var option = _.find(this.options, op => { + return op.text === urlValue || op.value === urlValue; + }); + + option = option || { text: urlValue, value: urlValue }; + return this.setValue(option); + }); + } + updateOptions() { return this.datasourceSrv.get(this.datasource) .then(this.updateOptionsFromMetricFindQuery.bind(this)) @@ -102,24 +120,30 @@ export class QueryVariable implements Variable { var sortType = Math.ceil(sortOrder / 2); var reverseSort = (sortOrder % 2 === 0); + if (sortType === 1) { options = _.sortBy(options, 'text'); } else if (sortType === 2) { options = _.sortBy(options, function(opt) { - var matches = opt.text.match(new RegExp(".*?(\d+).*")); - if (!matches) { - return 0; - } else { - return parseInt(matches[1], 10); - } + var matches = opt.text.match(/.*?(\d+).*/); +if (!matches) { + return 0; +} else { + return parseInt(matches[1], 10); +} }); } + if (reverseSort) { options = options.reverse(); } return options; } + + dependsOn(variableName) { + return containsVariable(this.query, variableName) || containsVariable(this.datasource, variableName); + } } variableConstructorMap['query'] = QueryVariable; diff --git a/public/app/features/templating/specs/variable_specs.ts b/public/app/features/templating/specs/variable_specs.ts new file mode 100644 index 00000000000..a87cd744aaa --- /dev/null +++ b/public/app/features/templating/specs/variable_specs.ts @@ -0,0 +1,36 @@ +import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; + +import {containsVariable} from '../variable'; + +describe('containsVariable', function() { + + describe('when checking if a string contains a variable', function() { + + it('should find it with $var syntax', function() { + var contains = containsVariable('this.$test.filters', 'test'); + expect(contains).to.be(true); + }); + + it('should not find it if only part matches with $var syntax', function() { + var contains = containsVariable('this.$ServerDomain.filters', 'Server'); + expect(contains).to.be(false); + }); + + it('should find it with [[var]] syntax', function() { + var contains = containsVariable('this.[[test]].filters', 'test'); + expect(contains).to.be(true); + }); + + it('should find it when part of segment', function() { + var contains = containsVariable('metrics.$env.$group-*', 'group'); + expect(contains).to.be(true); + }); + + it('should find it its the only thing', function() { + var contains = containsVariable('$env', 'env'); + expect(contains).to.be(true); + }); + }); + +}); + diff --git a/public/app/features/templating/specs/variable_srv_specs.ts b/public/app/features/templating/specs/variable_srv_specs.ts index c5a1ea6f536..f6a7224da42 100644 --- a/public/app/features/templating/specs/variable_srv_specs.ts +++ b/public/app/features/templating/specs/variable_srv_specs.ts @@ -4,7 +4,7 @@ import moment from 'moment'; import helpers from 'test/specs/helpers'; import '../all'; -describe.only('VariableSrv', function() { +describe('VariableSrv', function() { var ctx = new helpers.ControllerTestContext(); beforeEach(angularMocks.module('grafana.core')); @@ -15,11 +15,58 @@ describe.only('VariableSrv', function() { beforeEach(angularMocks.inject(($rootScope, $q, $location, $injector) => { ctx.$q = $q; ctx.$rootScope = $rootScope; + ctx.$location = $location; ctx.variableSrv = $injector.get('variableSrv'); ctx.variableSrv.init({templating: {list: []}}); ctx.$rootScope.$digest(); })); + function describeInitSceneario(desc, fn) { + describe(desc, function() { + var scenario: any = { + urlParams: {}, + setup: setupFn => { + scenario.setupFn = setupFn; + } + }; + + beforeEach(function() { + scenario.setupFn(); + var ds: any = {}; + ds.metricFindQuery = sinon.stub().returns(ctx.$q.when(scenario.queryResult)); + ctx.datasourceSrv.get = sinon.stub().returns(ctx.$q.when(ds)); + ctx.datasourceSrv.getMetricSources = sinon.stub().returns(scenario.metricSources); + + ctx.$location.search = sinon.stub().returns(scenario.urlParams); + + ctx.dashboard = {templating: {list: scenario.variables}}; + ctx.variableSrv.init(ctx.dashboard); + ctx.$rootScope.$digest(); + + scenario.variables = ctx.variableSrv.variables; + }); + + fn(scenario); + }); + } + + describeInitSceneario('when setting simple variable via url', scenario => { + scenario.setup(() => { + scenario.variables = [{ + name: 'apps', + type: 'query', + current: {text: "test", value: "test"}, + options: [{text: "test", value: "test"}] + }]; + scenario.urlParams["var-apps"] = "new"; + }); + + it('should update current value', () => { + expect(scenario.variables[0].current.value).to.be("new"); + expect(scenario.variables[0].current.text).to.be("new"); + }); + }); + function describeUpdateVariable(desc, fn) { describe(desc, function() { var scenario: any = {}; @@ -32,6 +79,8 @@ describe.only('VariableSrv', function() { var ds: any = {}; ds.metricFindQuery = sinon.stub().returns(ctx.$q.when(scenario.queryResult)); ctx.datasourceSrv.get = sinon.stub().returns(ctx.$q.when(ds)); + ctx.datasourceSrv.getMetricSources = sinon.stub().returns(scenario.metricSources); + scenario.variable = ctx.variableSrv.addVariable(scenario.variableModel); ctx.variableSrv.updateOptions(scenario.variable); @@ -274,6 +323,114 @@ describe.only('VariableSrv', function() { }); }); + describeUpdateVariable('without sort', function(scenario) { + scenario.setup(function() { + scenario.variableModel = {type: 'query', query: 'apps.*', name: 'test', sort: 0}; + scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}]; + }); + + it('should return options without sort', function() { + expect(scenario.variable.options[0].text).to.be('bbb2'); + expect(scenario.variable.options[1].text).to.be('aaa10'); + expect(scenario.variable.options[2].text).to.be('ccc3'); + }); + }); + + describeUpdateVariable('with alphabetical sort (asc)', function(scenario) { + scenario.setup(function() { + scenario.variableModel = {type: 'query', query: 'apps.*', name: 'test', sort: 1}; + scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}]; + }); + + it('should return options with alphabetical sort', function() { + expect(scenario.variable.options[0].text).to.be('aaa10'); + expect(scenario.variable.options[1].text).to.be('bbb2'); + expect(scenario.variable.options[2].text).to.be('ccc3'); + }); + }); + + describeUpdateVariable('with alphabetical sort (desc)', function(scenario) { + scenario.setup(function() { + scenario.variableModel = {type: 'query', query: 'apps.*', name: 'test', sort: 2}; + scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}]; + }); + + it('should return options with alphabetical sort', function() { + expect(scenario.variable.options[0].text).to.be('ccc3'); + expect(scenario.variable.options[1].text).to.be('bbb2'); + expect(scenario.variable.options[2].text).to.be('aaa10'); + }); + }); + + describeUpdateVariable('with numerical sort (asc)', function(scenario) { + scenario.setup(function() { + scenario.variableModel = {type: 'query', query: 'apps.*', name: 'test', sort: 3}; + scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}]; + }); + + it('should return options with numerical sort', function() { + expect(scenario.variable.options[0].text).to.be('bbb2'); + expect(scenario.variable.options[1].text).to.be('ccc3'); + expect(scenario.variable.options[2].text).to.be('aaa10'); + }); + }); + + describeUpdateVariable('with numerical sort (desc)', function(scenario) { + scenario.setup(function() { + scenario.variableModel = {type: 'query', query: 'apps.*', name: 'test', sort: 4}; + scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}]; + }); + + it('should return options with numerical sort', function() { + expect(scenario.variable.options[0].text).to.be('aaa10'); + expect(scenario.variable.options[1].text).to.be('ccc3'); + expect(scenario.variable.options[2].text).to.be('bbb2'); + }); + }); + + // + // datasource variable update + // + describeUpdateVariable('datasource variable with regex filter', function(scenario) { + scenario.setup(function() { + scenario.variableModel = { + type: 'datasource', + query: 'graphite', + name: 'test', + current: {value: 'backend4_pee', text: 'backend4_pee'}, + regex: '/pee$/' + }; + scenario.metricSources = [ + {name: 'backend1', meta: {id: 'influx'}}, + {name: 'backend2_pee', meta: {id: 'graphite'}}, + {name: 'backend3', meta: {id: 'graphite'}}, + {name: 'backend4_pee', meta: {id: 'graphite'}}, + ]; + }); + + it('should set only contain graphite ds and filtered using regex', function() { + expect(scenario.variable.options.length).to.be(2); + expect(scenario.variable.options[0].value).to.be('backend2_pee'); + expect(scenario.variable.options[1].value).to.be('backend4_pee'); + }); + + it('should keep current value if available', function() { + expect(scenario.variable.current.value).to.be('backend4_pee'); + }); + }); + + // + // Custom variable update + // + describeUpdateVariable('update custom variable', function(scenario) { + scenario.setup(function() { + scenario.variableModel = {type: 'custom', query: 'hej, hop, asd', name: 'test'}; + }); + + it('should update options array', function() { + expect(scenario.variable.options.length).to.be(3); + expect(scenario.variable.options[0].text).to.be('hej'); + expect(scenario.variable.options[1].value).to.be('hop'); + }); + }); }); - - diff --git a/public/app/features/templating/templateSrv.js b/public/app/features/templating/templateSrv.js index d6ed0b65877..7615aa7da0f 100644 --- a/public/app/features/templating/templateSrv.js +++ b/public/app/features/templating/templateSrv.js @@ -1,8 +1,9 @@ define([ 'angular', 'lodash', + 'app/core/utils/kbn', ], -function (angular, _) { +function (angular, _, kbn) { 'use strict'; var module = angular.module('grafana.services'); @@ -32,10 +33,6 @@ function (angular, _) { } }; - function regexEscape(value) { - return value.replace(/[\\^$*+?.()|[\]{}\/]/g, '\\$&'); - } - function luceneEscape(value) { return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, "\\$1"); } @@ -61,10 +58,10 @@ function (angular, _) { switch(format) { case "regex": { if (typeof value === 'string') { - return regexEscape(value); + return kbn.regexEscape(value); } - var escapedValues = _.map(value, regexEscape); + var escapedValues = _.map(value, kbn.regexEscape); return '(' + escapedValues.join('|') + ')'; } case "lucene": { @@ -95,17 +92,6 @@ function (angular, _) { return match && (self._index[match[1] || match[2]] !== void 0); }; - this.containsVariable = function(str, variableName) { - if (!str) { - return false; - } - - variableName = regexEscape(variableName); - var findVarRegex = new RegExp('\\$(' + variableName + ')(?:\\W|$)|\\[\\[(' + variableName + ')\\]\\]', 'g'); - var match = findVarRegex.exec(str); - return match !== null; - }; - this.highlightVariablesAsHtml = function(str) { if (!str || !_.isString(str)) { return str; } diff --git a/public/app/features/templating/variable.ts b/public/app/features/templating/variable.ts index fb8b08112d6..cbee8b0b9c1 100644 --- a/public/app/features/templating/variable.ts +++ b/public/app/features/templating/variable.ts @@ -1,5 +1,23 @@ +import kbn from 'app/core/utils/kbn'; + +export function containsVariable(str, variableName) { + if (!str) { + return false; + } + + variableName = kbn.regexEscape(variableName); + var findVarRegex = new RegExp('\\$(' + variableName + ')(?:\\W|$)|\\[\\[(' + variableName + ')\\]\\]', 'g'); + var match = findVarRegex.exec(str); + return match !== null; +} + export interface Variable { setValue(option); + updateOptions(); + dependsOn(variableName); } + + + diff --git a/public/app/features/templating/variable_srv.ts b/public/app/features/templating/variable_srv.ts index e7470bdfa98..f03cd8a576d 100644 --- a/public/app/features/templating/variable_srv.ts +++ b/public/app/features/templating/variable_srv.ts @@ -34,7 +34,52 @@ export class VariableSrv { dashboard.templating.list.map(this.addVariable.bind(this)); this.templateSrv.init(this.variables); - return this.$q.when(); + var queryParams = this.$location.search(); + + for (let variable of this.variables) { + this.variableLock[variable.name] = this.$q.defer(); + } + + var promises = []; + + for (let variable of this.variables) { + promises.push(this.processVariable(variable, queryParams)); + } + + return this.$q.all(this.variables.map(variable => { + return this.processVariable(variable, queryParams); + })); + } + + processVariable(variable, queryParams) { + var dependencies = []; + var lock = this.variableLock[variable.name]; + + for (let otherVariable of this.variables) { + if (variable.dependsOn(otherVariable)) { + dependencies.push(this.variableLock[otherVariable.name].promise); + } + } + + return this.$q.all(dependencies).then(() => { + var urlValue = queryParams['var-' + variable.name]; + if (urlValue !== void 0) { + return variable.setValueFromUrl(urlValue).then(lock.resolve); + } + + if (variable.refresh === 1 || variable.refresh === 2) { + return variable.updateOptions().then(() => { + // if (_.isEmpty(variable.current) && variable.options.length) { + // self.setVariableValue(variable, variable.options[0]); + // } + lock.resolve(); + }); + } + + lock.resolve(); + }).finally(() => { + delete this.variableLock[variable.name]; + }); } addVariable(model) { @@ -60,14 +105,13 @@ export class VariableSrv { return this.$q.when(); } + // cascade updates to variables that use this variable var promises = _.map(this.variables, otherVariable => { if (otherVariable === variable) { return; } - if (this.templateSrv.containsVariable(otherVariable.regex, variable.name) || - this.templateSrv.containsVariable(otherVariable.query, variable.name) || - this.templateSrv.containsVariable(otherVariable.datasource, variable.name)) { + if (otherVariable.dependsOn(variable)) { return this.updateOptions(otherVariable); } }); @@ -101,7 +145,7 @@ export class VariableSrv { validateVariableSelectionState(variable) { if (!variable.current) { - if (!variable.options.length) { return Promise.resolve(); } + if (!variable.options.length) { return this.$q.when(); } return variable.setValue(variable.options[0]); } @@ -129,6 +173,7 @@ export class VariableSrv { } } } + } coreModule.service('variableSrv', VariableSrv); diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index af692bdd799..eedf9c1badd 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -3,7 +3,6 @@ import angular from 'angular'; import _ from 'lodash'; import moment from 'moment'; -import kbn from 'app/core/utils/kbn'; import * as dateMath from 'app/core/utils/datemath'; import PrometheusMetricFindQuery from './metric_find_query'; @@ -41,6 +40,10 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS return backendSrv.datasourceRequest(options); }; + function prometheusSpecialRegexEscape(value) { + return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&'); + } + this.interpolateQueryExpr = function(value, variable, defaultFormatFn) { // if no multi or include all do not regexEscape if (!variable.multi && !variable.includeAll) { @@ -48,10 +51,10 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS } if (typeof value === 'string') { - return kbn.regexEscape(value); + return prometheusSpecialRegexEscape(value); } - var escapedValues = _.map(value, kbn.regexEscape); + var escapedValues = _.map(value, prometheusSpecialRegexEscape); return escapedValues.join('|'); }; diff --git a/public/test/specs/templateSrv-specs.js b/public/test/specs/templateSrv-specs.js index a592cc7a152..3dac01fbdf2 100644 --- a/public/test/specs/templateSrv-specs.js +++ b/public/test/specs/templateSrv-specs.js @@ -177,38 +177,6 @@ define([ var result = _templateSrv.highlightVariablesAsHtml('this $google ok'); expect(result).to.be('this $google ok'); }); - - }); - - describe('when checking if a string contains a variable', function() { - beforeEach(function() { - _templateSrv.init([{ name: 'test', current: { value: 'muuuu' } }]); - }); - - it('should find it with $var syntax', function() { - var contains = _templateSrv.containsVariable('this.$test.filters', 'test'); - expect(contains).to.be(true); - }); - - it('should not find it if only part matches with $var syntax', function() { - var contains = _templateSrv.containsVariable('this.$ServerDomain.filters', 'Server'); - expect(contains).to.be(false); - }); - - it('should find it with [[var]] syntax', function() { - var contains = _templateSrv.containsVariable('this.[[test]].filters', 'test'); - expect(contains).to.be(true); - }); - - it('should find it when part of segment', function() { - var contains = _templateSrv.containsVariable('metrics.$env.$group-*', 'group'); - expect(contains).to.be(true); - }); - - it('should find it its the only thing', function() { - var contains = _templateSrv.containsVariable('$env', 'env'); - expect(contains).to.be(true); - }); }); describe('updateTemplateData with simple value', function() { diff --git a/public/test/specs/templateValuesSrv-specs.js b/public/test/specs/templateValuesSrv-specs.js index 2ddb69a5b4a..1807157ae35 100644 --- a/public/test/specs/templateValuesSrv-specs.js +++ b/public/test/specs/templateValuesSrv-specs.js @@ -1,9 +1,8 @@ define([ '../mocks/dashboard-mock', './helpers', - 'moment', 'app/features/templating/templateValuesSrv' -], function(dashboardMock, helpers, moment) { +], function(dashboardMock, helpers) { 'use strict'; describe('templateValuesSrv', function() { @@ -13,20 +12,6 @@ define([ beforeEach(ctx.providePhase(['datasourceSrv', 'timeSrv', 'templateSrv', '$location'])); beforeEach(ctx.createService('templateValuesSrv')); - describe('update interval variable options', function() { - var variable = { type: 'interval', query: 'auto,1s,2h,5h,1d', name: 'test' }; - - beforeEach(function() { - ctx.service.updateOptions(variable); - }); - - it('should update options array', function() { - expect(variable.options.length).to.be(5); - expect(variable.options[1].text).to.be('1s'); - expect(variable.options[1].value).to.be('1s'); - }); - }); - describe('when template variable is present in url', function() { describe('and setting simple variable', function() { var variable = { @@ -99,375 +84,6 @@ define([ }); }); - function describeUpdateVariable(desc, fn) { - describe(desc, function() { - var scenario = {}; - scenario.setup = function(setupFn) { - scenario.setupFn = setupFn; - }; - beforeEach(function() { - scenario.setupFn(); - var ds = {}; - ds.metricFindQuery = sinon.stub().returns(ctx.$q.when(scenario.queryResult)); - ctx.datasourceSrv.get = sinon.stub().returns(ctx.$q.when(ds)); - ctx.datasourceSrv.getMetricSources = sinon.stub().returns(scenario.metricSources); - - ctx.service.updateOptions(scenario.variable); - ctx.$rootScope.$digest(); - }); - - fn(scenario); - }); - } - - describeUpdateVariable('interval variable without auto', function(scenario) { - scenario.setup(function() { - scenario.variable = { type: 'interval', query: '1s,2h,5h,1d', name: 'test' }; - }); - - it('should update options array', function() { - expect(scenario.variable.options.length).to.be(4); - expect(scenario.variable.options[0].text).to.be('1s'); - expect(scenario.variable.options[0].value).to.be('1s'); - }); - }); - - describeUpdateVariable('query variable with empty current object and refresh', function(scenario) { - scenario.setup(function() { - scenario.variable = { type: 'query', query: '', name: 'test', current: {} }; - scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}]; - }); - - it('should set current value to first option', function() { - expect(scenario.variable.options.length).to.be(2); - expect(scenario.variable.current.value).to.be('backend1'); - }); - }); - - describeUpdateVariable('query variable with multi select and new options does not contain some selected values', function(scenario) { - scenario.setup(function() { - scenario.variable = { - type: 'query', - query: '', - name: 'test', - current: { - value: ['val1', 'val2', 'val3'], - text: 'val1 + val2 + val3' - } - }; - scenario.queryResult = [{text: 'val2'}, {text: 'val3'}]; - }); - - it('should update current value', function() { - expect(scenario.variable.current.value).to.eql(['val2', 'val3']); - expect(scenario.variable.current.text).to.eql('val2 + val3'); - }); - }); - - describeUpdateVariable('query variable with multi select and new options does not contain any selected values', function(scenario) { - scenario.setup(function() { - scenario.variable = { - type: 'query', - query: '', - name: 'test', - current: { - value: ['val1', 'val2', 'val3'], - text: 'val1 + val2 + val3' - } - }; - scenario.queryResult = [{text: 'val5'}, {text: 'val6'}]; - }); - - it('should update current value with first one', function() { - expect(scenario.variable.current.value).to.eql('val5'); - expect(scenario.variable.current.text).to.eql('val5'); - }); - }); - - describeUpdateVariable('query variable with multi select and $__all selected', function(scenario) { - scenario.setup(function() { - scenario.variable = { - type: 'query', - query: '', - name: 'test', - includeAll: true, - current: { - value: ['$__all'], - text: 'All' - } - }; - scenario.queryResult = [{text: 'val5'}, {text: 'val6'}]; - }); - - it('should keep current All value', function() { - expect(scenario.variable.current.value).to.eql(['$__all']); - expect(scenario.variable.current.text).to.eql('All'); - }); - }); - - describeUpdateVariable('query variable with numeric results', function(scenario) { - scenario.setup(function() { - scenario.variable = { type: 'query', query: '', name: 'test', current: {} }; - scenario.queryResult = [{text: 12, value: 12}]; - }); - - it('should set current value to first option', function() { - expect(scenario.variable.current.value).to.be('12'); - expect(scenario.variable.options[0].value).to.be('12'); - expect(scenario.variable.options[0].text).to.be('12'); - }); - }); - - describeUpdateVariable('interval variable without auto', function(scenario) { - scenario.setup(function() { - scenario.variable = { type: 'interval', query: '1s,2h,5h,1d', name: 'test' }; - }); - - it('should update options array', function() { - expect(scenario.variable.options.length).to.be(4); - expect(scenario.variable.options[0].text).to.be('1s'); - expect(scenario.variable.options[0].value).to.be('1s'); - }); - }); - - describeUpdateVariable('interval variable with auto', function(scenario) { - scenario.setup(function() { - scenario.variable = { type: 'interval', query: '1s,2h,5h,1d', name: 'test', auto: true, auto_count: 10 }; - - var range = { - from: moment(new Date()).subtract(7, 'days').toDate(), - to: new Date() - }; - - ctx.timeSrv.timeRange = sinon.stub().returns(range); - ctx.templateSrv.setGrafanaVariable = sinon.spy(); - }); - - it('should update options array', function() { - expect(scenario.variable.options.length).to.be(5); - expect(scenario.variable.options[0].text).to.be('auto'); - expect(scenario.variable.options[0].value).to.be('$__auto_interval'); - }); - - it('should set $__auto_interval', function() { - var call = ctx.templateSrv.setGrafanaVariable.getCall(0); - expect(call.args[0]).to.be('$__auto_interval'); - expect(call.args[1]).to.be('12h'); - }); - }); - - describeUpdateVariable('update custom variable', function(scenario) { - scenario.setup(function() { - scenario.variable = {type: 'custom', query: 'hej, hop, asd', name: 'test'}; - }); - - it('should update options array', function() { - expect(scenario.variable.options.length).to.be(3); - expect(scenario.variable.options[0].text).to.be('hej'); - expect(scenario.variable.options[1].value).to.be('hop'); - }); - - it('should set $__auto_interval', function() { - var call = ctx.templateSrv.setGrafanaVariable.getCall(0); - expect(call.args[0]).to.be('$__auto_interval'); - expect(call.args[1]).to.be('12h'); - }); - }); - - describeUpdateVariable('basic query variable', function(scenario) { - scenario.setup(function() { - scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; - scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}]; - }); - - it('should update options array', function() { - expect(scenario.variable.options.length).to.be(2); - expect(scenario.variable.options[0].text).to.be('backend1'); - expect(scenario.variable.options[0].value).to.be('backend1'); - expect(scenario.variable.options[1].value).to.be('backend2'); - }); - - it('should select first option as value', function() { - expect(scenario.variable.current.value).to.be('backend1'); - }); - }); - - describeUpdateVariable('and existing value still exists in options', function(scenario) { - scenario.setup(function() { - scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; - scenario.variable.current = { value: 'backend2', text: 'backend2'}; - scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}]; - }); - - it('should keep variable value', function() { - expect(scenario.variable.current.text).to.be('backend2'); - }); - }); - - describeUpdateVariable('and regex pattern exists', function(scenario) { - scenario.setup(function() { - scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; - scenario.variable.regex = '/apps.*(backend_[0-9]+)/'; - scenario.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_02.counters.req'}]; - }); - - it('should extract and use match group', function() { - expect(scenario.variable.options[0].value).to.be('backend_01'); - }); - }); - - describeUpdateVariable('and regex pattern exists and no match', function(scenario) { - scenario.setup(function() { - scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; - scenario.variable.regex = '/apps.*(backendasd[0-9]+)/'; - scenario.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_02.counters.req'}]; - }); - - it('should not add non matching items, None option should be added instead', function() { - expect(scenario.variable.options.length).to.be(1); - expect(scenario.variable.options[0].isNone).to.be(true); - }); - }); - - describeUpdateVariable('regex pattern without slashes', function(scenario) { - scenario.setup(function() { - scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; - scenario.variable.regex = 'backend_01'; - scenario.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_02.counters.req'}]; - }); - - it('should return matches options', function() { - expect(scenario.variable.options.length).to.be(1); - }); - }); - - describeUpdateVariable('regex pattern remove duplicates', function(scenario) { - scenario.setup(function() { - scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; - scenario.variable.regex = 'backend_01'; - scenario.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_01.counters.req'}]; - }); - - it('should return matches options', function() { - expect(scenario.variable.options.length).to.be(1); - }); - }); - - describeUpdateVariable('with include All', function(scenario) { - scenario.setup(function() { - scenario.variable = {type: 'query', query: 'apps.*', name: 'test', includeAll: true}; - scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}]; - }); - - it('should add All option', function() { - expect(scenario.variable.options[0].text).to.be('All'); - expect(scenario.variable.options[0].value).to.be('$__all'); - }); - }); - - describeUpdateVariable('with include all and custom value', function(scenario) { - scenario.setup(function() { - scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allValue: '*' }; - scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}]; - }); - - it('should add All option with custom value', function() { - expect(scenario.variable.options[0].value).to.be('$__all'); - }); - }); - - describeUpdateVariable('datasource variable with regex filter', function(scenario) { - scenario.setup(function() { - scenario.variable = { - type: 'datasource', - query: 'graphite', - name: 'test', - current: {value: 'backend4_pee', text: 'backend4_pee'}, - regex: '/pee$/' - }; - scenario.metricSources = [ - {name: 'backend1', meta: {id: 'influx'}}, - {name: 'backend2_pee', meta: {id: 'graphite'}}, - {name: 'backend3', meta: {id: 'graphite'}}, - {name: 'backend4_pee', meta: {id: 'graphite'}}, - ]; - }); - - it('should set only contain graphite ds and filtered using regex', function() { - expect(scenario.variable.options.length).to.be(2); - expect(scenario.variable.options[0].value).to.be('backend2_pee'); - expect(scenario.variable.options[1].value).to.be('backend4_pee'); - }); - - it('should keep current value if available', function() { - expect(scenario.variable.current.value).to.be('backend4_pee'); - }); - }); - - describeUpdateVariable('without sort', function(scenario) { - scenario.setup(function() { - scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 0}; - scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}]; - }); - - it('should return options without sort', function() { - expect(scenario.variable.options[0].text).to.be('bbb2'); - expect(scenario.variable.options[1].text).to.be('aaa10'); - expect(scenario.variable.options[2].text).to.be('ccc3'); - }); - }); - - describeUpdateVariable('with alphabetical sort (asc)', function(scenario) { - scenario.setup(function() { - scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 1}; - scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}]; - }); - - it('should return options with alphabetical sort', function() { - expect(scenario.variable.options[0].text).to.be('aaa10'); - expect(scenario.variable.options[1].text).to.be('bbb2'); - expect(scenario.variable.options[2].text).to.be('ccc3'); - }); - }); - - describeUpdateVariable('with alphabetical sort (desc)', function(scenario) { - scenario.setup(function() { - scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 2}; - scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}]; - }); - - it('should return options with alphabetical sort', function() { - expect(scenario.variable.options[0].text).to.be('ccc3'); - expect(scenario.variable.options[1].text).to.be('bbb2'); - expect(scenario.variable.options[2].text).to.be('aaa10'); - }); - }); - - describeUpdateVariable('with numerical sort (asc)', function(scenario) { - scenario.setup(function() { - scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 3}; - scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}]; - }); - - it('should return options with numerical sort', function() { - expect(scenario.variable.options[0].text).to.be('bbb2'); - expect(scenario.variable.options[1].text).to.be('ccc3'); - expect(scenario.variable.options[2].text).to.be('aaa10'); - }); - }); - - describeUpdateVariable('with numerical sort (desc)', function(scenario) { - scenario.setup(function() { - scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 4}; - scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}]; - }); - - it('should return options with numerical sort', function() { - expect(scenario.variable.options[0].text).to.be('aaa10'); - expect(scenario.variable.options[1].text).to.be('ccc3'); - expect(scenario.variable.options[2].text).to.be('bbb2'); - }); - }); }); });