diff --git a/pkg/services/ngalert/notifier/channels/line.go b/pkg/services/ngalert/notifier/channels/line.go index fc3dcd72113..d46703f9280 100644 --- a/pkg/services/ngalert/notifier/channels/line.go +++ b/pkg/services/ngalert/notifier/channels/line.go @@ -19,78 +19,62 @@ var ( LineNotifyURL string = "https://notify-api.line.me/api/notify" ) -type LineConfig struct { - *NotificationChannelConfig - Token string +// LineNotifier is responsible for sending +// alert notifications to LINE. +type LineNotifier struct { + *Base + log log.Logger + ns notifications.WebhookSender + tmpl *template.Template + settings lineSettings +} + +type lineSettings struct { + token string + title string + description string } func LineFactory(fc FactoryConfig) (NotificationChannel, error) { - cfg, err := NewLineConfig(fc.Config, fc.DecryptFunc) + n, err := newLineNotifier(fc) if err != nil { return nil, receiverInitError{ Reason: err.Error(), Cfg: *fc.Config, } } - return NewLineNotifier(cfg, fc.NotificationService, fc.Template), nil + return n, nil } -func NewLineConfig(config *NotificationChannelConfig, decryptFunc GetDecryptedValueFn) (*LineConfig, error) { - token := decryptFunc(context.Background(), config.SecureSettings, "token", config.Settings.Get("token").MustString()) +// newLineNotifier is the constructor for the LINE notifier +func newLineNotifier(fc FactoryConfig) (*LineNotifier, error) { + token := fc.DecryptFunc(context.Background(), fc.Config.SecureSettings, "token", fc.Config.Settings.Get("token").MustString()) if token == "" { return nil, errors.New("could not find token in settings") } - return &LineConfig{ - NotificationChannelConfig: config, - Token: token, - }, nil -} + title := fc.Config.Settings.Get("title").MustString(DefaultMessageTitleEmbed) + description := fc.Config.Settings.Get("description").MustString(DefaultMessageEmbed) -// NewLineNotifier is the constructor for the LINE notifier -func NewLineNotifier(config *LineConfig, ns notifications.WebhookSender, t *template.Template) *LineNotifier { return &LineNotifier{ Base: NewBase(&models.AlertNotification{ - Uid: config.UID, - Name: config.Name, - Type: config.Type, - DisableResolveMessage: config.DisableResolveMessage, - Settings: config.Settings, + Uid: fc.Config.UID, + Name: fc.Config.Name, + Type: fc.Config.Type, + DisableResolveMessage: fc.Config.DisableResolveMessage, + Settings: fc.Config.Settings, }), - Token: config.Token, - log: log.New("alerting.notifier.line"), - ns: ns, - tmpl: t, - } -} - -// LineNotifier is responsible for sending -// alert notifications to LINE. -type LineNotifier struct { - *Base - Token string - log log.Logger - ns notifications.WebhookSender - tmpl *template.Template + log: log.New("alerting.notifier.line"), + ns: fc.NotificationService, + tmpl: fc.Template, + settings: lineSettings{token: token, title: title, description: description}, + }, nil } // Notify send an alert notification to LINE func (ln *LineNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { ln.log.Debug("executing line notification", "notification", ln.Name) - ruleURL := path.Join(ln.tmpl.ExternalURL.String(), "/alerting/list") - - var tmplErr error - tmpl, _ := TmplText(ctx, ln.tmpl, as, ln.log, &tmplErr) - - body := fmt.Sprintf( - "%s\n%s\n\n%s", - tmpl(DefaultMessageTitleEmbed), - ruleURL, - tmpl(DefaultMessageEmbed), - ) - if tmplErr != nil { - ln.log.Warn("failed to template Line message", "error", tmplErr.Error()) - } + body := ln.buildMessage(ctx, as...) form := url.Values{} form.Add("message", body) @@ -99,7 +83,7 @@ func (ln *LineNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, e Url: LineNotifyURL, HttpMethod: "POST", HttpHeader: map[string]string{ - "Authorization": fmt.Sprintf("Bearer %s", ln.Token), + "Authorization": fmt.Sprintf("Bearer %s", ln.settings.token), "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", }, Body: form.Encode(), @@ -116,3 +100,21 @@ func (ln *LineNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, e func (ln *LineNotifier) SendResolved() bool { return !ln.GetDisableResolveMessage() } + +func (ln *LineNotifier) buildMessage(ctx context.Context, as ...*types.Alert) string { + ruleURL := path.Join(ln.tmpl.ExternalURL.String(), "/alerting/list") + + var tmplErr error + tmpl, _ := TmplText(ctx, ln.tmpl, as, ln.log, &tmplErr) + + body := fmt.Sprintf( + "%s\n%s\n\n%s", + tmpl(ln.settings.title), + ruleURL, + tmpl(ln.settings.description), + ) + if tmplErr != nil { + ln.log.Warn("failed to template Line message", "error", tmplErr.Error()) + } + return body +} diff --git a/pkg/services/ngalert/notifier/channels/line_test.go b/pkg/services/ngalert/notifier/channels/line_test.go index 259045d3d93..a03f521b60e 100644 --- a/pkg/services/ngalert/notifier/channels/line_test.go +++ b/pkg/services/ngalert/notifier/channels/line_test.go @@ -70,6 +70,23 @@ func TestLineNotifier(t *testing.T) { }, expMsg: "message=%5BFIRING%3A2%5D++%0Ahttp%3A%2Flocalhost%2Falerting%2Flist%0A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+%5Bno+value%5D%0ALabels%3A%0A+-+alertname+%3D+alert1%0A+-+lbl1+%3D+val1%0AAnnotations%3A%0A+-+ann1+%3D+annv1%0ASilence%3A+http%3A%2F%2Flocalhost%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253Dalert1%26matcher%3Dlbl1%253Dval1%0A%0AValue%3A+%5Bno+value%5D%0ALabels%3A%0A+-+alertname+%3D+alert1%0A+-+lbl1+%3D+val2%0AAnnotations%3A%0A+-+ann1+%3D+annv2%0ASilence%3A+http%3A%2F%2Flocalhost%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253Dalert1%26matcher%3Dlbl1%253Dval2%0A", expMsgError: nil, + }, { + name: "One alert custom title and description", + settings: `{"token": "sometoken", "title": "customTitle {{ .Alerts.Firing | len }}", "description": "customDescription"}`, + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"}, + Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"}, + }, + }, + }, + expHeaders: map[string]string{ + "Authorization": "Bearer sometoken", + "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", + }, + expMsg: "message=customTitle+1%0Ahttp%3A%2Flocalhost%2Falerting%2Flist%0A%0AcustomDescription", + expMsgError: nil, }, { name: "Token missing", settings: `{}`, @@ -82,18 +99,23 @@ func TestLineNotifier(t *testing.T) { settingsJSON, err := simplejson.NewJson([]byte(c.settings)) require.NoError(t, err) secureSettings := make(map[string][]byte) - - m := &NotificationChannelConfig{ - Name: "line_testing", - Type: "line", - Settings: settingsJSON, - SecureSettings: secureSettings, - } - webhookSender := mockNotificationService() secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) decryptFn := secretsService.GetDecryptedValue - cfg, err := NewLineConfig(m, decryptFn) + + fc := FactoryConfig{ + Config: &NotificationChannelConfig{ + Name: "line_testing", + Type: "line", + Settings: settingsJSON, + SecureSettings: secureSettings, + }, + // TODO: allow changing the associated values for different tests. + NotificationService: webhookSender, + DecryptFunc: decryptFn, + Template: tmpl, + } + pn, err := newLineNotifier(fc) if c.expInitError != "" { require.Error(t, err) require.Equal(t, c.expInitError, err.Error()) @@ -103,7 +125,6 @@ func TestLineNotifier(t *testing.T) { ctx := notify.WithGroupKey(context.Background(), "alertname") ctx = notify.WithGroupLabels(ctx, model.LabelSet{"alertname": ""}) - pn := NewLineNotifier(cfg, webhookSender, tmpl) ok, err := pn.Notify(ctx, c.alerts...) if c.expMsgError != nil { require.False(t, ok) diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels.go b/pkg/services/ngalert/notifier/channels_config/available_channels.go index a93ff12f3c4..e33cfe62ddd 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels.go @@ -893,7 +893,24 @@ func GetAvailableNotifiers() []*NotifierPlugin { PropertyName: "token", Required: true, Secure: true, - }}, + }, + { // New in 9.3 + Label: "Title", + Element: ElementTypeInput, + InputType: InputTypeText, + Description: "Templated title of the message", + PropertyName: "title", + Placeholder: channels.DefaultMessageTitleEmbed, + }, + { // New in 9.3 + Label: "Description", + Element: ElementTypeInput, + InputType: InputTypeText, + Description: "Templated description of the message", + PropertyName: "description", + Placeholder: channels.DefaultMessageEmbed, + }, + }, }, { Type: "threema",