diff --git a/config/config.go b/config/config.go index 5bed42d45e..ffceabcea4 100644 --- a/config/config.go +++ b/config/config.go @@ -100,6 +100,7 @@ type Config struct { SigningSecret string `password:"true" info:"Signing secret to verify requests from slack."` InteractiveMessages bool `info:"Enable interactive messages (e.g. buttons)."` + DisableBroadcastThreadReplies bool `info:"Disable broadcasting alert status updates in threads to the main channel." public:"true"` } Twilio struct { diff --git a/graphql2/mapconfig.go b/graphql2/mapconfig.go index f53c8bd316..c6b1cd4a06 100644 --- a/graphql2/mapconfig.go +++ b/graphql2/mapconfig.go @@ -68,6 +68,7 @@ func MapConfigValues(cfg config.Config) []ConfigValue { {ID: "Slack.AccessToken", Type: ConfigTypeString, Description: "Slack app bot user OAuth access token (should start with xoxb-).", Value: cfg.Slack.AccessToken, Password: true}, {ID: "Slack.SigningSecret", Type: ConfigTypeString, Description: "Signing secret to verify requests from slack.", Value: cfg.Slack.SigningSecret, Password: true}, {ID: "Slack.InteractiveMessages", Type: ConfigTypeBoolean, Description: "Enable interactive messages (e.g. buttons).", Value: fmt.Sprintf("%t", cfg.Slack.InteractiveMessages)}, + {ID: "Slack.DisableBroadcastThreadReplies", Type: ConfigTypeBoolean, Description: "Disable broadcasting alert status updates in threads to the main channel.", Value: fmt.Sprintf("%t", cfg.Slack.DisableBroadcastThreadReplies)}, {ID: "Twilio.Enable", Type: ConfigTypeBoolean, Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, {ID: "Twilio.VoiceName", Type: ConfigTypeString, Description: "The Twilio voice to use for Text To Speech for phone calls. See https://www.twilio.com/docs/voice/twiml/say/text-speech#polly-standard-and-neural-voices", Value: cfg.Twilio.VoiceName}, {ID: "Twilio.VoiceLanguage", Type: ConfigTypeString, Description: "The Twilio voice language to use for Text To Speech for phone calls. See https://www.twilio.com/docs/voice/twiml/say/text-speech#polly-standard-and-neural-voices", Value: cfg.Twilio.VoiceLanguage}, @@ -117,6 +118,7 @@ func MapPublicConfigValues(cfg config.Config) []ConfigValue { {ID: "OIDC.Enable", Type: ConfigTypeBoolean, Description: "Enable OpenID Connect authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.Enable)}, {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, {ID: "Slack.Enable", Type: ConfigTypeBoolean, Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, + {ID: "Slack.DisableBroadcastThreadReplies", Type: ConfigTypeBoolean, Description: "Disable broadcasting alert status updates in threads to the main channel.", Value: fmt.Sprintf("%t", cfg.Slack.DisableBroadcastThreadReplies)}, {ID: "Twilio.Enable", Type: ConfigTypeBoolean, Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, {ID: "Twilio.FromNumber", Type: ConfigTypeString, Description: "The Twilio number to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, {ID: "Twilio.MessagingServiceSID", Type: ConfigTypeString, Description: "If set, replaces the use of From Number for SMS notifications.", Value: cfg.Twilio.MessagingServiceSID}, @@ -313,6 +315,12 @@ func ApplyConfigValues(cfg config.Config, vals []ConfigValueInput) (config.Confi return cfg, err } cfg.Slack.InteractiveMessages = val + case "Slack.DisableBroadcastThreadReplies": + val, err := parseBool(v.ID, v.Value) + if err != nil { + return cfg, err + } + cfg.Slack.DisableBroadcastThreadReplies = val case "Twilio.Enable": val, err := parseBool(v.ID, v.Value) if err != nil { diff --git a/notification/slack/channel.go b/notification/slack/channel.go index fadc8259e7..c2f33ed892 100644 --- a/notification/slack/channel.go +++ b/notification/slack/channel.go @@ -457,11 +457,17 @@ func (s *ChannelSender) SendMessage(ctx context.Context, msg notification.Messag channelID, ts = chanTS(channelID, t.OriginalStatus.ProviderMessageID.ExternalID) // Reply in thread if we already sent a message for this alert. - opts = append(opts, + threadOpts := []slack.MsgOption{ slack.MsgOptionTS(ts), - slack.MsgOptionBroadcast(), slack.MsgOptionText(alertLink(ctx, t.AlertID, t.Summary), false), - ) + } + + // Conditionally add broadcast based on config (default: enabled) + if !cfg.Slack.DisableBroadcastThreadReplies { + threadOpts = append(threadOpts, slack.MsgOptionBroadcast()) + } + + opts = append(opts, threadOpts...) break } diff --git a/test/smoke/slacknotification_test.go b/test/smoke/slacknotification_test.go index 540c64a272..6c3721bfa0 100644 --- a/test/smoke/slacknotification_test.go +++ b/test/smoke/slacknotification_test.go @@ -42,3 +42,41 @@ func TestSlackNotification(t *testing.T) { // should broadcast reply to channel msg.ExpectBroadcastReply("Alert #1") } + +// TestSlackNotification_NoBroadcast tests that thread replies don't broadcast when disabled. +func TestSlackNotification_NoBroadcast(t *testing.T) { + t.Parallel() + + sql := ` + insert into escalation_policies (id, name, repeat) + values + ({{uuid "eid"}}, 'esc policy', 1); + insert into escalation_policy_steps (id, escalation_policy_id, delay) + values + ({{uuid "esid"}}, {{uuid "eid"}}, 30); + + insert into notification_channels (id, type, name, value) + values + ({{uuid "chan"}}, 'SLACK', '#test', {{slackChannelID "test"}}); + + insert into escalation_policy_actions (escalation_policy_step_id, channel_id) + values + ({{uuid "esid"}}, {{uuid "chan"}}); + + insert into services (id, escalation_policy_id, name) + values + ({{uuid "sid"}}, {{uuid "eid"}}, 'service'); + ` + h := harness.NewHarness(t, sql, "slack-user-link") + defer h.Close() + + // Disable broadcast + h.SetConfigValue("Slack.DisableBroadcastThreadReplies", "true") + + h.CreateAlert(h.UUID("sid"), "testing") + msg := h.Slack().Channel("test").ExpectMessage("testing") + + h.FastForward(time.Hour) + // Should reply in thread WITHOUT broadcast + msg.ExpectThreadReply("Alert #1") +} diff --git a/web/src/schema.d.ts b/web/src/schema.d.ts index 9699d529d7..38972ffedd 100644 --- a/web/src/schema.d.ts +++ b/web/src/schema.d.ts @@ -1657,6 +1657,7 @@ type ConfigID = | 'Slack.AccessToken' | 'Slack.SigningSecret' | 'Slack.InteractiveMessages' + | 'Slack.DisableBroadcastThreadReplies' | 'Twilio.Enable' | 'Twilio.VoiceName' | 'Twilio.VoiceLanguage'