Optional custom title and description for OpsGenie (#50131)

* optional custom description for OpsGenie

* custom title and message, tests

* update changelog

* check for empty / whitespace only strings

* truncate the title to 130 characters if needed

* unnecessary validation removed

* truncate title to 127 characters and add three dots
This commit is contained in:
Santiago 2022-06-08 17:55:31 -03:00 committed by GitHub
parent c59938b235
commit 9dc7e752b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 124 additions and 11 deletions

View File

@ -56,6 +56,7 @@ Scopes must have an order to ensure consistency and ease of search, this helps u
- [ENHANCEMENT] Create folder 'General Alerting' when Grafana starts from the scratch #48866 - [ENHANCEMENT] Create folder 'General Alerting' when Grafana starts from the scratch #48866
- [ENHANCEMENT] Rule changes authorization logic to use UID folder scope instead of ID scope #48970 - [ENHANCEMENT] Rule changes authorization logic to use UID folder scope instead of ID scope #48970
- [ENHANCEMENT] Scheduler: ticker to support stopping #48142 - [ENHANCEMENT] Scheduler: ticker to support stopping #48142
- [ENHANCEMENT] Optional custom title and description for OpsGenie #50131
- [ENHANCEMENT] Scheduler: Adds new metrics to track rules that might be scheduled #49874 - [ENHANCEMENT] Scheduler: Adds new metrics to track rules that might be scheduled #49874
- `grafana_alerting_schedule_alert_rules ` - `grafana_alerting_schedule_alert_rules `
- `grafana_alerting_schedule_alert_rules_hash ` - `grafana_alerting_schedule_alert_rules_hash `

View File

@ -863,6 +863,20 @@ func GetAvailableNotifiers() []*alerting.NotifierPlugin {
PropertyName: "apiUrl", PropertyName: "apiUrl",
Required: true, Required: true,
}, },
{
Label: "Message",
Description: "Alert text limited to 130 characters.",
Element: alerting.ElementTypeInput,
InputType: alerting.InputTypeText,
Placeholder: `{{ template "default.title" . }}`,
PropertyName: "message",
},
{
Label: "Description",
Description: "A description of the incident.",
Element: alerting.ElementTypeTextArea,
PropertyName: "description",
},
{ {
Label: "Auto close incidents", Label: "Auto close incidents",
Element: alerting.ElementTypeCheckbox, Element: alerting.ElementTypeCheckbox,

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"sort" "sort"
"strings"
"github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
@ -36,6 +37,8 @@ type OpsgenieNotifier struct {
*Base *Base
APIKey string APIKey string
APIUrl string APIUrl string
Message string
Description string
AutoClose bool AutoClose bool
OverridePriority bool OverridePriority bool
SendTagsAs string SendTagsAs string
@ -49,6 +52,8 @@ type OpsgenieConfig struct {
*NotificationChannelConfig *NotificationChannelConfig
APIKey string APIKey string
APIUrl string APIUrl string
Message string
Description string
AutoClose bool AutoClose bool
OverridePriority bool OverridePriority bool
SendTagsAs string SendTagsAs string
@ -82,6 +87,8 @@ func NewOpsgenieConfig(config *NotificationChannelConfig, decryptFunc GetDecrypt
APIUrl: config.Settings.Get("apiUrl").MustString(OpsgenieAlertURL), APIUrl: config.Settings.Get("apiUrl").MustString(OpsgenieAlertURL),
AutoClose: config.Settings.Get("autoClose").MustBool(true), AutoClose: config.Settings.Get("autoClose").MustBool(true),
OverridePriority: config.Settings.Get("overridePriority").MustBool(true), OverridePriority: config.Settings.Get("overridePriority").MustBool(true),
Message: config.Settings.Get("message").MustString(`{{ template "default.title" . }}`),
Description: config.Settings.Get("description").MustString(""),
SendTagsAs: sendTagsAs, SendTagsAs: sendTagsAs,
}, nil }, nil
} }
@ -98,6 +105,8 @@ func NewOpsgenieNotifier(config *OpsgenieConfig, ns notifications.WebhookSender,
}), }),
APIKey: config.APIKey, APIKey: config.APIKey,
APIUrl: config.APIUrl, APIUrl: config.APIUrl,
Description: config.Description,
Message: config.Message,
AutoClose: config.AutoClose, AutoClose: config.AutoClose,
OverridePriority: config.OverridePriority, OverridePriority: config.OverridePriority,
SendTagsAs: config.SendTagsAs, SendTagsAs: config.SendTagsAs,
@ -180,13 +189,25 @@ func (on *OpsgenieNotifier) buildOpsgenieMessage(ctx context.Context, alerts mod
var tmplErr error var tmplErr error
tmpl, data := TmplText(ctx, on.tmpl, as, on.log, &tmplErr) tmpl, data := TmplText(ctx, on.tmpl, as, on.log, &tmplErr)
title := tmpl(DefaultMessageTitleEmbed) titleTmpl := on.Message
description := fmt.Sprintf( if strings.TrimSpace(titleTmpl) == "" {
"%s\n%s\n\n%s", titleTmpl = `{{ template "default.title" . }}`
tmpl(DefaultMessageTitleEmbed), }
ruleURL,
tmpl(`{{ template "default.message" . }}`), title := tmpl(titleTmpl)
) if len(title) > 130 {
title = title[:127] + "..."
}
description := tmpl(on.Description)
if strings.TrimSpace(description) == "" {
description = fmt.Sprintf(
"%s\n%s\n\n%s",
tmpl(DefaultMessageTitleEmbed),
ruleURL,
tmpl(`{{ template "default.message" . }}`),
)
}
var priority string var priority string
@ -202,6 +223,12 @@ func (on *OpsgenieNotifier) buildOpsgenieMessage(ctx context.Context, alerts mod
} }
} }
// Check for templating errors
if tmplErr != nil {
on.log.Warn("failed to template Opsgenie message", "err", tmplErr.Error())
tmplErr = nil
}
bodyJSON.Set("message", title) bodyJSON.Set("message", title)
bodyJSON.Set("source", "Grafana") bodyJSON.Set("source", "Grafana")
bodyJSON.Set("alias", alias) bodyJSON.Set("alias", alias)
@ -244,9 +271,9 @@ func (on *OpsgenieNotifier) buildOpsgenieMessage(ctx context.Context, alerts mod
bodyJSON.Set("tags", tags) bodyJSON.Set("tags", tags)
bodyJSON.Set("details", details) bodyJSON.Set("details", details)
apiURL = tmpl(on.APIUrl) apiURL = tmpl(on.APIUrl)
if tmplErr != nil { if tmplErr != nil {
on.log.Warn("failed to template Opsgenie message", "err", tmplErr.Error()) on.log.Warn("failed to template Opsgenie URL", "err", tmplErr.Error(), "fallback", on.APIUrl)
apiURL = on.APIUrl
} }
return bodyJSON, apiURL, nil return bodyJSON, apiURL, nil

View File

@ -54,10 +54,81 @@ func TestOpsgenieNotifier(t *testing.T) {
}`, }`,
}, },
{ {
name: "Default config with one alert and send tags as tags", name: "Default config with one alert, custom message and description",
settings: `{"apiKey": "abcdefgh0123456789", "message": "test message", "description": "test description"}`,
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
},
},
},
expMsg: `{
"alias": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
"description": "test description",
"details": {
"url": "http://localhost/alerting/list"
},
"message": "test message",
"source": "Grafana",
"tags": ["alertname:alert1", "lbl1:val1"]
}`,
},
{
name: "Default config with one alert, message length > 130",
settings: `{ settings: `{
"apiKey": "abcdefgh0123456789", "apiKey": "abcdefgh0123456789",
"sendTagsAs": "tags" "message": "IyJnsW78xQoiBJ7L7NqASv31JCFf0At3r9KUykqBVxSiC6qkDhvDLDW9VImiFcq0Iw2XwFy5fX4FcbTmlkaZzUzjVwx9VUuokhzqQlJVhWDYFqhj3a5wX0LjyvNQjsqT9WaWJAWOJanwOAWon"
}`,
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
},
},
},
expMsg: `{
"alias": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
"description": "[FIRING:1] (val1)\nhttp://localhost/alerting/list\n\n**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",
"details": {
"url": "http://localhost/alerting/list"
},
"message": "IyJnsW78xQoiBJ7L7NqASv31JCFf0At3r9KUykqBVxSiC6qkDhvDLDW9VImiFcq0Iw2XwFy5fX4FcbTmlkaZzUzjVwx9VUuokhzqQlJVhWDYFqhj3a5wX0LjyvNQjsq...",
"source": "Grafana",
"tags": ["alertname:alert1", "lbl1:val1"]
}`,
},
{
name: "Default config with one alert, templated message and description",
settings: `{"apiKey": "abcdefgh0123456789", "message": "Firing: {{ len .Alerts.Firing }}", "description": "{{ len .Alerts.Firing }} firing, {{ len .Alerts.Resolved }} resolved."}`,
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
},
},
},
expMsg: `{
"alias": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
"description": "1 firing, 0 resolved.",
"details": {
"url": "http://localhost/alerting/list"
},
"message": "Firing: 1",
"source": "Grafana",
"tags": ["alertname:alert1", "lbl1:val1"]
}`,
},
{
name: "Default config with one alert and send tags as tags, empty description and message",
settings: `{
"apiKey": "abcdefgh0123456789",
"sendTagsAs": "tags",
"message": " ",
"description": " "
}`, }`,
alerts: []*types.Alert{ alerts: []*types.Alert{
{ {