{strengthText}
diff --git a/public/app/core/components/code_editor/code_editor.ts b/public/app/core/components/code_editor/code_editor.ts
index 61ebb17d9b3..2615a635c7e 100644
--- a/public/app/core/components/code_editor/code_editor.ts
+++ b/public/app/core/components/code_editor/code_editor.ts
@@ -26,60 +26,31 @@
* Ctrl-Enter (Command-Enter): run onChange() function
*/
-///
-import _ from 'lodash';
import coreModule from 'app/core/core_module';
import config from 'app/core/config';
-import ace from 'ace';
+import ace from 'brace';
+import './theme-grafana-dark';
+import 'brace/ext/language_tools';
+import 'brace/theme/textmate';
+import 'brace/mode/text';
+import 'brace/snippets/text';
+import 'brace/mode/sql';
+import 'brace/snippets/sql';
-const ACE_SRC_BASE = "public/vendor/npm/ace-builds/src-noconflict/";
-const DEFAULT_THEME_DARK = "grafana-dark";
-const DEFAULT_THEME_LIGHT = "textmate";
+const DEFAULT_THEME_DARK = "ace/theme/grafana-dark";
+const DEFAULT_THEME_LIGHT = "ace/theme/textmate";
const DEFAULT_MODE = "text";
const DEFAULT_MAX_LINES = 10;
const DEFAULT_TAB_SIZE = 2;
const DEFAULT_BEHAVIOURS = true;
-const GRAFANA_MODULES = ['theme-grafana-dark'];
-const GRAFANA_MODULE_BASE = "public/app/core/components/code_editor/";
-
-// Trick for loading additional modules
-function setModuleUrl(moduleType, name, pluginBaseUrl = null) {
- let baseUrl = ACE_SRC_BASE;
- let aceModeName = `ace/${moduleType}/${name}`;
- let moduleName = `${moduleType}-${name}`;
- let componentName = `${moduleName}.js`;
-
- if (_.includes(GRAFANA_MODULES, moduleName)) {
- baseUrl = GRAFANA_MODULE_BASE;
- }
-
- if (pluginBaseUrl) {
- baseUrl = pluginBaseUrl + '/';
- }
-
- if (moduleType === 'snippets') {
- componentName = `${moduleType}/${name}.js`;
- }
-
- ace.config.setModuleUrl(aceModeName, baseUrl + componentName);
-}
-
-setModuleUrl("ext", "language_tools");
-setModuleUrl("mode", "text");
-setModuleUrl("snippets", "text");
-
let editorTemplate = `
`;
function link(scope, elem, attrs) {
- let lightTheme = config.bootData.user.lightTheme;
- let default_theme = lightTheme ? DEFAULT_THEME_LIGHT : DEFAULT_THEME_DARK;
-
// Options
let langMode = attrs.mode || DEFAULT_MODE;
let maxLines = attrs.maxLines || DEFAULT_MAX_LINES;
let showGutter = attrs.showGutter !== undefined;
- let theme = attrs.theme || default_theme;
let tabSize = attrs.tabSize || DEFAULT_TAB_SIZE;
let behavioursEnabled = attrs.behavioursEnabled ? attrs.behavioursEnabled === 'true' : DEFAULT_BEHAVIOURS;
@@ -103,10 +74,10 @@ function link(scope, elem, attrs) {
// disable depreacation warning
codeEditor.$blockScrolling = Infinity;
// Padding hacks
- codeEditor.renderer.setScrollMargin(15, 15);
+ (
codeEditor.renderer).setScrollMargin(15, 15);
codeEditor.renderer.setPadding(10);
- setThemeMode(theme);
+ setThemeMode();
setLangMode(langMode);
setEditorContent(scope.content);
@@ -162,44 +133,31 @@ function link(scope, elem, attrs) {
});
function setLangMode(lang) {
- let aceModeName = `ace/mode/${lang}`;
- setModuleUrl("mode", lang, scope.datasource.meta.baseUrl || null);
- setModuleUrl("snippets", lang, scope.datasource.meta.baseUrl || null);
- editorSession.setMode(aceModeName);
-
- ace.config.loadModule("ace/ext/language_tools", (language_tools) => {
- codeEditor.setOptions({
- enableBasicAutocompletion: true,
- enableLiveAutocompletion: true,
- enableSnippets: true
- });
-
- if (scope.getCompleter()) {
- // make copy of array as ace seems to share completers array between instances
- codeEditor.completers = codeEditor.completers.slice();
- codeEditor.completers.push(scope.getCompleter());
- }
+ ace.acequire("ace/ext/language_tools");
+ codeEditor.setOptions({
+ enableBasicAutocompletion: true,
+ enableLiveAutocompletion: true,
+ enableSnippets: true
});
+
+ if (scope.getCompleter()) {
+ // make copy of array as ace seems to share completers array between instances
+ const anyEditor = codeEditor;
+ anyEditor.completers = anyEditor.completers.slice();
+ anyEditor.completers.push(scope.getCompleter());
+ }
+
+ let aceModeName = `ace/mode/${lang}`;
+ editorSession.setMode(aceModeName);
}
- function setThemeMode(theme) {
- setModuleUrl("theme", theme);
- let themeModule = `ace/theme/${theme}`;
- ace.config.loadModule(themeModule, (theme_module) => {
- // Check is theme light or dark and fix if needed
- let lightTheme = config.bootData.user.lightTheme;
- let fixedTheme = theme;
- if (lightTheme && theme_module.isDark) {
- fixedTheme = DEFAULT_THEME_LIGHT;
- } else if (!lightTheme && !theme_module.isDark) {
- fixedTheme = DEFAULT_THEME_DARK;
- }
- setModuleUrl("theme", fixedTheme);
- themeModule = `ace/theme/${fixedTheme}`;
- codeEditor.setTheme(themeModule);
+ function setThemeMode() {
+ let theme = DEFAULT_THEME_DARK;
+ if (config.bootData.user.lightTheme) {
+ theme = DEFAULT_THEME_LIGHT;
+ }
- elem.addClass("gf-code-editor--theme-loaded");
- });
+ codeEditor.setTheme(theme);
}
function setEditorContent(value) {
diff --git a/public/app/core/components/code_editor/theme-grafana-dark.js b/public/app/core/components/code_editor/theme-grafana-dark.js
index 621ec2c5efb..a48715e698e 100644
--- a/public/app/core/components/code_editor/theme-grafana-dark.js
+++ b/public/app/core/components/code_editor/theme-grafana-dark.js
@@ -1,6 +1,6 @@
/* jshint ignore:start */
-ace.define("ace/theme/grafana-dark",["require","exports","module","ace/lib/dom"], function(require, exports, module) {
+ace.define("ace/theme/grafana-dark",["require","exports","module","ace/lib/dom"], function(acequire, exports, module) {
"use strict";
exports.isDark = true;
@@ -109,7 +109,7 @@ ace.define("ace/theme/grafana-dark",["require","exports","module","ace/lib/dom"]
background: url(data:image/png;base64,ivborw0kggoaaaansuheugaaaaeaaaaccayaaaczgbynaaaaekleqvqimwpq0fd0zxbzd/wpaajvaoxesgneaaaaaelftksuqmcc) right repeat-y\
}";
- var dom = require("../lib/dom");
+ var dom = acequire("../lib/dom");
dom.importCssString(exports.cssText, exports.cssClass);
});
diff --git a/public/app/core/core.ts b/public/app/core/core.ts
index 526504380ad..675b9eba2fb 100644
--- a/public/app/core/core.ts
+++ b/public/app/core/core.ts
@@ -1,6 +1,3 @@
-///
-///
-
import "./directives/dash_class";
import "./directives/confirm_click";
import "./directives/dash_edit_link";
@@ -11,7 +8,6 @@ import "./directives/ng_model_on_blur";
import "./directives/spectrum_picker";
import "./directives/tags";
import "./directives/value_select_dropdown";
-import "./directives/plugin_component";
import "./directives/rebuild_on_change";
import "./directives/give_focus";
import "./directives/diff-view";
diff --git a/public/app/core/core_module.ts b/public/app/core/core_module.ts
index 8ac4ae6e91f..f6c30e6cf15 100644
--- a/public/app/core/core_module.ts
+++ b/public/app/core/core_module.ts
@@ -1,4 +1,2 @@
-///
-
import angular from 'angular';
export default angular.module('grafana.core', ['ngRoute']);
diff --git a/public/app/core/directives/rebuild_on_change.ts b/public/app/core/directives/rebuild_on_change.ts
index 56f945f129a..db2634869c0 100644
--- a/public/app/core/directives/rebuild_on_change.ts
+++ b/public/app/core/directives/rebuild_on_change.ts
@@ -1,7 +1,4 @@
-///
-
import $ from 'jquery';
-
import coreModule from '../core_module';
function getBlockNodes(nodes) {
@@ -21,6 +18,7 @@ function getBlockNodes(nodes) {
return blockNodes || nodes;
}
+/** @ngInject **/
function rebuildOnChange($animate) {
return {
diff --git a/public/app/core/directives/spectrum_picker.js b/public/app/core/directives/spectrum_picker.js
index fa1d0a82a47..612188b71d9 100644
--- a/public/app/core/directives/spectrum_picker.js
+++ b/public/app/core/directives/spectrum_picker.js
@@ -1,7 +1,7 @@
define([
'angular',
'../core_module',
- 'spectrum',
+ 'vendor/spectrum',
],
function (angular, coreModule) {
'use strict';
diff --git a/public/app/core/directives/tags.js b/public/app/core/directives/tags.js
index 627d516128a..90a355dea07 100644
--- a/public/app/core/directives/tags.js
+++ b/public/app/core/directives/tags.js
@@ -2,7 +2,7 @@ define([
'angular',
'jquery',
'../core_module',
- 'bootstrap-tagsinput',
+ 'vendor/tagsinput/bootstrap-tagsinput.js',
],
function (angular, $, coreModule) {
'use strict';
diff --git a/public/app/core/filters/filters.ts b/public/app/core/filters/filters.ts
index 63f2bf28bc3..0f53df8b4fb 100644
--- a/public/app/core/filters/filters.ts
+++ b/public/app/core/filters/filters.ts
@@ -57,7 +57,8 @@ coreModule.filter('noXml', function() {
};
});
-coreModule.filter('interpolateTemplateVars', function (templateSrv) {
+/** @ngInject */
+function interpolateTemplateVars(templateSrv) {
var filterFunc: any = function(text, scope) {
var scopedVars;
if (scope.ctrl) {
@@ -71,6 +72,7 @@ coreModule.filter('interpolateTemplateVars', function (templateSrv) {
filterFunc.$stateful = true;
return filterFunc;
-});
+}
+coreModule.filter('interpolateTemplateVars', interpolateTemplateVars);
export default {};
diff --git a/public/app/core/live/live_srv.ts b/public/app/core/live/live_srv.ts
index 7b570f05df3..8368992d052 100644
--- a/public/app/core/live/live_srv.ts
+++ b/public/app/core/live/live_srv.ts
@@ -3,7 +3,7 @@
import _ from 'lodash';
import config from 'app/core/config';
-import {Observable} from 'vendor/npm/rxjs/Observable';
+import {Observable} from 'rxjs/Observable';
export class LiveSrv {
conn: any;
diff --git a/public/app/core/partials.js b/public/app/core/partials.js
deleted file mode 100644
index 4efe276393b..00000000000
--- a/public/app/core/partials.js
+++ /dev/null
@@ -1,2 +0,0 @@
-define([
-], function () {});
diff --git a/public/app/core/partials.ts b/public/app/core/partials.ts
new file mode 100644
index 00000000000..fb70e30f96a
--- /dev/null
+++ b/public/app/core/partials.ts
@@ -0,0 +1,4 @@
+var templates = (require).context('../', true, /\.html$/);
+templates.keys().forEach(function(key) {
+ templates(key);
+});
diff --git a/public/app/core/profiler.ts b/public/app/core/profiler.ts
index 7b732e7fb43..e762b3fc4b3 100644
--- a/public/app/core/profiler.ts
+++ b/public/app/core/profiler.ts
@@ -1,5 +1,3 @@
-///
-
import $ from 'jquery';
import angular from 'angular';
diff --git a/public/app/core/routes/bundle_loader.ts b/public/app/core/routes/bundle_loader.ts
index 473ac66b4b9..b86f9fb1495 100644
--- a/public/app/core/routes/bundle_loader.ts
+++ b/public/app/core/routes/bundle_loader.ts
@@ -1,5 +1,3 @@
-///
-
export class BundleLoader {
lazy: any;
diff --git a/public/app/core/routes/routes.ts b/public/app/core/routes/routes.ts
index 85301ec692c..1f9dbcae57c 100644
--- a/public/app/core/routes/routes.ts
+++ b/public/app/core/routes/routes.ts
@@ -1,18 +1,27 @@
-///
-
import './dashboard_loaders';
-
import coreModule from 'app/core/core_module';
-import {BundleLoader} from './bundle_loader';
/** @ngInject **/
function setupAngularRoutes($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
- var loadOrgBundle = new BundleLoader('app/features/org/all');
- var loadPluginsBundle = new BundleLoader('app/features/plugins/all');
- var loadAdminBundle = new BundleLoader('app/features/admin/admin');
- var loadAlertingBundle = new BundleLoader('app/features/alerting/all');
+ var loadOrgBundle = {
+ lazy: ["$q", "$route", "$rootScope", ($q, $route, $rootScope) => {
+ return System.import('app/features/org/all');
+ }]
+ };
+
+ var loadAdminBundle = {
+ lazy: ["$q", "$route", "$rootScope", ($q, $route, $rootScope) => {
+ return System.import('app/features/admin/admin');
+ }]
+ };
+
+ var loadAlertingBundle = {
+ lazy: ["$q", "$route", "$rootScope", ($q, $route, $rootScope) => {
+ return System.import('app/features/alerting/all');
+ }]
+ };
$routeProvider
.when('/', {
@@ -47,19 +56,16 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
templateUrl: 'public/app/features/plugins/partials/ds_list.html',
controller : 'DataSourcesCtrl',
controllerAs: 'ctrl',
- resolve: loadPluginsBundle,
})
.when('/datasources/edit/:id', {
templateUrl: 'public/app/features/plugins/partials/ds_edit.html',
controller : 'DataSourceEditCtrl',
controllerAs: 'ctrl',
- resolve: loadPluginsBundle,
})
.when('/datasources/new', {
templateUrl: 'public/app/features/plugins/partials/ds_edit.html',
controller : 'DataSourceEditCtrl',
controllerAs: 'ctrl',
- resolve: loadPluginsBundle,
})
.when('/org', {
templateUrl: 'public/app/features/org/partials/orgDetails.html',
@@ -175,19 +181,16 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
templateUrl: 'public/app/features/plugins/partials/plugin_list.html',
controller: 'PluginListCtrl',
controllerAs: 'ctrl',
- resolve: loadPluginsBundle,
})
.when('/plugins/:pluginId/edit', {
templateUrl: 'public/app/features/plugins/partials/plugin_edit.html',
controller: 'PluginEditCtrl',
controllerAs: 'ctrl',
- resolve: loadPluginsBundle,
})
.when('/plugins/:pluginId/page/:slug', {
templateUrl: 'public/app/features/plugins/partials/plugin_page.html',
controller: 'AppPageCtrl',
controllerAs: 'ctrl',
- resolve: loadPluginsBundle,
})
.when('/styleguide/:page?', {
controller: 'StyleGuideCtrl',
diff --git a/public/app/core/services/all.js b/public/app/core/services/all.js
index 96a4ec77737..1fea3e5a248 100644
--- a/public/app/core/services/all.js
+++ b/public/app/core/services/all.js
@@ -1,7 +1,6 @@
define([
'./alert_srv',
'./util_srv',
- './datasource_srv',
'./context_srv',
'./timer',
'./keyboard_manager',
diff --git a/public/app/core/specs/PasswordStrength_specs.tsx b/public/app/core/specs/PasswordStrength_specs.tsx
index 0ba239b2637..7a46d6b153f 100644
--- a/public/app/core/specs/PasswordStrength_specs.tsx
+++ b/public/app/core/specs/PasswordStrength_specs.tsx
@@ -1,14 +1,25 @@
-// import React from 'react';
-// import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
-// import {shallow} from 'enzyme';
-//
-// import {PasswordStrength} from '../components/PasswordStrength';
-//
-// describe('PasswordStrength', () => {
-//
-// it.skip('should have class bad if length below 4', () => {
-// const wrapper = shallow();
-// expect(wrapper.find(".password-strength-bad")).to.have.length(3);
-// });
-// });
-//
+import React from 'react';
+import {describe, it, expect} from 'test/lib/common';
+import {shallow} from 'enzyme';
+
+import {PasswordStrength} from '../components/PasswordStrength';
+
+describe('PasswordStrength', () => {
+
+ it('should have class bad if length below 4', () => {
+ const wrapper = shallow();
+ expect(wrapper.find(".password-strength-bad")).to.have.length(1);
+ });
+
+ it('should have class ok if length below 8', () => {
+ const wrapper = shallow();
+ expect(wrapper.find(".password-strength-ok")).to.have.length(1);
+ });
+
+ it('should have class good if length above 8', () => {
+ const wrapper = shallow();
+ expect(wrapper.find(".password-strength-good")).to.have.length(1);
+ });
+
+});
+
diff --git a/public/app/core/specs/backend_srv_specs.ts b/public/app/core/specs/backend_srv_specs.ts
new file mode 100644
index 00000000000..0e78007f210
--- /dev/null
+++ b/public/app/core/specs/backend_srv_specs.ts
@@ -0,0 +1,29 @@
+import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common';
+import 'app/core/services/backend_srv';
+
+describe('backend_srv', function() {
+ var _backendSrv;
+ var _http;
+ var _httpBackend;
+
+ beforeEach(angularMocks.module('grafana.core'));
+ beforeEach(angularMocks.module('grafana.services'));
+ beforeEach(angularMocks.inject(function ($httpBackend, $http, backendSrv) {
+ _httpBackend = $httpBackend;
+ _http = $http;
+ _backendSrv = backendSrv;
+ }));
+
+ describe('when handling errors', function() {
+ it('should return the http status code', function(done) {
+ _httpBackend.whenGET('gateway-error').respond(502);
+ _backendSrv.datasourceRequest({
+ url: 'gateway-error'
+ }).catch(function(err) {
+ expect(err.status).to.be(502);
+ done();
+ });
+ _httpBackend.flush();
+ });
+ });
+});
diff --git a/public/app/core/specs/value_select_dropdown_specs.ts b/public/app/core/specs/value_select_dropdown_specs.ts
new file mode 100644
index 00000000000..644320e7e0d
--- /dev/null
+++ b/public/app/core/specs/value_select_dropdown_specs.ts
@@ -0,0 +1,167 @@
+import {describe, beforeEach, it, expect, angularMocks, sinon} from 'test/lib/common';
+import 'app/core/directives/value_select_dropdown';
+
+describe("SelectDropdownCtrl", function() {
+ var scope;
+ var ctrl;
+ var tagValuesMap: any = {};
+ var rootScope;
+ var q;
+
+ beforeEach(angularMocks.module('grafana.core'));
+ beforeEach(angularMocks.inject(function($controller, $rootScope, $q, $httpBackend) {
+ rootScope = $rootScope;
+ q = $q;
+ scope = $rootScope.$new();
+ ctrl = $controller('ValueSelectDropdownCtrl', {$scope: scope});
+ ctrl.onUpdated = sinon.spy();
+ $httpBackend.when('GET', /\.html$/).respond('');
+ }));
+
+ describe("Given simple variable", function() {
+ beforeEach(function() {
+ ctrl.variable = {
+ current: {text: 'hej', value: 'hej' },
+ getValuesForTag: function(key) {
+ return q.when(tagValuesMap[key]);
+ },
+ };
+ ctrl.init();
+ });
+
+ it("Should init labelText and linkText", function() {
+ expect(ctrl.linkText).to.be("hej");
+ });
+ });
+
+ describe("Given variable with tags and dropdown is opened", function() {
+ beforeEach(function() {
+ ctrl.variable = {
+ current: {text: 'server-1', value: 'server-1'},
+ options: [
+ {text: 'server-1', value: 'server-1', selected: true},
+ {text: 'server-2', value: 'server-2'},
+ {text: 'server-3', value: 'server-3'},
+ ],
+ tags: ["key1", "key2", "key3"],
+ getValuesForTag: function(key) {
+ return q.when(tagValuesMap[key]);
+ },
+ multi: true
+ };
+ tagValuesMap.key1 = ['server-1', 'server-3'];
+ tagValuesMap.key2 = ['server-2', 'server-3'];
+ tagValuesMap.key3 = ['server-1', 'server-2', 'server-3'];
+ ctrl.init();
+ ctrl.show();
+ });
+
+ it("should init tags model", function() {
+ expect(ctrl.tags.length).to.be(3);
+ expect(ctrl.tags[0].text).to.be("key1");
+ });
+
+ it("should init options model", function() {
+ expect(ctrl.options.length).to.be(3);
+ });
+
+ it("should init selected values array", function() {
+ expect(ctrl.selectedValues.length).to.be(1);
+ });
+
+ it("should set linkText", function() {
+ expect(ctrl.linkText).to.be('server-1');
+ });
+
+ describe('after adititional value is selected', function() {
+ beforeEach(function() {
+ ctrl.selectValue(ctrl.options[2], {});
+ ctrl.commitChanges();
+ });
+
+ it('should update link text', function() {
+ expect(ctrl.linkText).to.be('server-1 + server-3');
+ });
+ });
+
+ describe('When tag is selected', function() {
+ beforeEach(function() {
+ ctrl.selectTag(ctrl.tags[0]);
+ rootScope.$digest();
+ ctrl.commitChanges();
+ });
+
+ it("should select tag", function() {
+ expect(ctrl.selectedTags.length).to.be(1);
+ });
+
+ it("should select values", function() {
+ expect(ctrl.options[0].selected).to.be(true);
+ expect(ctrl.options[2].selected).to.be(true);
+ });
+
+ it("link text should not include tag values", function() {
+ expect(ctrl.linkText).to.be('');
+ });
+
+ describe('and then dropdown is opened and closed without changes', function() {
+ beforeEach(function() {
+ ctrl.show();
+ ctrl.commitChanges();
+ rootScope.$digest();
+ });
+
+ it("should still have selected tag", function() {
+ expect(ctrl.selectedTags.length).to.be(1);
+ });
+ });
+
+ describe('and then unselected', function() {
+ beforeEach(function() {
+ ctrl.selectTag(ctrl.tags[0]);
+ rootScope.$digest();
+ });
+
+ it("should deselect tag", function() {
+ expect(ctrl.selectedTags.length).to.be(0);
+ });
+ });
+
+ describe('and then value is unselected', function() {
+ beforeEach(function() {
+ ctrl.selectValue(ctrl.options[0], {});
+ });
+
+ it("should deselect tag", function() {
+ expect(ctrl.selectedTags.length).to.be(0);
+ });
+ });
+ });
+ });
+
+ describe("Given variable with selected tags", function() {
+ beforeEach(function() {
+ ctrl.variable = {
+ current: {text: 'server-1', value: 'server-1', tags: [{text: 'key1', selected: true}] },
+ options: [
+ {text: 'server-1', value: 'server-1'},
+ {text: 'server-2', value: 'server-2'},
+ {text: 'server-3', value: 'server-3'},
+ ],
+ tags: ["key1", "key2", "key3"],
+ getValuesForTag: function(key) {
+ return q.when(tagValuesMap[key]);
+ },
+ multi: true
+ };
+ ctrl.init();
+ ctrl.show();
+ });
+
+ it("should set tag as selected", function() {
+ expect(ctrl.tags[0].selected).to.be(true);
+ });
+
+ });
+});
+
diff --git a/public/app/features/all.js b/public/app/features/all.js
index 96c28288e8e..f039cb0ae90 100644
--- a/public/app/features/all.js
+++ b/public/app/features/all.js
@@ -3,6 +3,7 @@ define([
'./dashlinks/module',
'./annotations/all',
'./templating/all',
+ './plugins/all',
'./dashboard/all',
'./playlist/all',
'./snapshot/all',
diff --git a/public/app/features/dashboard/repeat_option/repeat_option.ts b/public/app/features/dashboard/repeat_option/repeat_option.ts
index 380c2861a28..056cfded0b5 100644
--- a/public/app/features/dashboard/repeat_option/repeat_option.ts
+++ b/public/app/features/dashboard/repeat_option/repeat_option.ts
@@ -9,7 +9,8 @@ var template = `
`;
-coreModule.directive('dashRepeatOption', function(variableSrv) {
+/** @ngInject **/
+function dashRepeatOptionDirective(variableSrv) {
return {
restrict: 'E',
template: template,
@@ -30,5 +31,6 @@ coreModule.directive('dashRepeatOption', function(variableSrv) {
scope.variables.unshift({text: 'Disabled', value: null});
}
};
-});
+}
+coreModule.directive('dashRepeatOption', dashRepeatOptionDirective);
diff --git a/public/app/features/dashboard/row/row_ctrl.ts b/public/app/features/dashboard/row/row_ctrl.ts
index 496335eaac5..e5c936ef4ed 100644
--- a/public/app/features/dashboard/row/row_ctrl.ts
+++ b/public/app/features/dashboard/row/row_ctrl.ts
@@ -113,7 +113,8 @@ export class DashRowCtrl {
}
}
-coreModule.directive('dashRow', function($rootScope) {
+/** @ngInject */
+function dashRowDirective($rootScope) {
return {
restrict: 'E',
templateUrl: 'public/app/features/dashboard/row/row.html',
@@ -142,9 +143,10 @@ coreModule.directive('dashRow', function($rootScope) {
}, scope);
}
};
-});
+}
-coreModule.directive('panelWidth', function($rootScope) {
+/** @ngInject */
+function panelWidthDirective($rootScope) {
return function(scope, element) {
var fullscreen = false;
@@ -180,10 +182,10 @@ coreModule.directive('panelWidth', function($rootScope) {
element.hide();
}
};
-});
+}
-
-coreModule.directive('panelDropZone', function($timeout) {
+/** @ngInject */
+function panelDropZoneDirective($timeout) {
return function(scope, element) {
var row = scope.ctrl.row;
var indrag = false;
@@ -237,5 +239,9 @@ coreModule.directive('panelDropZone', function($timeout) {
updateState();
};
-});
+}
+
+coreModule.directive('dashRow', dashRowDirective);
+coreModule.directive('panelWidth', panelWidthDirective);
+coreModule.directive('panelDropZone', panelDropZoneDirective);
diff --git a/public/app/features/dashboard/specs/share_modal_ctrl_specs.ts b/public/app/features/dashboard/specs/share_modal_ctrl_specs.ts
new file mode 100644
index 00000000000..7a04f5f7579
--- /dev/null
+++ b/public/app/features/dashboard/specs/share_modal_ctrl_specs.ts
@@ -0,0 +1,110 @@
+import {describe, beforeEach, it, expect, sinon, angularMocks} from 'test/lib/common';
+import helpers from 'test/specs/helpers';
+import '../shareModalCtrl';
+import config from 'app/core/config';
+import 'app/features/panellinks/linkSrv';
+
+describe('ShareModalCtrl', function() {
+ var ctx = new helpers.ControllerTestContext();
+
+ function setTime(range) {
+ ctx.timeSrv.timeRange = sinon.stub().returns(range);
+ }
+
+ beforeEach(function() {
+ config.bootData = {
+ user: {
+ orgId: 1
+ }
+ };
+ });
+
+ setTime({ from: new Date(1000), to: new Date(2000) });
+
+ beforeEach(angularMocks.module('grafana.controllers'));
+ beforeEach(angularMocks.module('grafana.services'));
+ beforeEach(angularMocks.module(function($compileProvider) {
+ $compileProvider.preAssignBindingsEnabled(true);
+ }));
+
+ beforeEach(ctx.providePhase());
+
+ beforeEach(ctx.createControllerPhase('ShareModalCtrl'));
+
+ describe('shareUrl with current time range and panel', function() {
+ it('should generate share url absolute time', function() {
+ ctx.$location.path('/test');
+ ctx.scope.panel = { id: 22 };
+
+ ctx.scope.init();
+ expect(ctx.scope.shareUrl).to.be('http://server/#!/test?from=1000&to=2000&orgId=1&panelId=22&fullscreen');
+ });
+
+ it('should generate render url', function() {
+ ctx.$location.$$absUrl = 'http://dashboards.grafana.com/dashboard/db/my-dash';
+
+ ctx.scope.panel = { id: 22 };
+
+ ctx.scope.init();
+ var base = 'http://dashboards.grafana.com/render/dashboard-solo/db/my-dash';
+ var params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
+ expect(ctx.scope.imageUrl).to.contain(base + params);
+ });
+
+ it('should remove panel id when no panel in scope', function() {
+ ctx.$location.path('/test');
+ ctx.scope.options.forCurrent = true;
+ ctx.scope.panel = null;
+
+ ctx.scope.init();
+ expect(ctx.scope.shareUrl).to.be('http://server/#!/test?from=1000&to=2000&orgId=1');
+ });
+
+ it('should add theme when specified', function() {
+ ctx.$location.path('/test');
+ ctx.scope.options.theme = 'light';
+ ctx.scope.panel = null;
+
+ ctx.scope.init();
+ expect(ctx.scope.shareUrl).to.be('http://server/#!/test?from=1000&to=2000&orgId=1&theme=light');
+ });
+
+ it('should remove fullscreen from image url when is first param in querystring and modeSharePanel is true', function() {
+ ctx.$location.url('/test?fullscreen&edit');
+ ctx.scope.modeSharePanel = true;
+ ctx.scope.panel = { id: 1 };
+
+ ctx.scope.buildUrl();
+
+ expect(ctx.scope.shareUrl).to.contain('?fullscreen&edit&from=1000&to=2000&orgId=1&panelId=1');
+ expect(ctx.scope.imageUrl).to.contain('?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
+
+ });
+
+ it('should remove edit from image url when is first param in querystring and modeSharePanel is true', function() {
+ ctx.$location.url('/test?edit&fullscreen');
+ ctx.scope.modeSharePanel = true;
+ ctx.scope.panel = { id: 1 };
+
+ ctx.scope.buildUrl();
+
+ expect(ctx.scope.shareUrl).to.contain('?edit&fullscreen&from=1000&to=2000&orgId=1&panelId=1');
+ expect(ctx.scope.imageUrl).to.contain('?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
+
+ });
+
+ it('should include template variables in url', function() {
+ ctx.$location.path('/test');
+ ctx.scope.options.includeTemplateVars = true;
+
+ ctx.templateSrv.fillVariableValuesForUrl = function(params) {
+ params['var-app'] = 'mupp';
+ params['var-server'] = 'srv-01';
+ };
+
+ ctx.scope.buildUrl();
+ expect(ctx.scope.shareUrl).to.be('http://server/#!/test?from=1000&to=2000&orgId=1&var-app=mupp&var-server=srv-01');
+ });
+ });
+});
+
diff --git a/public/app/features/dashboard/specs/unsaved_changes_srv_specs.ts b/public/app/features/dashboard/specs/unsaved_changes_srv_specs.ts
new file mode 100644
index 00000000000..135a81c166e
--- /dev/null
+++ b/public/app/features/dashboard/specs/unsaved_changes_srv_specs.ts
@@ -0,0 +1,82 @@
+import {describe, beforeEach, it, expect, sinon, angularMocks} from 'test/lib/common';
+import 'app/features/dashboard/unsavedChangesSrv';
+import 'app/features/dashboard/dashboard_srv';
+
+describe("unsavedChangesSrv", function() {
+ var _unsavedChangesSrv;
+ var _dashboardSrv;
+ var _location;
+ var _contextSrvStub = { isEditor: true };
+ var _rootScope;
+ var tracker;
+ var dash;
+ var scope;
+
+ beforeEach(angularMocks.module('grafana.core'));
+ beforeEach(angularMocks.module('grafana.services'));
+ beforeEach(angularMocks.module(function($provide) {
+ $provide.value('contextSrv', _contextSrvStub);
+ $provide.value('$window', {});
+ }));
+
+ beforeEach(angularMocks.inject(function(unsavedChangesSrv, $location, $rootScope, dashboardSrv) {
+ _unsavedChangesSrv = unsavedChangesSrv;
+ _dashboardSrv = dashboardSrv;
+ _location = $location;
+ _rootScope = $rootScope;
+ }));
+
+ beforeEach(function() {
+ dash = _dashboardSrv.create({
+ refresh: false,
+ rows: [
+ {
+ panels: [{ test: "asd", legend: { } }]
+ }
+ ]
+ });
+ scope = _rootScope.$new();
+ scope.appEvent = sinon.spy();
+ scope.onAppEvent = sinon.spy();
+
+ tracker = new _unsavedChangesSrv.Tracker(dash, scope);
+ });
+
+ it('No changes should not have changes', function() {
+ expect(tracker.hasChanges()).to.be(false);
+ });
+
+ it('Simple change should be registered', function() {
+ dash.property = "google";
+ expect(tracker.hasChanges()).to.be(true);
+ });
+
+ it('Should ignore a lot of changes', function() {
+ dash.time = {from: '1h'};
+ dash.refresh = true;
+ dash.schemaVersion = 10;
+ expect(tracker.hasChanges()).to.be(false);
+ });
+
+ it('Should ignore row collapse change', function() {
+ dash.rows[0].collapse = true;
+ expect(tracker.hasChanges()).to.be(false);
+ });
+
+ it('Should ignore panel legend changes', function() {
+ dash.rows[0].panels[0].legend.sortDesc = true;
+ dash.rows[0].panels[0].legend.sort = "avg";
+ expect(tracker.hasChanges()).to.be(false);
+ });
+
+ it('Should ignore panel repeats', function() {
+ dash.rows[0].panels.push({repeatPanelId: 10});
+ expect(tracker.hasChanges()).to.be(false);
+ });
+
+ it('Should ignore row repeats', function() {
+ dash.addEmptyRow();
+ dash.rows[1].repeatRowId = 10;
+ expect(tracker.hasChanges()).to.be(false);
+ });
+});
diff --git a/public/app/features/dashboard/specs/viewstate_srv_specs.ts b/public/app/features/dashboard/specs/viewstate_srv_specs.ts
new file mode 100644
index 00000000000..6112f1bd41f
--- /dev/null
+++ b/public/app/features/dashboard/specs/viewstate_srv_specs.ts
@@ -0,0 +1,53 @@
+import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common';
+import 'app/features/dashboard/viewStateSrv';
+import config from 'app/core/config';
+
+describe('when updating view state', function() {
+ var viewState, location;
+ var timeSrv = {};
+ var templateSrv = {};
+ var contextSrv = {
+ user: {
+ orgId: 19
+ }
+ };
+ beforeEach(function() {
+ config.bootData = {
+ user: {
+ orgId: 1
+ }
+ };
+ });
+ beforeEach(angularMocks.module('grafana.services'));
+ beforeEach(angularMocks.module(function($provide) {
+ $provide.value('timeSrv', timeSrv);
+ $provide.value('templateSrv', templateSrv);
+ $provide.value('contextSrv', contextSrv);
+ }));
+
+ beforeEach(angularMocks.inject(function(dashboardViewStateSrv, $location, $rootScope) {
+ $rootScope.onAppEvent = function() {};
+ $rootScope.dashboard = {meta: {}};
+ viewState = dashboardViewStateSrv.create($rootScope);
+ location = $location;
+ }));
+
+ describe('to fullscreen true and edit true', function() {
+ it('should update querystring and view state', function() {
+ var updateState = {fullscreen: true, edit: true, panelId: 1};
+ viewState.update(updateState);
+ expect(location.search()).to.eql({fullscreen: true, edit: true, panelId: 1, orgId: 1});
+ expect(viewState.dashboard.meta.fullscreen).to.be(true);
+ expect(viewState.state.fullscreen).to.be(true);
+ });
+ });
+
+ describe('to fullscreen false', function() {
+ it('should remove params from query string', function() {
+ viewState.update({fullscreen: true, panelId: 1, edit: true});
+ viewState.update({fullscreen: false});
+ expect(viewState.dashboard.meta.fullscreen).to.be(false);
+ expect(viewState.state.fullscreen).to.be(null);
+ });
+ });
+});
diff --git a/public/app/features/org/profile_ctrl.ts b/public/app/features/org/profile_ctrl.ts
index 84999abafad..7e2cc1425ab 100644
--- a/public/app/features/org/profile_ctrl.ts
+++ b/public/app/features/org/profile_ctrl.ts
@@ -1,5 +1,3 @@
-///