Browse Source

Revert changes post code review and move them to notification page

John Baublitz 7 năm trước cách đây
mục cha
commit
3cb0e27e1c
34 tập tin đã thay đổi với 215 bổ sung107 xóa
  1. 13 12
      pkg/api/dtos/alerting.go
  2. 0 9
      pkg/models/alert.go
  3. 54 17
      pkg/models/alert_notifications.go
  4. 15 0
      pkg/services/alerting/eval_context.go
  5. 0 2
      pkg/services/alerting/extractor.go
  6. 2 0
      pkg/services/alerting/interfaces.go
  7. 11 1
      pkg/services/alerting/notifier.go
  8. 1 1
      pkg/services/alerting/notifiers/alertmanager.go
  9. 20 5
      pkg/services/alerting/notifiers/base.go
  10. 1 1
      pkg/services/alerting/notifiers/dingding.go
  11. 1 1
      pkg/services/alerting/notifiers/discord.go
  12. 1 1
      pkg/services/alerting/notifiers/email.go
  13. 1 1
      pkg/services/alerting/notifiers/hipchat.go
  14. 1 1
      pkg/services/alerting/notifiers/kafka.go
  15. 1 1
      pkg/services/alerting/notifiers/line.go
  16. 1 1
      pkg/services/alerting/notifiers/opsgenie.go
  17. 1 1
      pkg/services/alerting/notifiers/pagerduty.go
  18. 1 1
      pkg/services/alerting/notifiers/pushover.go
  19. 1 1
      pkg/services/alerting/notifiers/sensu.go
  20. 1 1
      pkg/services/alerting/notifiers/slack.go
  21. 1 1
      pkg/services/alerting/notifiers/teams.go
  22. 1 1
      pkg/services/alerting/notifiers/telegram.go
  23. 1 1
      pkg/services/alerting/notifiers/threema.go
  24. 1 1
      pkg/services/alerting/notifiers/victorops.go
  25. 1 1
      pkg/services/alerting/notifiers/webhook.go
  26. 0 1
      pkg/services/alerting/result_handler.go
  27. 0 6
      pkg/services/alerting/rule.go
  28. 1 21
      pkg/services/sqlstore/alert.go
  29. 51 7
      pkg/services/sqlstore/alert_notification.go
  30. 24 3
      pkg/services/sqlstore/migrations/alert_mig.go
  31. 0 3
      public/app/features/alerting/alert_tab_ctrl.ts
  32. 2 0
      public/app/features/alerting/notification_edit_ctrl.ts
  33. 0 3
      public/app/features/alerting/partials/alert_tab.html
  34. 5 0
      public/app/features/alerting/partials/notification_edit.html

+ 13 - 12
pkg/api/dtos/alerting.go

