1package notifiers
2
3import (
4	"encoding/json"
5	"strconv"
6	"strings"
7
8	"fmt"
9
10	"github.com/grafana/grafana/pkg/bus"
11	"github.com/grafana/grafana/pkg/infra/log"
12	"github.com/grafana/grafana/pkg/models"
13	"github.com/grafana/grafana/pkg/services/alerting"
14)
15
16func init() {
17	alerting.RegisterNotifier(&alerting.NotifierPlugin{
18		Type:        "hipchat",
19		Name:        "HipChat",
20		Description: "Sends notifications uto a HipChat Room",
21		Heading:     "HipChat settings",
22		Factory:     NewHipChatNotifier,
23		Options: []alerting.NotifierOption{
24			{
25				Label:        "Hip Chat Url",
26				Element:      alerting.ElementTypeInput,
27				InputType:    alerting.InputTypeText,
28				Placeholder:  "HipChat URL (ex https://grafana.hipchat.com)",
29				PropertyName: "url",
30				Required:     true,
31			},
32			{
33				Label:        "API Key",
34				Element:      alerting.ElementTypeInput,
35				InputType:    alerting.InputTypeText,
36				Placeholder:  "HipChat API Key",
37				PropertyName: "apiKey",
38				Required:     true,
39			},
40			{
41				Label:        "Room ID",
42				Element:      alerting.ElementTypeInput,
43				InputType:    alerting.InputTypeText,
44				PropertyName: "roomid",
45			},
46		},
47	})
48}
49
50const (
51	maxFieldCount int = 4
52)
53
54// NewHipChatNotifier is the constructor functions
55// for the HipChatNotifier
56func NewHipChatNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
57	url := model.Settings.Get("url").MustString()
58	if strings.HasSuffix(url, "/") {
59		url = url[:len(url)-1]
60	}
61	if url == "" {
62		return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
63	}
64
65	apikey := model.Settings.Get("apikey").MustString()
66	roomID := model.Settings.Get("roomid").MustString()
67
68	return &HipChatNotifier{
69		NotifierBase: NewNotifierBase(model),
70		URL:          url,
71		APIKey:       apikey,
72		RoomID:       roomID,
73		log:          log.New("alerting.notifier.hipchat"),
74	}, nil
75}
76
77// HipChatNotifier is responsible for sending
78// alert notifications to Hipchat.
79type HipChatNotifier struct {
80	NotifierBase
81	URL    string
82	APIKey string
83	RoomID string
84	log    log.Logger
85}
86
87// Notify sends an alert notification to HipChat
88func (hc *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error {
89	hc.log.Info("Executing hipchat notification", "ruleId", evalContext.Rule.ID, "notification", hc.Name)
90
91	ruleURL, err := evalContext.GetRuleURL()
92	if err != nil {
93		hc.log.Error("Failed get rule link", "error", err)
94		return err
95	}
96
97	attributes := make([]map[string]interface{}, 0)
98	for index, evt := range evalContext.EvalMatches {
99		metricName := evt.Metric
100		if len(metricName) > 50 {
101			metricName = metricName[:50]
102		}
103		attributes = append(attributes, map[string]interface{}{
104			"label": metricName,
105			"value": map[string]interface{}{
106				"label": strconv.FormatFloat(evt.Value.Float64, 'f', -1, 64),
107			},
108		})
109		if index > maxFieldCount {
110			break
111		}
112	}
113
114	if evalContext.Error != nil {
115		attributes = append(attributes, map[string]interface{}{
116			"label": "Error message",
117			"value": map[string]interface{}{
118				"label": evalContext.Error.Error(),
119			},
120		})
121	}
122
123	message := ""
124	if evalContext.Rule.State != models.AlertStateOK { // don't add message when going back to alert state ok.
125		message += " " + evalContext.Rule.Message
126	}
127
128	if message == "" {
129		message = evalContext.GetNotificationTitle() + " in state " + evalContext.GetStateModel().Text
130	}
131
132	// HipChat has a set list of colors
133	var color string
134	switch evalContext.Rule.State {
135	case models.AlertStateOK:
136		color = "green"
137	case models.AlertStateNoData:
138		color = "gray"
139	case models.AlertStateAlerting:
140		color = "red"
141	default:
142		// Handle other cases?
143	}
144
145	// Add a card with link to the dashboard
146	card := map[string]interface{}{
147		"style":       "application",
148		"url":         ruleURL,
149		"id":          "1",
150		"title":       evalContext.GetNotificationTitle(),
151		"description": message,
152		"icon": map[string]interface{}{
153			"url": "https://grafana.com/assets/img/fav32.png",
154		},
155		"date":       evalContext.EndTime.Unix(),
156		"attributes": attributes,
157	}
158	if hc.NeedsImage() && evalContext.ImagePublicURL != "" {
159		card["thumbnail"] = map[string]interface{}{
160			"url":    evalContext.ImagePublicURL,
161			"url@2x": evalContext.ImagePublicURL,
162			"width":  1193,
163			"height": 564,
164		}
165	}
166
167	body := map[string]interface{}{
168		"message":        message,
169		"notify":         "true",
170		"message_format": "html",
171		"color":          color,
172		"card":           card,
173	}
174
175	hipURL := fmt.Sprintf("%s/v2/room/%s/notification?auth_token=%s", hc.URL, hc.RoomID, hc.APIKey)
176	data, _ := json.Marshal(&body)
177	hc.log.Info("Request payload", "json", string(data))
178	cmd := &models.SendWebhookSync{Url: hipURL, Body: string(data)}
179
180	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
181		hc.log.Error("Failed to send hipchat notification", "error", err, "webhook", hc.Name)
182		return err
183	}
184
185	return nil
186}
187