feat(templating): progress on template system rewrite #6048

This commit is contained in:
Torkel Ödegaard 2016-09-16 16:50:30 +02:00
parent 7e8b279895
commit 46ebae7304
14 changed files with 413 additions and 455 deletions

View File

@ -10,7 +10,7 @@ function($, _, moment) {
kbn.valueFormats = {};
kbn.regexEscape = function(value) {
return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&');
return value.replace(/[\\^$*+?.()|[\]{}\/]/g, '\\$&')
};
///// HELPER FUNCTIONS /////

View File

@ -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,
}

View File

@ -0,0 +1,41 @@
///<reference path="../../headers/common.d.ts" />
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;

View File

@ -0,0 +1,56 @@
///<reference path="../../headers/common.d.ts" />
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;

View File

@ -43,6 +43,10 @@ export class IntervalVariable implements Variable {
this.updateAutoValue();
}
}
dependsOn(variableName) {
return false;
}
}
variableConstructorMap['interval'] = IntervalVariable;

View File

@ -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;

View File

@ -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);
});
});
});

View File

@ -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');
});
});
});

View File

@ -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; }

View File

@ -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);
}

View File

@ -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);

View File

@ -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('|');
};

View File

@ -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() {

View File

@ -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');
});
});
});
});