@@ -21,18 +21,17 @@ type AlertRule struct {
 	ExecutionError string           `json:"executionError"`
 	Url            string           `json:"url"`
 	CanEdit        bool             `json:"canEdit"`
-	NotifyOnce     bool             `json:"notifyOnce"`
-	NotifyEval     uint64           `json:"notifyEval"`
-	NotifyFreq     uint64           `json:"notifyFrequency"`
 }
 
 type AlertNotification struct {
-	Id        int64     `json:"id"`
-	Name      string    `json:"name"`
-	Type      string    `json:"type"`
-	IsDefault bool      `json:"isDefault"`
-	Created   time.Time `json:"created"`
-	Updated   time.Time `json:"updated"`
+	Id         int64     `json:"id"`
+	Name       string    `json:"name"`
+	Type       string    `json:"type"`
+	IsDefault  bool      `json:"isDefault"`
+	NotifyOnce bool      `json:"notifyOnce"`
+	Frequency  bool      `json:"frequency"`
+	Created    time.Time `json:"created"`
+	Updated    time.Time `json:"updated"`
 }
 
 type AlertTestCommand struct {
@@ -62,9 +61,11 @@ type EvalMatch struct {
 }
 
 type NotificationTestCommand struct {
-	Name     string           `json:"name"`
-	Type     string           `json:"type"`
-	Settings *simplejson.Json `json:"settings"`
+	Name       string           `json:"name"`
+	Type       string           `json:"type"`
+	NotifyOnce bool             `json:"notifyOnce"`
+	Frequency  time.Duration    `json:"frequency"`
+	Settings   *simplejson.Json `json:"settings"`
 }
 
 type PauseAlertCommand struct {

+ 0 - 9
pkg/models/alert.go

@@ -72,9 +72,6 @@ type Alert struct {
 	Silenced       bool
 	ExecutionError string
 	Frequency      int64
-	NotifyOnce     bool
-	NotifyFreq     uint64
-	NotifyEval     uint64
 
 	EvalData     *simplejson.Json
 	NewStateDate time.Time
@@ -98,8 +95,6 @@ func (this *Alert) ContainsUpdates(other *Alert) bool {
 	result := false
 	result = result || this.Name != other.Name
 	result = result || this.Message != other.Message
-	result = result || this.NotifyOnce != other.NotifyOnce
-	result = result || (!other.NotifyOnce && this.NotifyFreq != other.NotifyFreq)
 
 	if this.Settings != nil && other.Settings != nil {
 		json1, err1 := this.Settings.Encode()
@@ -164,10 +159,6 @@ type SetAlertStateCommand struct {
 	Timestamp time.Time
 }
 
-type IncAlertEvalCommand struct {
-	AlertId int64
-}
-
 //Queries
 type GetAlertsQuery struct {
 	OrgId        int64

+ 54 - 17
pkg/models/alert_notifications.go

@@ -7,32 +7,38 @@ import (
 )
 
 type AlertNotification struct {
-	Id        int64            `json:"id"`
-	OrgId     int64            `json:"-"`
-	Name      string           `json:"name"`
-	Type      string           `json:"type"`
-	IsDefault bool             `json:"isDefault"`
-	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"`
+	NotifyOnce bool             `json:"notifyOnce"`
+	Frequency  time.Duration    `json:"frequency"`
+	IsDefault  bool             `json:"isDefault"`
+	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"`
-	IsDefault bool             `json:"isDefault"`
-	Settings  *simplejson.Json `json:"settings"`
+	Name       string           `json:"name"  binding:"Required"`
+	Type       string           `json:"type"  binding:"Required"`
+	NotifyOnce bool             `json:"notifyOnce"  binding:"Required"`
+	Frequency  time.Duration    `json:"frequency"`
+	IsDefault  bool             `json:"isDefault"`
+	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"`
-	IsDefault bool             `json:"isDefault"`
-	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"`
+	NotifyOnce string           `json:"notifyOnce"  binding:"Required"`
+	Frequency  string           `json:"frequency"`
+	IsDefault  bool             `json:"isDefault"`
+	Settings   *simplejson.Json `json:"settings"  binding:"Required"`
 
 	OrgId  int64 `json:"-"`
 	Result *AlertNotification
@@ -63,3 +69,34 @@ type GetAllAlertNotificationsQuery struct {
 
 	Result []*AlertNotification
 }
+
+type NotificationJournal struct {
+	Id         int64
+	OrgId      int64
+	AlertId    int64
+	NotifierId int64
+	SentAt     time.Time
+	Success    bool
+}
+
+type RecordNotificationJournalCommand struct {
+	OrgId      int64
+	AlertId    int64
+	NotifierId int64
+	SentAt     time.Time
+	Success    bool
+}
+
+type GetLatestNotificationQuery struct {
+	OrgId      int64
+	AlertId    int64
+	NotifierId int64
+
+	Result *NotificationJournal
+}
+
+type CleanNotificationJournalCommand struct {
+	OrgId      int64
+	AlertId    int64
+	NotifierId int64
+}

+ 15 - 0
pkg/services/alerting/eval_context.go

@@ -143,3 +143,18 @@ func (c *EvalContext) GetNewState() m.AlertStateType {
 
 	return m.AlertStateOK
 }
+
+func (c *EvalContext) LastNotify(notifierId int64) *time.Time {
+	cmd := &m.GetLatestNotificationQuery{
+		OrgId:      c.Rule.OrgId,
+		AlertId:    c.Rule.Id,
+		NotifierId: notifierId,
+	}
+	if err := bus.Dispatch(cmd); err != nil {
+		c.log.Warn("Could not determine last time alert",
+			c.Rule.Name, "notified")
+		return nil
+	}
+
+	return &cmd.Result.SentAt
+}

+ 0 - 2
pkg/services/alerting/extractor.go

@@ -122,8 +122,6 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
 			Handler:     jsonAlert.Get("handler").MustInt64(),
 			Message:     jsonAlert.Get("message").MustString(),
 			Frequency:   frequency,
-			NotifyOnce:  jsonAlert.Get("notifyOnce").MustBool(),
-			NotifyFreq:  jsonAlert.Get("notifyFrequency").MustUint64(),
 		}
 
 		for _, condition := range jsonAlert.Get("conditions").MustArray() {

+ 2 - 0
pkg/services/alerting/interfaces.go

@@ -19,6 +19,8 @@ type Notifier interface {
 
 	GetNotifierId() int64
 	GetIsDefault() bool
+	GetNotifyOnce() bool
+	GetFrequency() time.Duration
 }
 
 type NotifierSlice []Notifier

+ 11 - 1
pkg/services/alerting/notifier.go

@@ -66,7 +66,17 @@ func (n *notificationService) sendNotifications(context *EvalContext, notifiers
 		not := notifier //avoid updating scope variable in go routine
 		n.log.Debug("Sending notification", "type", not.GetType(), "id", not.GetNotifierId(), "isDefault", not.GetIsDefault())
 		metrics.M_Alerting_Notification_Sent.WithLabelValues(not.GetType()).Inc()
-		g.Go(func() error { return not.Notify(context) })
+		g.Go(func() error {
+			success := not.Notify(context) == nil
+			cmd := &m.RecordNotificationJournalCommand{
+				OrgId:      context.Rule.OrgId,
+				AlertId:    context.Rule.Id,
+				NotifierId: not.GetNotifierId(),
+				SentAt:     time.Now(),
+				Success:    success,
+			}
+			return bus.Dispatch(cmd)
+		})
 	}
 
 	return g.Wait()

+ 1 - 1
pkg/services/alerting/notifiers/alertmanager.go

@@ -33,7 +33,7 @@ func NewAlertmanagerNotifier(model *m.AlertNotification) (alerting.Notifier, err
 	}
 
 	return &AlertmanagerNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		Url:          url,
 		log:          log.New("alerting.notifier.prometheus-alertmanager"),
 	}, nil

+ 20 - 5
pkg/services/alerting/notifiers/base.go

@@ -1,6 +1,8 @@
 package notifiers
 
 import (
+	"time"
+
 	"github.com/grafana/grafana/pkg/components/simplejson"
 	m "github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/services/alerting"
@@ -12,9 +14,11 @@ type NotifierBase struct {
 	Id          int64
 	IsDeault    bool
 	UploadImage bool
+	NotifyOnce  bool
+	Frequency   time.Duration
 }
 
-func NewNotifierBase(id int64, isDefault bool, name, notifierType string, model *simplejson.Json) NotifierBase {
+func NewNotifierBase(id int64, isDefault bool, name, notifierType string, notifyOnce bool, frequency time.Duration, model *simplejson.Json) NotifierBase {
 	uploadImage := true
 	value, exist := model.CheckGet("uploadImage")
 	if exist {
@@ -27,15 +31,17 @@ func NewNotifierBase(id int64, isDefault bool, name, notifierType string, model
 		IsDeault:    isDefault,
 		Type:        notifierType,
 		UploadImage: uploadImage,
+		NotifyOnce:  notifyOnce,
+		Frequency:   frequency,
 	}
 }
 
-func defaultShouldNotify(context *alerting.EvalContext) bool {
+func defaultShouldNotify(context *alerting.EvalContext, notifyOnce bool, frequency time.Duration, lastNotify *time.Time) bool {
 	// Only notify on state change.
-	if context.PrevAlertState == context.Rule.State && context.Rule.NotifyOnce {
+	if context.PrevAlertState == context.Rule.State && notifyOnce {
 		return false
 	}
-	if !context.Rule.NotifyOnce && context.Rule.NotifyEval != 0 {
+	if !notifyOnce && lastNotify != nil && lastNotify.Add(frequency).After(time.Now()) {
 		return false
 	}
 	// Do not notify when we become OK for the first time.
@@ -46,7 +52,8 @@ func defaultShouldNotify(context *alerting.EvalContext) bool {
 }
 
 func (n *NotifierBase) ShouldNotify(context *alerting.EvalContext) bool {
-	return defaultShouldNotify(context)
+	lastNotify := context.LastNotify(n.Id)
+	return defaultShouldNotify(context, n.NotifyOnce, n.Frequency, lastNotify)
 }
 
 func (n *NotifierBase) GetType() string {
@@ -64,3 +71,11 @@ func (n *NotifierBase) GetNotifierId() int64 {
 func (n *NotifierBase) GetIsDefault() bool {
 	return n.IsDeault
 }
+
+func (n *NotifierBase) GetNotifyOnce() bool {
+	return n.NotifyOnce
+}
+
+func (n *NotifierBase) GetFrequency() time.Duration {
+	return n.Frequency
+}

+ 1 - 1
pkg/services/alerting/notifiers/dingding.go

@@ -32,7 +32,7 @@ func NewDingDingNotifier(model *m.AlertNotification) (alerting.Notifier, error)
 	}
 
 	return &DingDingNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		Url:          url,
 		log:          log.New("alerting.notifier.dingding"),
 	}, nil

+ 1 - 1
pkg/services/alerting/notifiers/discord.go

@@ -39,7 +39,7 @@ func NewDiscordNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 	}
 
 	return &DiscordNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		WebhookURL:   url,
 		log:          log.New("alerting.notifier.discord"),
 	}, nil

+ 1 - 1
pkg/services/alerting/notifiers/email.go

@@ -52,7 +52,7 @@ func NewEmailNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 	})
 
 	return &EmailNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		Addresses:    addresses,
 		log:          log.New("alerting.notifier.email"),
 	}, nil

+ 1 - 1
pkg/services/alerting/notifiers/hipchat.go

@@ -59,7 +59,7 @@ func NewHipChatNotifier(model *models.AlertNotification) (alerting.Notifier, err
 	roomId := model.Settings.Get("roomid").MustString()
 
 	return &HipChatNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		Url:          url,
 		ApiKey:       apikey,
 		RoomId:       roomId,

+ 1 - 1
pkg/services/alerting/notifiers/kafka.go

@@ -43,7 +43,7 @@ func NewKafkaNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 	}
 
 	return &KafkaNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		Endpoint:     endpoint,
 		Topic:        topic,
 		log:          log.New("alerting.notifier.kafka"),

+ 1 - 1
pkg/services/alerting/notifiers/line.go

@@ -39,7 +39,7 @@ func NewLINENotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 	}
 
 	return &LineNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		Token:        token,
 		log:          log.New("alerting.notifier.line"),
 	}, nil

+ 1 - 1
pkg/services/alerting/notifiers/opsgenie.go

@@ -56,7 +56,7 @@ func NewOpsGenieNotifier(model *m.AlertNotification) (alerting.Notifier, error)
 	}
 
 	return &OpsGenieNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		ApiKey:       apiKey,
 		ApiUrl:       apiUrl,
 		AutoClose:    autoClose,

+ 1 - 1
pkg/services/alerting/notifiers/pagerduty.go

@@ -51,7 +51,7 @@ func NewPagerdutyNotifier(model *m.AlertNotification) (alerting.Notifier, error)
 	}
 
 	return &PagerdutyNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		Key:          key,
 		AutoResolve:  autoResolve,
 		log:          log.New("alerting.notifier.pagerduty"),

+ 1 - 1
pkg/services/alerting/notifiers/pushover.go

@@ -99,7 +99,7 @@ func NewPushoverNotifier(model *m.AlertNotification) (alerting.Notifier, error)
 		return nil, alerting.ValidationError{Reason: "API token not given"}
 	}
 	return &PushoverNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		UserKey:      userKey,
 		ApiToken:     apiToken,
 		Priority:     priority,

+ 1 - 1
pkg/services/alerting/notifiers/sensu.go

@@ -51,7 +51,7 @@ func NewSensuNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 	}
 
 	return &SensuNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		Url:          url,
 		User:         model.Settings.Get("username").MustString(),
 		Source:       model.Settings.Get("source").MustString(),

+ 1 - 1
pkg/services/alerting/notifiers/slack.go

@@ -78,7 +78,7 @@ func NewSlackNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 	uploadImage := model.Settings.Get("uploadImage").MustBool(true)
 
 	return &SlackNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		Url:          url,
 		Recipient:    recipient,
 		Mention:      mention,

+ 1 - 1
pkg/services/alerting/notifiers/teams.go

@@ -33,7 +33,7 @@ func NewTeamsNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 	}
 
 	return &TeamsNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		Url:          url,
 		log:          log.New("alerting.notifier.teams"),
 	}, nil

