diff --git a/public/app/core/services/context_srv.ts b/public/app/core/services/context_srv.ts index 41d99c3198f..ac00528db20 100644 --- a/public/app/core/services/context_srv.ts +++ b/public/app/core/services/context_srv.ts @@ -9,6 +9,7 @@ export class User { isGrafanaAdmin: any; isSignedIn: any; orgRole: any; + timezone: string; constructor() { if (config.bootData.user) { diff --git a/public/app/features/dashboard/dashboard_srv.ts b/public/app/features/dashboard/dashboard_srv.ts index e68133bd3a4..289e3a841f5 100644 --- a/public/app/features/dashboard/dashboard_srv.ts +++ b/public/app/features/dashboard/dashboard_srv.ts @@ -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) { diff --git a/public/app/features/dashboard/specs/dashboard_srv_specs.ts b/public/app/features/dashboard/specs/dashboard_srv_specs.ts index 3c17053dff0..56ce355881a 100644 --- a/public/app/features/dashboard/specs/dashboard_srv_specs.ts +++ b/public/app/features/dashboard/specs/dashboard_srv_specs.ts @@ -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() { diff --git a/public/app/features/dashboard/viewStateSrv.js b/public/app/features/dashboard/viewStateSrv.js index b8a8af24ab9..758d8ab7067 100644 --- a/public/app/features/dashboard/viewStateSrv.js +++ b/public/app/features/dashboard/viewStateSrv.js @@ -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; diff --git a/public/app/features/templating/all.ts b/public/app/features/templating/all.ts index c6e8dfb82ea..e3c36b653a0 100644 --- a/public/app/features/templating/all.ts +++ b/public/app/features/templating/all.ts @@ -1,5 +1,4 @@ import './templateSrv'; -import './templateValuesSrv'; import './editorCtrl'; import {VariableSrv} from './variable_srv'; diff --git a/public/app/features/templating/interval_variable.ts b/public/app/features/templating/interval_variable.ts index 409a0824aea..f2366da8163 100644 --- a/public/app/features/templating/interval_variable.ts +++ b/public/app/features/templating/interval_variable.ts @@ -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) { diff --git a/public/app/features/templating/specs/variabe_srv_init_specs.ts b/public/app/features/templating/specs/variabe_srv_init_specs.ts deleted file mode 100644 index 080b1706736..00000000000 --- a/public/app/features/templating/specs/variabe_srv_init_specs.ts +++ /dev/null @@ -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"); - }); - }); - -}); - diff --git a/public/app/features/templating/specs/variable_srv_init_specs.ts b/public/app/features/templating/specs/variable_srv_init_specs.ts index 2703b70bce3..8cac63135ca 100644 --- a/public/app/features/templating/specs/variable_srv_init_specs.ts +++ b/public/app/features/templating/specs/variable_srv_init_specs.ts @@ -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() { }); }); - }); diff --git a/public/app/features/templating/specs/variable_srv_specs.ts b/public/app/features/templating/specs/variable_srv_specs.ts index 7dc007c663c..3e5af531668 100644 --- a/public/app/features/templating/specs/variable_srv_specs.ts +++ b/public/app/features/templating/specs/variable_srv_specs.ts @@ -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(); })); diff --git a/public/app/features/templating/variable_srv.ts b/public/app/features/templating/variable_srv.ts index dfc938543a8..767765296e5 100644 --- a/public/app/features/templating/variable_srv.ts +++ b/public/app/features/templating/variable_srv.ts @@ -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; - this.variables = dashboard.templating.list.map(this.createVariableFromModel.bind(this)); - this.templateSrv.init(this.variables); + init(dashboard) { + this.variableLock = {}; + this.dashboard = dashboard; - var queryParams = this.$location.search(); + // create working class models representing variables + this.variables = dashboard.templating.list.map(this.createVariableFromModel.bind(this)); + this.templateSrv.init(this.variables); - for (let variable of this.variables) { - this.variableLock[variable.name] = this.$q.defer(); - } + // register event to sync back to persisted model + this.dashboard.events.on('prepare-save-model', this.syncToDashboardModel.bind(this)); - return this.$q.all(this.variables.map(variable => { - return this.processVariable(variable, queryParams); - })); + // init variables + 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); - } + var queryParams = this.$location.search(); + return this.$q.all(this.variables.map(variable => { + return this.processVariable(variable, queryParams); + })); + } + + 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); + } + + 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(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; + return this.$q.all(dependencies).then(() => { + var urlValue = queryParams['var-' + variable.name]; + if (urlValue !== void 0) { + return variable.setValueFromUrl(urlValue).then(lock.resolve); } - var variable = this.$injector.instantiate(ctor, {model: model}); - return variable; - } + if (variable.refresh === 1 || variable.refresh === 2) { + return variable.updateOptions().then(lock.resolve); + } - addVariable(model) { - var variable = this.createVariableFromModel(model); - this.variables.push(this.createVariableFromModel(variable)); - return variable; - } + lock.resolve(); + }).finally(() => { + delete this.variableLock[variable.name]; + }); + } - syncToDashboardModel() { - this.dashboard.templating.list = this.variables.map(variable => { - return variable.model; - }); + createVariableFromModel(model) { + var ctor = variableConstructorMap[model.type]; + if (!ctor) { + throw "Unable to find variable constructor for " + model.type; } - updateOptions(variable) { - return variable.updateOptions(); + 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(); } - 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; } diff --git a/public/test/core/utils/emitter_specs.ts b/public/test/core/utils/emitter_specs.ts index fec4d02a649..388d3849055 100644 --- a/public/test/core/utils/emitter_specs.ts +++ b/public/test/core/utils/emitter_specs.ts @@ -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; diff --git a/public/test/specs/unsavedChangesSrv-specs.js b/public/test/specs/unsavedChangesSrv-specs.js index c69c2f67885..a304279b77b 100644 --- a/public/test/specs/unsavedChangesSrv-specs.js +++ b/public/test/specs/unsavedChangesSrv-specs.js @@ -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);