Merge pull request #11410 from PatrickSchuster/add_google_hangouts_chat_notifier

Add Google Hangouts Chat notifier.
This commit is contained in:
Carl Bergquist 2018-12-07 14:49:28 +01:00 committed by GitHub
commit 93dee6d4c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 288 additions and 18 deletions

View File

@ -157,27 +157,29 @@ 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. Once these two properties are set, you can send the alerts to Kafka for further processing or throttling.
### All supported notifiers ### Google Hangouts Chat
Name | Type |Support images | Support reminders 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).
-----|------------ | ------ | ------ |
Slack | `slack` | yes | yes
Pagerduty | `pagerduty` | yes | yes
Email | `email` | yes | yes
Webhook | `webhook` | link | yes
Kafka | `kafka` | no | yes
Hipchat | `hipchat` | yes | yes
VictorOps | `victorops` | yes | yes
Sensu | `sensu` | yes | yes
OpsGenie | `opsgenie` | yes | yes
Threema | `threema` | yes | yes
Pushover | `pushover` | no | yes
Telegram | `telegram` | no | yes
Line | `line` | no | yes
Microsoft Teams | `teams` | yes | yes
Prometheus Alertmanager | `prometheus-alertmanager` | no | no
### All supported notifier
Name | Type |Support images
-----|------------ | ------
Slack | `slack` | yes
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
OpsGenie | `opsgenie` | yes
Threema | `threema` | yes
Pushover | `pushover` | no
Telegram | `telegram` | no
Line | `line` | no
Prometheus Alertmanager | `prometheus-alertmanager` | no
# Enable images in notifications {#external-image-store} # Enable images in notifications {#external-image-store}

View File

@ -0,0 +1,215 @@
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"
)
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: `
<h3 class="page-heading">Google Hangouts Chat settings</h3>
<div class="gf-form max-width-30">
<span class="gf-form-label width-6">Url</span>
<input type="text" required class="gf-form-input max-width-30" ng-model="ctrl.model.settings.url" placeholder="Google Hangouts Chat incoming webhook url"></input>
</div>
`,
})
}
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),
Url: url,
log: log.New("alerting.notifier.googlechat"),
}, nil
}
type GoogleChatNotifier struct {
NotifierBase
Url 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: "<i>" + evt.Metric + ": " + fmt.Sprint(evt.Value) + "</i>",
},
},
)
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
}

View File

@ -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")
})
})
})
}