浏览代码

merge: fixed conflicts in discord PR

Torkel Ödegaard 7 年之前
父节点
当前提交
e5e8b9800c

+ 9 - 0
pkg/components/null/float.go

@@ -106,6 +106,15 @@ func (f Float) String() string {
 	return fmt.Sprintf("%1.3f", f.Float64)
 	return fmt.Sprintf("%1.3f", f.Float64)
 }
 }
 
 
+// FullString returns float as string in full precision
+func (f Float) FullString() string {
+	if !f.Valid {
+		return "null"
+	}
+
+	return fmt.Sprintf("%f", f.Float64)
+}
+
 // SetValid changes this Float's value and also sets it to be non-null.
 // SetValid changes this Float's value and also sets it to be non-null.
 func (f *Float) SetValid(n float64) {
 func (f *Float) SetValid(n float64) {
 	f.Float64 = n
 	f.Float64 = n

+ 7 - 6
pkg/models/notifications.go

@@ -19,12 +19,13 @@ type SendEmailCommandSync struct {
 }
 }
 
 
 type SendWebhookSync struct {
 type SendWebhookSync struct {
-	Url        string
-	User       string
-	Password   string
-	Body       string
-	HttpMethod string
-	HttpHeader map[string]string
+	Url         string
+	User        string
+	Password    string
+	Body        string
+	HttpMethod  string
+	HttpHeader  map[string]string
+	ContentType string
 }
 }
 
 
 type SendResetPasswordEmailCommand struct {
 type SendResetPasswordEmailCommand struct {

+ 171 - 0
pkg/services/alerting/notifiers/discord.go

@@ -0,0 +1,171 @@
+package notifiers
+
+import (
+	"bytes"
+	"io"
+	"mime/multipart"
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/components/simplejson"
+	"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:        "discord",
+		Name:        "Discord",
+		Description: "Sends notifications to Discord",
+		Factory:     NewDiscordNotifier,
+		OptionsTemplate: `
+      <h3 class="page-heading">Discord settings</h3>
+      <div class="gf-form">
+        <span class="gf-form-label width-14">Webhook URL</span>
+        <input type="text" required class="gf-form-input max-width-22" ng-model="ctrl.model.settings.url" placeholder="Discord webhook URL"></input>
+      </div>
+    `,
+	})
+}
+
+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)
+
+	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.FullString(),
+			"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)
+
+	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)
+
+	var image = make(map[string]interface{})
+
+	var embeddedImage = false
+
+	if evalContext.ImagePublicUrl != "" {
+		image = map[string]interface{}{
+			"url": evalContext.ImagePublicUrl,
+		}
+		embed.Set("image", image)
+	} else {
+		image = map[string]interface{}{
+			"url": "attachment://graph.png",
+		}
+		embed.Set("image", image)
+		embeddedImage = true
+	}
+
+	bodyJSON.Set("embeds", []interface{}{embed})
+
+	json, _ := bodyJSON.MarshalJSON()
+
+	content_type := "application/json"
+
+	var body []byte
+
+	if embeddedImage {
+
+		var b bytes.Buffer
+
+		w := multipart.NewWriter(&b)
+
+		f, err := os.Open(evalContext.ImageOnDiskPath)
+
+		if err != nil {
+			this.log.Error("Can't open graph file", err)
+			return err
+		}
+
+		defer f.Close()
+
+		fw, err := w.CreateFormField("payload_json")
+		if err != nil {
+			return err
+		}
+
+		if _, err = fw.Write([]byte(string(json))); err != nil {
+			return err
+		}
+
+		fw, err = w.CreateFormFile("file", "graph.png")
+
+		if _, err = io.Copy(fw, f); err != nil {
+			return err
+		}
+
+		w.Close()
+
+		body = b.Bytes()
+		content_type = w.FormDataContentType()
+
+	} else {
+		body = json
+	}
+
+	cmd := &m.SendWebhookSync{
+		Url:         this.WebhookURL,
+		Body:        string(body),
+		HttpMethod:  "POST",
+		ContentType: content_type,
+	}
+
+	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
+		this.log.Error("Failed to send notification to Discord", "error", err)
+		return err
+	}
+
+	return nil
+}

+ 52 - 0
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/")
+			})
+		})
+	})
+}

+ 7 - 6
pkg/services/notifications/notifications.go

@@ -104,12 +104,13 @@ func (ns *NotificationService) Run(ctx context.Context) error {
 
 
 func (ns *NotificationService) SendWebhookSync(ctx context.Context, cmd *m.SendWebhookSync) error {
 func (ns *NotificationService) SendWebhookSync(ctx context.Context, cmd *m.SendWebhookSync) error {
 	return ns.sendWebRequestSync(ctx, &Webhook{
 	return ns.sendWebRequestSync(ctx, &Webhook{
-		Url:        cmd.Url,
-		User:       cmd.User,
-		Password:   cmd.Password,
-		Body:       cmd.Body,
-		HttpMethod: cmd.HttpMethod,
-		HttpHeader: cmd.HttpHeader,
+		Url:         cmd.Url,
+		User:        cmd.User,
+		Password:    cmd.Password,
+		Body:        cmd.Body,
+		HttpMethod:  cmd.HttpMethod,
+		HttpHeader:  cmd.HttpHeader,
+		ContentType: cmd.ContentType,
 	})
 	})
 }
 }
 
 

+ 13 - 7
pkg/services/notifications/webhook.go

@@ -15,12 +15,13 @@ import (
 )
 )
 
 
 type Webhook struct {
 type Webhook struct {
-	Url        string
-	User       string
-	Password   string
-	Body       string
-	HttpMethod string
-	HttpHeader map[string]string
+	Url         string
+	User        string
+	Password    string
+	Body        string
+	HttpMethod  string
+	HttpHeader  map[string]string
+	ContentType string
 }
 }
 
 
 var netTransport = &http.Transport{
 var netTransport = &http.Transport{
@@ -48,8 +49,13 @@ func (ns *NotificationService) sendWebRequestSync(ctx context.Context, webhook *
 		return err
 		return err
 	}
 	}
 
 
-	request.Header.Add("Content-Type", "application/json")
+	if webhook.ContentType == "" {
+		webhook.ContentType = "application/json"
+	}
+
+	request.Header.Add("Content-Type", webhook.ContentType)
 	request.Header.Add("User-Agent", "Grafana")
 	request.Header.Add("User-Agent", "Grafana")
+
 	if webhook.User != "" && webhook.Password != "" {
 	if webhook.User != "" && webhook.Password != "" {
 		request.Header.Add("Authorization", util.GetBasicAuthHeader(webhook.User, webhook.Password))
 		request.Header.Add("Authorization", util.GetBasicAuthHeader(webhook.User, webhook.Password))
 	}
 	}