+ 1 - 1
pkg/services/alerting/notifiers/telegram.go

@@ -78,7 +78,7 @@ func NewTelegramNotifier(model *m.AlertNotification) (alerting.Notifier, error)
 	}
 
 	return &TelegramNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		BotToken:     botToken,
 		ChatID:       chatId,
 		UploadImage:  uploadImage,

+ 1 - 1
pkg/services/alerting/notifiers/threema.go

@@ -106,7 +106,7 @@ func NewThreemaNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 	}
 
 	return &ThreemaNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		GatewayID:    gatewayID,
 		RecipientID:  recipientID,
 		APISecret:    apiSecret,

+ 1 - 1
pkg/services/alerting/notifiers/victorops.go

@@ -51,7 +51,7 @@ func NewVictoropsNotifier(model *models.AlertNotification) (alerting.Notifier, e
 	}
 
 	return &VictoropsNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		URL:          url,
 		AutoResolve:  autoResolve,
 		log:          log.New("alerting.notifier.victorops"),

+ 1 - 1
pkg/services/alerting/notifiers/webhook.go

@@ -47,7 +47,7 @@ func NewWebHookNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 	}
 
 	return &WebhookNotifier{
-		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.NotifyOnce, model.Frequency, model.Settings),
 		Url:          url,
 		User:         model.Settings.Get("username").MustString(),
 		Password:     model.Settings.Get("password").MustString(),

