diff --git a/docs/sources/reference/keyboard_shortcuts.md b/docs/sources/reference/keyboard_shortcuts.md index c9da3d5c6a1..9947418ae5a 100644 --- a/docs/sources/reference/keyboard_shortcuts.md +++ b/docs/sources/reference/keyboard_shortcuts.md @@ -18,10 +18,10 @@ Press `Shift`+`?` to open the keyboard shortcut dialog from anywhere within the |---|---| |`Esc`|Exit fullscreen edit/view mode, close search or any editor view| |`F`|Open dashboard search view (also contains import/playlist controls)| +|`R`|Refresh (Fetches new data and rerenders panels)| |`CTRL`+`S`|Save dashboard| |`CTRL`+`H`|Hide row controls| |`CTRL`+`Z`|Zoom out| -|`CTRL`+`R`|Refresh (Fetches new data and rerenders panels)| |`CTRL`+`O`|Enable/Disable shared graph crosshair| diff --git a/package.json b/package.json index d6a7719601d..ef79f3367d8 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "grunt-ng-annotate": "^1.0.1", "grunt-string-replace": "~1.2.1", "grunt-systemjs-builder": "^0.2.5", - "grunt-tslint": "^2.5.0", + "grunt-tslint": "^3.0.1", "grunt-typescript": "^0.8.0", "grunt-usemin": "3.0.0", "jshint-stylish": "~0.1.5", @@ -70,7 +70,7 @@ "lodash": "^2.4.1", "sinon": "1.16.1", "systemjs-builder": "^0.14.15", - "tslint": "^3.2.0", + "tslint": "^3.2.1", "typescript": "^1.7.5" } } diff --git a/pkg/api/dtos/apps.go b/pkg/api/dtos/apps.go index c520b6921a1..88886e40dd9 100644 --- a/pkg/api/dtos/apps.go +++ b/pkg/api/dtos/apps.go @@ -10,6 +10,7 @@ type AppSettings struct { AppId string `json:"appId"` Enabled bool `json:"enabled"` Pinned bool `json:"pinned"` + Module string `json:"module"` Info *plugins.PluginInfo `json:"info"` Pages []*plugins.AppPluginPage `json:"pages"` JsonData map[string]interface{} `json:"jsonData"` @@ -17,10 +18,11 @@ type AppSettings struct { func NewAppSettingsDto(def *plugins.AppPlugin, data *models.AppSettings) *AppSettings { dto := &AppSettings{ - AppId: def.Id, - Name: def.Name, - Info: &def.Info, - Pages: def.Pages, + AppId: def.Id, + Name: def.Name, + Info: &def.Info, + Module: def.Module, + Pages: def.Pages, } if data != nil { diff --git a/public/app/core/controllers/grafana_ctrl.ts b/public/app/core/controllers/grafana_ctrl.ts index 9802a2e3b15..2363ffb94b1 100644 --- a/public/app/core/controllers/grafana_ctrl.ts +++ b/public/app/core/controllers/grafana_ctrl.ts @@ -47,13 +47,13 @@ coreModule.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, $rootSc }; $rootScope.colors = [ - "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1 - "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2 - "#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3 - "#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4 - "#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5 - "#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6 - "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7 + "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", + "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", + "#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", + "#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", + "#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", + "#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", + "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" ]; $scope.getTotalWatcherCount = function() { @@ -85,6 +85,7 @@ coreModule.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, $rootSc $scope.$watch(function digestCounter() { count++; }, function() { + // something }); $rootScope.performance.panels = []; diff --git a/public/app/core/controllers/signup_ctrl.ts b/public/app/core/controllers/signup_ctrl.ts index 7b11e0b72c4..36544586a31 100644 --- a/public/app/core/controllers/signup_ctrl.ts +++ b/public/app/core/controllers/signup_ctrl.ts @@ -1,6 +1,5 @@ /// -import angular from 'angular'; import config from 'app/core/config'; import coreModule from '../core_module'; @@ -8,10 +7,10 @@ export class SignUpCtrl { /** @ngInject */ constructor( - private $scope : any, - private $location : any, - private contextSrv : any, - private backendSrv : any) { + private $scope: any, + private $location: any, + private contextSrv: any, + private backendSrv: any) { contextSrv.sidemenu = false; $scope.ctrl = this; diff --git a/public/app/core/core.ts b/public/app/core/core.ts index d50f330ad53..6882a87da69 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -17,15 +17,15 @@ import "./directives/spectrum_picker"; import "./directives/tags"; import "./directives/topnav"; import "./directives/value_select_dropdown"; +import "./directives/give_focus"; import './jquery_extended'; import './partials'; import {arrayJoin} from './directives/array_join'; -import * as controllers from 'app/core/controllers/all'; -import * as services from 'app/core/services/all'; -import * as routes from 'app/core/routes/all'; +import 'app/core/controllers/all'; +import 'app/core/services/all'; +import 'app/core/routes/all'; import './filters/filters'; +import coreModule from './core_module'; -// export * from './directives/give_focus' - -export {arrayJoin, controllers, services, routes}; +export {arrayJoin, coreModule}; diff --git a/public/app/core/directives/array_join.ts b/public/app/core/directives/array_join.ts index 538780e0695..6eb1cc0dd65 100644 --- a/public/app/core/directives/array_join.ts +++ b/public/app/core/directives/array_join.ts @@ -1,7 +1,6 @@ /// import _ from 'lodash'; -import angular from 'angular'; import coreModule from '../core_module'; export function arrayJoin() { diff --git a/public/app/core/directives/give_focus.ts b/public/app/core/directives/give_focus.ts index 16d0df3fc33..722cc4e5c10 100644 --- a/public/app/core/directives/give_focus.ts +++ b/public/app/core/directives/give_focus.ts @@ -1,9 +1,8 @@ /// -import angular = require('angular'); import coreModule from '../core_module'; -coreModule.default.directive('giveFocus', function() { +coreModule.directive('giveFocus', function() { return function(scope, element, attrs) { element.click(function(e) { e.stopPropagation(); diff --git a/public/app/core/filters/filters.ts b/public/app/core/filters/filters.ts index 7ac7ba6b01a..57b2b1bd9ba 100644 --- a/public/app/core/filters/filters.ts +++ b/public/app/core/filters/filters.ts @@ -1,6 +1,5 @@ /// -import jquery from 'jquery'; import _ from 'lodash'; import angular from 'angular'; import moment from 'moment'; @@ -59,7 +58,7 @@ coreModule.filter('noXml', function() { }); coreModule.filter('interpolateTemplateVars', function (templateSrv) { - var filterFunc : any = function (text, scope) { + var filterFunc: any = function(text, scope) { if (scope.panel) { return templateSrv.replaceWithText(text, scope.panel.scopedVars); } else { diff --git a/public/app/core/services/all.js b/public/app/core/services/all.js index 2d4415a8fa2..96a4ec77737 100644 --- a/public/app/core/services/all.js +++ b/public/app/core/services/all.js @@ -9,5 +9,6 @@ define([ './popover_srv', './segment_srv', './backend_srv', + './dynamic_directive_srv', ], function () {}); diff --git a/public/app/core/services/dynamic_directive_srv.ts b/public/app/core/services/dynamic_directive_srv.ts new file mode 100644 index 00000000000..d6cc8a19184 --- /dev/null +++ b/public/app/core/services/dynamic_directive_srv.ts @@ -0,0 +1,64 @@ +/// + +import angular from 'angular'; +import coreModule from '../core_module'; + +class DynamicDirectiveSrv { + + /** @ngInject */ + constructor(private $compile, private $parse, private $rootScope) {} + + addDirective(element, name, scope) { + var child = angular.element(document.createElement(name)); + this.$compile(child)(scope); + + element.empty(); + element.append(child); + } + + link(scope, elem, attrs, options) { + options.directive(scope).then(directiveInfo => { + if (!directiveInfo || !directiveInfo.fn) { + elem.empty(); + return; + } + + if (!directiveInfo.fn.registered) { + coreModule.directive(attrs.$normalize(directiveInfo.name), directiveInfo.fn); + directiveInfo.fn.registered = true; + } + + this.addDirective(elem, directiveInfo.name, scope); + }).catch(err => { + console.log('Plugin load:', err); + this.$rootScope.appEvent('alert-error', ['Plugin error', err.toString()]); + }); + } + + create(options) { + let directiveDef = { + restrict: 'E', + scope: options.scope, + link: (scope, elem, attrs) => { + if (options.watch) { + let childScope = null; + scope.$watch(options.watch, () => { + if (childScope) { + childScope.$destroy(); + } + childScope = scope.$new(); + this.link(childScope, elem, attrs, options); + }); + } else { + this.link(scope, elem, attrs, options); + } + } + }; + + return directiveDef; + } +} + +coreModule.service('dynamicDirectiveSrv', DynamicDirectiveSrv); + + diff --git a/public/app/core/time_series2.ts b/public/app/core/time_series2.ts index 1bc6dc93854..c8384b5b40a 100644 --- a/public/app/core/time_series2.ts +++ b/public/app/core/time_series2.ts @@ -132,7 +132,7 @@ export default class TimeSeries { } } - if (currentValue != 0) { + if (currentValue !== 0) { this.allIsZero = false; } diff --git a/public/app/core/utils/datemath.ts b/public/app/core/utils/datemath.ts index 55da5b42b30..b66325c71a8 100644 --- a/public/app/core/utils/datemath.ts +++ b/public/app/core/utils/datemath.ts @@ -4,11 +4,6 @@ import _ from 'lodash'; import moment from 'moment'; var units = ['y', 'M', 'w', 'd', 'h', 'm', 's']; -var unitsAsc = _.sortBy(units, function (unit) { - return moment.duration(1, unit).valueOf(); -}); - -var unitsDesc = unitsAsc.reverse(); export function parse(text, roundUp?) { if (!text) { return undefined; } @@ -104,8 +99,7 @@ export function parseDateMath(mathString, time, roundUp?) { if (type === 0) { if (roundUp) { dateTime.endOf(unit); - } - else { + } else { dateTime.startOf(unit); } } else if (type === 1) { diff --git a/public/app/core/utils/rangeutil.ts b/public/app/core/utils/rangeutil.ts index 5647056e2a1..7f2f2fd72b1 100644 --- a/public/app/core/utils/rangeutil.ts +++ b/public/app/core/utils/rangeutil.ts @@ -1,7 +1,6 @@ /// import _ from 'lodash'; -import angular from 'angular'; import moment from 'moment'; import * as dateMath from './datemath'; diff --git a/public/app/features/all.js b/public/app/features/all.js index 9019ec06172..d4436c9deaa 100644 --- a/public/app/features/all.js +++ b/public/app/features/all.js @@ -1,7 +1,7 @@ define([ './panellinks/module', './dashlinks/module', - './annotations/annotationsSrv', + './annotations/annotations_srv', './templating/templateSrv', './dashboard/all', './playlist/all', diff --git a/public/app/features/annotations/annotationsSrv.js b/public/app/features/annotations/annotations_srv.js similarity index 98% rename from public/app/features/annotations/annotationsSrv.js rename to public/app/features/annotations/annotations_srv.js index c58c5ff6d4e..9dce53084fe 100644 --- a/public/app/features/annotations/annotationsSrv.js +++ b/public/app/features/annotations/annotations_srv.js @@ -1,7 +1,8 @@ define([ 'angular', 'lodash', - './editorCtrl' + './editor_ctrl', + './query_editor' ], function (angular, _) { 'use strict'; diff --git a/public/app/features/annotations/editorCtrl.js b/public/app/features/annotations/editor_ctrl.js similarity index 86% rename from public/app/features/annotations/editorCtrl.js rename to public/app/features/annotations/editor_ctrl.js index 5a509fe4ca9..69bc07ae26d 100644 --- a/public/app/features/annotations/editorCtrl.js +++ b/public/app/features/annotations/editor_ctrl.js @@ -31,10 +31,10 @@ function (angular, _, $) { }; $scope.datasourceChanged = function() { - $scope.currentDatasource = _.findWhere($scope.datasources, { name: $scope.currentAnnotation.datasource }); - if (!$scope.currentDatasource) { - $scope.currentDatasource = $scope.datasources[0]; - } + datasourceSrv.get($scope.currentAnnotation.datasource).then(function(ds) { + $scope.currentDatasource = ds; + $scope.currentAnnotation.datasource = ds.name; + }); }; $scope.edit = function(annotation) { @@ -50,7 +50,6 @@ function (angular, _, $) { $scope.currentAnnotation = angular.copy(annotationDefaults); $scope.currentIsNew = true; $scope.datasourceChanged(); - $scope.currentAnnotation.datasource = $scope.currentDatasource.name; }; $scope.update = function() { diff --git a/public/app/features/annotations/partials/editor.html b/public/app/features/annotations/partials/editor.html index 875f205c1f9..35adae93137 100644 --- a/public/app/features/annotations/partials/editor.html +++ b/public/app/features/annotations/partials/editor.html @@ -91,7 +91,8 @@ - + +
diff --git a/public/app/features/annotations/query_editor.ts b/public/app/features/annotations/query_editor.ts new file mode 100644 index 00000000000..387f17e1454 --- /dev/null +++ b/public/app/features/annotations/query_editor.ts @@ -0,0 +1,25 @@ +/// + +import angular from 'angular'; + +/** @ngInject */ +function annotationsQueryEditor(dynamicDirectiveSrv) { + return dynamicDirectiveSrv.create({ + scope: { + annotation: "=", + datasource: "=" + }, + watch: "datasource.type", + directive: scope => { + return System.import(scope.datasource.meta.module).then(function(dsModule) { + return { + name: 'annotation-query-editor-' + scope.datasource.meta.id, + fn: dsModule.annotationsQueryEditor, + }; + }); + }, + }); +} + + +angular.module('grafana.directives').directive('annotationsQueryEditor', annotationsQueryEditor); diff --git a/public/app/features/apps/all.ts b/public/app/features/apps/all.ts index fcdd27dff4d..6f15e8b1bf6 100644 --- a/public/app/features/apps/all.ts +++ b/public/app/features/apps/all.ts @@ -1,2 +1,3 @@ import './edit_ctrl'; import './list_ctrl'; +import './config_view'; diff --git a/public/app/features/apps/app_srv.ts b/public/app/features/apps/app_srv.ts deleted file mode 100644 index 18c6979b388..00000000000 --- a/public/app/features/apps/app_srv.ts +++ /dev/null @@ -1,43 +0,0 @@ -/// - -import _ from 'lodash'; -import angular from 'angular'; - -export class AppSrv { - apps: any = {}; - - /** @ngInject */ - constructor( - private $rootScope, - private $timeout, - private $q, - private backendSrv) { - } - - get(type) { - return this.getAll().then(() => { - return this.apps[type]; - }); - } - - getAll() { - if (!_.isEmpty(this.apps)) { - return this.$q.when(this.apps); - } - - return this.backendSrv.get('api/org/apps').then(results => { - return results.reduce((prev, current) => { - prev[current.type] = current; - return prev; - }, this.apps); - }); - } - - update(app) { - return this.backendSrv.post('api/org/apps', app).then(resp => { - - }); - } -} - -angular.module('grafana.services').service('appSrv', AppSrv); diff --git a/public/app/features/apps/config_view.ts b/public/app/features/apps/config_view.ts new file mode 100644 index 00000000000..31e85967cb2 --- /dev/null +++ b/public/app/features/apps/config_view.ts @@ -0,0 +1,23 @@ +/// + +import angular from 'angular'; + +/** @ngInject */ +function appConfigView(dynamicDirectiveSrv) { + return dynamicDirectiveSrv.create({ + scope: { + appModel: "=" + }, + directive: scope => { + return System.import(scope.appModel.module).then(function(appModule) { + return { + name: 'app-config-' + scope.appModel.appId, + fn: appModule.configView, + }; + }); + }, + }); +} + + +angular.module('grafana.directives').directive('appConfigView', appConfigView); diff --git a/public/app/features/apps/edit_ctrl.ts b/public/app/features/apps/edit_ctrl.ts index fe77c0a6797..2cbeb4a2ae1 100644 --- a/public/app/features/apps/edit_ctrl.ts +++ b/public/app/features/apps/edit_ctrl.ts @@ -1,6 +1,5 @@ /// -import config from 'app/core/config'; import angular from 'angular'; import _ from 'lodash'; diff --git a/public/app/features/apps/list_ctrl.ts b/public/app/features/apps/list_ctrl.ts index 3f195536ae4..142f0e12b0a 100644 --- a/public/app/features/apps/list_ctrl.ts +++ b/public/app/features/apps/list_ctrl.ts @@ -1,6 +1,5 @@ /// -import config = require('app/core/config'); import angular from 'angular'; export class AppListCtrl { diff --git a/public/app/features/apps/partials/edit.html b/public/app/features/apps/partials/edit.html index ff022299ac2..91b174cb6a3 100644 --- a/public/app/features/apps/partials/edit.html +++ b/public/app/features/apps/partials/edit.html @@ -29,7 +29,6 @@ Version: {{ctrl.appModel.info.version}}     Updated: {{ctrl.appModel.info.updated}} -

@@ -94,9 +93,12 @@

Configuration:

+
+ +
- + diff --git a/public/app/features/dashboard/keybindings.js b/public/app/features/dashboard/keybindings.js index 2219cab2d4c..32980f46d7b 100644 --- a/public/app/features/dashboard/keybindings.js +++ b/public/app/features/dashboard/keybindings.js @@ -52,7 +52,7 @@ function(angular, $) { scope.appEvent('save-dashboard', evt); }, { inputDisabled: true }); - keyboardManager.bind('ctrl+r', function() { + keyboardManager.bind('r', function() { scope.broadcastRefresh(); }, { inputDisabled: true }); diff --git a/public/app/features/dashboard/timepicker/input_date.ts b/public/app/features/dashboard/timepicker/input_date.ts index 66173ffd1b9..cf215b9e25e 100644 --- a/public/app/features/dashboard/timepicker/input_date.ts +++ b/public/app/features/dashboard/timepicker/input_date.ts @@ -1,7 +1,5 @@ /// -import _ from 'lodash'; -import angular from 'angular'; import moment from 'moment'; export function inputDateDirective() { diff --git a/public/app/features/dashboard/timepicker/timepicker.ts b/public/app/features/dashboard/timepicker/timepicker.ts index b44f6ed8a9a..c8d319a12f2 100644 --- a/public/app/features/dashboard/timepicker/timepicker.ts +++ b/public/app/features/dashboard/timepicker/timepicker.ts @@ -1,19 +1,17 @@ /// import _ from 'lodash'; -import kbn from 'app/core/utils/kbn'; import angular from 'angular'; import moment from 'moment'; -import * as dateMath from 'app/core/utils/datemath'; import * as rangeUtil from 'app/core/utils/rangeutil'; export class TimePickerCtrl { static tooltipFormat = 'MMM D, YYYY HH:mm:ss'; static defaults = { - time_options : ['5m','15m','1h','6h','12h','24h','2d','7d','30d'], - refresh_intervals : ['5s','10s','30s','1m','5m','15m','30m','1h','2h','1d'], + time_options: ['5m', '15m', '1h', '6h', '12h', '24h', '2d', '7d', '30d'], + refresh_intervals: ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'], }; dashboard: any; diff --git a/public/app/features/datasources/all.js b/public/app/features/datasources/all.js index b181fd475c2..8a57bbc5b8a 100644 --- a/public/app/features/datasources/all.js +++ b/public/app/features/datasources/all.js @@ -1,4 +1,5 @@ define([ './list_ctrl', './edit_ctrl', + './config_view', ], function () {}); diff --git a/public/app/features/datasources/config_view.ts b/public/app/features/datasources/config_view.ts new file mode 100644 index 00000000000..8be06bbcdfd --- /dev/null +++ b/public/app/features/datasources/config_view.ts @@ -0,0 +1,25 @@ +/// + +import angular from 'angular'; + +/** @ngInject */ +function dsConfigView(dynamicDirectiveSrv) { + return dynamicDirectiveSrv.create({ + scope: { + dsMeta: "=", + current: "=" + }, + watch: "dsMeta.module", + directive: scope => { + return System.import(scope.dsMeta.module).then(function(dsModule) { + return { + name: 'ds-config-' + scope.dsMeta.id, + fn: dsModule.configView, + }; + }); + }, + }); +} + + +angular.module('grafana.directives').directive('dsConfigView', dsConfigView); diff --git a/public/app/features/datasources/partials/edit.html b/public/app/features/datasources/partials/edit.html index fda9acde58a..24375506b85 100644 --- a/public/app/features/datasources/partials/edit.html +++ b/public/app/features/datasources/partials/edit.html @@ -42,7 +42,7 @@
- +
Testing....
diff --git a/public/app/features/panel/all.js b/public/app/features/panel/all.js index bb687b035b5..ef3ea9b9b0f 100644 --- a/public/app/features/panel/all.js +++ b/public/app/features/panel/all.js @@ -4,4 +4,6 @@ define([ './panel_srv', './panel_helper', './solo_panel_ctrl', + './panel_loader', + './query_editor', ], function () {}); diff --git a/public/app/features/panel/panel_directive.js b/public/app/features/panel/panel_directive.js index 0c3ab11a9a7..b4c1a2d6a0f 100644 --- a/public/app/features/panel/panel_directive.js +++ b/public/app/features/panel/panel_directive.js @@ -1,32 +1,12 @@ define([ 'angular', 'jquery', - 'app/core/config', ], -function (angular, $, config) { +function (angular, $) { 'use strict'; var module = angular.module('grafana.directives'); - module.directive('panelLoader', function($compile, $parse) { - return { - restrict: 'E', - link: function(scope, elem, attr) { - var getter = $parse(attr.type), panelType = getter(scope); - var module = config.panels[panelType].module; - - System.import(module).then(function() { - var panelEl = angular.element(document.createElement('grafana-panel-' + panelType)); - elem.append(panelEl); - $compile(panelEl)(scope); - }).catch(function(err) { - console.log('Failed to load panel:', err); - scope.appEvent('alert-error', ['Panel Load Error', 'Failed to load panel ' + panelType + ', ' + err]); - }); - } - }; - }); - module.directive('grafanaPanel', function() { return { restrict: 'E', @@ -43,103 +23,6 @@ function (angular, $, config) { }; }); - module.directive('datasourceCustomSettingsView', function($compile) { - return { - restrict: 'E', - scope: { - dsMeta: "=", - current: "=", - }, - link: function(scope, elem) { - scope.$watch("dsMeta.module", function() { - if (!scope.dsMeta) { - return; - } - - System.import(scope.dsMeta.module).then(function() { - elem.empty(); - var panelEl = angular.element(document.createElement('datasource-custom-settings-view-' + scope.dsMeta.id)); - elem.append(panelEl); - $compile(panelEl)(scope); - }).catch(function(err) { - console.log('Failed to load plugin:', err); - scope.appEvent('alert-error', ['Plugin Load Error', 'Failed to load plugin ' + scope.dsMeta.id + ', ' + err]); - }); - }); - } - }; - }); - - module.service('dynamicDirectiveSrv', function($compile, $parse, datasourceSrv) { - var self = this; - - this.addDirective = function(options, type, editorScope) { - var panelEl = angular.element(document.createElement(options.name + '-' + type)); - options.parentElem.append(panelEl); - $compile(panelEl)(editorScope); - }; - - this.define = function(options) { - var editorScope; - options.scope.$watch(options.datasourceProperty, function(newVal) { - if (editorScope) { - editorScope.$destroy(); - options.parentElem.empty(); - } - - editorScope = options.scope.$new(); - datasourceSrv.get(newVal).then(function(ds) { - self.addDirective(options, ds.meta.id, editorScope); - }); - }); - }; - }); - - module.directive('datasourceEditorView', function(dynamicDirectiveSrv) { - return { - restrict: 'E', - link: function(scope, elem, attrs) { - dynamicDirectiveSrv.define({ - datasourceProperty: attrs.datasource, - name: attrs.name, - scope: scope, - parentElem: elem, - }); - } - }; - }); - - module.directive('queryEditorLoader', function($compile, $parse, datasourceSrv) { - return { - restrict: 'E', - link: function(scope, elem) { - var editorScope; - - scope.$watch("panel.datasource", function() { - var datasource = scope.target.datasource || scope.panel.datasource; - - datasourceSrv.get(datasource).then(function(ds) { - if (editorScope) { - editorScope.$destroy(); - elem.empty(); - } - - editorScope = scope.$new(); - editorScope.datasource = ds; - - if (!scope.target.refId) { - scope.target.refId = 'A'; - } - - var panelEl = angular.element(document.createElement('metric-query-editor-' + ds.meta.id)); - elem.append(panelEl); - $compile(panelEl)(editorScope); - }); - }); - } - }; - }); - module.directive('panelResizer', function($rootScope) { return { restrict: 'E', diff --git a/public/app/features/panel/panel_loader.ts b/public/app/features/panel/panel_loader.ts new file mode 100644 index 00000000000..ebcb8d2631c --- /dev/null +++ b/public/app/features/panel/panel_loader.ts @@ -0,0 +1,22 @@ +/// + +import angular from 'angular'; +import config from 'app/core/config'; + +/** @ngInject */ +function panelLoader($parse, dynamicDirectiveSrv) { + return dynamicDirectiveSrv.create({ + directive: scope => { + let modulePath = config.panels[scope.panel.type].module; + + return System.import(modulePath).then(function(panelModule) { + return { + name: 'panel-directive-' + scope.panel.type, + fn: panelModule.panel, + }; + }); + }, + }); +} + +angular.module('grafana.directives').directive('panelLoader', panelLoader); diff --git a/public/app/features/panel/query_editor.ts b/public/app/features/panel/query_editor.ts new file mode 100644 index 00000000000..b360ad2aa79 --- /dev/null +++ b/public/app/features/panel/query_editor.ts @@ -0,0 +1,48 @@ +/// + +import angular from 'angular'; + +/** @ngInject */ +function metricsQueryEditor(dynamicDirectiveSrv, datasourceSrv) { + return dynamicDirectiveSrv.create({ + watch: "panel.datasource", + directive: scope => { + let datasource = scope.target.datasource || scope.panel.datasource; + return datasourceSrv.get(datasource).then(ds => { + scope.datasource = ds; + + if (!scope.target.refId) { + scope.target.refId = 'A'; + } + + return System.import(ds.meta.module).then(dsModule => { + return { + name: 'metrics-query-editor-' + ds.meta.id, + fn: dsModule.metricsQueryEditor, + }; + }); + }); + } + }); +} + +/** @ngInject */ +function metricsQueryOptions(dynamicDirectiveSrv, datasourceSrv) { + return dynamicDirectiveSrv.create({ + watch: "panel.datasource", + directive: scope => { + return datasourceSrv.get(scope.panel.datasource).then(ds => { + return System.import(ds.meta.module).then(dsModule => { + return { + name: 'metrics-query-options-' + ds.meta.id, + fn: dsModule.metricsQueryOptions + }; + }); + }); + } + }); +} + +angular.module('grafana.directives') + .directive('metricsQueryEditor', metricsQueryEditor) + .directive('metricsQueryOptions', metricsQueryOptions); diff --git a/public/app/features/playlist/specs/playlist-edit-ctrl-specs.ts b/public/app/features/playlist/specs/playlist-edit-ctrl-specs.ts index e0ffdd49893..4a71979b8ab 100644 --- a/public/app/features/playlist/specs/playlist-edit-ctrl-specs.ts +++ b/public/app/features/playlist/specs/playlist-edit-ctrl-specs.ts @@ -1,5 +1,5 @@ import '../playlist_edit_ctrl'; -import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; +import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common'; import helpers from 'test/specs/helpers'; describe('PlaylistEditCtrl', function() { @@ -83,4 +83,4 @@ describe('PlaylistEditCtrl', function() { }); }); }); -}); \ No newline at end of file +}); diff --git a/public/app/grafana.ts b/public/app/grafana.ts index 11d521e6757..7316a8164bc 100644 --- a/public/app/grafana.ts +++ b/public/app/grafana.ts @@ -8,12 +8,12 @@ import 'angular-sanitize'; import 'angular-dragdrop'; import 'angular-bindonce'; import 'angular-ui'; -import 'app/core/core'; import $ from 'jquery'; import angular from 'angular'; import config from 'app/core/config'; import _ from 'lodash'; +import {coreModule} from './core/core'; export class GrafanaApp { registerFunctions: any; @@ -67,6 +67,9 @@ export class GrafanaApp { this.useModule(angular.module(moduleName, [])); }); + // makes it possible to add dynamic stuff + this.useModule(coreModule); + var preBootRequires = [System.import('app/features/all')]; var pluginModules = config.bootData.pluginModules || []; diff --git a/public/app/panels/graph/alerting.html b/public/app/panels/graph/alerting.html deleted file mode 100644 index 9e08fc4f6cb..00000000000 --- a/public/app/panels/graph/alerting.html +++ /dev/null @@ -1,220 +0,0 @@ -
- Last updated by Grafana October 4, 2015 12:15:04 by $username -
-
General Alerting Options
-
-
    -
  • - Alert Title -
  • -
  • - -
  • -
  • - Alerting Backend -
  • -
  • - -
  • -
  • - - - -
  • -
-
-
-
-
-
-
Choose your query:
-

Select an exising query to alert on:

-
-
    -
  • -
  • None
  • -
-
-
-
-
    -
  • -
  • A
  • -
  • apps
  • -
  • -
  • fakesite
  • -
  • counters
  • -
  • requests
  • -
  • count
  • -
  • scaleToSeconds(1)
  • -
  • aliasByNode(2)
  • -
  • -
-
-
-
-
    -
  • -
  • B
  • -
  • Metric: us-west-2 AWS/EC2 CPUUtilization Stats: Minimum Maximum Dimensions InstanceIS = i-b0e8a447 Alias {{stat}} Period 60
  • -
  • -
-
-
-
-
    -
  • -
  • C
  • -
  • Query: avg(counters_logins) by(server) Legend Format: {{app}} - {{server}} Step: 1s Resolution: 1/2
  • -
  • -
-
-
-
-
    -
  • -
  • D
  • -
  • SELECT mean(value) FROM logins.count WHERE hostname = /$Hostname$/ GROUP BY time($internal) hostname
  • -
  • -
-
-
-
-
    -
  • -
  • E
  • -
  • Metric: apps.backend.backend_01.counters.requests.count Alias: Bristow Aggregator: Sum Downsample: 1m Aggregator Sum Tags host = test
  • -
  • -
-
-
-
-
-

Or write a new custom alerting query:

-
-
-
    -
  • -
  • - - - -
  • -
  • - select metric -
  • -
  • - -
  • -
-
-
-
-
-
-
-
Define Your States
-
-
    -
  • - by -
  • -
  • - -
  • -
  • - the values in the query over the last -
  • -
  • - -
  • -
-
-
-
-
-
-
-
-
    -
  • - Warn -
  • -
  • - -
  • -
  • - -
  • -
  • - .notify -
  • -
  • - -
  • -
  • - - - -
  • -
-
-
-
-
    -
  • - Critical -
  • -
  • - -
  • -
  • - -
  • -
  • - .notify -
  • -
  • - -
  • -
  • - - - -
  • -
-
-
-
-
-
-
-
What to Say Variables | Preview
-
-
    -
  • - Summary -
  • -
  • - -
  • -
-
-
-
-
    -
  • - Description -
  • -
  • - -
  • -
-
-
-
-
diff --git a/public/app/partials/dashboard.html b/public/app/partials/dashboard.html index 09c80fd3341..3234903ff8c 100644 --- a/public/app/partials/dashboard.html +++ b/public/app/partials/dashboard.html @@ -83,7 +83,7 @@
- +
diff --git a/public/app/partials/help_modal.html b/public/app/partials/help_modal.html index 5d7abfe2490..4fc29d1065d 100644 --- a/public/app/partials/help_modal.html +++ b/public/app/partials/help_modal.html @@ -24,6 +24,10 @@ F Open dashboard search view (also contains import/playlist controls) + + R + Refresh (Fetches new data and rerenders panels) + CTRL+S Save dashboard @@ -36,10 +40,6 @@ CTRL+Z Zoom out - - CTRL+R - Refresh (Fetches new data and rerenders panels) - CTRL+O Enable/Disable shared graph crosshair diff --git a/public/app/partials/metrics.html b/public/app/partials/metrics.html index 215bc2dd945..b5ff9f50327 100644 --- a/public/app/partials/metrics.html +++ b/public/app/partials/metrics.html @@ -1,8 +1,8 @@
- - + +
@@ -26,7 +26,7 @@
- +
diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index a2a74ca1883..6f9f24ff867 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -3,7 +3,6 @@ define([ 'lodash', 'moment', 'app/core/utils/datemath', - './query_ctrl', ], function (angular, _, moment, dateMath) { 'use strict'; diff --git a/public/app/plugins/datasource/cloudwatch/module.js b/public/app/plugins/datasource/cloudwatch/module.js index bb9d9102063..b4e6c2b6ccb 100644 --- a/public/app/plugins/datasource/cloudwatch/module.js +++ b/public/app/plugins/datasource/cloudwatch/module.js @@ -1,39 +1,27 @@ define([ - 'angular', './datasource', './query_parameter_ctrl', + './query_ctrl', ], -function (angular, CloudWatchDatasource) { +function (CloudWatchDatasource) { 'use strict'; - var module = angular.module('grafana.directives'); - - module.directive('metricQueryEditorCloudwatch', function() { + function metricsQueryEditor() { return {controller: 'CloudWatchQueryCtrl', templateUrl: 'app/plugins/datasource/cloudwatch/partials/query.editor.html'}; - }); + } - module.directive('annotationsQueryEditorCloudwatch', function() { + function annotationsQueryEditor() { return {templateUrl: 'app/plugins/datasource/cloudwatch/partials/annotations.editor.html'}; - }); + } - module.directive('cloudwatchQueryParameter', function() { - return { - templateUrl: 'app/plugins/datasource/cloudwatch/partials/query.parameter.html', - controller: 'CloudWatchQueryParameterCtrl', - restrict: 'E', - scope: { - target: "=", - datasourceName: "@", - onChange: "&", - } - }; - }); - - module.directive('datasourceCustomSettingsViewCloudwatch', function() { + function configView() { return {templateUrl: 'app/plugins/datasource/cloudwatch/partials/edit_view.html'}; - }); + } return { - Datasource: CloudWatchDatasource + Datasource: CloudWatchDatasource, + configView: configView, + annotationsQueryEditor: annotationsQueryEditor, + metricsQueryEditor: metricsQueryEditor, }; }); diff --git a/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html b/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html index dfecb80032d..583a578134d 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html +++ b/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html @@ -1 +1 @@ - + diff --git a/public/app/plugins/datasource/cloudwatch/partials/query.editor.html b/public/app/plugins/datasource/cloudwatch/partials/query.editor.html index d2dd9d84436..09ac6279c12 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/query.editor.html +++ b/public/app/plugins/datasource/cloudwatch/partials/query.editor.html @@ -35,4 +35,4 @@
- + diff --git a/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js b/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js index 0ed23aff205..3be1f0d5a01 100644 --- a/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js +++ b/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js @@ -7,6 +7,19 @@ function (angular, _) { var module = angular.module('grafana.controllers'); + module.directive('cloudwatchQueryParameter', function() { + return { + templateUrl: 'app/plugins/datasource/cloudwatch/partials/query.parameter.html', + controller: 'CloudWatchQueryParameterCtrl', + restrict: 'E', + scope: { + target: "=", + datasource: "=", + onChange: "&", + } + }; + }); + module.controller('CloudWatchQueryParameterCtrl', function($scope, templateSrv, uiSegmentSrv, datasourceSrv, $q) { $scope.init = function() { @@ -38,12 +51,9 @@ function (angular, _) { $scope.removeDimSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove dimension --'}); $scope.removeStatSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove stat --'}); - datasourceSrv.get($scope.datasourceName).then(function(datasource) { - $scope.datasource = datasource; - if (_.isEmpty($scope.target.region)) { - $scope.target.region = $scope.datasource.getDefaultRegion(); - } - }); + if (_.isEmpty($scope.target.region)) { + $scope.target.region = $scope.datasource.getDefaultRegion(); + } if (!$scope.onChange) { $scope.onChange = function() {}; diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index 7c5c06839de..ed5f9418f8f 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -228,10 +228,8 @@ describe('CloudWatchDatasource', function() { switch (params.data.action) { case 'DescribeAlarmsForMetric': return ctx.$q.when({data: alarmResponse}); - break; case 'DescribeAlarmHistory': return ctx.$q.when({data: historyResponse}); - break; } }; }); diff --git a/public/app/plugins/datasource/elasticsearch/bucket_agg.js b/public/app/plugins/datasource/elasticsearch/bucket_agg.js index 8f302d2b136..bda666b3602 100644 --- a/public/app/plugins/datasource/elasticsearch/bucket_agg.js +++ b/public/app/plugins/datasource/elasticsearch/bucket_agg.js @@ -8,6 +8,20 @@ function (angular, _, queryDef) { var module = angular.module('grafana.directives'); + module.directive('elasticBucketAgg', function() { + return { + templateUrl: 'app/plugins/datasource/elasticsearch/partials/bucket_agg.html', + controller: 'ElasticBucketAggCtrl', + restrict: 'E', + scope: { + target: "=", + index: "=", + onChange: "&", + getFields: "&", + } + }; + }); + module.controller('ElasticBucketAggCtrl', function($scope, uiSegmentSrv, $q, $rootScope) { var bucketAggs = $scope.target.bucketAggs; diff --git a/public/app/plugins/datasource/elasticsearch/metric_agg.js b/public/app/plugins/datasource/elasticsearch/metric_agg.js index 60a84e01e19..02a60b885bb 100644 --- a/public/app/plugins/datasource/elasticsearch/metric_agg.js +++ b/public/app/plugins/datasource/elasticsearch/metric_agg.js @@ -8,6 +8,21 @@ function (angular, _, queryDef) { var module = angular.module('grafana.directives'); + module.directive('elasticMetricAgg', function() { + return { + templateUrl: 'app/plugins/datasource/elasticsearch/partials/metric_agg.html', + controller: 'ElasticMetricAggCtrl', + restrict: 'E', + scope: { + target: "=", + index: "=", + onChange: "&", + getFields: "&", + esVersion: '=' + } + }; + }); + module.controller('ElasticMetricAggCtrl', function($scope, uiSegmentSrv, $q, $rootScope) { var metricAggs = $scope.target.metrics; diff --git a/public/app/plugins/datasource/elasticsearch/module.js b/public/app/plugins/datasource/elasticsearch/module.js index 958eb3eb8e7..171fa68308c 100644 --- a/public/app/plugins/datasource/elasticsearch/module.js +++ b/public/app/plugins/datasource/elasticsearch/module.js @@ -1,60 +1,30 @@ define([ - 'angular', './datasource', './edit_view', './bucket_agg', './metric_agg', ], -function (angular, ElasticDatasource, editView) { +function (ElasticDatasource, editView) { 'use strict'; - var module = angular.module('grafana.directives'); - - module.directive('metricQueryEditorElasticsearch', function() { + function metricsQueryEditor() { return {controller: 'ElasticQueryCtrl', templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.editor.html'}; - }); + } - module.directive('metricQueryOptionsElasticsearch', function() { + function metricsQueryOptions() { return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.options.html'}; - }); + } - module.directive('annotationsQueryEditorElasticsearch', function() { + function annotationsQueryEditor() { return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/annotations.editor.html'}; - }); - - module.directive('elasticMetricAgg', function() { - return { - templateUrl: 'app/plugins/datasource/elasticsearch/partials/metric_agg.html', - controller: 'ElasticMetricAggCtrl', - restrict: 'E', - scope: { - target: "=", - index: "=", - onChange: "&", - getFields: "&", - esVersion: '=' - } - }; - }); - - module.directive('elasticBucketAgg', function() { - return { - templateUrl: 'app/plugins/datasource/elasticsearch/partials/bucket_agg.html', - controller: 'ElasticBucketAggCtrl', - restrict: 'E', - scope: { - target: "=", - index: "=", - onChange: "&", - getFields: "&", - } - }; - }); - - module.directive('datasourceCustomSettingsViewElasticsearch', editView.default); + } return { Datasource: ElasticDatasource, + configView: editView.default, + annotationsQueryEditor: annotationsQueryEditor, + metricsQueryEditor: metricsQueryEditor, + metricsQueryOptions: metricsQueryOptions, }; }); diff --git a/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html b/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html index 4ef9a916866..0b7070904de 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html +++ b/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html @@ -1,14 +1,14 @@
-
+
Index name
- +
Search query (lucene) Use [[filterName]] in query to replace part of the query with a filter value
- +
@@ -18,22 +18,22 @@
Field mappings
- +
- +
- +
- +
diff --git a/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts index 3f3ebf745d2..c80fcb7169f 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts @@ -1,5 +1,5 @@ -import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; +import {describe, beforeEach, it, expect} from 'test/lib/common'; import ElasticResponse from '../elastic_response'; describe('ElasticResponse', function() { @@ -497,11 +497,6 @@ describe('ElasticResponse', function() { }); }); - describe('', function() { - - - }); - describe('Raw documents query', function() { beforeEach(function() { targets = [{ refId: 'A', metrics: [{type: 'raw_document', id: '1'}], bucketAggs: [] }]; diff --git a/public/app/plugins/datasource/grafana/module.ts b/public/app/plugins/datasource/grafana/module.ts index bfe2b041fb1..f52676adb1c 100644 --- a/public/app/plugins/datasource/grafana/module.ts +++ b/public/app/plugins/datasource/grafana/module.ts @@ -5,10 +5,14 @@ import {GrafanaDatasource} from './datasource'; var module = angular.module('grafana.directives'); -module.directive('metricQueryEditorGrafana', function() { +function grafanaMetricsQueryEditor() { return {templateUrl: 'app/plugins/datasource/grafana/partials/query.editor.html'}; -}); +} -export {GrafanaDatasource, GrafanaDatasource as Datasource}; +export { + GrafanaDatasource, + GrafanaDatasource as Datasource, + grafanaMetricsQueryEditor as metricsQueryEditor +}; diff --git a/public/app/plugins/datasource/graphite/module.js b/public/app/plugins/datasource/graphite/module.js index 72dd9eeaf0a..6d7592c421c 100644 --- a/public/app/plugins/datasource/graphite/module.js +++ b/public/app/plugins/datasource/graphite/module.js @@ -1,29 +1,30 @@ define([ - 'angular', './datasource', ], -function (angular, GraphiteDatasource) { +function (GraphiteDatasource) { 'use strict'; - var module = angular.module('grafana.directives'); - - module.directive('metricQueryEditorGraphite', function() { + function metricsQueryEditor() { return {controller: 'GraphiteQueryCtrl', templateUrl: 'app/plugins/datasource/graphite/partials/query.editor.html'}; - }); + } - module.directive('metricQueryOptionsGraphite', function() { + function metricsQueryOptions() { return {templateUrl: 'app/plugins/datasource/graphite/partials/query.options.html'}; - }); + } - module.directive('annotationsQueryEditorGraphite', function() { + function annotationsQueryEditor() { return {templateUrl: 'app/plugins/datasource/graphite/partials/annotations.editor.html'}; - }); + } - module.directive('datasourceCustomSettingsViewGraphite', function() { + function configView() { return {templateUrl: 'app/plugins/datasource/graphite/partials/config.html'}; - }); + } return { Datasource: GraphiteDatasource, + configView: configView, + annotationsQueryEditor: annotationsQueryEditor, + metricsQueryEditor: metricsQueryEditor, + metricsQueryOptions: metricsQueryOptions, }; }); diff --git a/public/app/plugins/datasource/graphite/partials/annotations.editor.html b/public/app/plugins/datasource/graphite/partials/annotations.editor.html index c5e4a98a84c..9253bf614a6 100644 --- a/public/app/plugins/datasource/graphite/partials/annotations.editor.html +++ b/public/app/plugins/datasource/graphite/partials/annotations.editor.html @@ -1,14 +1,14 @@
- +
- +
diff --git a/public/app/plugins/datasource/graphite/specs/gfunc_specs.ts b/public/app/plugins/datasource/graphite/specs/gfunc_specs.ts index ab49979826c..4f8f00d901b 100644 --- a/public/app/plugins/datasource/graphite/specs/gfunc_specs.ts +++ b/public/app/plugins/datasource/graphite/specs/gfunc_specs.ts @@ -1,6 +1,5 @@ -/// -import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; +import {describe, it, expect} from 'test/lib/common'; import gfunc from '../gfunc'; describe('when creating func instance from func names', function() { diff --git a/public/app/plugins/datasource/influxdb/module.js b/public/app/plugins/datasource/influxdb/module.js index 2a15ecad839..406690f4f2c 100644 --- a/public/app/plugins/datasource/influxdb/module.js +++ b/public/app/plugins/datasource/influxdb/module.js @@ -1,29 +1,30 @@ define([ - 'angular', './datasource', ], -function (angular, InfluxDatasource) { +function (InfluxDatasource) { 'use strict'; - var module = angular.module('grafana.directives'); - - module.directive('metricQueryEditorInfluxdb', function() { + function influxMetricsQueryEditor() { return {controller: 'InfluxQueryCtrl', templateUrl: 'app/plugins/datasource/influxdb/partials/query.editor.html'}; - }); + } - module.directive('metricQueryOptionsInfluxdb', function() { + function influxMetricsQueryOptions() { return {templateUrl: 'app/plugins/datasource/influxdb/partials/query.options.html'}; - }); + } - module.directive('annotationsQueryEditorInfluxdb', function() { + function influxAnnotationsQueryEditor() { return {templateUrl: 'app/plugins/datasource/influxdb/partials/annotations.editor.html'}; - }); + } - module.directive('datasourceCustomSettingsViewInfluxdb', function() { + function influxConfigView() { return {templateUrl: 'app/plugins/datasource/influxdb/partials/config.html'}; - }); + } return { - Datasource: InfluxDatasource + Datasource: InfluxDatasource, + metricsQueryEditor: influxMetricsQueryEditor, + metricsQueryOptions: influxMetricsQueryOptions, + annotationsQueryEditor: influxAnnotationsQueryEditor, + configView: influxConfigView, }; }); diff --git a/public/app/plugins/datasource/influxdb/partials/annotations.editor.html b/public/app/plugins/datasource/influxdb/partials/annotations.editor.html index cec47e7a7e4..6d71ec3211a 100644 --- a/public/app/plugins/datasource/influxdb/partials/annotations.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/annotations.editor.html @@ -2,7 +2,7 @@
InfluxDB Query Example: select text from events where $timeFilter
- +
@@ -12,17 +12,17 @@
Column mappings If your influxdb query returns more than one column you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field.
- +
- +
- +
diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 3fc92c4e4d5..922440bd482 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -126,9 +126,7 @@ function addMathStrategy(selectParts, partModel) { if (selectParts[partCount-2].def.type === 'math') { selectParts[partCount-2] = partModel; return; - } - // if last is alias add it before - else if (selectParts[partCount-1].def.type === 'alias') { + } else if (selectParts[partCount-1].def.type === 'alias') { // if last is alias add it before selectParts.splice(partCount-1, 0, partModel); return; } @@ -399,8 +397,7 @@ class QueryPart { if (strValue === '' && this.def.params[index].optional) { this.params.splice(index, 1); - } - else { + } else { this.params[index] = strValue; } diff --git a/public/app/plugins/datasource/mixed/module.ts b/public/app/plugins/datasource/mixed/module.ts index 1f29e1e80f4..cdc740cf11a 100644 --- a/public/app/plugins/datasource/mixed/module.ts +++ b/public/app/plugins/datasource/mixed/module.ts @@ -3,12 +3,5 @@ import angular from 'angular'; import {MixedDatasource} from './datasource'; -var module = angular.module('grafana.directives'); - -module.directive('metricQueryEditorMixed', function() { - return {templateUrl: 'app/plugins/datasource/mixed/partials/query.editor.html'}; -}); - - export {MixedDatasource, MixedDatasource as Datasource}; diff --git a/public/app/plugins/datasource/opentsdb/module.js b/public/app/plugins/datasource/opentsdb/module.js index 82a5a5e25d6..470f40322d7 100644 --- a/public/app/plugins/datasource/opentsdb/module.js +++ b/public/app/plugins/datasource/opentsdb/module.js @@ -1,24 +1,23 @@ define([ - 'angular', './datasource', ], -function (angular, OpenTsDatasource) { +function (OpenTsDatasource) { 'use strict'; - var module = angular.module('grafana.directives'); - - module.directive('metricQueryEditorOpentsdb', function() { + function metricsQueryEditor() { return { controller: 'OpenTSDBQueryCtrl', templateUrl: 'app/plugins/datasource/opentsdb/partials/query.editor.html', }; - }); + } - module.directive('datasourceCustomSettingsViewOpentsdb', function() { + function configView() { return {templateUrl: 'app/plugins/datasource/opentsdb/partials/config.html'}; - }); + } return { - Datasource: OpenTsDatasource + Datasource: OpenTsDatasource, + metricsQueryEditor: metricsQueryEditor, + configView: configView, }; }); diff --git a/public/app/plugins/datasource/prometheus/module.js b/public/app/plugins/datasource/prometheus/module.js index 91b8d498645..3a4a274763f 100644 --- a/public/app/plugins/datasource/prometheus/module.js +++ b/public/app/plugins/datasource/prometheus/module.js @@ -1,21 +1,20 @@ define([ - 'angular', './datasource', ], -function (angular, PromDatasource) { +function (PromDatasource) { 'use strict'; - var module = angular.module('grafana.directives'); - - module.directive('metricQueryEditorPrometheus', function() { + function metricsQueryEditor() { return {controller: 'PrometheusQueryCtrl', templateUrl: 'app/plugins/datasource/prometheus/partials/query.editor.html'}; - }); + } - module.directive('datasourceCustomSettingsViewPrometheus', function() { + function configView() { return {templateUrl: 'app/plugins/datasource/prometheus/partials/config.html'}; - }); + } return { - Datasource: PromDatasource + Datasource: PromDatasource, + metricsQueryEditor: metricsQueryEditor, + configView: configView, }; }); diff --git a/public/app/plugins/panel/dashlist/module.js b/public/app/plugins/panel/dashlist/module.js index d9152566975..8ffee99ab2c 100644 --- a/public/app/plugins/panel/dashlist/module.js +++ b/public/app/plugins/panel/dashlist/module.js @@ -11,14 +11,8 @@ function (angular, app, _, config, PanelMeta) { var module = angular.module('grafana.panels.dashlist', []); app.useModule(module); - module.directive('grafanaPanelDashlist', function() { - return { - controller: 'DashListPanelCtrl', - templateUrl: 'app/plugins/panel/dashlist/module.html', - }; - }); - - module.controller('DashListPanelCtrl', function($scope, panelSrv, backendSrv) { + /** @ngInject */ + function DashListPanelCtrl($scope, panelSrv, backendSrv) { $scope.panelMeta = new PanelMeta({ panelName: 'Dashboard list', @@ -73,5 +67,16 @@ function (angular, app, _, config, PanelMeta) { }; $scope.init(); - }); + } + + function dashListPanelDirective() { + return { + controller: DashListPanelCtrl, + templateUrl: 'app/plugins/panel/dashlist/module.html', + }; + } + + return { + panel: dashListPanelDirective + }; }); diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 39c1c6707c1..1ebf75d9e73 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -4,7 +4,7 @@ define([ 'moment', 'lodash', 'app/core/utils/kbn', - './graph.tooltip', + './graph_tooltip', 'jquery.flot', 'jquery.flot.events', 'jquery.flot.selection', diff --git a/public/app/plugins/panel/graph/graph_tooltip.d.ts b/public/app/plugins/panel/graph/graph_tooltip.d.ts new file mode 100644 index 00000000000..40805903029 --- /dev/null +++ b/public/app/plugins/panel/graph/graph_tooltip.d.ts @@ -0,0 +1,2 @@ +declare var GraphTooltip: any; +export default GraphTooltip; diff --git a/public/app/plugins/panel/graph/graph.tooltip.js b/public/app/plugins/panel/graph/graph_tooltip.js similarity index 100% rename from public/app/plugins/panel/graph/graph.tooltip.js rename to public/app/plugins/panel/graph/graph_tooltip.js diff --git a/public/app/plugins/panel/graph/module.d.ts b/public/app/plugins/panel/graph/module.d.ts new file mode 100644 index 00000000000..f3fe6043d65 --- /dev/null +++ b/public/app/plugins/panel/graph/module.d.ts @@ -0,0 +1,3 @@ +declare var panel: any; +declare var GraphCtrl: any; +export {panel, GraphCtrl}; diff --git a/public/app/plugins/panel/graph/module.js b/public/app/plugins/panel/graph/module.js index 50a5ab59317..47d0e4f83a3 100644 --- a/public/app/plugins/panel/graph/module.js +++ b/public/app/plugins/panel/graph/module.js @@ -12,16 +12,8 @@ define([ function (angular, _, moment, kbn, TimeSeries, PanelMeta) { 'use strict'; - var module = angular.module('grafana.panels.graph'); - - module.directive('grafanaPanelGraph', function() { - return { - controller: 'GraphCtrl', - templateUrl: 'app/plugins/panel/graph/module.html', - }; - }); - - module.controller('GraphCtrl', function($scope, $rootScope, panelSrv, annotationsSrv, panelHelper) { + /** @ngInject */ + function GraphCtrl($scope, $rootScope, panelSrv, annotationsSrv, panelHelper) { $scope.panelMeta = new PanelMeta({ panelName: 'Graph', @@ -294,7 +286,17 @@ function (angular, _, moment, kbn, TimeSeries, PanelMeta) { }; panelSrv.init($scope); + } - }); + function graphPanelDirective() { + return { + controller: GraphCtrl, + templateUrl: 'app/plugins/panel/graph/module.html', + }; + } + return { + GraphCtrl: GraphCtrl, + panel: graphPanelDirective, + }; }); diff --git a/public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts b/public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts new file mode 100644 index 00000000000..73e47863f80 --- /dev/null +++ b/public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts @@ -0,0 +1,53 @@ +/// + +import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common'; + +import 'app/features/panel/panel_srv'; +import 'app/features/panel/panel_helper'; + +import angular from 'angular'; +import {GraphCtrl} from '../module'; +import helpers from '../../../../../test/specs/helpers'; + +angular.module('grafana.controllers').controller('GraphCtrl', GraphCtrl); + +describe('GraphCtrl', function() { + var ctx = new helpers.ControllerTestContext(); + + beforeEach(angularMocks.module('grafana.services')); + beforeEach(angularMocks.module('grafana.controllers')); + + beforeEach(ctx.providePhase()); + beforeEach(ctx.createControllerPhase('GraphCtrl')); + + describe('get_data with 2 series', function() { + beforeEach(function() { + ctx.annotationsSrv.getAnnotations = sinon.stub().returns(ctx.$q.when([])); + ctx.datasource.query = sinon.stub().returns(ctx.$q.when({ + data: [ + { target: 'test.cpu1', datapoints: [[1, 10]]}, + { target: 'test.cpu2', datapoints: [[1, 10]]} + ] + })); + ctx.scope.render = sinon.spy(); + ctx.scope.refreshData(ctx.datasource); + ctx.scope.$digest(); + }); + + it('should send time series to render', function() { + var data = ctx.scope.render.getCall(0).args[0]; + expect(data.length).to.be(2); + }); + + describe('get_data failure following success', function() { + beforeEach(function() { + ctx.datasource.query = sinon.stub().returns(ctx.$q.reject('Datasource Error')); + ctx.scope.refreshData(ctx.datasource); + ctx.scope.$digest(); + }); + + }); + + }); + +}); diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts new file mode 100644 index 00000000000..e07033583b7 --- /dev/null +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -0,0 +1,230 @@ +/// + +import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common'; + +import '../module'; +import angular from 'angular'; +import $ from 'jquery'; +import helpers from '../../../../../test/specs/helpers'; +import TimeSeries from '../../../../core/time_series2'; + +describe('grafanaGraph', function() { + + beforeEach(angularMocks.module('grafana.directives')); + + function graphScenario(desc, func) { + describe(desc, function() { + var ctx: any = {}; + + ctx.setup = function(setupFunc) { + + beforeEach(angularMocks.module(function($provide) { + $provide.value("timeSrv", new helpers.TimeSrvStub()); + })); + + beforeEach(angularMocks.inject(function($rootScope, $compile) { + var scope = $rootScope.$new(); + var element = angular.element("
"); + + scope.height = '200px'; + scope.panel = { + legend: {}, + grid: { }, + y_formats: [], + seriesOverrides: [], + tooltip: { + shared: true + } + }; + + scope.panelRenderingComplete = sinon.spy(); + scope.appEvent = sinon.spy(); + scope.onAppEvent = sinon.spy(); + scope.hiddenSeries = {}; + scope.dashboard = { timezone: 'browser' }; + scope.range = { + from: new Date('2014-08-09 10:00:00'), + to: new Date('2014-09-09 13:00:00') + }; + ctx.data = []; + ctx.data.push(new TimeSeries({ + datapoints: [[1,1],[2,2]], + alias: 'series1' + })); + ctx.data.push(new TimeSeries({ + datapoints: [[1,1],[2,2]], + alias: 'series2' + })); + + setupFunc(scope, ctx.data); + + $compile(element)(scope); + scope.$digest(); + $.plot = ctx.plotSpy = sinon.spy(); + + scope.$emit('render', ctx.data); + ctx.plotData = ctx.plotSpy.getCall(0).args[1]; + ctx.plotOptions = ctx.plotSpy.getCall(0).args[2]; + })); + }; + + func(ctx); + }); + } + + graphScenario('simple lines options', function(ctx) { + ctx.setup(function(scope) { + scope.panel.lines = true; + scope.panel.fill = 5; + scope.panel.linewidth = 3; + scope.panel.steppedLine = true; + }); + + it('should configure plot with correct options', function() { + expect(ctx.plotOptions.series.lines.show).to.be(true); + expect(ctx.plotOptions.series.lines.fill).to.be(0.5); + expect(ctx.plotOptions.series.lines.lineWidth).to.be(3); + expect(ctx.plotOptions.series.lines.steps).to.be(true); + }); + }); + + graphScenario('grid thresholds 100, 200', function(ctx) { + ctx.setup(function(scope) { + scope.panel.grid = { + threshold1: 100, + threshold1Color: "#111", + threshold2: 200, + threshold2Color: "#222", + }; + }); + + it('should add grid markings', function() { + var markings = ctx.plotOptions.grid.markings; + expect(markings[0].yaxis.from).to.be(100); + expect(markings[0].yaxis.to).to.be(200); + expect(markings[0].color).to.be('#111'); + expect(markings[1].yaxis.from).to.be(200); + expect(markings[1].yaxis.to).to.be(Infinity); + }); + }); + + graphScenario('inverted grid thresholds 200, 100', function(ctx) { + ctx.setup(function(scope) { + scope.panel.grid = { + threshold1: 200, + threshold1Color: "#111", + threshold2: 100, + threshold2Color: "#222", + }; + }); + + it('should add grid markings', function() { + var markings = ctx.plotOptions.grid.markings; + expect(markings[0].yaxis.from).to.be(200); + expect(markings[0].yaxis.to).to.be(100); + expect(markings[0].color).to.be('#111'); + expect(markings[1].yaxis.from).to.be(100); + expect(markings[1].yaxis.to).to.be(-Infinity); + }); + }); + + graphScenario('grid thresholds from zero', function(ctx) { + ctx.setup(function(scope) { + scope.panel.grid = { + threshold1: 0, + threshold1Color: "#111", + }; + }); + + it('should add grid markings', function() { + var markings = ctx.plotOptions.grid.markings; + expect(markings[0].yaxis.from).to.be(0); + }); + }); + + graphScenario('when logBase is log 10', function(ctx) { + ctx.setup(function(scope) { + scope.panel.grid = { + leftMax: null, + rightMax: null, + leftMin: null, + rightMin: null, + leftLogBase: 10, + }; + }); + + it('should apply axis transform and ticks', function() { + var axis = ctx.plotOptions.yaxes[0]; + expect(axis.transform(100)).to.be(Math.log(100+0.1)); + expect(axis.ticks[0]).to.be(0); + expect(axis.ticks[1]).to.be(1); + }); + }); + + graphScenario('should use timeStep for barWidth', function(ctx) { + ctx.setup(function(scope, data) { + scope.panel.bars = true; + data[0] = new TimeSeries({ + datapoints: [[1,10],[2,20]], + alias: 'series1', + }); + }); + + it('should set barWidth', function() { + expect(ctx.plotOptions.series.bars.barWidth).to.be(10/1.5); + }); + }); + + graphScenario('series option overrides, fill & points', function(ctx) { + ctx.setup(function(scope, data) { + scope.panel.lines = true; + scope.panel.fill = 5; + scope.panel.seriesOverrides = [ + { alias: 'test', fill: 0, points: true } + ]; + + data[1].alias = 'test'; + }); + + it('should match second series and fill zero, and enable points', function() { + expect(ctx.plotOptions.series.lines.fill).to.be(0.5); + expect(ctx.plotData[1].lines.fill).to.be(0.001); + expect(ctx.plotData[1].points.show).to.be(true); + }); + }); + + graphScenario('should order series order according to zindex', function(ctx) { + ctx.setup(function(scope) { + scope.panel.seriesOverrides = [{ alias: 'series1', zindex: 2 }]; + }); + + it('should move zindex 2 last', function() { + expect(ctx.plotData[0].alias).to.be('series2'); + expect(ctx.plotData[1].alias).to.be('series1'); + }); + }); + + graphScenario('when series is hidden', function(ctx) { + ctx.setup(function(scope) { + scope.hiddenSeries = {'series2': true}; + }); + + it('should remove datapoints and disable stack', function() { + expect(ctx.plotData[0].alias).to.be('series1'); + expect(ctx.plotData[1].data.length).to.be(0); + expect(ctx.plotData[1].stack).to.be(false); + }); + }); + + graphScenario('when stack and percent', function(ctx) { + ctx.setup(function(scope) { + scope.panel.percentage = true; + scope.panel.stack = true; + }); + + it('should show percentage', function() { + var axis = ctx.plotOptions.yaxes[0]; + expect(axis.tickFormatter(100, axis)).to.be("100%"); + }); + }); +}); diff --git a/public/app/plugins/panel/graph/specs/tooltip_specs.ts b/public/app/plugins/panel/graph/specs/tooltip_specs.ts new file mode 100644 index 00000000000..de173350b2b --- /dev/null +++ b/public/app/plugins/panel/graph/specs/tooltip_specs.ts @@ -0,0 +1,171 @@ +/// + +import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common'; + +import $ from 'jquery'; +import GraphTooltip from '../graph_tooltip'; + +var scope = { + appEvent: sinon.spy(), + onAppEvent: sinon.spy(), +}; + +var elem = $('
'); +var dashboard = { }; + +function describeSharedTooltip(desc, fn) { + var ctx: any = {}; + ctx.scope = scope; + ctx.scope.panel = { + tooltip: { + shared: true + }, + legend: { }, + stack: false + }; + + ctx.setup = function(setupFn) { + ctx.setupFn = setupFn; + }; + + describe(desc, function() { + beforeEach(function() { + ctx.setupFn(); + var tooltip = new GraphTooltip(elem, dashboard, scope); + ctx.results = tooltip.getMultiSeriesPlotHoverInfo(ctx.data, ctx.pos); + }); + + fn(ctx); + }); +} + +describeSharedTooltip("steppedLine false, stack false", function(ctx) { + ctx.setup(function() { + ctx.data = [ + { data: [[10, 15], [12, 20]], lines: {} }, + { data: [[10, 2], [12, 3]], lines: {} } + ]; + ctx.pos = { x: 11 }; + }); + + it('should return 2 series', function() { + expect(ctx.results.length).to.be(2); + }); + it('should add time to results array', function() { + expect(ctx.results.time).to.be(10); + }); + it('should set value and hoverIndex', function() { + expect(ctx.results[0].value).to.be(15); + expect(ctx.results[1].value).to.be(2); + expect(ctx.results[0].hoverIndex).to.be(0); + }); +}); + +describeSharedTooltip("one series is hidden", function(ctx) { + ctx.setup(function() { + ctx.data = [ + { data: [[10, 15], [12, 20]], }, + { data: [] } + ]; + ctx.pos = { x: 11 }; + }); +}); + +describeSharedTooltip("steppedLine false, stack true, individual false", function(ctx) { + ctx.setup(function() { + ctx.data = [ + { + data: [[10, 15], [12, 20]], + lines: {}, + datapoints: { + pointsize: 2, + points: [[10,15], [12,20]], + }, + stack: true, + }, + { + data: [[10, 2], [12, 3]], + lines: {}, + datapoints: { + pointsize: 2, + points: [[10, 2], [12, 3]], + }, + stack: true + } + ]; + ctx.scope.panel.stack = true; + ctx.pos = { x: 11 }; + }); + + it('should show stacked value', function() { + expect(ctx.results[1].value).to.be(17); + }); +}); + +describeSharedTooltip("steppedLine false, stack true, individual false, series stack false", function(ctx) { + ctx.setup(function() { + ctx.data = [ + { + data: [[10, 15], [12, 20]], + lines: {}, + datapoints: { + pointsize: 2, + points: [[10, 15], [12, 20]], + }, + stack: true + }, + { + data: [[10, 2], [12, 3]], + lines: {}, + datapoints: { + pointsize: 2, + points: [[10, 2], [12, 3]], + }, + stack: false + } + ]; + ctx.scope.panel.stack = true; + ctx.pos = { x: 11 }; + }); + + it('should not show stacked value', function() { + expect(ctx.results[1].value).to.be(2); + }); + +}); + +describeSharedTooltip("steppedLine false, stack true, individual true", function(ctx) { + ctx.setup(function() { + ctx.data = [ + { + data: [[10, 15], [12, 20]], + lines: {}, + datapoints: { + pointsize: 2, + points: [[10, 15], [12, 20]], + }, + stack: true + }, + { + data: [[10, 2], [12, 3]], + lines: {}, + datapoints: { + pointsize: 2, + points: [[10, 2], [12, 3]], + }, + stack: false + } + ]; + ctx.scope.panel.stack = true; + ctx.scope.panel.tooltip.value_type = 'individual'; + ctx.pos = { x: 11 }; + }); + + it('should not show stacked value', function() { + expect(ctx.results[1].value).to.be(2); + }); + +}); + + + diff --git a/public/app/plugins/panel/singlestat/controller.js b/public/app/plugins/panel/singlestat/controller.js new file mode 100644 index 00000000000..62ae13d0f5f --- /dev/null +++ b/public/app/plugins/panel/singlestat/controller.js @@ -0,0 +1,245 @@ +define([ + 'angular', + 'app/app', + 'lodash', + 'app/core/utils/kbn', + 'app/core/time_series', + 'app/features/panel/panel_meta', +], +function (angular, app, _, kbn, TimeSeries, PanelMeta) { + 'use strict'; + + /** @ngInject */ + function SingleStatCtrl($scope, panelSrv, panelHelper) { + + $scope.panelMeta = new PanelMeta({ + panelName: 'Singlestat', + editIcon: "fa fa-dashboard", + fullscreen: true, + metricsEditor: true + }); + + $scope.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%']; + + $scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html'); + $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html'); + + // Set and populate defaults + var _d = { + links: [], + datasource: null, + maxDataPoints: 100, + interval: null, + targets: [{}], + cacheTimeout: null, + format: 'none', + prefix: '', + postfix: '', + nullText: null, + valueMaps: [ + { value: 'null', op: '=', text: 'N/A' } + ], + nullPointMode: 'connected', + valueName: 'avg', + prefixFontSize: '50%', + valueFontSize: '80%', + postfixFontSize: '50%', + thresholds: '', + colorBackground: false, + colorValue: false, + colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], + sparkline: { + show: false, + full: false, + lineColor: 'rgb(31, 120, 193)', + fillColor: 'rgba(31, 118, 189, 0.18)', + } + }; + + _.defaults($scope.panel, _d); + $scope.unitFormats = kbn.getUnitFormats(); + + $scope.setUnitFormat = function(subItem) { + $scope.panel.format = subItem.value; + $scope.render(); + }; + + $scope.init = function() { + panelSrv.init($scope); + }; + + $scope.refreshData = function(datasource) { + panelHelper.updateTimeRange($scope); + + return panelHelper.issueMetricQuery($scope, datasource) + .then($scope.dataHandler, function(err) { + $scope.series = []; + $scope.render(); + throw err; + }); + }; + + $scope.loadSnapshot = function(snapshotData) { + panelHelper.updateTimeRange($scope); + $scope.dataHandler(snapshotData); + }; + + $scope.dataHandler = function(results) { + $scope.series = _.map(results.data, $scope.seriesHandler); + $scope.render(); + }; + + $scope.seriesHandler = function(seriesData) { + var series = new TimeSeries({ + datapoints: seriesData.datapoints, + alias: seriesData.target, + }); + + series.flotpairs = series.getFlotPairs($scope.panel.nullPointMode); + + return series; + }; + + $scope.setColoring = function(options) { + if (options.background) { + $scope.panel.colorValue = false; + $scope.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)']; + } + else { + $scope.panel.colorBackground = false; + $scope.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)']; + } + $scope.render(); + }; + + $scope.invertColorOrder = function() { + var tmp = $scope.panel.colors[0]; + $scope.panel.colors[0] = $scope.panel.colors[2]; + $scope.panel.colors[2] = tmp; + $scope.render(); + }; + + $scope.getDecimalsForValue = function(value) { + if (_.isNumber($scope.panel.decimals)) { + return { decimals: $scope.panel.decimals, scaledDecimals: null }; + } + + var delta = value / 2; + var dec = -Math.floor(Math.log(delta) / Math.LN10); + + var magn = Math.pow(10, -dec), + norm = delta / magn, // norm is between 1.0 and 10.0 + size; + + if (norm < 1.5) { + size = 1; + } else if (norm < 3) { + size = 2; + // special case for 2.5, requires an extra decimal + if (norm > 2.25) { + size = 2.5; + ++dec; + } + } else if (norm < 7.5) { + size = 5; + } else { + size = 10; + } + + size *= magn; + + // reduce starting decimals if not needed + if (Math.floor(value) === value) { dec = 0; } + + var result = {}; + result.decimals = Math.max(0, dec); + result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2; + + return result; + }; + + $scope.render = function() { + var data = {}; + + $scope.setValues(data); + + data.thresholds = $scope.panel.thresholds.split(',').map(function(strVale) { + return Number(strVale.trim()); + }); + + data.colorMap = $scope.panel.colors; + + $scope.data = data; + $scope.$broadcast('render'); + }; + + $scope.setValues = function(data) { + data.flotpairs = []; + + if($scope.series.length > 1) { + $scope.inspector.error = new Error(); + $scope.inspector.error.message = 'Multiple Series Error'; + $scope.inspector.error.data = 'Metric query returns ' + $scope.series.length + + ' series. Single Stat Panel expects a single series.\n\nResponse:\n'+JSON.stringify($scope.series); + throw $scope.inspector.error; + } + + if ($scope.series && $scope.series.length > 0) { + var lastPoint = _.last($scope.series[0].datapoints); + var lastValue = _.isArray(lastPoint) ? lastPoint[0] : null; + + if (_.isString(lastValue)) { + data.value = 0; + data.valueFormated = lastValue; + data.valueRounded = 0; + } else { + data.value = $scope.series[0].stats[$scope.panel.valueName]; + data.flotpairs = $scope.series[0].flotpairs; + + var decimalInfo = $scope.getDecimalsForValue(data.value); + var formatFunc = kbn.valueFormats[$scope.panel.format]; + data.valueFormated = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals); + data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals); + } + } + + // check value to text mappings + for(var i = 0; i < $scope.panel.valueMaps.length; i++) { + var map = $scope.panel.valueMaps[i]; + // special null case + if (map.value === 'null') { + if (data.value === null || data.value === void 0) { + data.valueFormated = map.text; + return; + } + continue; + } + + // value/number to text mapping + var value = parseFloat(map.value); + if (value === data.value) { + data.valueFormated = map.text; + return; + } + } + + if (data.value === null || data.value === void 0) { + data.valueFormated = "no value"; + } + }; + + $scope.removeValueMap = function(map) { + var index = _.indexOf($scope.panel.valueMaps, map); + $scope.panel.valueMaps.splice(index, 1); + $scope.render(); + }; + + $scope.addValueMap = function() { + $scope.panel.valueMaps.push({value: '', op: '=', text: '' }); + }; + + $scope.init(); + } + + return SingleStatCtrl; +}); diff --git a/public/app/plugins/panel/singlestat/module.html b/public/app/plugins/panel/singlestat/module.html index 5aeb468db27..748915e6329 100644 --- a/public/app/plugins/panel/singlestat/module.html +++ b/public/app/plugins/panel/singlestat/module.html @@ -1,4 +1,4 @@ -
+
diff --git a/public/app/plugins/panel/singlestat/module.js b/public/app/plugins/panel/singlestat/module.js index 5c99cf7d3b1..2ed8221150a 100644 --- a/public/app/plugins/panel/singlestat/module.js +++ b/public/app/plugins/panel/singlestat/module.js @@ -1,253 +1,231 @@ define([ - 'angular', - 'app/app', + './controller', 'lodash', - 'app/core/utils/kbn', - 'app/core/time_series', - 'app/features/panel/panel_meta', - './singleStatPanel', + 'jquery', + 'jquery.flot', ], -function (angular, app, _, kbn, TimeSeries, PanelMeta) { +function (SingleStatCtrl, _, $) { 'use strict'; - var module = angular.module('grafana.panels.singlestat'); - app.useModule(module); - - module.directive('grafanaPanelSinglestat', function() { + /** @ngInject */ + function singleStatPanel($location, linkSrv, $timeout, templateSrv) { return { - controller: 'SingleStatCtrl', + controller: SingleStatCtrl, templateUrl: 'app/plugins/panel/singlestat/module.html', - }; - }); + link: function(scope, elem) { + var data, panel, linkInfo, $panelContainer; + var firstRender = true; - module.controller('SingleStatCtrl', function($scope, panelSrv, panelHelper) { + scope.$on('render', function() { + if (firstRender) { + var inner = elem.find('.singlestat-panel'); + if (inner.length) { + elem = inner; + $panelContainer = elem.parents('.panel-container'); + firstRender = false; + } + } - $scope.panelMeta = new PanelMeta({ - panelName: 'Singlestat', - editIcon: "fa fa-dashboard", - fullscreen: true, - metricsEditor: true - }); - - $scope.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%']; - - $scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html'); - $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html'); - - // Set and populate defaults - var _d = { - links: [], - datasource: null, - maxDataPoints: 100, - interval: null, - targets: [{}], - cacheTimeout: null, - format: 'none', - prefix: '', - postfix: '', - nullText: null, - valueMaps: [ - { value: 'null', op: '=', text: 'N/A' } - ], - nullPointMode: 'connected', - valueName: 'avg', - prefixFontSize: '50%', - valueFontSize: '80%', - postfixFontSize: '50%', - thresholds: '', - colorBackground: false, - colorValue: false, - colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], - sparkline: { - show: false, - full: false, - lineColor: 'rgb(31, 120, 193)', - fillColor: 'rgba(31, 118, 189, 0.18)', - } - }; - - _.defaults($scope.panel, _d); - $scope.unitFormats = kbn.getUnitFormats(); - - $scope.setUnitFormat = function(subItem) { - $scope.panel.format = subItem.value; - $scope.render(); - }; - - $scope.init = function() { - panelSrv.init($scope); - }; - - $scope.refreshData = function(datasource) { - panelHelper.updateTimeRange($scope); - - return panelHelper.issueMetricQuery($scope, datasource) - .then($scope.dataHandler, function(err) { - $scope.series = []; - $scope.render(); - throw err; + render(); + scope.panelRenderingComplete(); }); - }; - $scope.loadSnapshot = function(snapshotData) { - panelHelper.updateTimeRange($scope); - $scope.dataHandler(snapshotData); - }; + function setElementHeight() { + try { + var height = scope.height || panel.height || scope.row.height; + if (_.isString(height)) { + height = parseInt(height.replace('px', ''), 10); + } - $scope.dataHandler = function(results) { - $scope.series = _.map(results.data, $scope.seriesHandler); - $scope.render(); - }; + height -= 5; // padding + height -= panel.title ? 24 : 9; // subtract panel title bar - $scope.seriesHandler = function(seriesData) { - var series = new TimeSeries({ - datapoints: seriesData.datapoints, - alias: seriesData.target, - }); + elem.css('height', height + 'px'); - series.flotpairs = series.getFlotPairs($scope.panel.nullPointMode); - - return series; - }; - - $scope.setColoring = function(options) { - if (options.background) { - $scope.panel.colorValue = false; - $scope.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)']; - } - else { - $scope.panel.colorBackground = false; - $scope.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)']; - } - $scope.render(); - }; - - $scope.invertColorOrder = function() { - var tmp = $scope.panel.colors[0]; - $scope.panel.colors[0] = $scope.panel.colors[2]; - $scope.panel.colors[2] = tmp; - $scope.render(); - }; - - $scope.getDecimalsForValue = function(value) { - if (_.isNumber($scope.panel.decimals)) { - return { decimals: $scope.panel.decimals, scaledDecimals: null }; - } - - var delta = value / 2; - var dec = -Math.floor(Math.log(delta) / Math.LN10); - - var magn = Math.pow(10, -dec), - norm = delta / magn, // norm is between 1.0 and 10.0 - size; - - if (norm < 1.5) { - size = 1; - } else if (norm < 3) { - size = 2; - // special case for 2.5, requires an extra decimal - if (norm > 2.25) { - size = 2.5; - ++dec; + return true; + } catch(e) { // IE throws errors sometimes + return false; + } } - } else if (norm < 7.5) { - size = 5; - } else { - size = 10; - } - size *= magn; + function applyColoringThresholds(value, valueString) { + if (!panel.colorValue) { + return valueString; + } - // reduce starting decimals if not needed - if (Math.floor(value) === value) { dec = 0; } + var color = getColorForValue(value); + if (color) { + return ''+ valueString + ''; + } - var result = {}; - result.decimals = Math.max(0, dec); - result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2; - - return result; - }; - - $scope.render = function() { - var data = {}; - - $scope.setValues(data); - - data.thresholds = $scope.panel.thresholds.split(',').map(function(strVale) { - return Number(strVale.trim()); - }); - - data.colorMap = $scope.panel.colors; - - $scope.data = data; - $scope.$broadcast('render'); - }; - - $scope.setValues = function(data) { - data.flotpairs = []; - - if($scope.series.length > 1) { - $scope.inspector.error = new Error(); - $scope.inspector.error.message = 'Multiple Series Error'; - $scope.inspector.error.data = 'Metric query returns ' + $scope.series.length + - ' series. Single Stat Panel expects a single series.\n\nResponse:\n'+JSON.stringify($scope.series); - throw $scope.inspector.error; - } - - if ($scope.series && $scope.series.length > 0) { - var lastPoint = _.last($scope.series[0].datapoints); - var lastValue = _.isArray(lastPoint) ? lastPoint[0] : null; - - if (_.isString(lastValue)) { - data.value = 0; - data.valueFormated = lastValue; - data.valueRounded = 0; - } else { - data.value = $scope.series[0].stats[$scope.panel.valueName]; - data.flotpairs = $scope.series[0].flotpairs; - - var decimalInfo = $scope.getDecimalsForValue(data.value); - var formatFunc = kbn.valueFormats[$scope.panel.format]; - data.valueFormated = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals); - data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals); + return valueString; } - } - // check value to text mappings - for(var i = 0; i < $scope.panel.valueMaps.length; i++) { - var map = $scope.panel.valueMaps[i]; - // special null case - if (map.value === 'null') { - if (data.value === null || data.value === void 0) { - data.valueFormated = map.text; + function getColorForValue(value) { + for (var i = data.thresholds.length - 1; i >= 0 ; i--) { + if (value >= data.thresholds[i]) { + return data.colorMap[i]; + } + } + return null; + } + + function getSpan(className, fontSize, value) { + value = templateSrv.replace(value); + return '' + + value + ''; + } + + function getBigValueHtml() { + var body = '
'; + + if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); } + + var value = applyColoringThresholds(data.valueRounded, data.valueFormated); + body += getSpan('singlestat-panel-value', panel.valueFontSize, value); + + if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); } + + body += '
'; + + return body; + } + + function addSparkline() { + var panel = scope.panel; + var width = elem.width() + 20; + var height = elem.height() || 100; + + var plotCanvas = $('
'); + var plotCss = {}; + plotCss.position = 'absolute'; + + if (panel.sparkline.full) { + plotCss.bottom = '5px'; + plotCss.left = '-5px'; + plotCss.width = (width - 10) + 'px'; + var dynamicHeightMargin = height <= 100 ? 5 : (Math.round((height/100)) * 15) + 5; + plotCss.height = (height - dynamicHeightMargin) + 'px'; + } + else { + plotCss.bottom = "0px"; + plotCss.left = "-5px"; + plotCss.width = (width - 10) + 'px'; + plotCss.height = Math.floor(height * 0.25) + "px"; + } + + plotCanvas.css(plotCss); + + var options = { + legend: { show: false }, + series: { + lines: { + show: true, + fill: 1, + lineWidth: 1, + fillColor: panel.sparkline.fillColor, + }, + }, + yaxes: { show: false }, + xaxis: { + show: false, + mode: "time", + min: scope.range.from.valueOf(), + max: scope.range.to.valueOf(), + }, + grid: { hoverable: false, show: false }, + }; + + elem.append(plotCanvas); + + var plotSeries = { + data: data.flotpairs, + color: panel.sparkline.lineColor + }; + + $.plot(plotCanvas, [plotSeries], options); + } + + function render() { + if (!scope.data) { return; } + + data = scope.data; + panel = scope.panel; + + setElementHeight(); + + var body = getBigValueHtml(); + + if (panel.colorBackground && !isNaN(data.valueRounded)) { + var color = getColorForValue(data.valueRounded); + if (color) { + $panelContainer.css('background-color', color); + if (scope.fullscreen) { + elem.css('background-color', color); + } else { + elem.css('background-color', ''); + } + } + } else { + $panelContainer.css('background-color', ''); + elem.css('background-color', ''); + } + + elem.html(body); + + if (panel.sparkline.show) { + addSparkline(); + } + + elem.toggleClass('pointer', panel.links.length > 0); + + if (panel.links.length > 0) { + linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], scope.panel.scopedVars); + } else { + linkInfo = null; + } + } + + // drilldown link tooltip + var drilldownTooltip = $('
hello
"'); + + elem.mouseleave(function() { + if (panel.links.length === 0) { return;} + drilldownTooltip.detach(); + }); + + elem.click(function() { + if (!linkInfo) { return; } + + if (linkInfo.target === '_blank') { + var redirectWindow = window.open(linkInfo.href, '_blank'); + redirectWindow.location; return; } - continue; - } - // value/number to text mapping - var value = parseFloat(map.value); - if (value === data.value) { - data.valueFormated = map.text; - return; - } - } + if (linkInfo.href.indexOf('http') === 0) { + window.location.href = linkInfo.href; + } else { + $timeout(function() { + $location.url(linkInfo.href); + }); + } - if (data.value === null || data.value === void 0) { - data.valueFormated = "no value"; + drilldownTooltip.detach(); + }); + + elem.mousemove(function(e) { + if (!linkInfo) { return;} + + drilldownTooltip.text('click to go to: ' + linkInfo.title); + + drilldownTooltip.place_tt(e.pageX+20, e.pageY-15); + }); } }; + } - $scope.removeValueMap = function(map) { - var index = _.indexOf($scope.panel.valueMaps, map); - $scope.panel.valueMaps.splice(index, 1); - $scope.render(); - }; - - $scope.addValueMap = function() { - $scope.panel.valueMaps.push({value: '', op: '=', text: '' }); - }; - - $scope.init(); - }); + return { + panel: singleStatPanel + }; }); diff --git a/public/app/plugins/panel/table/controller.ts b/public/app/plugins/panel/table/controller.ts index 64e5123b0f6..f05a36c4cdf 100644 --- a/public/app/plugins/panel/table/controller.ts +++ b/public/app/plugins/panel/table/controller.ts @@ -20,7 +20,7 @@ export class TablePanelCtrl { metricsEditor: true, }); - $scope.panelMeta.addEditorTab('Options', 'app/plugins/panels/table/options.html'); + $scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/table/options.html'); $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html'); var panelDefaults = { diff --git a/public/app/plugins/panel/table/editor.ts b/public/app/plugins/panel/table/editor.ts index d16805ecb07..42536642d4a 100644 --- a/public/app/plugins/panel/table/editor.ts +++ b/public/app/plugins/panel/table/editor.ts @@ -118,7 +118,7 @@ export function tablePanelEditor($q, uiSegmentSrv) { return { restrict: 'E', scope: true, - templateUrl: 'app/plugins/panels/table/editor.html', + templateUrl: 'app/plugins/panel/table/editor.html', controller: TablePanelEditorCtrl, }; } diff --git a/public/app/plugins/panel/table/module.ts b/public/app/plugins/panel/table/module.ts index 4344eb41229..834bea42ba5 100644 --- a/public/app/plugins/panel/table/module.ts +++ b/public/app/plugins/panel/table/module.ts @@ -10,7 +10,9 @@ import {TablePanelCtrl} from './controller'; import {TableRenderer} from './renderer'; import {tablePanelEditor} from './editor'; -export function tablePanel() { +angular.module('grafana.directives').directive('grafanaPanelTableEditor', tablePanelEditor); + +function tablePanel() { 'use strict'; return { restrict: 'E', @@ -102,5 +104,4 @@ export function tablePanel() { }; } -angular.module('grafana.directives').directive('grafanaPanelTable', tablePanel); -angular.module('grafana.directives').directive('grafanaPanelTableEditor', tablePanelEditor); +export {tablePanel as panel}; diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts index 9223d35dc5d..be73af06e64 100644 --- a/public/app/plugins/panel/table/renderer.ts +++ b/public/app/plugins/panel/table/renderer.ts @@ -96,13 +96,12 @@ export class TableRenderer { } renderCell(columnIndex, value, addWidthHack = false) { - var value = this.formatColumnValue(columnIndex, value); + value = this.formatColumnValue(columnIndex, value); var style = ''; if (this.colorState.cell) { style = ' style="background-color:' + this.colorState.cell + ';color: white"'; this.colorState.cell = null; - } - else if (this.colorState.value) { + } else if (this.colorState.value) { style = ' style="color:' + this.colorState.value + '"'; this.colorState.value = null; } diff --git a/public/app/plugins/panel/table/transformers.ts b/public/app/plugins/panel/table/transformers.ts index 493fd354ba6..b1cc644790e 100644 --- a/public/app/plugins/panel/table/transformers.ts +++ b/public/app/plugins/panel/table/transformers.ts @@ -52,8 +52,7 @@ transformers['timeseries_to_columns'] = { if (!points[timeKey]) { points[timeKey] = {time: dp[1]}; points[timeKey][i] = dp[0]; - } - else { + } else { points[timeKey][i] = dp[0]; } } diff --git a/public/app/plugins/panel/text/module.js b/public/app/plugins/panel/text/module.js index d3add3a15b8..e940f3b7d21 100644 --- a/public/app/plugins/panel/text/module.js +++ b/public/app/plugins/panel/text/module.js @@ -10,17 +10,7 @@ function (angular, app, _, require, PanelMeta) { var converter; - var module = angular.module('grafana.panels.text', []); - app.useModule(module); - - module.directive('grafanaPanelText', function() { - return { - controller: 'TextPanelCtrl', - templateUrl: 'app/plugins/panel/text/module.html', - }; - }); - - module.controller('TextPanelCtrl', function($scope, templateSrv, $sce, panelSrv) { + function TextPanelCtrl($scope, templateSrv, $sce, panelSrv) { $scope.panelMeta = new PanelMeta({ panelName: 'Text', @@ -107,5 +97,16 @@ function (angular, app, _, require, PanelMeta) { }; $scope.init(); - }); + } + + function textPanel() { + return { + controller: TextPanelCtrl, + templateUrl: 'app/plugins/panel/text/module.html', + }; + } + + return { + panel: textPanel, + }; }); diff --git a/public/app/plugins/plugin_api.md b/public/app/plugins/plugin_api.md index 138dab09931..707838ba8fd 100644 --- a/public/app/plugins/plugin_api.md +++ b/public/app/plugins/plugin_api.md @@ -1,8 +1,52 @@ # Plugin API -## Changelog +### 3.0 changes to plugin api changes -2.5.1 +There has been big changes to both data source and plugin schema (plugin.json) and how +you write the plugin main module. + +#### Datasource plugin + +Now data source plugins AMD/SystemJS module should return: + +```javascript +return { + Datasource: ElasticDatasource, + configView: editView.default, + annotationsQueryEditor: annotationsQueryEditor, + metricsQueryEditor: metricsQueryEditor, + metricsQueryOptions: metricsQueryOptions, +}; +``` + +Where ElasticDatasource is a constructor function to a javascript. The constructor +function can take angular services and `instanceSettings` as parameters. + +Example: + +```javascript +function ElasticDatasource(instanceSettings, templateSrv) { + this.instanceSettings = this.instanceSettings; + ///... +}; +``` + +A datasource module can optionally return a configView directive function, metricsQueryEditor directive function, etc. + +Example: + +```javascript +function metricsQueryEditor() { + return {controller: 'ElasticQueryCtrl', templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.editor.html'}; +} +``` + +#### Panel plugin + +The panel plugin AMD/SystemJS module should return an object with a property named `panel`. This needs to be +a directive function. + +### 2.5.1 changes datasource annotationQuery changed. now single options parameter with: - range - rangeRaw diff --git a/public/test/specs/graph-ctrl-specs.js b/public/test/specs/graph-ctrl-specs.js deleted file mode 100644 index 607c13cf528..00000000000 --- a/public/test/specs/graph-ctrl-specs.js +++ /dev/null @@ -1,51 +0,0 @@ -define([ - './helpers', - 'app/features/panel/panel_srv', - 'app/features/panel/panel_helper', - 'app/plugins/panel/graph/module' -], function(helpers) { - 'use strict'; - - describe('GraphCtrl', function() { - var ctx = new helpers.ControllerTestContext(); - - beforeEach(module('grafana.services')); - beforeEach(module('grafana.panels.graph')); - - beforeEach(ctx.providePhase()); - beforeEach(ctx.createControllerPhase('GraphCtrl')); - - describe('get_data with 2 series', function() { - beforeEach(function() { - ctx.annotationsSrv.getAnnotations = sinon.stub().returns(ctx.$q.when([])); - ctx.datasource.query = sinon.stub().returns(ctx.$q.when({ - data: [ - { target: 'test.cpu1', datapoints: [[1, 10]]}, - { target: 'test.cpu2', datapoints: [[1, 10]]} - ] - })); - ctx.scope.render = sinon.spy(); - ctx.scope.refreshData(ctx.datasource); - ctx.scope.$digest(); - }); - - it('should send time series to render', function() { - var data = ctx.scope.render.getCall(0).args[0]; - expect(data.length).to.be(2); - }); - - describe('get_data failure following success', function() { - beforeEach(function() { - ctx.datasource.query = sinon.stub().returns(ctx.$q.reject('Datasource Error')); - ctx.scope.refreshData(ctx.datasource); - ctx.scope.$digest(); - }); - - }); - - }); - - }); - -}); - diff --git a/public/test/specs/graph-specs.js b/public/test/specs/graph-specs.js deleted file mode 100644 index db3a4acf61f..00000000000 --- a/public/test/specs/graph-specs.js +++ /dev/null @@ -1,231 +0,0 @@ -define([ - './helpers', - 'angular', - 'jquery', - 'app/core/time_series', - 'app/plugins/panel/graph/graph' -], function(helpers, angular, $, TimeSeries) { - 'use strict'; - - describe('grafanaGraph', function() { - - beforeEach(module('grafana.directives')); - - function graphScenario(desc, func) { - describe(desc, function() { - var ctx = {}; - - ctx.setup = function (setupFunc) { - - beforeEach(module(function($provide) { - $provide.value("timeSrv", new helpers.TimeSrvStub()); - })); - - beforeEach(inject(function($rootScope, $compile) { - var scope = $rootScope.$new(); - var element = angular.element("
"); - - scope.height = '200px'; - scope.panel = { - legend: {}, - grid: { }, - y_formats: [], - seriesOverrides: [], - tooltip: { - shared: true - } - }; - - scope.panelRenderingComplete = sinon.spy(); - scope.appEvent = sinon.spy(); - scope.onAppEvent = sinon.spy(); - scope.hiddenSeries = {}; - scope.dashboard = { timezone: 'browser' }; - scope.range = { - from: new Date('2014-08-09 10:00:00'), - to: new Date('2014-09-09 13:00:00') - }; - ctx.data = []; - ctx.data.push(new TimeSeries({ - datapoints: [[1,1],[2,2]], - alias: 'series1' - })); - ctx.data.push(new TimeSeries({ - datapoints: [[1,1],[2,2]], - alias: 'series2' - })); - - setupFunc(scope, ctx.data); - - $compile(element)(scope); - scope.$digest(); - $.plot = ctx.plotSpy = sinon.spy(); - - scope.$emit('render', ctx.data); - ctx.plotData = ctx.plotSpy.getCall(0).args[1]; - ctx.plotOptions = ctx.plotSpy.getCall(0).args[2]; - })); - }; - - func(ctx); - }); - } - - graphScenario('simple lines options', function(ctx) { - ctx.setup(function(scope) { - scope.panel.lines = true; - scope.panel.fill = 5; - scope.panel.linewidth = 3; - scope.panel.steppedLine = true; - }); - - it('should configure plot with correct options', function() { - expect(ctx.plotOptions.series.lines.show).to.be(true); - expect(ctx.plotOptions.series.lines.fill).to.be(0.5); - expect(ctx.plotOptions.series.lines.lineWidth).to.be(3); - expect(ctx.plotOptions.series.lines.steps).to.be(true); - }); - }); - - graphScenario('grid thresholds 100, 200', function(ctx) { - ctx.setup(function(scope) { - scope.panel.grid = { - threshold1: 100, - threshold1Color: "#111", - threshold2: 200, - threshold2Color: "#222", - }; - }); - - it('should add grid markings', function() { - var markings = ctx.plotOptions.grid.markings; - expect(markings[0].yaxis.from).to.be(100); - expect(markings[0].yaxis.to).to.be(200); - expect(markings[0].color).to.be('#111'); - expect(markings[1].yaxis.from).to.be(200); - expect(markings[1].yaxis.to).to.be(Infinity); - }); - }); - - graphScenario('inverted grid thresholds 200, 100', function(ctx) { - ctx.setup(function(scope) { - scope.panel.grid = { - threshold1: 200, - threshold1Color: "#111", - threshold2: 100, - threshold2Color: "#222", - }; - }); - - it('should add grid markings', function() { - var markings = ctx.plotOptions.grid.markings; - expect(markings[0].yaxis.from).to.be(200); - expect(markings[0].yaxis.to).to.be(100); - expect(markings[0].color).to.be('#111'); - expect(markings[1].yaxis.from).to.be(100); - expect(markings[1].yaxis.to).to.be(-Infinity); - }); - }); - - graphScenario('grid thresholds from zero', function(ctx) { - ctx.setup(function(scope) { - scope.panel.grid = { - threshold1: 0, - threshold1Color: "#111", - }; - }); - - it('should add grid markings', function() { - var markings = ctx.plotOptions.grid.markings; - expect(markings[0].yaxis.from).to.be(0); - }); - }); - - graphScenario('when logBase is log 10', function(ctx) { - ctx.setup(function(scope) { - scope.panel.grid = { - leftMax: null, - rightMax: null, - leftMin: null, - rightMin: null, - leftLogBase: 10, - }; - }); - - it('should apply axis transform and ticks', function() { - var axis = ctx.plotOptions.yaxes[0]; - expect(axis.transform(100)).to.be(Math.log(100+0.1)); - expect(axis.ticks[0]).to.be(0); - expect(axis.ticks[1]).to.be(1); - }); - }); - - graphScenario('should use timeStep for barWidth', function(ctx) { - ctx.setup(function(scope, data) { - scope.panel.bars = true; - data[0] = new TimeSeries({ - datapoints: [[1,10],[2,20]], - alias: 'series1', - }); - }); - - it('should set barWidth', function() { - expect(ctx.plotOptions.series.bars.barWidth).to.be(10/1.5); - }); - }); - - graphScenario('series option overrides, fill & points', function(ctx) { - ctx.setup(function(scope, data) { - scope.panel.lines = true; - scope.panel.fill = 5; - scope.panel.seriesOverrides = [ - { alias: 'test', fill: 0, points: true } - ]; - - data[1].alias = 'test'; - }); - - it('should match second series and fill zero, and enable points', function() { - expect(ctx.plotOptions.series.lines.fill).to.be(0.5); - expect(ctx.plotData[1].lines.fill).to.be(0.001); - expect(ctx.plotData[1].points.show).to.be(true); - }); - }); - - graphScenario('should order series order according to zindex', function(ctx) { - ctx.setup(function(scope) { - scope.panel.seriesOverrides = [{ alias: 'series1', zindex: 2 }]; - }); - - it('should move zindex 2 last', function() { - expect(ctx.plotData[0].alias).to.be('series2'); - expect(ctx.plotData[1].alias).to.be('series1'); - }); - }); - - graphScenario('when series is hidden', function(ctx) { - ctx.setup(function(scope) { - scope.hiddenSeries = {'series2': true}; - }); - - it('should remove datapoints and disable stack', function() { - expect(ctx.plotData[0].alias).to.be('series1'); - expect(ctx.plotData[1].data.length).to.be(0); - expect(ctx.plotData[1].stack).to.be(false); - }); - }); - - graphScenario('when stack and percent', function(ctx) { - ctx.setup(function(scope) { - scope.panel.percentage = true; - scope.panel.stack = true; - }); - - it('should show percentage', function() { - var axis = ctx.plotOptions.yaxes[0]; - expect(axis.tickFormatter(100, axis)).to.be("100%"); - }); - }); - }); -}); - diff --git a/public/test/specs/graph-tooltip-specs.js b/public/test/specs/graph-tooltip-specs.js deleted file mode 100644 index de39d855a70..00000000000 --- a/public/test/specs/graph-tooltip-specs.js +++ /dev/null @@ -1,171 +0,0 @@ -define([ - 'jquery', - 'app/plugins/panel/graph/graph.tooltip' -], function($, GraphTooltip) { - 'use strict'; - - var scope = { - appEvent: sinon.spy(), - onAppEvent: sinon.spy(), - }; - - var elem = $('
'); - var dashboard = { }; - - function describeSharedTooltip(desc, fn) { - var ctx = {}; - ctx.scope = scope; - ctx.scope.panel = { - tooltip: { - shared: true - }, - legend: { }, - stack: false - }; - - ctx.setup = function(setupFn) { - ctx.setupFn = setupFn; - }; - - describe(desc, function() { - beforeEach(function() { - ctx.setupFn(); - var tooltip = new GraphTooltip(elem, dashboard, scope); - ctx.results = tooltip.getMultiSeriesPlotHoverInfo(ctx.data, ctx.pos); - }); - - fn(ctx); - }); - } - - describeSharedTooltip("steppedLine false, stack false", function(ctx) { - ctx.setup(function() { - ctx.data = [ - { data: [[10, 15], [12, 20]], lines: {} }, - { data: [[10, 2], [12, 3]], lines: {} } - ]; - ctx.pos = { x: 11 }; - }); - - it('should return 2 series', function() { - expect(ctx.results.length).to.be(2); - }); - it('should add time to results array', function() { - expect(ctx.results.time).to.be(10); - }); - it('should set value and hoverIndex', function() { - expect(ctx.results[0].value).to.be(15); - expect(ctx.results[1].value).to.be(2); - expect(ctx.results[0].hoverIndex).to.be(0); - }); - }); - - describeSharedTooltip("one series is hidden", function(ctx) { - ctx.setup(function() { - ctx.data = [ - { data: [[10, 15], [12, 20]], }, - { data: [] } - ]; - ctx.pos = { x: 11 }; - }); - }); - - describeSharedTooltip("steppedLine false, stack true, individual false", function(ctx) { - ctx.setup(function() { - ctx.data = [ - { - data: [[10, 15], [12, 20]], - lines: {}, - datapoints: { - pointsize: 2, - points: [[10,15], [12,20]], - }, - stack: true, - }, - { - data: [[10, 2], [12, 3]], - lines: {}, - datapoints: { - pointsize: 2, - points: [[10, 2], [12, 3]], - }, - stack: true - } - ]; - ctx.scope.panel.stack = true; - ctx.pos = { x: 11 }; - }); - - it('should show stacked value', function() { - expect(ctx.results[1].value).to.be(17); - }); - }); - - describeSharedTooltip("steppedLine false, stack true, individual false, series stack false", function(ctx) { - ctx.setup(function() { - ctx.data = [ - { - data: [[10, 15], [12, 20]], - lines: {}, - datapoints: { - pointsize: 2, - points: [[10, 15], [12, 20]], - }, - stack: true - }, - { - data: [[10, 2], [12, 3]], - lines: {}, - datapoints: { - pointsize: 2, - points: [[10, 2], [12, 3]], - }, - stack: false - } - ]; - ctx.scope.panel.stack = true; - ctx.pos = { x: 11 }; - }); - - it('should not show stacked value', function() { - expect(ctx.results[1].value).to.be(2); - }); - - }); - - describeSharedTooltip("steppedLine false, stack true, individual true", function(ctx) { - ctx.setup(function() { - ctx.data = [ - { - data: [[10, 15], [12, 20]], - lines: {}, - datapoints: { - pointsize: 2, - points: [[10, 15], [12, 20]], - }, - stack: true - }, - { - data: [[10, 2], [12, 3]], - lines: {}, - datapoints: { - pointsize: 2, - points: [[10, 2], [12, 3]], - }, - stack: false - } - ]; - ctx.scope.panel.stack = true; - ctx.scope.panel.tooltip.value_type = 'individual'; - ctx.pos = { x: 11 }; - }); - - it('should not show stacked value', function() { - expect(ctx.results[1].value).to.be(2); - }); - - }); - -}); - - diff --git a/public/test/specs/helpers.d.ts b/public/test/specs/helpers.d.ts index 2447bc7868d..4b64bd79031 100644 --- a/public/test/specs/helpers.d.ts +++ b/public/test/specs/helpers.d.ts @@ -1,6 +1,4 @@ -declare module "test/specs/helpers" { - let helpers: any; - export default helpers; -} +declare let helpers: any; +export default helpers; diff --git a/public/test/specs/singlestat-specs.js b/public/test/specs/singlestat-specs.js index 733f7f37e98..60953345f2d 100644 --- a/public/test/specs/singlestat-specs.js +++ b/public/test/specs/singlestat-specs.js @@ -1,11 +1,14 @@ define([ + 'angular', './helpers', + 'app/plugins/panel/singlestat/controller', 'app/features/panel/panel_srv', 'app/features/panel/panel_helper', - 'app/plugins/panel/singlestat/module' -], function(helpers) { +], function(angular, helpers, SingleStatCtrl) { 'use strict'; + angular.module('grafana.controllers').controller('SingleStatCtrl', SingleStatCtrl); + describe('SingleStatCtrl', function() { var ctx = new helpers.ControllerTestContext(); @@ -16,7 +19,7 @@ define([ ctx.setup = function (setupFunc) { beforeEach(module('grafana.services')); - beforeEach(module('grafana.panels.singlestat')); + beforeEach(module('grafana.controllers')); beforeEach(ctx.providePhase()); beforeEach(ctx.createControllerPhase('SingleStatCtrl')); @@ -24,7 +27,7 @@ define([ beforeEach(function() { setupFunc(); ctx.datasource.query = sinon.stub().returns(ctx.$q.when({ - data: [ { target: 'test.cpu1', datapoints: ctx.datapoints } ] + data: [{target: 'test.cpu1', datapoints: ctx.datapoints}] })); ctx.scope.refreshData(ctx.datasource); diff --git a/tasks/options/tslint.js b/tasks/options/tslint.js index 0b76e623e95..c9658b32f8f 100644 --- a/tasks/options/tslint.js +++ b/tasks/options/tslint.js @@ -6,18 +6,19 @@ module.exports = function(config) { } }, options: { - configuration: { - rules: { - curly: true, - align: [true, "parameters", "statements"], - indent: [true, "spaces"], - "class-name": true, - "interface-name": true, - "semicolon": true, - "use-strict": [false, "check-module", "check-function"], - "whitespace": [true, "check-branch", "check-decl", "check-type"], - } - } + configuration: 'tslint.json' + // { + // rules: { + // curly: true, + // align: [true, "parameters", "statements"], + // indent: [true, "spaces"], + // "class-name": true, + // "interface-name": true, + // "semicolon": true, + // "use-strict": [false, "check-module", "check-function"], + // "whitespace": [true, "check-branch", "check-decl", "check-type"], + // } + // } } }; }; diff --git a/tasks/options/watch.js b/tasks/options/watch.js index 515877b102e..187b07696c7 100644 --- a/tasks/options/watch.js +++ b/tasks/options/watch.js @@ -27,15 +27,17 @@ module.exports = function(config, grunt) { } if (/(\.ts)$/.test(filepath)) { + newPath = filepath.replace(/^public/, 'public_gen'); + grunt.log.writeln('Copying to ' + newPath); + grunt.file.copy(filepath, newPath); + + // copy ts file also used by source maps //changes changed file source to that of the changed file var option = 'typescript.build.src'; var result = filepath; grunt.config(option, result); grunt.task.run('typescript:build'); grunt.task.run('tslint'); - // copy ts file also used by source maps - newPath = filepath.replace(/^public/, 'public_gen'); - grunt.file.copy(filepath, newPath); } }); diff --git a/tslint.json b/tslint.json new file mode 100644 index 00000000000..a6d1cf26d22 --- /dev/null +++ b/tslint.json @@ -0,0 +1,62 @@ +{ + "rules": { + "class-name": true, + "comment-format": [true, "check-space"], + "curly": true, + "eofline": true, + "forin": false, + "indent": [true, "spaces"], + "label-position": true, + "label-undefined": true, + "max-line-length": [true, 140], + "member-access": false, + "no-arg": true, + "no-bitwise": true, + "no-console": [true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-key": true, + "no-duplicate-variable": true, + "no-empty": true, + "no-eval": true, + "no-inferrable-types": true, + "no-shadowed-variable": false, + "no-string-literal": false, + "no-switch-case-fall-through": true, + "no-trailing-comma": true, + "no-trailing-whitespace": true, + "no-unused-expression": false, + "no-unused-variable": false, + "no-unreachable": true, + "no-use-before-declare": true, + "no-var-keyword": false, + "object-literal-sort-keys": false, + "one-line": [true, + "check-open-brace", + "check-catch", + "check-else" + ], + "radix": false, + "semicolon": true, + "triple-equals": [true, "allow-null-check"], + "typedef-whitespace": [true, { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }], + "variable-name": [true, "ban-keywords"], + "whitespace": [true, + "check-branch", + "check-decl", + "check-type" + ] + } +}