mirror of https://github.com/grafana/grafana
feat(templating): good progress on new variable update code, #6048
parent
0f4a9f1ed1
commit
7e8b279895
@ -0,0 +1,13 @@ |
||||
import './templateSrv'; |
||||
import './templateValuesSrv'; |
||||
import './editorCtrl'; |
||||
|
||||
import {VariableSrv} from './variable_srv'; |
||||
import {IntervalVariable} from './interval_variable'; |
||||
import {QueryVariable} from './query_variable'; |
||||
|
||||
export { |
||||
VariableSrv, |
||||
IntervalVariable, |
||||
QueryVariable, |
||||
} |
@ -0,0 +1,48 @@ |
||||
///<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 IntervalVariable implements Variable { |
||||
auto_count: number; |
||||
auto_min: number; |
||||
options: any; |
||||
auto: boolean; |
||||
query: string; |
||||
|
||||
/** @ngInject */ |
||||
constructor(private model, private timeSrv, private templateSrv) { |
||||
_.extend(this, model); |
||||
} |
||||
|
||||
setValue(option) { |
||||
if (this.auto) { |
||||
this.updateAutoValue(); |
||||
} |
||||
} |
||||
|
||||
updateAutoValue() { |
||||
// add auto option if missing
|
||||
if (this.options.length && this.options[0].text !== 'auto') { |
||||
this.options.unshift({ text: 'auto', value: '$__auto_interval' }); |
||||
} |
||||
|
||||
var interval = kbn.calculateInterval(this.timeSrv.timeRange(), this.auto_count, (this.auto_min ? ">"+this.auto_min : null)); |
||||
this.templateSrv.setGrafanaVariable('$__auto_interval', interval); |
||||
} |
||||
|
||||
updateOptions() { |
||||
// extract options in comma separated string
|
||||
this.options = _.map(this.query.split(/[,]+/), function(text) { |
||||
return {text: text.trim(), value: text.trim()}; |
||||
}); |
||||
|
||||
if (this.auto) { |
||||
this.updateAutoValue(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
variableConstructorMap['interval'] = IntervalVariable; |
@ -0,0 +1,125 @@ |
||||
///<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'; |
||||
|
||||
function getNoneOption() { |
||||
return { text: 'None', value: '', isNone: true }; |
||||
} |
||||
|
||||
export class QueryVariable implements Variable { |
||||
datasource: any; |
||||
query: any; |
||||
regex: any; |
||||
sort: any; |
||||
options: any; |
||||
current: any; |
||||
includeAll: boolean; |
||||
|
||||
constructor(private model, private datasourceSrv, private templateSrv, private variableSrv) { |
||||
_.extend(this, model); |
||||
} |
||||
|
||||
setValue(option){ |
||||
this.current = _.cloneDeep(option); |
||||
|
||||
if (_.isArray(this.current.text)) { |
||||
this.current.text = this.current.text.join(' + '); |
||||
} |
||||
|
||||
this.variableSrv.selectOptionsForCurrentValue(this); |
||||
return this.variableSrv.variableUpdated(this); |
||||
} |
||||
|
||||
updateOptions() { |
||||
return this.datasourceSrv.get(this.datasource) |
||||
.then(this.updateOptionsFromMetricFindQuery.bind(this)) |
||||
.then(() => { |
||||
this.variableSrv.validateVariableSelectionState(this); |
||||
}); |
||||
} |
||||
|
||||
updateOptionsFromMetricFindQuery(datasource) { |
||||
return datasource.metricFindQuery(this.query).then(results => { |
||||
this.options = this.metricNamesToVariableValues(results); |
||||
if (this.includeAll) { |
||||
this.addAllOption(); |
||||
} |
||||
if (!this.options.length) { |
||||
this.options.push(getNoneOption()); |
||||
} |
||||
return datasource; |
||||
}); |
||||
} |
||||
|
||||
addAllOption() { |
||||
this.options.unshift({text: 'All', value: "$__all"}); |
||||
} |
||||
|
||||
metricNamesToVariableValues(metricNames) { |
||||
var regex, options, i, matches; |
||||
options = []; |
||||
|
||||
if (this.model.regex) { |
||||
regex = kbn.stringToJsRegex(this.templateSrv.replace(this.regex)); |
||||
} |
||||
|
||||
for (i = 0; i < metricNames.length; i++) { |
||||
var item = metricNames[i]; |
||||
var value = item.value || item.text; |
||||
var text = item.text || item.value; |
||||
|
||||
if (_.isNumber(value)) { |
||||
value = value.toString(); |
||||
} |
||||
|
||||
if (_.isNumber(text)) { |
||||
text = text.toString(); |
||||
} |
||||
|
||||
if (regex) { |
||||
matches = regex.exec(value); |
||||
if (!matches) { continue; } |
||||
if (matches.length > 1) { |
||||
value = matches[1]; |
||||
text = value; |
||||
} |
||||
} |
||||
|
||||
options.push({text: text, value: value}); |
||||
} |
||||
|
||||
options = _.uniq(options, 'value'); |
||||
return this.sortVariableValues(options, this.sort); |
||||
} |
||||
|
||||
sortVariableValues(options, sortOrder) { |
||||
if (sortOrder === 0) { |
||||
return options; |
||||
} |
||||
|
||||
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); |
||||
} |
||||
}); |
||||
} |
||||
if (reverseSort) { |
||||
options = options.reverse(); |
||||
} |
||||
|
||||
return options; |
||||
} |
||||
} |
||||
|
||||
variableConstructorMap['query'] = QueryVariable; |
@ -1,4 +1,279 @@ |
||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; |
||||
|
||||
describe('VariableSrv', function() { |
||||
import moment from 'moment'; |
||||
import helpers from 'test/specs/helpers'; |
||||
import '../all'; |
||||
|
||||
describe.only('VariableSrv', function() { |
||||
var ctx = new helpers.ControllerTestContext(); |
||||
|
||||
beforeEach(angularMocks.module('grafana.core')); |
||||
beforeEach(angularMocks.module('grafana.controllers')); |
||||
beforeEach(angularMocks.module('grafana.services')); |
||||
|
||||
beforeEach(ctx.providePhase(['datasourceSrv', 'timeSrv', 'templateSrv', '$location'])); |
||||
beforeEach(angularMocks.inject(($rootScope, $q, $location, $injector) => { |
||||
ctx.$q = $q; |
||||
ctx.$rootScope = $rootScope; |
||||
ctx.variableSrv = $injector.get('variableSrv'); |
||||
ctx.variableSrv.init({templating: {list: []}}); |
||||
ctx.$rootScope.$digest(); |
||||
})); |
||||
|
||||
function describeUpdateVariable(desc, fn) { |
||||
describe(desc, function() { |
||||
var scenario: any = {}; |
||||
scenario.setup = function(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)); |
||||
|
||||
scenario.variable = ctx.variableSrv.addVariable(scenario.variableModel); |
||||
ctx.variableSrv.updateOptions(scenario.variable); |
||||
ctx.$rootScope.$digest(); |
||||
}); |
||||
|
||||
fn(scenario); |
||||
}); |
||||
} |
||||
|
||||
describeUpdateVariable('interval variable without auto', scenario => { |
||||
scenario.setup(() => { |
||||
scenario.variableModel = {type: 'interval', query: '1s,2h,5h,1d', name: 'test'}; |
||||
}); |
||||
|
||||
it('should update options array', () => { |
||||
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'); |
||||
}); |
||||
}); |
||||
|
||||
//
|
||||
// Interval variable update
|
||||
//
|
||||
describeUpdateVariable('interval variable with auto', scenario => { |
||||
scenario.setup(() => { |
||||
scenario.variableModel = {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'); |
||||
}); |
||||
}); |
||||
|
||||
//
|
||||
// Query variable update
|
||||
//
|
||||
describeUpdateVariable('query variable with empty current object and refresh', function(scenario) { |
||||
scenario.setup(function() { |
||||
scenario.variableModel = {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.variableModel = { |
||||
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.variableModel = { |
||||
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.variableModel = { |
||||
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.variableModel = { 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('basic query variable', function(scenario) { |
||||
scenario.setup(function() { |
||||
scenario.variableModel = { 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.variableModel = {type: 'query', query: 'apps.*', name: 'test'}; |
||||
scenario.variableModel.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.variableModel = {type: 'query', query: 'apps.*', name: 'test'}; |
||||
scenario.variableModel.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.variableModel = {type: 'query', query: 'apps.*', name: 'test'}; |
||||
scenario.variableModel.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.variableModel = {type: 'query', query: 'apps.*', name: 'test'}; |
||||
scenario.variableModel.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.variableModel = {type: 'query', query: 'apps.*', name: 'test'}; |
||||
scenario.variableModel.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.variableModel = {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.variableModel = {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'); |
||||
}); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
|
||||
|
@ -0,0 +1,5 @@ |
||||
|
||||
export interface Variable { |
||||
setValue(option); |
||||
} |
||||
|
Loading…
Reference in new issue