+ 0 - 1
pkg/services/alerting/result_handler.go

@@ -88,7 +88,6 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
 		}
 	}
 
-	bus.Dispatch(&m.IncAlertEvalCommand{AlertId: evalContext.Rule.Id})
 	handler.notifier.SendIfNeeded(evalContext)
 
 	return nil

+ 0 - 6
pkg/services/alerting/rule.go

@@ -23,9 +23,6 @@ type Rule struct {
 	State               m.AlertStateType
 	Conditions          []Condition
 	Notifications       []int64
-	NotifyOnce          bool
-	NotifyFreq          uint64
-	NotifyEval          uint64
 }
 
 type ValidationError struct {
@@ -100,9 +97,6 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) {
 	model.Name = ruleDef.Name
 	model.Message = ruleDef.Message
 	model.Frequency = ruleDef.Frequency
-	model.NotifyOnce = ruleDef.NotifyOnce
-	model.NotifyFreq = ruleDef.NotifyFreq
-	model.NotifyEval = ruleDef.NotifyEval
 	model.State = ruleDef.State
 	model.NoDataState = m.NoDataOption(ruleDef.Settings.Get("noDataState").MustString("no_data"))
 	model.ExecutionErrorState = m.ExecutionErrorOption(ruleDef.Settings.Get("executionErrorState").MustString("alerting"))

+ 1 - 21
pkg/services/sqlstore/alert.go

@@ -22,7 +22,6 @@ func init() {
 	bus.AddHandler("sql", GetAlertStatesForDashboard)
 	bus.AddHandler("sql", PauseAlert)
 	bus.AddHandler("sql", PauseAllAlerts)
-	bus.AddHandler("sql", IncAlertEval)
 }
 
 func GetAlertById(query *m.GetAlertByIdQuery) error {
@@ -189,7 +188,7 @@ func updateAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS
 			if alertToUpdate.ContainsUpdates(alert) {
 				alert.Updated = timeNow()
 				alert.State = alertToUpdate.State
-				sess.MustCols("message", "notify_freq", "notify_once")
+				sess.MustCols("message")
 				_, err := sess.Id(alert.Id).Update(alert)
 				if err != nil {
 					return err
@@ -344,22 +343,3 @@ func GetAlertStatesForDashboard(query *m.GetAlertStatesForDashboardQuery) error
 
 	return err
 }
-
-func IncAlertEval(cmd *m.IncAlertEvalCommand) error {
-	return inTransaction(func(sess *DBSession) error {
-		alert := m.Alert{}
-
-		if _, err := sess.Id(cmd.AlertId).Get(&alert); err != nil {
-			return err
-		}
-
-		alert.NotifyEval = (alert.NotifyEval + 1) % alert.NotifyFreq
-
-		sess.MustCols("notify_eval")
-		if _, err := sess.Id(cmd.AlertId).Update(alert); err != nil {
-			return err
-		}
-
-		return nil
-	})
-}

+ 51 - 7
pkg/services/sqlstore/alert_notification.go

@@ -17,6 +17,9 @@ func init() {
 	bus.AddHandler("sql", DeleteAlertNotification)
 	bus.AddHandler("sql", GetAlertNotificationsToSend)
 	bus.AddHandler("sql", GetAllAlertNotifications)
+	bus.AddHandler("sql", RecordNotificationJournal)
+	bus.AddHandler("sql", GetLatestNotification)
+	bus.AddHandler("sql", CleanNotificationJournal)
 }
 
 func DeleteAlertNotification(cmd *m.DeleteAlertNotificationCommand) error {
@@ -138,13 +141,15 @@ func CreateAlertNotificationCommand(cmd *m.CreateAlertNotificationCommand) error
 		}
 
 		alertNotification := &m.AlertNotification{
-			OrgId:     cmd.OrgId,
-			Name:      cmd.Name,
-			Type:      cmd.Type,
-			Settings:  cmd.Settings,
-			Created:   time.Now(),
-			Updated:   time.Now(),
-			IsDefault: cmd.IsDefault,
+			OrgId:      cmd.OrgId,
+			Name:       cmd.Name,
+			Type:       cmd.Type,
+			Settings:   cmd.Settings,
+			NotifyOnce: cmd.NotifyOnce,
+			Frequency:  cmd.Frequency,
+			Created:    time.Now(),
+			Updated:    time.Now(),
+			IsDefault:  cmd.IsDefault,
 		}
 
 		if _, err = sess.Insert(alertNotification); err != nil {
@@ -192,3 +197,42 @@ func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error {
 		return nil
 	})
 }
+
+func RecordNotificationJournal(cmd *m.RecordNotificationJournalCommand) error {
+	return inTransaction(func(sess *DBSession) error {
+		journalEntry := &m.NotificationJournal{
+			OrgId:      cmd.OrgId,
+			AlertId:    cmd.AlertId,
+			NotifierId: cmd.NotifierId,
+			SentAt:     cmd.SentAt,
+			Success:    cmd.Success,
+		}
+
+		if _, err := sess.Insert(journalEntry); err != nil {
+			return err
+		}
+
+		return nil
+	})
+}
+
+func GetLatestNotification(cmd *m.GetLatestNotificationQuery) error {
+	return inTransaction(func(sess *DBSession) error {
+		notificationJournal := &m.NotificationJournal{}
+		_, err := sess.OrderBy("notification_journal.sent_at").Desc().Where("notification_journal.org_id = ? AND notification_journal.alert_id = ? AND notification_journal.notifier_id = ?", cmd.OrgId, cmd.AlertId, cmd.NotifierId).Get(notificationJournal)
+		if err != nil {
+			return err
+		}
+
+		cmd.Result = notificationJournal
+		return nil
+	})
+}
+
+func CleanNotificationJournal(cmd *m.CleanNotificationJournalCommand) error {
+	return inTransaction(func(sess *DBSession) error {
+		sql := "DELETE FROM notification_journal WHERE notification_journal.org_id = ? AND notification_journal.alert_id = ? AND notification_journal.notifier_id = ?"
+		_, err := sess.Exec(sql, cmd.OrgId, cmd.AlertId, cmd.NotifierId)
+		return err
+	})
+}

+ 24 - 3
pkg/services/sqlstore/migrations/alert_mig.go

@@ -29,9 +29,6 @@ func addAlertMigrations(mg *Migrator) {
 			{Name: "state_changes", Type: DB_Int, Nullable: false},
 			{Name: "created", Type: DB_DateTime, Nullable: false},
 			{Name: "updated", Type: DB_DateTime, Nullable: false},
-			{Name: "notify_once", Type: DB_Bool, Nullable: false},
-			{Name: "notify_freq", Type: DB_Int, Nullable: false},
-			{Name: "notify_eval", Type: DB_Int, Nullable: false},
 		},
 		Indices: []*Index{
 			{Cols: []string{"org_id", "id"}, Type: IndexType},
@@ -68,8 +65,32 @@ func addAlertMigrations(mg *Migrator) {
 	mg.AddMigration("Add column is_default", NewAddColumnMigration(alert_notification, &Column{
 		Name: "is_default", Type: DB_Bool, Nullable: false, Default: "0",
 	}))
+	mg.AddMigration("Add column frequency", NewAddColumnMigration(alert_notification, &Column{
+		Name: "frequency", Type: DB_BigInt, Nullable: true,
+	}))
+	mg.AddMigration("Add column notify_once", NewAddColumnMigration(alert_notification, &Column{
+		Name: "notify_once", Type: DB_Bool, Nullable: false, Default: "1",
+	}))
 	mg.AddMigration("add index alert_notification org_id & name", NewAddIndexMigration(alert_notification, alert_notification.Indices[0]))
 
+	notification_journal := Table{
+		Name: "notification_journal",
+		Columns: []*Column{
+			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			{Name: "org_id", Type: DB_BigInt, Nullable: false},
+			{Name: "alert_id", Type: DB_BigInt, Nullable: false},
+			{Name: "notifier_id", Type: DB_BigInt, Nullable: false},
+			{Name: "sent_at", Type: DB_DateTime, Nullable: false},
+			{Name: "success", Type: DB_Bool, Nullable: false},
+		},
+		Indices: []*Index{
+			{Cols: []string{"org_id", "alert_id", "notifier_id"}, Type: IndexType},
+		},
+	}
+
+	mg.AddMigration("create notification_journal table v1", NewAddTableMigration(notification_journal))
+	mg.AddMigration("add index notification_journal org_id & alert_id & notifier_id", NewAddIndexMigration(notification_journal, notification_journal.Indices[0]))
+
 	mg.AddMigration("Update alert table charset", NewTableCharsetMigration("alert", []*Column{
 		{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
 		{Name: "message", Type: DB_Text, Nullable: false},

+ 0 - 3
public/app/features/alerting/alert_tab_ctrl.ts

@@ -167,9 +167,6 @@ export class AlertTabCtrl {
     alert.noDataState = alert.noDataState || 'no_data';
     alert.executionErrorState = alert.executionErrorState || 'alerting';
     alert.frequency = alert.frequency || '60s';
-    alert.notifyFrequency = alert.notifyFrequency || 10;
-    alert.notifyOnce = alert.notifyOnce == null ? true : alert.notifyOnce;
-    alert.frequency = alert.frequency || '60s';
     alert.handler = alert.handler || 1;
     alert.notifications = alert.notifications || [];
 

+ 2 - 0
public/app/features/alerting/notification_edit_ctrl.ts

@@ -11,6 +11,7 @@ export class AlertNotificationEditCtrl {
   model: any;
   defaults: any = {
     type: 'email',
+    notifyOnce: true,
     settings: {
       httpMethod: 'POST',
       autoResolve: true,
@@ -102,6 +103,7 @@ export class AlertNotificationEditCtrl {
     var payload = {
       name: this.model.name,
       type: this.model.type,
+      frequency: this.model.frequency,
       settings: this.model.settings,
     };
 

+ 0 - 3
public/app/features/alerting/partials/alert_tab.html

@@ -31,9 +31,6 @@
 					<input type="text" class="gf-form-input width-20" ng-model="ctrl.alert.name">
 					<span class="gf-form-label">Evaluate every</span>
 					<input class="gf-form-input max-width-5" type="text" ng-model="ctrl.alert.frequency"></input>
-					<a class="gf-form-label" ng-click="ctrl.alert.notifyOnce = !ctrl.alert.notifyOnce;">{{ ctrl.alert.notifyOnce ? 'Notify on state change' : 'Notify every' }}</a>
-					<input class="gf-form-input max-width-5" type="number" ng-model="ctrl.alert.notifyFrequency" ng-hide="ctrl.alert.notifyOnce"></input>
-					<span class="gf-form-label" ng-hide="ctrl.alert.notifyOnce">evaluations</span>
 				</div>
 			</div>
 

+ 5 - 0
public/app/features/alerting/partials/notification_edit.html

@@ -18,6 +18,11 @@
           </select>
         </div>
       </div>
+      <div class="gf-form">
+        <a class="gf-form-label width-12" ng-click="ctrl.model.notifyOnce = !ctrl.model.notifyOnce;">{{ ctrl.model.notifyOnce ? 'Notify on state change' : 'Notify at most every' }}</a>
+        <input class="gf-form-input max-width-10" type="number" required ng-model="ctrl.model.frequency" required ng-hide="ctrl.model.notifyOnce"></input>
+        <span class="gf-form-label max-width-5" ng-hide="ctrl.model.notifyOnce">seconds</span>
+      </div>
       <gf-form-switch
           class="gf-form"
           label="Send on all alerts"