From a6c609477577d7d95bcc43d684647bf01835f06d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 22 Jul 2016 16:45:17 +0200 Subject: [PATCH] feat(alerting): started reworking notifications --- ' | 200 ------------------ pkg/api/alerting.go | 22 +- pkg/api/dtos/alerting.go | 4 +- pkg/models/alert_notifications.go | 46 ++-- pkg/models/annotations.go | 22 ++ pkg/services/sqlstore/alert_notification.go | 119 ++++------- .../alerting/notification_edit_ctrl.ts | 5 +- .../alerting/partials/notification_edit.html | 23 +- 8 files changed, 102 insertions(+), 339 deletions(-) delete mode 100644 ' create mode 100644 pkg/models/annotations.go diff --git a/' b/' deleted file mode 100644 index 2e342921a63..00000000000 --- a/' +++ /dev/null @@ -1,200 +0,0 @@ - -/** Created by: Alex Wendland (me@alexwendland.com), 2014-08-06 - * - * angular-json-tree - * - * Directive for creating a tree-view out of a JS Object. Only loads - * sub-nodes on demand in order to improve performance of rendering large - * objects. - * - * Attributes: - * - object (Object, 2-way): JS object to build the tree from - * - start-expanded (Boolean, 1-way, ?=true): should the tree default to expanded - * - * Usage: - * // In the controller - * scope.someObject = { - * test: 'hello', - * array: [1,1,2,3,5,8] - * }; - * // In the html - * - * - * Dependencies: - * - utils (json-tree.js) - * - ajsRecursiveDirectiveHelper (json-tree.js) - * - * Test: json-tree-test.js - */ - -import angular from 'angular'; -import coreModule from 'app/core/core_module'; - -var utils = { - /* See link for possible type values to check against. - * http://stackoverflow.com/questions/4622952/json-object-containing-array - * - * Value Class Type - * ------------------------------------- - * "foo" String string - * new String("foo") String object - * 1.2 Number number - * new Number(1.2) Number object - * true Boolean boolean - * new Boolean(true) Boolean object - * new Date() Date object - * new Error() Error object - * [1,2,3] Array object - * new Array(1, 2, 3) Array object - * new Function("") Function function - * /abc/g RegExp object (function in Nitro/V8) - * new RegExp("meow") RegExp object (function in Nitro/V8) - * {} Object object - * new Object() Object object - */ - is: function is(obj, clazz) { - return Object.prototype.toString.call(obj).slice(8, -1) === clazz; - }, - - // See above for possible values - whatClass: function whatClass(obj) { - return Object.prototype.toString.call(obj).slice(8, -1); - }, - - // Iterate over an objects keyset - forKeys: function forKeys(obj, f) { - for (var key in obj) { - if (obj.hasOwnProperty(key) && typeof obj[key] !== 'function') { - if (f(key, obj[key])) { - break; - } - } - } - } -}; - -coreModule.directive('jsonTree', [function jsonTreeDirective() { - return { - restrict: 'E', - scope: { - object: '=', - startExpanded: '=', - rootName: '@', - }, - template: '' - }; -}]); - -coreModule.directive('jsonNode', ['ajsRecursiveDirectiveHelper', function jsonNodeDirective(ajsRecursiveDirectiveHelper) { - return { - restrict: 'E', - scope: { - key: '=', - value: '=', - startExpanded: '=' - }, - compile: function jsonNodeDirectiveCompile(elem) { - return ajsRecursiveDirectiveHelper.compile(elem, this); - }, - template: ' {{key}}' + - ' {{value}}' + - ' ' + - ' {preview}}' + - ' ', - pre: function jsonNodeDirectiveLink(scope, elem, attrs) { - // Set value's type as Class for CSS styling - elem.addClass(utils.whatClass(scope.value).toLowerCase()); - // If the value is an Array or Object, use expandable view type - if (utils.is(scope.value, 'Object') || utils.is(scope.value, 'Array')) { - scope.isExpandable = true; - // Add expandable class for CSS usage - elem.addClass('expandable'); - // Setup preview text - var isArray = utils.is(scope.value, 'Array'); - scope.preview = isArray ? '[ ' : '{ '; - utils.forKeys(scope.value, function jsonNodeDirectiveLinkForKeys(key, value) { - if (isArray) { - scope.preview += value + ', '; - } else { - scope.preview += key + ': ' + value + ', '; - } - }); - scope.preview = scope.preview.substring(0, scope.preview.length - (scope.preview.length > 2 ? 2 : 0)) + (isArray ? ' ]' : ' }'); - // If directive initially has isExpanded set, also set shouldRender to true - if (scope.startExpanded) { - scope.shouldRender = true; - elem.addClass('expanded'); - } - // Setup isExpanded state handling - scope.isExpanded = scope.startExpanded ? scope.startExpanded() : false; - scope.toggleExpanded = function jsonNodeDirectiveToggleExpanded() { - scope.isExpanded = !scope.isExpanded; - if (scope.isExpanded) { - elem.addClass('expanded'); - } else { - elem.removeClass('expanded'); - } - // For delaying subnode render until requested - scope.shouldRender = true; - }; - } else { - scope.isExpandable = false; - // Add expandable class for CSS usage - elem.addClass('not-expandable'); - } - } - }; -}]); - -/** Added by: Alex Wendland (me@alexwendland.com), 2014-08-09 - * Source: http://stackoverflow.com/questions/14430655/recursion-in-angular-directives - * - * Used to allow for recursion within directives - */ -coreModule.factory('ajsRecursiveDirectiveHelper', ['$compile', function RecursiveDirectiveHelper($compile) { - return { - /** - * Manually compiles the element, fixing the recursion loop. - * @param element - * @param [link] A post-link function, or an object with function(s) registered via pre and post properties. - * @returns An object containing the linking functions. - */ - compile: function RecursiveDirectiveHelperCompile(element, link) { - // Normalize the link parameter - if (angular.isFunction(link)) { - link = { - post: link - }; - } - - // Break the recursion loop by removing the contents - var contents = element.contents().remove(); - var compiledContents; - return { - pre: (link && link.pre) ? link.pre : null, - /** - * Compiles and re-adds the contents - */ - post: function RecursiveDirectiveHelperCompilePost(scope, element) { - // Compile the contents - if (!compiledContents) { - compiledContents = $compile(contents); - } - // Re-add the compiled contents to the element - compiledContents(scope, function (clone) { - element.append(clone); - }); - - // Call the post-linking function, if any - if (link && link.post) { - link.post.apply(null, arguments); - } - } - }; - } - }; -}]); diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index ef845337d7b..634e9650f01 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -39,10 +39,10 @@ func GetAlerts(c *middleware.Context) Response { } dashboardIds := make([]int64, 0) - alertDTOs := make([]*dtos.AlertRuleDTO, 0) + alertDTOs := make([]*dtos.AlertRule, 0) for _, alert := range query.Result { dashboardIds = append(dashboardIds, alert.DashboardId) - alertDTOs = append(alertDTOs, &dtos.AlertRuleDTO{ + alertDTOs = append(alertDTOs, &dtos.AlertRule{ Id: alert.Id, DashboardId: alert.DashboardId, PanelId: alert.PanelId, @@ -176,18 +176,16 @@ func DelAlert(c *middleware.Context) Response { // } func GetAlertNotifications(c *middleware.Context) Response { - query := &models.GetAlertNotificationQuery{ - OrgID: c.OrgId, - } + query := &models.GetAlertNotificationsQuery{OrgId: c.OrgId} if err := bus.Dispatch(query); err != nil { return ApiError(500, "Failed to get alert notifications", err) } - var result []dtos.AlertNotificationDTO + var result []dtos.AlertNotification for _, notification := range query.Result { - result = append(result, dtos.AlertNotificationDTO{ + result = append(result, dtos.AlertNotification{ Id: notification.Id, Name: notification.Name, Type: notification.Type, @@ -200,8 +198,8 @@ func GetAlertNotifications(c *middleware.Context) Response { } func GetAlertNotificationById(c *middleware.Context) Response { - query := &models.GetAlertNotificationQuery{ - OrgID: c.OrgId, + query := &models.GetAlertNotificationsQuery{ + OrgId: c.OrgId, Id: c.ParamsInt64("notificationId"), } @@ -213,7 +211,7 @@ func GetAlertNotificationById(c *middleware.Context) Response { } func CreateAlertNotification(c *middleware.Context, cmd models.CreateAlertNotificationCommand) Response { - cmd.OrgID = c.OrgId + cmd.OrgId = c.OrgId if err := bus.Dispatch(&cmd); err != nil { return ApiError(500, "Failed to create alert notification", err) @@ -223,7 +221,7 @@ func CreateAlertNotification(c *middleware.Context, cmd models.CreateAlertNotifi } func UpdateAlertNotification(c *middleware.Context, cmd models.UpdateAlertNotificationCommand) Response { - cmd.OrgID = c.OrgId + cmd.OrgId = c.OrgId if err := bus.Dispatch(&cmd); err != nil { return ApiError(500, "Failed to update alert notification", err) @@ -242,5 +240,5 @@ func DeleteAlertNotification(c *middleware.Context) Response { return ApiError(500, "Failed to delete alert notification", err) } - return Json(200, map[string]interface{}{"notificationId": cmd.Id}) + return ApiSuccess("Notification deleted") } diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go index 35fc3f9e638..91678600a4e 100644 --- a/pkg/api/dtos/alerting.go +++ b/pkg/api/dtos/alerting.go @@ -7,7 +7,7 @@ import ( m "github.com/grafana/grafana/pkg/models" ) -type AlertRuleDTO struct { +type AlertRule struct { Id int64 `json:"id"` DashboardId int64 `json:"dashboardId"` PanelId int64 `json:"panelId"` @@ -19,7 +19,7 @@ type AlertRuleDTO struct { DashbboardUri string `json:"dashboardUri"` } -type AlertNotificationDTO struct { +type AlertNotification struct { Id int64 `json:"id"` Name string `json:"name"` Type string `json:"type"` diff --git a/pkg/models/alert_notifications.go b/pkg/models/alert_notifications.go index 3ac23438b8e..464d6dc88da 100644 --- a/pkg/models/alert_notifications.go +++ b/pkg/models/alert_notifications.go @@ -7,34 +7,31 @@ import ( ) type AlertNotification struct { - Id int64 `json:"id"` - OrgId int64 `json:"-"` - Name string `json:"name"` - Type string `json:"type"` - AlwaysExecute bool `json:"alwaysExecute"` - Settings *simplejson.Json `json:"settings"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` + Id int64 `json:"id"` + OrgId int64 `json:"-"` + Name string `json:"name"` + Type string `json:"type"` + Settings *simplejson.Json `json:"settings"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` } type CreateAlertNotificationCommand struct { - Name string `json:"name" binding:"Required"` - Type string `json:"type" binding:"Required"` - AlwaysExecute bool `json:"alwaysExecute"` - OrgID int64 `json:"-"` - Settings *simplejson.Json `json:"settings"` + Name string `json:"name" binding:"Required"` + Type string `json:"type" binding:"Required"` + Settings *simplejson.Json `json:"settings"` + OrgId int64 `json:"-"` Result *AlertNotification } type UpdateAlertNotificationCommand struct { - Id int64 `json:"id" binding:"Required"` - Name string `json:"name" binding:"Required"` - Type string `json:"type" binding:"Required"` - AlwaysExecute bool `json:"alwaysExecute"` - OrgID int64 `json:"-"` - Settings *simplejson.Json `json:"settings" binding:"Required"` + Id int64 `json:"id" binding:"Required"` + Name string `json:"name" binding:"Required"` + Type string `json:"type" binding:"Required"` + Settings *simplejson.Json `json:"settings" binding:"Required"` + OrgId int64 `json:"-"` Result *AlertNotification } @@ -43,12 +40,11 @@ type DeleteAlertNotificationCommand struct { OrgId int64 } -type GetAlertNotificationQuery struct { - Name string - Id int64 - Ids []int64 - OrgID int64 - IncludeAlwaysExecute bool +type GetAlertNotificationsQuery struct { + Name string + Id int64 + Ids []int64 + OrgId int64 Result []*AlertNotification } diff --git a/pkg/models/annotations.go b/pkg/models/annotations.go new file mode 100644 index 00000000000..149181fe81a --- /dev/null +++ b/pkg/models/annotations.go @@ -0,0 +1,22 @@ +package models + +import ( + "time" + + "github.com/grafana/grafana/pkg/components/simplejson" +) + +type AnnotationType string + +type AnnotationEvent struct { + Id int64 + OrgId int64 + Type AnnotationType + Title string + Text string + AlertId int64 + UserId int64 + Timestamp time.Time + + Data *simplejson.Json +} diff --git a/pkg/services/sqlstore/alert_notification.go b/pkg/services/sqlstore/alert_notification.go index 3f4b9a03406..8a3c8543f91 100644 --- a/pkg/services/sqlstore/alert_notification.go +++ b/pkg/services/sqlstore/alert_notification.go @@ -3,7 +3,6 @@ package sqlstore import ( "bytes" "fmt" - "strconv" "time" "github.com/go-xorm/xorm" @@ -31,11 +30,11 @@ func DeleteAlertNotification(cmd *m.DeleteAlertNotificationCommand) error { }) } -func AlertNotificationQuery(query *m.GetAlertNotificationQuery) error { +func AlertNotificationQuery(query *m.GetAlertNotificationsQuery) error { return getAlertNotifications(query, x.NewSession()) } -func getAlertNotifications(query *m.GetAlertNotificationQuery, sess *xorm.Session) error { +func getAlertNotifications(query *m.GetAlertNotificationsQuery, sess *xorm.Session) error { var sql bytes.Buffer params := make([]interface{}, 0) @@ -43,16 +42,15 @@ func getAlertNotifications(query *m.GetAlertNotificationQuery, sess *xorm.Sessio alert_notification.id, alert_notification.org_id, alert_notification.name, - alert_notification.type, + alert_notification.type, alert_notification.created, - alert_notification.updated, - alert_notification.settings, - alert_notification.always_execute + alert_notification.updated, + alert_notification.settings FROM alert_notification `) sql.WriteString(` WHERE alert_notification.org_id = ?`) - params = append(params, query.OrgID) + params = append(params, query.OrgId) if query.Name != "" { sql.WriteString(` AND alert_notification.name = ?`) @@ -61,60 +59,26 @@ func getAlertNotifications(query *m.GetAlertNotificationQuery, sess *xorm.Sessio if query.Id != 0 { sql.WriteString(` AND alert_notification.id = ?`) - params = append(params, strconv.Itoa(int(query.Id))) + params = append(params, query.Id) } if len(query.Ids) > 0 { - sql.WriteString(` AND (`) - - for i, id := range query.Ids { - if i != 0 { - sql.WriteString(` OR`) - } - sql.WriteString(` alert_notification.id = ?`) - params = append(params, id) - } - - sql.WriteString(`)`) + sql.WriteString(` AND alert_notification.id IN (?)`) + params = append(params, query.Ids) } - var searches []*m.AlertNotification - if err := sess.Sql(sql.String(), params...).Find(&searches); err != nil { + results := make([]*m.AlertNotification, 0) + if err := sess.Sql(sql.String(), params...).Find(&results); err != nil { return err } - var result []*m.AlertNotification - var def []*m.AlertNotification - if query.IncludeAlwaysExecute { - - if err := sess.Where("org_id = ? AND always_execute = 1", query.OrgID).Find(&def); err != nil { - return err - } - - result = append(result, def...) - } - - for _, s := range searches { - canAppend := true - for _, d := range result { - if d.Id == s.Id { - canAppend = false - break - } - } - - if canAppend { - result = append(result, s) - } - } - - query.Result = result + query.Result = results return nil } func CreateAlertNotificationCommand(cmd *m.CreateAlertNotificationCommand) error { return inTransaction(func(sess *xorm.Session) error { - existingQuery := &m.GetAlertNotificationQuery{OrgID: cmd.OrgID, Name: cmd.Name, IncludeAlwaysExecute: false} + existingQuery := &m.GetAlertNotificationsQuery{OrgId: cmd.OrgId, Name: cmd.Name} err := getAlertNotifications(existingQuery, sess) if err != nil { @@ -126,18 +90,15 @@ func CreateAlertNotificationCommand(cmd *m.CreateAlertNotificationCommand) error } alertNotification := &m.AlertNotification{ - OrgId: cmd.OrgID, - Name: cmd.Name, - Type: cmd.Type, - Created: time.Now(), - Settings: cmd.Settings, - Updated: time.Now(), - AlwaysExecute: cmd.AlwaysExecute, + OrgId: cmd.OrgId, + Name: cmd.Name, + Type: cmd.Type, + Settings: cmd.Settings, + Created: time.Now(), + Updated: time.Now(), } - _, err = sess.Insert(alertNotification) - - if err != nil { + if _, err = sess.Insert(alertNotification); err != nil { return err } @@ -148,38 +109,34 @@ func CreateAlertNotificationCommand(cmd *m.CreateAlertNotificationCommand) error func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error { return inTransaction(func(sess *xorm.Session) (err error) { - current := &m.AlertNotification{} - _, err = sess.Id(cmd.Id).Get(current) + current := m.AlertNotification{} - if err != nil { + if _, err = sess.Id(cmd.Id).Get(¤t); err != nil { return err } - alertNotification := &m.AlertNotification{ - Id: cmd.Id, - OrgId: cmd.OrgID, - Name: cmd.Name, - Type: cmd.Type, - Settings: cmd.Settings, - Updated: time.Now(), - Created: current.Created, - AlwaysExecute: cmd.AlwaysExecute, - } - - sess.UseBool("always_execute") - - var affected int64 - affected, err = sess.Id(alertNotification.Id).Update(alertNotification) - - if err != nil { + // check if name exists + sameNameQuery := &m.GetAlertNotificationsQuery{OrgId: cmd.OrgId, Name: cmd.Name} + if err := getAlertNotifications(sameNameQuery, sess); err != nil { return err } - if affected == 0 { + if len(sameNameQuery.Result) > 0 && sameNameQuery.Result[0].Id != current.Id { + return fmt.Errorf("Alert notification name %s already exists", cmd.Name) + } + + current.Updated = time.Now() + current.Settings = cmd.Settings + current.Name = cmd.Name + current.Type = cmd.Type + + if affected, err := sess.Id(cmd.Id).Update(current); err != nil { + return err + } else if affected == 0 { return fmt.Errorf("Could not find alert notification") } - cmd.Result = alertNotification + cmd.Result = ¤t return nil }) } diff --git a/public/app/features/alerting/notification_edit_ctrl.ts b/public/app/features/alerting/notification_edit_ctrl.ts index 43dceea8dc2..69e2e276862 100644 --- a/public/app/features/alerting/notification_edit_ctrl.ts +++ b/public/app/features/alerting/notification_edit_ctrl.ts @@ -15,10 +15,7 @@ export class AlertNotificationEditCtrl { this.loadNotification($routeParams.notificationId); } else { this.notification = { - settings: { - sendCrit: true, - sendWarn: true, - } + type: 'email', }; } } diff --git a/public/app/features/alerting/partials/notification_edit.html b/public/app/features/alerting/partials/notification_edit.html index fea91e5045a..8ed471887d5 100644 --- a/public/app/features/alerting/partials/notification_edit.html +++ b/public/app/features/alerting/partials/notification_edit.html @@ -6,14 +6,14 @@

Alert notification

-
+
Name - +
Type -
+
@@ -48,9 +41,9 @@
+

Email addresses

- To - +