From 0b919c752be7fbb44763a3bb19f0538370318f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 11 Jun 2016 14:37:33 +0200 Subject: [PATCH 1/6] feat(alerting): poc of dragable alert handles --- public/app/plugins/panel/graph/graph.js | 1 + public/sass/components/_panel_graph.scss | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 3a42df657a5..621229d9d88 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -13,6 +13,7 @@ define([ 'jquery.flot.fillbelow', 'jquery.flot.crosshair', './jquery.flot.events', + './jquery.flot.alerts', ], function (angular, $, moment, _, kbn, GraphTooltip) { 'use strict'; diff --git a/public/sass/components/_panel_graph.scss b/public/sass/components/_panel_graph.scss index b830561f816..b040b95ce31 100644 --- a/public/sass/components/_panel_graph.scss +++ b/public/sass/components/_panel_graph.scss @@ -315,3 +315,8 @@ font-size: 12px; } +.alert-handle { + padding: 0.4rem;; + background-color: $dark-4; + box-shadow: $search-shadow; +} From f387e39b67ed5d766680a4a1a90d99b11f7aa1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 11 Jun 2016 14:37:52 +0200 Subject: [PATCH 2/6] mend --- .../plugins/panel/graph/jquery.flot.alerts.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 public/app/plugins/panel/graph/jquery.flot.alerts.ts diff --git a/public/app/plugins/panel/graph/jquery.flot.alerts.ts b/public/app/plugins/panel/graph/jquery.flot.alerts.ts new file mode 100644 index 00000000000..e95e0871d92 --- /dev/null +++ b/public/app/plugins/panel/graph/jquery.flot.alerts.ts @@ -0,0 +1,39 @@ +/// + +import 'jquery.flot'; +import $ from 'jquery'; + +var options = {}; + +function getHandleTemplate(type) { + return ` +
+ + > 100 +
+ `; +} + +function drawAlertHandles(plot, canvascontext) { + var $warnHandle = $(getHandleTemplate('warn')); + + var $placeholder = plot.getPlaceholder(); + $placeholder.find(".alert-warn-handle").remove(); + $placeholder.append($warnHandle); +} + +function 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' +}); + From 1500c0e954f8078192a0b18be4d401a0f180a4c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 11 Jun 2016 22:33:02 +0200 Subject: [PATCH 3/6] feat(alerting): alert threshold handles progress --- pkg/services/alerting/alert_rule.go | 8 ++- pkg/services/alerting/commands.go | 56 ----------------- pkg/services/alerting/extractor_test.go | 4 +- pkg/services/alerting/reader.go | 2 +- .../sqlstore/alert_rule_parser_test.go | 55 ----------------- public/app/features/dashboard/viewStateSrv.js | 23 ++++--- public/app/features/panel/panel_ctrl.ts | 4 +- .../app/plugins/panel/graph/alert_tab_ctrl.ts | 46 ++++++++------ public/app/plugins/panel/graph/graph.js | 56 +++++++++++++++++ .../plugins/panel/graph/jquery.flot.alerts.ts | 55 ++++++++++++++--- .../panel/graph/partials/tab_alerting.html | 6 +- public/sass/_variables.dark.scss | 2 +- public/sass/components/_panel_graph.scss | 61 +++++++++++++++++-- public/sass/pages/_dashboard.scss | 6 -- 14 files changed, 217 insertions(+), 167 deletions(-) delete mode 100644 pkg/services/sqlstore/alert_rule_parser_test.go diff --git a/pkg/services/alerting/alert_rule.go b/pkg/services/alerting/alert_rule.go index 8678311a6a2..cfcf17f0a56 100644 --- a/pkg/services/alerting/alert_rule.go +++ b/pkg/services/alerting/alert_rule.go @@ -25,6 +25,10 @@ type AlertRule struct { Transformer Transformer } +func getTimeDurationStringToSeconds(str string) int64 { + return 60 +} + func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) { model := &AlertRule{} model.Id = ruleDef.Id @@ -39,13 +43,13 @@ func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) { Level: critical.Get("level").MustFloat64(), } - warning := ruleDef.Expression.Get("warning") + warning := ruleDef.Expression.Get("warn") model.Warning = Level{ Operator: warning.Get("op").MustString(), Level: warning.Get("level").MustFloat64(), } - model.Frequency = ruleDef.Expression.Get("frequency").MustInt64() + model.Frequency = getTimeDurationStringToSeconds(ruleDef.Expression.Get("frequency").MustString()) model.Transform = ruleDef.Expression.Get("transform").Get("type").MustString() model.TransformParams = *ruleDef.Expression.Get("transform") diff --git a/pkg/services/alerting/commands.go b/pkg/services/alerting/commands.go index 2d32396c7e1..4e269aca695 100644 --- a/pkg/services/alerting/commands.go +++ b/pkg/services/alerting/commands.go @@ -1,8 +1,6 @@ package alerting import ( - "fmt" - "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" ) @@ -38,57 +36,3 @@ func updateDashboardAlerts(cmd *UpdateDashboardAlertsCommand) error { return nil } - -func getTimeDurationStringToSeconds(str string) int64 { - return 60 -} - -func ConvetAlertModelToAlertRule(ruleDef *m.Alert) (*AlertRule, error) { - model := &AlertRule{} - model.Id = ruleDef.Id - model.OrgId = ruleDef.OrgId - model.Name = ruleDef.Name - model.Description = ruleDef.Description - model.State = ruleDef.State - - critical := ruleDef.Expression.Get("critical") - model.Critical = Level{ - Operator: critical.Get("op").MustString(), - Level: critical.Get("level").MustFloat64(), - } - - warning := ruleDef.Expression.Get("warning") - model.Warning = Level{ - Operator: warning.Get("op").MustString(), - Level: warning.Get("level").MustFloat64(), - } - - model.Frequency = getTimeDurationStringToSeconds(ruleDef.Expression.Get("frequency").MustString()) - model.Transform = ruleDef.Expression.Get("transform").Get("type").MustString() - model.TransformParams = *ruleDef.Expression.Get("transform") - - if model.Transform == "aggregation" { - model.Transformer = &AggregationTransformer{ - Method: ruleDef.Expression.Get("transform").Get("method").MustString(), - } - } - - query := ruleDef.Expression.Get("query") - model.Query = AlertQuery{ - Query: query.Get("query").MustString(), - DatasourceId: query.Get("datasourceId").MustInt64(), - From: query.Get("from").MustString(), - To: query.Get("to").MustString(), - Aggregator: query.Get("agg").MustString(), - } - - if model.Query.Query == "" { - return nil, fmt.Errorf("missing query.query") - } - - if model.Query.DatasourceId == 0 { - return nil, fmt.Errorf("missing query.datasourceId") - } - - return model, nil -} diff --git a/pkg/services/alerting/extractor_test.go b/pkg/services/alerting/extractor_test.go index 069489dfb23..6eda321f0dc 100644 --- a/pkg/services/alerting/extractor_test.go +++ b/pkg/services/alerting/extractor_test.go @@ -55,7 +55,7 @@ func TestAlertRuleExtraction(t *testing.T) { "method": "avg", "type": "aggregation" }, - "warning": { + "warn": { "level": 10, "op": ">" } @@ -90,7 +90,7 @@ func TestAlertRuleExtraction(t *testing.T) { "method": "avg", "name": "aggregation" }, - "warning": { + "warn": { "level": 10, "op": ">" } diff --git a/pkg/services/alerting/reader.go b/pkg/services/alerting/reader.go index 7f8f6b2c5de..db7da930746 100644 --- a/pkg/services/alerting/reader.go +++ b/pkg/services/alerting/reader.go @@ -49,7 +49,7 @@ func (arr *AlertRuleReader) Fetch() []*AlertRule { res := make([]*AlertRule, len(cmd.Result)) for i, ruleDef := range cmd.Result { - model, _ := ConvetAlertModelToAlertRule(ruleDef) + model, _ := NewAlertRuleFromDBModel(ruleDef) res[i] = model } diff --git a/pkg/services/sqlstore/alert_rule_parser_test.go b/pkg/services/sqlstore/alert_rule_parser_test.go deleted file mode 100644 index 8ec7c24429b..00000000000 --- a/pkg/services/sqlstore/alert_rule_parser_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package sqlstore - -import ( - "testing" - - "github.com/grafana/grafana/pkg/components/simplejson" - m "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/alerting" - . "github.com/smartystreets/goconvey/convey" -) - -func TestAlertRuleModelParsing(t *testing.T) { - - Convey("Parsing alertRule from expression", t, func() { - alertRuleDAO := &m.Alert{} - json, _ := simplejson.NewJson([]byte(` - { - "frequency": 10, - "warning": { - "op": ">", - "level": 10 - }, - "critical": { - "op": ">", - "level": 20 - }, - "query": { - "refId": "A", - "from": "5m", - "to": "now", - "datasourceId": 1, - "query": "aliasByNode(statsd.fakesite.counters.session_start.*.count, 4)" - }, - "transform": { - "type": "aggregation", - "method": "avg" - } - }`)) - - alertRuleDAO.Name = "Test" - alertRuleDAO.Expression = json - rule, _ := alerting.ConvetAlertModelToAlertRule(alertRuleDAO) - - Convey("Confirm that all properties are set", func() { - So(rule.Query.Query, ShouldEqual, "aliasByNode(statsd.fakesite.counters.session_start.*.count, 4)") - So(rule.Query.From, ShouldEqual, "5m") - So(rule.Query.To, ShouldEqual, "now") - So(rule.Query.DatasourceId, ShouldEqual, 1) - So(rule.Warning.Level, ShouldEqual, 10) - So(rule.Warning.Operator, ShouldEqual, ">") - So(rule.Critical.Level, ShouldEqual, 20) - So(rule.Critical.Operator, ShouldEqual, ">") - }) - }) -} diff --git a/public/app/features/dashboard/viewStateSrv.js b/public/app/features/dashboard/viewStateSrv.js index 035bfb6ae6e..fe1277c59c0 100644 --- a/public/app/features/dashboard/viewStateSrv.js +++ b/public/app/features/dashboard/viewStateSrv.js @@ -120,25 +120,28 @@ function (angular, _, $) { if (this.panelScopes.length === 0) { return; } if (this.dashboard.meta.fullscreen) { - if (this.fullscreenPanel) { - this.leaveFullscreen(false); - } var panelScope = this.getPanelScope(this.state.panelId); - // panel could be about to be created/added and scope does - // not exist yet if (!panelScope) { return; } + if (this.fullscreenPanel) { + // if already fullscreen + if (this.fullscreenPanel === panelScope) { + return; + } else { + this.leaveFullscreen(false); + } + } + if (!panelScope.ctrl.editModeInitiated) { panelScope.ctrl.initEditMode(); } - this.enterFullscreen(panelScope); - return; - } - - if (this.fullscreenPanel) { + if (!panelScope.ctrl.fullscreen) { + this.enterFullscreen(panelScope); + } + } else if (this.fullscreenPanel) { this.leaveFullscreen(true); } }; diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 0f253b5048a..bcb1980f854 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -152,8 +152,8 @@ export class PanelCtrl { calculatePanelHeight() { if (this.fullscreen) { var docHeight = $(window).height(); - var editHeight = Math.floor(docHeight * 0.3); - var fullscreenHeight = Math.floor(docHeight * 0.7); + var editHeight = Math.floor(docHeight * 0.4); + var fullscreenHeight = Math.floor(docHeight * 0.6); this.containerHeight = this.editMode ? editHeight : fullscreenHeight; } else { this.containerHeight = this.panel.height || this.row.height; diff --git a/public/app/plugins/panel/graph/alert_tab_ctrl.ts b/public/app/plugins/panel/graph/alert_tab_ctrl.ts index 3f83e6db38e..d16274efb56 100644 --- a/public/app/plugins/panel/graph/alert_tab_ctrl.ts +++ b/public/app/plugins/panel/graph/alert_tab_ctrl.ts @@ -50,7 +50,7 @@ export class AlertTabCtrl { notify: [], enabled: false, scheduler: 1, - warning: { op: '>', level: undefined }, + warn: { op: '>', level: undefined }, critical: { op: '>', level: undefined }, query: { refId: 'A', @@ -70,8 +70,16 @@ export class AlertTabCtrl { $scope.ctrl = this; this.metricTargets = this.panel.targets.map(val => val); - this.initAlertModel(); + + // set panel alert edit mode + this.panelCtrl.editingAlert = true; + this.panelCtrl.render(); + + $scope.$on("$destroy", () => { + this.panelCtrl.editingAlert = false; + this.panelCtrl.render(); + }); } initAlertModel() { @@ -125,21 +133,21 @@ export class AlertTabCtrl { } convertThresholdsToAlertThresholds() { - if (this.panel.grid - && this.panel.grid.threshold1 - && this.alert.warnLevel === undefined - ) { - this.alert.warning.op = '>'; - this.alert.warning.level = this.panel.grid.threshold1; - } - - if (this.panel.grid - && this.panel.grid.threshold2 - && this.alert.critical.level === undefined - ) { - this.alert.critical.op = '>'; - this.alert.critical.level = this.panel.grid.threshold2; - } + // if (this.panel.grid + // && this.panel.grid.threshold1 + // && this.alert.warnLevel === undefined + // ) { + // this.alert.warning.op = '>'; + // this.alert.warning.level = this.panel.grid.threshold1; + // } + // + // if (this.panel.grid + // && this.panel.grid.threshold2 + // && this.alert.critical.level === undefined + // ) { + // this.alert.critical.op = '>'; + // this.alert.critical.level = this.panel.grid.threshold2; + // } } delete() { @@ -156,6 +164,10 @@ export class AlertTabCtrl { disable() { this.alert.enabled = false; } + + levelsUpdated() { + this.panelCtrl.render(); + } } /** @ngInject */ diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 621229d9d88..23c9cd65a9f 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -169,6 +169,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) { var right = panel.yaxes[1]; if (left.show && left.label) { gridMargin.left = 20; } if (right.show && right.label) { gridMargin.right = 20; } + } // Function for rendering panel @@ -178,6 +179,12 @@ 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; } @@ -186,6 +193,10 @@ function (angular, $, moment, _, kbn, GraphTooltip) { // Populate element var options = { + alerting: { + editing: ctrl.editingAlert, + alert: panel.alert, + }, hooks: { draw: [drawHook], processOffset: [processOffsetHook], @@ -260,6 +271,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) { function callPlot(incrementRenderCounter) { try { + console.log('rendering'); $.plot(elem, sortedSeries, options); } catch (e) { console.log('flotcharts error', e); @@ -312,6 +324,50 @@ function (angular, $, moment, _, kbn, GraphTooltip) { } function addGridThresholds(options, panel) { + if (panel.alert && panel.alert.enabled) { + var crit = panel.alert.critical; + var warn = panel.alert.warn; + var critEdge = Infinity; + var warnEdge = crit.level; + + if (_.isNumber(crit.level)) { + if (crit.op === '<') { + critEdge = -Infinity; + } + + // fill + options.grid.markings.push({ + yaxis: {from: crit.level, to: critEdge}, + color: 'rgba(234, 112, 112, 0.10)', + }); + + // line + options.grid.markings.push({ + yaxis: {from: crit.level, to: crit.level}, + color: '#ed2e18' + }); + } + + if (_.isNumber(warn.level)) { + // if (warn.op === '<') { + // } + + // fill + options.grid.markings.push({ + yaxis: {from: warn.level, to: warnEdge}, + color: 'rgba(216, 200, 27, 0.10)', + }); + + // line + options.grid.markings.push({ + yaxis: {from: warn.level, to: warn.level}, + color: '#F79520' + }); + } + + return; + } + if (_.isNumber(panel.grid.threshold1)) { var limit1 = panel.grid.thresholdLine ? panel.grid.threshold1 : (panel.grid.threshold2 || null); options.grid.markings.push({ diff --git a/public/app/plugins/panel/graph/jquery.flot.alerts.ts b/public/app/plugins/panel/graph/jquery.flot.alerts.ts index e95e0871d92..65229b31065 100644 --- a/public/app/plugins/panel/graph/jquery.flot.alerts.ts +++ b/public/app/plugins/panel/graph/jquery.flot.alerts.ts @@ -2,24 +2,63 @@ import 'jquery.flot'; import $ from 'jquery'; +import _ from 'lodash'; var options = {}; -function getHandleTemplate(type) { +function getHandleTemplate(type, op, value) { + if (op === '>') { op = '>'; } + if (op === '<') { op = '<'; } + return ` -
- - > 100 +
+
+
+
+ + ${op} ${value} +
`; } -function drawAlertHandles(plot, canvascontext) { - var $warnHandle = $(getHandleTemplate('warn')); +function drawAlertHandles(plot) { + var options = plot.getOptions(); var $placeholder = plot.getPlaceholder(); - $placeholder.find(".alert-warn-handle").remove(); - $placeholder.append($warnHandle); + + if (!options.alerting.editing) { + $placeholder.find(".alert-handle").remove(); + return; + } + + var alert = options.alerting.alert; + var height = plot.height(); + + function renderHandle(type, model) { + var $handle = $placeholder.find(`.alert-handle-${type}`); + + if (!_.isNumber(model.level)) { + $handle.remove(); + return; + } + + if ($handle.length === 0) { + $handle = $(getHandleTemplate(type, model.op, model.level)); + $placeholder.append($handle); + } else { + $handle.html(getHandleTemplate(type, model.op, model.level)); + } + + var levelCanvasPos = plot.p2c({x: 0, y: model.level}); + console.log('canvas level pos', levelCanvasPos.top); + + 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() { diff --git a/public/app/plugins/panel/graph/partials/tab_alerting.html b/public/app/plugins/panel/graph/partials/tab_alerting.html index 2b7b4e112e3..a81bbe2f996 100644 --- a/public/app/plugins/panel/graph/partials/tab_alerting.html +++ b/public/app/plugins/panel/graph/partials/tab_alerting.html @@ -44,8 +44,8 @@ Warn if - - + +
@@ -53,7 +53,7 @@ Critcal if - +
diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 047e50faa73..99070cad837 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -44,7 +44,7 @@ $brand-text-highlight: #f7941d; // Status colors // ------------------------- $online: #10a345; -$warn: #ffc03c; +$warn: #F79520; $critical: #ed2e18; // Scaffolding diff --git a/public/sass/components/_panel_graph.scss b/public/sass/components/_panel_graph.scss index b040b95ce31..20b9376b7dd 100644 --- a/public/sass/components/_panel_graph.scss +++ b/public/sass/components/_panel_graph.scss @@ -315,8 +315,61 @@ font-size: 12px; } -.alert-handle { - padding: 0.4rem;; - background-color: $dark-4; - box-shadow: $search-shadow; +.alert-handle-wrapper { + position: absolute; + + &--warn { + right: -221px; + 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; + float: right; + padding: 0.4rem;; + background-color: $btn-inverse-bg; + box-shadow: $search-shadow; + cursor: pointer; + width: 100px; + font-size: $font-size-sm; + box-shadow: 4px 4px 3px 0px $body-bg; + border-radius: 4px; + border-width: 0 1px 1px 0; + border-style: solid; + border-color: $black; + + .icon-gf { + font-size: 17px; + position: relative; + top: 2px; + } + } + } diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 8fb1e6bcdaa..eec86e78234 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -197,12 +197,6 @@ div.flot-text { bottom: 0; } -.panel-fullscreen { - .panel-title-container { - padding: 8px; - } -} - .panel-full-edit { margin-top: 20px; margin-bottom: 20px; From ec640bd5dab298ae47aa7d8ee0324b34b9e814fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 11 Jun 2016 23:31:49 +0200 Subject: [PATCH 4/6] feat(alerting): more work on alerting ui --- public/app/plugins/panel/graph/graph.js | 1 - .../plugins/panel/graph/jquery.flot.alerts.ts | 22 ++++++++-- .../panel/graph/partials/tab_alerting.html | 44 +++++++++---------- public/sass/components/_panel_graph.scss | 11 +++-- public/vendor/flot/jquery.flot.js | 2 +- 5 files changed, 49 insertions(+), 31 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 23c9cd65a9f..13dcf76857d 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -169,7 +169,6 @@ function (angular, $, moment, _, kbn, GraphTooltip) { var right = panel.yaxes[1]; if (left.show && left.label) { gridMargin.left = 20; } if (right.show && right.label) { gridMargin.right = 20; } - } // Function for rendering panel diff --git a/public/app/plugins/panel/graph/jquery.flot.alerts.ts b/public/app/plugins/panel/graph/jquery.flot.alerts.ts index 65229b31065..1ce4783cdf9 100644 --- a/public/app/plugins/panel/graph/jquery.flot.alerts.ts +++ b/public/app/plugins/panel/graph/jquery.flot.alerts.ts @@ -22,6 +22,17 @@ function getHandleTemplate(type, op, value) { `; } +var dragGhostElem = document.createElement('div'); + +function dragStartHandler(evt) { + evt.dataTransfer.setDragImage(dragGhostElem, -99999, -99999); +} + +function dragEndHandler() { + console.log('drag end'); +} + +var past; function drawAlertHandles(plot) { var options = plot.getOptions(); @@ -36,7 +47,7 @@ function drawAlertHandles(plot) { var height = plot.height(); function renderHandle(type, model) { - var $handle = $placeholder.find(`.alert-handle-${type}`); + var $handle = $placeholder.find(`.alert-handle-wrapper--${type}`); if (!_.isNumber(model.level)) { $handle.remove(); @@ -44,15 +55,19 @@ function drawAlertHandles(plot) { } if ($handle.length === 0) { + console.log('not found'); $handle = $(getHandleTemplate(type, model.op, model.level)); + $handle.attr('draggable', true); + $handle.bind('dragend', dragEndHandler); + $handle.bind('dragstart', dragStartHandler); $placeholder.append($handle); + console.log('registering drag events'); } else { + console.log('reusing!'); $handle.html(getHandleTemplate(type, model.op, model.level)); } var levelCanvasPos = plot.p2c({x: 0, y: model.level}); - console.log('canvas level pos', levelCanvasPos.top); - var levelTopPos = Math.min(Math.max(levelCanvasPos.top, 0), height) - 6; $handle.css({top: levelTopPos}); } @@ -62,6 +77,7 @@ function drawAlertHandles(plot) { } function shutdown() { + console.log('shutdown'); } function init(plot, classes) { diff --git a/public/app/plugins/panel/graph/partials/tab_alerting.html b/public/app/plugins/panel/graph/partials/tab_alerting.html index a81bbe2f996..a90c3b78209 100644 --- a/public/app/plugins/panel/graph/partials/tab_alerting.html +++ b/public/app/plugins/panel/graph/partials/tab_alerting.html @@ -36,28 +36,28 @@ -
-
Levels
-
-
- - - Warn if - - - -
-
- - - Critcal if - - - -
-
-
- + + + + + + + + + + + + + + + + + + + + + +
diff --git a/public/sass/components/_panel_graph.scss b/public/sass/components/_panel_graph.scss index 20b9376b7dd..4eb8b703534 100644 --- a/public/sass/components/_panel_graph.scss +++ b/public/sass/components/_panel_graph.scss @@ -317,9 +317,10 @@ .alert-handle-wrapper { position: absolute; + user-select: none; &--warn { - right: -221px; + right: -111px; width: 238px; .alert-handle-line { @@ -334,7 +335,7 @@ } &--critical { - right: -105px; + right: -54px; width: 123px; .alert-handle-line { @@ -353,7 +354,7 @@ z-index: 10; position: relative; float: right; - padding: 0.4rem;; + padding: 0.4rem 0.6rem 0.4rem 0.4rem; background-color: $btn-inverse-bg; box-shadow: $search-shadow; cursor: pointer; @@ -364,11 +365,13 @@ border-width: 0 1px 1px 0; border-style: solid; border-color: $black; + text-align: right; .icon-gf { font-size: 17px; position: relative; - top: 2px; + top: 0px; + float: left; } } diff --git a/public/vendor/flot/jquery.flot.js b/public/vendor/flot/jquery.flot.js index 380030a77c9..e2c460ddbd0 100644 --- a/public/vendor/flot/jquery.flot.js +++ b/public/vendor/flot/jquery.flot.js @@ -1322,7 +1322,7 @@ Licensed under the MIT license. placeholder.css("padding", 0) // padding messes up the positioning .children().filter(function(){ - return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base'); + return $(this).hasClass("flot-text"); }).remove(); if (placeholder.css("position") == 'static') From 5b6fb3b124a058ab31e97f90c157d039cf245bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 11 Jun 2016 23:52:25 +0200 Subject: [PATCH 5/6] feat(alerting): level handle progress --- .../plugins/panel/graph/jquery.flot.alerts.ts | 35 ++++++++++--------- public/sass/components/_panel_graph.scss | 4 +-- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/public/app/plugins/panel/graph/jquery.flot.alerts.ts b/public/app/plugins/panel/graph/jquery.flot.alerts.ts index 1ce4783cdf9..630f58f85e9 100644 --- a/public/app/plugins/panel/graph/jquery.flot.alerts.ts +++ b/public/app/plugins/panel/graph/jquery.flot.alerts.ts @@ -6,18 +6,24 @@ import _ from 'lodash'; var options = {}; -function getHandleTemplate(type, op, value) { +function getHandleInnerHtml(type, op, value) { if (op === '>') { op = '>'; } if (op === '<') { op = '<'; } - return ` + return ` +
+
+
+ + ${op} ${value} +
`; +} + +function getFullHandleHtml(type, op, value) { + var innerTemplate = getHandleInnerHtml(type, op, value); + return `
-
-
-
- - ${op} ${value} -
+ ${innerTemplate}
`; } @@ -32,14 +38,12 @@ function dragEndHandler() { console.log('drag end'); } -var past; - function drawAlertHandles(plot) { var options = plot.getOptions(); var $placeholder = plot.getPlaceholder(); if (!options.alerting.editing) { - $placeholder.find(".alert-handle").remove(); + $placeholder.find(".alert-handle-wrapper").remove(); return; } @@ -55,16 +59,15 @@ function drawAlertHandles(plot) { } if ($handle.length === 0) { - console.log('not found'); - $handle = $(getHandleTemplate(type, model.op, model.level)); + 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); - console.log('registering drag events'); } else { - console.log('reusing!'); - $handle.html(getHandleTemplate(type, model.op, model.level)); + console.log('reusing handle!'); + $handle.html(getHandleInnerHtml(type, model.op, model.level)); } var levelCanvasPos = plot.p2c({x: 0, y: model.level}); diff --git a/public/sass/components/_panel_graph.scss b/public/sass/components/_panel_graph.scss index 4eb8b703534..c2c574d48dc 100644 --- a/public/sass/components/_panel_graph.scss +++ b/public/sass/components/_panel_graph.scss @@ -320,7 +320,7 @@ user-select: none; &--warn { - right: -111px; + right: -222px; width: 238px; .alert-handle-line { @@ -335,7 +335,7 @@ } &--critical { - right: -54px; + right: -105px; width: 123px; .alert-handle-line { 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 6/6] 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; + } + } }