diff --git a/docs/sources/alerting/notifications.md b/docs/sources/alerting/notifications.md
index e791492495e..5327039341f 100644
--- a/docs/sources/alerting/notifications.md
+++ b/docs/sources/alerting/notifications.md
@@ -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.
-### All supported notifiers
+### Google Hangouts Chat
-Name | Type |Support images | Support reminders
------|------------ | ------ | ------ |
-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
+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
+-----|------------ | ------
+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}
diff --git a/pkg/services/alerting/notifiers/googlechat.go b/pkg/services/alerting/notifiers/googlechat.go
new file mode 100644
index 00000000000..1aba15a7928
--- /dev/null
+++ b/pkg/services/alerting/notifiers/googlechat.go
@@ -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: `
+
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),
+ 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: "" + 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")
+ })
+
+ })
+ })
+}