From 2f78584cdb735389792de1f868a424a9cfd4c304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 1 Feb 2016 12:42:10 +0100 Subject: [PATCH 01/32] poc(plugin editors): experimential test for plugin editors --- public/app/core/plugins/directive.ts | 0 public/app/features/panel/panel.ts | 39 +---- public/app/features/panel/panel_directive.js | 104 ------------- public/app/features/panel/panel_directive.ts | 142 ++++++++++++++++++ public/app/features/panel/query_editor.ts | 80 +++++++++- public/app/partials/metrics.html | 4 +- .../app/plugins/datasource/grafana/module.ts | 15 +- 7 files changed, 236 insertions(+), 148 deletions(-) create mode 100644 public/app/core/plugins/directive.ts delete mode 100644 public/app/features/panel/panel_directive.js create mode 100644 public/app/features/panel/panel_directive.ts diff --git a/public/app/core/plugins/directive.ts b/public/app/core/plugins/directive.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/public/app/features/panel/panel.ts b/public/app/features/panel/panel.ts index 7312e18ccc7..535ccdcf06a 100644 --- a/public/app/features/panel/panel.ts +++ b/public/app/features/panel/panel.ts @@ -4,44 +4,7 @@ import config from 'app/core/config'; import {PanelCtrl} from './panel_ctrl'; import {MetricsPanelCtrl} from './metrics_panel_ctrl'; - -export class DefaultPanelCtrl extends PanelCtrl { - constructor($scope, $injector) { - super($scope, $injector); - } -} - -class PanelDirective { - template: string; - templateUrl: string; - bindToController: boolean; - scope: any; - controller: any; - controllerAs: string; - - getDirective() { - if (!this.controller) { - this.controller = DefaultPanelCtrl; - } - - return { - template: this.template, - templateUrl: this.templateUrl, - controller: this.controller, - controllerAs: 'ctrl', - bindToController: true, - scope: {dashboard: "=", panel: "=", row: "="}, - link: (scope, elem, attrs, ctrl) => { - ctrl.init(); - this.link(scope, elem, attrs, ctrl); - } - }; - } - - link(scope, elem, attrs, ctrl) { - return null; - } -} +import {PanelDirective} from './panel_directive'; export { PanelCtrl, diff --git a/public/app/features/panel/panel_directive.js b/public/app/features/panel/panel_directive.js deleted file mode 100644 index ffe978a55ad..00000000000 --- a/public/app/features/panel/panel_directive.js +++ /dev/null @@ -1,104 +0,0 @@ -define([ - 'angular', - 'jquery', -], -function (angular, $) { - 'use strict'; - - var module = angular.module('grafana.directives'); - - module.directive('grafanaPanel', function() { - return { - restrict: 'E', - templateUrl: 'app/features/panel/partials/panel.html', - transclude: true, - scope: { ctrl: "=" }, - link: function(scope, elem) { - var panelContainer = elem.find('.panel-container'); - var ctrl = scope.ctrl; - scope.$watchGroup(['ctrl.fullscreen', 'ctrl.height', 'ctrl.panel.height', 'ctrl.row.height'], function() { - panelContainer.css({ minHeight: ctrl.height || ctrl.panel.height || ctrl.row.height, display: 'block' }); - elem.toggleClass('panel-fullscreen', ctrl.fullscreen ? true : false); - }); - } - }; - }); - - module.directive('panelResizer', function($rootScope) { - return { - restrict: 'E', - template: '', - link: function(scope, elem) { - var resizing = false; - var lastPanel = false; - var ctrl = scope.ctrl; - var handleOffset; - var originalHeight; - var originalWidth; - var maxWidth; - - function dragStartHandler(e) { - e.preventDefault(); - resizing = true; - - handleOffset = $(e.target).offset(); - originalHeight = parseInt(ctrl.row.height); - originalWidth = ctrl.panel.span; - maxWidth = $(document).width(); - - lastPanel = ctrl.row.panels[ctrl.row.panels.length - 1]; - - $('body').on('mousemove', moveHandler); - $('body').on('mouseup', dragEndHandler); - } - - function moveHandler(e) { - ctrl.row.height = originalHeight + (e.pageY - handleOffset.top); - ctrl.panel.span = originalWidth + (((e.pageX - handleOffset.left) / maxWidth) * 12); - ctrl.panel.span = Math.min(Math.max(ctrl.panel.span, 1), 12); - - var rowSpan = ctrl.dashboard.rowSpan(ctrl.row); - - // auto adjust other panels - if (Math.floor(rowSpan) < 14) { - // last panel should not push row down - if (lastPanel === ctrl.panel && rowSpan > 12) { - lastPanel.span -= rowSpan - 12; - } - // reduce width of last panel so total in row is 12 - else if (lastPanel !== ctrl.panel) { - lastPanel.span = lastPanel.span - (rowSpan - 12); - lastPanel.span = Math.min(Math.max(lastPanel.span, 1), 12); - } - } - - scope.$apply(function() { - scope.$broadcast('render'); - }); - } - - function dragEndHandler() { - // if close to 12 - var rowSpan = ctrl.dashboard.rowSpan(ctrl.row); - if (rowSpan < 12 && rowSpan > 11) { - lastPanel.span += 12 - rowSpan; - } - - scope.$apply(function() { - $rootScope.$broadcast('render'); - }); - - $('body').off('mousemove', moveHandler); - $('body').off('mouseup', dragEndHandler); - } - - elem.on('mousedown', dragStartHandler); - - scope.$on("$destroy", function() { - elem.off('mousedown', dragStartHandler); - }); - } - }; - }); - -}); diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts new file mode 100644 index 00000000000..eac2594a26e --- /dev/null +++ b/public/app/features/panel/panel_directive.ts @@ -0,0 +1,142 @@ +/// + +import angular from 'angular'; +import $ from 'jquery'; + +import {PanelCtrl} from './panel_ctrl'; + +export class DefaultPanelCtrl extends PanelCtrl { + constructor($scope, $injector) { + super($scope, $injector); + } +} + +export class PanelDirective { + template: string; + templateUrl: string; + bindToController: boolean; + scope: any; + controller: any; + controllerAs: string; + + getDirective() { + if (!this.controller) { + this.controller = DefaultPanelCtrl; + } + + return { + template: this.template, + templateUrl: this.templateUrl, + controller: this.controller, + controllerAs: 'ctrl', + bindToController: true, + scope: {dashboard: "=", panel: "=", row: "="}, + link: (scope, elem, attrs, ctrl) => { + ctrl.init(); + this.link(scope, elem, attrs, ctrl); + } + }; + } + + link(scope, elem, attrs, ctrl) { + return null; + } +} + + +var module = angular.module('grafana.directives'); + +module.directive('grafanaPanel', function() { + return { + restrict: 'E', + templateUrl: 'app/features/panel/partials/panel.html', + transclude: true, + scope: { ctrl: "=" }, + link: function(scope, elem) { + var panelContainer = elem.find('.panel-container'); + var ctrl = scope.ctrl; + scope.$watchGroup(['ctrl.fullscreen', 'ctrl.height', 'ctrl.panel.height', 'ctrl.row.height'], function() { + panelContainer.css({ minHeight: ctrl.height || ctrl.panel.height || ctrl.row.height, display: 'block' }); + elem.toggleClass('panel-fullscreen', ctrl.fullscreen ? true : false); + }); + } + }; +}); + +module.directive('panelResizer', function($rootScope) { + return { + restrict: 'E', + template: '', + link: function(scope, elem) { + var resizing = false; + var lastPanel; + var ctrl = scope.ctrl; + var handleOffset; + var originalHeight; + var originalWidth; + var maxWidth; + + function dragStartHandler(e) { + e.preventDefault(); + resizing = true; + + handleOffset = $(e.target).offset(); + originalHeight = parseInt(ctrl.row.height); + originalWidth = ctrl.panel.span; + maxWidth = $(document).width(); + + lastPanel = ctrl.row.panels[ctrl.row.panels.length - 1]; + + $('body').on('mousemove', moveHandler); + $('body').on('mouseup', dragEndHandler); + } + + function moveHandler(e) { + ctrl.row.height = originalHeight + (e.pageY - handleOffset.top); + ctrl.panel.span = originalWidth + (((e.pageX - handleOffset.left) / maxWidth) * 12); + ctrl.panel.span = Math.min(Math.max(ctrl.panel.span, 1), 12); + + var rowSpan = ctrl.dashboard.rowSpan(ctrl.row); + + // auto adjust other panels + if (Math.floor(rowSpan) < 14) { + // last panel should not push row down + if (lastPanel === ctrl.panel && rowSpan > 12) { + lastPanel.span -= rowSpan - 12; + } else if (lastPanel !== ctrl.panel) { + // reduce width of last panel so total in row is 12 + lastPanel.span = lastPanel.span - (rowSpan - 12); + lastPanel.span = Math.min(Math.max(lastPanel.span, 1), 12); + } + } + + scope.$apply(function() { + scope.$broadcast('render'); + }); + } + + function dragEndHandler() { + // if close to 12 + var rowSpan = ctrl.dashboard.rowSpan(ctrl.row); + if (rowSpan < 12 && rowSpan > 11) { + lastPanel.span += 12 - rowSpan; + } + + scope.$apply(function() { + $rootScope.$broadcast('render'); + }); + + $('body').off('mousemove', moveHandler); + $('body').off('mouseup', dragEndHandler); + } + + elem.on('mousedown', dragStartHandler); + + scope.$on("$destroy", function() { + elem.off('mousedown', dragStartHandler); + }); + } + }; +}); + + diff --git a/public/app/features/panel/query_editor.ts b/public/app/features/panel/query_editor.ts index 5b493362762..5c33480a3ab 100644 --- a/public/app/features/panel/query_editor.ts +++ b/public/app/features/panel/query_editor.ts @@ -1,6 +1,80 @@ /// import angular from 'angular'; +import _ from 'lodash'; + +var directivesModule = angular.module('grafana.directives'); + +function pluginDirectiveLoader($compile, datasourceSrv) { + + function getPluginComponentDirective(options) { + return function() { + return { + templateUrl: options.Component.templateUrl, + restrict: 'E', + controller: options.Component, + controllerAs: 'ctrl', + bindToController: true, + scope: options.bindings, + link: (scope, elem, attrs, ctrl) => { + if (ctrl.link) { + ctrl.link(scope, elem, attrs, ctrl); + } + } + }; + }; + } + + function getModule(scope, attrs) { + switch (attrs.type) { + case "metrics-query-editor": { + let datasource = scope.target.datasource || scope.ctrl.panel.datasource; + return datasourceSrv.get(datasource).then(ds => { + return System.import(ds.meta.module).then(dsModule => { + return { + name: 'metrics-query-editor-' + ds.meta.id, + bindings: {target: "=", panelCtrl: "="}, + attrs: {"target": "target", "panel-ctrl": "ctrl"}, + Component: dsModule.MetricsQueryEditor + }; + }); + }); + } + } + } + + function appendAndCompile(scope, elem, componentInfo) { + var child = angular.element(document.createElement(componentInfo.name)); + _.each(componentInfo.attrs, (value, key) => { + child.attr(key, value); + }); + + $compile(child)(scope); + + elem.empty(); + elem.append(child); + } + + function registerPluginComponent(scope, elem, attrs, componentInfo) { + if (!componentInfo.Component.registered) { + var directiveName = attrs.$normalize(componentInfo.name); + var directiveFn = getPluginComponentDirective(componentInfo); + directivesModule.directive(directiveName, directiveFn); + componentInfo.Component.registered = true; + } + + appendAndCompile(scope, elem, componentInfo); + } + + return { + restrict: 'E', + link: function(scope, elem, attrs) { + getModule(scope, attrs).then(function (componentInfo) { + registerPluginComponent(scope, elem, attrs, componentInfo); + }); + } + }; +} /** @ngInject */ function metricsQueryEditor(dynamicDirectiveSrv, datasourceSrv) { @@ -43,6 +117,6 @@ function metricsQueryOptions(dynamicDirectiveSrv, datasourceSrv) { }); } -angular.module('grafana.directives') - .directive('metricsQueryEditor', metricsQueryEditor) - .directive('metricsQueryOptions', metricsQueryOptions); +directivesModule.directive('pluginDirectiveLoader', pluginDirectiveLoader); +directivesModule.directive('metricsQueryEditor', metricsQueryEditor); +directivesModule.directive('metricsQueryOptions', metricsQueryOptions); diff --git a/public/app/partials/metrics.html b/public/app/partials/metrics.html index 8a7eb1bfd71..6d580e7575f 100644 --- a/public/app/partials/metrics.html +++ b/public/app/partials/metrics.html @@ -1,8 +1,8 @@
- - + +
diff --git a/public/app/plugins/datasource/grafana/module.ts b/public/app/plugins/datasource/grafana/module.ts index f52676adb1c..b725a162426 100644 --- a/public/app/plugins/datasource/grafana/module.ts +++ b/public/app/plugins/datasource/grafana/module.ts @@ -9,10 +9,23 @@ function grafanaMetricsQueryEditor() { return {templateUrl: 'app/plugins/datasource/grafana/partials/query.editor.html'}; } +export class MetricsQueryEditor { + panelCtrl: any; + target: any; +} + +class GrafanaMetricsQueryEditor extends MetricsQueryEditor { + static templateUrl = 'app/plugins/datasource/grafana/partials/query.editor.html'; + + constructor() { + super(); + console.log('this is a metrics editor', this.panelCtrl, this.target); + } +} export { GrafanaDatasource, GrafanaDatasource as Datasource, - grafanaMetricsQueryEditor as metricsQueryEditor + GrafanaMetricsQueryEditor as MetricsQueryEditor, }; From c843637a6a34ab5c26637983e8ce5620357b64ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 1 Feb 2016 13:02:11 +0100 Subject: [PATCH 02/32] more progress on some experimental stuff --- public/app/features/panel/query_editor.ts | 4 ++++ .../app/plugins/datasource/grafana/module.ts | 18 +----------------- .../grafana/partials/query.editor.html | 2 +- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/public/app/features/panel/query_editor.ts b/public/app/features/panel/query_editor.ts index 5c33480a3ab..89038f0f430 100644 --- a/public/app/features/panel/query_editor.ts +++ b/public/app/features/panel/query_editor.ts @@ -30,6 +30,10 @@ function pluginDirectiveLoader($compile, datasourceSrv) { case "metrics-query-editor": { let datasource = scope.target.datasource || scope.ctrl.panel.datasource; return datasourceSrv.get(datasource).then(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, diff --git a/public/app/plugins/datasource/grafana/module.ts b/public/app/plugins/datasource/grafana/module.ts index b725a162426..860cae89483 100644 --- a/public/app/plugins/datasource/grafana/module.ts +++ b/public/app/plugins/datasource/grafana/module.ts @@ -3,24 +3,8 @@ import angular from 'angular'; import {GrafanaDatasource} from './datasource'; -var module = angular.module('grafana.directives'); - -function grafanaMetricsQueryEditor() { - return {templateUrl: 'app/plugins/datasource/grafana/partials/query.editor.html'}; -} - -export class MetricsQueryEditor { - panelCtrl: any; - target: any; -} - -class GrafanaMetricsQueryEditor extends MetricsQueryEditor { +class GrafanaMetricsQueryEditor { static templateUrl = 'app/plugins/datasource/grafana/partials/query.editor.html'; - - constructor() { - super(); - console.log('this is a metrics editor', this.panelCtrl, this.target); - } } export { diff --git a/public/app/plugins/datasource/grafana/partials/query.editor.html b/public/app/plugins/datasource/grafana/partials/query.editor.html index 15297d5c3f0..fd2953e4be4 100644 --- a/public/app/plugins/datasource/grafana/partials/query.editor.html +++ b/public/app/plugins/datasource/grafana/partials/query.editor.html @@ -41,7 +41,7 @@
- +
+ + +
+ +
Testing....
diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index ab3d6001e67..63484785d75 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -50,7 +50,7 @@ var module = angular.module('grafana.directives'); module.directive('grafanaPanel', function() { return { restrict: 'E', - templateUrl: 'app/features/panel/partials/panel.html', + templateUrl: 'public/app/features/panel/partials/panel.html', transclude: true, scope: { ctrl: "=" }, link: function(scope, elem) { diff --git a/public/app/features/panel/query_editor.ts b/public/app/features/panel/query_editor.ts index c456f42cc40..8e43019fdf3 100644 --- a/public/app/features/panel/query_editor.ts +++ b/public/app/features/panel/query_editor.ts @@ -5,105 +5,6 @@ import _ from 'lodash'; var directivesModule = angular.module('grafana.directives'); -function pluginDirectiveLoader($compile, datasourceSrv) { - - function getPluginComponentDirective(options) { - return function() { - return { - templateUrl: options.Component.templateUrl, - restrict: 'E', - controller: options.Component, - controllerAs: 'ctrl', - bindToController: true, - scope: options.bindings, - link: (scope, elem, attrs, ctrl) => { - if (ctrl.link) { - ctrl.link(scope, elem, attrs, ctrl); - } - } - }; - }; - } - - function getModule(scope, attrs) { - switch (attrs.type) { - case "metrics-query-editor": { - let datasource = scope.target.datasource || scope.ctrl.panel.datasource; - return datasourceSrv.get(datasource).then(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, - bindings: {target: "=", panelCtrl: "="}, - attrs: {"target": "target", "panel-ctrl": "ctrl"}, - Component: dsModule.MetricsQueryEditor - }; - }); - }); - } - } - } - - function appendAndCompile(scope, elem, componentInfo) { - var child = angular.element(document.createElement(componentInfo.name)); - _.each(componentInfo.attrs, (value, key) => { - child.attr(key, value); - }); - - $compile(child)(scope); - - elem.empty(); - elem.append(child); - } - - function registerPluginComponent(scope, elem, attrs, componentInfo) { - if (!componentInfo.Component.registered) { - var directiveName = attrs.$normalize(componentInfo.name); - var directiveFn = getPluginComponentDirective(componentInfo); - directivesModule.directive(directiveName, directiveFn); - componentInfo.Component.registered = true; - } - - appendAndCompile(scope, elem, componentInfo); - } - - return { - restrict: 'E', - link: function(scope, elem, attrs) { - getModule(scope, attrs).then(function (componentInfo) { - registerPluginComponent(scope, elem, attrs, componentInfo); - }); - } - }; -} - -/** @ngInject */ -function metricsQueryEditor(dynamicDirectiveSrv, datasourceSrv) { - return dynamicDirectiveSrv.create({ - watchPath: "ctrl.panel.datasource", - directive: scope => { - let datasource = scope.target.datasource || scope.ctrl.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({ @@ -121,6 +22,4 @@ function metricsQueryOptions(dynamicDirectiveSrv, datasourceSrv) { }); } -directivesModule.directive('pluginDirectiveLoader', pluginDirectiveLoader); -directivesModule.directive('metricsQueryEditor', metricsQueryEditor); directivesModule.directive('metricsQueryOptions', metricsQueryOptions); diff --git a/public/app/plugins/datasource/graphite/module.js b/public/app/plugins/datasource/graphite/module.js index 64f0945aef3..a1d4ed5fc2b 100644 --- a/public/app/plugins/datasource/graphite/module.js +++ b/public/app/plugins/datasource/graphite/module.js @@ -23,11 +23,16 @@ function (GraphiteDatasource) { return {templateUrl: 'public/app/plugins/datasource/graphite/partials/config.html'}; } + function ConfigView() { + } + ConfigView.templateUrl = 'public/app/plugins/datasource/graphite/partials/config.html'; + return { Datasource: GraphiteDatasource, configView: configView, annotationsQueryEditor: annotationsQueryEditor, metricsQueryEditor: metricsQueryEditor, metricsQueryOptions: metricsQueryOptions, + ConfigView: ConfigView }; }); diff --git a/public/app/plugins/datasource/prometheus/datasource.d.ts b/public/app/plugins/datasource/prometheus/datasource.d.ts deleted file mode 100644 index a50d7ca49cc..00000000000 --- a/public/app/plugins/datasource/prometheus/datasource.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare var Datasource: any; -export default Datasource; - diff --git a/public/app/plugins/datasource/prometheus/datasource.js b/public/app/plugins/datasource/prometheus/datasource.js deleted file mode 100644 index 6b35a966e85..00000000000 --- a/public/app/plugins/datasource/prometheus/datasource.js +++ /dev/null @@ -1,282 +0,0 @@ -define([ - 'angular', - 'lodash', - 'moment', - 'app/core/utils/datemath', - './query_ctrl', -], -function (angular, _, moment, dateMath) { - 'use strict'; - - var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/; - - /** @ngInject */ - function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv) { - this.type = 'prometheus'; - this.editorSrc = 'app/features/prometheus/partials/query.editor.html'; - this.name = instanceSettings.name; - this.supportMetrics = true; - this.url = instanceSettings.url; - this.directUrl = instanceSettings.directUrl; - this.basicAuth = instanceSettings.basicAuth; - this.withCredentials = instanceSettings.withCredentials; - this.lastErrors = {}; - - this._request = function(method, url) { - var options = { - url: this.url + url, - method: method - }; - - if (this.basicAuth || this.withCredentials) { - options.withCredentials = true; - } - if (this.basicAuth) { - options.headers = { - "Authorization": this.basicAuth - }; - } - - return backendSrv.datasourceRequest(options); - }; - - // Called once per panel (graph) - this.query = function(options) { - var start = getPrometheusTime(options.range.from, false); - var end = getPrometheusTime(options.range.to, true); - - var queries = []; - options = _.clone(options); - _.each(options.targets, _.bind(function(target) { - if (!target.expr || target.hide) { - return; - } - - var query = {}; - query.expr = templateSrv.replace(target.expr, options.scopedVars); - - var interval = target.interval || options.interval; - var intervalFactor = target.intervalFactor || 1; - target.step = query.step = this.calculateInterval(interval, intervalFactor); - var range = Math.ceil(end - start); - // Prometheus drop query if range/step > 11000 - // calibrate step if it is too big - if (query.step !== 0 && range / query.step > 11000) { - target.step = query.step = Math.ceil(range / 11000); - } - - queries.push(query); - }, this)); - - // No valid targets, return the empty result to save a round trip. - if (_.isEmpty(queries)) { - var d = $q.defer(); - d.resolve({ data: [] }); - return d.promise; - } - - var allQueryPromise = _.map(queries, _.bind(function(query) { - return this.performTimeSeriesQuery(query, start, end); - }, this)); - - var self = this; - return $q.all(allQueryPromise) - .then(function(allResponse) { - var result = []; - - _.each(allResponse, function(response, index) { - if (response.status === 'error') { - self.lastErrors.query = response.error; - throw response.error; - } - delete self.lastErrors.query; - - _.each(response.data.data.result, function(metricData) { - result.push(transformMetricData(metricData, options.targets[index], start, end)); - }); - }); - - return { data: result }; - }); - }; - - this.performTimeSeriesQuery = function(query, start, end) { - var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end + '&step=' + query.step; - return this._request('GET', url); - }; - - this.performSuggestQuery = function(query) { - var url = '/api/v1/label/__name__/values'; - - return this._request('GET', url).then(function(result) { - return _.filter(result.data.data, function (metricName) { - return metricName.indexOf(query) !== 1; - }); - }); - }; - - this.metricFindQuery = function(query) { - if (!query) { return $q.when([]); } - - var interpolated; - try { - interpolated = templateSrv.replace(query); - } - catch (err) { - return $q.reject(err); - } - - var label_values_regex = /^label_values\(([^,]+)(?:,\s*(.+))?\)$/; - var metric_names_regex = /^metrics\((.+)\)$/; - - var url; - var label_values_query = interpolated.match(label_values_regex); - if (label_values_query) { - if (!label_values_query[2]) { - // return label values globally - url = '/api/v1/label/' + label_values_query[1] + '/values'; - - return this._request('GET', url).then(function(result) { - return _.map(result.data.data, function(value) { - return {text: value}; - }); - }); - } else { - url = '/api/v1/series?match[]=' + encodeURIComponent(label_values_query[1]); - - return this._request('GET', url) - .then(function(result) { - return _.map(result.data.data, function(metric) { - return { - text: metric[label_values_query[2]], - expandable: true - }; - }); - }); - } - } - - var metric_names_query = interpolated.match(metric_names_regex); - if (metric_names_query) { - url = '/api/v1/label/__name__/values'; - - return this._request('GET', url) - .then(function(result) { - return _.chain(result.data.data) - .filter(function(metricName) { - var r = new RegExp(metric_names_query[1]); - return r.test(metricName); - }) - .map(function(matchedMetricName) { - return { - text: matchedMetricName, - expandable: true - }; - }) - .value(); - }); - } else { - // if query contains full metric name, return metric name and label list - url = '/api/v1/series?match[]=' + encodeURIComponent(interpolated); - - return this._request('GET', url) - .then(function(result) { - return _.map(result.data.data, function(metric) { - return { - text: getOriginalMetricName(metric), - expandable: true - }; - }); - }); - } - }; - - this.testDatasource = function() { - return this.metricFindQuery('metrics(.*)').then(function() { - return { status: 'success', message: 'Data source is working', title: 'Success' }; - }); - }; - - PrometheusDatasource.prototype.calculateInterval = function(interval, intervalFactor) { - var m = interval.match(durationSplitRegexp); - var dur = moment.duration(parseInt(m[1]), m[2]); - var sec = dur.asSeconds(); - if (sec < 1) { - sec = 1; - } - - return Math.ceil(sec * intervalFactor); - }; - - function transformMetricData(md, options, start, end) { - var dps = [], - metricLabel = null; - - metricLabel = createMetricLabel(md.metric, options); - - var stepMs = parseInt(options.step) * 1000; - var baseTimestamp = start * 1000; - _.each(md.values, function(value) { - var dp_value = parseFloat(value[1]); - if (_.isNaN(dp_value)) { - dp_value = null; - } - - var timestamp = value[0] * 1000; - for (var t = baseTimestamp; t < timestamp; t += stepMs) { - dps.push([null, t]); - } - baseTimestamp = timestamp + stepMs; - dps.push([dp_value, timestamp]); - }); - - var endTimestamp = end * 1000; - for (var t = baseTimestamp; t <= endTimestamp; t += stepMs) { - dps.push([null, t]); - } - - return { target: metricLabel, datapoints: dps }; - } - - function createMetricLabel(labelData, options) { - if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) { - return getOriginalMetricName(labelData); - } - - var originalSettings = _.templateSettings; - _.templateSettings = { - interpolate: /\{\{(.+?)\}\}/g - }; - - var template = _.template(templateSrv.replace(options.legendFormat)); - var metricName; - try { - metricName = template(labelData); - } catch (e) { - metricName = '{}'; - } - - _.templateSettings = originalSettings; - - return metricName; - } - - function getOriginalMetricName(labelData) { - var metricName = labelData.__name__ || ''; - delete labelData.__name__; - var labelPart = _.map(_.pairs(labelData), function(label) { - return label[0] + '="' + label[1] + '"'; - }).join(','); - return metricName + '{' + labelPart + '}'; - } - - function getPrometheusTime(date, roundUp) { - if (_.isString(date)) { - date = dateMath.parse(date, roundUp); - } - return (date.valueOf() / 1000).toFixed(0); - } - } - - return PrometheusDatasource; -}); diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts new file mode 100644 index 00000000000..9b754b58eb3 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -0,0 +1,278 @@ +/// + +import angular from 'angular'; +import _ from 'lodash'; +import moment from 'moment'; + +import * as dateMath from 'app/core/utils/datemath'; + +var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/; + +/** @ngInject */ +function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv) { + this.type = 'prometheus'; + this.editorSrc = 'app/features/prometheus/partials/query.editor.html'; + this.name = instanceSettings.name; + this.supportMetrics = true; + this.url = instanceSettings.url; + this.directUrl = instanceSettings.directUrl; + this.basicAuth = instanceSettings.basicAuth; + this.withCredentials = instanceSettings.withCredentials; + this.lastErrors = {}; + + this._request = function(method, url) { + var options: any = { + url: this.url + url, + method: method + }; + + if (this.basicAuth || this.withCredentials) { + options.withCredentials = true; + } + if (this.basicAuth) { + options.headers = { + "Authorization": this.basicAuth + }; + } + + return backendSrv.datasourceRequest(options); + }; + + // Called once per panel (graph) + this.query = function(options) { + var start = getPrometheusTime(options.range.from, false); + var end = getPrometheusTime(options.range.to, true); + + var queries = []; + options = _.clone(options); + _.each(options.targets, _.bind(function(target) { + if (!target.expr || target.hide) { + return; + } + + var query: any = {}; + query.expr = templateSrv.replace(target.expr, options.scopedVars); + + var interval = target.interval || options.interval; + var intervalFactor = target.intervalFactor || 1; + target.step = query.step = this.calculateInterval(interval, intervalFactor); + var range = Math.ceil(end - start); + // Prometheus drop query if range/step > 11000 + // calibrate step if it is too big + if (query.step !== 0 && range / query.step > 11000) { + target.step = query.step = Math.ceil(range / 11000); + } + + queries.push(query); + }, this)); + + // No valid targets, return the empty result to save a round trip. + if (_.isEmpty(queries)) { + var d = $q.defer(); + d.resolve({ data: [] }); + return d.promise; + } + + var allQueryPromise = _.map(queries, _.bind(function(query) { + return this.performTimeSeriesQuery(query, start, end); + }, this)); + + var self = this; + return $q.all(allQueryPromise) + .then(function(allResponse) { + var result = []; + + _.each(allResponse, function(response, index) { + if (response.status === 'error') { + self.lastErrors.query = response.error; + throw response.error; + } + delete self.lastErrors.query; + + _.each(response.data.data.result, function(metricData) { + result.push(transformMetricData(metricData, options.targets[index], start, end)); + }); + }); + + return { data: result }; + }); + }; + + this.performTimeSeriesQuery = function(query, start, end) { + var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end + '&step=' + query.step; + return this._request('GET', url); + }; + + this.performSuggestQuery = function(query) { + var url = '/api/v1/label/__name__/values'; + + return this._request('GET', url).then(function(result) { + return _.filter(result.data.data, function (metricName) { + return metricName.indexOf(query) !== 1; + }); + }); + }; + + this.metricFindQuery = function(query) { + if (!query) { return $q.when([]); } + + var interpolated; + try { + interpolated = templateSrv.replace(query); + } catch (err) { + return $q.reject(err); + } + + var label_values_regex = /^label_values\(([^,]+)(?:,\s*(.+))?\)$/; + var metric_names_regex = /^metrics\((.+)\)$/; + + var url; + var label_values_query = interpolated.match(label_values_regex); + if (label_values_query) { + if (!label_values_query[2]) { + // return label values globally + url = '/api/v1/label/' + label_values_query[1] + '/values'; + + return this._request('GET', url).then(function(result) { + return _.map(result.data.data, function(value) { + return {text: value}; + }); + }); + } else { + url = '/api/v1/series?match[]=' + encodeURIComponent(label_values_query[1]); + + return this._request('GET', url) + .then(function(result) { + return _.map(result.data.data, function(metric) { + return { + text: metric[label_values_query[2]], + expandable: true + }; + }); + }); + } + } + + var metric_names_query = interpolated.match(metric_names_regex); + if (metric_names_query) { + url = '/api/v1/label/__name__/values'; + + return this._request('GET', url) + .then(function(result) { + return _.chain(result.data.data) + .filter(function(metricName) { + var r = new RegExp(metric_names_query[1]); + return r.test(metricName); + }) + .map(function(matchedMetricName) { + return { + text: matchedMetricName, + expandable: true + }; + }) + .value(); + }); + } else { + // if query contains full metric name, return metric name and label list + url = '/api/v1/series?match[]=' + encodeURIComponent(interpolated); + + return this._request('GET', url) + .then(function(result) { + return _.map(result.data.data, function(metric) { + return { + text: getOriginalMetricName(metric), + expandable: true + }; + }); + }); + } + }; + + this.testDatasource = function() { + return this.metricFindQuery('metrics(.*)').then(function() { + return { status: 'success', message: 'Data source is working', title: 'Success' }; + }); + }; + + PrometheusDatasource.prototype.calculateInterval = function(interval, intervalFactor) { + var m = interval.match(durationSplitRegexp); + var dur = moment.duration(parseInt(m[1]), m[2]); + var sec = dur.asSeconds(); + if (sec < 1) { + sec = 1; + } + + return Math.ceil(sec * intervalFactor); + }; + + function transformMetricData(md, options, start, end) { + var dps = [], + metricLabel = null; + + metricLabel = createMetricLabel(md.metric, options); + + var stepMs = parseInt(options.step) * 1000; + var baseTimestamp = start * 1000; + _.each(md.values, function(value) { + var dp_value = parseFloat(value[1]); + if (_.isNaN(dp_value)) { + dp_value = null; + } + + var timestamp = value[0] * 1000; + for (var t = baseTimestamp; t < timestamp; t += stepMs) { + dps.push([null, t]); + } + baseTimestamp = timestamp + stepMs; + dps.push([dp_value, timestamp]); + }); + + var endTimestamp = end * 1000; + for (var t = baseTimestamp; t <= endTimestamp; t += stepMs) { + dps.push([null, t]); + } + + return { target: metricLabel, datapoints: dps }; + } + + function createMetricLabel(labelData, options) { + if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) { + return getOriginalMetricName(labelData); + } + + var originalSettings = _.templateSettings; + _.templateSettings = { + interpolate: /\{\{(.+?)\}\}/g + }; + + var template = _.template(templateSrv.replace(options.legendFormat)); + var metricName; + try { + metricName = template(labelData); + } catch (e) { + metricName = '{}'; + } + + _.templateSettings = originalSettings; + + return metricName; + } + + function getOriginalMetricName(labelData) { + var metricName = labelData.__name__ || ''; + delete labelData.__name__; + var labelPart = _.map(_.pairs(labelData), function(label) { + return label[0] + '="' + label[1] + '"'; + }).join(','); + return metricName + '{' + labelPart + '}'; + } + + function getPrometheusTime(date, roundUp): number { + if (_.isString(date)) { + date = dateMath.parse(date, roundUp); + } + return Math.floor(date.valueOf() / 1000); + } +} + +export {PrometheusDatasource}; diff --git a/public/app/plugins/datasource/prometheus/module.js b/public/app/plugins/datasource/prometheus/module.js deleted file mode 100644 index 042224c2e38..00000000000 --- a/public/app/plugins/datasource/prometheus/module.js +++ /dev/null @@ -1,20 +0,0 @@ -define([ - './datasource', -], -function (PromDatasource) { - 'use strict'; - - function metricsQueryEditor() { - return {controller: 'PrometheusQueryCtrl', templateUrl: 'public/app/plugins/datasource/prometheus/partials/query.editor.html'}; - } - - function configView() { - return {templateUrl: 'public/app/plugins/datasource/prometheus/partials/config.html'}; - } - - return { - Datasource: PromDatasource, - metricsQueryEditor: metricsQueryEditor, - configView: configView, - }; -}); diff --git a/public/app/plugins/datasource/prometheus/module.ts b/public/app/plugins/datasource/prometheus/module.ts new file mode 100644 index 00000000000..b04bd10004d --- /dev/null +++ b/public/app/plugins/datasource/prometheus/module.ts @@ -0,0 +1,23 @@ +import {PrometheusDatasource} from './datasource'; +import {PrometheusQueryCtrl} from './query_ctrl'; + + + + + // function metricsQueryEditor() { + // return {controller: 'PrometheusQueryCtrl', templateUrl: 'public/app/plugins/datasource/prometheus/partials/query.editor.html'}; + // } + // + // function configView() { + // return {templateUrl: ''}; + // } + +class PrometheusConfigViewCtrl { + static templateUrl = 'public/app/plugins/datasource/prometheus/partials/config.html'; +} + +export { + PrometheusDatasource as Datasource, + PrometheusQueryCtrl as MetricsQueryEditor, + PrometheusConfigViewCtrl as ConfigView +}; diff --git a/public/app/plugins/datasource/prometheus/query_ctrl.js b/public/app/plugins/datasource/prometheus/query_ctrl.js deleted file mode 100644 index b78152c62d0..00000000000 --- a/public/app/plugins/datasource/prometheus/query_ctrl.js +++ /dev/null @@ -1,67 +0,0 @@ -define([ - 'angular', - 'lodash', -], -function (angular, _) { - 'use strict'; - - var module = angular.module('grafana.controllers'); - - module.controller('PrometheusQueryCtrl', function($scope, templateSrv) { - $scope.panelCtrl = $scope.ctrl; - $scope.panel = $scope.panelCtrl.panel; - - $scope.init = function() { - var target = $scope.target; - - target.expr = target.expr || ''; - target.intervalFactor = target.intervalFactor || 2; - - $scope.metric = ''; - $scope.resolutions = _.map([1,2,3,4,5,10], function(f) { - return {factor: f, label: '1/' + f}; - }); - - $scope.$on('typeahead-updated', function() { - $scope.$apply($scope.inputMetric); - $scope.refreshMetricData(); - }); - }; - - $scope.refreshMetricData = function() { - if (!_.isEqual($scope.oldTarget, $scope.target)) { - $scope.oldTarget = angular.copy($scope.target); - $scope.paneCtrl.refresh(); - } - }; - - $scope.inputMetric = function() { - $scope.target.expr += $scope.target.metric; - $scope.metric = ''; - }; - - $scope.suggestMetrics = function(query, callback) { - $scope.datasource - .performSuggestQuery(query) - .then(callback); - }; - - $scope.linkToPrometheus = function() { - var range = Math.ceil(($scope.range.to.valueOf() - $scope.range.from.valueOf()) / 1000); - var endTime = $scope.range.to.utc().format('YYYY-MM-DD HH:mm'); - var expr = { - expr: templateSrv.replace($scope.target.expr, $scope.panel.scopedVars), - range_input: range + 's', - end_input: endTime, - step_input: '', - stacked: $scope.panel.stack, - tab: 0 - }; - var hash = encodeURIComponent(JSON.stringify([expr])); - return $scope.datasource.directUrl + '/graph#' + hash; - }; - - $scope.init(); - }); - -}); diff --git a/public/app/plugins/datasource/prometheus/query_ctrl.ts b/public/app/plugins/datasource/prometheus/query_ctrl.ts new file mode 100644 index 00000000000..e0fd9aea02d --- /dev/null +++ b/public/app/plugins/datasource/prometheus/query_ctrl.ts @@ -0,0 +1,66 @@ +/// + +import angular from 'angular'; +import _ from 'lodash'; +import moment from 'moment'; + +import * as dateMath from 'app/core/utils/datemath'; + +function PrometheusQueryCtrl($scope, templateSrv) { + $scope.panelCtrl = $scope.ctrl; + $scope.panel = $scope.panelCtrl.panel; + + $scope.init = function() { + var target = $scope.target; + + target.expr = target.expr || ''; + target.intervalFactor = target.intervalFactor || 2; + + $scope.metric = ''; + $scope.resolutions = _.map([1,2,3,4,5,10], function(f) { + return {factor: f, label: '1/' + f}; + }); + + $scope.$on('typeahead-updated', function() { + $scope.$apply($scope.inputMetric); + $scope.refreshMetricData(); + }); + }; + + $scope.refreshMetricData = function() { + if (!_.isEqual($scope.oldTarget, $scope.target)) { + $scope.oldTarget = angular.copy($scope.target); + $scope.paneCtrl.refresh(); + } + }; + + $scope.inputMetric = function() { + $scope.target.expr += $scope.target.metric; + $scope.metric = ''; + }; + + $scope.suggestMetrics = function(query, callback) { + $scope.datasource + .performSuggestQuery(query) + .then(callback); + }; + + $scope.linkToPrometheus = function() { + var range = Math.ceil(($scope.range.to.valueOf() - $scope.range.from.valueOf()) / 1000); + var endTime = $scope.range.to.utc().format('YYYY-MM-DD HH:mm'); + var expr = { + expr: templateSrv.replace($scope.target.expr, $scope.panel.scopedVars), + range_input: range + 's', + end_input: endTime, + step_input: '', + stacked: $scope.panel.stack, + tab: 0 + }; + var hash = encodeURIComponent(JSON.stringify([expr])); + return $scope.datasource.directUrl + '/graph#' + hash; + }; + + $scope.init(); +} + +export {PrometheusQueryCtrl}; diff --git a/public/views/index.html b/public/views/index.html index 728cd084c88..1d612e53385 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -59,7 +59,6 @@ - From 0583ec0f933874ae330f46fdbb44d50d5fc6b0c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 1 Feb 2016 23:32:12 +0100 Subject: [PATCH 04/32] feat(plugins): more work on plugin directives and isolation --- public/app/core/directives/plugin_directive_loader.ts | 1 - public/app/features/datasources/edit_ctrl.js | 5 ++++- public/app/features/datasources/partials/edit.html | 2 -- public/app/plugins/datasource/graphite/partials/config.html | 3 ++- .../app/plugins/datasource/prometheus/partials/config.html | 3 ++- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/public/app/core/directives/plugin_directive_loader.ts b/public/app/core/directives/plugin_directive_loader.ts index b6abb91b065..4456afacbe1 100644 --- a/public/app/core/directives/plugin_directive_loader.ts +++ b/public/app/core/directives/plugin_directive_loader.ts @@ -56,7 +56,6 @@ function pluginDirectiveLoader($compile, datasourceSrv) { } function appendAndCompile(scope, elem, componentInfo) { - console.log('compile', elem, componentInfo); var child = angular.element(document.createElement(componentInfo.name)); _.each(componentInfo.attrs, (value, key) => { child.attr(key, value); diff --git a/public/app/features/datasources/edit_ctrl.js b/public/app/features/datasources/edit_ctrl.js index 2bd86e5f385..c3242f5b565 100644 --- a/public/app/features/datasources/edit_ctrl.js +++ b/public/app/features/datasources/edit_ctrl.js @@ -10,7 +10,10 @@ function (angular, _, config) { var datasourceTypes = []; module.directive('datasourceHttpSettings', function() { - return {templateUrl: 'public/app/features/datasources/partials/http_settings.html'}; + return { + scope: {current: "="}, + templateUrl: 'public/app/features/datasources/partials/http_settings.html' + }; }); module.controller('DataSourceEditCtrl', function($scope, $q, backendSrv, $routeParams, $location, datasourceSrv) { diff --git a/public/app/features/datasources/partials/edit.html b/public/app/features/datasources/partials/edit.html index 5b9facb83a8..c27be475bd5 100644 --- a/public/app/features/datasources/partials/edit.html +++ b/public/app/features/datasources/partials/edit.html @@ -46,8 +46,6 @@
- -
Testing....
Test results
diff --git a/public/app/plugins/datasource/graphite/partials/config.html b/public/app/plugins/datasource/graphite/partials/config.html index 9f5259cb2ea..d7a0f739ccb 100644 --- a/public/app/plugins/datasource/graphite/partials/config.html +++ b/public/app/plugins/datasource/graphite/partials/config.html @@ -1,2 +1,3 @@ - + + diff --git a/public/app/plugins/datasource/prometheus/partials/config.html b/public/app/plugins/datasource/prometheus/partials/config.html index 9f5259cb2ea..d7a0f739ccb 100644 --- a/public/app/plugins/datasource/prometheus/partials/config.html +++ b/public/app/plugins/datasource/prometheus/partials/config.html @@ -1,2 +1,3 @@ - + + From eaaf9246b7284c68aa5bf6e9cca1b75a30ac3359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 2 Feb 2016 09:12:58 +0100 Subject: [PATCH 05/32] feat(plugins): more work on refining datasource editors --- public/app/core/core.ts | 2 +- .../app/core/directives/plugin_component.ts | 90 +++++++++++++++++++ .../directives/plugin_directive_loader.ts | 5 +- public/app/features/dashboard/dashboardSrv.js | 36 -------- .../app/features/panel/metrics_panel_ctrl.ts | 24 ++--- public/app/features/panel/panel.ts | 2 + public/app/features/panel/query_editor.ts | 79 ++++++++++++---- public/app/partials/metrics.html | 4 +- .../plugins/datasource/influxdb/datasource.js | 1 - .../prometheus/partials/query.editor.html | 16 ++-- .../datasource/prometheus/query_ctrl.ts | 70 ++++++++------- 11 files changed, 211 insertions(+), 118 deletions(-) create mode 100644 public/app/core/directives/plugin_component.ts diff --git a/public/app/core/core.ts b/public/app/core/core.ts index c216151f2dc..ca608fb35f9 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -16,7 +16,7 @@ import "./directives/password_strenght"; import "./directives/spectrum_picker"; import "./directives/tags"; import "./directives/value_select_dropdown"; -import "./directives/plugin_directive_loader"; +import "./directives/plugin_component"; import "./directives/rebuild_on_change"; import "./directives/give_focus"; import './jquery_extended'; diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts new file mode 100644 index 00000000000..1405af3ad3b --- /dev/null +++ b/public/app/core/directives/plugin_component.ts @@ -0,0 +1,90 @@ +/// + +import angular from 'angular'; +import _ from 'lodash'; + +import coreModule from '../core_module'; + +function pluginDirectiveLoader($compile, datasourceSrv) { + + function getPluginComponentDirective(options) { + return function() { + return { + templateUrl: options.Component.templateUrl, + restrict: 'E', + controller: options.Component, + controllerAs: 'ctrl', + bindToController: true, + scope: options.bindings, + link: (scope, elem, attrs, ctrl) => { + if (ctrl.link) { + ctrl.link(scope, elem, attrs, ctrl); + } + } + }; + }; + } + + function getModule(scope, attrs) { + switch (attrs.type) { + case "metrics-query-editor": + let datasource = scope.target.datasource || scope.ctrl.panel.datasource; + return datasourceSrv.get(datasource).then(ds => { + scope.datasource = ds; + + return System.import(ds.meta.module).then(dsModule => { + return { + name: 'metrics-query-editor-' + ds.meta.id, + bindings: {target: "=", panelCtrl: "="}, + attrs: {"target": "target", "panel-ctrl": "ctrl"}, + Component: dsModule.MetricsQueryEditor + }; + }); + }); + + case 'datasource-config-view': + return System.import(scope.datasourceMeta.module).then(function(dsModule) { + return { + name: 'ds-config-' + scope.datasourceMeta.id, + bindings: {meta: "=", current: "="}, + attrs: {meta: "datasourceMeta", current: "current"}, + Component: dsModule.ConfigView, + }; + }); + } + } + + function appendAndCompile(scope, elem, componentInfo) { + var child = angular.element(document.createElement(componentInfo.name)); + _.each(componentInfo.attrs, (value, key) => { + child.attr(key, value); + }); + + $compile(child)(scope); + + elem.empty(); + elem.append(child); + } + + function registerPluginComponent(scope, elem, attrs, componentInfo) { + if (!componentInfo.Component.registered) { + var directiveName = attrs.$normalize(componentInfo.name); + var directiveFn = getPluginComponentDirective(componentInfo); + coreModule.directive(directiveName, directiveFn); + componentInfo.Component.registered = true; + } + + appendAndCompile(scope, elem, componentInfo); + } + + return { + restrict: 'E', + link: function(scope, elem, attrs) { + getModule(scope, attrs).then(function (componentInfo) { + registerPluginComponent(scope, elem, attrs, componentInfo); + }); + } + }; +} + +coreModule.directive('pluginComponent', pluginDirectiveLoader); diff --git a/public/app/core/directives/plugin_directive_loader.ts b/public/app/core/directives/plugin_directive_loader.ts index 4456afacbe1..4c0d1092a09 100644 --- a/public/app/core/directives/plugin_directive_loader.ts +++ b/public/app/core/directives/plugin_directive_loader.ts @@ -5,7 +5,8 @@ import _ from 'lodash'; import coreModule from '../core_module'; -function pluginDirectiveLoader($compile, datasourceSrv) { +/** @ngInject */ +function pluginComponentLoader($compile, datasourceSrv) { function getPluginComponentDirective(options) { return function() { @@ -88,4 +89,4 @@ function pluginDirectiveLoader($compile, datasourceSrv) { }; } -coreModule.directive('pluginDirectiveLoader', pluginDirectiveLoader); +coreModule.directive('pluginComponent', pluginComponentLoader); diff --git a/public/app/features/dashboard/dashboardSrv.js b/public/app/features/dashboard/dashboardSrv.js index e4d74c985ba..ad45ce64a70 100644 --- a/public/app/features/dashboard/dashboardSrv.js +++ b/public/app/features/dashboard/dashboardSrv.js @@ -177,42 +177,6 @@ function (angular, $, _, moment) { return newPanel; }; - p.getNextQueryLetter = function(panel) { - var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - - return _.find(letters, function(refId) { - return _.every(panel.targets, function(other) { - return other.refId !== refId; - }); - }); - }; - - p.addDataQueryTo = function(panel, datasource) { - var target = { - refId: this.getNextQueryLetter(panel) - }; - - if (datasource) { - target.datasource = datasource.name; - } - - panel.targets.push(target); - }; - - p.removeDataQuery = function (panel, query) { - panel.targets = _.without(panel.targets, query); - }; - - p.duplicateDataQuery = function(panel, query) { - var clone = angular.copy(query); - clone.refId = this.getNextQueryLetter(panel); - panel.targets.push(clone); - }; - - p.moveDataQuery = function(panel, fromIndex, toIndex) { - _.move(panel.targets, fromIndex, toIndex); - }; - p.formatDate = function(date, format) { date = moment.isMoment(date) ? date : moment(date); format = format || 'YYYY-MM-DD HH:mm:ss'; diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index 76b6a7688e4..148d8647f28 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -193,23 +193,6 @@ class MetricsPanelCtrl extends PanelCtrl { }); } - addDataQuery(datasource) { - this.dashboard.addDataQueryTo(this.panel, datasource); - } - - removeDataQuery(query) { - this.dashboard.removeDataQuery(this.panel, query); - this.refresh(); - }; - - duplicateDataQuery(query) { - this.dashboard.duplicateDataQuery(this.panel, query); - } - - moveDataQuery(fromIndex, toIndex) { - this.dashboard.moveDataQuery(this.panel, fromIndex, toIndex); - } - setDatasource(datasource) { // switching to mixed if (datasource.meta.mixed) { @@ -229,6 +212,13 @@ class MetricsPanelCtrl extends PanelCtrl { this.datasource = null; this.refresh(); } + + addDataQuery(datasource) { + var target = { + datasource: datasource ? datasource.name : undefined + }; + this.panel.targets.push(target); + } } export {MetricsPanelCtrl}; diff --git a/public/app/features/panel/panel.ts b/public/app/features/panel/panel.ts index 535ccdcf06a..3f98b77ff1b 100644 --- a/public/app/features/panel/panel.ts +++ b/public/app/features/panel/panel.ts @@ -5,9 +5,11 @@ import config from 'app/core/config'; import {PanelCtrl} from './panel_ctrl'; import {MetricsPanelCtrl} from './metrics_panel_ctrl'; import {PanelDirective} from './panel_directive'; +import {QueryEditorCtrl} from './query_editor'; export { PanelCtrl, MetricsPanelCtrl, PanelDirective, + QueryEditorCtrl, } diff --git a/public/app/features/panel/query_editor.ts b/public/app/features/panel/query_editor.ts index 8e43019fdf3..47c201fee5d 100644 --- a/public/app/features/panel/query_editor.ts +++ b/public/app/features/panel/query_editor.ts @@ -3,23 +3,70 @@ import angular from 'angular'; import _ from 'lodash'; -var directivesModule = angular.module('grafana.directives'); +export class QueryEditorCtrl { + target: any; + datasource: any; + panelCtrl: any; + panel: any; -/** @ngInject */ -function metricsQueryOptions(dynamicDirectiveSrv, datasourceSrv) { - return dynamicDirectiveSrv.create({ - watchPath: "ctrl.panel.datasource", - directive: scope => { - return datasourceSrv.get(scope.ctrl.panel.datasource).then(ds => { - return System.import(ds.meta.module).then(dsModule => { - return { - name: 'metrics-query-options-' + ds.meta.id, - fn: dsModule.metricsQueryOptions - }; - }); - }); + constructor(private $scope, private $injector) { + this.panel = this.panelCtrl.panel; + this.datasource = $scope.datasource; + + if (!this.target.refId) { + this.target.refId = this.getNextQueryLetter(); } - }); + } + + getNextQueryLetter() { + var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + return _.find(letters, refId => { + return _.every(this.panel.targets, function(other) { + return other.refId !== refId; + }); + }); + } + + removeDataQuery(query) { + this.panel.targets = _.without(this.panel.targets, query); + this.panelCtrl.refresh(); + }; + + duplicateDataQuery(query) { + var clone = angular.copy(query); + clone.refId = this.getNextQueryLetter(); + this.panel.targets.push(clone); + } + + moveDataQuery(direction) { + var index = _.indexOf(this.panel.targets, this.target); + _.move(this.panel.targets, index, index + direction); + } + + toggleHideQuery(target) { + target.hide = !target.hide; + this.panelCtrl.refresh(); + } } -directivesModule.directive('metricsQueryOptions', metricsQueryOptions); +// var directivesModule = angular.module('grafana.directives'); +// +// /** @ngInject */ +// function metricsQueryOptions(dynamicDirectiveSrv, datasourceSrv) { +// return dynamicDirectiveSrv.create({ +// watchPath: "ctrl.panel.datasource", +// directive: scope => { +// return datasourceSrv.get(scope.ctrl.panel.datasource).then(ds => { +// return System.import(ds.meta.module).then(dsModule => { +// return { +// name: 'metrics-query-options-' + ds.meta.id, +// fn: dsModule.metricsQueryOptions +// }; +// }); +// }); +// } +// }); +// } +// +// directivesModule.directive('metricsQueryOptions', metricsQueryOptions); diff --git a/public/app/partials/metrics.html b/public/app/partials/metrics.html index 6d580e7575f..8ab5c4a1677 100644 --- a/public/app/partials/metrics.html +++ b/public/app/partials/metrics.html @@ -1,8 +1,8 @@
- - + +
diff --git a/public/app/plugins/datasource/influxdb/datasource.js b/public/app/plugins/datasource/influxdb/datasource.js index 8d171deb7f9..984d32ab035 100644 --- a/public/app/plugins/datasource/influxdb/datasource.js +++ b/public/app/plugins/datasource/influxdb/datasource.js @@ -4,7 +4,6 @@ define([ 'app/core/utils/datemath', './influx_series', './influx_query', - './query_ctrl', ], function (angular, _, dateMath, InfluxSeries, InfluxQuery) { 'use strict'; diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index 4ad94d022eb..ccb67887164 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -1,7 +1,7 @@
  • - {{target.datasource}} + {{ctrl.target.datasource}}
  • - +
  • @@ -24,12 +24,10 @@
    • - {{target.refId}} + {{ctrl.target.refId}}
    • - +
    • diff --git a/public/app/plugins/datasource/prometheus/query_ctrl.ts b/public/app/plugins/datasource/prometheus/query_ctrl.ts index e0fd9aea02d..6535e16c599 100644 --- a/public/app/plugins/datasource/prometheus/query_ctrl.ts +++ b/public/app/plugins/datasource/prometheus/query_ctrl.ts @@ -5,62 +5,64 @@ import _ from 'lodash'; import moment from 'moment'; import * as dateMath from 'app/core/utils/datemath'; +import {QueryEditorCtrl} from 'app/features/panel/panel'; -function PrometheusQueryCtrl($scope, templateSrv) { - $scope.panelCtrl = $scope.ctrl; - $scope.panel = $scope.panelCtrl.panel; +/** @ngInject */ +class PrometheusQueryCtrl extends QueryEditorCtrl { + static templateUrl = 'public/app/plugins/datasource/prometheus/partials/query.editor.html'; + metric: any; + resolutions: any; + oldTarget: any; - $scope.init = function() { - var target = $scope.target; + constructor($scope, $injector, private templateSrv) { + super($scope, $injector); + var target = this.target; target.expr = target.expr || ''; target.intervalFactor = target.intervalFactor || 2; - $scope.metric = ''; - $scope.resolutions = _.map([1,2,3,4,5,10], function(f) { + this.metric = ''; + this.resolutions = _.map([1,2,3,4,5,10], function(f) { return {factor: f, label: '1/' + f}; }); - $scope.$on('typeahead-updated', function() { - $scope.$apply($scope.inputMetric); - $scope.refreshMetricData(); + $scope.$on('typeahead-updated', () => { + $scope.$apply(this.inputMetric); + this.refreshMetricData(); }); - }; + } - $scope.refreshMetricData = function() { - if (!_.isEqual($scope.oldTarget, $scope.target)) { - $scope.oldTarget = angular.copy($scope.target); - $scope.paneCtrl.refresh(); + refreshMetricData() { + if (!_.isEqual(this.oldTarget, this.target)) { + this.oldTarget = angular.copy(this.target); + this.panelCtrl.refresh(); } - }; + } - $scope.inputMetric = function() { - $scope.target.expr += $scope.target.metric; - $scope.metric = ''; - }; + inputMetric() { + this.target.expr += this.target.metric; + this.metric = ''; + } - $scope.suggestMetrics = function(query, callback) { - $scope.datasource - .performSuggestQuery(query) - .then(callback); - }; + suggestMetrics(query, callback) { + this.datasource.performSuggestQuery(query).then(callback); + } - $scope.linkToPrometheus = function() { - var range = Math.ceil(($scope.range.to.valueOf() - $scope.range.from.valueOf()) / 1000); - var endTime = $scope.range.to.utc().format('YYYY-MM-DD HH:mm'); + linkToPrometheus() { + var range = this.panelCtrl.range; + var rangeDiff = Math.ceil((range.to.valueOf() - range.from.valueOf()) / 1000); + var endTime = range.to.utc().format('YYYY-MM-DD HH:mm'); var expr = { - expr: templateSrv.replace($scope.target.expr, $scope.panel.scopedVars), - range_input: range + 's', + expr: this.templateSrv.replace(this.target.expr, this.panelCtrl.panel.scopedVars), + range_input: rangeDiff + 's', end_input: endTime, step_input: '', - stacked: $scope.panel.stack, + stacked: this.panelCtrl.panel.stack, tab: 0 }; var hash = encodeURIComponent(JSON.stringify([expr])); - return $scope.datasource.directUrl + '/graph#' + hash; + return this.datasource.directUrl + '/graph#' + hash; }; - - $scope.init(); } export {PrometheusQueryCtrl}; From efdd4a66823f4d334e3a5eeae2f23a9291eb09f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 2 Feb 2016 10:19:15 +0100 Subject: [PATCH 06/32] feat(plugins): more work on plugin editors,prometheus query editor is working --- .../app/core/directives/plugin_component.ts | 4 +- .../directives/plugin_directive_loader.ts | 92 ------------------- .../features/datasources/partials/edit.html | 4 +- public/app/features/panel/query_editor.ts | 7 +- .../prometheus/partials/query.editor.html | 30 +++--- .../datasource/prometheus/query_ctrl.ts | 35 ++++--- 6 files changed, 43 insertions(+), 129 deletions(-) delete mode 100644 public/app/core/directives/plugin_directive_loader.ts diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts index 1405af3ad3b..3b18ac62acf 100644 --- a/public/app/core/directives/plugin_component.ts +++ b/public/app/core/directives/plugin_component.ts @@ -35,8 +35,8 @@ function pluginDirectiveLoader($compile, datasourceSrv) { return System.import(ds.meta.module).then(dsModule => { return { name: 'metrics-query-editor-' + ds.meta.id, - bindings: {target: "=", panelCtrl: "="}, - attrs: {"target": "target", "panel-ctrl": "ctrl"}, + bindings: {target: "=", panelCtrl: "=", datasource: "="}, + attrs: {"target": "target", "panel-ctrl": "ctrl", datasource: "datasource"}, Component: dsModule.MetricsQueryEditor }; }); diff --git a/public/app/core/directives/plugin_directive_loader.ts b/public/app/core/directives/plugin_directive_loader.ts deleted file mode 100644 index 4c0d1092a09..00000000000 --- a/public/app/core/directives/plugin_directive_loader.ts +++ /dev/null @@ -1,92 +0,0 @@ -/// - -import angular from 'angular'; -import _ from 'lodash'; - -import coreModule from '../core_module'; - -/** @ngInject */ -function pluginComponentLoader($compile, datasourceSrv) { - - function getPluginComponentDirective(options) { - return function() { - return { - templateUrl: options.Component.templateUrl, - restrict: 'E', - controller: options.Component, - controllerAs: 'ctrl', - bindToController: true, - scope: options.bindings, - link: (scope, elem, attrs, ctrl) => { - if (ctrl.link) { - ctrl.link(scope, elem, attrs, ctrl); - } - } - }; - }; - } - - function getModule(scope, attrs) { - switch (attrs.type) { - case "metrics-query-editor": - let datasource = scope.target.datasource || scope.ctrl.panel.datasource; - return datasourceSrv.get(datasource).then(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, - bindings: {target: "=", panelCtrl: "="}, - attrs: {"target": "target", "panel-ctrl": "ctrl"}, - Component: dsModule.MetricsQueryEditor - }; - }); - }); - case 'datasource-config-view': - return System.import(scope.datasourceMeta.module).then(function(dsModule) { - return { - name: 'ds-config-' + scope.datasourceMeta.id, - bindings: {meta: "=", current: "="}, - attrs: {meta: "datasourceMeta", current: "current"}, - Component: dsModule.ConfigView, - }; - }); - } - } - - function appendAndCompile(scope, elem, componentInfo) { - var child = angular.element(document.createElement(componentInfo.name)); - _.each(componentInfo.attrs, (value, key) => { - child.attr(key, value); - }); - - $compile(child)(scope); - - elem.empty(); - elem.append(child); - } - - function registerPluginComponent(scope, elem, attrs, componentInfo) { - if (!componentInfo.Component.registered) { - var directiveName = attrs.$normalize(componentInfo.name); - var directiveFn = getPluginComponentDirective(componentInfo); - coreModule.directive(directiveName, directiveFn); - componentInfo.Component.registered = true; - } - - appendAndCompile(scope, elem, componentInfo); - } - - return { - restrict: 'E', - link: function(scope, elem, attrs) { - getModule(scope, attrs).then(function (componentInfo) { - registerPluginComponent(scope, elem, attrs, componentInfo); - }); - } - }; -} - -coreModule.directive('pluginComponent', pluginComponentLoader); diff --git a/public/app/features/datasources/partials/edit.html b/public/app/features/datasources/partials/edit.html index c27be475bd5..520515907d5 100644 --- a/public/app/features/datasources/partials/edit.html +++ b/public/app/features/datasources/partials/edit.html @@ -42,8 +42,8 @@
- - + +
diff --git a/public/app/features/panel/query_editor.ts b/public/app/features/panel/query_editor.ts index 47c201fee5d..a186266ecaf 100644 --- a/public/app/features/panel/query_editor.ts +++ b/public/app/features/panel/query_editor.ts @@ -9,9 +9,8 @@ export class QueryEditorCtrl { panelCtrl: any; panel: any; - constructor(private $scope, private $injector) { + constructor(public $scope, private $injector) { this.panel = this.panelCtrl.panel; - this.datasource = $scope.datasource; if (!this.target.refId) { this.target.refId = this.getNextQueryLetter(); @@ -44,8 +43,8 @@ export class QueryEditorCtrl { _.move(this.panel.targets, index, index + direction); } - toggleHideQuery(target) { - target.hide = !target.hide; + toggleHideQuery() { + this.target.hide = !this.target.hide; this.panelCtrl.refresh(); } } diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index ccb67887164..5a5a468ece7 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -1,6 +1,6 @@
    -
  • +
  • {{ctrl.target.datasource}}
  • @@ -27,7 +27,7 @@ {{ctrl.target.refId}}
  • - +
  • @@ -40,12 +40,12 @@
  • + ng-change="ctrl.refreshMetricData()">
  • Metric @@ -53,9 +53,9 @@
  • @@ -70,9 +70,9 @@ Legend format
  • - + ng-model-onblur ng-change="ctrl.refreshMetricData()">
@@ -86,14 +86,14 @@ Step
  • -
  • @@ -102,13 +102,13 @@ Resolution
  • -
  • - +
  • diff --git a/public/app/plugins/datasource/prometheus/query_ctrl.ts b/public/app/plugins/datasource/prometheus/query_ctrl.ts index 6535e16c599..08da9e76735 100644 --- a/public/app/plugins/datasource/prometheus/query_ctrl.ts +++ b/public/app/plugins/datasource/prometheus/query_ctrl.ts @@ -13,6 +13,8 @@ class PrometheusQueryCtrl extends QueryEditorCtrl { metric: any; resolutions: any; oldTarget: any; + suggestMetrics: any; + linkToPrometheus: any; constructor($scope, $injector, private templateSrv) { super($scope, $injector); @@ -27,28 +29,33 @@ class PrometheusQueryCtrl extends QueryEditorCtrl { }); $scope.$on('typeahead-updated', () => { - $scope.$apply(this.inputMetric); - this.refreshMetricData(); + this.$scope.$apply(() => { + + this.target.expr += this.target.metric; + this.metric = ''; + this.refreshMetricData(); + }); }); + + // called from typeahead so need this + // here in order to ensure this ref + this.suggestMetrics = (query, callback) => { + console.log(this); + this.datasource.performSuggestQuery(query).then(callback); + }; + + this.updateLink(); } refreshMetricData() { if (!_.isEqual(this.oldTarget, this.target)) { this.oldTarget = angular.copy(this.target); this.panelCtrl.refresh(); + this.updateLink(); } } - inputMetric() { - this.target.expr += this.target.metric; - this.metric = ''; - } - - suggestMetrics(query, callback) { - this.datasource.performSuggestQuery(query).then(callback); - } - - linkToPrometheus() { + updateLink() { var range = this.panelCtrl.range; var rangeDiff = Math.ceil((range.to.valueOf() - range.from.valueOf()) / 1000); var endTime = range.to.utc().format('YYYY-MM-DD HH:mm'); @@ -61,8 +68,8 @@ class PrometheusQueryCtrl extends QueryEditorCtrl { tab: 0 }; var hash = encodeURIComponent(JSON.stringify([expr])); - return this.datasource.directUrl + '/graph#' + hash; - }; + this.linkToPrometheus = this.datasource.directUrl + '/graph#' + hash; + } } export {PrometheusQueryCtrl}; From 822c8f15759a93a18a70a993fca185829affc2a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 2 Feb 2016 12:52:43 +0100 Subject: [PATCH 07/32] feat(plugins): migrating graphite query editor to new model --- .../app/core/directives/plugin_component.ts | 2 +- public/app/features/dashboard/dashboardSrv.js | 10 + public/app/features/panel/all.js | 2 +- public/app/features/panel/panel.ts | 4 +- .../panel/{query_editor.ts => query_ctrl.ts} | 16 +- .../datasource/graphite/datasource.d.ts | 3 - .../plugins/datasource/graphite/datasource.js | 296 -------- .../plugins/datasource/graphite/datasource.ts | 281 ++++++++ .../app/plugins/datasource/graphite/lexer.js | 682 ------------------ .../app/plugins/datasource/graphite/lexer.ts | 678 +++++++++++++++++ .../app/plugins/datasource/graphite/module.js | 38 - .../app/plugins/datasource/graphite/module.ts | 51 ++ .../app/plugins/datasource/graphite/parser.js | 265 ------- .../app/plugins/datasource/graphite/parser.ts | 258 +++++++ .../graphite/partials/query.editor.html | 34 +- .../plugins/datasource/graphite/query_ctrl.js | 292 -------- .../plugins/datasource/graphite/query_ctrl.ts | 275 +++++++ .../graphite/specs/datasource_specs.ts | 4 +- .../datasource/graphite/specs/lexer_specs.ts | 118 +++ .../datasource/graphite/specs/parser_specs.ts | 183 +++++ .../graphite/specs/query_ctrl_specs.ts | 115 ++- .../datasource/prometheus/datasource.ts | 6 +- .../plugins/datasource/prometheus/module.ts | 13 +- .../datasource/prometheus/query_ctrl.ts | 6 +- .../prometheus/specs/datasource_specs.ts | 4 +- public/test/specs/dashboardSrv-specs.js | 33 - public/test/specs/lexer-specs.js | 122 ---- public/test/specs/parser-specs.js | 188 ----- tslint.json | 2 +- 29 files changed, 1946 insertions(+), 2035 deletions(-) rename public/app/features/panel/{query_editor.ts => query_ctrl.ts} (86%) delete mode 100644 public/app/plugins/datasource/graphite/datasource.d.ts delete mode 100644 public/app/plugins/datasource/graphite/datasource.js create mode 100644 public/app/plugins/datasource/graphite/datasource.ts delete mode 100644 public/app/plugins/datasource/graphite/lexer.js create mode 100644 public/app/plugins/datasource/graphite/lexer.ts delete mode 100644 public/app/plugins/datasource/graphite/module.js create mode 100644 public/app/plugins/datasource/graphite/module.ts delete mode 100644 public/app/plugins/datasource/graphite/parser.js create mode 100644 public/app/plugins/datasource/graphite/parser.ts delete mode 100644 public/app/plugins/datasource/graphite/query_ctrl.js create mode 100644 public/app/plugins/datasource/graphite/query_ctrl.ts create mode 100644 public/app/plugins/datasource/graphite/specs/lexer_specs.ts create mode 100644 public/app/plugins/datasource/graphite/specs/parser_specs.ts delete mode 100644 public/test/specs/lexer-specs.js delete mode 100644 public/test/specs/parser-specs.js diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts index 3b18ac62acf..d4722c6d2f5 100644 --- a/public/app/core/directives/plugin_component.ts +++ b/public/app/core/directives/plugin_component.ts @@ -37,7 +37,7 @@ function pluginDirectiveLoader($compile, datasourceSrv) { name: 'metrics-query-editor-' + ds.meta.id, bindings: {target: "=", panelCtrl: "=", datasource: "="}, attrs: {"target": "target", "panel-ctrl": "ctrl", datasource: "datasource"}, - Component: dsModule.MetricsQueryEditor + Component: dsModule.QueryCtrl }; }); }); diff --git a/public/app/features/dashboard/dashboardSrv.js b/public/app/features/dashboard/dashboardSrv.js index ad45ce64a70..2f64fec9be1 100644 --- a/public/app/features/dashboard/dashboardSrv.js +++ b/public/app/features/dashboard/dashboardSrv.js @@ -194,6 +194,16 @@ function (angular, $, _, moment) { moment.utc(date).fromNow(); }; + p.getNextQueryLetter = function(panel) { + var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + return _.find(letters, function(refId) { + return _.every(panel.targets, function(other) { + return other.refId !== refId; + }); + }); + }; + p._updateSchema = function(old) { var i, j, k; var oldVersion = this.schemaVersion; diff --git a/public/app/features/panel/all.js b/public/app/features/panel/all.js index c03089bbf9b..96a119c8b48 100644 --- a/public/app/features/panel/all.js +++ b/public/app/features/panel/all.js @@ -3,6 +3,6 @@ define([ './panel_directive', './solo_panel_ctrl', './panel_loader', - './query_editor', + './query_ctrl', './panel_editor_tab', ], function () {}); diff --git a/public/app/features/panel/panel.ts b/public/app/features/panel/panel.ts index 3f98b77ff1b..634591801b6 100644 --- a/public/app/features/panel/panel.ts +++ b/public/app/features/panel/panel.ts @@ -5,11 +5,11 @@ import config from 'app/core/config'; import {PanelCtrl} from './panel_ctrl'; import {MetricsPanelCtrl} from './metrics_panel_ctrl'; import {PanelDirective} from './panel_directive'; -import {QueryEditorCtrl} from './query_editor'; +import {QueryCtrl} from './query_ctrl'; export { PanelCtrl, MetricsPanelCtrl, PanelDirective, - QueryEditorCtrl, + QueryCtrl, } diff --git a/public/app/features/panel/query_editor.ts b/public/app/features/panel/query_ctrl.ts similarity index 86% rename from public/app/features/panel/query_editor.ts rename to public/app/features/panel/query_ctrl.ts index a186266ecaf..5227febed2f 100644 --- a/public/app/features/panel/query_editor.ts +++ b/public/app/features/panel/query_ctrl.ts @@ -3,7 +3,7 @@ import angular from 'angular'; import _ from 'lodash'; -export class QueryEditorCtrl { +export class QueryCtrl { target: any; datasource: any; panelCtrl: any; @@ -27,22 +27,26 @@ export class QueryEditorCtrl { }); } - removeDataQuery(query) { - this.panel.targets = _.without(this.panel.targets, query); + removeQuery() { + this.panel.targets = _.without(this.panel.targets, this.target); this.panelCtrl.refresh(); }; - duplicateDataQuery(query) { - var clone = angular.copy(query); + duplicateQuery() { + var clone = angular.copy(this.target); clone.refId = this.getNextQueryLetter(); this.panel.targets.push(clone); } - moveDataQuery(direction) { + moveQuery(direction) { var index = _.indexOf(this.panel.targets, this.target); _.move(this.panel.targets, index, index + direction); } + refresh() { + this.panelCtrl.refresh(); + } + toggleHideQuery() { this.target.hide = !this.target.hide; this.panelCtrl.refresh(); diff --git a/public/app/plugins/datasource/graphite/datasource.d.ts b/public/app/plugins/datasource/graphite/datasource.d.ts deleted file mode 100644 index a50d7ca49cc..00000000000 --- a/public/app/plugins/datasource/graphite/datasource.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare var Datasource: any; -export default Datasource; - diff --git a/public/app/plugins/datasource/graphite/datasource.js b/public/app/plugins/datasource/graphite/datasource.js deleted file mode 100644 index 3a169eaae4e..00000000000 --- a/public/app/plugins/datasource/graphite/datasource.js +++ /dev/null @@ -1,296 +0,0 @@ -define([ - 'angular', - 'lodash', - 'jquery', - 'app/core/config', - 'app/core/utils/datemath', - './query_ctrl', - './func_editor', - './add_graphite_func', -], -function (angular, _, $, config, dateMath) { - 'use strict'; - - /** @ngInject */ - function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv) { - this.basicAuth = instanceSettings.basicAuth; - this.url = instanceSettings.url; - this.name = instanceSettings.name; - this.cacheTimeout = instanceSettings.cacheTimeout; - this.withCredentials = instanceSettings.withCredentials; - this.render_method = instanceSettings.render_method || 'POST'; - - this.query = function(options) { - try { - var graphOptions = { - from: this.translateTime(options.rangeRaw.from, false), - until: this.translateTime(options.rangeRaw.to, true), - targets: options.targets, - format: options.format, - cacheTimeout: options.cacheTimeout || this.cacheTimeout, - maxDataPoints: options.maxDataPoints, - }; - - var params = this.buildGraphiteParams(graphOptions, options.scopedVars); - if (params.length === 0) { - return $q.when([]); - } - - if (options.format === 'png') { - return $q.when(this.url + '/render' + '?' + params.join('&')); - } - - var httpOptions = { method: this.render_method, url: '/render' }; - - if (httpOptions.method === 'GET') { - httpOptions.url = httpOptions.url + '?' + params.join('&'); - } - else { - httpOptions.data = params.join('&'); - httpOptions.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; - } - - return this.doGraphiteRequest(httpOptions).then(this.convertDataPointsToMs); - } - catch(err) { - return $q.reject(err); - } - }; - - this.convertDataPointsToMs = function(result) { - if (!result || !result.data) { return []; } - for (var i = 0; i < result.data.length; i++) { - var series = result.data[i]; - for (var y = 0; y < series.datapoints.length; y++) { - series.datapoints[y][1] *= 1000; - } - } - return result; - }; - - this.annotationQuery = function(options) { - // Graphite metric as annotation - if (options.annotation.target) { - var target = templateSrv.replace(options.annotation.target); - var graphiteQuery = { - rangeRaw: options.rangeRaw, - targets: [{ target: target }], - format: 'json', - maxDataPoints: 100 - }; - - return this.query(graphiteQuery) - .then(function(result) { - var list = []; - - for (var i = 0; i < result.data.length; i++) { - var target = result.data[i]; - - for (var y = 0; y < target.datapoints.length; y++) { - var datapoint = target.datapoints[y]; - if (!datapoint[0]) { continue; } - - list.push({ - annotation: options.annotation, - time: datapoint[1], - title: target.target - }); - } - } - - return list; - }); - } - // Graphite event as annotation - else { - var tags = templateSrv.replace(options.annotation.tags); - return this.events({range: options.rangeRaw, tags: tags}).then(function(results) { - var list = []; - for (var i = 0; i < results.data.length; i++) { - var e = results.data[i]; - - list.push({ - annotation: options.annotation, - time: e.when * 1000, - title: e.what, - tags: e.tags, - text: e.data - }); - } - return list; - }); - } - }; - - this.events = function(options) { - try { - var tags = ''; - if (options.tags) { - tags = '&tags=' + options.tags; - } - - return this.doGraphiteRequest({ - method: 'GET', - url: '/events/get_data?from=' + this.translateTime(options.range.from, false) + - '&until=' + this.translateTime(options.range.to, true) + tags, - }); - } - catch(err) { - return $q.reject(err); - } - }; - - this.translateTime = function(date, roundUp) { - if (_.isString(date)) { - if (date === 'now') { - return 'now'; - } - else if (date.indexOf('now-') >= 0 && date.indexOf('/') === -1) { - date = date.substring(3); - date = date.replace('m', 'min'); - date = date.replace('M', 'mon'); - return date; - } - date = dateMath.parse(date, roundUp); - } - - // graphite' s from filter is exclusive - // here we step back one minute in order - // to guarantee that we get all the data that - // exists for the specified range - if (roundUp) { - if (date.get('s')) { - date.add(1, 'm'); - } - } - else if (roundUp === false) { - if (date.get('s')) { - date.subtract(1, 'm'); - } - } - - return date.unix(); - }; - - this.metricFindQuery = function(query) { - var interpolated; - try { - interpolated = encodeURIComponent(templateSrv.replace(query)); - } - catch(err) { - return $q.reject(err); - } - - return this.doGraphiteRequest({method: 'GET', url: '/metrics/find/?query=' + interpolated }) - .then(function(results) { - return _.map(results.data, function(metric) { - return { - text: metric.text, - expandable: metric.expandable ? true : false - }; - }); - }); - }; - - this.testDatasource = function() { - return this.metricFindQuery('*').then(function () { - return { status: "success", message: "Data source is working", title: "Success" }; - }); - }; - - this.listDashboards = function(query) { - return this.doGraphiteRequest({ method: 'GET', url: '/dashboard/find/', params: {query: query || ''} }) - .then(function(results) { - return results.data.dashboards; - }); - }; - - this.loadDashboard = function(dashName) { - return this.doGraphiteRequest({method: 'GET', url: '/dashboard/load/' + encodeURIComponent(dashName) }); - }; - - this.doGraphiteRequest = function(options) { - if (this.basicAuth || this.withCredentials) { - options.withCredentials = true; - } - if (this.basicAuth) { - options.headers = options.headers || {}; - options.headers.Authorization = this.basicAuth; - } - - options.url = this.url + options.url; - options.inspect = { type: 'graphite' }; - - return backendSrv.datasourceRequest(options); - }; - - this._seriesRefLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - - this.buildGraphiteParams = function(options, scopedVars) { - var graphite_options = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout']; - var clean_options = [], targets = {}; - var target, targetValue, i; - var regex = /\#([A-Z])/g; - var intervalFormatFixRegex = /'(\d+)m'/gi; - var hasTargets = false; - - if (options.format !== 'png') { - options['format'] = 'json'; - } - - function fixIntervalFormat(match) { - return match.replace('m', 'min').replace('M', 'mon'); - } - - for (i = 0; i < options.targets.length; i++) { - target = options.targets[i]; - if (!target.target) { - continue; - } - - if (!target.refId) { - target.refId = this._seriesRefLetters[i]; - } - - targetValue = templateSrv.replace(target.target, scopedVars); - targetValue = targetValue.replace(intervalFormatFixRegex, fixIntervalFormat); - targets[target.refId] = targetValue; - } - - function nestedSeriesRegexReplacer(match, g1) { - return targets[g1]; - } - - for (i = 0; i < options.targets.length; i++) { - target = options.targets[i]; - if (!target.target) { - continue; - } - - targetValue = targets[target.refId]; - targetValue = targetValue.replace(regex, nestedSeriesRegexReplacer); - targets[target.refId] = targetValue; - - if (!target.hide) { - hasTargets = true; - clean_options.push("target=" + encodeURIComponent(targetValue)); - } - } - - _.each(options, function (value, key) { - if ($.inArray(key, graphite_options) === -1) { return; } - if (value) { - clean_options.push(key + "=" + encodeURIComponent(value)); - } - }); - - if (!hasTargets) { - return []; - } - - return clean_options; - }; - } - - return GraphiteDatasource; -}); diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts new file mode 100644 index 00000000000..c57e5fcfd26 --- /dev/null +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -0,0 +1,281 @@ +/// + +import angular from 'angular'; +import _ from 'lodash'; +import moment from 'moment'; + +import * as dateMath from 'app/core/utils/datemath'; + +/** @ngInject */ +export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv) { + this.basicAuth = instanceSettings.basicAuth; + this.url = instanceSettings.url; + this.name = instanceSettings.name; + this.cacheTimeout = instanceSettings.cacheTimeout; + this.withCredentials = instanceSettings.withCredentials; + this.render_method = instanceSettings.render_method || 'POST'; + + this.query = function(options) { + try { + var graphOptions = { + from: this.translateTime(options.rangeRaw.from, false), + until: this.translateTime(options.rangeRaw.to, true), + targets: options.targets, + format: options.format, + cacheTimeout: options.cacheTimeout || this.cacheTimeout, + maxDataPoints: options.maxDataPoints, + }; + + var params = this.buildGraphiteParams(graphOptions, options.scopedVars); + if (params.length === 0) { + return $q.when([]); + } + + if (options.format === 'png') { + return $q.when(this.url + '/render' + '?' + params.join('&')); + } + + var httpOptions: any = {method: this.render_method, url: '/render'}; + + if (httpOptions.method === 'GET') { + httpOptions.url = httpOptions.url + '?' + params.join('&'); + } else { + httpOptions.data = params.join('&'); + httpOptions.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; + } + + return this.doGraphiteRequest(httpOptions).then(this.convertDataPointsToMs); + } catch (err) { + return $q.reject(err); + } + }; + + this.convertDataPointsToMs = function(result) { + if (!result || !result.data) { return []; } + for (var i = 0; i < result.data.length; i++) { + var series = result.data[i]; + for (var y = 0; y < series.datapoints.length; y++) { + series.datapoints[y][1] *= 1000; + } + } + return result; + }; + + this.annotationQuery = function(options) { + // Graphite metric as annotation + if (options.annotation.target) { + var target = templateSrv.replace(options.annotation.target); + var graphiteQuery = { + rangeRaw: options.rangeRaw, + targets: [{ target: target }], + format: 'json', + maxDataPoints: 100 + }; + + return this.query(graphiteQuery) + .then(function(result) { + var list = []; + + for (var i = 0; i < result.data.length; i++) { + var target = result.data[i]; + + for (var y = 0; y < target.datapoints.length; y++) { + var datapoint = target.datapoints[y]; + if (!datapoint[0]) { continue; } + + list.push({ + annotation: options.annotation, + time: datapoint[1], + title: target.target + }); + } + } + + return list; + }); + } else { + // Graphite event as annotation + var tags = templateSrv.replace(options.annotation.tags); + return this.events({range: options.rangeRaw, tags: tags}).then(function(results) { + var list = []; + for (var i = 0; i < results.data.length; i++) { + var e = results.data[i]; + + list.push({ + annotation: options.annotation, + time: e.when * 1000, + title: e.what, + tags: e.tags, + text: e.data + }); + } + return list; + }); + } + }; + + this.events = function(options) { + try { + var tags = ''; + if (options.tags) { + tags = '&tags=' + options.tags; + } + + return this.doGraphiteRequest({ + method: 'GET', + url: '/events/get_data?from=' + this.translateTime(options.range.from, false) + + '&until=' + this.translateTime(options.range.to, true) + tags, + }); + } catch (err) { + return $q.reject(err); + } + }; + + this.translateTime = function(date, roundUp) { + if (_.isString(date)) { + if (date === 'now') { + return 'now'; + } else if (date.indexOf('now-') >= 0 && date.indexOf('/') === -1) { + date = date.substring(3); + date = date.replace('m', 'min'); + date = date.replace('M', 'mon'); + return date; + } + date = dateMath.parse(date, roundUp); + } + + // graphite' s from filter is exclusive + // here we step back one minute in order + // to guarantee that we get all the data that + // exists for the specified range + if (roundUp) { + if (date.get('s')) { + date.add(1, 'm'); + } + } else if (roundUp === false) { + if (date.get('s')) { + date.subtract(1, 'm'); + } + } + + return date.unix(); + }; + + this.metricFindQuery = function(query) { + var interpolated; + try { + interpolated = encodeURIComponent(templateSrv.replace(query)); + } catch (err) { + return $q.reject(err); + } + + return this.doGraphiteRequest({method: 'GET', url: '/metrics/find/?query=' + interpolated }) + .then(function(results) { + return _.map(results.data, function(metric) { + return { + text: metric.text, + expandable: metric.expandable ? true : false + }; + }); + }); + }; + + this.testDatasource = function() { + return this.metricFindQuery('*').then(function () { + return { status: "success", message: "Data source is working", title: "Success" }; + }); + }; + + this.listDashboards = function(query) { + return this.doGraphiteRequest({ method: 'GET', url: '/dashboard/find/', params: {query: query || ''} }) + .then(function(results) { + return results.data.dashboards; + }); + }; + + this.loadDashboard = function(dashName) { + return this.doGraphiteRequest({method: 'GET', url: '/dashboard/load/' + encodeURIComponent(dashName) }); + }; + + this.doGraphiteRequest = function(options) { + if (this.basicAuth || this.withCredentials) { + options.withCredentials = true; + } + if (this.basicAuth) { + options.headers = options.headers || {}; + options.headers.Authorization = this.basicAuth; + } + + options.url = this.url + options.url; + options.inspect = { type: 'graphite' }; + + return backendSrv.datasourceRequest(options); + }; + + this._seriesRefLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + this.buildGraphiteParams = function(options, scopedVars) { + var graphite_options = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout']; + var clean_options = [], targets = {}; + var target, targetValue, i; + var regex = /\#([A-Z])/g; + var intervalFormatFixRegex = /'(\d+)m'/gi; + var hasTargets = false; + + if (options.format !== 'png') { + options['format'] = 'json'; + } + + function fixIntervalFormat(match) { + return match.replace('m', 'min').replace('M', 'mon'); + } + + for (i = 0; i < options.targets.length; i++) { + target = options.targets[i]; + if (!target.target) { + continue; + } + + if (!target.refId) { + target.refId = this._seriesRefLetters[i]; + } + + targetValue = templateSrv.replace(target.target, scopedVars); + targetValue = targetValue.replace(intervalFormatFixRegex, fixIntervalFormat); + targets[target.refId] = targetValue; + } + + function nestedSeriesRegexReplacer(match, g1) { + return targets[g1]; + } + + for (i = 0; i < options.targets.length; i++) { + target = options.targets[i]; + if (!target.target) { + continue; + } + + targetValue = targets[target.refId]; + targetValue = targetValue.replace(regex, nestedSeriesRegexReplacer); + targets[target.refId] = targetValue; + + if (!target.hide) { + hasTargets = true; + clean_options.push("target=" + encodeURIComponent(targetValue)); + } + } + + _.each(options, function (value, key) { + if (_.indexOf(graphite_options, key) === -1) { return; } + if (value) { + clean_options.push(key + "=" + encodeURIComponent(value)); + } + }); + + if (!hasTargets) { + return []; + } + + return clean_options; + }; +} diff --git a/public/app/plugins/datasource/graphite/lexer.js b/public/app/plugins/datasource/graphite/lexer.js deleted file mode 100644 index 2b8affd04e2..00000000000 --- a/public/app/plugins/datasource/graphite/lexer.js +++ /dev/null @@ -1,682 +0,0 @@ -define([ - 'lodash' -], function(_) { - 'use strict'; - - // This is auto generated from the unicode tables. - // The tables are at: - // http://www.fileformat.info/info/unicode/category/Lu/list.htm - // http://www.fileformat.info/info/unicode/category/Ll/list.htm - // http://www.fileformat.info/info/unicode/category/Lt/list.htm - // http://www.fileformat.info/info/unicode/category/Lm/list.htm - // http://www.fileformat.info/info/unicode/category/Lo/list.htm - // http://www.fileformat.info/info/unicode/category/Nl/list.htm - - var unicodeLetterTable = [ - 170, 170, 181, 181, 186, 186, 192, 214, - 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, - 880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, - 910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366, - 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610, - 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, - 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, - 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, - 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2308, 2361, - 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431, - 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, - 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, - 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, - 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, - 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, - 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, - 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, - 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, - 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, - 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, - 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, - 3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212, - 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, - 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, - 3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455, - 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, - 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, - 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, - 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, - 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805, - 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, - 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, - 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4304, 4346, - 4348, 4348, 4352, 4680, 4682, 4685, 4688, 4694, 4696, 4696, - 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, - 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, - 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740, - 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, - 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, - 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312, - 6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516, - 6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823, - 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7104, 7141, - 7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409, - 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, - 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, - 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, - 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, - 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, - 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, - 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, - 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, - 11360, 11492, 11499, 11502, 11520, 11557, 11568, 11621, - 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, - 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, - 11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295, - 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, - 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, - 12593, 12686, 12704, 12730, 12784, 12799, 13312, 13312, - 19893, 19893, 19968, 19968, 40907, 40907, 40960, 42124, - 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, - 42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783, - 42786, 42888, 42891, 42894, 42896, 42897, 42912, 42921, - 43002, 43009, 43011, 43013, 43015, 43018, 43020, 43042, - 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, - 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, - 43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595, - 43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697, - 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, - 43739, 43741, 43777, 43782, 43785, 43790, 43793, 43798, - 43808, 43814, 43816, 43822, 43968, 44002, 44032, 44032, - 55203, 55203, 55216, 55238, 55243, 55291, 63744, 64045, - 64048, 64109, 64112, 64217, 64256, 64262, 64275, 64279, - 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, - 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, - 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, - 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, - 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, - 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, - 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, - 65856, 65908, 66176, 66204, 66208, 66256, 66304, 66334, - 66352, 66378, 66432, 66461, 66464, 66499, 66504, 66511, - 66513, 66517, 66560, 66717, 67584, 67589, 67592, 67592, - 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, - 67840, 67861, 67872, 67897, 68096, 68096, 68112, 68115, - 68117, 68119, 68121, 68147, 68192, 68220, 68352, 68405, - 68416, 68437, 68448, 68466, 68608, 68680, 69635, 69687, - 69763, 69807, 73728, 74606, 74752, 74850, 77824, 78894, - 92160, 92728, 110592, 110593, 119808, 119892, 119894, 119964, - 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, - 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, - 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, - 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, - 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, - 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, - 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, - 131072, 131072, 173782, 173782, 173824, 173824, 177972, 177972, - 177984, 177984, 178205, 178205, 194560, 195101 - ]; - - var identifierStartTable = []; - - for (var i = 0; i < 128; i++) { - identifierStartTable[i] = - i >= 48 && i <= 57 || // 0-9 - i === 36 || // $ - i === 126 || // ~ - i === 124 || // | - i >= 65 && i <= 90 || // A-Z - i === 95 || // _ - i === 45 || // - - i === 42 || // * - i === 58 || // : - i === 91 || // templateStart [ - i === 93 || // templateEnd ] - i === 63 || // ? - i === 37 || // % - i === 35 || // # - i === 61 || // = - i >= 97 && i <= 122; // a-z - } - - var identifierPartTable = []; - - for (var i2 = 0; i2 < 128; i2++) { - identifierPartTable[i2] = - identifierStartTable[i2] || // $, _, A-Z, a-z - i2 >= 48 && i2 <= 57; // 0-9 - } - - function Lexer(expression) { - this.input = expression; - this.char = 1; - this.from = 1; - } - - Lexer.prototype = { - - peek: function (i) { - return this.input.charAt(i || 0); - }, - - skip: function (i) { - i = i || 1; - this.char += i; - this.input = this.input.slice(i); - }, - - tokenize: function() { - var list = []; - var token; - while (token = this.next()) { - list.push(token); - } - return list; - }, - - next: function() { - this.from = this.char; - - // Move to the next non-space character. - var start; - if (/\s/.test(this.peek())) { - start = this.char; - - while (/\s/.test(this.peek())) { - this.from += 1; - this.skip(); - } - - if (this.peek() === "") { // EOL - return null; - } - } - - var match = this.scanStringLiteral(); - if (match) { - return match; - } - - match = - this.scanPunctuator() || - this.scanNumericLiteral() || - this.scanIdentifier() || - this.scanTemplateSequence(); - - if (match) { - this.skip(match.value.length); - return match; - } - - // No token could be matched, give up. - return null; - }, - - scanTemplateSequence: function() { - if (this.peek() === '[' && this.peek(1) === '[') { - return { - type: 'templateStart', - value: '[[', - pos: this.char - }; - } - - if (this.peek() === ']' && this.peek(1) === ']') { - return { - type: 'templateEnd', - value: '[[', - pos: this.char - }; - } - - return null; - }, - - /* - * Extract a JavaScript identifier out of the next sequence of - * characters or return 'null' if its not possible. In addition, - * to Identifier this method can also produce BooleanLiteral - * (true/false) and NullLiteral (null). - */ - scanIdentifier: function() { - var id = ""; - var index = 0; - var type, char; - - // Detects any character in the Unicode categories "Uppercase - // letter (Lu)", "Lowercase letter (Ll)", "Titlecase letter - // (Lt)", "Modifier letter (Lm)", "Other letter (Lo)", or - // "Letter number (Nl)". - // - // Both approach and unicodeLetterTable were borrowed from - // Google's Traceur. - - function isUnicodeLetter(code) { - for (var i = 0; i < unicodeLetterTable.length;) { - if (code < unicodeLetterTable[i++]) { - return false; - } - - if (code <= unicodeLetterTable[i++]) { - return true; - } - } - - return false; - } - - function isHexDigit(str) { - return (/^[0-9a-fA-F]$/).test(str); - } - - var readUnicodeEscapeSequence = _.bind(function () { - /*jshint validthis:true */ - index += 1; - - if (this.peek(index) !== "u") { - return null; - } - - var ch1 = this.peek(index + 1); - var ch2 = this.peek(index + 2); - var ch3 = this.peek(index + 3); - var ch4 = this.peek(index + 4); - var code; - - if (isHexDigit(ch1) && isHexDigit(ch2) && isHexDigit(ch3) && isHexDigit(ch4)) { - code = parseInt(ch1 + ch2 + ch3 + ch4, 16); - - if (isUnicodeLetter(code)) { - index += 5; - return "\\u" + ch1 + ch2 + ch3 + ch4; - } - - return null; - } - - return null; - }, this); - - var getIdentifierStart = _.bind(function () { - /*jshint validthis:true */ - var chr = this.peek(index); - var code = chr.charCodeAt(0); - - if (chr === '*') { - index += 1; - return chr; - } - - if (code === 92) { - return readUnicodeEscapeSequence(); - } - - if (code < 128) { - if (identifierStartTable[code]) { - index += 1; - return chr; - } - - return null; - } - - if (isUnicodeLetter(code)) { - index += 1; - return chr; - } - - return null; - }, this); - - var getIdentifierPart = _.bind(function () { - /*jshint validthis:true */ - var chr = this.peek(index); - var code = chr.charCodeAt(0); - - if (code === 92) { - return readUnicodeEscapeSequence(); - } - - if (code < 128) { - if (identifierPartTable[code]) { - index += 1; - return chr; - } - - return null; - } - - if (isUnicodeLetter(code)) { - index += 1; - return chr; - } - - return null; - }, this); - - char = getIdentifierStart(); - if (char === null) { - return null; - } - - id = char; - for (;;) { - char = getIdentifierPart(); - - if (char === null) { - break; - } - - id += char; - } - - switch (id) { - case 'true': { - type = 'bool'; - break; - } - case 'false': { - type = 'bool'; - break; - } - default: - type = "identifier"; - } - - return { - type: type, - value: id, - pos: this.char - }; - - }, - - /* - * Extract a numeric literal out of the next sequence of - * characters or return 'null' if its not possible. This method - * supports all numeric literals described in section 7.8.3 - * of the EcmaScript 5 specification. - * - * This method's implementation was heavily influenced by the - * scanNumericLiteral function in the Esprima parser's source code. - */ - scanNumericLiteral: function () { - var index = 0; - var value = ""; - var length = this.input.length; - var char = this.peek(index); - var bad; - - function isDecimalDigit(str) { - return (/^[0-9]$/).test(str); - } - - function isOctalDigit(str) { - return (/^[0-7]$/).test(str); - } - - function isHexDigit(str) { - return (/^[0-9a-fA-F]$/).test(str); - } - - function isIdentifierStart(ch) { - return (ch === "$") || (ch === "_") || (ch === "\\") || - (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z"); - } - - // handle negative num literals - if (char === '-') { - value += char; - index += 1; - char = this.peek(index); - } - - // Numbers must start either with a decimal digit or a point. - if (char !== "." && !isDecimalDigit(char)) { - return null; - } - - if (char !== ".") { - value += this.peek(index); - index += 1; - char = this.peek(index); - - if (value === "0") { - // Base-16 numbers. - if (char === "x" || char === "X") { - index += 1; - value += char; - - while (index < length) { - char = this.peek(index); - if (!isHexDigit(char)) { - break; - } - value += char; - index += 1; - } - - if (value.length <= 2) { // 0x - return { - type: 'number', - value: value, - isMalformed: true, - pos: this.char - }; - } - - if (index < length) { - char = this.peek(index); - if (isIdentifierStart(char)) { - return null; - } - } - - return { - type: 'number', - value: value, - base: 16, - isMalformed: false, - pos: this.char - }; - } - - // Base-8 numbers. - if (isOctalDigit(char)) { - index += 1; - value += char; - bad = false; - - while (index < length) { - char = this.peek(index); - - // Numbers like '019' (note the 9) are not valid octals - // but we still parse them and mark as malformed. - - if (isDecimalDigit(char)) { - bad = true; - } else if (!isOctalDigit(char)) { - break; - } - value += char; - index += 1; - } - - if (index < length) { - char = this.peek(index); - if (isIdentifierStart(char)) { - return null; - } - } - - return { - type: 'number', - value: value, - base: 8, - isMalformed: false - }; - } - - // Decimal numbers that start with '0' such as '09' are illegal - // but we still parse them and return as malformed. - - if (isDecimalDigit(char)) { - index += 1; - value += char; - } - } - - while (index < length) { - char = this.peek(index); - if (!isDecimalDigit(char)) { - break; - } - value += char; - index += 1; - } - } - - // Decimal digits. - - if (char === ".") { - value += char; - index += 1; - - while (index < length) { - char = this.peek(index); - if (!isDecimalDigit(char)) { - break; - } - value += char; - index += 1; - } - } - - // Exponent part. - - if (char === "e" || char === "E") { - value += char; - index += 1; - char = this.peek(index); - - if (char === "+" || char === "-") { - value += this.peek(index); - index += 1; - } - - char = this.peek(index); - if (isDecimalDigit(char)) { - value += char; - index += 1; - - while (index < length) { - char = this.peek(index); - if (!isDecimalDigit(char)) { - break; - } - value += char; - index += 1; - } - } else { - return null; - } - } - - if (index < length) { - char = this.peek(index); - if (!this.isPunctuator(char)) { - return null; - } - } - - return { - type: 'number', - value: value, - base: 10, - pos: this.char, - isMalformed: !isFinite(value) - }; - }, - - isPunctuator: function (ch1) { - switch (ch1) { - case ".": - case "(": - case ")": - case ",": - case "{": - case "}": - return true; - } - - return false; - }, - - scanPunctuator: function () { - var ch1 = this.peek(); - - if (this.isPunctuator(ch1)) { - return { - type: ch1, - value: ch1, - pos: this.char - }; - } - - return null; - }, - - /* - * Extract a string out of the next sequence of characters and/or - * lines or return 'null' if its not possible. Since strings can - * span across multiple lines this method has to move the char - * pointer. - * - * This method recognizes pseudo-multiline JavaScript strings: - * - * var str = "hello\ - * world"; - */ - scanStringLiteral: function () { - /*jshint loopfunc:true */ - var quote = this.peek(); - - // String must start with a quote. - if (quote !== "\"" && quote !== "'") { - return null; - } - - var value = ""; - - this.skip(); - - while (this.peek() !== quote) { - if (this.peek() === "") { // End Of Line - return { - type: 'string', - value: value, - isUnclosed: true, - quote: quote, - pos: this.char - }; - } - - var char = this.peek(); - var jump = 1; // A length of a jump, after we're done - // parsing this character. - - value += char; - this.skip(jump); - } - - this.skip(); - return { - type: 'string', - value: value, - isUnclosed: false, - quote: quote, - pos: this.char - }; - }, - - }; - - return Lexer; - -}); diff --git a/public/app/plugins/datasource/graphite/lexer.ts b/public/app/plugins/datasource/graphite/lexer.ts new file mode 100644 index 00000000000..1835921d40b --- /dev/null +++ b/public/app/plugins/datasource/graphite/lexer.ts @@ -0,0 +1,678 @@ +/// + +import _ from 'lodash'; + +// This is auto generated from the unicode tables. +// The tables are at: +// http://www.fileformat.info/info/unicode/category/Lu/list.htm +// http://www.fileformat.info/info/unicode/category/Ll/list.htm +// http://www.fileformat.info/info/unicode/category/Lt/list.htm +// http://www.fileformat.info/info/unicode/category/Lm/list.htm +// http://www.fileformat.info/info/unicode/category/Lo/list.htm +// http://www.fileformat.info/info/unicode/category/Nl/list.htm + +var unicodeLetterTable = [ + 170, 170, 181, 181, 186, 186, 192, 214, + 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, + 880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, + 910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366, + 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610, + 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, + 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, + 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, + 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2308, 2361, + 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431, + 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, + 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, + 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, + 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, + 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, + 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, + 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, + 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, + 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, + 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, + 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, + 3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212, + 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, + 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, + 3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455, + 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, + 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, + 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, + 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, + 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805, + 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, + 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, + 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4304, 4346, + 4348, 4348, 4352, 4680, 4682, 4685, 4688, 4694, 4696, 4696, + 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, + 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, + 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740, + 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, + 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, + 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312, + 6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516, + 6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823, + 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7104, 7141, + 7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409, + 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, + 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, + 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, + 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, + 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, + 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, + 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, + 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, + 11360, 11492, 11499, 11502, 11520, 11557, 11568, 11621, + 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, + 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, + 11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295, + 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, + 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, + 12593, 12686, 12704, 12730, 12784, 12799, 13312, 13312, + 19893, 19893, 19968, 19968, 40907, 40907, 40960, 42124, + 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, + 42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783, + 42786, 42888, 42891, 42894, 42896, 42897, 42912, 42921, + 43002, 43009, 43011, 43013, 43015, 43018, 43020, 43042, + 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, + 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, + 43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595, + 43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697, + 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, + 43739, 43741, 43777, 43782, 43785, 43790, 43793, 43798, + 43808, 43814, 43816, 43822, 43968, 44002, 44032, 44032, + 55203, 55203, 55216, 55238, 55243, 55291, 63744, 64045, + 64048, 64109, 64112, 64217, 64256, 64262, 64275, 64279, + 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, + 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, + 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, + 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, + 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, + 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, + 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, + 65856, 65908, 66176, 66204, 66208, 66256, 66304, 66334, + 66352, 66378, 66432, 66461, 66464, 66499, 66504, 66511, + 66513, 66517, 66560, 66717, 67584, 67589, 67592, 67592, + 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, + 67840, 67861, 67872, 67897, 68096, 68096, 68112, 68115, + 68117, 68119, 68121, 68147, 68192, 68220, 68352, 68405, + 68416, 68437, 68448, 68466, 68608, 68680, 69635, 69687, + 69763, 69807, 73728, 74606, 74752, 74850, 77824, 78894, + 92160, 92728, 110592, 110593, 119808, 119892, 119894, 119964, + 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, + 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, + 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, + 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, + 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, + 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, + 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, + 131072, 131072, 173782, 173782, 173824, 173824, 177972, 177972, + 177984, 177984, 178205, 178205, 194560, 195101 +]; + +var identifierStartTable = []; + +for (var i = 0; i < 128; i++) { + identifierStartTable[i] = + i >= 48 && i <= 57 || // 0-9 + i === 36 || // $ + i === 126 || // ~ + i === 124 || // | + i >= 65 && i <= 90 || // A-Z + i === 95 || // _ + i === 45 || // - + i === 42 || // * + i === 58 || // : + i === 91 || // templateStart [ + i === 93 || // templateEnd ] + i === 63 || // ? + i === 37 || // % + i === 35 || // # + i === 61 || // = + i >= 97 && i <= 122; // a-z +} + +var identifierPartTable = []; + +for (var i2 = 0; i2 < 128; i2++) { + identifierPartTable[i2] = + identifierStartTable[i2] || // $, _, A-Z, a-z + i2 >= 48 && i2 <= 57; // 0-9 +} + +export function Lexer(expression) { + this.input = expression; + this.char = 1; + this.from = 1; +} + +Lexer.prototype = { + + peek: function (i) { + return this.input.charAt(i || 0); + }, + + skip: function (i) { + i = i || 1; + this.char += i; + this.input = this.input.slice(i); + }, + + tokenize: function() { + var list = []; + var token; + while (token = this.next()) { + list.push(token); + } + return list; + }, + + next: function() { + this.from = this.char; + + // Move to the next non-space character. + var start; + if (/\s/.test(this.peek())) { + start = this.char; + + while (/\s/.test(this.peek())) { + this.from += 1; + this.skip(); + } + + if (this.peek() === "") { // EOL + return null; + } + } + + var match = this.scanStringLiteral(); + if (match) { + return match; + } + + match = + this.scanPunctuator() || + this.scanNumericLiteral() || + this.scanIdentifier() || + this.scanTemplateSequence(); + + if (match) { + this.skip(match.value.length); + return match; + } + + // No token could be matched, give up. + return null; + }, + + scanTemplateSequence: function() { + if (this.peek() === '[' && this.peek(1) === '[') { + return { + type: 'templateStart', + value: '[[', + pos: this.char + }; + } + + if (this.peek() === ']' && this.peek(1) === ']') { + return { + type: 'templateEnd', + value: '[[', + pos: this.char + }; + } + + return null; + }, + + /* + * Extract a JavaScript identifier out of the next sequence of + * characters or return 'null' if its not possible. In addition, + * to Identifier this method can also produce BooleanLiteral + * (true/false) and NullLiteral (null). + */ + scanIdentifier: function() { + var id = ""; + var index = 0; + var type, char; + + // Detects any character in the Unicode categories "Uppercase + // letter (Lu)", "Lowercase letter (Ll)", "Titlecase letter + // (Lt)", "Modifier letter (Lm)", "Other letter (Lo)", or + // "Letter number (Nl)". + // + // Both approach and unicodeLetterTable were borrowed from + // Google's Traceur. + + function isUnicodeLetter(code) { + for (var i = 0; i < unicodeLetterTable.length;) { + if (code < unicodeLetterTable[i++]) { + return false; + } + + if (code <= unicodeLetterTable[i++]) { + return true; + } + } + + return false; + } + + function isHexDigit(str) { + return (/^[0-9a-fA-F]$/).test(str); + } + + var readUnicodeEscapeSequence = _.bind(function () { + /*jshint validthis:true */ + index += 1; + + if (this.peek(index) !== "u") { + return null; + } + + var ch1 = this.peek(index + 1); + var ch2 = this.peek(index + 2); + var ch3 = this.peek(index + 3); + var ch4 = this.peek(index + 4); + var code; + + if (isHexDigit(ch1) && isHexDigit(ch2) && isHexDigit(ch3) && isHexDigit(ch4)) { + code = parseInt(ch1 + ch2 + ch3 + ch4, 16); + + if (isUnicodeLetter(code)) { + index += 5; + return "\\u" + ch1 + ch2 + ch3 + ch4; + } + + return null; + } + + return null; + }, this); + + var getIdentifierStart = _.bind(function () { + /*jshint validthis:true */ + var chr = this.peek(index); + var code = chr.charCodeAt(0); + + if (chr === '*') { + index += 1; + return chr; + } + + if (code === 92) { + return readUnicodeEscapeSequence(); + } + + if (code < 128) { + if (identifierStartTable[code]) { + index += 1; + return chr; + } + + return null; + } + + if (isUnicodeLetter(code)) { + index += 1; + return chr; + } + + return null; + }, this); + + var getIdentifierPart = _.bind(function () { + /*jshint validthis:true */ + var chr = this.peek(index); + var code = chr.charCodeAt(0); + + if (code === 92) { + return readUnicodeEscapeSequence(); + } + + if (code < 128) { + if (identifierPartTable[code]) { + index += 1; + return chr; + } + + return null; + } + + if (isUnicodeLetter(code)) { + index += 1; + return chr; + } + + return null; + }, this); + + char = getIdentifierStart(); + if (char === null) { + return null; + } + + id = char; + for (;;) { + char = getIdentifierPart(); + + if (char === null) { + break; + } + + id += char; + } + + switch (id) { + case 'true': { + type = 'bool'; + break; + } + case 'false': { + type = 'bool'; + break; + } + default: + type = "identifier"; + } + + return { + type: type, + value: id, + pos: this.char + }; + + }, + + /* + * Extract a numeric literal out of the next sequence of + * characters or return 'null' if its not possible. This method + * supports all numeric literals described in section 7.8.3 + * of the EcmaScript 5 specification. + * + * This method's implementation was heavily influenced by the + * scanNumericLiteral function in the Esprima parser's source code. + */ + scanNumericLiteral: function (): any { + var index = 0; + var value = ""; + var length = this.input.length; + var char = this.peek(index); + var bad; + + function isDecimalDigit(str) { + return (/^[0-9]$/).test(str); + } + + function isOctalDigit(str) { + return (/^[0-7]$/).test(str); + } + + function isHexDigit(str) { + return (/^[0-9a-fA-F]$/).test(str); + } + + function isIdentifierStart(ch) { + return (ch === "$") || (ch === "_") || (ch === "\\") || + (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z"); + } + + // handle negative num literals + if (char === '-') { + value += char; + index += 1; + char = this.peek(index); + } + + // Numbers must start either with a decimal digit or a point. + if (char !== "." && !isDecimalDigit(char)) { + return null; + } + + if (char !== ".") { + value += this.peek(index); + index += 1; + char = this.peek(index); + + if (value === "0") { + // Base-16 numbers. + if (char === "x" || char === "X") { + index += 1; + value += char; + + while (index < length) { + char = this.peek(index); + if (!isHexDigit(char)) { + break; + } + value += char; + index += 1; + } + + if (value.length <= 2) { // 0x + return { + type: 'number', + value: value, + isMalformed: true, + pos: this.char + }; + } + + if (index < length) { + char = this.peek(index); + if (isIdentifierStart(char)) { + return null; + } + } + + return { + type: 'number', + value: value, + base: 16, + isMalformed: false, + pos: this.char + }; + } + + // Base-8 numbers. + if (isOctalDigit(char)) { + index += 1; + value += char; + bad = false; + + while (index < length) { + char = this.peek(index); + + // Numbers like '019' (note the 9) are not valid octals + // but we still parse them and mark as malformed. + + if (isDecimalDigit(char)) { + bad = true; + } else if (!isOctalDigit(char)) { + break; + } + value += char; + index += 1; + } + + if (index < length) { + char = this.peek(index); + if (isIdentifierStart(char)) { + return null; + } + } + + return { + type: 'number', + value: value, + base: 8, + isMalformed: false + }; + } + + // Decimal numbers that start with '0' such as '09' are illegal + // but we still parse them and return as malformed. + + if (isDecimalDigit(char)) { + index += 1; + value += char; + } + } + + while (index < length) { + char = this.peek(index); + if (!isDecimalDigit(char)) { + break; + } + value += char; + index += 1; + } + } + + // Decimal digits. + + if (char === ".") { + value += char; + index += 1; + + while (index < length) { + char = this.peek(index); + if (!isDecimalDigit(char)) { + break; + } + value += char; + index += 1; + } + } + + // Exponent part. + + if (char === "e" || char === "E") { + value += char; + index += 1; + char = this.peek(index); + + if (char === "+" || char === "-") { + value += this.peek(index); + index += 1; + } + + char = this.peek(index); + if (isDecimalDigit(char)) { + value += char; + index += 1; + + while (index < length) { + char = this.peek(index); + if (!isDecimalDigit(char)) { + break; + } + value += char; + index += 1; + } + } else { + return null; + } + } + + if (index < length) { + char = this.peek(index); + if (!this.isPunctuator(char)) { + return null; + } + } + + return { + type: 'number', + value: value, + base: 10, + pos: this.char, + isMalformed: !isFinite(+value) + }; + }, + + isPunctuator: function (ch1) { + switch (ch1) { + case ".": + case "(": + case ")": + case ",": + case "{": + case "}": + return true; + } + + return false; + }, + + scanPunctuator: function () { + var ch1 = this.peek(); + + if (this.isPunctuator(ch1)) { + return { + type: ch1, + value: ch1, + pos: this.char + }; + } + + return null; + }, + + /* + * Extract a string out of the next sequence of characters and/or + * lines or return 'null' if its not possible. Since strings can + * span across multiple lines this method has to move the char + * pointer. + * + * This method recognizes pseudo-multiline JavaScript strings: + * + * var str = "hello\ + * world"; + */ + scanStringLiteral: function () { + /*jshint loopfunc:true */ + var quote = this.peek(); + + // String must start with a quote. + if (quote !== "\"" && quote !== "'") { + return null; + } + + var value = ""; + + this.skip(); + + while (this.peek() !== quote) { + if (this.peek() === "") { // End Of Line + return { + type: 'string', + value: value, + isUnclosed: true, + quote: quote, + pos: this.char + }; + } + + var char = this.peek(); + var jump = 1; // A length of a jump, after we're done + // parsing this character. + + value += char; + this.skip(jump); + } + + this.skip(); + return { + type: 'string', + value: value, + isUnclosed: false, + quote: quote, + pos: this.char + }; + }, + + }; + diff --git a/public/app/plugins/datasource/graphite/module.js b/public/app/plugins/datasource/graphite/module.js deleted file mode 100644 index a1d4ed5fc2b..00000000000 --- a/public/app/plugins/datasource/graphite/module.js +++ /dev/null @@ -1,38 +0,0 @@ -define([ - './datasource', -], -function (GraphiteDatasource) { - 'use strict'; - - function metricsQueryEditor() { - return { - controller: 'GraphiteQueryCtrl', - templateUrl: 'public/app/plugins/datasource/graphite/partials/query.editor.html' - }; - } - - function metricsQueryOptions() { - return {templateUrl: 'public/app/plugins/datasource/graphite/partials/query.options.html'}; - } - - function annotationsQueryEditor() { - return {templateUrl: 'public/app/plugins/datasource/graphite/partials/annotations.editor.html'}; - } - - function configView() { - return {templateUrl: 'public/app/plugins/datasource/graphite/partials/config.html'}; - } - - function ConfigView() { - } - ConfigView.templateUrl = 'public/app/plugins/datasource/graphite/partials/config.html'; - - return { - Datasource: GraphiteDatasource, - configView: configView, - annotationsQueryEditor: annotationsQueryEditor, - metricsQueryEditor: metricsQueryEditor, - metricsQueryOptions: metricsQueryOptions, - ConfigView: ConfigView - }; -}); diff --git a/public/app/plugins/datasource/graphite/module.ts b/public/app/plugins/datasource/graphite/module.ts new file mode 100644 index 00000000000..abab560aafd --- /dev/null +++ b/public/app/plugins/datasource/graphite/module.ts @@ -0,0 +1,51 @@ +import {GraphiteDatasource} from './datasource'; +import {GraphiteQueryCtrl} from './query_ctrl'; + +class GraphiteConfigView { + static templateUrl = 'public/app/plugins/datasource/graphite/partials/config.html'; +} + +export { + GraphiteDatasource as Datasource, + GraphiteQueryCtrl as QueryCtrl, + GraphiteConfigView as ConfigView +}; + +// define([ +// './datasource', +// ], +// function (GraphiteDatasource) { +// 'use strict'; +// +// function metricsQueryEditor() { +// return { +// controller: 'GraphiteQueryCtrl', +// templateUrl: 'public/app/plugins/datasource/graphite/partials/query.editor.html' +// }; +// } +// +// function metricsQueryOptions() { +// return {templateUrl: 'public/app/plugins/datasource/graphite/partials/query.options.html'}; +// } +// +// function annotationsQueryEditor() { +// return {templateUrl: 'public/app/plugins/datasource/graphite/partials/annotations.editor.html'}; +// } +// +// function configView() { +// return {templateUrl: 'public/app/plugins/datasource/graphite/partials/config.html'}; +// } +// +// function ConfigView() { +// } +// ConfigView.templateUrl = 'public/app/plugins/datasource/graphite/partials/config.html'; +// +// return { +// Datasource: GraphiteDatasource, +// configView: configView, +// annotationsQueryEditor: annotationsQueryEditor, +// metricsQueryEditor: metricsQueryEditor, +// metricsQueryOptions: metricsQueryOptions, +// ConfigView: ConfigView +// }; +// }); diff --git a/public/app/plugins/datasource/graphite/parser.js b/public/app/plugins/datasource/graphite/parser.js deleted file mode 100644 index 43fd148f1ea..00000000000 --- a/public/app/plugins/datasource/graphite/parser.js +++ /dev/null @@ -1,265 +0,0 @@ -define([ - './lexer' -], function (Lexer) { - 'use strict'; - - function Parser(expression) { - this.expression = expression; - this.lexer = new Lexer(expression); - this.tokens = this.lexer.tokenize(); - this.index = 0; - } - - Parser.prototype = { - - getAst: function () { - return this.start(); - }, - - start: function () { - try { - return this.functionCall() || this.metricExpression(); - } - catch (e) { - return { - type: 'error', - message: e.message, - pos: e.pos - }; - } - }, - - curlyBraceSegment: function() { - if (this.match('identifier', '{') || this.match('{')) { - - var curlySegment = ""; - - while (!this.match('') && !this.match('}')) { - curlySegment += this.consumeToken().value; - } - - if (!this.match('}')) { - this.errorMark("Expected closing '}'"); - } - - curlySegment += this.consumeToken().value; - - // if curly segment is directly followed by identifier - // include it in the segment - if (this.match('identifier')) { - curlySegment += this.consumeToken().value; - } - - return { - type: 'segment', - value: curlySegment - }; - } - else { - return null; - } - }, - - metricSegment: function() { - var curly = this.curlyBraceSegment(); - if (curly) { - return curly; - } - - if (this.match('identifier') || this.match('number')) { - // hack to handle float numbers in metric segments - var parts = this.consumeToken().value.split('.'); - if (parts.length === 2) { - this.tokens.splice(this.index, 0, { type: '.' }); - this.tokens.splice(this.index + 1, 0, { type: 'number', value: parts[1] }); - } - - return { - type: 'segment', - value: parts[0] - }; - } - - if (!this.match('templateStart')) { - this.errorMark('Expected metric identifier'); - } - - this.consumeToken(); - - if (!this.match('identifier')) { - this.errorMark('Expected identifier after templateStart'); - } - - var node = { - type: 'template', - value: this.consumeToken().value - }; - - if (!this.match('templateEnd')) { - this.errorMark('Expected templateEnd'); - } - - this.consumeToken(); - return node; - }, - - metricExpression: function() { - if (!this.match('templateStart') && - !this.match('identifier') && - !this.match('number') && - !this.match('{')) { - return null; - } - - var node = { - type: 'metric', - segments: [] - }; - - node.segments.push(this.metricSegment()); - - while (this.match('.')) { - this.consumeToken(); - - var segment = this.metricSegment(); - if (!segment) { - this.errorMark('Expected metric identifier'); - } - - node.segments.push(segment); - } - - return node; - }, - - functionCall: function() { - if (!this.match('identifier', '(')) { - return null; - } - - var node = { - type: 'function', - name: this.consumeToken().value, - }; - - // consume left parenthesis - this.consumeToken(); - - node.params = this.functionParameters(); - - if (!this.match(')')) { - this.errorMark('Expected closing parenthesis'); - } - - this.consumeToken(); - - return node; - }, - - boolExpression: function() { - if (!this.match('bool')) { - return null; - } - - return { - type: 'bool', - value: this.consumeToken().value === 'true', - }; - }, - - functionParameters: function () { - if (this.match(')') || this.match('')) { - return []; - } - - var param = - this.functionCall() || - this.numericLiteral() || - this.seriesRefExpression() || - this.boolExpression() || - this.metricExpression() || - this.stringLiteral(); - - if (!this.match(',')) { - return [param]; - } - - this.consumeToken(); - return [param].concat(this.functionParameters()); - }, - - seriesRefExpression: function() { - if (!this.match('identifier')) { - return null; - } - - var value = this.tokens[this.index].value; - if (!value.match(/\#[A-Z]/)) { - return null; - } - - var token = this.consumeToken(); - - return { - type: 'series-ref', - value: token.value - }; - }, - - numericLiteral: function () { - if (!this.match('number')) { - return null; - } - - return { - type: 'number', - value: parseFloat(this.consumeToken().value) - }; - }, - - stringLiteral: function () { - if (!this.match('string')) { - return null; - } - - var token = this.consumeToken(); - if (token.isUnclosed) { - throw { message: 'Unclosed string parameter', pos: token.pos }; - } - - return { - type: 'string', - value: token.value - }; - }, - - errorMark: function(text) { - var currentToken = this.tokens[this.index]; - var type = currentToken ? currentToken.type : 'end of string'; - throw { - message: text + " instead found " + type, - pos: currentToken ? currentToken.pos : this.lexer.char - }; - }, - - // returns token value and incre - consumeToken: function() { - this.index++; - return this.tokens[this.index - 1]; - }, - - matchToken: function(type, index) { - var token = this.tokens[this.index + index]; - return (token === undefined && type === '') || - token && token.type === type; - }, - - match: function(token1, token2) { - return this.matchToken(token1, 0) && - (!token2 || this.matchToken(token2, 1)); - }, - - }; - - return Parser; -}); diff --git a/public/app/plugins/datasource/graphite/parser.ts b/public/app/plugins/datasource/graphite/parser.ts new file mode 100644 index 00000000000..bfafdda7815 --- /dev/null +++ b/public/app/plugins/datasource/graphite/parser.ts @@ -0,0 +1,258 @@ + +import {Lexer} from './lexer'; + +export function Parser(expression) { + this.expression = expression; + this.lexer = new Lexer(expression); + this.tokens = this.lexer.tokenize(); + this.index = 0; +} + +Parser.prototype = { + + getAst: function () { + return this.start(); + }, + + start: function () { + try { + return this.functionCall() || this.metricExpression(); + } catch (e) { + return { + type: 'error', + message: e.message, + pos: e.pos + }; + } + }, + + curlyBraceSegment: function() { + if (this.match('identifier', '{') || this.match('{')) { + + var curlySegment = ""; + + while (!this.match('') && !this.match('}')) { + curlySegment += this.consumeToken().value; + } + + if (!this.match('}')) { + this.errorMark("Expected closing '}'"); + } + + curlySegment += this.consumeToken().value; + + // if curly segment is directly followed by identifier + // include it in the segment + if (this.match('identifier')) { + curlySegment += this.consumeToken().value; + } + + return { + type: 'segment', + value: curlySegment + }; + } else { + return null; + } + }, + + metricSegment: function() { + var curly = this.curlyBraceSegment(); + if (curly) { + return curly; + } + + if (this.match('identifier') || this.match('number')) { + // hack to handle float numbers in metric segments + var parts = this.consumeToken().value.split('.'); + if (parts.length === 2) { + this.tokens.splice(this.index, 0, { type: '.' }); + this.tokens.splice(this.index + 1, 0, { type: 'number', value: parts[1] }); + } + + return { + type: 'segment', + value: parts[0] + }; + } + + if (!this.match('templateStart')) { + this.errorMark('Expected metric identifier'); + } + + this.consumeToken(); + + if (!this.match('identifier')) { + this.errorMark('Expected identifier after templateStart'); + } + + var node = { + type: 'template', + value: this.consumeToken().value + }; + + if (!this.match('templateEnd')) { + this.errorMark('Expected templateEnd'); + } + + this.consumeToken(); + return node; + }, + + metricExpression: function() { + if (!this.match('templateStart') && + !this.match('identifier') && + !this.match('number') && + !this.match('{')) { + return null; + } + + var node = { + type: 'metric', + segments: [] + }; + + node.segments.push(this.metricSegment()); + + while (this.match('.')) { + this.consumeToken(); + + var segment = this.metricSegment(); + if (!segment) { + this.errorMark('Expected metric identifier'); + } + + node.segments.push(segment); + } + + return node; + }, + + functionCall: function() { + if (!this.match('identifier', '(')) { + return null; + } + + var node: any = { + type: 'function', + name: this.consumeToken().value, + }; + + // consume left parenthesis + this.consumeToken(); + + node.params = this.functionParameters(); + + if (!this.match(')')) { + this.errorMark('Expected closing parenthesis'); + } + + this.consumeToken(); + + return node; + }, + + boolExpression: function() { + if (!this.match('bool')) { + return null; + } + + return { + type: 'bool', + value: this.consumeToken().value === 'true', + }; + }, + + functionParameters: function () { + if (this.match(')') || this.match('')) { + return []; + } + + var param = + this.functionCall() || + this.numericLiteral() || + this.seriesRefExpression() || + this.boolExpression() || + this.metricExpression() || + this.stringLiteral(); + + if (!this.match(',')) { + return [param]; + } + + this.consumeToken(); + return [param].concat(this.functionParameters()); + }, + + seriesRefExpression: function() { + if (!this.match('identifier')) { + return null; + } + + var value = this.tokens[this.index].value; + if (!value.match(/\#[A-Z]/)) { + return null; + } + + var token = this.consumeToken(); + + return { + type: 'series-ref', + value: token.value + }; + }, + + numericLiteral: function () { + if (!this.match('number')) { + return null; + } + + return { + type: 'number', + value: parseFloat(this.consumeToken().value) + }; + }, + + stringLiteral: function () { + if (!this.match('string')) { + return null; + } + + var token = this.consumeToken(); + if (token.isUnclosed) { + throw { message: 'Unclosed string parameter', pos: token.pos }; + } + + return { + type: 'string', + value: token.value + }; + }, + + errorMark: function(text) { + var currentToken = this.tokens[this.index]; + var type = currentToken ? currentToken.type : 'end of string'; + throw { + message: text + " instead found " + type, + pos: currentToken ? currentToken.pos : this.lexer.char + }; + }, + + // returns token value and incre + consumeToken: function() { + this.index++; + return this.tokens[this.index - 1]; + }, + + matchToken: function(type, index) { + var token = this.tokens[this.index + index]; + return (token === undefined && type === '') || + token && token.type === type; + }, + + match: function(token1, token2) { + return this.matchToken(token1, 0) && + (!token2 || this.matchToken(token2, 1)); + }, +}; + diff --git a/public/app/plugins/datasource/graphite/partials/query.editor.html b/public/app/plugins/datasource/graphite/partials/query.editor.html index 9cb2b454132..2557dfbf0a2 100755 --- a/public/app/plugins/datasource/graphite/partials/query.editor.html +++ b/public/app/plugins/datasource/graphite/partials/query.editor.html @@ -1,15 +1,15 @@
  • - +
  • @@ -45,24 +45,24 @@
    • - {{target.refId}} + {{ctrl.target.refId}}
    • - +
    - + -
    -
    + -
    +
    Testing....
    diff --git a/public/app/partials/metrics.html b/public/app/partials/metrics.html index 8ab5c4a1677..934feb1eb2c 100644 --- a/public/app/partials/metrics.html +++ b/public/app/partials/metrics.html @@ -1,8 +1,10 @@
    - - + + + +
    diff --git a/public/app/plugins/datasource/graphite/add_graphite_func.js b/public/app/plugins/datasource/graphite/add_graphite_func.js index 46d93f6481f..6c218513d57 100644 --- a/public/app/plugins/datasource/graphite/add_graphite_func.js +++ b/public/app/plugins/datasource/graphite/add_graphite_func.js @@ -22,6 +22,7 @@ function (angular, _, $, gfunc) { link: function($scope, elem) { var categories = gfunc.getCategories(); var allFunctions = getAllFunctionNames(categories); + var ctrl = $scope.ctrl; $scope.functionMenu = createFunctionDropDownMenu(categories); @@ -48,7 +49,7 @@ function (angular, _, $, gfunc) { } $scope.$apply(function() { - $scope.addFunction(funcDef); + ctrl.addFunction(funcDef); }); $input.trigger('blur'); diff --git a/public/app/plugins/datasource/graphite/func_editor.js b/public/app/plugins/datasource/graphite/func_editor.js index 63ee4b3f7cf..cd1f747bc4d 100644 --- a/public/app/plugins/datasource/graphite/func_editor.js +++ b/public/app/plugins/datasource/graphite/func_editor.js @@ -27,6 +27,7 @@ function (angular, _, $) { link: function postLink($scope, elem) { var $funcLink = $(funcSpanTemplate); var $funcControls = $(funcControlsTemplate); + var ctrl = $scope.ctrl; var func = $scope.func; var funcDef = func.def; var scheduledRelink = false; @@ -79,11 +80,13 @@ function (angular, _, $) { func.updateParam($input.val(), paramIndex); scheduledRelinkIfNeeded(); - $scope.$apply($scope.targetChanged); - } + $scope.$apply(function() { + ctrl.targetChanged(); + }); - $input.hide(); - $link.show(); + $input.hide(); + $link.show(); + } } function inputKeyPress(paramIndex, e) { @@ -198,7 +201,7 @@ function (angular, _, $) { if ($target.hasClass('fa-remove')) { toggleFuncControls(); $scope.$apply(function() { - $scope.removeFunction($scope.func); + ctrl.removeFunction($scope.func); }); return; } @@ -206,7 +209,7 @@ function (angular, _, $) { if ($target.hasClass('fa-arrow-left')) { $scope.$apply(function() { _.move($scope.functions, $scope.$index, $scope.$index - 1); - $scope.targetChanged(); + ctrl.targetChanged(); }); return; } @@ -214,7 +217,7 @@ function (angular, _, $) { if ($target.hasClass('fa-arrow-right')) { $scope.$apply(function() { _.move($scope.functions, $scope.$index, $scope.$index + 1); - $scope.targetChanged(); + ctrl.targetChanged(); }); return; } diff --git a/public/app/plugins/datasource/graphite/query_ctrl.ts b/public/app/plugins/datasource/graphite/query_ctrl.ts index 8d1922c4802..f6afe8a6d13 100644 --- a/public/app/plugins/datasource/graphite/query_ctrl.ts +++ b/public/app/plugins/datasource/graphite/query_ctrl.ts @@ -1,6 +1,7 @@ /// import './add_graphite_func'; +import './func_editor'; import angular from 'angular'; import _ from 'lodash'; diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index 5a5a468ece7..cb51f28ff0e 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -9,14 +9,14 @@
  • - +
  • From 21f6c07686f433936316816dd061d5364f774d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 2 Feb 2016 15:15:20 +0100 Subject: [PATCH 09/32] feat(plugins): more progress on plugin editors --- .../app/core/directives/plugin_component.ts | 38 ++++++++++++++++--- .../app/core/directives/rebuild_on_change.ts | 30 ++++++++++----- .../features/datasources/partials/edit.html | 2 +- public/app/partials/metrics.html | 14 ++++--- .../app/plugins/datasource/graphite/module.ts | 10 ++++- .../graphite/partials/query.options.html | 27 +++++++------ .../plugins/datasource/prometheus/module.ts | 4 +- 7 files changed, 86 insertions(+), 39 deletions(-) diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts index d4722c6d2f5..e628fbede39 100644 --- a/public/app/core/directives/plugin_component.ts +++ b/public/app/core/directives/plugin_component.ts @@ -5,7 +5,7 @@ import _ from 'lodash'; import coreModule from '../core_module'; -function pluginDirectiveLoader($compile, datasourceSrv) { +function pluginDirectiveLoader($compile, datasourceSrv, $rootScope) { function getPluginComponentDirective(options) { return function() { @@ -27,30 +27,53 @@ function pluginDirectiveLoader($compile, datasourceSrv) { function getModule(scope, attrs) { switch (attrs.type) { - case "metrics-query-editor": + // QueryCtrl + case "query-ctrl": { let datasource = scope.target.datasource || scope.ctrl.panel.datasource; return datasourceSrv.get(datasource).then(ds => { scope.datasource = ds; return System.import(ds.meta.module).then(dsModule => { return { - name: 'metrics-query-editor-' + ds.meta.id, + name: 'query-ctrl-' + ds.meta.id, bindings: {target: "=", panelCtrl: "=", datasource: "="}, attrs: {"target": "target", "panel-ctrl": "ctrl", datasource: "datasource"}, Component: dsModule.QueryCtrl }; }); }); + } + // QueryOptionsCtrl + case "query-options-ctrl": { + return datasourceSrv.get(scope.ctrl.panel.datasource).then(ds => { + return System.import(ds.meta.module).then((dsModule): any => { + if (!dsModule.QueryOptionsCtrl) { + return {notFound: true}; + } - case 'datasource-config-view': + return { + name: 'query-options-ctrl-' + ds.meta.id, + bindings: {panelCtrl: "="}, + attrs: {"panel-ctrl": "ctrl"}, + Component: dsModule.QueryOptionsCtrl + }; + }); + }); + } + // ConfigCtrl + case 'datasource-config-ctrl': { return System.import(scope.datasourceMeta.module).then(function(dsModule) { return { name: 'ds-config-' + scope.datasourceMeta.id, bindings: {meta: "=", current: "="}, attrs: {meta: "datasourceMeta", current: "current"}, - Component: dsModule.ConfigView, + Component: dsModule.ConfigCtrl, }; }); + } + default: { + $rootScope.appEvent('alert-error', ['Plugin component error', 'could not find component '+ attrs.type]); + } } } @@ -67,6 +90,11 @@ function pluginDirectiveLoader($compile, datasourceSrv) { } function registerPluginComponent(scope, elem, attrs, componentInfo) { + if (componentInfo.notFound) { + elem.empty(); + return; + } + if (!componentInfo.Component.registered) { var directiveName = attrs.$normalize(componentInfo.name); var directiveFn = getPluginComponentDirective(componentInfo); diff --git a/public/app/core/directives/rebuild_on_change.ts b/public/app/core/directives/rebuild_on_change.ts index 6cc7ad899c1..847903f22ff 100644 --- a/public/app/core/directives/rebuild_on_change.ts +++ b/public/app/core/directives/rebuild_on_change.ts @@ -23,9 +23,11 @@ function getBlockNodes(nodes) { return blockNodes || nodes; } -function rebuildOnChange($compile) { +function rebuildOnChange($animate) { return { + multiElement: true, + terminal: true, transclude: true, priority: 600, restrict: 'E', @@ -33,23 +35,31 @@ function rebuildOnChange($compile) { var childScope, previousElements; var uncompiledHtml; - scope.$watch(attrs.property, function rebuildOnChangeAction(value) { - + function cleanUp() { if (childScope) { childScope.$destroy(); childScope = null; elem.empty(); } + } - if (value || attrs.ignoreNull) { - if (!childScope) { - transclude(function(clone, newScope) { - childScope = newScope; - elem.append($compile(clone)(childScope)); - }); + scope.$watch(attrs.property, function rebuildOnChangeAction(value, oldValue) { + if (value || attrs.showNull) { + // if same value and we have childscope + // ignore this double event + if (value === oldValue && childScope) { + return; } - } + cleanUp(); + transclude(function(clone, newScope) { + childScope = newScope; + $animate.enter(clone, elem.parent(), elem); + }); + + } else { + cleanUp(); + } }); } }; diff --git a/public/app/features/datasources/partials/edit.html b/public/app/features/datasources/partials/edit.html index ad3473f0ea7..00706cb4de5 100644 --- a/public/app/features/datasources/partials/edit.html +++ b/public/app/features/datasources/partials/edit.html @@ -42,7 +42,7 @@
    - + diff --git a/public/app/partials/metrics.html b/public/app/partials/metrics.html index 934feb1eb2c..bf2ef7bdfed 100644 --- a/public/app/partials/metrics.html +++ b/public/app/partials/metrics.html @@ -1,10 +1,10 @@
    - - - - + + + +
    @@ -28,7 +28,11 @@
    - + + + + +
    diff --git a/public/app/plugins/datasource/graphite/module.ts b/public/app/plugins/datasource/graphite/module.ts index abab560aafd..a743c0ff65a 100644 --- a/public/app/plugins/datasource/graphite/module.ts +++ b/public/app/plugins/datasource/graphite/module.ts @@ -1,14 +1,20 @@ import {GraphiteDatasource} from './datasource'; import {GraphiteQueryCtrl} from './query_ctrl'; -class GraphiteConfigView { +class GraphiteConfigCtrl { static templateUrl = 'public/app/plugins/datasource/graphite/partials/config.html'; } +class GraphiteQueryOptionsCtrl { + static templateUrl = 'public/app/plugins/datasource/graphite/partials/query.options.html'; +} + + export { GraphiteDatasource as Datasource, GraphiteQueryCtrl as QueryCtrl, - GraphiteConfigView as ConfigView + GraphiteConfigCtrl as ConfigCtrl, + GraphiteQueryOptionsCtrl as QueryOptionsCtrl, }; // define([ diff --git a/public/app/plugins/datasource/graphite/partials/query.options.html b/public/app/plugins/datasource/graphite/partials/query.options.html index f20b55b1348..35c95d03e88 100644 --- a/public/app/plugins/datasource/graphite/partials/query.options.html +++ b/public/app/plugins/datasource/graphite/partials/query.options.html @@ -1,5 +1,4 @@
    -
    • @@ -11,7 +10,7 @@
    • @@ -39,27 +38,27 @@
    • - + shorter legend names
    • - + series as parameters
    • - + stacking
    • - + templating
    • - + max data points
    • @@ -71,7 +70,7 @@
      -
      +
      Shorter legend names
      • alias() function to specify a custom series name
      • @@ -81,7 +80,7 @@
      -
      +
      Series as parameter
      • Some graphite functions allow you to have many series arguments
      • @@ -99,7 +98,7 @@
      -
      +
      Stacking
      • You find the stacking option under Display Styles tab
      • @@ -107,7 +106,7 @@
      -
      +
      Templating
      • You can use a template variable in place of metric names
      • @@ -116,7 +115,7 @@
      -
      +
      Max data points
      • Every graphite request is issued with a maxDataPoints parameter
      • diff --git a/public/app/plugins/datasource/prometheus/module.ts b/public/app/plugins/datasource/prometheus/module.ts index ae0b6d99e9e..9af37384b4f 100644 --- a/public/app/plugins/datasource/prometheus/module.ts +++ b/public/app/plugins/datasource/prometheus/module.ts @@ -1,12 +1,12 @@ import {PrometheusDatasource} from './datasource'; import {PrometheusQueryCtrl} from './query_ctrl'; -class PrometheusConfigViewCtrl { +class PrometheusConfigCtrl { static templateUrl = 'public/app/plugins/datasource/prometheus/partials/config.html'; } export { PrometheusDatasource as Datasource, PrometheusQueryCtrl as QueryCtrl, - PrometheusConfigViewCtrl as ConfigView + PrometheusConfigCtrl as ConfigCtrl }; From f2700822e9108334de324e2df96ecf4305b6e9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 2 Feb 2016 16:32:36 +0100 Subject: [PATCH 10/32] feat(plugins): extracted first plugin row to its own component --- .../app/core/directives/rebuild_on_change.ts | 29 +-- public/app/features/panel/all.js | 1 + .../app/features/panel/metrics_panel_ctrl.ts | 4 +- .../panel/partials/query_editor_row.html | 56 ++++++ public/app/features/panel/query_ctrl.ts | 1 + public/app/features/panel/query_editor_row.ts | 18 ++ public/app/partials/metrics.html | 10 +- .../graphite/partials/query.editor.html | 62 +----- .../plugins/datasource/graphite/query_ctrl.ts | 1 + .../prometheus/partials/query.editor.html | 184 +++++++----------- 10 files changed, 181 insertions(+), 185 deletions(-) create mode 100644 public/app/features/panel/partials/query_editor_row.html create mode 100644 public/app/features/panel/query_editor_row.ts diff --git a/public/app/core/directives/rebuild_on_change.ts b/public/app/core/directives/rebuild_on_change.ts index 847903f22ff..b807d5bc50d 100644 --- a/public/app/core/directives/rebuild_on_change.ts +++ b/public/app/core/directives/rebuild_on_change.ts @@ -32,31 +32,38 @@ function rebuildOnChange($animate) { priority: 600, restrict: 'E', link: function(scope, elem, attrs, ctrl, transclude) { - var childScope, previousElements; - var uncompiledHtml; + var block, childScope, previousElements; function cleanUp() { + if (previousElements) { + previousElements.remove(); + previousElements = null; + } if (childScope) { childScope.$destroy(); childScope = null; - elem.empty(); + } + if (block) { + previousElements = getBlockNodes(block.clone); + $animate.leave(previousElements).then(function() { + previousElements = null; + }); + block = null; } } scope.$watch(attrs.property, function rebuildOnChangeAction(value, oldValue) { - if (value || attrs.showNull) { - // if same value and we have childscope - // ignore this double event - if (value === oldValue && childScope) { - return; - } - + if (childScope && value !== oldValue) { cleanUp(); + } + + if (!childScope && (value || attrs.showNull)) { transclude(function(clone, newScope) { childScope = newScope; + clone[clone.length++] = document.createComment(' end rebuild on change '); + block = {clone: clone}; $animate.enter(clone, elem.parent(), elem); }); - } else { cleanUp(); } diff --git a/public/app/features/panel/all.js b/public/app/features/panel/all.js index 96a119c8b48..47fe256e7cf 100644 --- a/public/app/features/panel/all.js +++ b/public/app/features/panel/all.js @@ -5,4 +5,5 @@ define([ './panel_loader', './query_ctrl', './panel_editor_tab', + './query_editor_row', ], function () {}); diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index 148d8647f28..14f5a59490b 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -164,12 +164,12 @@ class MetricsPanelCtrl extends PanelCtrl { }; issueQueries(datasource) { + this.updateTimeRange(); + if (!this.panel.targets || this.panel.targets.length === 0) { return this.$q.when([]); } - this.updateTimeRange(); - var metricsQuery = { range: this.range, rangeRaw: this.rangeRaw, diff --git a/public/app/features/panel/partials/query_editor_row.html b/public/app/features/panel/partials/query_editor_row.html new file mode 100644 index 00000000000..9df0dcdbc51 --- /dev/null +++ b/public/app/features/panel/partials/query_editor_row.html @@ -0,0 +1,56 @@ +
        + + +
          +
        • + {{ctrl.target.refId}} +
        • +
        • + + + +
        • +
        + +
          +
        + +
        +
        diff --git a/public/app/features/panel/query_ctrl.ts b/public/app/features/panel/query_ctrl.ts index 5227febed2f..236d55e3bda 100644 --- a/public/app/features/panel/query_ctrl.ts +++ b/public/app/features/panel/query_ctrl.ts @@ -8,6 +8,7 @@ export class QueryCtrl { datasource: any; panelCtrl: any; panel: any; + hasRawMode: boolean; constructor(public $scope, private $injector) { this.panel = this.panelCtrl.panel; diff --git a/public/app/features/panel/query_editor_row.ts b/public/app/features/panel/query_editor_row.ts new file mode 100644 index 00000000000..252fc38fe0d --- /dev/null +++ b/public/app/features/panel/query_editor_row.ts @@ -0,0 +1,18 @@ +/// + +import angular from 'angular'; +import $ from 'jquery'; + +var module = angular.module('grafana.directives'); + +/** @ngInject **/ +function queryEditorRowDirective() { + return { + restrict: 'E', + templateUrl: 'public/app/features/panel/partials/query_editor_row.html', + transclude: true, + scope: { ctrl: "=" }, + }; +} + +module.directive('queryEditorRow', queryEditorRowDirective); diff --git a/public/app/partials/metrics.html b/public/app/partials/metrics.html index bf2ef7bdfed..f7b1672cd97 100644 --- a/public/app/partials/metrics.html +++ b/public/app/partials/metrics.html @@ -1,10 +1,12 @@
        - - - - +
        + + + + +
        diff --git a/public/app/plugins/datasource/graphite/partials/query.editor.html b/public/app/plugins/datasource/graphite/partials/query.editor.html index 2557dfbf0a2..c78062b1cb0 100755 --- a/public/app/plugins/datasource/graphite/partials/query.editor.html +++ b/public/app/plugins/datasource/graphite/partials/query.editor.html @@ -1,61 +1,7 @@ -
        - - -
          -
        • - {{ctrl.target.refId}} -
        • -
        • - - - -
        • -
        + - + -
        -
        + + diff --git a/public/app/plugins/datasource/graphite/query_ctrl.ts b/public/app/plugins/datasource/graphite/query_ctrl.ts index f6afe8a6d13..01c61ad2a40 100644 --- a/public/app/plugins/datasource/graphite/query_ctrl.ts +++ b/public/app/plugins/datasource/graphite/query_ctrl.ts @@ -17,6 +17,7 @@ export class GraphiteQueryCtrl extends QueryCtrl { segments: any[]; parserError: string; + /** @ngInject **/ constructor($scope, $injector, private uiSegmentSrv, private templateSrv) { super($scope, $injector); diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index cb51f28ff0e..e65d716e832 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -1,118 +1,82 @@ + + +
      • + Query +
      • +
      • + +
      • +
      • + Metric +
      • +
      • + +
      • + +
        +
        - + -
          -
        • - {{ctrl.target.refId}} -
        • -
        • - - - -
        • -
        - - - -
        +
        - +
        +
      • + Resolution +
      • +
      • + +
      • +
      • + + + +
      • +
      -
      - - -
      +
      From fc829b32d9d9699d736bea5556677a7da89e352e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 2 Feb 2016 16:57:05 +0100 Subject: [PATCH 11/32] feat(plugins): minor fixes to breaking out query editor row into reusable component --- public/app/features/panel/query_editor_row.ts | 2 +- .../graphite/partials/query.editor.html | 32 ++++++++++--------- .../prometheus/partials/query.editor.html | 4 +-- public/less/tightform.less | 1 + 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/public/app/features/panel/query_editor_row.ts b/public/app/features/panel/query_editor_row.ts index 252fc38fe0d..a4cb6155139 100644 --- a/public/app/features/panel/query_editor_row.ts +++ b/public/app/features/panel/query_editor_row.ts @@ -11,7 +11,7 @@ function queryEditorRowDirective() { restrict: 'E', templateUrl: 'public/app/features/panel/partials/query_editor_row.html', transclude: true, - scope: { ctrl: "=" }, + scope: {ctrl: "="}, }; } diff --git a/public/app/plugins/datasource/graphite/partials/query.editor.html b/public/app/plugins/datasource/graphite/partials/query.editor.html index c78062b1cb0..90dfe0794c0 100755 --- a/public/app/plugins/datasource/graphite/partials/query.editor.html +++ b/public/app/plugins/datasource/graphite/partials/query.editor.html @@ -1,19 +1,21 @@ - + - - - +
    • + +
    • - +
    • + +
    • + +
    • +
    • + + +
    • + + +
    • diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index e65d716e832..fa04bb33b18 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -1,5 +1,4 @@ - - +
    • Query
    • @@ -25,7 +24,6 @@ placeholder="metric name" data-min-length=0 data-items=100> -
      diff --git a/public/less/tightform.less b/public/less/tightform.less index b46456e74c2..a58fa80b652 100644 --- a/public/less/tightform.less +++ b/public/less/tightform.less @@ -62,6 +62,7 @@ .tight-form-flex-wrapper { display: flex; flex-direction: row; + float: none !important; } .grafana-metric-options { From 05dfccbb74a50a5775d87cdd7a8cd69ad04fed9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 2 Feb 2016 18:16:30 +0100 Subject: [PATCH 12/32] feat(plugins): moved annotation editor to new plugin component loader --- .../app/core/directives/plugin_component.ts | 11 +++++ .../features/annotations/annotations_srv.js | 1 - .../features/annotations/partials/editor.html | 6 ++- .../app/features/annotations/query_editor.ts | 25 ----------- public/app/features/datasources/all.js | 1 - .../app/features/datasources/config_view.ts | 25 ----------- .../app/plugins/datasource/graphite/module.ts | 42 ++----------------- .../graphite/partials/annotations.editor.html | 4 +- 8 files changed, 21 insertions(+), 94 deletions(-) delete mode 100644 public/app/features/annotations/query_editor.ts delete mode 100644 public/app/features/datasources/config_view.ts diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts index e628fbede39..32b80cc0966 100644 --- a/public/app/core/directives/plugin_component.ts +++ b/public/app/core/directives/plugin_component.ts @@ -60,6 +60,17 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope) { }); }); } + // QueryOptionsCtrl + case "annotations-query-ctrl": { + return System.import(scope.currentDatasource.meta.module).then(function(dsModule) { + return { + name: 'annotations-query-ctrl-' + scope.currentDatasource.meta.id, + bindings: {annotation: "=", datasource: "="}, + attrs: {"annotation": "currentAnnotation", datasource: "currentDatasource"}, + Component: dsModule.AnnotationsQueryCtrl, + }; + }); + } // ConfigCtrl case 'datasource-config-ctrl': { return System.import(scope.datasourceMeta.module).then(function(dsModule) { diff --git a/public/app/features/annotations/annotations_srv.js b/public/app/features/annotations/annotations_srv.js index 9dce53084fe..b0022135ef5 100644 --- a/public/app/features/annotations/annotations_srv.js +++ b/public/app/features/annotations/annotations_srv.js @@ -2,7 +2,6 @@ define([ 'angular', 'lodash', './editor_ctrl', - './query_editor' ], function (angular, _) { 'use strict'; diff --git a/public/app/features/annotations/partials/editor.html b/public/app/features/annotations/partials/editor.html index 8894cb4b47f..f5d29e57677 100644 --- a/public/app/features/annotations/partials/editor.html +++ b/public/app/features/annotations/partials/editor.html @@ -91,8 +91,10 @@
      - - + + + +
      diff --git a/public/app/features/annotations/query_editor.ts b/public/app/features/annotations/query_editor.ts deleted file mode 100644 index 42d3e9047b5..00000000000 --- a/public/app/features/annotations/query_editor.ts +++ /dev/null @@ -1,25 +0,0 @@ -/// - -import angular from 'angular'; - -/** @ngInject */ -function annotationsQueryEditor(dynamicDirectiveSrv) { - return dynamicDirectiveSrv.create({ - scope: { - annotation: "=", - datasource: "=" - }, - watchPath: "annotation.datasource", - 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/datasources/all.js b/public/app/features/datasources/all.js index 8a57bbc5b8a..b181fd475c2 100644 --- a/public/app/features/datasources/all.js +++ b/public/app/features/datasources/all.js @@ -1,5 +1,4 @@ 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 deleted file mode 100644 index 39f593ba508..00000000000 --- a/public/app/features/datasources/config_view.ts +++ /dev/null @@ -1,25 +0,0 @@ -/// - -import angular from 'angular'; - -/** @ngInject */ -function dsConfigView(dynamicDirectiveSrv) { - return dynamicDirectiveSrv.create({ - scope: { - dsMeta: "=", - current: "=" - }, - watchPath: "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/plugins/datasource/graphite/module.ts b/public/app/plugins/datasource/graphite/module.ts index a743c0ff65a..63c04833fc2 100644 --- a/public/app/plugins/datasource/graphite/module.ts +++ b/public/app/plugins/datasource/graphite/module.ts @@ -9,49 +9,15 @@ class GraphiteQueryOptionsCtrl { static templateUrl = 'public/app/plugins/datasource/graphite/partials/query.options.html'; } +class AnnotationsQueryCtrl { + static templateUrl = 'public/app/plugins/datasource/graphite/partials/annotations.editor.html'; +} export { GraphiteDatasource as Datasource, GraphiteQueryCtrl as QueryCtrl, GraphiteConfigCtrl as ConfigCtrl, GraphiteQueryOptionsCtrl as QueryOptionsCtrl, + AnnotationsQueryCtrl as AnnotationsQueryCtrl, }; -// define([ -// './datasource', -// ], -// function (GraphiteDatasource) { -// 'use strict'; -// -// function metricsQueryEditor() { -// return { -// controller: 'GraphiteQueryCtrl', -// templateUrl: 'public/app/plugins/datasource/graphite/partials/query.editor.html' -// }; -// } -// -// function metricsQueryOptions() { -// return {templateUrl: 'public/app/plugins/datasource/graphite/partials/query.options.html'}; -// } -// -// function annotationsQueryEditor() { -// return {templateUrl: 'public/app/plugins/datasource/graphite/partials/annotations.editor.html'}; -// } -// -// function configView() { -// return {templateUrl: 'public/app/plugins/datasource/graphite/partials/config.html'}; -// } -// -// function ConfigView() { -// } -// ConfigView.templateUrl = 'public/app/plugins/datasource/graphite/partials/config.html'; -// -// return { -// Datasource: GraphiteDatasource, -// configView: configView, -// annotationsQueryEditor: annotationsQueryEditor, -// metricsQueryEditor: metricsQueryEditor, -// metricsQueryOptions: metricsQueryOptions, -// ConfigView: ConfigView -// }; -// }); diff --git a/public/app/plugins/datasource/graphite/partials/annotations.editor.html b/public/app/plugins/datasource/graphite/partials/annotations.editor.html index 9253bf614a6..ea5c2f7f50f 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 @@
      - +
      - +
      From eecf844ca2ae2ca574973d4f68b7e4af11c21395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 2 Feb 2016 22:58:37 +0100 Subject: [PATCH 13/32] feat(plugins): migrated influxdb query editor to new plugin model --- .../app/core/directives/plugin_component.ts | 2 +- .../app/features/panel/metrics_panel_ctrl.ts | 25 +- public/app/features/panel/partials/panel.html | 2 +- .../panel/partials/query_editor_row.html | 4 +- public/app/features/panel/query_ctrl.ts | 1 + .../plugins/datasource/graphite/query_ctrl.ts | 15 +- .../plugins/datasource/influxdb/datasource.js | 220 ------------ .../plugins/datasource/influxdb/datasource.ts | 213 ++++++++++++ .../app/plugins/datasource/influxdb/module.js | 30 -- .../app/plugins/datasource/influxdb/module.ts | 53 +++ .../influxdb/partials/query.editor.html | 134 +++----- .../plugins/datasource/influxdb/query_ctrl.js | 322 ------------------ .../plugins/datasource/influxdb/query_ctrl.ts | 318 +++++++++++++++++ .../influxdb/specs/query_ctrl_specs.ts | 138 ++++---- 14 files changed, 713 insertions(+), 764 deletions(-) delete mode 100644 public/app/plugins/datasource/influxdb/datasource.js create mode 100644 public/app/plugins/datasource/influxdb/datasource.ts delete mode 100644 public/app/plugins/datasource/influxdb/module.js create mode 100644 public/app/plugins/datasource/influxdb/module.ts delete mode 100644 public/app/plugins/datasource/influxdb/query_ctrl.js create mode 100644 public/app/plugins/datasource/influxdb/query_ctrl.ts diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts index 32b80cc0966..065a0be0392 100644 --- a/public/app/core/directives/plugin_component.ts +++ b/public/app/core/directives/plugin_component.ts @@ -60,7 +60,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope) { }); }); } - // QueryOptionsCtrl + // Annotations case "annotations-query-ctrl": { return System.import(scope.currentDatasource.meta.module).then(function(dsModule) { return { diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index 14f5a59490b..47f2393c4c0 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -38,13 +38,6 @@ class MetricsPanelCtrl extends PanelCtrl { if (!this.panel.targets) { this.panel.targets = [{}]; } - - // hookup initial data fetch - this.$timeout(() => { - if (!this.skipDataOnInit) { - this.refresh(); - } - }, 30);; } initEditMode() { @@ -182,15 +175,19 @@ class MetricsPanelCtrl extends PanelCtrl { }; this.setTimeQueryStart(); - return datasource.query(metricsQuery).then(results => { - this.setTimeQueryEnd(); + try { + return datasource.query(metricsQuery).then(results => { + this.setTimeQueryEnd(); - if (this.dashboard.snapshot) { - this.panel.snapshotData = results; - } + if (this.dashboard.snapshot) { + this.panel.snapshotData = results; + } - return results; - }); + return results; + }); + } catch (err) { + return this.$q.reject(err); + } } setDatasource(datasource) { diff --git a/public/app/features/panel/partials/panel.html b/public/app/features/panel/partials/panel.html index 13c2178e0b4..06132c107a4 100644 --- a/public/app/features/panel/partials/panel.html +++ b/public/app/features/panel/partials/panel.html @@ -1,6 +1,6 @@
      - + diff --git a/public/app/features/panel/partials/query_editor_row.html b/public/app/features/panel/partials/query_editor_row.html index 9df0dcdbc51..4cf21b32e17 100644 --- a/public/app/features/panel/partials/query_editor_row.html +++ b/public/app/features/panel/partials/query_editor_row.html @@ -1,7 +1,7 @@
        -
      • - +
      • +
      • diff --git a/public/app/features/panel/query_ctrl.ts b/public/app/features/panel/query_ctrl.ts index 236d55e3bda..016f675dd0f 100644 --- a/public/app/features/panel/query_ctrl.ts +++ b/public/app/features/panel/query_ctrl.ts @@ -9,6 +9,7 @@ export class QueryCtrl { panelCtrl: any; panel: any; hasRawMode: boolean; + error: string; constructor(public $scope, private $injector) { this.panel = this.panelCtrl.panel; diff --git a/public/app/plugins/datasource/graphite/query_ctrl.ts b/public/app/plugins/datasource/graphite/query_ctrl.ts index 01c61ad2a40..23c6c2aa6d6 100644 --- a/public/app/plugins/datasource/graphite/query_ctrl.ts +++ b/public/app/plugins/datasource/graphite/query_ctrl.ts @@ -15,7 +15,6 @@ export class GraphiteQueryCtrl extends QueryCtrl { functions: any[]; segments: any[]; - parserError: string; /** @ngInject **/ constructor($scope, $injector, private uiSegmentSrv, private templateSrv) { @@ -35,7 +34,7 @@ export class GraphiteQueryCtrl extends QueryCtrl { parseTarget() { this.functions = []; this.segments = []; - delete this.parserError; + this.error = null; if (this.target.textEditor) { return; @@ -49,7 +48,7 @@ export class GraphiteQueryCtrl extends QueryCtrl { } if (astNode.type === 'error') { - this.parserError = astNode.message + " at position: " + astNode.pos; + this.error = astNode.message + " at position: " + astNode.pos; this.target.textEditor = true; return; } @@ -58,7 +57,7 @@ export class GraphiteQueryCtrl extends QueryCtrl { this.parseTargeRecursive(astNode, null, 0); } catch (err) { console.log('error parsing target:', err.message); - this.parserError = err.message; + this.error = err.message; this.target.textEditor = true; } @@ -142,7 +141,7 @@ export class GraphiteQueryCtrl extends QueryCtrl { } } }).catch(err => { - this.parserError = err.message || 'Failed to issue metric query'; + this.error = err.message || 'Failed to issue metric query'; }); } @@ -179,13 +178,13 @@ export class GraphiteQueryCtrl extends QueryCtrl { altSegments.unshift(this.uiSegmentSrv.newSegment('*')); return altSegments; }).catch(err => { - this.parserError = err.message || 'Failed to issue metric query'; + this.error = err.message || 'Failed to issue metric query'; return []; }); } segmentValueChanged(segment, segmentIndex) { - delete this.parserError; + this.error = null; if (this.functions.length > 0 && this.functions[0].def.fake) { this.functions = []; @@ -210,7 +209,7 @@ export class GraphiteQueryCtrl extends QueryCtrl { } targetChanged() { - if (this.parserError) { + if (this.error) { return; } diff --git a/public/app/plugins/datasource/influxdb/datasource.js b/public/app/plugins/datasource/influxdb/datasource.js deleted file mode 100644 index 984d32ab035..00000000000 --- a/public/app/plugins/datasource/influxdb/datasource.js +++ /dev/null @@ -1,220 +0,0 @@ -define([ - 'angular', - 'lodash', - 'app/core/utils/datemath', - './influx_series', - './influx_query', -], -function (angular, _, dateMath, InfluxSeries, InfluxQuery) { - 'use strict'; - - InfluxQuery = InfluxQuery.default; - - /** @ngInject */ - function InfluxDatasource(instanceSettings, $q, backendSrv, templateSrv) { - this.type = 'influxdb'; - this.urls = _.map(instanceSettings.url.split(','), function(url) { - return url.trim(); - }); - - this.username = instanceSettings.username; - this.password = instanceSettings.password; - this.name = instanceSettings.name; - this.database = instanceSettings.database; - this.basicAuth = instanceSettings.basicAuth; - - this.supportAnnotations = true; - this.supportMetrics = true; - - this.query = function(options) { - var timeFilter = getTimeFilter(options); - var queryTargets = []; - var i, y; - - var allQueries = _.map(options.targets, function(target) { - if (target.hide) { return []; } - - queryTargets.push(target); - - // build query - var queryModel = new InfluxQuery(target); - var query = queryModel.render(); - query = query.replace(/\$interval/g, (target.interval || options.interval)); - return query; - - }).join("\n"); - - // replace grafana variables - allQueries = allQueries.replace(/\$timeFilter/g, timeFilter); - - // replace templated variables - allQueries = templateSrv.replace(allQueries, options.scopedVars); - - return this._seriesQuery(allQueries).then(function(data) { - if (!data || !data.results) { - return []; - } - - var seriesList = []; - for (i = 0; i < data.results.length; i++) { - var result = data.results[i]; - if (!result || !result.series) { continue; } - - var target = queryTargets[i]; - var alias = target.alias; - if (alias) { - alias = templateSrv.replace(target.alias, options.scopedVars); - } - - var influxSeries = new InfluxSeries({ series: data.results[i].series, alias: alias }); - - switch(target.resultFormat) { - case 'table': { - seriesList.push(influxSeries.getTable()); - break; - } - default: { - var timeSeries = influxSeries.getTimeSeries(); - for (y = 0; y < timeSeries.length; y++) { - seriesList.push(timeSeries[y]); - } - break; - } - } - } - - return { data: seriesList }; - }); - }; - - this.annotationQuery = function(options) { - var timeFilter = getTimeFilter({rangeRaw: options.rangeRaw}); - var query = options.annotation.query.replace('$timeFilter', timeFilter); - query = templateSrv.replace(query); - - return this._seriesQuery(query).then(function(data) { - if (!data || !data.results || !data.results[0]) { - throw { message: 'No results in response from InfluxDB' }; - } - return new InfluxSeries({series: data.results[0].series, annotation: options.annotation}).getAnnotations(); - }); - }; - - this.metricFindQuery = function (query) { - var interpolated; - try { - interpolated = templateSrv.replace(query); - } - catch (err) { - return $q.reject(err); - } - - return this._seriesQuery(interpolated).then(function (results) { - if (!results || results.results.length === 0) { return []; } - - var influxResults = results.results[0]; - if (!influxResults.series) { - return []; - } - - var series = influxResults.series[0]; - return _.map(series.values, function(value) { - if (_.isArray(value)) { - return { text: value[0] }; - } else { - return { text: value }; - } - }); - }); - }; - - this._seriesQuery = function(query) { - return this._influxRequest('GET', '/query', {q: query, epoch: 'ms'}); - }; - - this.testDatasource = function() { - return this.metricFindQuery('SHOW MEASUREMENTS LIMIT 1').then(function () { - return { status: "success", message: "Data source is working", title: "Success" }; - }); - }; - - this._influxRequest = function(method, url, data) { - var self = this; - - var currentUrl = self.urls.shift(); - self.urls.push(currentUrl); - - var params = { - u: self.username, - p: self.password, - }; - - if (self.database) { - params.db = self.database; - } - - if (method === 'GET') { - _.extend(params, data); - data = null; - } - - var options = { - method: method, - url: currentUrl + url, - params: params, - data: data, - precision: "ms", - inspect: { type: 'influxdb' }, - }; - - options.headers = options.headers || {}; - if (self.basicAuth) { - options.headers.Authorization = self.basicAuth; - } - - return backendSrv.datasourceRequest(options).then(function(result) { - return result.data; - }, function(err) { - if (err.status !== 0 || err.status >= 300) { - if (err.data && err.data.error) { - throw { message: 'InfluxDB Error Response: ' + err.data.error, data: err.data, config: err.config }; - } - else { - throw { message: 'InfluxDB Error: ' + err.message, data: err.data, config: err.config }; - } - } - }); - }; - - function getTimeFilter(options) { - var from = getInfluxTime(options.rangeRaw.from, false); - var until = getInfluxTime(options.rangeRaw.to, true); - var fromIsAbsolute = from[from.length-1] === 's'; - - if (until === 'now()' && !fromIsAbsolute) { - return 'time > ' + from; - } - - return 'time > ' + from + ' and time < ' + until; - } - - function getInfluxTime(date, roundUp) { - if (_.isString(date)) { - if (date === 'now') { - return 'now()'; - } - - var parts = /^now-(\d+)([d|h|m|s])$/.exec(date); - if (parts) { - var amount = parseInt(parts[1]); - var unit = parts[2]; - return 'now() - ' + amount + unit; - } - date = dateMath.parse(date, roundUp); - } - return (date.valueOf() / 1000).toFixed(0) + 's'; - } - } - - return InfluxDatasource; -}); diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts new file mode 100644 index 00000000000..2ef73dc8f2d --- /dev/null +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -0,0 +1,213 @@ +/// + +import angular from 'angular'; +import _ from 'lodash'; + +import * as dateMath from 'app/core/utils/datemath'; +import InfluxSeries from './influx_series'; +import InfluxQuery from './influx_query'; + +/** @ngInject */ +export function InfluxDatasource(instanceSettings, $q, backendSrv, templateSrv) { + this.type = 'influxdb'; + this.urls = _.map(instanceSettings.url.split(','), function(url) { + return url.trim(); + }); + + this.username = instanceSettings.username; + this.password = instanceSettings.password; + this.name = instanceSettings.name; + this.database = instanceSettings.database; + this.basicAuth = instanceSettings.basicAuth; + + this.supportAnnotations = true; + this.supportMetrics = true; + + this.query = function(options) { + var timeFilter = getTimeFilter(options); + var queryTargets = []; + var i, y; + + var allQueries = _.map(options.targets, function(target) { + if (target.hide) { return []; } + + queryTargets.push(target); + + // build query + var queryModel = new InfluxQuery(target); + var query = queryModel.render(); + query = query.replace(/\$interval/g, (target.interval || options.interval)); + return query; + + }).join("\n"); + + // replace grafana variables + allQueries = allQueries.replace(/\$timeFilter/g, timeFilter); + + // replace templated variables + allQueries = templateSrv.replace(allQueries, options.scopedVars); + + return this._seriesQuery(allQueries).then(function(data): any { + if (!data || !data.results) { + return []; + } + + var seriesList = []; + for (i = 0; i < data.results.length; i++) { + var result = data.results[i]; + if (!result || !result.series) { continue; } + + var target = queryTargets[i]; + var alias = target.alias; + if (alias) { + alias = templateSrv.replace(target.alias, options.scopedVars); + } + + var influxSeries = new InfluxSeries({ series: data.results[i].series, alias: alias }); + + switch (target.resultFormat) { + case 'table': { + seriesList.push(influxSeries.getTable()); + break; + } + default: { + var timeSeries = influxSeries.getTimeSeries(); + for (y = 0; y < timeSeries.length; y++) { + seriesList.push(timeSeries[y]); + } + break; + } + } + } + + return { data: seriesList }; + }); + }; + + this.annotationQuery = function(options) { + var timeFilter = getTimeFilter({rangeRaw: options.rangeRaw}); + var query = options.annotation.query.replace('$timeFilter', timeFilter); + query = templateSrv.replace(query); + + return this._seriesQuery(query).then(function(data) { + if (!data || !data.results || !data.results[0]) { + throw { message: 'No results in response from InfluxDB' }; + } + return new InfluxSeries({series: data.results[0].series, annotation: options.annotation}).getAnnotations(); + }); + }; + + this.metricFindQuery = function (query) { + var interpolated; + try { + interpolated = templateSrv.replace(query); + } catch (err) { + return $q.reject(err); + } + + return this._seriesQuery(interpolated).then(function (results) { + if (!results || results.results.length === 0) { return []; } + + var influxResults = results.results[0]; + if (!influxResults.series) { + return []; + } + + var series = influxResults.series[0]; + return _.map(series.values, function(value) { + if (_.isArray(value)) { + return { text: value[0] }; + } else { + return { text: value }; + } + }); + }); + }; + + this._seriesQuery = function(query) { + return this._influxRequest('GET', '/query', {q: query, epoch: 'ms'}); + }; + + this.testDatasource = function() { + return this.metricFindQuery('SHOW MEASUREMENTS LIMIT 1').then(function () { + return { status: "success", message: "Data source is working", title: "Success" }; + }); + }; + + this._influxRequest = function(method, url, data) { + var self = this; + + var currentUrl = self.urls.shift(); + self.urls.push(currentUrl); + + var params: any = { + u: self.username, + p: self.password, + }; + + if (self.database) { + params.db = self.database; + } + + if (method === 'GET') { + _.extend(params, data); + data = null; + } + + var options: any = { + method: method, + url: currentUrl + url, + params: params, + data: data, + precision: "ms", + inspect: { type: 'influxdb' }, + }; + + options.headers = options.headers || {}; + if (self.basicAuth) { + options.headers.Authorization = self.basicAuth; + } + + return backendSrv.datasourceRequest(options).then(function(result) { + return result.data; + }, function(err) { + if (err.status !== 0 || err.status >= 300) { + if (err.data && err.data.error) { + throw { message: 'InfluxDB Error Response: ' + err.data.error, data: err.data, config: err.config }; + } else { + throw { message: 'InfluxDB Error: ' + err.message, data: err.data, config: err.config }; + } + } + }); + }; + + function getTimeFilter(options) { + var from = getInfluxTime(options.rangeRaw.from, false); + var until = getInfluxTime(options.rangeRaw.to, true); + var fromIsAbsolute = from[from.length-1] === 's'; + + if (until === 'now()' && !fromIsAbsolute) { + return 'time > ' + from; + } + + return 'time > ' + from + ' and time < ' + until; + } + + function getInfluxTime(date, roundUp) { + if (_.isString(date)) { + if (date === 'now') { + return 'now()'; + } + + var parts = /^now-(\d+)([d|h|m|s])$/.exec(date); + if (parts) { + var amount = parseInt(parts[1]); + var unit = parts[2]; + return 'now() - ' + amount + unit; + } + date = dateMath.parse(date, roundUp); + } + return (date.valueOf() / 1000).toFixed(0) + 's'; + } +} + diff --git a/public/app/plugins/datasource/influxdb/module.js b/public/app/plugins/datasource/influxdb/module.js deleted file mode 100644 index b7f266f9f89..00000000000 --- a/public/app/plugins/datasource/influxdb/module.js +++ /dev/null @@ -1,30 +0,0 @@ -define([ - './datasource', -], -function (InfluxDatasource) { - 'use strict'; - - function influxMetricsQueryEditor() { - return {controller: 'InfluxQueryCtrl', templateUrl: 'public/app/plugins/datasource/influxdb/partials/query.editor.html'}; - } - - function influxMetricsQueryOptions() { - return {templateUrl: 'public/app/plugins/datasource/influxdb/partials/query.options.html'}; - } - - function influxAnnotationsQueryEditor() { - return {templateUrl: 'public/app/plugins/datasource/influxdb/partials/annotations.editor.html'}; - } - - function influxConfigView() { - return {templateUrl: 'public/app/plugins/datasource/influxdb/partials/config.html'}; - } - - return { - Datasource: InfluxDatasource, - metricsQueryEditor: influxMetricsQueryEditor, - metricsQueryOptions: influxMetricsQueryOptions, - annotationsQueryEditor: influxAnnotationsQueryEditor, - configView: influxConfigView, - }; -}); diff --git a/public/app/plugins/datasource/influxdb/module.ts b/public/app/plugins/datasource/influxdb/module.ts new file mode 100644 index 00000000000..a8acffe6695 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/module.ts @@ -0,0 +1,53 @@ +import {InfluxDatasource} from './datasource'; +import {InfluxQueryCtrl} from './query_ctrl'; + +class InfluxConfigCtrl { + static templateUrl = 'public/app/plugins/datasource/influxdb/partials/config.html'; +} + +class InfluxQueryOptionsCtrl { + static templateUrl = 'public/app/plugins/datasource/influxdb/partials/query.options.html'; +} + +class InfluxAnnotationsQueryCtrl { + static templateUrl = 'public/app/plugins/datasource/influxdb/partials/annotations.editor.html'; +} + +export { + InfluxDatasource as Datasource, + InfluxQueryCtrl as QueryCtrl, + InfluxConfigCtrl as ConfigCtrl, + InfluxQueryOptionsCtrl as QueryOptionsCtrl, + InfluxAnnotationsQueryCtrl as AnnotationsQueryCtrl, +}; + +// define([ +// './datasource', +// ], +// function (InfluxDatasource) { +// 'use strict'; +// +// function influxMetricsQueryEditor() { +// return {controller: 'InfluxQueryCtrl', templateUrl: 'public/app/plugins/datasource/influxdb/partials/query.editor.html'}; +// } +// +// function influxMetricsQueryOptions() { +// return {templateUrl: 'public/app/plugins/datasource/influxdb/partials/query.options.html'}; +// } +// +// function influxAnnotationsQueryEditor() { +// return {templateUrl: 'public/app/plugins/datasource/influxdb/partials/annotations.editor.html'}; +// } +// +// function influxConfigView() { +// return {templateUrl: 'public/app/plugins/datasource/influxdb/partials/config.html'}; +// } +// +// return { +// Datasource: InfluxDatasource, +// metricsQueryEditor: influxMetricsQueryEditor, +// metricsQueryOptions: influxMetricsQueryOptions, +// annotationsQueryEditor: influxAnnotationsQueryEditor, +// configView: influxConfigView, +// }; +// }); diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 3322be6c423..8e5546573d7 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -1,119 +1,73 @@ -
        -
        - - -
          -
        • - {{target.refId}} -
        • -
        • - - - -
        • -
        - -
          + +
          • FROM
          • - +
          • - +
          • WHERE
          • -
          • - +
          • +
          - +
          +
          +
          +
          +
            +
          • + SELECT +
          • +
          • + +
          • + +
          -
          - -
          -
            -
          • - SELECT -
          • -
          • - -
          • - -
          -
          -
          - -
          -
            -
          • - GROUP BY -
          • -
          • - -
          • -
          • - -
          • -
          -
          -
          -
          -
          • - ALIAS BY + GROUP BY +
          • +
          • +
          • - -
          • -
          • - Format as -
          • -
          • - +
          -
          + +
          +
            +
          • + ALIAS BY +
          • +
          • + +
          • +
          • + Format as +
          • +
          • + +
          • +
          +
          +
          + diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js deleted file mode 100644 index fee1d5d1c77..00000000000 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ /dev/null @@ -1,322 +0,0 @@ -define([ - 'angular', - 'lodash', - './query_builder', - './influx_query', - './query_part', - './query_part_editor', -], -function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { - 'use strict'; - - var module = angular.module('grafana.controllers'); - - InfluxQuery = InfluxQuery.default; - queryPart = queryPart.default; - - module.controller('InfluxQueryCtrl', function($scope, templateSrv, $q, uiSegmentSrv) { - var panelCtrl = $scope.ctrl; - var datasource = $scope.datasource; - $scope.panelCtrl = panelCtrl; - - $scope.init = function() { - if (!$scope.target) { return; } - - $scope.target = $scope.target; - $scope.queryModel = new InfluxQuery($scope.target); - $scope.queryBuilder = new InfluxQueryBuilder($scope.target, datasource.database); - $scope.groupBySegment = uiSegmentSrv.newPlusButton(); - $scope.resultFormats = [ - {text: 'Time series', value: 'time_series'}, - {text: 'Table', value: 'table'}, - ]; - - $scope.policySegment = uiSegmentSrv.newSegment($scope.target.policy); - - if (!$scope.target.measurement) { - $scope.measurementSegment = uiSegmentSrv.newSelectMeasurement(); - } else { - $scope.measurementSegment = uiSegmentSrv.newSegment($scope.target.measurement); - } - - $scope.tagSegments = []; - _.each($scope.target.tags, function(tag) { - if (!tag.operator) { - if (/^\/.*\/$/.test(tag.value)) { - tag.operator = "=~"; - } else { - tag.operator = '='; - } - } - - if (tag.condition) { - $scope.tagSegments.push(uiSegmentSrv.newCondition(tag.condition)); - } - - $scope.tagSegments.push(uiSegmentSrv.newKey(tag.key)); - $scope.tagSegments.push(uiSegmentSrv.newOperator(tag.operator)); - $scope.tagSegments.push(uiSegmentSrv.newKeyValue(tag.value)); - }); - - $scope.fixTagSegments(); - $scope.buildSelectMenu(); - $scope.removeTagFilterSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove tag filter --'}); - }; - - $scope.buildSelectMenu = function() { - var categories = queryPart.getCategories(); - $scope.selectMenu = _.reduce(categories, function(memo, cat, key) { - var menu = {text: key}; - menu.submenu = _.map(cat, function(item) { - return {text: item.type, value: item.type}; - }); - memo.push(menu); - return memo; - }, []); - }; - - $scope.getGroupByOptions = function() { - var query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS'); - - return datasource.metricFindQuery(query) - .then(function(tags) { - var options = []; - if (!$scope.queryModel.hasFill()) { - options.push(uiSegmentSrv.newSegment({value: 'fill(null)'})); - } - if (!$scope.queryModel.hasGroupByTime()) { - options.push(uiSegmentSrv.newSegment({value: 'time($interval)'})); - } - _.each(tags, function(tag) { - options.push(uiSegmentSrv.newSegment({value: 'tag(' + tag.text + ')'})); - }); - return options; - }) - .then(null, $scope.handleQueryError); - }; - - $scope.groupByAction = function() { - $scope.queryModel.addGroupBy($scope.groupBySegment.value); - var plusButton = uiSegmentSrv.newPlusButton(); - $scope.groupBySegment.value = plusButton.value; - $scope.groupBySegment.html = plusButton.html; - panelCtrl.refresh(); - }; - - $scope.removeGroupByPart = function(part, index) { - $scope.queryModel.removeGroupByPart(part, index); - panelCtrl.refresh(); - }; - - $scope.addSelectPart = function(selectParts, cat, subitem) { - $scope.queryModel.addSelectPart(selectParts, subitem.value); - panelCtrl.refresh(); - }; - - $scope.removeSelectPart = function(selectParts, part) { - $scope.queryModel.removeSelectPart(selectParts, part); - panelCtrl.refresh(); - }; - - $scope.selectPartUpdated = function() { - panelCtrl.refresh(); - }; - - $scope.fixTagSegments = function() { - var count = $scope.tagSegments.length; - var lastSegment = $scope.tagSegments[Math.max(count-1, 0)]; - - if (!lastSegment || lastSegment.type !== 'plus-button') { - $scope.tagSegments.push(uiSegmentSrv.newPlusButton()); - } - }; - - $scope.measurementChanged = function() { - $scope.target.measurement = $scope.measurementSegment.value; - panelCtrl.refresh(); - }; - - $scope.getPolicySegments = function() { - var policiesQuery = $scope.queryBuilder.buildExploreQuery('RETENTION POLICIES'); - return datasource.metricFindQuery(policiesQuery) - .then($scope.transformToSegments(false)) - .then(null, $scope.handleQueryError); - }; - - $scope.policyChanged = function() { - $scope.target.policy = $scope.policySegment.value; - panelCtrl.refresh(); - }; - - $scope.toggleQueryMode = function () { - $scope.target.rawQuery = !$scope.target.rawQuery; - }; - - $scope.getMeasurements = function () { - var query = $scope.queryBuilder.buildExploreQuery('MEASUREMENTS'); - return datasource.metricFindQuery(query) - .then($scope.transformToSegments(true), $scope.handleQueryError); - }; - - $scope.getPartOptions = function(part) { - if (part.def.type === 'field') { - var fieldsQuery = $scope.queryBuilder.buildExploreQuery('FIELDS'); - return datasource.metricFindQuery(fieldsQuery) - .then($scope.transformToSegments(true), $scope.handleQueryError); - } - if (part.def.type === 'tag') { - var tagsQuery = $scope.queryBuilder.buildExploreQuery('TAG_KEYS'); - return datasource.metricFindQuery(tagsQuery) - .then($scope.transformToSegments(true), $scope.handleQueryError); - } - }; - - $scope.handleQueryError = function(err) { - $scope.parserError = err.message || 'Failed to issue metric query'; - return []; - }; - - $scope.transformToSegments = function(addTemplateVars) { - return function(results) { - var segments = _.map(results, function(segment) { - return uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable }); - }); - - if (addTemplateVars) { - _.each(templateSrv.variables, function(variable) { - segments.unshift(uiSegmentSrv.newSegment({ type: 'template', value: '/$' + variable.name + '$/', expandable: true })); - }); - } - - return segments; - }; - }; - - $scope.getTagsOrValues = function(segment, index) { - if (segment.type === 'condition') { - return $q.when([uiSegmentSrv.newSegment('AND'), uiSegmentSrv.newSegment('OR')]); - } - if (segment.type === 'operator') { - var nextValue = $scope.tagSegments[index+1].value; - if (/^\/.*\/$/.test(nextValue)) { - return $q.when(uiSegmentSrv.newOperators(['=~', '!~'])); - } else { - return $q.when(uiSegmentSrv.newOperators(['=', '<>', '<', '>'])); - } - } - - var query, addTemplateVars; - if (segment.type === 'key' || segment.type === 'plus-button') { - query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS'); - addTemplateVars = false; - } else if (segment.type === 'value') { - query = $scope.queryBuilder.buildExploreQuery('TAG_VALUES', $scope.tagSegments[index-2].value); - addTemplateVars = true; - } - - return datasource.metricFindQuery(query) - .then($scope.transformToSegments(addTemplateVars)) - .then(function(results) { - if (segment.type === 'key') { - results.splice(0, 0, angular.copy($scope.removeTagFilterSegment)); - } - return results; - }) - .then(null, $scope.handleQueryError); - }; - - $scope.getFieldSegments = function() { - var fieldsQuery = $scope.queryBuilder.buildExploreQuery('FIELDS'); - return datasource.metricFindQuery(fieldsQuery) - .then($scope.transformToSegments(false)) - .then(null, $scope.handleQueryError); - }; - - $scope.getTagOptions = function() { - }; - - $scope.setFill = function(fill) { - $scope.target.fill = fill; - panelCtrl.refresh(); - }; - - $scope.tagSegmentUpdated = function(segment, index) { - $scope.tagSegments[index] = segment; - - // handle remove tag condition - if (segment.value === $scope.removeTagFilterSegment.value) { - $scope.tagSegments.splice(index, 3); - if ($scope.tagSegments.length === 0) { - $scope.tagSegments.push(uiSegmentSrv.newPlusButton()); - } else if ($scope.tagSegments.length > 2) { - $scope.tagSegments.splice(Math.max(index-1, 0), 1); - if ($scope.tagSegments[$scope.tagSegments.length-1].type !== 'plus-button') { - $scope.tagSegments.push(uiSegmentSrv.newPlusButton()); - } - } - } - else { - if (segment.type === 'plus-button') { - if (index > 2) { - $scope.tagSegments.splice(index, 0, uiSegmentSrv.newCondition('AND')); - } - $scope.tagSegments.push(uiSegmentSrv.newOperator('=')); - $scope.tagSegments.push(uiSegmentSrv.newFake('select tag value', 'value', 'query-segment-value')); - segment.type = 'key'; - segment.cssClass = 'query-segment-key'; - } - - if ((index+1) === $scope.tagSegments.length) { - $scope.tagSegments.push(uiSegmentSrv.newPlusButton()); - } - } - - $scope.rebuildTargetTagConditions(); - }; - - $scope.rebuildTargetTagConditions = function() { - var tags = []; - var tagIndex = 0; - var tagOperator = ""; - _.each($scope.tagSegments, function(segment2, index) { - if (segment2.type === 'key') { - if (tags.length === 0) { - tags.push({}); - } - tags[tagIndex].key = segment2.value; - } - else if (segment2.type === 'value') { - tagOperator = $scope.getTagValueOperator(segment2.value, tags[tagIndex].operator); - if (tagOperator) { - $scope.tagSegments[index-1] = uiSegmentSrv.newOperator(tagOperator); - tags[tagIndex].operator = tagOperator; - } - tags[tagIndex].value = segment2.value; - } - else if (segment2.type === 'condition') { - tags.push({ condition: segment2.value }); - tagIndex += 1; - } - else if (segment2.type === 'operator') { - tags[tagIndex].operator = segment2.value; - } - }); - - $scope.target.tags = tags; - panelCtrl.refresh(); - }; - - $scope.getTagValueOperator = function(tagValue, tagOperator) { - if (tagOperator !== '=~' && tagOperator !== '!~' && /^\/.*\/$/.test(tagValue)) { - return '=~'; - } - else if ((tagOperator === '=~' || tagOperator === '!~') && /^(?!\/.*\/$)/.test(tagValue)) { - return '='; - } - }; - - $scope.init(); - - }); - -}); diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.ts b/public/app/plugins/datasource/influxdb/query_ctrl.ts new file mode 100644 index 00000000000..8c32ff36b0b --- /dev/null +++ b/public/app/plugins/datasource/influxdb/query_ctrl.ts @@ -0,0 +1,318 @@ +/// + +import './query_part_editor'; +import './query_part_editor'; + +import angular from 'angular'; +import _ from 'lodash'; +import InfluxQueryBuilder from './query_builder'; +import InfluxQuery from './influx_query'; +import queryPart from './query_part'; +import {QueryCtrl} from 'app/features/panel/panel'; + +export class InfluxQueryCtrl extends QueryCtrl { + static templateUrl = 'public/app/plugins/datasource/influxdb/partials/query.editor.html'; + + queryModel: InfluxQuery; + queryBuilder: any; + groupBySegment: any; + resultFormats: any[]; + policySegment: any; + tagSegments: any[]; + selectMenu: any; + measurementSegment: any; + removeTagFilterSegment: any; + + constructor($scope, $injector, private templateSrv, private $q, private uiSegmentSrv) { + super($scope, $injector); + + this.target = this.target; + this.queryModel = new InfluxQuery(this.target); + this.queryBuilder = new InfluxQueryBuilder(this.target, this.datasource.database); + this.groupBySegment = this.uiSegmentSrv.newPlusButton(); + this.resultFormats = [ + {text: 'Time series', value: 'time_series'}, + {text: 'Table', value: 'table'}, + ]; + + this.policySegment = uiSegmentSrv.newSegment(this.target.policy); + + if (!this.target.measurement) { + this.measurementSegment = uiSegmentSrv.newSelectMeasurement(); + } else { + this.measurementSegment = uiSegmentSrv.newSegment(this.target.measurement); + } + + this.tagSegments = []; + for (let tag of this.target.tags) { + if (!tag.operator) { + if (/^\/.*\/$/.test(tag.value)) { + tag.operator = "=~"; + } else { + tag.operator = '='; + } + } + + if (tag.condition) { + this.tagSegments.push(uiSegmentSrv.newCondition(tag.condition)); + } + + this.tagSegments.push(uiSegmentSrv.newKey(tag.key)); + this.tagSegments.push(uiSegmentSrv.newOperator(tag.operator)); + this.tagSegments.push(uiSegmentSrv.newKeyValue(tag.value)); + } + + this.fixTagSegments(); + this.buildSelectMenu(); + this.removeTagFilterSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove tag filter --'}); + } + + buildSelectMenu() { + var categories = queryPart.getCategories(); + this.selectMenu = _.reduce(categories, function(memo, cat, key) { + var menu = { + text: key, + submenu: cat.map(item => { + return {text: item.type, value: item.type}; + }), + }; + memo.push(menu); + return memo; + }, []); + } + + getGroupByOptions() { + var query = this.queryBuilder.buildExploreQuery('TAG_KEYS'); + + return this.datasource.metricFindQuery(query).then(tags => { + var options = []; + if (!this.queryModel.hasFill()) { + options.push(this.uiSegmentSrv.newSegment({value: 'fill(null)'})); + } + if (!this.queryModel.hasGroupByTime()) { + options.push(this.uiSegmentSrv.newSegment({value: 'time($interval)'})); + } + for (let tag of tags) { + options.push(this.uiSegmentSrv.newSegment({value: 'tag(' + tag.text + ')'})); + } + return options; + }).catch(this.handleQueryError.bind(this)); + } + + groupByAction() { + this.queryModel.addGroupBy(this.groupBySegment.value); + var plusButton = this.uiSegmentSrv.newPlusButton(); + this.groupBySegment.value = plusButton.value; + this.groupBySegment.html = plusButton.html; + this.panelCtrl.refresh(); + } + + removeGroupByPart(part, index) { + this.queryModel.removeGroupByPart(part, index); + this.panelCtrl.refresh(); + } + + addSelectPart(selectParts, cat, subitem) { + this.queryModel.addSelectPart(selectParts, subitem.value); + this.panelCtrl.refresh(); + } + + removeSelectPart(selectParts, part) { + this.queryModel.removeSelectPart(selectParts, part); + this.panelCtrl.refresh(); + } + + selectPartUpdated() { + this.panelCtrl.refresh(); + } + + fixTagSegments() { + var count = this.tagSegments.length; + var lastSegment = this.tagSegments[Math.max(count-1, 0)]; + + if (!lastSegment || lastSegment.type !== 'plus-button') { + this.tagSegments.push(this.uiSegmentSrv.newPlusButton()); + } + } + + measurementChanged() { + this.target.measurement = this.measurementSegment.value; + this.panelCtrl.refresh(); + } + + getPolicySegments() { + var policiesQuery = this.queryBuilder.buildExploreQuery('RETENTION POLICIES'); + return this.datasource.metricFindQuery(policiesQuery) + .then(this.transformToSegments(false)) + .catch(this.handleQueryError.bind(this)); + } + + policyChanged() { + this.target.policy = this.policySegment.value; + this.panelCtrl.refresh(); + } + + toggleQueryMode() { + this.target.rawQuery = !this.target.rawQuery; + } + + getMeasurements() { + var query = this.queryBuilder.buildExploreQuery('MEASUREMENTS'); + return this.datasource.metricFindQuery(query) + .then(this.transformToSegments(true)) + .catch(this.handleQueryError.bind(this)); + } + + getPartOptions(part) { + if (part.def.type === 'field') { + var fieldsQuery = this.queryBuilder.buildExploreQuery('FIELDS'); + return this.datasource.metricFindQuery(fieldsQuery) + .then(this.transformToSegments(true)) + .catch(this.handleQueryError.bind(this)); + } + if (part.def.type === 'tag') { + var tagsQuery = this.queryBuilder.buildExploreQuery('TAG_KEYS'); + return this.datasource.metricFindQuery(tagsQuery) + .then(this.transformToSegments(true)) + .catch(this.handleQueryError.bind(true)); + } + } + + handleQueryError(err) { + this.error = err.message || 'Failed to issue metric query'; + return []; + } + + transformToSegments(addTemplateVars) { + return (results) => { + var segments = _.map(results, segment => { + return this.uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable }); + }); + + if (addTemplateVars) { + for (let variable of this.templateSrv.variables) { + segments.unshift(this.uiSegmentSrv.newSegment({ type: 'template', value: '/$' + variable.name + '$/', expandable: true })); + } + } + + return segments; + }; + } + + getTagsOrValues(segment, index) { + if (segment.type === 'condition') { + return this.$q.when([this.uiSegmentSrv.newSegment('AND'), this.uiSegmentSrv.newSegment('OR')]); + } + if (segment.type === 'operator') { + var nextValue = this.tagSegments[index+1].value; + if (/^\/.*\/$/.test(nextValue)) { + return this.$q.when(this.uiSegmentSrv.newOperators(['=~', '!~'])); + } else { + return this.$q.when(this.uiSegmentSrv.newOperators(['=', '<>', '<', '>'])); + } + } + + var query, addTemplateVars; + if (segment.type === 'key' || segment.type === 'plus-button') { + query = this.queryBuilder.buildExploreQuery('TAG_KEYS'); + addTemplateVars = false; + } else if (segment.type === 'value') { + query = this.queryBuilder.buildExploreQuery('TAG_VALUES', this.tagSegments[index-2].value); + addTemplateVars = true; + } + + return this.datasource.metricFindQuery(query) + .then(this.transformToSegments(addTemplateVars)) + .then(results => { + if (segment.type === 'key') { + results.splice(0, 0, angular.copy(this.removeTagFilterSegment)); + } + return results; + }) + .catch(this.handleQueryError.bind(this)); + } + + getFieldSegments() { + var fieldsQuery = this.queryBuilder.buildExploreQuery('FIELDS'); + return this.datasource.metricFindQuery(fieldsQuery) + .then(this.transformToSegments(false)) + .catch(this.handleQueryError); + } + + setFill(fill) { + this.target.fill = fill; + this.panelCtrl.refresh(); + } + + tagSegmentUpdated(segment, index) { + this.tagSegments[index] = segment; + + // handle remove tag condition + if (segment.value === this.removeTagFilterSegment.value) { + this.tagSegments.splice(index, 3); + if (this.tagSegments.length === 0) { + this.tagSegments.push(this.uiSegmentSrv.newPlusButton()); + } else if (this.tagSegments.length > 2) { + this.tagSegments.splice(Math.max(index-1, 0), 1); + if (this.tagSegments[this.tagSegments.length-1].type !== 'plus-button') { + this.tagSegments.push(this.uiSegmentSrv.newPlusButton()); + } + } + } else { + if (segment.type === 'plus-button') { + if (index > 2) { + this.tagSegments.splice(index, 0, this.uiSegmentSrv.newCondition('AND')); + } + this.tagSegments.push(this.uiSegmentSrv.newOperator('=')); + this.tagSegments.push(this.uiSegmentSrv.newFake('select tag value', 'value', 'query-segment-value')); + segment.type = 'key'; + segment.cssClass = 'query-segment-key'; + } + + if ((index+1) === this.tagSegments.length) { + this.tagSegments.push(this.uiSegmentSrv.newPlusButton()); + } + } + + this.rebuildTargetTagConditions(); + } + + rebuildTargetTagConditions() { + var tags = []; + var tagIndex = 0; + var tagOperator = ""; + + _.each(this.tagSegments, (segment2, index) => { + if (segment2.type === 'key') { + if (tags.length === 0) { + tags.push({}); + } + tags[tagIndex].key = segment2.value; + } else if (segment2.type === 'value') { + tagOperator = this.getTagValueOperator(segment2.value, tags[tagIndex].operator); + if (tagOperator) { + this.tagSegments[index-1] = this.uiSegmentSrv.newOperator(tagOperator); + tags[tagIndex].operator = tagOperator; + } + tags[tagIndex].value = segment2.value; + } else if (segment2.type === 'condition') { + tags.push({ condition: segment2.value }); + tagIndex += 1; + } else if (segment2.type === 'operator') { + tags[tagIndex].operator = segment2.value; + } + }); + + this.target.tags = tags; + this.panelCtrl.refresh(); + } + + getTagValueOperator(tagValue, tagOperator) { + if (tagOperator !== '=~' && tagOperator !== '!~' && /^\/.*\/$/.test(tagValue)) { + return '=~'; + } else if ((tagOperator === '=~' || tagOperator === '!~') && /^(?!\/.*\/$)/.test(tagValue)) { + return '='; + } + } +} + diff --git a/public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts b/public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts index 778f08e1352..427869c1d10 100644 --- a/public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts @@ -2,6 +2,7 @@ import '../query_ctrl'; import 'app/core/services/segment_srv'; import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; import helpers from 'test/specs/helpers'; +import {InfluxQueryCtrl} from '../query_ctrl'; describe('InfluxDBQueryCtrl', function() { var ctx = new helpers.ControllerTestContext(); @@ -14,179 +15,164 @@ describe('InfluxDBQueryCtrl', function() { beforeEach(angularMocks.inject(($rootScope, $controller, $q) => { ctx.$q = $q; ctx.scope = $rootScope.$new(); - ctx.scope.ctrl = {panel: ctx.panel}; - ctx.scope.datasource = ctx.datasource; - ctx.scope.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([])); - ctx.panelCtrl = ctx.scope.ctrl; - ctx.controller = $controller('InfluxQueryCtrl', {$scope: ctx.scope}); + ctx.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([])); + ctx.panelCtrl = {panel: {}}; + ctx.panelCtrl.refresh = sinon.spy(); + ctx.target = {target: {}}; + ctx.ctrl = $controller(InfluxQueryCtrl, {$scope: ctx.scope}, { + panelCtrl: ctx.panelCtrl, + target: ctx.target, + datasource: ctx.datasource + }); })); - beforeEach(function() { - ctx.scope.target = {}; - ctx.panelCtrl.refresh = sinon.spy(); - }); - describe('init', function() { - beforeEach(function() { - ctx.scope.init(); - }); - it('should init tagSegments', function() { - expect(ctx.scope.tagSegments.length).to.be(1); + expect(ctx.ctrl.tagSegments.length).to.be(1); }); it('should init measurementSegment', function() { - expect(ctx.scope.measurementSegment.value).to.be('select measurement'); + expect(ctx.ctrl.measurementSegment.value).to.be('select measurement'); }); }); describe('when first tag segment is updated', function() { beforeEach(function() { - ctx.scope.init(); - ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button'}, 0); + ctx.ctrl.tagSegmentUpdated({value: 'asd', type: 'plus-button'}, 0); }); it('should update tag key', function() { - expect(ctx.scope.target.tags[0].key).to.be('asd'); - expect(ctx.scope.tagSegments[0].type).to.be('key'); + expect(ctx.ctrl.target.tags[0].key).to.be('asd'); + expect(ctx.ctrl.tagSegments[0].type).to.be('key'); }); it('should add tagSegments', function() { - expect(ctx.scope.tagSegments.length).to.be(3); + expect(ctx.ctrl.tagSegments.length).to.be(3); }); }); describe('when last tag value segment is updated', function() { beforeEach(function() { - ctx.scope.init(); - ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button'}, 0); - ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); + ctx.ctrl.tagSegmentUpdated({value: 'asd', type: 'plus-button'}, 0); + ctx.ctrl.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); }); it('should update tag value', function() { - expect(ctx.scope.target.tags[0].value).to.be('server1'); + expect(ctx.ctrl.target.tags[0].value).to.be('server1'); }); it('should set tag operator', function() { - expect(ctx.scope.target.tags[0].operator).to.be('='); + expect(ctx.ctrl.target.tags[0].operator).to.be('='); }); it('should add plus button for another filter', function() { - expect(ctx.scope.tagSegments[3].fake).to.be(true); + expect(ctx.ctrl.tagSegments[3].fake).to.be(true); }); }); describe('when last tag value segment is updated to regex', function() { beforeEach(function() { - ctx.scope.init(); - ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button'}, 0); - ctx.scope.tagSegmentUpdated({value: '/server.*/', type: 'value'}, 2); + ctx.ctrl.tagSegmentUpdated({value: 'asd', type: 'plus-button'}, 0); + ctx.ctrl.tagSegmentUpdated({value: '/server.*/', type: 'value'}, 2); }); it('should update operator', function() { - expect(ctx.scope.tagSegments[1].value).to.be('=~'); - expect(ctx.scope.target.tags[0].operator).to.be('=~'); + expect(ctx.ctrl.tagSegments[1].value).to.be('=~'); + expect(ctx.ctrl.target.tags[0].operator).to.be('=~'); }); }); describe('when second tag key is added', function() { beforeEach(function() { - ctx.scope.init(); - ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0); - ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); - ctx.scope.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3); + ctx.ctrl.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0); + ctx.ctrl.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); + ctx.ctrl.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3); }); it('should update tag key', function() { - expect(ctx.scope.target.tags[1].key).to.be('key2'); + expect(ctx.ctrl.target.tags[1].key).to.be('key2'); }); it('should add AND segment', function() { - expect(ctx.scope.tagSegments[3].value).to.be('AND'); + expect(ctx.ctrl.tagSegments[3].value).to.be('AND'); }); }); describe('when condition is changed', function() { beforeEach(function() { - ctx.scope.init(); - ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0); - ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); - ctx.scope.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3); - ctx.scope.tagSegmentUpdated({value: 'OR', type: 'condition'}, 3); + ctx.ctrl.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0); + ctx.ctrl.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); + ctx.ctrl.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3); + ctx.ctrl.tagSegmentUpdated({value: 'OR', type: 'condition'}, 3); }); it('should update tag condition', function() { - expect(ctx.scope.target.tags[1].condition).to.be('OR'); + expect(ctx.ctrl.target.tags[1].condition).to.be('OR'); }); it('should update AND segment', function() { - expect(ctx.scope.tagSegments[3].value).to.be('OR'); - expect(ctx.scope.tagSegments.length).to.be(7); + expect(ctx.ctrl.tagSegments[3].value).to.be('OR'); + expect(ctx.ctrl.tagSegments.length).to.be(7); }); }); describe('when deleting first tag filter after value is selected', function() { beforeEach(function() { - ctx.scope.init(); - ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0); - ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); - ctx.scope.tagSegmentUpdated(ctx.scope.removeTagFilterSegment, 0); + ctx.ctrl.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0); + ctx.ctrl.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); + ctx.ctrl.tagSegmentUpdated(ctx.ctrl.removeTagFilterSegment, 0); }); it('should remove tags', function() { - expect(ctx.scope.target.tags.length).to.be(0); + expect(ctx.ctrl.target.tags.length).to.be(0); }); it('should remove all segment after 2 and replace with plus button', function() { - expect(ctx.scope.tagSegments.length).to.be(1); - expect(ctx.scope.tagSegments[0].type).to.be('plus-button'); + expect(ctx.ctrl.tagSegments.length).to.be(1); + expect(ctx.ctrl.tagSegments[0].type).to.be('plus-button'); }); }); describe('when deleting second tag value before second tag value is complete', function() { beforeEach(function() { - ctx.scope.init(); - ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0); - ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); - ctx.scope.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3); - ctx.scope.tagSegmentUpdated(ctx.scope.removeTagFilterSegment, 4); + ctx.ctrl.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0); + ctx.ctrl.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); + ctx.ctrl.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3); + ctx.ctrl.tagSegmentUpdated(ctx.ctrl.removeTagFilterSegment, 4); }); it('should remove all segment after 2 and replace with plus button', function() { - expect(ctx.scope.tagSegments.length).to.be(4); - expect(ctx.scope.tagSegments[3].type).to.be('plus-button'); + expect(ctx.ctrl.tagSegments.length).to.be(4); + expect(ctx.ctrl.tagSegments[3].type).to.be('plus-button'); }); }); describe('when deleting second tag value before second tag value is complete', function() { beforeEach(function() { - ctx.scope.init(); - ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0); - ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); - ctx.scope.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3); - ctx.scope.tagSegmentUpdated(ctx.scope.removeTagFilterSegment, 4); + ctx.ctrl.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0); + ctx.ctrl.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); + ctx.ctrl.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3); + ctx.ctrl.tagSegmentUpdated(ctx.ctrl.removeTagFilterSegment, 4); }); it('should remove all segment after 2 and replace with plus button', function() { - expect(ctx.scope.tagSegments.length).to.be(4); - expect(ctx.scope.tagSegments[3].type).to.be('plus-button'); + expect(ctx.ctrl.tagSegments.length).to.be(4); + expect(ctx.ctrl.tagSegments[3].type).to.be('plus-button'); }); }); describe('when deleting second tag value after second tag filter is complete', function() { beforeEach(function() { - ctx.scope.init(); - ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0); - ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); - ctx.scope.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3); - ctx.scope.tagSegmentUpdated({value: 'value', type: 'value'}, 6); - ctx.scope.tagSegmentUpdated(ctx.scope.removeTagFilterSegment, 4); + ctx.ctrl.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0); + ctx.ctrl.tagSegmentUpdated({value: 'server1', type: 'value'}, 2); + ctx.ctrl.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3); + ctx.ctrl.tagSegmentUpdated({value: 'value', type: 'value'}, 6); + ctx.ctrl.tagSegmentUpdated(ctx.ctrl.removeTagFilterSegment, 4); }); it('should remove all segment after 2 and replace with plus button', function() { - expect(ctx.scope.tagSegments.length).to.be(4); - expect(ctx.scope.tagSegments[3].type).to.be('plus-button'); + expect(ctx.ctrl.tagSegments.length).to.be(4); + expect(ctx.ctrl.tagSegments[3].type).to.be('plus-button'); }); }); - }); From c90619c665daa3420cb8d5c5fa987e8951f110b7 Mon Sep 17 00:00:00 2001 From: Masafumi Yokoyama Date: Wed, 3 Feb 2016 23:10:48 +0900 Subject: [PATCH 14/32] doc: fix a broken link --- docs/sources/guides/basic_concepts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/guides/basic_concepts.md b/docs/sources/guides/basic_concepts.md index 9deda2ee97e..b654554fa48 100644 --- a/docs/sources/guides/basic_concepts.md +++ b/docs/sources/guides/basic_concepts.md @@ -77,7 +77,7 @@ The Query Editor exposes capabilities of your Data Source and allows you to quer Use the Query Editor to build one or more queries (for one or more series) in your time series database. The panel will instantly update allowing you to effectively explore your data in real time and build a perfect query for that particular Panel. -You can utilize [Template variables]((reference/templating/) in the Query Editor within the queries themselves. This provides a powerful way to explore data dynamically based on the Templating variables selected on the Dashboard. +You can utilize [Template variables](/reference/templating/) in the Query Editor within the queries themselves. This provides a powerful way to explore data dynamically based on the Templating variables selected on the Dashboard. Grafana allows you to reference queries in the Query Editor by the row that they’re on. If you add a second query to graph, you can reference the first query simply by typing in #A. This provides an easy and convenient way to build compounded queries. From 0bea6aba63f5b8c4cd575dc5c4907ccc9c9149d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 3 Feb 2016 16:31:07 +0100 Subject: [PATCH 15/32] feat(plugins): migrated elasticsearch to new plugin editor model, also minor fixes --- .../app/core/directives/plugin_component.ts | 10 +- .../datasource/elasticsearch/datasource.d.ts | 4 +- .../datasource/elasticsearch/datasource.js | 4 +- .../datasource/elasticsearch/module.js | 30 ------ .../datasource/elasticsearch/module.ts | 22 ++++ .../elasticsearch/partials/query.editor.html | 101 +++++------------- .../elasticsearch/partials/query.options.html | 6 +- .../datasource/elasticsearch/query_ctrl.js | 46 -------- .../datasource/elasticsearch/query_ctrl.ts | 45 ++++++++ .../elasticsearch/specs/datasource_specs.ts | 4 +- .../elasticsearch/specs/query_ctrl_specs.ts | 29 ----- .../app/plugins/datasource/grafana/module.ts | 5 +- .../grafana/partials/query.editor.html | 61 +---------- .../app/plugins/datasource/influxdb/module.ts | 31 +----- .../influxdb/partials/query.options.html | 14 +-- 15 files changed, 129 insertions(+), 283 deletions(-) delete mode 100644 public/app/plugins/datasource/elasticsearch/module.js create mode 100644 public/app/plugins/datasource/elasticsearch/module.ts delete mode 100644 public/app/plugins/datasource/elasticsearch/query_ctrl.js create mode 100644 public/app/plugins/datasource/elasticsearch/query_ctrl.ts delete mode 100644 public/app/plugins/datasource/elasticsearch/specs/query_ctrl_specs.ts diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts index 065a0be0392..9bfab78074e 100644 --- a/public/app/core/directives/plugin_component.ts +++ b/public/app/core/directives/plugin_component.ts @@ -5,7 +5,7 @@ import _ from 'lodash'; import coreModule from '../core_module'; -function pluginDirectiveLoader($compile, datasourceSrv, $rootScope) { +function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q) { function getPluginComponentDirective(options) { return function() { @@ -83,7 +83,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope) { }); } default: { - $rootScope.appEvent('alert-error', ['Plugin component error', 'could not find component '+ attrs.type]); + return $q.reject({message: "Could not find component type: " + attrs.type }); } } } @@ -106,6 +106,10 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope) { return; } + if (!componentInfo.Component) { + throw {message: 'Failed to find exported plugin component for ' + componentInfo.name}; + } + if (!componentInfo.Component.registered) { var directiveName = attrs.$normalize(componentInfo.name); var directiveFn = getPluginComponentDirective(componentInfo); @@ -121,6 +125,8 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope) { link: function(scope, elem, attrs) { getModule(scope, attrs).then(function (componentInfo) { registerPluginComponent(scope, elem, attrs, componentInfo); + }).catch(err => { + $rootScope.appEvent('alert-error', ['Plugin Error', err.message || err]); }); } }; diff --git a/public/app/plugins/datasource/elasticsearch/datasource.d.ts b/public/app/plugins/datasource/elasticsearch/datasource.d.ts index a50d7ca49cc..3682abfb614 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.d.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.d.ts @@ -1,3 +1,3 @@ -declare var Datasource: any; -export default Datasource; +declare var ElasticDatasource: any; +export {ElasticDatasource}; diff --git a/public/app/plugins/datasource/elasticsearch/datasource.js b/public/app/plugins/datasource/elasticsearch/datasource.js index 0d2fd174c4d..294f666abc7 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.js +++ b/public/app/plugins/datasource/elasticsearch/datasource.js @@ -304,5 +304,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes }; } - return ElasticDatasource; + return { + ElasticDatasource: ElasticDatasource + }; }); diff --git a/public/app/plugins/datasource/elasticsearch/module.js b/public/app/plugins/datasource/elasticsearch/module.js deleted file mode 100644 index d38afe8e936..00000000000 --- a/public/app/plugins/datasource/elasticsearch/module.js +++ /dev/null @@ -1,30 +0,0 @@ -define([ - './datasource', - './edit_view', - './bucket_agg', - './metric_agg', -], -function (ElasticDatasource, editView) { - 'use strict'; - - function metricsQueryEditor() { - return {controller: 'ElasticQueryCtrl', templateUrl: 'public/app/plugins/datasource/elasticsearch/partials/query.editor.html'}; - } - - function metricsQueryOptions() { - return {templateUrl: 'public/app/plugins/datasource/elasticsearch/partials/query.options.html'}; - } - - function annotationsQueryEditor() { - return {templateUrl: 'public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html'}; - } - - return { - Datasource: ElasticDatasource, - configView: editView.default, - annotationsQueryEditor: annotationsQueryEditor, - metricsQueryEditor: metricsQueryEditor, - metricsQueryOptions: metricsQueryOptions, - }; - -}); diff --git a/public/app/plugins/datasource/elasticsearch/module.ts b/public/app/plugins/datasource/elasticsearch/module.ts new file mode 100644 index 00000000000..f5463af0527 --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/module.ts @@ -0,0 +1,22 @@ +import {ElasticDatasource} from './datasource'; +import {ElasticQueryCtrl} from './query_ctrl'; + +class ElasticConfigCtrl { + static templateUrl = 'public/app/plugins/datasource/elasticsearch/partials/config.html'; +} + +class ElasticQueryOptionsCtrl { + static templateUrl = 'public/app/plugins/datasource/elasticsearch/partials/query.options.html'; +} + +class ElasticAnnotationsQueryCtrl { + static templateUrl = 'public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html'; +} + +export { + ElasticDatasource as Datasource, + ElasticQueryCtrl as QueryCtrl, + ElasticConfigCtrl as ConfigCtrl, + ElasticQueryOptionsCtrl as QueryOptionsCtrl, + ElasticAnnotationsQueryCtrl as AnnotationsQueryCtrl, +}; diff --git a/public/app/plugins/datasource/elasticsearch/partials/query.editor.html b/public/app/plugins/datasource/elasticsearch/partials/query.editor.html index bbf5964b220..017f5cf1a42 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/query.editor.html +++ b/public/app/plugins/datasource/elasticsearch/partials/query.editor.html @@ -1,77 +1,32 @@ -
          - - -
            -
          • - {{target.refId}} -
          • -
          • - - - -
          • -
          - -
            -
          • - Query -
          • -
          • - -
          • -
          • - Alias -
          • -
          • - -
          • -
          -
          +
          + +
          -
          -
          - - -
          - -
          - - -
          - +
          + +
          + diff --git a/public/app/plugins/datasource/elasticsearch/partials/query.options.html b/public/app/plugins/datasource/elasticsearch/partials/query.options.html index 628a0a0bf3d..bac22df7136 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/query.options.html +++ b/public/app/plugins/datasource/elasticsearch/partials/query.options.html @@ -8,7 +8,7 @@ Group by time interval
        • -
        • @@ -23,7 +23,7 @@
        • - + alias patterns
        • @@ -34,7 +34,7 @@
          -
          +
          Alias patterns
          • {{term fieldname}} = replaced with value of term group by
          • diff --git a/public/app/plugins/datasource/elasticsearch/query_ctrl.js b/public/app/plugins/datasource/elasticsearch/query_ctrl.js deleted file mode 100644 index 90214dff17c..00000000000 --- a/public/app/plugins/datasource/elasticsearch/query_ctrl.js +++ /dev/null @@ -1,46 +0,0 @@ -define([ - 'angular', -], -function (angular) { - 'use strict'; - - var module = angular.module('grafana.controllers'); - - module.controller('ElasticQueryCtrl', function($scope, $rootScope, $timeout, uiSegmentSrv) { - $scope.esVersion = $scope.datasource.esVersion; - $scope.panelCtrl = $scope.ctrl; - - $scope.init = function() { - var target = $scope.target; - if (!target) { return; } - - $scope.queryUpdated(); - }; - - $scope.getFields = function(type) { - var jsonStr = angular.toJson({find: 'fields', type: type}); - return $scope.datasource.metricFindQuery(jsonStr) - .then(uiSegmentSrv.transformToSegments(false)) - .then(null, $scope.handleQueryError); - }; - - $scope.queryUpdated = function() { - var newJson = angular.toJson($scope.datasource.queryBuilder.build($scope.target), true); - if (newJson !== $scope.oldQueryRaw) { - $scope.rawQueryOld = newJson; - $scope.panelCtrl.refresh(); - } - - $rootScope.appEvent('elastic-query-updated'); - }; - - $scope.handleQueryError = function(err) { - $scope.parserError = err.message || 'Failed to issue metric query'; - return []; - }; - - $scope.init(); - - }); - -}); diff --git a/public/app/plugins/datasource/elasticsearch/query_ctrl.ts b/public/app/plugins/datasource/elasticsearch/query_ctrl.ts new file mode 100644 index 00000000000..fb04f39b8ac --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/query_ctrl.ts @@ -0,0 +1,45 @@ +/// + +import './bucket_agg'; +import './metric_agg'; + +import angular from 'angular'; +import _ from 'lodash'; +import {QueryCtrl} from 'app/features/panel/panel'; + +export class ElasticQueryCtrl extends QueryCtrl { + static templateUrl = 'public/app/plugins/datasource/elasticsearch/partials/query.editor.html'; + + esVersion: any; + rawQueryOld: string; + + /** @ngInject **/ + constructor($scope, $injector, private $rootScope, private $timeout, private uiSegmentSrv) { + super($scope, $injector); + + this.esVersion = this.datasource.esVersion; + this.queryUpdated(); + } + + getFields(type) { + var jsonStr = angular.toJson({find: 'fields', type: type}); + return this.datasource.metricFindQuery(jsonStr) + .then(this.uiSegmentSrv.transformToSegments(false)) + .catch(this.handleQueryError.bind(this)); + } + + queryUpdated() { + var newJson = angular.toJson(this.datasource.queryBuilder.build(this.target), true); + if (newJson !== this.rawQueryOld) { + this.rawQueryOld = newJson; + this.refresh(); + } + + this.$rootScope.appEvent('elastic-query-updated'); + } + + handleQueryError(err) { + this.error = err.message || 'Failed to issue metric query'; + return []; + } +} diff --git a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts index a7e6a642550..133d138be82 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts @@ -3,7 +3,7 @@ import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/co import moment from 'moment'; import angular from 'angular'; import helpers from 'test/specs/helpers'; -import Datasource from "../datasource"; +import {ElasticDatasource} from "../datasource"; describe('ElasticDatasource', function() { var ctx = new helpers.ServiceTestContext(); @@ -21,7 +21,7 @@ describe('ElasticDatasource', function() { function createDatasource(instanceSettings) { instanceSettings.jsonData = instanceSettings.jsonData || {}; - ctx.ds = ctx.$injector.instantiate(Datasource, {instanceSettings: instanceSettings}); + ctx.ds = ctx.$injector.instantiate(ElasticDatasource, {instanceSettings: instanceSettings}); } describe('When testing datasource with index pattern', function() { diff --git a/public/app/plugins/datasource/elasticsearch/specs/query_ctrl_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/query_ctrl_specs.ts deleted file mode 100644 index aceef08a3d9..00000000000 --- a/public/app/plugins/datasource/elasticsearch/specs/query_ctrl_specs.ts +++ /dev/null @@ -1,29 +0,0 @@ -/// -/// -/// - -import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; -import helpers from 'test/specs/helpers'; - -describe('ElasticQueryCtrl', function() { - var ctx = new helpers.ControllerTestContext(); - - beforeEach(angularMocks.module('grafana.controllers')); - beforeEach(angularMocks.module('grafana.services')); - beforeEach(ctx.providePhase()); - beforeEach(ctx.createControllerPhase('ElasticQueryCtrl')); - - beforeEach(function() { - ctx.scope.target = {}; - ctx.scope.$parent = { get_data: sinon.spy() }; - - ctx.scope.datasource = ctx.datasource; - ctx.scope.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([])); - }); - - describe('init', function() { - beforeEach(function() { - ctx.scope.init(); - }); - }); -}); diff --git a/public/app/plugins/datasource/grafana/module.ts b/public/app/plugins/datasource/grafana/module.ts index b9c4997af7d..19233c4f975 100644 --- a/public/app/plugins/datasource/grafana/module.ts +++ b/public/app/plugins/datasource/grafana/module.ts @@ -2,14 +2,15 @@ import angular from 'angular'; import {GrafanaDatasource} from './datasource'; +import {QueryCtrl} from 'app/features/panel/panel'; -class GrafanaMetricsQueryEditor { +class GrafanaQueryCtrl extends QueryCtrl { static templateUrl = 'public/app/plugins/datasource/grafana/partials/query.editor.html'; } export { GrafanaDatasource, GrafanaDatasource as Datasource, - GrafanaMetricsQueryEditor as MetricsQueryEditor, + GrafanaQueryCtrl as QueryCtrl, }; diff --git a/public/app/plugins/datasource/grafana/partials/query.editor.html b/public/app/plugins/datasource/grafana/partials/query.editor.html index fd2953e4be4..3131b4cc346 100644 --- a/public/app/plugins/datasource/grafana/partials/query.editor.html +++ b/public/app/plugins/datasource/grafana/partials/query.editor.html @@ -1,56 +1,5 @@ -
            - - -
              -
            • - {{ctrl.target.refId}} -
            • -
            • - - - -
            • -
            • - Test metric (fake data source) -
            • -
            -
            -
            + +
          • + Test metric (fake data source) +
          • +
            diff --git a/public/app/plugins/datasource/influxdb/module.ts b/public/app/plugins/datasource/influxdb/module.ts index a8acffe6695..fc19413f18d 100644 --- a/public/app/plugins/datasource/influxdb/module.ts +++ b/public/app/plugins/datasource/influxdb/module.ts @@ -21,33 +21,4 @@ export { InfluxAnnotationsQueryCtrl as AnnotationsQueryCtrl, }; -// define([ -// './datasource', -// ], -// function (InfluxDatasource) { -// 'use strict'; -// -// function influxMetricsQueryEditor() { -// return {controller: 'InfluxQueryCtrl', templateUrl: 'public/app/plugins/datasource/influxdb/partials/query.editor.html'}; -// } -// -// function influxMetricsQueryOptions() { -// return {templateUrl: 'public/app/plugins/datasource/influxdb/partials/query.options.html'}; -// } -// -// function influxAnnotationsQueryEditor() { -// return {templateUrl: 'public/app/plugins/datasource/influxdb/partials/annotations.editor.html'}; -// } -// -// function influxConfigView() { -// return {templateUrl: 'public/app/plugins/datasource/influxdb/partials/config.html'}; -// } -// -// return { -// Datasource: InfluxDatasource, -// metricsQueryEditor: influxMetricsQueryEditor, -// metricsQueryOptions: influxMetricsQueryOptions, -// annotationsQueryEditor: influxAnnotationsQueryEditor, -// configView: influxConfigView, -// }; -// }); + diff --git a/public/app/plugins/datasource/influxdb/partials/query.options.html b/public/app/plugins/datasource/influxdb/partials/query.options.html index dd6c7accd28..f0b106b4765 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.options.html +++ b/public/app/plugins/datasource/influxdb/partials/query.options.html @@ -8,7 +8,7 @@ Group by time interval
          • -
          • @@ -24,17 +24,17 @@
          • - + alias patterns
          • - + stacking & and fill
          • - + group by time
          • @@ -46,7 +46,7 @@
            -
            +
            Alias patterns
            • $m = replaced with measurement name
            • @@ -58,7 +58,7 @@
            -
            +
            Stacking and fill
            • When stacking is enabled it important that points align
            • @@ -69,7 +69,7 @@
            -
            +
            Group by time
            • Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana
            • From 2fc8da7a877ec08dbb2c76d68b9db71b985a570b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 3 Feb 2016 17:01:53 +0100 Subject: [PATCH 16/32] feat(plugins): migrated cloudwatch and fixed a bunch of issues with data source edit views --- .../datasource/cloudwatch/datasource.d.ts | 4 +- .../datasource/cloudwatch/datasource.js | 4 +- .../plugins/datasource/cloudwatch/module.js | 27 ------------- .../plugins/datasource/cloudwatch/module.ts | 20 ++++++++++ .../partials/annotations.editor.html | 2 +- .../partials/{edit_view.html => config.html} | 6 +-- .../cloudwatch/partials/query.editor.html | 40 ++----------------- .../datasource/cloudwatch/query_ctrl.js | 27 ------------- .../datasource/cloudwatch/query_ctrl.ts | 17 ++++++++ .../cloudwatch/query_parameter_ctrl.js | 2 +- .../cloudwatch/specs/datasource_specs.ts | 4 +- .../datasource/elasticsearch/config_ctrl.ts | 34 ++++++++++++++++ .../datasource/elasticsearch/edit_view.ts | 39 ------------------ .../datasource/elasticsearch/module.ts | 5 +-- .../partials/annotations.editor.html | 14 +++---- .../partials/{edit_view.html => config.html} | 13 +++--- .../datasource/influxdb/partials/config.html | 9 +++-- public/less/panel.less | 1 - 18 files changed, 106 insertions(+), 162 deletions(-) delete mode 100644 public/app/plugins/datasource/cloudwatch/module.js create mode 100644 public/app/plugins/datasource/cloudwatch/module.ts rename public/app/plugins/datasource/cloudwatch/partials/{edit_view.html => config.html} (76%) delete mode 100644 public/app/plugins/datasource/cloudwatch/query_ctrl.js create mode 100644 public/app/plugins/datasource/cloudwatch/query_ctrl.ts create mode 100644 public/app/plugins/datasource/elasticsearch/config_ctrl.ts delete mode 100644 public/app/plugins/datasource/elasticsearch/edit_view.ts rename public/app/plugins/datasource/elasticsearch/partials/{edit_view.html => config.html} (67%) diff --git a/public/app/plugins/datasource/cloudwatch/datasource.d.ts b/public/app/plugins/datasource/cloudwatch/datasource.d.ts index a50d7ca49cc..afc49d07dea 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.d.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.d.ts @@ -1,3 +1,3 @@ -declare var Datasource: any; -export default Datasource; +declare var CloudWatchDatasource: any; +export {CloudWatchDatasource}; diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index 6f9f24ff867..119c4a83167 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -357,5 +357,7 @@ function (angular, _, moment, dateMath) { } - return CloudWatchDatasource; + return { + CloudWatchDatasource: CloudWatchDatasource + }; }); diff --git a/public/app/plugins/datasource/cloudwatch/module.js b/public/app/plugins/datasource/cloudwatch/module.js deleted file mode 100644 index 6f15457a43e..00000000000 --- a/public/app/plugins/datasource/cloudwatch/module.js +++ /dev/null @@ -1,27 +0,0 @@ -define([ - './datasource', - './query_parameter_ctrl', - './query_ctrl', -], -function (CloudWatchDatasource) { - 'use strict'; - - function metricsQueryEditor() { - return {controller: 'CloudWatchQueryCtrl', templateUrl: 'public/app/plugins/datasource/cloudwatch/partials/query.editor.html'}; - } - - function annotationsQueryEditor() { - return {templateUrl: 'public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html'}; - } - - function configView() { - return {templateUrl: 'public/app/plugins/datasource/cloudwatch/partials/edit_view.html'}; - } - - return { - Datasource: CloudWatchDatasource, - configView: configView, - annotationsQueryEditor: annotationsQueryEditor, - metricsQueryEditor: metricsQueryEditor, - }; -}); diff --git a/public/app/plugins/datasource/cloudwatch/module.ts b/public/app/plugins/datasource/cloudwatch/module.ts new file mode 100644 index 00000000000..674506310ab --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/module.ts @@ -0,0 +1,20 @@ +import './query_parameter_ctrl'; + +import {CloudWatchDatasource} from './datasource'; +import {CloudWatchQueryCtrl} from './query_ctrl'; + +class CloudWatchConfigCtrl { + static templateUrl = 'public/app/plugins/datasource/cloudwatch/partials/config.html'; +} + +class CloudWatchAnnotationsQueryCtrl { + static templateUrl = 'public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html'; +} + +export { + CloudWatchDatasource as Datasource, + CloudWatchQueryCtrl as QueryCtrl, + CloudWatchConfigCtrl as ConfigCtrl, + CloudWatchAnnotationsQueryCtrl as AnnotationsQueryCtrl, +}; + diff --git a/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html b/public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html index 583a578134d..050698f3ff2 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/edit_view.html b/public/app/plugins/datasource/cloudwatch/partials/config.html similarity index 76% rename from public/app/plugins/datasource/cloudwatch/partials/edit_view.html rename to public/app/plugins/datasource/cloudwatch/partials/config.html index fbb641633ac..92a963da7c6 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/edit_view.html +++ b/public/app/plugins/datasource/cloudwatch/partials/config.html @@ -9,7 +9,7 @@ Credentials profile nameCredentials profile name, as specified in ~/.aws/credentials, leave blank for default
            • - +
            @@ -19,12 +19,12 @@
          • Default RegionSpecify the region, such as for US West (Oregon) use ` us-west-2 ` as the region.
          • -
          • - +
          diff --git a/public/app/plugins/datasource/cloudwatch/partials/query.editor.html b/public/app/plugins/datasource/cloudwatch/partials/query.editor.html index fa5e74c33c2..a64635dc397 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/query.editor.html +++ b/public/app/plugins/datasource/cloudwatch/partials/query.editor.html @@ -1,38 +1,4 @@ -
          - + + -
            -
          • - {{target.refId}} -
          • -
          • - - - -
          • -
          - -
          -
          - - + diff --git a/public/app/plugins/datasource/cloudwatch/query_ctrl.js b/public/app/plugins/datasource/cloudwatch/query_ctrl.js deleted file mode 100644 index 927c08b3f2d..00000000000 --- a/public/app/plugins/datasource/cloudwatch/query_ctrl.js +++ /dev/null @@ -1,27 +0,0 @@ -define([ - 'angular', - 'lodash', -], -function (angular, _) { - 'use strict'; - - var module = angular.module('grafana.controllers'); - - module.controller('CloudWatchQueryCtrl', function($scope) { - - $scope.init = function() { - $scope.aliasSyntax = '{{metric}} {{stat}} {{namespace}} {{region}} {{}}'; - }; - - $scope.refreshMetricData = function() { - if (!_.isEqual($scope.oldTarget, $scope.target)) { - $scope.oldTarget = angular.copy($scope.target); - $scope.ctrl.refresh(); - } - }; - - $scope.init(); - - }); - -}); diff --git a/public/app/plugins/datasource/cloudwatch/query_ctrl.ts b/public/app/plugins/datasource/cloudwatch/query_ctrl.ts new file mode 100644 index 00000000000..daacd7fe198 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/query_ctrl.ts @@ -0,0 +1,17 @@ +/// + +import './query_parameter_ctrl'; +import _ from 'lodash'; +import {QueryCtrl} from 'app/features/panel/panel'; + +export class CloudWatchQueryCtrl extends QueryCtrl { + static templateUrl = 'public/app/plugins/datasource/cloudwatch/partials/query.editor.html'; + + aliasSyntax: string; + + /** @ngInject **/ + constructor($scope, $injector) { + super($scope, $injector); + this.aliasSyntax = '{{metric}} {{stat}} {{namespace}} {{region}} {{}}'; + } +} diff --git a/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js b/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js index 3be1f0d5a01..dc027f60aef 100644 --- a/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js +++ b/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js @@ -9,7 +9,7 @@ function (angular, _) { module.directive('cloudwatchQueryParameter', function() { return { - templateUrl: 'app/plugins/datasource/cloudwatch/partials/query.parameter.html', + templateUrl: 'public/app/plugins/datasource/cloudwatch/partials/query.parameter.html', controller: 'CloudWatchQueryParameterCtrl', restrict: 'E', scope: { diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index ed5f9418f8f..cec32420aea 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -3,7 +3,7 @@ import "../datasource"; import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; import moment from 'moment'; import helpers from 'test/specs/helpers'; -import Datasource from "../datasource"; +import {CloudWatchDatasource} from "../datasource"; describe('CloudWatchDatasource', function() { var ctx = new helpers.ServiceTestContext(); @@ -20,7 +20,7 @@ describe('CloudWatchDatasource', function() { ctx.$q = $q; ctx.$httpBackend = $httpBackend; ctx.$rootScope = $rootScope; - ctx.ds = $injector.instantiate(Datasource, {instanceSettings: instanceSettings}); + ctx.ds = $injector.instantiate(CloudWatchDatasource, {instanceSettings: instanceSettings}); })); describe('When performing CloudWatch query', function() { diff --git a/public/app/plugins/datasource/elasticsearch/config_ctrl.ts b/public/app/plugins/datasource/elasticsearch/config_ctrl.ts new file mode 100644 index 00000000000..4c14e1133bf --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/config_ctrl.ts @@ -0,0 +1,34 @@ +/// + +import angular from 'angular'; +import _ from 'lodash'; + +export class ElasticConfigCtrl { + static templateUrl = 'public/app/plugins/datasource/elasticsearch/partials/config.html'; + current: any; + + /** @ngInject */ + constructor($scope) { + this.current.jsonData.timeField = this.current.jsonData.timeField || '@timestamp'; + } + + indexPatternTypes = [ + {name: 'No pattern', value: undefined}, + {name: 'Hourly', value: 'Hourly', example: '[logstash-]YYYY.MM.DD.HH'}, + {name: 'Daily', value: 'Daily', example: '[logstash-]YYYY.MM.DD'}, + {name: 'Weekly', value: 'Weekly', example: '[logstash-]GGGG.WW'}, + {name: 'Monthly', value: 'Monthly', example: '[logstash-]YYYY.MM'}, + {name: 'Yearly', value: 'Yearly', example: '[logstash-]YYYY'}, + ]; + + esVersions = [ + {name: '1.x', value: 1}, + {name: '2.x', value: 2}, + ]; + + indexPatternTypeChanged() { + var def = _.findWhere(this.indexPatternTypes, {value: this.current.jsonData.interval}); + this.current.database = def.example || 'es-index-name'; + } +} + diff --git a/public/app/plugins/datasource/elasticsearch/edit_view.ts b/public/app/plugins/datasource/elasticsearch/edit_view.ts deleted file mode 100644 index c7e17d4fbea..00000000000 --- a/public/app/plugins/datasource/elasticsearch/edit_view.ts +++ /dev/null @@ -1,39 +0,0 @@ -/// - -import angular from 'angular'; -import _ from 'lodash'; - -export class EditViewCtrl { - - /** @ngInject */ - constructor($scope) { - $scope.indexPatternTypes = [ - {name: 'No pattern', value: undefined}, - {name: 'Hourly', value: 'Hourly', example: '[logstash-]YYYY.MM.DD.HH'}, - {name: 'Daily', value: 'Daily', example: '[logstash-]YYYY.MM.DD'}, - {name: 'Weekly', value: 'Weekly', example: '[logstash-]GGGG.WW'}, - {name: 'Monthly', value: 'Monthly', example: '[logstash-]YYYY.MM'}, - {name: 'Yearly', value: 'Yearly', example: '[logstash-]YYYY'}, - ]; - - $scope.esVersions = [ - {name: '1.x', value: 1}, - {name: '2.x', value: 2}, - ]; - - $scope.indexPatternTypeChanged = function() { - var def = _.findWhere($scope.indexPatternTypes, {value: $scope.current.jsonData.interval}); - $scope.current.database = def.example || 'es-index-name'; - }; - } -} - -function editViewDirective() { - return { - templateUrl: 'public/app/plugins/datasource/elasticsearch/partials/edit_view.html', - controller: EditViewCtrl, - }; -}; - - -export default editViewDirective; diff --git a/public/app/plugins/datasource/elasticsearch/module.ts b/public/app/plugins/datasource/elasticsearch/module.ts index f5463af0527..438649017e5 100644 --- a/public/app/plugins/datasource/elasticsearch/module.ts +++ b/public/app/plugins/datasource/elasticsearch/module.ts @@ -1,9 +1,6 @@ import {ElasticDatasource} from './datasource'; import {ElasticQueryCtrl} from './query_ctrl'; - -class ElasticConfigCtrl { - static templateUrl = 'public/app/plugins/datasource/elasticsearch/partials/config.html'; -} +import {ElasticConfigCtrl} from './config_ctrl'; class ElasticQueryOptionsCtrl { static templateUrl = 'public/app/plugins/datasource/elasticsearch/partials/query.options.html'; diff --git a/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html b/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html index 0b7070904de..8f761b67865 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/partials/edit_view.html b/public/app/plugins/datasource/elasticsearch/partials/config.html similarity index 67% rename from public/app/plugins/datasource/elasticsearch/partials/edit_view.html rename to public/app/plugins/datasource/elasticsearch/partials/config.html index bf43012c72e..05df80143e3 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/edit_view.html +++ b/public/app/plugins/datasource/elasticsearch/partials/config.html @@ -1,4 +1,5 @@ - + +

          Elasticsearch details

          @@ -8,13 +9,13 @@ Index name
        • - +
        • Pattern
        • - +
        @@ -25,7 +26,7 @@ Time field name
      • - +
      @@ -36,7 +37,7 @@ Version
    • - +
    @@ -52,7 +53,7 @@ Group by time interval
  • -
  • diff --git a/public/app/plugins/datasource/influxdb/partials/config.html b/public/app/plugins/datasource/influxdb/partials/config.html index 9d1c967c668..a371fc9a7c8 100644 --- a/public/app/plugins/datasource/influxdb/partials/config.html +++ b/public/app/plugins/datasource/influxdb/partials/config.html @@ -1,4 +1,5 @@ - + +

    InfluxDB Details

    @@ -8,7 +9,7 @@ Database
  • - +
  • @@ -19,13 +20,13 @@ User
  • - +
  • Password
  • - +
  • diff --git a/public/less/panel.less b/public/less/panel.less index 0156571a822..19bf93ed49a 100644 --- a/public/less/panel.less +++ b/public/less/panel.less @@ -86,7 +86,6 @@ } .panel-fullscreen { - margin: 5px 20px; .panel-title-container { padding: 8px; } From 0da733de9ced6bfce1f1a530771e2f30aa345f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 3 Feb 2016 18:25:39 +0100 Subject: [PATCH 17/32] feat(plugins): migrated opentsdb plugin --- .../datasource/opentsdb/datasource.d.ts | 4 +- .../plugins/datasource/opentsdb/datasource.js | 13 +- .../app/plugins/datasource/opentsdb/module.js | 23 -- .../app/plugins/datasource/opentsdb/module.ts | 13 ++ .../datasource/opentsdb/partials/config.html | 2 +- .../opentsdb/partials/query.editor.html | 206 +++++++----------- .../plugins/datasource/opentsdb/queryCtrl.js | 127 ----------- .../plugins/datasource/opentsdb/query_ctrl.ts | 128 +++++++++++ .../opentsdb/specs/datasource-specs.ts | 4 +- 9 files changed, 236 insertions(+), 284 deletions(-) delete mode 100644 public/app/plugins/datasource/opentsdb/module.js create mode 100644 public/app/plugins/datasource/opentsdb/module.ts delete mode 100644 public/app/plugins/datasource/opentsdb/queryCtrl.js create mode 100644 public/app/plugins/datasource/opentsdb/query_ctrl.ts diff --git a/public/app/plugins/datasource/opentsdb/datasource.d.ts b/public/app/plugins/datasource/opentsdb/datasource.d.ts index a50d7ca49cc..bbbf1680f27 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.d.ts +++ b/public/app/plugins/datasource/opentsdb/datasource.d.ts @@ -1,3 +1,3 @@ -declare var Datasource: any; -export default Datasource; +declare var OpenTsDatasource: any; +export {OpenTsDatasource}; diff --git a/public/app/plugins/datasource/opentsdb/datasource.js b/public/app/plugins/datasource/opentsdb/datasource.js index 0171595060a..eace5275754 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.js +++ b/public/app/plugins/datasource/opentsdb/datasource.js @@ -3,13 +3,12 @@ define([ 'lodash', 'app/core/utils/datemath', 'moment', - './queryCtrl', ], function (angular, _, dateMath) { 'use strict'; /** @ngInject */ - function OpenTSDBDatasource(instanceSettings, $q, backendSrv, templateSrv) { + function OpenTsDatasource(instanceSettings, $q, backendSrv, templateSrv) { this.type = 'opentsdb'; this.url = instanceSettings.url; this.name = instanceSettings.name; @@ -73,13 +72,13 @@ function (angular, _, dateMath) { url: this.url + '/api/query', data: reqBody }; + if (this.basicAuth || this.withCredentials) { options.withCredentials = true; } + if (this.basicAuth) { - options.headers = { - "Authorization": this.basicAuth - }; + options.headers = {"Authorization": this.basicAuth}; } // In case the backend is 3rd-party hosted and does not suport OPTIONS, urlencoded requests @@ -325,5 +324,7 @@ function (angular, _, dateMath) { } - return OpenTSDBDatasource; + return { + OpenTsDatasource: OpenTsDatasource + }; }); diff --git a/public/app/plugins/datasource/opentsdb/module.js b/public/app/plugins/datasource/opentsdb/module.js deleted file mode 100644 index a85daf37bba..00000000000 --- a/public/app/plugins/datasource/opentsdb/module.js +++ /dev/null @@ -1,23 +0,0 @@ -define([ - './datasource', -], -function (OpenTsDatasource) { - 'use strict'; - - function metricsQueryEditor() { - return { - controller: 'OpenTSDBQueryCtrl', - templateUrl: 'public/app/plugins/datasource/opentsdb/partials/query.editor.html', - }; - } - - function configView() { - return {templateUrl: 'public/app/plugins/datasource/opentsdb/partials/config.html'}; - } - - return { - Datasource: OpenTsDatasource, - metricsQueryEditor: metricsQueryEditor, - configView: configView, - }; -}); diff --git a/public/app/plugins/datasource/opentsdb/module.ts b/public/app/plugins/datasource/opentsdb/module.ts new file mode 100644 index 00000000000..ba7a6ccf316 --- /dev/null +++ b/public/app/plugins/datasource/opentsdb/module.ts @@ -0,0 +1,13 @@ +import {OpenTsDatasource} from './datasource'; +import {OpenTsQueryCtrl} from './query_ctrl'; + +class OpenTsConfigCtrl { + static templateUrl = 'public/app/plugins/datasource/opentsdb/partials/config.html'; +} + +export { + OpenTsDatasource as Datasource, + OpenTsQueryCtrl as QueryCtrl, + OpenTsConfigCtrl as ConfigCtrl, +}; + diff --git a/public/app/plugins/datasource/opentsdb/partials/config.html b/public/app/plugins/datasource/opentsdb/partials/config.html index 9f5259cb2ea..3b7f169a0a8 100644 --- a/public/app/plugins/datasource/opentsdb/partials/config.html +++ b/public/app/plugins/datasource/opentsdb/partials/config.html @@ -1,2 +1,2 @@ - + diff --git a/public/app/plugins/datasource/opentsdb/partials/query.editor.html b/public/app/plugins/datasource/opentsdb/partials/query.editor.html index e27f068032f..0c7c7d25a57 100644 --- a/public/app/plugins/datasource/opentsdb/partials/query.editor.html +++ b/public/app/plugins/datasource/opentsdb/partials/query.editor.html @@ -1,82 +1,42 @@ -
    - + +
  • + Metric +
  • +
  • + + + + + +
  • +
  • + Aggregator +
  • +
  • + + + + +
  • -
      -
    • - {{target.refId}} -
    • -
    • - - - -
    • -
    - - - -
    -
    +
  • + Alias: + Use patterns like $tag_tagname to replace part of the alias for a tag value +
  • +
  • + +
  • +
    @@ -128,35 +88,35 @@
  • Tags
  • -
  • - {{key}} = {{value}} - - - - +
  • + {{key}} = {{value}} + + + +
  • -
  • - +
  • +
  • -
  • +
  • + bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100 + ng-model="ctrl.target.currentTagKey" placeholder="key"> + spellcheck='false' bs-typeahead="ctrl.suggestTagValues" + data-min-length=0 data-items=100 ng-model="ctrl.target.currentTagValue" placeholder="value"> - + add tag - @@ -169,31 +129,31 @@
    diff --git a/public/app/plugins/datasource/opentsdb/queryCtrl.js b/public/app/plugins/datasource/opentsdb/queryCtrl.js deleted file mode 100644 index 04259382390..00000000000 --- a/public/app/plugins/datasource/opentsdb/queryCtrl.js +++ /dev/null @@ -1,127 +0,0 @@ -define([ - 'angular', - 'lodash', - 'app/core/utils/kbn' -], -function (angular, _, kbn) { - 'use strict'; - - var module = angular.module('grafana.controllers'); - - module.controller('OpenTSDBQueryCtrl', function($scope) { - $scope.panelCtrl = $scope.ctrl; - - $scope.init = function() { - $scope.target.errors = validateTarget($scope.target); - $scope.aggregators = ['avg', 'sum', 'min', 'max', 'dev', 'zimsum', 'mimmin', 'mimmax']; - $scope.fillPolicies = ['none', 'nan', 'null', 'zero']; - - if (!$scope.target.aggregator) { - $scope.target.aggregator = 'sum'; - } - - if (!$scope.target.downsampleAggregator) { - $scope.target.downsampleAggregator = 'avg'; - } - - if (!$scope.target.downsampleFillPolicy) { - $scope.target.downsampleFillPolicy = 'none'; - } - - $scope.datasource.getAggregators().then(function(aggs) { - $scope.aggregators = aggs; - }); - }; - - $scope.targetBlur = function() { - $scope.target.errors = validateTarget($scope.target); - - // this does not work so good - if (!_.isEqual($scope.oldTarget, $scope.target) && _.isEmpty($scope.target.errors)) { - $scope.oldTarget = angular.copy($scope.target); - $scope.get_data(); - } - }; - - $scope.getTextValues = function(metricFindResult) { - return _.map(metricFindResult, function(value) { return value.text; }); - }; - - $scope.suggestMetrics = function(query, callback) { - $scope.datasource.metricFindQuery('metrics(' + query + ')') - .then($scope.getTextValues) - .then(callback); - }; - - $scope.suggestTagKeys = function(query, callback) { - $scope.datasource.metricFindQuery('suggest_tagk(' + query + ')') - .then($scope.getTextValues) - .then(callback); - }; - - $scope.suggestTagValues = function(query, callback) { - $scope.datasource.metricFindQuery('suggest_tagv(' + query + ')') - .then($scope.getTextValues) - .then(callback); - }; - - $scope.addTag = function() { - if (!$scope.addTagMode) { - $scope.addTagMode = true; - return; - } - - if (!$scope.target.tags) { - $scope.target.tags = {}; - } - - $scope.target.errors = validateTarget($scope.target); - - if (!$scope.target.errors.tags) { - $scope.target.tags[$scope.target.currentTagKey] = $scope.target.currentTagValue; - $scope.target.currentTagKey = ''; - $scope.target.currentTagValue = ''; - $scope.targetBlur(); - } - - $scope.addTagMode = false; - }; - - $scope.removeTag = function(key) { - delete $scope.target.tags[key]; - $scope.targetBlur(); - }; - - $scope.editTag = function(key, value) { - $scope.removeTag(key); - $scope.target.currentTagKey = key; - $scope.target.currentTagValue = value; - $scope.addTag(); - }; - - function validateTarget(target) { - var errs = {}; - - if (target.shouldDownsample) { - try { - if (target.downsampleInterval) { - kbn.describe_interval(target.downsampleInterval); - } else { - errs.downsampleInterval = "You must supply a downsample interval (e.g. '1m' or '1h')."; - } - } catch(err) { - errs.downsampleInterval = err.message; - } - } - - if (target.tags && _.has(target.tags, target.currentTagKey)) { - errs.tags = "Duplicate tag key '" + target.currentTagKey + "'."; - } - - return errs; - } - - $scope.init(); - }); - -}); diff --git a/public/app/plugins/datasource/opentsdb/query_ctrl.ts b/public/app/plugins/datasource/opentsdb/query_ctrl.ts new file mode 100644 index 00000000000..9ed1aab55ce --- /dev/null +++ b/public/app/plugins/datasource/opentsdb/query_ctrl.ts @@ -0,0 +1,128 @@ +/// + +import _ from 'lodash'; +import kbn from 'app/core/utils/kbn'; +import {QueryCtrl} from 'app/features/panel/panel'; + +export class OpenTsQueryCtrl extends QueryCtrl { + static templateUrl = 'public/app/plugins/datasource/opentsdb/partials/query.editor.html'; + aggregators: any; + fillPolicies: any; + aggregator: any; + downsampleInterval: any; + downsampleAggregator: any; + downsampleFillPolicy: any; + errors: any; + suggestMetrics: any; + suggestTagKeys: any; + suggestTagValues: any; + addTagMode: boolean; + + constructor($scope, $injector) { + super($scope, $injector); + + this.errors = this.validateTarget(); + this.aggregators = ['avg', 'sum', 'min', 'max', 'dev', 'zimsum', 'mimmin', 'mimmax']; + this.fillPolicies = ['none', 'nan', 'null', 'zero']; + + if (!this.target.aggregator) { + this.target.aggregator = 'sum'; + } + + if (!this.target.downsampleAggregator) { + this.target.downsampleAggregator = 'avg'; + } + + if (!this.target.downsampleFillPolicy) { + this.target.downsampleFillPolicy = 'none'; + } + + this.datasource.getAggregators().then(function(aggs) { + this.aggregators = aggs; + }); + + // needs to be defined here as it is called from typeahead + this.suggestMetrics = (query, callback) => { + this.datasource.metricFindQuery('metrics(' + query + ')') + .then(this.getTextValues) + .then(callback); + }; + + this.suggestTagKeys = (query, callback) => { + this.datasource.metricFindQuery('suggest_tagk(' + query + ')') + .then(this.getTextValues) + .then(callback); + }; + + this.suggestTagValues = (query, callback) => { + this.datasource.metricFindQuery('suggest_tagv(' + query + ')') + .then(this.getTextValues) + .then(callback); + }; + } + + targetBlur() { + this.errors = this.validateTarget(); + this.refresh(); + } + + getTextValues(metricFindResult) { + return _.map(metricFindResult, function(value) { return value.text; }); + } + + addTag() { + if (!this.addTagMode) { + this.addTagMode = true; + return; + } + + if (!this.target.tags) { + this.target.tags = {}; + } + + this.errors = this.validateTarget(); + + if (!this.errors.tags) { + this.target.tags[this.target.currentTagKey] = this.target.currentTagValue; + this.target.currentTagKey = ''; + this.target.currentTagValue = ''; + this.targetBlur(); + } + + this.addTagMode = false; + } + + removeTag(key) { + delete this.target.tags[key]; + this.targetBlur(); + } + + editTag(key, value) { + this.removeTag(key); + this.target.currentTagKey = key; + this.target.currentTagValue = value; + this.addTag(); + } + + validateTarget() { + var errs: any = {}; + + if (this.target.shouldDownsample) { + try { + if (this.target.downsampleInterval) { + kbn.describe_interval(this.target.downsampleInterval); + } else { + errs.downsampleInterval = "You must supply a downsample interval (e.g. '1m' or '1h')."; + } + } catch (err) { + errs.downsampleInterval = err.message; + } + } + + if (this.target.tags && _.has(this.target.tags, this.target.currentTagKey)) { + errs.tags = "Duplicate tag key '" + this.target.currentTagKey + "'."; + } + + return errs; + } +} diff --git a/public/app/plugins/datasource/opentsdb/specs/datasource-specs.ts b/public/app/plugins/datasource/opentsdb/specs/datasource-specs.ts index 6f8b8917588..b786a93f14c 100644 --- a/public/app/plugins/datasource/opentsdb/specs/datasource-specs.ts +++ b/public/app/plugins/datasource/opentsdb/specs/datasource-specs.ts @@ -1,6 +1,6 @@ import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; import helpers from 'test/specs/helpers'; -import Datasource from "../datasource"; +import {OpenTsDatasource} from "../datasource"; describe('opentsdb', function() { var ctx = new helpers.ServiceTestContext(); @@ -14,7 +14,7 @@ describe('opentsdb', function() { ctx.$q = $q; ctx.$httpBackend = $httpBackend; ctx.$rootScope = $rootScope; - ctx.ds = $injector.instantiate(Datasource, {instanceSettings: instanceSettings}); + ctx.ds = $injector.instantiate(OpenTsDatasource, {instanceSettings: instanceSettings}); })); describe('When performing metricFindQuery', function() { From 908765e0e777e50c4b84a0075bc1f278c20b5751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 3 Feb 2016 18:49:36 +0100 Subject: [PATCH 18/32] feat(plugins): various fixes entering edit mode after adding a new panel --- public/app/features/dashboard/viewStateSrv.js | 5 +++++ .../app/features/panel/metrics_panel_ctrl.ts | 1 + public/app/features/panel/panel_ctrl.ts | 20 ++++++++----------- public/app/plugins/panel/singlestat/module.ts | 2 ++ public/app/plugins/panel/table/controller.ts | 2 +- public/app/plugins/panel/text/module.ts | 2 ++ 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/public/app/features/dashboard/viewStateSrv.js b/public/app/features/dashboard/viewStateSrv.js index abe1ec3279e..b18610d7c1d 100644 --- a/public/app/features/dashboard/viewStateSrv.js +++ b/public/app/features/dashboard/viewStateSrv.js @@ -103,6 +103,11 @@ function (angular, _, $) { if (!panelScope) { return; } + + if (!panelScope.ctrl.editModeInitiated) { + panelScope.ctrl.initEditMode(); + } + this.enterFullscreen(panelScope); return; } diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index 47f2393c4c0..c56b900b59c 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -41,6 +41,7 @@ class MetricsPanelCtrl extends PanelCtrl { } initEditMode() { + super.initEditMode(); this.addEditorTab('Metrics', 'public/app/partials/metrics.html'); this.addEditorTab('Time range', 'public/app/features/panel/partials/panelTime.html'); this.datasources = this.datasourceSrv.getMetricSources(); diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index fa1b8663f9c..175fba2e5f9 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -63,12 +63,6 @@ export class PanelCtrl { } editPanel() { - if (!this.editModeInitiated) { - this.editorTabs = []; - this.addEditorTab('General', 'public/app/partials/panelgeneral.html'); - this.initEditMode(); - } - this.changeView(true, true); } @@ -77,7 +71,9 @@ export class PanelCtrl { } initEditMode() { - return; + this.editorTabs = []; + this.addEditorTab('General', 'public/app/partials/panelgeneral.html'); + this.editModeInitiated = true; } addEditorTab(title, directiveFn, index?) { @@ -166,12 +162,12 @@ export class PanelCtrl { }); } - sharePanel() { - var shareScope = this.$scope.$new(); - shareScope.panel = this.panel; - shareScope.dashboard = this.dashboard; + sharePanel() { + var shareScope = this.$scope.$new(); + shareScope.panel = this.panel; + shareScope.dashboard = this.dashboard; - this.publishAppEvent('show-modal', { + this.publishAppEvent('show-modal', { src: 'public/app/features/dashboard/partials/shareModal.html', scope: shareScope }); diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index ca892a59daf..e0ea4429b39 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -32,6 +32,8 @@ class SingleStatPanel extends PanelDirective { $panelContainer = elem.parents('.panel-container'); firstRender = false; hookupDrilldownLinkTooltip(); + } else { + return; } } diff --git a/public/app/plugins/panel/table/controller.ts b/public/app/plugins/panel/table/controller.ts index 1fa11d83858..a526a8a8798 100644 --- a/public/app/plugins/panel/table/controller.ts +++ b/public/app/plugins/panel/table/controller.ts @@ -57,7 +57,7 @@ export class TablePanelCtrl extends MetricsPanelCtrl { initEditMode() { super.initEditMode(); - this.addEditorTab('Options', tablePanelEditor, 1); + this.addEditorTab('Options', tablePanelEditor, 2); } getExtendedMenu() { diff --git a/public/app/plugins/panel/text/module.ts b/public/app/plugins/panel/text/module.ts index 3db30d1b43e..08dbb9d388b 100644 --- a/public/app/plugins/panel/text/module.ts +++ b/public/app/plugins/panel/text/module.ts @@ -21,8 +21,10 @@ export class TextPanelCtrl extends PanelCtrl { } initEditMode() { + super.initEditMode(); this.icon = 'fa fa-text-width'; this.addEditorTab('Options', 'public/app/plugins/panel/text/editor.html'); + this.editorTabIndex = 1; } refresh() { From de394311e0253813d4c09c4e4ec9ad5a281753e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 3 Feb 2016 19:10:01 +0100 Subject: [PATCH 19/32] feat(datasources): minor fix for optimized build for the refactored query editors --- public/app/plugins/datasource/influxdb/query_ctrl.ts | 1 + public/app/plugins/datasource/opentsdb/query_ctrl.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.ts b/public/app/plugins/datasource/influxdb/query_ctrl.ts index 8c32ff36b0b..f916adde9a7 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.ts +++ b/public/app/plugins/datasource/influxdb/query_ctrl.ts @@ -23,6 +23,7 @@ export class InfluxQueryCtrl extends QueryCtrl { measurementSegment: any; removeTagFilterSegment: any; + /** @ngInject **/ constructor($scope, $injector, private templateSrv, private $q, private uiSegmentSrv) { super($scope, $injector); diff --git a/public/app/plugins/datasource/opentsdb/query_ctrl.ts b/public/app/plugins/datasource/opentsdb/query_ctrl.ts index 9ed1aab55ce..1f5778666c8 100644 --- a/public/app/plugins/datasource/opentsdb/query_ctrl.ts +++ b/public/app/plugins/datasource/opentsdb/query_ctrl.ts @@ -18,6 +18,7 @@ export class OpenTsQueryCtrl extends QueryCtrl { suggestTagValues: any; addTagMode: boolean; + /** @ngInject **/ constructor($scope, $injector) { super($scope, $injector); From 80e15dd75488e91c55d3c59dbe03ad89d0a07128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 3 Feb 2016 19:41:39 +0100 Subject: [PATCH 20/32] feat(css): minor css tweaks --- public/app/plugins/panel/singlestat/controller.ts | 1 - public/less/search.less | 4 ++-- public/less/sidemenu.less | 3 ++- public/less/variables.dark.less | 4 ++++ public/less/variables.light.less | 6 +++++- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/panel/singlestat/controller.ts b/public/app/plugins/panel/singlestat/controller.ts index 6c258283f6c..78b87e5164d 100644 --- a/public/app/plugins/panel/singlestat/controller.ts +++ b/public/app/plugins/panel/singlestat/controller.ts @@ -50,7 +50,6 @@ export class SingleStatCtrl extends MetricsPanelCtrl { _.defaults(this.panel, panelDefaults); } - initEditMode() { super.initEditMode(); this.icon = "fa fa-dashboard"; diff --git a/public/less/search.less b/public/less/search.less index 543273fb27b..d56ce245b32 100644 --- a/public/less/search.less +++ b/public/less/search.less @@ -5,10 +5,10 @@ z-index: 1000; position: absolute; width: 700px; - box-shadow: 0px 0px 55px 0px black; + box-shadow: @searchShadow; padding: 10px; background-color: @grafanaPanelBackground; - border: 1px solid @grafanaTargetFuncBackground; + border: @grafanaPanelBorder; .label-tag { margin-left: 6px; diff --git a/public/less/sidemenu.less b/public/less/sidemenu.less index 76ac0cc892f..d58f5a654cc 100644 --- a/public/less/sidemenu.less +++ b/public/less/sidemenu.less @@ -12,6 +12,7 @@ z-index: 101; transform: translate3d(0, -100%, 0); visibility: hidden; + box-shadow: @sideMenuShadow; a:focus { text-decoration: none; @@ -191,7 +192,7 @@ .sidemenu-org { border-bottom: @sideMenuBorder; - box-shadow: @sideMenuTopShadow; + //box-shadow: @sideMenuTopShadow; padding: 17px 10px 15px 21px; box-sizing: border-box; cursor: pointer; diff --git a/public/less/variables.dark.less b/public/less/variables.dark.less index b22d3b96992..4a989af8c2e 100644 --- a/public/less/variables.dark.less +++ b/public/less/variables.dark.less @@ -163,6 +163,10 @@ @sideMenuBorder: 1px solid @bodyBackground; @sideMenuBackground: @grayDark; @sideMenuBackgroundHighlight: lighten(@grayDark, 4%); +@sideMenuShadow: 0 0 35px 0 @bodyBackground; + +// Search +@searchShadow: 0 0 35px 0 @bodyBackground; // Dropdowns // ------------------------- diff --git a/public/less/variables.light.less b/public/less/variables.light.less index 81d0c81528a..22b661c6adf 100644 --- a/public/less/variables.light.less +++ b/public/less/variables.light.less @@ -177,6 +177,10 @@ @sideMenuBorder: 1px solid @grafanaTargetBorder; @sideMenuBackground: @grafanaPanelBackground; @sideMenuBackgroundHighlight: darken(@sideMenuBackground, 4%); +@sideMenuShadow: 0 5px 30px 0 lighten(@grayLight, 30%); + +// search +@searchShadow: 0 5px 30px 0 lighten(@grayLight, 30%); // Dropdowns // ------------------------- @@ -254,7 +258,7 @@ @navbarLinkColorActive: #555; @navbarLinkBackgroundHover: transparent; @navbarLinkBackgroundActive: darken(@navbarBackground, 6.5%); -@navbarDropdownShadow: inset 0px 4px 10px -4px darken(@bodyBackground, 20%); +@navbarDropdownShadow: inset 0px 4px 7px -4px darken(@bodyBackground, 20%); @navbarBrandColor: @navbarLinkColor; From 37ff432f9db716930f9247c0c71286eac053c002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 4 Feb 2016 09:48:17 +0100 Subject: [PATCH 21/32] fix(influxdb): fix for influxdb when using format as table and having group by time, fixes #2928 --- CHANGELOG.md | 3 ++- .../app/plugins/datasource/influxdb/influx_series.js | 8 ++++++-- .../datasource/influxdb/specs/influx_series_specs.ts | 10 +++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 225e1db96ac..09e009d0c75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ * **InfluxDB**: Support for policy selection in query editor, closes [#2018](https://github.com/grafana/grafana/issues/2018) ### Breaking changes -* **Plugin API**: Both datasource and panel plugin api (and plugin.json schema) have been updated, requiring a minor update to plugins. See [plugin api](https://github.com/grafana/grafana/blob/master/public/app/plugins/plugin_api.md) for more info. +* **Plugin API**: Both datasource and panel plugin api (and plugin.json schema) have been updated, requiring an update to plugins. See [plugin api](https://github.com/grafana/grafana/blob/master/public/app/plugins/plugin_api.md) for more info. * **InfluxDB 0.8.x** The data source for the old version of influxdb (0.8.x) is no longer included in default builds, but can easily be installed via improved plugin system, closes [#3523](https://github.com/grafana/grafana/issues/3523) * **KairosDB** The data source is no longer included in default builds, but can easily be installed via improved plugin system, closes [#3524](https://github.com/grafana/grafana/issues/3524) @@ -18,6 +18,7 @@ ### Bug fixes * **Playlist**: Fix for memory leak when running a playlist, closes [#3794](https://github.com/grafana/grafana/pull/3794) +* **InfluxDB**: Fix for InfluxDB and table panel when using Format As Table and having group by time, fixes [#3928](https://github.com/grafana/grafana/pull/3928) # 2.6.1 (unrelased, 2.6.x branch) diff --git a/public/app/plugins/datasource/influxdb/influx_series.js b/public/app/plugins/datasource/influxdb/influx_series.js index 86f018ee11d..19e20390fd2 100644 --- a/public/app/plugins/datasource/influxdb/influx_series.js +++ b/public/app/plugins/datasource/influxdb/influx_series.js @@ -133,14 +133,18 @@ function (_, TableModel) { if (series.values) { for (i = 0; i < series.values.length; i++) { var values = series.values[i]; + var reordered = [values[0]]; if (series.tags) { for (var key in series.tags) { if (series.tags.hasOwnProperty(key)) { - values.splice(1, 0, series.tags[key]); + reordered.push(series.tags[key]); } } } - table.rows.push(values); + for (j = 1; j < values.length; j++) { + reordered.push(values[j]); + } + table.rows.push(reordered); } } }); diff --git a/public/app/plugins/datasource/influxdb/specs/influx_series_specs.ts b/public/app/plugins/datasource/influxdb/specs/influx_series_specs.ts index a99c1f77dc9..c60c45aa13c 100644 --- a/public/app/plugins/datasource/influxdb/specs/influx_series_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/influx_series_specs.ts @@ -189,9 +189,9 @@ describe('when generating timeseries from influxdb response', function() { series: [ { name: 'app.prod.server1.count', - tags: {}, - columns: ['time', 'datacenter', 'value'], - values: [[1431946625000, 'America', 10], [1431946626000, 'EU', 12]] + tags: {datacenter: 'Africa', server: 'server2'}, + columns: ['time', 'value2', 'value'], + values: [[1431946625000, 23, 10], [1431946626000, 25, 12]] } ] }; @@ -201,8 +201,8 @@ describe('when generating timeseries from influxdb response', function() { var table = series.getTable(); expect(table.type).to.be('table'); - expect(table.columns.length).to.be(3); - expect(table.rows[0]).to.eql([1431946625000, 'America', 10]); + expect(table.columns.length).to.be(5); + expect(table.rows[0]).to.eql([1431946625000, 'Africa', 'server2', 23, 10]); }); }); From f4ad673b6d85314822f7a1409b35be3fad61b798 Mon Sep 17 00:00:00 2001 From: Ivan Babrou Date: Sun, 31 Jan 2016 17:31:37 +0000 Subject: [PATCH 22/32] Show relevant tag name suggestions for OpenTSDB, closes #3610 --- .../plugins/datasource/opentsdb/datasource.js | 21 +++++++++++++++++-- .../plugins/datasource/opentsdb/query_ctrl.ts | 4 +--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/datasource/opentsdb/datasource.js b/public/app/plugins/datasource/opentsdb/datasource.js index eace5275754..c80d58a2ac7 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.js +++ b/public/app/plugins/datasource/opentsdb/datasource.js @@ -15,6 +15,7 @@ function (angular, _, dateMath) { this.withCredentials = instanceSettings.withCredentials; this.basicAuth = instanceSettings.basicAuth; this.supportMetrics = true; + this.tagKeys = {}; // Called once per panel (graph) this.query = function(options) { @@ -50,10 +51,13 @@ function (angular, _, dateMath) { if (index === -1) { index = 0; } + + this._saveTagKeys(metricData); + return transformMetricData(metricData, groupByTags, options.targets[index], options); - }); + }.bind(this)); return { data: result }; - }); + }.bind(this)); }; this.performTimeSeriesQuery = function(queries, start, end) { @@ -87,6 +91,19 @@ function (angular, _, dateMath) { return backendSrv.datasourceRequest(options); }; + this.suggestTagKeys = function(metric) { + return $q.when(this.tagKeys[metric] || []); + }; + + this._saveTagKeys = function(metricData) { + var tagKeys = Object.keys(metricData.tags); + _.each(metricData.aggregateTags, function(tag) { + tagKeys.push(tag); + }); + + this.tagKeys[metricData.metric] = tagKeys; + }; + this._performSuggestQuery = function(query, type) { return this._get('/api/suggest', {type: type, q: query, max: 1000}).then(function(result) { return result.data; diff --git a/public/app/plugins/datasource/opentsdb/query_ctrl.ts b/public/app/plugins/datasource/opentsdb/query_ctrl.ts index 1f5778666c8..69590b714ca 100644 --- a/public/app/plugins/datasource/opentsdb/query_ctrl.ts +++ b/public/app/plugins/datasource/opentsdb/query_ctrl.ts @@ -50,9 +50,7 @@ export class OpenTsQueryCtrl extends QueryCtrl { }; this.suggestTagKeys = (query, callback) => { - this.datasource.metricFindQuery('suggest_tagk(' + query + ')') - .then(this.getTextValues) - .then(callback); + this.datasource.suggestTagKeys(this.target.metric).then(callback); }; this.suggestTagValues = (query, callback) => { From 501f21b16c506b8d0ed36b4fee3489fda8b0e96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 4 Feb 2016 13:58:43 +0100 Subject: [PATCH 23/32] fix(dashlist): fix for entering dashboard list edit mode --- public/app/features/panel/panel_loader.ts | 2 +- public/app/plugins/panel/dashlist/module.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/public/app/features/panel/panel_loader.ts b/public/app/features/panel/panel_loader.ts index 58d0fe47c73..0608c37f9fd 100644 --- a/public/app/features/panel/panel_loader.ts +++ b/public/app/features/panel/panel_loader.ts @@ -8,7 +8,7 @@ import {UnknownPanel} from '../../plugins/panel/unknown/module'; var directiveModule = angular.module('grafana.directives'); /** @ngInject */ -function panelLoader($compile, dynamicDirectiveSrv, $http, $q, $injector, $templateCache) { +function panelLoader($compile, $http, $q, $injector, $templateCache) { return { restrict: 'E', scope: { diff --git a/public/app/plugins/panel/dashlist/module.ts b/public/app/plugins/panel/dashlist/module.ts index 5922902a34a..8974e90a3f0 100644 --- a/public/app/plugins/panel/dashlist/module.ts +++ b/public/app/plugins/panel/dashlist/module.ts @@ -28,6 +28,7 @@ class DashListCtrl extends PanelCtrl { } initEditMode() { + super.initEditMode(); this.modes = ['starred', 'search']; this.icon = "fa fa-star"; this.addEditorTab('Options', () => { From 2a8b96b680022a8ef549f58d714146da45c128f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 4 Feb 2016 14:36:19 +0100 Subject: [PATCH 24/32] feat(plugins): last refactoring of how panels are implemented, now the same way as plugin editors --- .../app/core/directives/plugin_component.ts | 64 +++- public/app/partials/dashboard.html | 4 +- public/app/plugins/panel/dashlist/module.ts | 7 +- public/app/plugins/panel/graph/graph_ctrl.ts | 295 ----------------- public/app/plugins/panel/graph/module.ts | 304 +++++++++++++++++- .../panel/graph/specs/graph_ctrl_specs.ts | 2 +- public/app/plugins/panel/text/module.ts | 9 +- 7 files changed, 366 insertions(+), 319 deletions(-) delete mode 100644 public/app/plugins/panel/graph/graph_ctrl.ts diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts index 9bfab78074e..020bcb08f0a 100644 --- a/public/app/core/directives/plugin_component.ts +++ b/public/app/core/directives/plugin_component.ts @@ -3,14 +3,30 @@ import angular from 'angular'; import _ from 'lodash'; -import coreModule from '../core_module'; +import config from 'app/core/config'; +import coreModule from 'app/core/core_module'; -function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q) { +/** @ngInject */ +function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $templateCache) { + + function getTemplate(component) { + if (component.template) { + return $q.when(component.template); + } + var cached = $templateCache.get(component.templateUrl); + if (cached) { + return $q.when(cached); + } + return $http.get(component.templateUrl).then(res => { + return res.data; + }); + } function getPluginComponentDirective(options) { return function() { return { templateUrl: options.Component.templateUrl, + template: options.Component.template, restrict: 'E', controller: options.Component, controllerAs: 'ctrl', @@ -20,11 +36,50 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q) { if (ctrl.link) { ctrl.link(scope, elem, attrs, ctrl); } + if (ctrl.init) { + ctrl.init(); + } } }; }; } + function loadPanelComponentInfo(scope, attrs) { + var panelElemName = 'panel-' + scope.panel.type; + let panelInfo = config.panels[scope.panel.type]; + if (!panelInfo) { + // unknown + } + + return System.import(panelInfo.module).then(function(panelModule): any { + var PanelCtrl = panelModule.PanelCtrl; + var componentInfo = { + name: 'panel-plugin-' + panelInfo.id, + bindings: {dashboard: "=", panel: "=", row: "="}, + attrs: {dashboard: "dashboard", panel: "panel", row: "row"}, + Component: PanelCtrl, + }; + + if (!PanelCtrl || PanelCtrl.registered) { + return componentInfo; + }; + + if (PanelCtrl.templatePromise) { + return PanelCtrl.templatePromise.then(res => { + return componentInfo; + }); + } + + PanelCtrl.templatePromise = getTemplate(PanelCtrl).then(template => { + PanelCtrl.templateUrl = null; + PanelCtrl.template = `${template}`; + return componentInfo; + }); + + return PanelCtrl.templatePromise; + }); + } + function getModule(scope, attrs) { switch (attrs.type) { // QueryCtrl @@ -82,6 +137,10 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q) { }; }); } + // Panel + case 'panel': { + return loadPanelComponentInfo(scope, attrs); + } default: { return $q.reject({message: "Could not find component type: " + attrs.type }); } @@ -127,6 +186,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q) { registerPluginComponent(scope, elem, attrs, componentInfo); }).catch(err => { $rootScope.appEvent('alert-error', ['Plugin Error', err.message || err]); + console.log('Plugin componnet error', err); }); } }; diff --git a/public/app/partials/dashboard.html b/public/app/partials/dashboard.html index 97db5617a1c..712cff0e849 100644 --- a/public/app/partials/dashboard.html +++ b/public/app/partials/dashboard.html @@ -81,8 +81,8 @@
    - - + +
    diff --git a/public/app/plugins/panel/dashlist/module.ts b/public/app/plugins/panel/dashlist/module.ts index 8974e90a3f0..5b1c4c89f66 100644 --- a/public/app/plugins/panel/dashlist/module.ts +++ b/public/app/plugins/panel/dashlist/module.ts @@ -13,6 +13,8 @@ var panelDefaults = { }; class DashListCtrl extends PanelCtrl { + static templateUrl = 'public/app/plugins/panel/dashlist/module.html'; + dashList: any[]; modes: any[]; @@ -55,10 +57,9 @@ class DashListCtrl extends PanelCtrl { class DashListPanel extends PanelDirective { controller = DashListCtrl; - templateUrl = 'public/app/plugins/panel/dashlist/module.html'; } export { - DashListCtrl, - DashListPanel as Panel + DashListCtrl as DashListCtrl, + DashListCtrl as PanelCtrl, } diff --git a/public/app/plugins/panel/graph/graph_ctrl.ts b/public/app/plugins/panel/graph/graph_ctrl.ts deleted file mode 100644 index 1a3c4d4edd3..00000000000 --- a/public/app/plugins/panel/graph/graph_ctrl.ts +++ /dev/null @@ -1,295 +0,0 @@ -/// - -import moment from 'moment'; -import kbn from 'app/core/utils/kbn'; -import _ from 'lodash'; -import TimeSeries from '../../../core/time_series2'; -import * as fileExport from '../../../core/utils/file_export'; -import {MetricsPanelCtrl} from '../../../features/panel/panel'; - -var panelDefaults = { - // datasource name, null = default datasource - datasource: null, - // sets client side (flot) or native graphite png renderer (png) - renderer: 'flot', - // Show/hide the x-axis - 'x-axis' : true, - // Show/hide y-axis - 'y-axis' : true, - // y axis formats, [left axis,right axis] - y_formats : ['short', 'short'], - // grid options - grid : { - leftLogBase: 1, - leftMax: null, - rightMax: null, - leftMin: null, - rightMin: null, - rightLogBase: 1, - threshold1: null, - threshold2: null, - threshold1Color: 'rgba(216, 200, 27, 0.27)', - threshold2Color: 'rgba(234, 112, 112, 0.22)' - }, - // show/hide lines - lines : true, - // fill factor - fill : 1, - // line width in pixels - linewidth : 2, - // show hide points - points : false, - // point radius in pixels - pointradius : 5, - // show hide bars - bars : false, - // enable/disable stacking - stack : false, - // stack percentage mode - percentage : false, - // legend options - legend: { - show: true, // disable/enable legend - values: false, // disable/enable legend values - min: false, - max: false, - current: false, - total: false, - avg: false - }, - // how null points should be handled - nullPointMode : 'connected', - // staircase line mode - steppedLine: false, - // tooltip options - tooltip : { - value_type: 'cumulative', - shared: true, - }, - // time overrides - timeFrom: null, - timeShift: null, - // metric queries - targets: [{}], - // series color overrides - aliasColors: {}, - // other style overrides - seriesOverrides: [], -}; - -class GraphCtrl extends MetricsPanelCtrl { - hiddenSeries: any = {}; - seriesList: any = []; - logScales: any; - unitFormats: any; - annotationsPromise: any; - datapointsCount: number; - datapointsOutside: boolean; - datapointsWarning: boolean; - colors: any = []; - - /** @ngInject */ - constructor($scope, $injector, private annotationsSrv) { - super($scope, $injector); - - _.defaults(this.panel, panelDefaults); - _.defaults(this.panel.tooltip, panelDefaults.tooltip); - _.defaults(this.panel.grid, panelDefaults.grid); - _.defaults(this.panel.legend, panelDefaults.legend); - - this.colors = $scope.$root.colors; - } - - initEditMode() { - super.initEditMode(); - - this.icon = "fa fa-bar-chart"; - this.addEditorTab('Axes & Grid', 'public/app/plugins/panel/graph/axisEditor.html', 2); - this.addEditorTab('Display Styles', 'public/app/plugins/panel/graph/styleEditor.html', 3); - - this.logScales = { - 'linear': 1, - 'log (base 2)': 2, - 'log (base 10)': 10, - 'log (base 32)': 32, - 'log (base 1024)': 1024 - }; - this.unitFormats = kbn.getUnitFormats(); - } - - getExtendedMenu() { - var menu = super.getExtendedMenu(); - menu.push({text: 'Export CSV', click: 'ctrl.exportCsv()'}); - menu.push({text: 'Toggle legend', click: 'ctrl.toggleLegend()'}); - return menu; - } - - setUnitFormat(axis, subItem) { - this.panel.y_formats[axis] = subItem.value; - this.render(); - } - - refreshData(datasource) { - this.annotationsPromise = this.annotationsSrv.getAnnotations(this.dashboard); - - return this.issueQueries(datasource) - .then(res => this.dataHandler(res)) - .catch(err => { - this.seriesList = []; - this.render([]); - throw err; - }); - } - - zoomOut(evt) { - this.publishAppEvent('zoom-out', evt); - } - - loadSnapshot(snapshotData) { - this.updateTimeRange(); - this.annotationsPromise = this.annotationsSrv.getAnnotations(this.dashboard); - this.dataHandler(snapshotData); - } - - dataHandler(results) { - // png renderer returns just a url - if (_.isString(results)) { - this.render(results); - return; - } - - this.datapointsWarning = false; - this.datapointsCount = 0; - this.datapointsOutside = false; - this.seriesList = _.map(results.data, (series, i) => this.seriesHandler(series, i)); - this.datapointsWarning = this.datapointsCount === 0 || this.datapointsOutside; - - this.annotationsPromise.then(annotations => { - this.loading = false; - this.seriesList.annotations = annotations; - this.render(this.seriesList); - }, () => { - this.loading = false; - this.render(this.seriesList); - }); - }; - - seriesHandler(seriesData, index) { - var datapoints = seriesData.datapoints; - var alias = seriesData.target; - var colorIndex = index % this.colors.length; - var color = this.panel.aliasColors[alias] || this.colors[colorIndex]; - - var series = new TimeSeries({ - datapoints: datapoints, - alias: alias, - color: color, - }); - - if (datapoints && datapoints.length > 0) { - var last = moment.utc(datapoints[datapoints.length - 1][1]); - var from = moment.utc(this.range.from); - if (last - from < -10000) { - this.datapointsOutside = true; - } - - this.datapointsCount += datapoints.length; - } - - return series; - } - - render(data?: any) { - this.broadcastRender(data); - } - - changeSeriesColor(series, color) { - series.color = color; - this.panel.aliasColors[series.alias] = series.color; - this.render(); - } - - toggleSeries(serie, event) { - if (event.ctrlKey || event.metaKey || event.shiftKey) { - if (this.hiddenSeries[serie.alias]) { - delete this.hiddenSeries[serie.alias]; - } else { - this.hiddenSeries[serie.alias] = true; - } - } else { - this.toggleSeriesExclusiveMode(serie); - } - - this.render(); - } - - toggleSeriesExclusiveMode (serie) { - var hidden = this.hiddenSeries; - - if (hidden[serie.alias]) { - delete hidden[serie.alias]; - } - - // check if every other series is hidden - var alreadyExclusive = _.every(this.seriesList, value => { - if (value.alias === serie.alias) { - return true; - } - - return hidden[value.alias]; - }); - - if (alreadyExclusive) { - // remove all hidden series - _.each(this.seriesList, value => { - delete this.hiddenSeries[value.alias]; - }); - } else { - // hide all but this serie - _.each(this.seriesList, value => { - if (value.alias === serie.alias) { - return; - } - - this.hiddenSeries[value.alias] = true; - }); - } - } - - toggleYAxis(info) { - var override = _.findWhere(this.panel.seriesOverrides, { alias: info.alias }); - if (!override) { - override = { alias: info.alias }; - this.panel.seriesOverrides.push(override); - } - override.yaxis = info.yaxis === 2 ? 1 : 2; - this.render(); - }; - - addSeriesOverride(override) { - this.panel.seriesOverrides.push(override || {}); - } - - removeSeriesOverride(override) { - this.panel.seriesOverrides = _.without(this.panel.seriesOverrides, override); - this.render(); - } - - // Called from panel menu - toggleLegend() { - this.panel.legend.show = !this.panel.legend.show; - this.refresh(); - } - - legendValuesOptionChanged() { - var legend = this.panel.legend; - legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total; - this.render(); - } - - exportCsv() { - fileExport.exportSeriesListToCsv(this.seriesList); - } -} - -export {GraphCtrl} diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 72e273e40b1..f2ca91efa2b 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -1,17 +1,301 @@ - -import {PanelDirective} from '../../../features/panel/panel'; -import {GraphCtrl} from './graph_ctrl'; +/// import './graph'; import './legend'; import './seriesOverridesCtrl'; -class GraphPanel extends PanelDirective { - controller = GraphCtrl; - templateUrl = 'public/app/plugins/panel/graph/module.html'; +import moment from 'moment'; +import kbn from 'app/core/utils/kbn'; +import _ from 'lodash'; +import TimeSeries from '../../../core/time_series2'; +import * as fileExport from '../../../core/utils/file_export'; +import {MetricsPanelCtrl} from '../../../features/panel/panel'; + +var panelDefaults = { + // datasource name, null = default datasource + datasource: null, + // sets client side (flot) or native graphite png renderer (png) + renderer: 'flot', + // Show/hide the x-axis + 'x-axis' : true, + // Show/hide y-axis + 'y-axis' : true, + // y axis formats, [left axis,right axis] + y_formats : ['short', 'short'], + // grid options + grid : { + leftLogBase: 1, + leftMax: null, + rightMax: null, + leftMin: null, + rightMin: null, + rightLogBase: 1, + threshold1: null, + threshold2: null, + threshold1Color: 'rgba(216, 200, 27, 0.27)', + threshold2Color: 'rgba(234, 112, 112, 0.22)' + }, + // show/hide lines + lines : true, + // fill factor + fill : 1, + // line width in pixels + linewidth : 2, + // show hide points + points : false, + // point radius in pixels + pointradius : 5, + // show hide bars + bars : false, + // enable/disable stacking + stack : false, + // stack percentage mode + percentage : false, + // legend options + legend: { + show: true, // disable/enable legend + values: false, // disable/enable legend values + min: false, + max: false, + current: false, + total: false, + avg: false + }, + // how null points should be handled + nullPointMode : 'connected', + // staircase line mode + steppedLine: false, + // tooltip options + tooltip : { + value_type: 'cumulative', + shared: true, + }, + // time overrides + timeFrom: null, + timeShift: null, + // metric queries + targets: [{}], + // series color overrides + aliasColors: {}, + // other style overrides + seriesOverrides: [], +}; + +class GraphCtrl extends MetricsPanelCtrl { + static templateUrl = 'public/app/plugins/panel/graph/module.html'; + + hiddenSeries: any = {}; + seriesList: any = []; + logScales: any; + unitFormats: any; + annotationsPromise: any; + datapointsCount: number; + datapointsOutside: boolean; + datapointsWarning: boolean; + colors: any = []; + + /** @ngInject */ + constructor($scope, $injector, private annotationsSrv) { + super($scope, $injector); + + _.defaults(this.panel, panelDefaults); + _.defaults(this.panel.tooltip, panelDefaults.tooltip); + _.defaults(this.panel.grid, panelDefaults.grid); + _.defaults(this.panel.legend, panelDefaults.legend); + + this.colors = $scope.$root.colors; + } + + initEditMode() { + super.initEditMode(); + + this.icon = "fa fa-bar-chart"; + this.addEditorTab('Axes & Grid', 'public/app/plugins/panel/graph/axisEditor.html', 2); + this.addEditorTab('Display Styles', 'public/app/plugins/panel/graph/styleEditor.html', 3); + + this.logScales = { + 'linear': 1, + 'log (base 2)': 2, + 'log (base 10)': 10, + 'log (base 32)': 32, + 'log (base 1024)': 1024 + }; + this.unitFormats = kbn.getUnitFormats(); + } + + getExtendedMenu() { + var menu = super.getExtendedMenu(); + menu.push({text: 'Export CSV', click: 'ctrl.exportCsv()'}); + menu.push({text: 'Toggle legend', click: 'ctrl.toggleLegend()'}); + return menu; + } + + setUnitFormat(axis, subItem) { + this.panel.y_formats[axis] = subItem.value; + this.render(); + } + + refreshData(datasource) { + this.annotationsPromise = this.annotationsSrv.getAnnotations(this.dashboard); + + return this.issueQueries(datasource) + .then(res => this.dataHandler(res)) + .catch(err => { + this.seriesList = []; + this.render([]); + throw err; + }); + } + + zoomOut(evt) { + this.publishAppEvent('zoom-out', evt); + } + + loadSnapshot(snapshotData) { + this.updateTimeRange(); + this.annotationsPromise = this.annotationsSrv.getAnnotations(this.dashboard); + this.dataHandler(snapshotData); + } + + dataHandler(results) { + // png renderer returns just a url + if (_.isString(results)) { + this.render(results); + return; + } + + this.datapointsWarning = false; + this.datapointsCount = 0; + this.datapointsOutside = false; + this.seriesList = _.map(results.data, (series, i) => this.seriesHandler(series, i)); + this.datapointsWarning = this.datapointsCount === 0 || this.datapointsOutside; + + this.annotationsPromise.then(annotations => { + this.loading = false; + this.seriesList.annotations = annotations; + this.render(this.seriesList); + }, () => { + this.loading = false; + this.render(this.seriesList); + }); + }; + + seriesHandler(seriesData, index) { + var datapoints = seriesData.datapoints; + var alias = seriesData.target; + var colorIndex = index % this.colors.length; + var color = this.panel.aliasColors[alias] || this.colors[colorIndex]; + + var series = new TimeSeries({ + datapoints: datapoints, + alias: alias, + color: color, + }); + + if (datapoints && datapoints.length > 0) { + var last = moment.utc(datapoints[datapoints.length - 1][1]); + var from = moment.utc(this.range.from); + if (last - from < -10000) { + this.datapointsOutside = true; + } + + this.datapointsCount += datapoints.length; + } + + return series; + } + + render(data?: any) { + this.broadcastRender(data); + } + + changeSeriesColor(series, color) { + series.color = color; + this.panel.aliasColors[series.alias] = series.color; + this.render(); + } + + toggleSeries(serie, event) { + if (event.ctrlKey || event.metaKey || event.shiftKey) { + if (this.hiddenSeries[serie.alias]) { + delete this.hiddenSeries[serie.alias]; + } else { + this.hiddenSeries[serie.alias] = true; + } + } else { + this.toggleSeriesExclusiveMode(serie); + } + + this.render(); + } + + toggleSeriesExclusiveMode (serie) { + var hidden = this.hiddenSeries; + + if (hidden[serie.alias]) { + delete hidden[serie.alias]; + } + + // check if every other series is hidden + var alreadyExclusive = _.every(this.seriesList, value => { + if (value.alias === serie.alias) { + return true; + } + + return hidden[value.alias]; + }); + + if (alreadyExclusive) { + // remove all hidden series + _.each(this.seriesList, value => { + delete this.hiddenSeries[value.alias]; + }); + } else { + // hide all but this serie + _.each(this.seriesList, value => { + if (value.alias === serie.alias) { + return; + } + + this.hiddenSeries[value.alias] = true; + }); + } + } + + toggleYAxis(info) { + var override = _.findWhere(this.panel.seriesOverrides, { alias: info.alias }); + if (!override) { + override = { alias: info.alias }; + this.panel.seriesOverrides.push(override); + } + override.yaxis = info.yaxis === 2 ? 1 : 2; + this.render(); + }; + + addSeriesOverride(override) { + this.panel.seriesOverrides.push(override || {}); + } + + removeSeriesOverride(override) { + this.panel.seriesOverrides = _.without(this.panel.seriesOverrides, override); + this.render(); + } + + // Called from panel menu + toggleLegend() { + this.panel.legend.show = !this.panel.legend.show; + this.refresh(); + } + + legendValuesOptionChanged() { + var legend = this.panel.legend; + legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total; + this.render(); + } + + exportCsv() { + fileExport.exportSeriesListToCsv(this.seriesList); + } } -export { - GraphPanel, - GraphPanel as Panel -} +export {GraphCtrl, GraphCtrl as PanelCtrl} diff --git a/public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts b/public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts index 9bdd7c3df79..de26448ec71 100644 --- a/public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts @@ -3,7 +3,7 @@ import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common'; import angular from 'angular'; -import {GraphCtrl} from '../graph_ctrl'; +import {GraphCtrl} from '../module'; import helpers from '../../../../../test/specs/helpers'; describe('GraphCtrl', function() { diff --git a/public/app/plugins/panel/text/module.ts b/public/app/plugins/panel/text/module.ts index 08dbb9d388b..d725fba8e5c 100644 --- a/public/app/plugins/panel/text/module.ts +++ b/public/app/plugins/panel/text/module.ts @@ -10,6 +10,8 @@ var panelDefaults = { }; export class TextPanelCtrl extends PanelCtrl { + static templateUrl = `public/app/plugins/panel/text/module.html`; + converter: any; content: string; @@ -79,9 +81,4 @@ export class TextPanelCtrl extends PanelCtrl { } } -class TextPanel extends PanelDirective { - templateUrl = `public/app/plugins/panel/text/module.html`; - controller = TextPanelCtrl; -} - -export {TextPanel as Panel} +export {TextPanelCtrl as PanelCtrl} From 14cc771cbe4d8c7c697370102ea8598df09a09ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 4 Feb 2016 15:04:07 +0100 Subject: [PATCH 25/32] feat(plugins): made panels loaded via plugin-componet directive --- .../app/core/directives/plugin_component.ts | 24 +- public/app/features/panel/all.js | 1 - public/app/features/panel/panel.ts | 10 +- public/app/features/panel/panel_directive.ts | 42 ---- public/app/features/panel/panel_loader.ts | 88 ------- public/app/plugins/panel/dashlist/module.ts | 11 +- .../plugins/panel/singlestat/controller.ts | 232 ----------------- public/app/plugins/panel/singlestat/module.ts | 237 +++++++++++++++++- .../singlestat/specs/singlestat-specs.ts | 2 +- public/app/plugins/panel/table/controller.ts | 133 ---------- public/app/plugins/panel/table/module.ts | 140 ++++++++++- public/app/plugins/panel/text/module.ts | 2 +- public/app/plugins/panel/unknown/module.ts | 14 +- 13 files changed, 392 insertions(+), 544 deletions(-) delete mode 100644 public/app/features/panel/panel_loader.ts delete mode 100644 public/app/plugins/panel/singlestat/controller.ts delete mode 100644 public/app/plugins/panel/table/controller.ts diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts index 020bcb08f0a..94e50bf5c86 100644 --- a/public/app/core/directives/plugin_component.ts +++ b/public/app/core/directives/plugin_component.ts @@ -5,6 +5,7 @@ import _ from 'lodash'; import config from 'app/core/config'; import coreModule from 'app/core/core_module'; +import {UnknownPanelCtrl} from 'app/plugins/panel/unknown/module'; /** @ngInject */ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $templateCache) { @@ -45,20 +46,23 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ } function loadPanelComponentInfo(scope, attrs) { + var componentInfo: any = { + name: 'panel-plugin-' + scope.panel.type, + bindings: {dashboard: "=", panel: "=", row: "="}, + attrs: {dashboard: "dashboard", panel: "panel", row: "row"}, + }; + var panelElemName = 'panel-' + scope.panel.type; let panelInfo = config.panels[scope.panel.type]; - if (!panelInfo) { - // unknown + var panelCtrlPromise = Promise.resolve(UnknownPanelCtrl); + if (panelInfo) { + panelCtrlPromise = System.import(panelInfo.module).then(function(panelModule) { + return panelModule.PanelCtrl; + }); } - return System.import(panelInfo.module).then(function(panelModule): any { - var PanelCtrl = panelModule.PanelCtrl; - var componentInfo = { - name: 'panel-plugin-' + panelInfo.id, - bindings: {dashboard: "=", panel: "=", row: "="}, - attrs: {dashboard: "dashboard", panel: "panel", row: "row"}, - Component: PanelCtrl, - }; + return panelCtrlPromise.then(function(PanelCtrl: any) { + componentInfo.Component = PanelCtrl; if (!PanelCtrl || PanelCtrl.registered) { return componentInfo; diff --git a/public/app/features/panel/all.js b/public/app/features/panel/all.js index 47fe256e7cf..b3ec16e541f 100644 --- a/public/app/features/panel/all.js +++ b/public/app/features/panel/all.js @@ -2,7 +2,6 @@ define([ './panel_menu', './panel_directive', './solo_panel_ctrl', - './panel_loader', './query_ctrl', './panel_editor_tab', './query_editor_row', diff --git a/public/app/features/panel/panel.ts b/public/app/features/panel/panel.ts index 634591801b6..94aeb3878aa 100644 --- a/public/app/features/panel/panel.ts +++ b/public/app/features/panel/panel.ts @@ -4,12 +4,18 @@ import config from 'app/core/config'; import {PanelCtrl} from './panel_ctrl'; import {MetricsPanelCtrl} from './metrics_panel_ctrl'; -import {PanelDirective} from './panel_directive'; import {QueryCtrl} from './query_ctrl'; +class DefaultPanelCtrl extends PanelCtrl { + /** @ngInject */ + constructor($scope, $injector) { + super($scope, $injector); + } +} + export { PanelCtrl, + DefaultPanelCtrl, MetricsPanelCtrl, - PanelDirective, QueryCtrl, } diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index 63484785d75..757f9f8e43d 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -3,48 +3,6 @@ import angular from 'angular'; import $ from 'jquery'; -import {PanelCtrl} from './panel_ctrl'; - -export class DefaultPanelCtrl extends PanelCtrl { - /** @ngInject */ - constructor($scope, $injector) { - super($scope, $injector); - } -} - -export class PanelDirective { - template: string; - templateUrl: string; - bindToController: boolean; - scope: any; - controller: any; - controllerAs: string; - - getDirective() { - if (!this.controller) { - this.controller = DefaultPanelCtrl; - } - - return { - template: this.template, - templateUrl: this.templateUrl, - controller: this.controller, - controllerAs: 'ctrl', - bindToController: true, - scope: {dashboard: "=", panel: "=", row: "="}, - link: (scope, elem, attrs, ctrl) => { - ctrl.init(); - this.link(scope, elem, attrs, ctrl); - } - }; - } - - link(scope, elem, attrs, ctrl) { - return null; - } -} - - var module = angular.module('grafana.directives'); module.directive('grafanaPanel', function() { diff --git a/public/app/features/panel/panel_loader.ts b/public/app/features/panel/panel_loader.ts deleted file mode 100644 index 0608c37f9fd..00000000000 --- a/public/app/features/panel/panel_loader.ts +++ /dev/null @@ -1,88 +0,0 @@ -/// - -import angular from 'angular'; -import config from 'app/core/config'; - -import {UnknownPanel} from '../../plugins/panel/unknown/module'; - -var directiveModule = angular.module('grafana.directives'); - -/** @ngInject */ -function panelLoader($compile, $http, $q, $injector, $templateCache) { - return { - restrict: 'E', - scope: { - dashboard: "=", - row: "=", - panel: "=" - }, - link: function(scope, elem, attrs) { - - function getTemplate(directive) { - if (directive.template) { - return $q.when(directive.template); - } - var cached = $templateCache.get(directive.templateUrl); - if (cached) { - return $q.when(cached); - } - return $http.get(directive.templateUrl).then(res => { - return res.data; - }); - } - - function addPanelAndCompile(name) { - var child = angular.element(document.createElement(name)); - child.attr('dashboard', 'dashboard'); - child.attr('panel', 'panel'); - child.attr('row', 'row'); - $compile(child)(scope); - - elem.empty(); - elem.append(child); - } - - function addPanel(name, Panel) { - if (Panel.registered) { - addPanelAndCompile(name); - return; - } - - if (Panel.promise) { - Panel.promise.then(() => { - addPanelAndCompile(name); - }); - return; - } - - var panelInstance = $injector.instantiate(Panel); - var directive = panelInstance.getDirective(); - - Panel.promise = getTemplate(directive).then(template => { - directive.templateUrl = null; - directive.template = `${template}`; - directiveModule.directive(attrs.$normalize(name), function() { - return directive; - }); - Panel.registered = true; - addPanelAndCompile(name); - }); - } - - var panelElemName = 'panel-directive-' + scope.panel.type; - let panelInfo = config.panels[scope.panel.type]; - if (!panelInfo) { - addPanel(panelElemName, UnknownPanel); - return; - } - - System.import(panelInfo.module).then(function(panelModule) { - addPanel(panelElemName, panelModule.Panel); - }).catch(err => { - console.log('Panel err: ', err); - }); - } - }; -} - -directiveModule.directive('panelLoader', panelLoader); diff --git a/public/app/plugins/panel/dashlist/module.ts b/public/app/plugins/panel/dashlist/module.ts index 5b1c4c89f66..829c98f39f5 100644 --- a/public/app/plugins/panel/dashlist/module.ts +++ b/public/app/plugins/panel/dashlist/module.ts @@ -2,7 +2,7 @@ import _ from 'lodash'; import config from 'app/core/config'; -import {PanelDirective, PanelCtrl} from '../../../features/panel/panel'; +import {PanelCtrl} from '../../../features/panel/panel'; // Set and populate defaults var panelDefaults = { @@ -55,11 +55,4 @@ class DashListCtrl extends PanelCtrl { } } -class DashListPanel extends PanelDirective { - controller = DashListCtrl; -} - -export { - DashListCtrl as DashListCtrl, - DashListCtrl as PanelCtrl, -} +export {DashListCtrl, DashListCtrl as PanelCtrl} diff --git a/public/app/plugins/panel/singlestat/controller.ts b/public/app/plugins/panel/singlestat/controller.ts deleted file mode 100644 index 78b87e5164d..00000000000 --- a/public/app/plugins/panel/singlestat/controller.ts +++ /dev/null @@ -1,232 +0,0 @@ -/// - -import angular from 'angular'; -import _ from 'lodash'; -import kbn from 'app/core/utils/kbn'; -import TimeSeries from '../../../core/time_series2'; -import {MetricsPanelCtrl} from '../../../features/panel/panel'; - -// Set and populate defaults -var panelDefaults = { - 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)', - } -}; - -export class SingleStatCtrl extends MetricsPanelCtrl { - series: any[]; - data: any[]; - fontSizes: any[]; - unitFormats: any[]; - - /** @ngInject */ - constructor($scope, $injector) { - super($scope, $injector); - _.defaults(this.panel, panelDefaults); - } - - initEditMode() { - super.initEditMode(); - this.icon = "fa fa-dashboard"; - this.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%']; - this.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html', 2); - this.unitFormats = kbn.getUnitFormats(); - } - - setUnitFormat(subItem) { - this.panel.format = subItem.value; - this.render(); - } - - refreshData(datasource) { - return this.issueQueries(datasource) - .then(this.dataHandler.bind(this)) - .catch(err => { - this.series = []; - this.render(); - throw err; - }); - } - - loadSnapshot(snapshotData) { - this.updateTimeRange(); - this.dataHandler(snapshotData); - } - - dataHandler(results) { - this.series = _.map(results.data, this.seriesHandler.bind(this)); - this.render(); - } - - seriesHandler(seriesData) { - var series = new TimeSeries({ - datapoints: seriesData.datapoints, - alias: seriesData.target, - }); - - series.flotpairs = series.getFlotPairs(this.panel.nullPointMode); - return series; - } - - setColoring(options) { - if (options.background) { - this.panel.colorValue = false; - this.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)']; - } else { - this.panel.colorBackground = false; - this.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)']; - } - this.render(); - } - - invertColorOrder() { - var tmp = this.panel.colors[0]; - this.panel.colors[0] = this.panel.colors[2]; - this.panel.colors[2] = tmp; - this.render(); - } - - getDecimalsForValue(value) { - if (_.isNumber(this.panel.decimals)) { - return {decimals: this.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: any = {}; - result.decimals = Math.max(0, dec); - result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2; - - return result; - } - - render() { - var data: any = {}; - this.setValues(data); - - data.thresholds = this.panel.thresholds.split(',').map(function(strVale) { - return Number(strVale.trim()); - }); - - data.colorMap = this.panel.colors; - - this.data = data; - this.broadcastRender(); - } - - setValues(data) { - data.flotpairs = []; - - if (this.series.length > 1) { - this.inspector.error = new Error(); - this.inspector.error.message = 'Multiple Series Error'; - this.inspector.error.data = 'Metric query returns ' + this.series.length + - ' series. Single Stat Panel expects a single series.\n\nResponse:\n'+JSON.stringify(this.series); - throw this.inspector.error; - } - - if (this.series && this.series.length > 0) { - var lastPoint = _.last(this.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 = this.series[0].stats[this.panel.valueName]; - data.flotpairs = this.series[0].flotpairs; - - var decimalInfo = this.getDecimalsForValue(data.value); - var formatFunc = kbn.valueFormats[this.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 < this.panel.valueMaps.length; i++) { - var map = this.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"; - } - }; - - removeValueMap(map) { - var index = _.indexOf(this.panel.valueMaps, map); - this.panel.valueMaps.splice(index, 1); - this.render(); - }; - - addValueMap() { - this.panel.valueMaps.push({value: '', op: '=', text: '' }); - } -} - diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index e0ea4429b39..8c6498778bd 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -1,18 +1,237 @@ /// +import angular from 'angular'; import _ from 'lodash'; import $ from 'jquery'; import 'jquery.flot'; -import {SingleStatCtrl} from './controller'; -import {PanelDirective} from '../../../features/panel/panel'; -class SingleStatPanel extends PanelDirective { - templateUrl = 'public/app/plugins/panel/singlestat/module.html'; - controller = SingleStatCtrl; +import kbn from 'app/core/utils/kbn'; +import TimeSeries from '../../../core/time_series2'; +import {MetricsPanelCtrl} from '../../../features/panel/panel'; + +// Set and populate defaults +var panelDefaults = { + 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)', + } +}; + +class SingleStatCtrl extends MetricsPanelCtrl { + static templateUrl = 'public/app/plugins/panel/singlestat/module.html'; + + series: any[]; + data: any[]; + fontSizes: any[]; + unitFormats: any[]; /** @ngInject */ - constructor(private $location, private linkSrv, private $timeout, private templateSrv) { - super(); + constructor($scope, $injector, private $location, private linkSrv, private templateSrv) { + super($scope, $injector); + _.defaults(this.panel, panelDefaults); + } + + initEditMode() { + super.initEditMode(); + this.icon = "fa fa-dashboard"; + this.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%']; + this.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html', 2); + this.unitFormats = kbn.getUnitFormats(); + } + + setUnitFormat(subItem) { + this.panel.format = subItem.value; + this.render(); + } + + refreshData(datasource) { + return this.issueQueries(datasource) + .then(this.dataHandler.bind(this)) + .catch(err => { + this.series = []; + this.render(); + throw err; + }); + } + + loadSnapshot(snapshotData) { + this.updateTimeRange(); + this.dataHandler(snapshotData); + } + + dataHandler(results) { + this.series = _.map(results.data, this.seriesHandler.bind(this)); + this.render(); + } + + seriesHandler(seriesData) { + var series = new TimeSeries({ + datapoints: seriesData.datapoints, + alias: seriesData.target, + }); + + series.flotpairs = series.getFlotPairs(this.panel.nullPointMode); + return series; + } + + setColoring(options) { + if (options.background) { + this.panel.colorValue = false; + this.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)']; + } else { + this.panel.colorBackground = false; + this.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)']; + } + this.render(); + } + + invertColorOrder() { + var tmp = this.panel.colors[0]; + this.panel.colors[0] = this.panel.colors[2]; + this.panel.colors[2] = tmp; + this.render(); + } + + getDecimalsForValue(value) { + if (_.isNumber(this.panel.decimals)) { + return {decimals: this.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: any = {}; + result.decimals = Math.max(0, dec); + result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2; + + return result; + } + + render() { + var data: any = {}; + this.setValues(data); + + data.thresholds = this.panel.thresholds.split(',').map(function(strVale) { + return Number(strVale.trim()); + }); + + data.colorMap = this.panel.colors; + + this.data = data; + this.broadcastRender(); + } + + setValues(data) { + data.flotpairs = []; + + if (this.series.length > 1) { + this.inspector.error = new Error(); + this.inspector.error.message = 'Multiple Series Error'; + this.inspector.error.data = 'Metric query returns ' + this.series.length + + ' series. Single Stat Panel expects a single series.\n\nResponse:\n'+JSON.stringify(this.series); + throw this.inspector.error; + } + + if (this.series && this.series.length > 0) { + var lastPoint = _.last(this.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 = this.series[0].stats[this.panel.valueName]; + data.flotpairs = this.series[0].flotpairs; + + var decimalInfo = this.getDecimalsForValue(data.value); + var formatFunc = kbn.valueFormats[this.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 < this.panel.valueMaps.length; i++) { + var map = this.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"; + } + }; + + removeValueMap(map) { + var index = _.indexOf(this.panel.valueMaps, map); + this.panel.valueMaps.splice(index, 1); + this.render(); + }; + + addValueMap() { + this.panel.valueMaps.push({value: '', op: '=', text: '' }); } link(scope, elem, attrs, ctrl) { @@ -235,7 +454,7 @@ function getColorForValue(data, value) { } export { - SingleStatPanel, - SingleStatPanel as Panel, + SingleStatCtrl, + SingleStatCtrl as PanelCtrl, getColorForValue }; diff --git a/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts b/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts index 21cbfb7602c..283389ee400 100644 --- a/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts +++ b/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts @@ -4,7 +4,7 @@ import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../.. import angular from 'angular'; import helpers from '../../../../../test/specs/helpers'; -import {SingleStatCtrl} from '../controller'; +import {SingleStatCtrl} from '../module'; describe('SingleStatCtrl', function() { var ctx = new helpers.ControllerTestContext(); diff --git a/public/app/plugins/panel/table/controller.ts b/public/app/plugins/panel/table/controller.ts deleted file mode 100644 index a526a8a8798..00000000000 --- a/public/app/plugins/panel/table/controller.ts +++ /dev/null @@ -1,133 +0,0 @@ -/// - -import angular from 'angular'; -import _ from 'lodash'; -import moment from 'moment'; -import * as FileExport from 'app/core/utils/file_export'; -import {MetricsPanelCtrl} from '../../../features/panel/panel'; -import {transformDataToTable} from './transformers'; -import {tablePanelEditor} from './editor'; - -var panelDefaults = { - targets: [{}], - transform: 'timeseries_to_columns', - pageSize: null, - showHeader: true, - styles: [ - { - type: 'date', - pattern: 'Time', - dateFormat: 'YYYY-MM-DD HH:mm:ss', - }, - { - unit: 'short', - type: 'number', - decimals: 2, - colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], - colorMode: null, - pattern: '/.*/', - thresholds: [], - } - ], - columns: [], - scroll: true, - fontSize: '100%', - sort: {col: 0, desc: true}, -}; - -export class TablePanelCtrl extends MetricsPanelCtrl { - pageIndex: number; - dataRaw: any; - table: any; - - /** @ngInject */ - constructor($scope, $injector, private annotationsSrv) { - super($scope, $injector); - this.pageIndex = 0; - - if (this.panel.styles === void 0) { - this.panel.styles = this.panel.columns; - this.panel.columns = this.panel.fields; - delete this.panel.columns; - delete this.panel.fields; - } - - _.defaults(this.panel, panelDefaults); - } - - initEditMode() { - super.initEditMode(); - this.addEditorTab('Options', tablePanelEditor, 2); - } - - getExtendedMenu() { - var menu = super.getExtendedMenu(); - menu.push({text: 'Export CSV', click: 'ctrl.exportCsv()'}); - return menu; - } - - refreshData(datasource) { - this.pageIndex = 0; - - if (this.panel.transform === 'annotations') { - return this.annotationsSrv.getAnnotations(this.dashboard).then(annotations => { - this.dataRaw = annotations; - this.render(); - }); - } - - return this.issueQueries(datasource) - .then(this.dataHandler.bind(this)) - .catch(err => { - this.render(); - throw err; - }); - } - - toggleColumnSort(col, colIndex) { - if (this.panel.sort.col === colIndex) { - if (this.panel.sort.desc) { - this.panel.sort.desc = false; - } else { - this.panel.sort.col = null; - } - } else { - this.panel.sort.col = colIndex; - this.panel.sort.desc = true; - } - - this.render(); - } - - dataHandler(results) { - this.dataRaw = results.data; - this.pageIndex = 0; - this.render(); - } - - render() { - // automatically correct transform mode - // based on data - if (this.dataRaw && this.dataRaw.length) { - if (this.dataRaw[0].type === 'table') { - this.panel.transform = 'table'; - } else { - if (this.dataRaw[0].type === 'docs') { - this.panel.transform = 'json'; - } else { - if (this.panel.transform === 'table' || this.panel.transform === 'json') { - this.panel.transform = 'timeseries_to_rows'; - } - } - } - } - - this.table = transformDataToTable(this.dataRaw, this.panel); - this.table.sort(this.panel.sort); - this.broadcastRender(this.table); - } - - exportCsv() { - FileExport.exportTableDataToCsv(this.table); - } -} diff --git a/public/app/plugins/panel/table/module.ts b/public/app/plugins/panel/table/module.ts index bb48da3f08a..a84d2678295 100644 --- a/public/app/plugins/panel/table/module.ts +++ b/public/app/plugins/panel/table/module.ts @@ -1,17 +1,139 @@ /// -import kbn = require('app/core/utils/kbn'); - +import angular from 'angular'; import _ from 'lodash'; import $ from 'jquery'; import moment from 'moment'; -import {PanelDirective} from '../../../features/panel/panel'; -import {TablePanelCtrl} from './controller'; +import * as FileExport from 'app/core/utils/file_export'; +import {MetricsPanelCtrl} from '../../../features/panel/panel'; +import {transformDataToTable} from './transformers'; +import {tablePanelEditor} from './editor'; import {TableRenderer} from './renderer'; -class TablePanel extends PanelDirective { - templateUrl = 'public/app/plugins/panel/table/module.html'; - controller = TablePanelCtrl; +var panelDefaults = { + targets: [{}], + transform: 'timeseries_to_columns', + pageSize: null, + showHeader: true, + styles: [ + { + type: 'date', + pattern: 'Time', + dateFormat: 'YYYY-MM-DD HH:mm:ss', + }, + { + unit: 'short', + type: 'number', + decimals: 2, + colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], + colorMode: null, + pattern: '/.*/', + thresholds: [], + } + ], + columns: [], + scroll: true, + fontSize: '100%', + sort: {col: 0, desc: true}, +}; + +class TablePanelCtrl extends MetricsPanelCtrl { + static templateUrl = 'public/app/plugins/panel/table/module.html'; + + pageIndex: number; + dataRaw: any; + table: any; + + /** @ngInject */ + constructor($scope, $injector, private annotationsSrv) { + super($scope, $injector); + this.pageIndex = 0; + + if (this.panel.styles === void 0) { + this.panel.styles = this.panel.columns; + this.panel.columns = this.panel.fields; + delete this.panel.columns; + delete this.panel.fields; + } + + _.defaults(this.panel, panelDefaults); + } + + initEditMode() { + super.initEditMode(); + this.addEditorTab('Options', tablePanelEditor, 2); + } + + getExtendedMenu() { + var menu = super.getExtendedMenu(); + menu.push({text: 'Export CSV', click: 'ctrl.exportCsv()'}); + return menu; + } + + refreshData(datasource) { + this.pageIndex = 0; + + if (this.panel.transform === 'annotations') { + return this.annotationsSrv.getAnnotations(this.dashboard).then(annotations => { + this.dataRaw = annotations; + this.render(); + }); + } + + return this.issueQueries(datasource) + .then(this.dataHandler.bind(this)) + .catch(err => { + this.render(); + throw err; + }); + } + + toggleColumnSort(col, colIndex) { + if (this.panel.sort.col === colIndex) { + if (this.panel.sort.desc) { + this.panel.sort.desc = false; + } else { + this.panel.sort.col = null; + } + } else { + this.panel.sort.col = colIndex; + this.panel.sort.desc = true; + } + + this.render(); + } + + dataHandler(results) { + this.dataRaw = results.data; + this.pageIndex = 0; + this.render(); + } + + render() { + // automatically correct transform mode + // based on data + if (this.dataRaw && this.dataRaw.length) { + if (this.dataRaw[0].type === 'table') { + this.panel.transform = 'table'; + } else { + if (this.dataRaw[0].type === 'docs') { + this.panel.transform = 'json'; + } else { + if (this.panel.transform === 'table' || this.panel.transform === 'json') { + this.panel.transform = 'timeseries_to_rows'; + } + } + } + } + + this.table = transformDataToTable(this.dataRaw, this.panel); + this.table.sort(this.panel.sort); + this.broadcastRender(this.table); + } + + exportCsv() { + FileExport.exportTableDataToCsv(this.table); + } link(scope, elem, attrs, ctrl) { var data; @@ -97,6 +219,6 @@ class TablePanel extends PanelDirective { } export { - TablePanel, - TablePanel as Panel + TablePanelCtrl, + TablePanelCtrl as PanelCtrl }; diff --git a/public/app/plugins/panel/text/module.ts b/public/app/plugins/panel/text/module.ts index d725fba8e5c..4cb35b71a4b 100644 --- a/public/app/plugins/panel/text/module.ts +++ b/public/app/plugins/panel/text/module.ts @@ -1,7 +1,7 @@ /// import _ from 'lodash'; -import {PanelDirective, PanelCtrl} from '../../../features/panel/panel'; +import {PanelCtrl} from '../../../features/panel/panel'; // Set and populate defaults var panelDefaults = { diff --git a/public/app/plugins/panel/unknown/module.ts b/public/app/plugins/panel/unknown/module.ts index d1acc57af8a..dad970047bf 100644 --- a/public/app/plugins/panel/unknown/module.ts +++ b/public/app/plugins/panel/unknown/module.ts @@ -1,14 +1,14 @@ /// -import {PanelDirective} from '../../../features/panel/panel'; +import {PanelCtrl} from '../../../features/panel/panel'; -class UnknownPanel extends PanelDirective { - templateUrl = 'public/app/plugins/panel/unknown/module.html'; +export class UnknownPanelCtrl extends PanelCtrl { + static templateUrl = 'public/app/plugins/panel/unknown/module.html'; + + constructor($scope, $injector) { + super($scope, $injector); + } } -export { - UnknownPanel, - UnknownPanel as Panel -} From 316e1aac67abb0f4d22303cef0f3556a6ee04d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 4 Feb 2016 15:12:44 +0100 Subject: [PATCH 26/32] fix(build): minor fix --- public/app/features/panel/query_ctrl.ts | 20 -------------------- public/app/plugins/panel/unknown/module.ts | 1 + 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/public/app/features/panel/query_ctrl.ts b/public/app/features/panel/query_ctrl.ts index 016f675dd0f..66370595252 100644 --- a/public/app/features/panel/query_ctrl.ts +++ b/public/app/features/panel/query_ctrl.ts @@ -55,23 +55,3 @@ export class QueryCtrl { } } -// var directivesModule = angular.module('grafana.directives'); -// -// /** @ngInject */ -// function metricsQueryOptions(dynamicDirectiveSrv, datasourceSrv) { -// return dynamicDirectiveSrv.create({ -// watchPath: "ctrl.panel.datasource", -// directive: scope => { -// return datasourceSrv.get(scope.ctrl.panel.datasource).then(ds => { -// return System.import(ds.meta.module).then(dsModule => { -// return { -// name: 'metrics-query-options-' + ds.meta.id, -// fn: dsModule.metricsQueryOptions -// }; -// }); -// }); -// } -// }); -// } -// -// directivesModule.directive('metricsQueryOptions', metricsQueryOptions); diff --git a/public/app/plugins/panel/unknown/module.ts b/public/app/plugins/panel/unknown/module.ts index dad970047bf..d625485cb63 100644 --- a/public/app/plugins/panel/unknown/module.ts +++ b/public/app/plugins/panel/unknown/module.ts @@ -5,6 +5,7 @@ import {PanelCtrl} from '../../../features/panel/panel'; export class UnknownPanelCtrl extends PanelCtrl { static templateUrl = 'public/app/plugins/panel/unknown/module.html'; + /** @ngInject */ constructor($scope, $injector) { super($scope, $injector); } From 33dc9fdd76530af359c3782f42334c90b977b4b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 4 Feb 2016 15:23:40 +0100 Subject: [PATCH 27/32] fix(inspector): fixed broken panel error inspect --- public/app/features/panel/panel_ctrl.ts | 14 +++++++++++++- public/app/features/panel/partials/panel.html | 2 +- public/app/partials/inspector.html | 6 +++--- .../plugins/datasource/influxdb/influx_query.ts | 2 +- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 175fba2e5f9..9f14a7c24a3 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -171,5 +171,17 @@ export class PanelCtrl { src: 'public/app/features/dashboard/partials/shareModal.html', scope: shareScope }); - } + } + + openInspector() { + var modalScope = this.$scope.$new(); + modalScope.panel = this.panel; + modalScope.dashboard = this.dashboard; + modalScope.inspector = this.inspector; + + this.publishAppEvent('show-modal', { + src: 'public/app/partials/inspector.html', + scope: modalScope + }); + } } diff --git a/public/app/features/panel/partials/panel.html b/public/app/features/panel/partials/panel.html index 06132c107a4..b5228840687 100644 --- a/public/app/features/panel/partials/panel.html +++ b/public/app/features/panel/partials/panel.html @@ -1,6 +1,6 @@
    - + diff --git a/public/app/partials/inspector.html b/public/app/partials/inspector.html index 5a83afe9a3b..9caa38bb39f 100644 --- a/public/app/partials/inspector.html +++ b/public/app/partials/inspector.html @@ -61,9 +61,9 @@
    -
    -			{{message}}
    -		
    +
    +{{message}}
    +
    diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts
    index d932e7dd8d7..6eb2d84aa49 100644
    --- a/public/app/plugins/datasource/influxdb/influx_query.ts
    +++ b/public/app/plugins/datasource/influxdb/influx_query.ts
    @@ -175,7 +175,7 @@ export default class InfluxQuery {
         }
     
         if (!target.measurement) {
    -      throw "Metric measurement is missing";
    +      throw {message: "Metric measurement is missing"};
         }
     
         var query = 'SELECT ';
    
    From 68a5fb66ffd4723d9616f44394c60b082f834292 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?Torkel=20=C3=96degaard?= 
    Date: Thu, 4 Feb 2016 15:28:24 +0100
    Subject: [PATCH 28/32] ux(panel menu): changed remove icon to trash icon,
     closes #3939
    
    ---
     public/app/features/panel/panel_menu.js | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/public/app/features/panel/panel_menu.js b/public/app/features/panel/panel_menu.js
    index b23da0f2a88..0af5a91729c 100644
    --- a/public/app/features/panel/panel_menu.js
    +++ b/public/app/features/panel/panel_menu.js
    @@ -37,7 +37,7 @@ function (angular, $, _) {
               template += '
    '; template += ''; template += ''; - template += ''; + template += ''; template += '
    '; template += '
    '; } From 5588e7597c1af04917606ecada1036ef260ba8fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 4 Feb 2016 15:31:54 +0100 Subject: [PATCH 29/32] feat(inspector): minor fix for inspector making the error not clear when having dashboard refresh, fixes #3938 --- public/app/features/panel/panel_ctrl.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 9f14a7c24a3..96b1a6d9d4d 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -2,6 +2,7 @@ import config from 'app/core/config'; import _ from 'lodash'; +import angular from 'angular'; export class PanelCtrl { panel: any; @@ -177,7 +178,7 @@ export class PanelCtrl { var modalScope = this.$scope.$new(); modalScope.panel = this.panel; modalScope.dashboard = this.dashboard; - modalScope.inspector = this.inspector; + modalScope.inspector = angular.copy(this.inspector); this.publishAppEvent('show-modal', { src: 'public/app/partials/inspector.html', From edf5868c38aef3d832834ee6b6bac0ce197680f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 4 Feb 2016 21:52:05 +0100 Subject: [PATCH 30/32] fix(panel timeshift): fixed so that panel time range works when dashboard time range does not end in now, like and , fixes #3941 --- CHANGELOG.md | 11 ++++++----- public/app/features/panel/metrics_panel_ctrl.ts | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09e009d0c75..272c752bf63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # 3.0.0 (unrelased master branch) ### New Features -* **Playlists**: Playlists can now be persisted and started from urls, closes [#3655](https://github.com/grafana/grafana/pull/3655) +* **Playlists**: Playlists can now be persisted and started from urls, closes [#3655](https://github.com/grafana/grafana/issues/3655) * **Metadata**: Settings panel now shows dashboard metadata, closes [#3304](https://github.com/grafana/grafana/issues/3304) * **InfluxDB**: Support for policy selection in query editor, closes [#2018](https://github.com/grafana/grafana/issues/2018) @@ -11,14 +11,15 @@ * **KairosDB** The data source is no longer included in default builds, but can easily be installed via improved plugin system, closes [#3524](https://github.com/grafana/grafana/issues/3524) ### Enhancements -* **Sessions**: Support for memcached as session storage, closes [#3458](https://github.com/grafana/grafana/pull/3458) -* **mysql**: Grafana now supports ssl for mysql, closes [#3584](https://github.com/grafana/grafana/pull/3584) -* **snapshot**: Annotations are now included in snapshots, closes [#3635](https://github.com/grafana/grafana/pull/3635) +* **Sessions**: Support for memcached as session storage, closes [#3458](https://github.com/grafana/grafana/issues/3458) +* **mysql**: Grafana now supports ssl for mysql, closes [#3584](https://github.com/grafana/grafana/issues/3584) +* **snapshot**: Annotations are now included in snapshots, closes [#3635](https://github.com/grafana/grafana/issues/3635) * **Admin**: Admin can now have global overview of Grafana setup, closes [#3812](https://github.com/grafana/grafana/issues/3812) ### Bug fixes * **Playlist**: Fix for memory leak when running a playlist, closes [#3794](https://github.com/grafana/grafana/pull/3794) -* **InfluxDB**: Fix for InfluxDB and table panel when using Format As Table and having group by time, fixes [#3928](https://github.com/grafana/grafana/pull/3928) +* **InfluxDB**: Fix for InfluxDB and table panel when using Format As Table and having group by time, fixes [#3928](https://github.com/grafana/grafana/issues/3928) +* **Panel Time shift**: Fix for panel time range and using dashboard times liek `Today` and `This Week`, fixes [#3941](https://github.com/grafana/grafana/issues/3941) # 2.6.1 (unrelased, 2.6.x branch) diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index c56b900b59c..09626131149 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -134,6 +134,7 @@ class MetricsPanelCtrl extends PanelCtrl { this.rangeRaw.from = timeFromInfo.from; this.rangeRaw.to = timeFromInfo.to; this.range.from = timeFromDate; + this.range.to = dateMath.parse(timeFromInfo.to); } } From 660ce3a61d30fcdb2a82ff6be171c55c54184cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 4 Feb 2016 22:19:46 +0100 Subject: [PATCH 31/32] fix(row editor): row editor fix and cleanup of unused code --- public/app/core/core.ts | 1 - public/app/core/directives/config_modal.js | 46 ------------------- public/app/core/directives/misc.js | 1 - public/app/features/dashboard/rowCtrl.js | 7 +++ public/app/features/panel/panel_menu.js | 1 - public/app/partials/dashboard.html | 2 +- .../influxdb/partials/query.editor.html | 2 +- .../plugins/datasource/influxdb/query_ctrl.ts | 2 +- 8 files changed, 10 insertions(+), 52 deletions(-) delete mode 100644 public/app/core/directives/config_modal.js diff --git a/public/app/core/core.ts b/public/app/core/core.ts index ca608fb35f9..55c0f4ec049 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -3,7 +3,6 @@ import "./directives/annotation_tooltip"; import "./directives/body_class"; -import "./directives/config_modal"; import "./directives/confirm_click"; import "./directives/dash_edit_link"; import "./directives/dash_upload"; diff --git a/public/app/core/directives/config_modal.js b/public/app/core/directives/config_modal.js deleted file mode 100644 index e37d6797fc4..00000000000 --- a/public/app/core/directives/config_modal.js +++ /dev/null @@ -1,46 +0,0 @@ -define([ - 'lodash', - 'jquery', - '../core_module', -], -function (_, $, coreModule) { - 'use strict'; - - coreModule.default.directive('configModal', function($modal, $q, $timeout) { - return { - restrict: 'A', - link: function(scope, elem, attrs) { - var partial = attrs.configModal; - var id = '#' + partial.replace('.html', '').replace(/[\/|\.|:]/g, '-') + '-' + scope.$id; - - elem.bind('click',function() { - if ($(id).length) { - elem.attr('data-target', id).attr('data-toggle', 'modal'); - scope.$apply(function() { scope.$broadcast('modal-opened'); }); - return; - } - - var panelModal = $modal({ - template: partial, - persist: false, - show: false, - scope: scope.$new(), - keyboard: false - }); - - $q.when(panelModal).then(function(modalEl) { - elem.attr('data-target', id).attr('data-toggle', 'modal'); - - $timeout(function () { - if (!modalEl.data('modal').isShown) { - modalEl.modal('show'); - } - }, 50); - }); - - scope.$apply(); - }); - } - }; - }); -}); diff --git a/public/app/core/directives/misc.js b/public/app/core/directives/misc.js index b3d6de2585d..1f96422d49e 100644 --- a/public/app/core/directives/misc.js +++ b/public/app/core/directives/misc.js @@ -90,7 +90,6 @@ function (angular, coreModule, kbn) { var li = '' + '' + (item.text || '') + ''; if (item.submenu && item.submenu.length) { diff --git a/public/app/features/dashboard/rowCtrl.js b/public/app/features/dashboard/rowCtrl.js index e8abb5bbb05..d7ccc22603f 100644 --- a/public/app/features/dashboard/rowCtrl.js +++ b/public/app/features/dashboard/rowCtrl.js @@ -61,6 +61,13 @@ function (angular, _, config) { }); }; + $scope.editRow = function() { + $scope.appEvent('show-dash-editor', { + src: 'public/app/partials/roweditor.html', + scope: $scope.$new() + }); + }; + $scope.moveRow = function(direction) { var rowsList = $scope.dashboard.rows; var rowIndex = _.indexOf(rowsList, $scope.row); diff --git a/public/app/features/panel/panel_menu.js b/public/app/features/panel/panel_menu.js index 0af5a91729c..0489489af8e 100644 --- a/public/app/features/panel/panel_menu.js +++ b/public/app/features/panel/panel_menu.js @@ -53,7 +53,6 @@ function (angular, $, _) { template += ''; }); diff --git a/public/app/partials/dashboard.html b/public/app/partials/dashboard.html index 712cff0e849..d63c0d01e29 100644 --- a/public/app/partials/dashboard.html +++ b/public/app/partials/dashboard.html @@ -65,7 +65,7 @@
  • - Row editor + Row editor
  • Delete row diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 8e5546573d7..140f298f458 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -17,7 +17,7 @@
  • -
    +
    diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.ts b/public/app/plugins/datasource/influxdb/query_ctrl.ts index f916adde9a7..0622fc17bd1 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.ts +++ b/public/app/plugins/datasource/influxdb/query_ctrl.ts @@ -153,7 +153,7 @@ export class InfluxQueryCtrl extends QueryCtrl { this.panelCtrl.refresh(); } - toggleQueryMode() { + toggleEditorMode() { this.target.rawQuery = !this.target.rawQuery; } From a167eb4fa1c21a03688ecc4f267ab2d914ead8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 4 Feb 2016 22:33:53 +0100 Subject: [PATCH 32/32] fix(row repeat): fix for row repeat where repeated row was added to the bottom and not next to the source row, fixes #2942 --- CHANGELOG.md | 1 + .../features/dashboard/dynamicDashboardSrv.js | 16 ++++++++-------- public/test/specs/dynamicDashboardSrv-specs.js | 9 +++++---- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 272c752bf63..d68dc949c30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * **Playlist**: Fix for memory leak when running a playlist, closes [#3794](https://github.com/grafana/grafana/pull/3794) * **InfluxDB**: Fix for InfluxDB and table panel when using Format As Table and having group by time, fixes [#3928](https://github.com/grafana/grafana/issues/3928) * **Panel Time shift**: Fix for panel time range and using dashboard times liek `Today` and `This Week`, fixes [#3941](https://github.com/grafana/grafana/issues/3941) +* **Row repeat**: Repeated rows will now appear next to each other and not by the bottom of the dashboard, fixes [#3942](https://github.com/grafana/grafana/issues/3942) # 2.6.1 (unrelased, 2.6.x branch) diff --git a/public/app/features/dashboard/dynamicDashboardSrv.js b/public/app/features/dashboard/dynamicDashboardSrv.js index 8e3c24f7202..5c0101be10f 100644 --- a/public/app/features/dashboard/dynamicDashboardSrv.js +++ b/public/app/features/dashboard/dynamicDashboardSrv.js @@ -34,7 +34,7 @@ function (angular, _) { // handle row repeats if (row.repeat) { - this.repeatRow(row); + this.repeatRow(row, i); } // clean up old left overs else if (row.repeatRowId && row.repeatIteration !== this.iteration) { @@ -58,13 +58,13 @@ function (angular, _) { }; // returns a new row clone or reuses a clone from previous iteration - this.getRowClone = function(sourceRow, index) { - if (index === 0) { + this.getRowClone = function(sourceRow, repeatIndex, sourceRowIndex) { + if (repeatIndex === 0) { return sourceRow; } var i, panel, row, copy; - var sourceRowId = _.indexOf(this.dashboard.rows, sourceRow) + 1; + var sourceRowId = sourceRowIndex + 1; // look for row to reuse for (i = 0; i < this.dashboard.rows.length; i++) { @@ -77,7 +77,7 @@ function (angular, _) { if (!copy) { copy = angular.copy(sourceRow); - this.dashboard.rows.push(copy); + this.dashboard.rows.splice(sourceRowIndex + repeatIndex, 0, copy); // set new panel ids for (i = 0; i < copy.panels.length; i++) { @@ -92,8 +92,8 @@ function (angular, _) { return copy; }; - // returns a new panel clone or reuses a clone from previous iteration - this.repeatRow = function(row) { + // returns a new row clone or reuses a clone from previous iteration + this.repeatRow = function(row, rowIndex) { var variables = this.dashboard.templating.list; var variable = _.findWhere(variables, {name: row.repeat}); if (!variable) { @@ -108,7 +108,7 @@ function (angular, _) { } _.each(selected, function(option, index) { - copy = self.getRowClone(row, index); + copy = self.getRowClone(row, index, rowIndex); copy.scopedVars = {}; copy.scopedVars[variable.name] = option; diff --git a/public/test/specs/dynamicDashboardSrv-specs.js b/public/test/specs/dynamicDashboardSrv-specs.js index 2c3405e152f..b7ff61c3684 100644 --- a/public/test/specs/dynamicDashboardSrv-specs.js +++ b/public/test/specs/dynamicDashboardSrv-specs.js @@ -106,6 +106,7 @@ define([ repeat: 'servers', panels: [{id: 2}] }); + dash.rows.push({panels: []}); dash.templating.list.push({ name: 'servers', current: { @@ -120,14 +121,14 @@ define([ }); it('should repeat row one time', function() { - expect(ctx.rows.length).to.be(2); + expect(ctx.rows.length).to.be(3); }); it('should keep panel ids on first row', function() { expect(ctx.rows[0].panels[0].id).to.be(2); }); - it('should mark second row as repeated', function() { + it('should keep first row as repeat', function() { expect(ctx.rows[0].repeat).to.be('servers'); }); @@ -159,7 +160,7 @@ define([ }); it('should still only have 2 rows', function() { - expect(ctx.rows.length).to.be(2); + expect(ctx.rows.length).to.be(3); }); it.skip('should have updated props from source', function() { @@ -178,7 +179,7 @@ define([ }); it('should remove repeated second row', function() { - expect(ctx.rows.length).to.be(1); + expect(ctx.rows.length).to.be(2); }); }); });