From e3b281dbac0fb959c917ced7823992684c9d84f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 12 Jun 2016 11:43:18 +0200 Subject: [PATCH] feat(alerting): more work on alerting thresholds --- pkg/services/alerting/extractor.go | 7 +- pkg/services/alerting/extractor_test.go | 7 +- .../app/plugins/panel/graph/alert_handle.ts | 135 ++++++++++++ .../app/plugins/panel/graph/alert_tab_ctrl.ts | 26 +-- public/app/plugins/panel/graph/graph.js | 36 ++-- .../plugins/panel/graph/jquery.flot.alerts.ts | 97 --------- .../panel/graph/partials/tab_alerting.html | 192 +++++++++--------- public/sass/components/_panel_graph.scss | 67 +++--- 8 files changed, 308 insertions(+), 259 deletions(-) create mode 100644 public/app/plugins/panel/graph/alert_handle.ts delete mode 100644 public/app/plugins/panel/graph/jquery.flot.alerts.ts diff --git a/pkg/services/alerting/extractor.go b/pkg/services/alerting/extractor.go index 6a0883c16fa..ae360973fad 100644 --- a/pkg/services/alerting/extractor.go +++ b/pkg/services/alerting/extractor.go @@ -57,12 +57,9 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) { for _, panelObj := range row.Get("panels").MustArray() { panel := simplejson.NewFromAny(panelObj) - jsonAlert := panel.Get("alert") + jsonAlert, hasAlert := panel.CheckGet("alert") - // check if marked for deletion - deleted := jsonAlert.Get("deleted").MustBool() - if deleted { - e.log.Info("Deleted alert rule found") + if !hasAlert { continue } diff --git a/pkg/services/alerting/extractor_test.go b/pkg/services/alerting/extractor_test.go index 6eda321f0dc..7cab1d94c7c 100644 --- a/pkg/services/alerting/extractor_test.go +++ b/pkg/services/alerting/extractor_test.go @@ -149,10 +149,7 @@ func TestAlertRuleExtraction(t *testing.T) { ], "title": "Broken influxdb panel", "transform": "table", - "type": "table", - "alert": { - "deleted": true - } + "type": "table" } ], "title": "New row" @@ -185,7 +182,7 @@ func TestAlertRuleExtraction(t *testing.T) { return nil }) - alerts, err := extractor.GetRuleModels() + alerts, err := extractor.GetAlerts() Convey("Get rules without error", func() { So(err, ShouldBeNil) diff --git a/public/app/plugins/panel/graph/alert_handle.ts b/public/app/plugins/panel/graph/alert_handle.ts new file mode 100644 index 00000000000..c1914196e89 --- /dev/null +++ b/public/app/plugins/panel/graph/alert_handle.ts @@ -0,0 +1,135 @@ +/// + +import 'jquery.flot'; +import $ from 'jquery'; +import _ from 'lodash'; + +export class AlertHandleManager { + plot: any; + placeholder: any; + height: any; + alert: any; + + constructor(private panelCtrl) { + this.alert = panelCtrl.panel.alert; + } + + getHandleInnerHtml(type, op, value) { + if (op === '>') { op = '>'; } + if (op === '<') { op = '<'; } + + return ` +
+
+
+ + ${op} ${value} +
`; + } + + getFullHandleHtml(type, op, value) { + var innerTemplate = this.getHandleInnerHtml(type, op, value); + return ` +
+ ${innerTemplate} +
+ `; + } + + setupDragging(handleElem, levelModel) { + var isMoving = false; + var lastY = null; + var posTop; + var plot = this.plot; + var panelCtrl = this.panelCtrl; + + function dragging(evt) { + if (lastY === null) { + lastY = evt.clientY; + } else { + var diff = evt.clientY - lastY; + posTop = posTop + diff; + lastY = evt.clientY; + handleElem.css({top: posTop + diff}); + } + } + + function stopped() { + isMoving = false; + // calculate graph level + var graphLevel = plot.c2p({left: 0, top: posTop}).y; + console.log('canvasPos:' + posTop + ' Graph level: ' + graphLevel); + graphLevel = parseInt(graphLevel.toFixed(0)); + levelModel.level = graphLevel; + console.log(levelModel); + + var levelCanvasPos = plot.p2c({x: 0, y: graphLevel}); + console.log('canvas pos', levelCanvasPos); + + console.log('stopped'); + handleElem.off("mousemove", dragging); + handleElem.off("mouseup", dragging); + + // trigger digest and render + panelCtrl.$scope.$apply(function() { + panelCtrl.render(); + }); + } + + handleElem.bind('mousedown', function() { + isMoving = true; + lastY = null; + posTop = handleElem.position().top; + console.log('start pos', posTop); + + handleElem.on("mousemove", dragging); + handleElem.on("mouseup", stopped); + }); + } + + cleanUp() { + if (this.placeholder) { + this.placeholder.find(".alert-handle-wrapper").remove(); + } + } + + renderHandle(type, model, defaultHandleTopPos) { + var handleElem = this.placeholder.find(`.alert-handle-wrapper--${type}`); + var level = model.level; + var levelStr = level; + var handleTopPos = 0; + + // handle no value + if (!_.isNumber(level)) { + levelStr = ''; + handleTopPos = defaultHandleTopPos; + } else { + var levelCanvasPos = this.plot.p2c({x: 0, y: level}); + handleTopPos = Math.min(Math.max(levelCanvasPos.top, 0), this.height) - 6; + } + + if (handleElem.length === 0) { + console.log('creating handle'); + handleElem = $(this.getFullHandleHtml(type, model.op, levelStr)); + this.placeholder.append(handleElem); + this.setupDragging(handleElem, model); + } else { + console.log('reusing handle!'); + handleElem.html(this.getHandleInnerHtml(type, model.op, levelStr)); + } + + handleElem.toggleClass('alert-handle-wrapper--no-value', levelStr === ''); + handleElem.css({top: handleTopPos}); + } + + draw(plot) { + this.plot = plot; + this.placeholder = plot.getPlaceholder(); + this.height = plot.height(); + + this.renderHandle('critical', this.alert.critical, 10); + this.renderHandle('warn', this.alert.warn, this.height-30); + } + +} + diff --git a/public/app/plugins/panel/graph/alert_tab_ctrl.ts b/public/app/plugins/panel/graph/alert_tab_ctrl.ts index d16274efb56..82ac33e3d47 100644 --- a/public/app/plugins/panel/graph/alert_tab_ctrl.ts +++ b/public/app/plugins/panel/graph/alert_tab_ctrl.ts @@ -73,9 +73,6 @@ export class AlertTabCtrl { this.initAlertModel(); // set panel alert edit mode - this.panelCtrl.editingAlert = true; - this.panelCtrl.render(); - $scope.$on("$destroy", () => { this.panelCtrl.editingAlert = false; this.panelCtrl.render(); @@ -83,7 +80,11 @@ export class AlertTabCtrl { } initAlertModel() { - this.alert = this.panel.alert = this.panel.alert || {}; + if (!this.panel.alert) { + return; + } + + this.alert = this.panel.alert; // set defaults _.defaults(this.alert, this.defaultValues); @@ -105,6 +106,9 @@ export class AlertTabCtrl { this.query = new QueryPart(this.queryParams, alertQueryDef); this.convertThresholdsToAlertThresholds(); this.transformDef = _.findWhere(this.transforms, {type: this.alert.transform.type}); + + this.panelCtrl.editingAlert = true; + this.panelCtrl.render(); } queryUpdated() { @@ -151,18 +155,14 @@ export class AlertTabCtrl { } delete() { - this.alert = this.panel.alert = {}; - this.alert.deleted = true; - this.initAlertModel(); + delete this.panel.alert; + this.panelCtrl.editingAlert = false; + this.panelCtrl.render(); } enable() { - delete this.alert.deleted; - this.alert.enabled = true; - } - - disable() { - this.alert.enabled = false; + this.panel.alert = {}; + this.initAlertModel(); } levelsUpdated() { diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 13dcf76857d..599bbe554bf 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -5,6 +5,7 @@ define([ 'lodash', 'app/core/utils/kbn', './graph_tooltip', + './alert_handle', 'jquery.flot', 'jquery.flot.selection', 'jquery.flot.time', @@ -13,15 +14,17 @@ define([ 'jquery.flot.fillbelow', 'jquery.flot.crosshair', './jquery.flot.events', - './jquery.flot.alerts', ], -function (angular, $, moment, _, kbn, GraphTooltip) { +function (angular, $, moment, _, kbn, GraphTooltip, AlertHandle) { 'use strict'; var module = angular.module('grafana.directives'); var labelWidthCache = {}; var panelWidthCache = {}; + // systemjs export + var AlertHandleManager = AlertHandle.AlertHandleManager; + module.directive('grafanaGraph', function($rootScope, timeSrv) { return { restrict: 'A', @@ -35,6 +38,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) { var legendSideLastValue = null; var rootScope = scope.$root; var panelWidth = 0; + var alertHandles; rootScope.onAppEvent('setCrosshair', function(event, info) { // do not need to to this if event is from this panel @@ -162,6 +166,10 @@ function (angular, $, moment, _, kbn, GraphTooltip) { rightLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[1].label, rightLabel) / 2) + 'px'; } + + if (alertHandles) { + alertHandles.draw(plot); + } } function processOffsetHook(plot, gridMargin) { @@ -178,24 +186,26 @@ function (angular, $, moment, _, kbn, GraphTooltip) { panelWidth = panelWidthCache[panel.span] = elem.width(); } - if (ctrl.editingAlert) { - elem.css('margin-right', '220px'); - } else { - elem.css('margin-right', ''); - } - if (shouldAbortRender()) { return; } + // give space to alert editing + if (ctrl.editingAlert) { + if (!alertHandles) { + elem.css('margin-right', '220px'); + alertHandles = new AlertHandleManager(ctrl); + } + } else if (alertHandles) { + elem.css('margin-right', '0'); + alertHandles.cleanUp(); + alertHandles = null; + } + var stack = panel.stack ? true : null; // Populate element var options = { - alerting: { - editing: ctrl.editingAlert, - alert: panel.alert, - }, hooks: { draw: [drawHook], processOffset: [processOffsetHook], @@ -323,7 +333,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) { } function addGridThresholds(options, panel) { - if (panel.alert && panel.alert.enabled) { + if (panel.alert) { var crit = panel.alert.critical; var warn = panel.alert.warn; var critEdge = Infinity; diff --git a/public/app/plugins/panel/graph/jquery.flot.alerts.ts b/public/app/plugins/panel/graph/jquery.flot.alerts.ts deleted file mode 100644 index 630f58f85e9..00000000000 --- a/public/app/plugins/panel/graph/jquery.flot.alerts.ts +++ /dev/null @@ -1,97 +0,0 @@ -/// - -import 'jquery.flot'; -import $ from 'jquery'; -import _ from 'lodash'; - -var options = {}; - -function getHandleInnerHtml(type, op, value) { - if (op === '>') { op = '>'; } - if (op === '<') { op = '<'; } - - return ` -
-
-
- - ${op} ${value} -
`; -} - -function getFullHandleHtml(type, op, value) { - var innerTemplate = getHandleInnerHtml(type, op, value); - return ` -
- ${innerTemplate} -
- `; -} - -var dragGhostElem = document.createElement('div'); - -function dragStartHandler(evt) { - evt.dataTransfer.setDragImage(dragGhostElem, -99999, -99999); -} - -function dragEndHandler() { - console.log('drag end'); -} - -function drawAlertHandles(plot) { - var options = plot.getOptions(); - var $placeholder = plot.getPlaceholder(); - - if (!options.alerting.editing) { - $placeholder.find(".alert-handle-wrapper").remove(); - return; - } - - var alert = options.alerting.alert; - var height = plot.height(); - - function renderHandle(type, model) { - var $handle = $placeholder.find(`.alert-handle-wrapper--${type}`); - - if (!_.isNumber(model.level)) { - $handle.remove(); - return; - } - - if ($handle.length === 0) { - console.log('creating handle'); - $handle = $(getFullHandleHtml(type, model.op, model.level)); - $handle.attr('draggable', true); - $handle.bind('dragend', dragEndHandler); - $handle.bind('dragstart', dragStartHandler); - $placeholder.append($handle); - } else { - console.log('reusing handle!'); - $handle.html(getHandleInnerHtml(type, model.op, model.level)); - } - - var levelCanvasPos = plot.p2c({x: 0, y: model.level}); - var levelTopPos = Math.min(Math.max(levelCanvasPos.top, 0), height) - 6; - $handle.css({top: levelTopPos}); - } - - renderHandle('critical', alert.critical); - renderHandle('warn', alert.warn); -} - -function shutdown() { - console.log('shutdown'); -} - -function init(plot, classes) { - plot.hooks.draw.push(drawAlertHandles); - plot.hooks.shutdown.push(shutdown); -} - -$.plot.plugins.push({ - init: init, - options: options, - name: 'navigationControl', - version: '1.4' -}); - diff --git a/public/app/plugins/panel/graph/partials/tab_alerting.html b/public/app/plugins/panel/graph/partials/tab_alerting.html index a90c3b78209..c2cce48a073 100644 --- a/public/app/plugins/panel/graph/partials/tab_alerting.html +++ b/public/app/plugins/panel/graph/partials/tab_alerting.html @@ -1,116 +1,120 @@ -
-
-
Alert Query
-
-
- - -
-
- Transform using -
- + +
+
+
+
Alert Query
+
+
+ + +
+
+ Transform using +
+ +
+
+
+ Method +
+ +
+
+
+ Timespan +
-
- Method -
- +
+ +
+
Levels
+
+
+ + + Warn if + + + +
+
+ + + Critcal if + + +
-
-
- Timespan -
- - - - - - - - - - - - - - - - - - - - - - +
+
+
Execution
+
+
+ Scheduler +
+ +
+
+
+ Evaluate every + +
+
+
+
+
Notifications
+
+
+ Groups + + +
+
+
+
-
-
Execution
+
Information
+
+ Alert name + +
- Scheduler -
- -
+ Alert description
- Evaluate every - +
-
-
Notifications
-
-
- Groups - - -
-
-
-
- - -
-
Information
-
- Alert name - -
-
-
- Alert description -
-
- -
-
- - - + +
diff --git a/public/sass/components/_panel_graph.scss b/public/sass/components/_panel_graph.scss index c2c574d48dc..5c85ac4d465 100644 --- a/public/sass/components/_panel_graph.scss +++ b/public/sass/components/_panel_graph.scss @@ -319,37 +319,6 @@ position: absolute; user-select: none; - &--warn { - right: -222px; - width: 238px; - - .alert-handle-line { - float: left; - height: 2px; - width: 138px; - margin-top: 14px; - background-color: $warn; - z-index: 0; - position: relative; - } - } - - &--critical { - right: -105px; - width: 123px; - - .alert-handle-line { - float: left; - height: 2px; - width: 23px; - margin-top: 14px; - background-color: $critical; - z-index: 0; - position: relative; - } - } - - .alert-handle { z-index: 10; position: relative; @@ -357,7 +326,7 @@ padding: 0.4rem 0.6rem 0.4rem 0.4rem; background-color: $btn-inverse-bg; box-shadow: $search-shadow; - cursor: pointer; + cursor: row-resize; width: 100px; font-size: $font-size-sm; box-shadow: 4px 4px 3px 0px $body-bg; @@ -366,6 +335,7 @@ border-style: solid; border-color: $black; text-align: right; + color: $text-muted; .icon-gf { font-size: 17px; @@ -375,4 +345,37 @@ } } + .alert-handle-line { + float: left; + height: 2px; + margin-top: 13px; + z-index: 0; + position: relative; + } + + &--warn { + right: -222px; + width: 238px; + + .alert-handle-line { + width: 138px; + background-color: $warn; + } + } + + &--critical { + right: -105px; + width: 123px; + + .alert-handle-line { + width: 23px; + background-color: $critical; + } + } + + &--no-value { + .alert-handle-line { + display: none; + } + } }