diff --git a/pkg/services/ngalert/notifier/channels/googlechat.go b/pkg/services/ngalert/notifier/channels/googlechat.go index f4590101706..544daf92fbd 100644 --- a/pkg/services/ngalert/notifier/channels/googlechat.go +++ b/pkg/services/ngalert/notifier/channels/googlechat.go @@ -22,59 +22,60 @@ import ( // alert notifications to Google chat. type GoogleChatNotifier struct { *Base - URL string - log log.Logger - ns notifications.WebhookSender - images ImageStore - tmpl *template.Template - content string + log log.Logger + ns notifications.WebhookSender + images ImageStore + tmpl *template.Template + settings googleChatSettings } -type GoogleChatConfig struct { - *NotificationChannelConfig +type googleChatSettings struct { URL string + Title string Content string } func GoogleChatFactory(fc FactoryConfig) (NotificationChannel, error) { - cfg, err := NewGoogleChatConfig(fc.Config) + gcn, err := newGoogleChatNotifier(fc) if err != nil { return nil, receiverInitError{ Reason: err.Error(), Cfg: *fc.Config, } } - return NewGoogleChatNotifier(cfg, fc.ImageStore, fc.NotificationService, fc.Template), nil + return gcn, nil } -func NewGoogleChatConfig(config *NotificationChannelConfig) (*GoogleChatConfig, error) { - url := config.Settings.Get("url").MustString() - if url == "" { +func newGoogleChatNotifier(fc FactoryConfig) (*GoogleChatNotifier, error) { + var settings googleChatSettings + err := fc.Config.unmarshalSettings(&settings) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal settings: %w", err) + } + + URL := fc.Config.Settings.Get("url").MustString() + if URL == "" { return nil, errors.New("could not find url property in settings") } - return &GoogleChatConfig{ - NotificationChannelConfig: config, - URL: url, - Content: config.Settings.Get("message").MustString(DefaultMessageEmbed), - }, nil -} -func NewGoogleChatNotifier(config *GoogleChatConfig, images ImageStore, ns notifications.WebhookSender, t *template.Template) *GoogleChatNotifier { return &GoogleChatNotifier{ 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, }), - content: config.Content, - URL: config.URL, - log: log.New("alerting.notifier.googlechat"), - ns: ns, - images: images, - tmpl: t, - } + log: log.New("alerting.notifier.googlechat"), + ns: fc.NotificationService, + images: fc.ImageStore, + tmpl: fc.Template, + settings: googleChatSettings{ + URL: URL, + Title: fc.Config.Settings.Get("title").MustString(DefaultMessageTitleEmbed), + Content: fc.Config.Settings.Get("message").MustString(DefaultMessageEmbed), + }, + }, nil } // Notify send an alert notification to Google Chat. @@ -84,16 +85,12 @@ func (gcn *GoogleChatNotifier) Notify(ctx context.Context, as ...*types.Alert) ( var tmplErr error tmpl, _ := TmplText(ctx, gcn.tmpl, as, gcn.log, &tmplErr) - widgets := []widget{} + var widgets []widget - if msg := tmpl(gcn.content); msg != "" { + if msg := tmpl(gcn.settings.Content); msg != "" { // Add a text paragraph widget for the message if there is a message. // Google Chat API doesn't accept an empty text property. - widgets = append(widgets, textParagraphWidget{ - Text: text{ - Text: msg, - }, - }) + widgets = append(widgets, textParagraphWidget{Text: text{Text: msg}}) } if tmplErr != nil { @@ -129,19 +126,16 @@ func (gcn *GoogleChatNotifier) Notify(ctx context.Context, as ...*types.Alert) ( }, }) + title := tmpl(gcn.settings.Title) // Nest the required structs. res := &outerStruct{ - PreviewText: tmpl(DefaultMessageTitleEmbed), - FallbackText: tmpl(DefaultMessageTitleEmbed), + PreviewText: title, + FallbackText: title, Cards: []card{ { - Header: header{ - Title: tmpl(DefaultMessageTitleEmbed), - }, + Header: header{Title: title}, Sections: []section{ - { - Widgets: widgets, - }, + {Widgets: widgets}, }, }, }, @@ -155,10 +149,10 @@ func (gcn *GoogleChatNotifier) Notify(ctx context.Context, as ...*types.Alert) ( tmplErr = nil } - u := tmpl(gcn.URL) + u := tmpl(gcn.settings.URL) if tmplErr != nil { - gcn.log.Warn("failed to template GoogleChat URL", "error", tmplErr.Error(), "fallback", gcn.URL) - u = gcn.URL + gcn.log.Warn("failed to template GoogleChat URL", "error", tmplErr.Error(), "fallback", gcn.settings.URL) + u = gcn.settings.URL } body, err := json.Marshal(res) @@ -199,9 +193,7 @@ func (gcn *GoogleChatNotifier) isUrlAbsolute(urlToCheck string) bool { func (gcn *GoogleChatNotifier) buildScreenshotCard(ctx context.Context, alerts []*types.Alert) *card { card := card{ - Header: header{ - Title: "Screenshots", - }, + Header: header{Title: "Screenshots"}, Sections: []section{}, } @@ -218,11 +210,7 @@ func (gcn *GoogleChatNotifier) buildScreenshotCard(ctx context.Context, alerts [ Text: fmt.Sprintf("%s: %s", alerts[index].Status(), alerts[index].Name()), }, }, - imageWidget{ - Image: imageData{ - ImageURL: image.URL, - }, - }, + imageWidget{Image: imageData{ImageURL: image.URL}}, }, } card.Sections = append(card.Sections, section) diff --git a/pkg/services/ngalert/notifier/channels/googlechat_test.go b/pkg/services/ngalert/notifier/channels/googlechat_test.go index ddf68a8c292..83e621cd849 100644 --- a/pkg/services/ngalert/notifier/channels/googlechat_test.go +++ b/pkg/services/ngalert/notifier/channels/googlechat_test.go @@ -203,6 +203,61 @@ func TestGoogleChatNotifier(t *testing.T) { }, }, expMsgError: nil, + }, { + name: "Customized title", + settings: `{"url": "http://localhost", "title": "Alerts firing: {{ len .Alerts.Firing }}"}`, + externalURL: "http://localhost", + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"}, + Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"}, + }, + }, + }, + expMsg: &outerStruct{ + PreviewText: "Alerts firing: 1", + FallbackText: "Alerts firing: 1", + Cards: []card{ + { + Header: header{ + Title: "Alerts firing: 1", + }, + Sections: []section{ + { + Widgets: []widget{ + textParagraphWidget{ + Text: text{ + Text: "**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", + }, + }, + buttonWidget{ + Buttons: []button{ + { + TextButton: textButton{ + Text: "OPEN IN GRAFANA", + OnClick: onClick{ + OpenLink: openLink{ + URL: "http://localhost/alerting/list", + }, + }, + }, + }, + }, + }, + textParagraphWidget{ + Text: text{ + // RFC822 only has the minute, hence it works in most cases. + Text: "Grafana v" + setting.BuildVersion + " | " + constNow.Format(time.RFC822), + }, + }, + }, + }, + }, + }, + }, + }, + expMsgError: nil, }, { name: "Missing field in template", settings: `{"url": "http://localhost", "message": "I'm a custom template {{ .NotAField }} bad template"}`, @@ -410,25 +465,30 @@ func TestGoogleChatNotifier(t *testing.T) { settingsJSON, err := simplejson.NewJson([]byte(c.settings)) require.NoError(t, err) - m := &NotificationChannelConfig{ - Name: "googlechat_testing", - Type: "googlechat", - Settings: settingsJSON, + webhookSender := mockNotificationService() + imageStore := &UnavailableImageStore{} + + fc := FactoryConfig{ + Config: &NotificationChannelConfig{ + Name: "googlechat_testing", + Type: "googlechat", + Settings: settingsJSON, + }, + ImageStore: imageStore, + NotificationService: webhookSender, + Template: tmpl, } - webhookSender := mockNotificationService() - cfg, err := NewGoogleChatConfig(m) + pn, err := newGoogleChatNotifier(fc) if c.expInitError != "" { require.Error(t, err) require.Equal(t, c.expInitError, err.Error()) return } require.NoError(t, err) - imageStore := &UnavailableImageStore{} ctx := notify.WithGroupKey(context.Background(), "alertname") ctx = notify.WithGroupLabels(ctx, model.LabelSet{"alertname": ""}) - pn := NewGoogleChatNotifier(cfg, imageStore, 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 9734d6c0053..18d798c2205 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels.go @@ -904,6 +904,14 @@ func GetAvailableNotifiers() []*NotifierPlugin { PropertyName: "url", Required: true, }, + { + Label: "Title", + Description: "Templated title of the message", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: channels.DefaultMessageTitleEmbed, + PropertyName: "title", + }, { Label: "Message", Element: ElementTypeTextArea,