mirror of
https://github.com/grafana/grafana.git
synced 2026-07-30 08:18:10 -05:00
Alerting: Add custom title to pushover contact point (#57530)
* Add custom title to pushover contact point * Update pkg/services/ngalert/notifier/channels/pushover.go Co-authored-by: Santiago <santiagohernandez.1997@gmail.com> * Use simplejson * Use more verbose variable names Co-authored-by: Santiago <santiagohernandez.1997@gmail.com>
This commit is contained in:
@@ -32,112 +32,90 @@ var (
|
||||
// alert notifications to Pushover
|
||||
type PushoverNotifier struct {
|
||||
*Base
|
||||
UserKey string
|
||||
APIToken string
|
||||
AlertingPriority int
|
||||
OKPriority int
|
||||
Retry int
|
||||
Expire int
|
||||
Device string
|
||||
AlertingSound string
|
||||
OKSound string
|
||||
Upload bool
|
||||
Message string
|
||||
tmpl *template.Template
|
||||
log log.Logger
|
||||
images ImageStore
|
||||
ns notifications.WebhookSender
|
||||
tmpl *template.Template
|
||||
log log.Logger
|
||||
images ImageStore
|
||||
ns notifications.WebhookSender
|
||||
settings pushoverSettings
|
||||
}
|
||||
|
||||
type PushoverConfig struct {
|
||||
*NotificationChannelConfig
|
||||
UserKey string
|
||||
APIToken string
|
||||
AlertingPriority int
|
||||
OKPriority int
|
||||
Retry int
|
||||
Expire int
|
||||
Device string
|
||||
AlertingSound string
|
||||
OKSound string
|
||||
Upload bool
|
||||
Message string
|
||||
type pushoverSettings struct {
|
||||
userKey string
|
||||
apiToken string
|
||||
alertingPriority int
|
||||
okPriority int
|
||||
retry int
|
||||
expire int
|
||||
device string
|
||||
alertingSound string
|
||||
okSound string
|
||||
upload bool
|
||||
title string
|
||||
message string
|
||||
}
|
||||
|
||||
func PushoverFactory(fc FactoryConfig) (NotificationChannel, error) {
|
||||
cfg, err := NewPushoverConfig(fc.Config, fc.DecryptFunc)
|
||||
pn, err := newPushoverNotifier(fc)
|
||||
if err != nil {
|
||||
return nil, receiverInitError{
|
||||
Reason: err.Error(),
|
||||
Cfg: *fc.Config,
|
||||
}
|
||||
}
|
||||
return NewPushoverNotifier(cfg, fc.ImageStore, fc.NotificationService, fc.Template), nil
|
||||
return pn, nil
|
||||
}
|
||||
|
||||
func NewPushoverConfig(config *NotificationChannelConfig, decryptFunc GetDecryptedValueFn) (*PushoverConfig, error) {
|
||||
userKey := decryptFunc(context.Background(), config.SecureSettings, "userKey", config.Settings.Get("userKey").MustString())
|
||||
// newPushoverNotifier is the constructor for the Pushover notifier
|
||||
func newPushoverNotifier(fc FactoryConfig) (*PushoverNotifier, error) {
|
||||
decryptFunc := fc.DecryptFunc
|
||||
userKey := decryptFunc(context.Background(), fc.Config.SecureSettings, "userKey", fc.Config.Settings.Get("userKey").MustString())
|
||||
if userKey == "" {
|
||||
return nil, errors.New("user key not found")
|
||||
}
|
||||
APIToken := decryptFunc(context.Background(), config.SecureSettings, "apiToken", config.Settings.Get("apiToken").MustString())
|
||||
if APIToken == "" {
|
||||
apiToken := decryptFunc(context.Background(), fc.Config.SecureSettings, "apiToken", fc.Config.Settings.Get("apiToken").MustString())
|
||||
if apiToken == "" {
|
||||
return nil, errors.New("API token not found")
|
||||
}
|
||||
alertingPriority, err := strconv.Atoi(config.Settings.Get("priority").MustString("0")) // default Normal
|
||||
|
||||
alertingPriority, err := strconv.Atoi(fc.Config.Settings.Get("priority").MustString("0")) // default Normal
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert alerting priority to integer: %w", err)
|
||||
}
|
||||
okPriority, err := strconv.Atoi(config.Settings.Get("okPriority").MustString("0")) // default Normal
|
||||
okPriority, err := strconv.Atoi(fc.Config.Settings.Get("okPriority").MustString("0")) // default Normal
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert OK priority to integer: %w", err)
|
||||
}
|
||||
retry, _ := strconv.Atoi(config.Settings.Get("retry").MustString())
|
||||
expire, _ := strconv.Atoi(config.Settings.Get("expire").MustString())
|
||||
return &PushoverConfig{
|
||||
NotificationChannelConfig: config,
|
||||
APIToken: APIToken,
|
||||
UserKey: userKey,
|
||||
AlertingPriority: alertingPriority,
|
||||
OKPriority: okPriority,
|
||||
Retry: retry,
|
||||
Expire: expire,
|
||||
Device: config.Settings.Get("device").MustString(),
|
||||
AlertingSound: config.Settings.Get("sound").MustString(),
|
||||
OKSound: config.Settings.Get("okSound").MustString(),
|
||||
Upload: config.Settings.Get("uploadImage").MustBool(true),
|
||||
Message: config.Settings.Get("message").MustString(DefaultMessageEmbed),
|
||||
}, nil
|
||||
}
|
||||
retry, _ := strconv.Atoi(fc.Config.Settings.Get("retry").MustString())
|
||||
expire, _ := strconv.Atoi(fc.Config.Settings.Get("expire").MustString())
|
||||
|
||||
// NewSlackNotifier is the constructor for the Slack notifier
|
||||
func NewPushoverNotifier(config *PushoverConfig, images ImageStore,
|
||||
ns notifications.WebhookSender, t *template.Template) *PushoverNotifier {
|
||||
return &PushoverNotifier{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: config.UID,
|
||||
Name: config.Name,
|
||||
Type: config.Type,
|
||||
DisableResolveMessage: config.DisableResolveMessage,
|
||||
Settings: config.Settings,
|
||||
SecureSettings: config.SecureSettings,
|
||||
Uid: fc.Config.UID,
|
||||
Name: fc.Config.Name,
|
||||
Type: fc.Config.Type,
|
||||
DisableResolveMessage: fc.Config.DisableResolveMessage,
|
||||
Settings: fc.Config.Settings,
|
||||
SecureSettings: fc.Config.SecureSettings,
|
||||
}),
|
||||
UserKey: config.UserKey,
|
||||
APIToken: config.APIToken,
|
||||
AlertingPriority: config.AlertingPriority,
|
||||
OKPriority: config.OKPriority,
|
||||
Retry: config.Retry,
|
||||
Expire: config.Expire,
|
||||
Device: config.Device,
|
||||
AlertingSound: config.AlertingSound,
|
||||
OKSound: config.OKSound,
|
||||
Upload: config.Upload,
|
||||
Message: config.Message,
|
||||
tmpl: t,
|
||||
log: log.New("alerting.notifier.pushover"),
|
||||
images: images,
|
||||
ns: ns,
|
||||
}
|
||||
tmpl: fc.Template,
|
||||
log: log.New("alerting.notifier.pushover"),
|
||||
images: fc.ImageStore,
|
||||
ns: fc.NotificationService,
|
||||
settings: pushoverSettings{
|
||||
userKey: userKey,
|
||||
apiToken: apiToken,
|
||||
alertingPriority: alertingPriority,
|
||||
okPriority: okPriority,
|
||||
retry: retry,
|
||||
expire: expire,
|
||||
device: fc.Config.Settings.Get("device").MustString(),
|
||||
alertingSound: fc.Config.Settings.Get("sound").MustString(),
|
||||
okSound: fc.Config.Settings.Get("okSound").MustString(),
|
||||
upload: fc.Config.Settings.Get("uploadImage").MustBool(true),
|
||||
title: fc.Config.Settings.Get("title").MustString(DefaultMessageTitleEmbed),
|
||||
message: fc.Config.Settings.Get("message").MustString(DefaultMessageEmbed),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Notify sends an alert notification to Slack.
|
||||
@@ -181,40 +159,40 @@ func (pn *PushoverNotifier) genPushoverBody(ctx context.Context, as ...*types.Al
|
||||
var tmplErr error
|
||||
tmpl, _ := TmplText(ctx, pn.tmpl, as, pn.log, &tmplErr)
|
||||
|
||||
if err := w.WriteField("user", tmpl(pn.UserKey)); err != nil {
|
||||
if err := w.WriteField("user", tmpl(pn.settings.userKey)); err != nil {
|
||||
return nil, b, fmt.Errorf("failed to write the user: %w", err)
|
||||
}
|
||||
|
||||
if err := w.WriteField("token", pn.APIToken); err != nil {
|
||||
if err := w.WriteField("token", pn.settings.apiToken); err != nil {
|
||||
return nil, b, fmt.Errorf("failed to write the token: %w", err)
|
||||
}
|
||||
|
||||
status := types.Alerts(as...).Status()
|
||||
priority := pn.AlertingPriority
|
||||
priority := pn.settings.alertingPriority
|
||||
if status == model.AlertResolved {
|
||||
priority = pn.OKPriority
|
||||
priority = pn.settings.okPriority
|
||||
}
|
||||
if err := w.WriteField("priority", strconv.Itoa(priority)); err != nil {
|
||||
return nil, b, fmt.Errorf("failed to write the priority: %w", err)
|
||||
}
|
||||
|
||||
if priority == 2 {
|
||||
if err := w.WriteField("retry", strconv.Itoa(pn.Retry)); err != nil {
|
||||
if err := w.WriteField("retry", strconv.Itoa(pn.settings.retry)); err != nil {
|
||||
return nil, b, fmt.Errorf("failed to write retry: %w", err)
|
||||
}
|
||||
|
||||
if err := w.WriteField("expire", strconv.Itoa(pn.Expire)); err != nil {
|
||||
if err := w.WriteField("expire", strconv.Itoa(pn.settings.expire)); err != nil {
|
||||
return nil, b, fmt.Errorf("failed to write expire: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if pn.Device != "" {
|
||||
if err := w.WriteField("device", tmpl(pn.Device)); err != nil {
|
||||
if pn.settings.device != "" {
|
||||
if err := w.WriteField("device", tmpl(pn.settings.device)); err != nil {
|
||||
return nil, b, fmt.Errorf("failed to write the device: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.WriteField("title", tmpl(DefaultMessageTitleEmbed)); err != nil {
|
||||
if err := w.WriteField("title", tmpl(pn.settings.title)); err != nil {
|
||||
return nil, b, fmt.Errorf("failed to write the title: %w", err)
|
||||
}
|
||||
|
||||
@@ -227,7 +205,7 @@ func (pn *PushoverNotifier) genPushoverBody(ctx context.Context, as ...*types.Al
|
||||
return nil, b, fmt.Errorf("failed to write the URL title: %w", err)
|
||||
}
|
||||
|
||||
if err := w.WriteField("message", tmpl(pn.Message)); err != nil {
|
||||
if err := w.WriteField("message", tmpl(pn.settings.message)); err != nil {
|
||||
return nil, b, fmt.Errorf("failed write the message: %w", err)
|
||||
}
|
||||
|
||||
@@ -267,9 +245,9 @@ func (pn *PushoverNotifier) genPushoverBody(ctx context.Context, as ...*types.Al
|
||||
|
||||
var sound string
|
||||
if status == model.AlertResolved {
|
||||
sound = tmpl(pn.OKSound)
|
||||
sound = tmpl(pn.settings.okSound)
|
||||
} else {
|
||||
sound = tmpl(pn.AlertingSound)
|
||||
sound = tmpl(pn.settings.alertingSound)
|
||||
}
|
||||
if sound != "default" {
|
||||
if err := w.WriteField("sound", sound); err != nil {
|
||||
|
||||
@@ -67,6 +67,35 @@ func TestPushoverNotifier(t *testing.T) {
|
||||
},
|
||||
expMsgError: nil,
|
||||
},
|
||||
{
|
||||
name: "Custom title",
|
||||
settings: `{
|
||||
"userKey": "<userKey>",
|
||||
"apiToken": "<apiToken>",
|
||||
"title": "Alerts firing: {{ len .Alerts.Firing }}"
|
||||
}`,
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"__alert_rule_uid__": "rule uid", "alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh", "__alertImageToken__": "test-image-1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expMsg: map[string]string{
|
||||
"user": "<userKey>",
|
||||
"token": "<apiToken>",
|
||||
"priority": "0",
|
||||
"sound": "",
|
||||
"title": "Alerts firing: 1",
|
||||
"url": "http://localhost/alerting/list",
|
||||
"url_title": "Show alert rule",
|
||||
"message": "**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n",
|
||||
"attachment": "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\b\x04\x00\x00\x00\xb5\x1c\f\x02\x00\x00\x00\vIDATx\xdacd`\x00\x00\x00\x06\x00\x020\x81\xd0/\x00\x00\x00\x00IEND\xaeB`\x82",
|
||||
"html": "1",
|
||||
},
|
||||
expMsgError: nil,
|
||||
},
|
||||
{
|
||||
name: "Custom config with multiple alerts",
|
||||
settings: `{
|
||||
@@ -141,17 +170,23 @@ func TestPushoverNotifier(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
secureSettings := make(map[string][]byte)
|
||||
|
||||
m := &NotificationChannelConfig{
|
||||
Name: "pushover_testing",
|
||||
Type: "pushover",
|
||||
Settings: settingsJSON,
|
||||
SecureSettings: secureSettings,
|
||||
}
|
||||
|
||||
webhookSender := mockNotificationService()
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
decryptFn := secretsService.GetDecryptedValue
|
||||
cfg, err := NewPushoverConfig(m, decryptFn)
|
||||
|
||||
fc := FactoryConfig{
|
||||
Config: &NotificationChannelConfig{
|
||||
Name: "pushover_testing",
|
||||
Type: "pushover",
|
||||
Settings: settingsJSON,
|
||||
SecureSettings: secureSettings,
|
||||
},
|
||||
ImageStore: images,
|
||||
NotificationService: webhookSender,
|
||||
DecryptFunc: decryptFn,
|
||||
Template: tmpl,
|
||||
}
|
||||
pn, err := newPushoverNotifier(fc)
|
||||
if c.expInitError != "" {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, c.expInitError, err.Error())
|
||||
@@ -161,7 +196,6 @@ func TestPushoverNotifier(t *testing.T) {
|
||||
|
||||
ctx := notify.WithGroupKey(context.Background(), "alertname")
|
||||
ctx = notify.WithGroupLabels(ctx, model.LabelSet{"alertname": ""})
|
||||
pn := NewPushoverNotifier(cfg, images, webhookSender, tmpl)
|
||||
ok, err := pn.Notify(ctx, c.alerts...)
|
||||
if c.expMsgError != nil {
|
||||
require.Error(t, err)
|
||||
|
||||
@@ -410,6 +410,13 @@ func GetAvailableNotifiers() []*NotifierPlugin {
|
||||
SelectOptions: pushoverSoundOptions,
|
||||
PropertyName: "okSound",
|
||||
},
|
||||
{ // New in 9.3.
|
||||
Label: "Title",
|
||||
Element: ElementTypeInput,
|
||||
InputType: InputTypeText,
|
||||
Placeholder: channels.DefaultMessageTitleEmbed,
|
||||
PropertyName: "title",
|
||||
},
|
||||
{ // New in 8.0.
|
||||
Label: "Message",
|
||||
Element: ElementTypeTextArea,
|
||||
|
||||
Reference in New Issue
Block a user