feat(templating): progress on variable system refactoring, #6048

This commit is contained in:
Torkel Ödegaard 2016-09-19 11:34:08 +02:00
parent 524f505664
commit 5ded88fa4d
12 changed files with 142 additions and 183 deletions

View File

@ -9,6 +9,7 @@ export class User {
isGrafanaAdmin: any;
isSignedIn: any;
orgRole: any;
timezone: string;
constructor() {
if (config.bootData.user) {

View File

@ -6,6 +6,8 @@ import moment from 'moment';
import _ from 'lodash';
import $ from 'jquery';
import {Emitter} from 'app/core/core';
import {contextSrv} from 'app/core/services/context_srv';
import coreModule from 'app/core/core_module';
export class DashboardModel {
@ -31,14 +33,14 @@ export class DashboardModel {
links: any;
gnetId: any;
meta: any;
contextSrv: any;
events: any;
constructor(data, meta, contextSrv) {
constructor(data, meta) {
if (!data) {
data = {};
}
this.contextSrv = contextSrv;
this.events = new Emitter();
this.id = data.id || null;
this.title = data.title || 'No Title';
this.autoUpdate = data.autoUpdate;
@ -85,8 +87,18 @@ export class DashboardModel {
// cleans meta data and other non peristent state
getSaveModelClone() {
// temp remove stuff
var events = this.events;
var meta = this.meta;
delete this.events;
delete this.meta;
events.emit('prepare-save-model');
var copy = $.extend(true, {}, this);
delete copy.meta;
// restore properties
this.events = events;
this.meta = meta;
return copy;
}
@ -233,7 +245,7 @@ export class DashboardModel {
}
getTimezone() {
return this.timezone ? this.timezone : this.contextSrv.user.timezone;
return this.timezone ? this.timezone : contextSrv.user.timezone;
}
private updateSchema(old) {
@ -561,12 +573,8 @@ export class DashboardModel {
export class DashboardSrv {
currentDashboard: any;
/** @ngInject */
constructor(private contextSrv) {
}
create(dashboard, meta) {
return new DashboardModel(dashboard, meta, this.contextSrv);
return new DashboardModel(dashboard, meta);
}
setCurrent(dashboard) {

View File

@ -6,7 +6,7 @@ describe('dashboardSrv', function() {
var _dashboardSrv;
beforeEach(() => {
_dashboardSrv = new DashboardSrv({});
_dashboardSrv = new DashboardSrv();
});
describe('when creating new dashboard with defaults only', function() {

View File

@ -185,7 +185,7 @@ function (angular, _, $) {
DashboardViewState.prototype.enterFullscreen = function(panelScope) {
var ctrl = panelScope.ctrl;
ctrl.editMode = this.state.edit && this.$scope.dashboardMeta.canEdit;
ctrl.editMode = this.state.edit && this.dashboard.meta.canEdit;
ctrl.fullscreen = true;
this.oldTimeRange = ctrl.range;

View File

@ -1,5 +1,4 @@
import './templateSrv';
import './templateValuesSrv';
import './editorCtrl';
import {VariableSrv} from './variable_srv';

View File

@ -42,9 +42,7 @@ export class IntervalVariable implements Variable {
return {text: text.trim(), value: text.trim()};
});
if (this.auto) {
this.updateAutoValue();
}
this.updateAutoValue();
}
dependsOn(variable) {

View File

@ -1,88 +0,0 @@
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
import moment from 'moment';
import helpers from 'test/specs/helpers';
import '../all';
describe('VariableSrv Init', 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.$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 query 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");
});
});
describeInitSceneario('when setting custom variable via url', scenario => {
scenario.setup(() => {
scenario.variables = [{
name: 'apps',
type: 'custom',
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");
});
});
});

View File

@ -1,8 +1,10 @@
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
import '../all';
import _ from 'lodash';
import helpers from 'test/specs/helpers';
import '../all';
import {Emitter} from 'app/core/core';
describe('VariableSrv init', function() {
var ctx = new helpers.ControllerTestContext();
@ -17,7 +19,6 @@ describe('VariableSrv init', function() {
ctx.$rootScope = $rootScope;
ctx.$location = $location;
ctx.variableSrv = $injector.get('variableSrv');
ctx.variableSrv.init({templating: {list: []}});
ctx.$rootScope.$digest();
}));
@ -39,8 +40,8 @@ describe('VariableSrv init', function() {
ctx.datasourceSrv.getMetricSources = sinon.stub().returns(scenario.metricSources);
ctx.$location.search = sinon.stub().returns(scenario.urlParams);
ctx.dashboard = {templating: {list: scenario.variables}, events: new Emitter()};
ctx.dashboard = {templating: {list: scenario.variables}};
ctx.variableSrv.init(ctx.dashboard);
ctx.$rootScope.$digest();
@ -137,6 +138,5 @@ describe('VariableSrv init', function() {
});
});
});

View File

@ -1,8 +1,10 @@
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
import '../all';
import moment from 'moment';
import helpers from 'test/specs/helpers';
import '../all';
import {Emitter} from 'app/core/core';
describe('VariableSrv', function() {
var ctx = new helpers.ControllerTestContext();
@ -17,7 +19,10 @@ describe('VariableSrv', function() {
ctx.$rootScope = $rootScope;
ctx.$location = $location;
ctx.variableSrv = $injector.get('variableSrv');
ctx.variableSrv.init({templating: {list: []}});
ctx.variableSrv.init({
templating: {list: []},
events: new Emitter(),
});
ctx.$rootScope.$digest();
}));

View File

@ -6,7 +6,6 @@ import $ from 'jquery';
import kbn from 'app/core/utils/kbn';
import coreModule from 'app/core/core_module';
import appEvents from 'app/core/app_events';
import {IntervalVariable} from './interval_variable';
import {Variable} from './variable';
export var variableConstructorMap: any = {};
@ -14,95 +13,115 @@ export var variableConstructorMap: any = {};
export class VariableSrv {
dashboard: any;
variables: any;
variableLock: any;
/** @ngInject */
constructor(
private $rootScope,
private $q,
private $location,
private $injector,
private templateSrv) {
constructor(private $rootScope, private $q, private $location, private $injector, private templateSrv) {
// update time variant variables
// $rootScope.onAppEvent('refresh', this.onDashboardRefresh.bind(this), $rootScope);
}
init(dashboard) {
this.variableLock = {};
this.dashboard = dashboard;
// create working class models representing variables
this.variables = dashboard.templating.list.map(this.createVariableFromModel.bind(this));
this.templateSrv.init(this.variables);
// register event to sync back to persisted model
this.dashboard.events.on('prepare-save-model', this.syncToDashboardModel.bind(this));
// init variables
for (let variable of this.variables) {
this.variableLock[variable.name] = this.$q.defer();
}
init(dashboard) {
this.variableLock = {};
this.dashboard = dashboard;
this.variables = dashboard.templating.list.map(this.createVariableFromModel.bind(this));
this.templateSrv.init(this.variables);
var queryParams = this.$location.search();
return this.$q.all(this.variables.map(variable => {
return this.processVariable(variable, queryParams);
}));
}
var queryParams = this.$location.search();
onDashboardRefresh() {
// var promises = this.variables
// .filter(variable => variable.refresh === 2)
// .map(variable => {
// var previousOptions = variable.options.slice();
//
// return self.updateOptions(variable).then(function () {
// return self.variableUpdated(variable).then(function () {
// // check if current options changed due to refresh
// if (angular.toJson(previousOptions) !== angular.toJson(variable.options)) {
// $rootScope.appEvent('template-variable-value-updated');
// }
// });
// });
// });
//
// return this.$q.all(promises);
}
for (let variable of this.variables) {
this.variableLock[variable.name] = this.$q.defer();
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);
}
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);
}
if (variable.refresh === 1 || variable.refresh === 2) {
return variable.updateOptions().then(lock.resolve);
}
return this.$q.all(dependencies).then(() => {
var urlValue = queryParams['var-' + variable.name];
if (urlValue !== void 0) {
return variable.setValueFromUrl(urlValue).then(lock.resolve);
}
lock.resolve();
}).finally(() => {
delete this.variableLock[variable.name];
});
}
if (variable.refresh === 1 || variable.refresh === 2) {
return variable.updateOptions().then(lock.resolve);
}
lock.resolve();
}).finally(() => {
delete this.variableLock[variable.name];
});
createVariableFromModel(model) {
var ctor = variableConstructorMap[model.type];
if (!ctor) {
throw "Unable to find variable constructor for " + model.type;
}
createVariableFromModel(model) {
var ctor = variableConstructorMap[model.type];
if (!ctor) {
throw "Unable to find variable constructor for " + model.type;
}
var variable = this.$injector.instantiate(ctor, {model: model});
return variable;
}
var variable = this.$injector.instantiate(ctor, {model: model});
return variable;
addVariable(model) {
var variable = this.createVariableFromModel(model);
this.variables.push(this.createVariableFromModel(variable));
return variable;
}
syncToDashboardModel() {
this.dashboard.templating.list = this.variables.map(variable => {
return variable.model;
});
}
updateOptions(variable) {
return variable.updateOptions();
}
variableUpdated(variable) {
// if there is a variable lock ignore cascading update because we are in a boot up scenario
if (this.variableLock[variable.name]) {
return this.$q.when();
}
addVariable(model) {
var variable = this.createVariableFromModel(model);
this.variables.push(this.createVariableFromModel(variable));
return variable;
}
syncToDashboardModel() {
this.dashboard.templating.list = this.variables.map(variable => {
return variable.model;
});
}
updateOptions(variable) {
return variable.updateOptions();
}
variableUpdated(variable) {
// if there is a variable lock ignore cascading update because we are in a boot up scenario
if (this.variableLock[variable.name]) {
return this.$q.when();
}
// cascade updates to variables that use this variable
var promises = _.map(this.variables, otherVariable => {
// cascade updates to variables that use this variable
var promises = _.map(this.variables, otherVariable => {
if (otherVariable === variable) {
return;
}

View File

@ -24,6 +24,22 @@ describe("Emitter", () => {
expect(sub2Called).to.be(true);
});
it('when subscribing twice', () => {
var events = new Emitter();
var sub1Called = 0;
function handler() {
sub1Called += 1;
}
events.on('test', handler);
events.on('test', handler);
events.emit('test', null);
expect(sub1Called).to.be(2);
});
it('should handle errors', () => {
var events = new Emitter();
var sub1Called = 0;

View File

@ -14,6 +14,7 @@ define([
var dash;
var scope;
beforeEach(module('grafana.core'));
beforeEach(module('grafana.services'));
beforeEach(module(function($provide) {
$provide.value('contextSrv', _contextSrvStub);