From 3091aece2b5dcdbf42b129c892eb505eba0325bc Mon Sep 17 00:00:00 2001 From: Patrick Schuster Date: Wed, 28 Mar 2018 11:56:54 +0200 Subject: [PATCH 1/4] Add Google Hangouts Chat notifier. --- pkg/services/alerting/notifiers/googlechat.go | 215 ++++++++++++++++++ .../alerting/notifiers/googlechat_test.go | 53 +++++ 2 files changed, 268 insertions(+) create mode 100644 pkg/services/alerting/notifiers/googlechat.go create mode 100644 pkg/services/alerting/notifiers/googlechat_test.go diff --git a/pkg/services/alerting/notifiers/googlechat.go b/pkg/services/alerting/notifiers/googlechat.go new file mode 100644 index 00000000000..5d8fba1f8c6 --- /dev/null +++ b/pkg/services/alerting/notifiers/googlechat.go @@ -0,0 +1,215 @@ +package notifiers + +import ( + "encoding/json" + "fmt" + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/log" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/setting" + "time" +) + +func init() { + alerting.RegisterNotifier(&alerting.NotifierPlugin{ + Type: "googlechat", + Name: "Google Hangouts Chat", + Description: "Sends notifications to Google Hangouts Chat via webhooks based on the official JSON message " + + "format (https://developers.google.com/hangouts/chat/reference/message-formats/).", + Factory: NewGoogleChatNotifier, + OptionsTemplate: ` +

Google Hangouts Chat settings

+
+ Url + +
+ `, + }) +} + +func NewGoogleChatNotifier(model *m.AlertNotification) (alerting.Notifier, error) { + url := model.Settings.Get("url").MustString() + if url == "" { + return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} + } + + return &GoogleChatNotifier{ + NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings), + Url: url, + log: log.New("alerting.notifier.googlechat"), + }, nil +} + +type GoogleChatNotifier struct { + NotifierBase + Url string + method string + log log.Logger +} + +/** +Structs used to build a custom Google Hangouts Chat message card. +See: https://developers.google.com/hangouts/chat/reference/message-formats/cards +*/ +type outerStruct struct { + Cards []card `json:"cards"` +} + +type card struct { + Header header `json:"header"` + Sections []section `json:"sections"` +} + +type header struct { + Title string `json:"title"` +} + +type section struct { + Widgets []widget `json:"widgets"` +} + +// "generic" widget used to add different types of widgets (buttonWidget, textParagraphWidget, imageWidget) +type widget interface { +} + +type buttonWidget struct { + Buttons []button `json:"buttons"` +} + +type textParagraphWidget struct { + Text text `json:"textParagraph"` +} + +type text struct { + Text string `json:"text"` +} + +type imageWidget struct { + Image image `json:"image"` +} + +type image struct { + ImageUrl string `json:"imageUrl"` +} + +type button struct { + TextButton textButton `json:"textButton"` +} + +type textButton struct { + Text string `json:"text"` + OnClick onClick `json:"onClick"` +} + +type onClick struct { + OpenLink openLink `json:"openLink"` +} + +type openLink struct { + Url string `json:"url"` +} + +func (this *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error { + this.log.Info("Executing Google Chat notification") + + headers := map[string]string{ + "Content-Type": "application/json; charset=UTF-8", + } + + ruleUrl, err := evalContext.GetRuleUrl() + if err != nil { + this.log.Error("evalContext returned an invalid rule URL") + } + + // add a text paragraph widget for the message + widgets := []widget{ + textParagraphWidget{ + Text: text{ + Text: evalContext.Rule.Message, + }, + }, + } + + // add a text paragraph widget for the fields + var fields []textParagraphWidget + fieldLimitCount := 4 + for index, evt := range evalContext.EvalMatches { + fields = append(fields, + textParagraphWidget{ + Text: text{ + Text: "" + evt.Metric + ": " + fmt.Sprint(evt.Value) + "", + }, + }, + ) + if index > fieldLimitCount { + break + } + } + widgets = append(widgets, fields) + + // if an image exists, add it as an image widget + if evalContext.ImagePublicUrl != "" { + widgets = append(widgets, imageWidget{ + Image: image{ + ImageUrl: evalContext.ImagePublicUrl, + }, + }) + } else { + this.log.Info("Could not retrieve a public image URL.") + } + + // add a button widget (link to Grafana) + widgets = append(widgets, buttonWidget{ + Buttons: []button{ + { + TextButton: textButton{ + Text: "OPEN IN GRAFANA", + OnClick: onClick{ + OpenLink: openLink{ + Url: ruleUrl, + }, + }, + }, + }, + }, + }) + + // add text paragraph widget for the build version and timestamp + widgets = append(widgets, textParagraphWidget{ + Text: text{ + Text: "Grafana v" + setting.BuildVersion + " | " + (time.Now()).Format(time.RFC822), + }, + }) + + // nest the required structs + res1D := &outerStruct{ + Cards: []card{ + { + Header: header{ + Title: evalContext.GetNotificationTitle(), + }, + Sections: []section{ + { + Widgets: widgets, + }, + }, + }, + }, + } + body, _ := json.Marshal(res1D) + + cmd := &m.SendWebhookSync{ + Url: this.Url, + HttpMethod: "POST", + HttpHeader: headers, + Body: string(body), + } + + if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { + this.log.Error("Failed to send Google Hangouts Chat alert", "error", err, "webhook", this.Name) + return err + } + + return nil +} diff --git a/pkg/services/alerting/notifiers/googlechat_test.go b/pkg/services/alerting/notifiers/googlechat_test.go new file mode 100644 index 00000000000..1fdce878926 --- /dev/null +++ b/pkg/services/alerting/notifiers/googlechat_test.go @@ -0,0 +1,53 @@ +package notifiers + +import ( + "testing" + + "github.com/grafana/grafana/pkg/components/simplejson" + m "github.com/grafana/grafana/pkg/models" + . "github.com/smartystreets/goconvey/convey" +) + +func TestGoogleChatNotifier(t *testing.T) { + Convey("Google Hangouts Chat notifier tests", t, func() { + + Convey("Parsing alert notification from settings", func() { + Convey("empty settings should return error", func() { + json := `{ }` + + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &m.AlertNotification{ + Name: "ops", + Type: "googlechat", + Settings: settingsJSON, + } + + _, err := NewGoogleChatNotifier(model) + So(err, ShouldNotBeNil) + }) + + Convey("from settings", func() { + json := ` + { + "url": "http://google.com" + }` + + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &m.AlertNotification{ + Name: "ops", + Type: "googlechat", + Settings: settingsJSON, + } + + not, err := NewGoogleChatNotifier(model) + webhookNotifier := not.(*GoogleChatNotifier) + + So(err, ShouldBeNil) + So(webhookNotifier.Name, ShouldEqual, "ops") + So(webhookNotifier.Type, ShouldEqual, "googlechat") + So(webhookNotifier.Url, ShouldEqual, "http://google.com") + }) + + }) + }) +} From 481dcb321d6037a28e704f15dcb2acd956558ebb Mon Sep 17 00:00:00 2001 From: Patrick Schuster Date: Fri, 16 Nov 2018 00:30:51 +0100 Subject: [PATCH 2/4] Update ReadMe. --- docs/sources/alerting/notifications.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/sources/alerting/notifications.md b/docs/sources/alerting/notifications.md index fe57fd0fa8f..40ef0a818f1 100644 --- a/docs/sources/alerting/notifications.md +++ b/docs/sources/alerting/notifications.md @@ -128,6 +128,10 @@ There are a couple of configuration options which need to be set up in Grafana U Once these two properties are set, you can send the alerts to Kafka for further processing or throttling. +### Google Hangouts Chat + +Notifications can be sent by setting up an incoming webhook in Google Hangouts chat. Configuring such a webhook is described [here](https://developers.google.com/hangouts/chat/how-tos/webhooks). + ### All supported notifier Name | Type |Support images @@ -137,6 +141,7 @@ Pagerduty | `pagerduty` | yes Email | `email` | yes Webhook | `webhook` | link Kafka | `kafka` | no +Google Hangouts Chat | `googlechat` | yes Hipchat | `hipchat` | yes VictorOps | `victorops` | yes Sensu | `sensu` | yes From a90bba859a0993735a9e309f70ea09eb8cbb5abe Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 7 Dec 2018 14:17:09 +0100 Subject: [PATCH 3/4] fixes merge error --- pkg/services/alerting/notifiers/googlechat.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/services/alerting/notifiers/googlechat.go b/pkg/services/alerting/notifiers/googlechat.go index 5d8fba1f8c6..5195a7d21bf 100644 --- a/pkg/services/alerting/notifiers/googlechat.go +++ b/pkg/services/alerting/notifiers/googlechat.go @@ -3,12 +3,13 @@ package notifiers import ( "encoding/json" "fmt" + "time" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/setting" - "time" ) func init() { @@ -35,7 +36,7 @@ func NewGoogleChatNotifier(model *m.AlertNotification) (alerting.Notifier, error } return &GoogleChatNotifier{ - NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings), + NotifierBase: NewNotifierBase(model), Url: url, log: log.New("alerting.notifier.googlechat"), }, nil From 9eea58577309ed8d9af137f9e019d19f6507ce02 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 7 Dec 2018 14:39:44 +0100 Subject: [PATCH 4/4] removes unused code --- pkg/services/alerting/notifiers/googlechat.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/services/alerting/notifiers/googlechat.go b/pkg/services/alerting/notifiers/googlechat.go index 5195a7d21bf..1aba15a7928 100644 --- a/pkg/services/alerting/notifiers/googlechat.go +++ b/pkg/services/alerting/notifiers/googlechat.go @@ -44,9 +44,8 @@ func NewGoogleChatNotifier(model *m.AlertNotification) (alerting.Notifier, error type GoogleChatNotifier struct { NotifierBase - Url string - method string - log log.Logger + Url string + log log.Logger } /**