diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go
index c23a53009a9..e00aafa54fd 100644
--- a/pkg/metrics/metrics.go
+++ b/pkg/metrics/metrics.go
@@ -52,6 +52,7 @@ var (
M_Alerting_Notification_Sent_Threema Counter
M_Alerting_Notification_Sent_Sensu Counter
M_Alerting_Notification_Sent_Pushover Counter
+ M_Alerting_Notification_Sent_Discord Counter
M_Aws_CloudWatch_GetMetricStatistics Counter
M_Aws_CloudWatch_ListMetrics Counter
@@ -124,6 +125,7 @@ func initMetricVars(settings *MetricSettings) {
M_Alerting_Notification_Sent_Sensu = RegCounter("alerting.notifications_sent", "type", "sensu")
M_Alerting_Notification_Sent_LINE = RegCounter("alerting.notifications_sent", "type", "LINE")
M_Alerting_Notification_Sent_Pushover = RegCounter("alerting.notifications_sent", "type", "pushover")
+ M_Alerting_Notification_Sent_Discord = RegCounter("alerting.notifications_sent", "type", "discord")
M_Aws_CloudWatch_GetMetricStatistics = RegCounter("aws.cloudwatch.get_metric_statistics")
M_Aws_CloudWatch_ListMetrics = RegCounter("aws.cloudwatch.list_metrics")
diff --git a/pkg/services/alerting/notifiers/discord.go b/pkg/services/alerting/notifiers/discord.go
new file mode 100644
index 00000000000..ce9849bbe6c
--- /dev/null
+++ b/pkg/services/alerting/notifiers/discord.go
@@ -0,0 +1,114 @@
+package notifiers
+
+import (
+ "strconv"
+ "strings"
+
+ "github.com/grafana/grafana/pkg/bus"
+ "github.com/grafana/grafana/pkg/components/simplejson"
+ "github.com/grafana/grafana/pkg/log"
+ "github.com/grafana/grafana/pkg/metrics"
+ 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: "discord",
+ Name: "Discord",
+ Description: "Sends notifications to Discord",
+ Factory: NewDiscordNotifier,
+ OptionsTemplate: `
+
Discord settings
+
+ Webhook URL
+
+
+ `,
+ })
+}
+
+func NewDiscordNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
+ url := model.Settings.Get("url").MustString()
+ if url == "" {
+ return nil, alerting.ValidationError{Reason: "Could not find webhook url property in settings"}
+ }
+
+ return &DiscordNotifier{
+ NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+ WebhookURL: url,
+ log: log.New("alerting.notifier.discord"),
+ }, nil
+}
+
+type DiscordNotifier struct {
+ NotifierBase
+ WebhookURL string
+ log log.Logger
+}
+
+func (this *DiscordNotifier) Notify(evalContext *alerting.EvalContext) error {
+ this.log.Info("Sending alert notification to", "webhook_url", this.WebhookURL)
+ metrics.M_Alerting_Notification_Sent_Discord.Inc(1)
+
+ ruleUrl, err := evalContext.GetRuleUrl()
+ if err != nil {
+ this.log.Error("Failed get rule link", "error", err)
+ return err
+ }
+
+ bodyJSON := simplejson.New()
+ bodyJSON.Set("username", "Grafana")
+
+ fields := make([]map[string]interface{}, 0)
+
+ for _, evt := range evalContext.EvalMatches {
+ fields = append(fields, map[string]interface{}{
+ "name": evt.Metric,
+ "value": evt.Value,
+ "inline": true,
+ })
+ }
+
+ footer := map[string]interface{}{
+ "text": "Grafana v" + setting.BuildVersion,
+ "icon_url": "https://grafana.com/assets/img/fav32.png",
+ }
+
+ color, _ := strconv.ParseInt(strings.TrimLeft(evalContext.GetStateModel().Color, "#"), 16, 0)
+
+ image := map[string]interface{}{
+ "url": evalContext.ImagePublicUrl,
+ }
+
+ embed := simplejson.New()
+ embed.Set("title", evalContext.GetNotificationTitle())
+ //Discord takes integer for color
+ embed.Set("color", color)
+ embed.Set("url", ruleUrl)
+ embed.Set("description", evalContext.Rule.Message)
+ embed.Set("type", "rich")
+ embed.Set("fields", fields)
+ embed.Set("footer", footer)
+ embed.Set("image", image)
+
+ bodyJSON.Set("embeds", []interface{}{embed})
+
+ body, _ := bodyJSON.MarshalJSON()
+
+ this.log.Info("Message", string(body))
+
+ cmd := &m.SendWebhookSync{
+ Url: this.WebhookURL,
+ Body: string(body),
+ HttpMethod: "POST",
+ }
+
+ if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
+ this.log.Error("Failed to send notification to Discord", "error", err, "body", string(body))
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/services/alerting/notifiers/discord_test.go b/pkg/services/alerting/notifiers/discord_test.go
new file mode 100644
index 00000000000..fe925aab362
--- /dev/null
+++ b/pkg/services/alerting/notifiers/discord_test.go
@@ -0,0 +1,52 @@
+package notifiers
+
+import (
+ "testing"
+
+ "github.com/grafana/grafana/pkg/components/simplejson"
+ m "github.com/grafana/grafana/pkg/models"
+ . "github.com/smartystreets/goconvey/convey"
+)
+
+func TestDiscordNotifier(t *testing.T) {
+ Convey("Telegram 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: "discord_testing",
+ Type: "discord",
+ Settings: settingsJSON,
+ }
+
+ _, err := NewDiscordNotifier(model)
+ So(err, ShouldNotBeNil)
+ })
+
+ Convey("settings should trigger incident", func() {
+ json := `
+ {
+ "url": "https://web.hook/"
+ }`
+
+ settingsJSON, _ := simplejson.NewJson([]byte(json))
+ model := &m.AlertNotification{
+ Name: "discord_testing",
+ Type: "discord",
+ Settings: settingsJSON,
+ }
+
+ not, err := NewDiscordNotifier(model)
+ discordNotifier := not.(*DiscordNotifier)
+
+ So(err, ShouldBeNil)
+ So(discordNotifier.Name, ShouldEqual, "discord_testing")
+ So(discordNotifier.Type, ShouldEqual, "discord")
+ So(discordNotifier.WebhookURL, ShouldEqual, "https://web.hook/")
+ })
+ })
+ })
+}