diff --git a/app/channel.go b/app/channel.go index 14cd74d87b..99265f3408 100644 --- a/app/channel.go +++ b/app/channel.go @@ -663,6 +663,10 @@ func (a *App) UpdateChannelMemberNotifyProps(data map[string]string, channelId s member.NotifyProps[model.PUSH_NOTIFY_PROP] = push } + if ignoreChannelMentions, exists := data[model.IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP]; exists { + member.NotifyProps[model.IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP] = ignoreChannelMentions + } + result := <-a.Srv.Store.Channel().UpdateMember(member) if result.Err != nil { return nil, result.Err diff --git a/app/notification.go b/app/notification.go index 4501b311d1..6a26ec0030 100644 --- a/app/notification.go +++ b/app/notification.go @@ -84,7 +84,7 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod } } else { - keywords := a.GetMentionKeywordsInChannel(profileMap, post.Type != model.POST_HEADER_CHANGE && post.Type != model.POST_PURPOSE_CHANGE) + keywords := a.GetMentionKeywordsInChannel(profileMap, post.Type != model.POST_HEADER_CHANGE && post.Type != model.POST_PURPOSE_CHANGE, channelMemberNotifyPropsMap) m := GetExplicitMentions(post, keywords) @@ -555,7 +555,7 @@ func GetMentionsEnabledFields(post *model.Post) model.StringArray { // Given a map of user IDs to profiles, returns a list of mention // keywords for all users in the channel. -func (a *App) GetMentionKeywordsInChannel(profiles map[string]*model.User, lookForSpecialMentions bool) map[string][]string { +func (a *App) GetMentionKeywordsInChannel(profiles map[string]*model.User, lookForSpecialMentions bool, channelMemberNotifyPropsMap map[string]model.StringMap) map[string][]string { keywords := make(map[string][]string) for id, profile := range profiles { @@ -577,9 +577,16 @@ func (a *App) GetMentionKeywordsInChannel(profiles map[string]*model.User, lookF keywords[profile.FirstName] = append(keywords[profile.FirstName], profile.Id) } + ignoreChannelMentions := false + if ignoreChannelMentionsNotifyProp, ok := channelMemberNotifyPropsMap[profile.Id][model.IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP]; ok { + if ignoreChannelMentionsNotifyProp == model.IGNORE_CHANNEL_MENTIONS_ON { + ignoreChannelMentions = true + } + } + // Add @channel and @all to keywords if user has them turned on if lookForSpecialMentions { - if int64(len(profiles)) <= *a.Config().TeamSettings.MaxNotificationsPerChannel && profile.NotifyProps["channel"] == "true" { + if int64(len(profiles)) <= *a.Config().TeamSettings.MaxNotificationsPerChannel && profile.NotifyProps["channel"] == "true" && !ignoreChannelMentions { keywords["@channel"] = append(keywords["@channel"], profile.Id) keywords["@all"] = append(keywords["@all"], profile.Id) diff --git a/app/notification_test.go b/app/notification_test.go index fa7bf7209a..d2247ac685 100644 --- a/app/notification_test.go +++ b/app/notification_test.go @@ -625,8 +625,14 @@ func TestGetMentionKeywords(t *testing.T) { }, } + channelMemberNotifyPropsMap1Off := map[string]model.StringMap{ + user1.Id: { + "ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF, + }, + } + profiles := map[string]*model.User{user1.Id: user1} - mentions := th.App.GetMentionKeywordsInChannel(profiles, true) + mentions := th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap1Off) if len(mentions) != 3 { t.Fatal("should've returned three mention keywords") } else if ids, ok := mentions["user"]; !ok || ids[0] != user1.Id { @@ -647,8 +653,14 @@ func TestGetMentionKeywords(t *testing.T) { }, } + channelMemberNotifyPropsMap2Off := map[string]model.StringMap{ + user2.Id: { + "ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF, + }, + } + profiles = map[string]*model.User{user2.Id: user2} - mentions = th.App.GetMentionKeywordsInChannel(profiles, true) + mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap2Off) if len(mentions) != 2 { t.Fatal("should've returned two mention keyword") } else if ids, ok := mentions["First"]; !ok || ids[0] != user2.Id { @@ -665,8 +677,14 @@ func TestGetMentionKeywords(t *testing.T) { }, } + // Channel-wide mentions are not ignored on channel level + channelMemberNotifyPropsMap3Off := map[string]model.StringMap{ + user3.Id: { + "ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF, + }, + } profiles = map[string]*model.User{user3.Id: user3} - mentions = th.App.GetMentionKeywordsInChannel(profiles, true) + mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap3Off) if len(mentions) != 3 { t.Fatal("should've returned three mention keywords") } else if ids, ok := mentions["@channel"]; !ok || ids[0] != user3.Id { @@ -675,6 +693,45 @@ func TestGetMentionKeywords(t *testing.T) { t.Fatal("should've returned mention key of @all") } + // Channel member notify props is set to default + channelMemberNotifyPropsMapDefault := map[string]model.StringMap{ + user3.Id: { + "ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_DEFAULT, + }, + } + profiles = map[string]*model.User{user3.Id: user3} + mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMapDefault) + if len(mentions) != 3 { + t.Fatal("should've returned three mention keywords") + } else if ids, ok := mentions["@channel"]; !ok || ids[0] != user3.Id { + t.Fatal("should've returned mention key of @channel") + } else if ids, ok := mentions["@all"]; !ok || ids[0] != user3.Id { + t.Fatal("should've returned mention key of @all") + } + + // Channel member notify props is empty + channelMemberNotifyPropsMapEmpty := map[string]model.StringMap{} + profiles = map[string]*model.User{user3.Id: user3} + mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMapEmpty) + if len(mentions) != 3 { + t.Fatal("should've returned three mention keywords") + } else if ids, ok := mentions["@channel"]; !ok || ids[0] != user3.Id { + t.Fatal("should've returned mention key of @channel") + } else if ids, ok := mentions["@all"]; !ok || ids[0] != user3.Id { + t.Fatal("should've returned mention key of @all") + } + + // Channel-wide mentions are ignored channel level + channelMemberNotifyPropsMap3On := map[string]model.StringMap{ + user3.Id: { + "ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_ON, + }, + } + mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap3On) + if len(mentions) == 0 { + t.Fatal("should've not returned any keywords") + } + // user with all types of mentions enabled user4 := &model.User{ Id: model.NewId(), @@ -687,8 +744,15 @@ func TestGetMentionKeywords(t *testing.T) { }, } + // Channel-wide mentions are not ignored on channel level + channelMemberNotifyPropsMap4Off := map[string]model.StringMap{ + user4.Id: { + "ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF, + }, + } + profiles = map[string]*model.User{user4.Id: user4} - mentions = th.App.GetMentionKeywordsInChannel(profiles, true) + mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap4Off) if len(mentions) != 6 { t.Fatal("should've returned six mention keywords") } else if ids, ok := mentions["user"]; !ok || ids[0] != user4.Id { @@ -705,6 +769,25 @@ func TestGetMentionKeywords(t *testing.T) { t.Fatal("should've returned mention key of @all") } + // Channel-wide mentions are ignored on channel level + channelMemberNotifyPropsMap4On := map[string]model.StringMap{ + user4.Id: { + "ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_ON, + }, + } + mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap4On) + if len(mentions) != 4 { + t.Fatal("should've returned four mention keywords") + } else if ids, ok := mentions["user"]; !ok || ids[0] != user4.Id { + t.Fatal("should've returned mention key of user") + } else if ids, ok := mentions["@user"]; !ok || ids[0] != user4.Id { + t.Fatal("should've returned mention key of @user") + } else if ids, ok := mentions["mention"]; !ok || ids[0] != user4.Id { + t.Fatal("should've returned mention key of mention") + } else if ids, ok := mentions["First"]; !ok || ids[0] != user4.Id { + t.Fatal("should've returned mention key of First") + } + dup_count := func(list []string) map[string]int { duplicate_frequency := make(map[string]int) @@ -731,7 +814,22 @@ func TestGetMentionKeywords(t *testing.T) { user3.Id: user3, user4.Id: user4, } - mentions = th.App.GetMentionKeywordsInChannel(profiles, true) + // Channel-wide mentions are not ignored on channel level for all users + channelMemberNotifyPropsMap5Off := map[string]model.StringMap{ + user1.Id: { + "ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF, + }, + user2.Id: { + "ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF, + }, + user3.Id: { + "ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF, + }, + user4.Id: { + "ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF, + }, + } + mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap5Off) if len(mentions) != 6 { t.Fatal("should've returned six mention keywords") } else if ids, ok := mentions["user"]; !ok || len(ids) != 2 || (ids[0] != user1.Id && ids[1] != user1.Id) || (ids[0] != user4.Id && ids[1] != user4.Id) { @@ -750,7 +848,7 @@ func TestGetMentionKeywords(t *testing.T) { // multiple users and more than MaxNotificationsPerChannel th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxNotificationsPerChannel = 3 }) - mentions = th.App.GetMentionKeywordsInChannel(profiles, true) + mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap4Off) if len(mentions) != 4 { t.Fatal("should've returned four mention keywords") } else if _, ok := mentions["@channel"]; ok { @@ -765,7 +863,7 @@ func TestGetMentionKeywords(t *testing.T) { profiles = map[string]*model.User{ user1.Id: user1, } - mentions = th.App.GetMentionKeywordsInChannel(profiles, false) + mentions = th.App.GetMentionKeywordsInChannel(profiles, false, channelMemberNotifyPropsMap4Off) if len(mentions) != 3 { t.Fatal("should've returned three mention keywords") } else if ids, ok := mentions["user"]; !ok || len(ids) != 1 || ids[0] != user1.Id { diff --git a/i18n/en.json b/i18n/en.json index 797141af8a..3f8dca0f01 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -3750,6 +3750,10 @@ "id": "model.channel_member.is_valid.email_value.app_error", "translation": "Invalid email notification value" }, + { + "id": "model.channel_member.is_valid.ignore_channel_mentions_value.app_error", + "translation": "Invalid ignore channel mentions status" + }, { "id": "model.channel_member.is_valid.notify_level.app_error", "translation": "Invalid notify level" diff --git a/model/channel_member.go b/model/channel_member.go index 941db62f79..753e0eb552 100644 --- a/model/channel_member.go +++ b/model/channel_member.go @@ -11,12 +11,16 @@ import ( ) const ( - CHANNEL_NOTIFY_DEFAULT = "default" - CHANNEL_NOTIFY_ALL = "all" - CHANNEL_NOTIFY_MENTION = "mention" - CHANNEL_NOTIFY_NONE = "none" - CHANNEL_MARK_UNREAD_ALL = "all" - CHANNEL_MARK_UNREAD_MENTION = "mention" + CHANNEL_NOTIFY_DEFAULT = "default" + CHANNEL_NOTIFY_ALL = "all" + CHANNEL_NOTIFY_MENTION = "mention" + CHANNEL_NOTIFY_NONE = "none" + CHANNEL_MARK_UNREAD_ALL = "all" + CHANNEL_MARK_UNREAD_MENTION = "mention" + IGNORE_CHANNEL_MENTIONS_DEFAULT = "default" + IGNORE_CHANNEL_MENTIONS_OFF = "off" + IGNORE_CHANNEL_MENTIONS_ON = "on" + IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP = "ignore_channel_mentions" ) type ChannelUnread struct { @@ -116,6 +120,12 @@ func (o *ChannelMember) IsValid() *AppError { } } + if ignoreChannelMentions, ok := o.NotifyProps[IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP]; ok { + if len(ignoreChannelMentions) > 40 || !IsIgnoreChannelMentionsValid(ignoreChannelMentions) { + return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.ignore_channel_mentions_value.app_error", nil, "ignore_channel_mentions="+ignoreChannelMentions, http.StatusBadRequest) + } + } + return nil } @@ -146,11 +156,16 @@ func IsSendEmailValid(sendEmail string) bool { return sendEmail == CHANNEL_NOTIFY_DEFAULT || sendEmail == "true" || sendEmail == "false" } +func IsIgnoreChannelMentionsValid(ignoreChannelMentions string) bool { + return ignoreChannelMentions == IGNORE_CHANNEL_MENTIONS_ON || ignoreChannelMentions == IGNORE_CHANNEL_MENTIONS_OFF || ignoreChannelMentions == IGNORE_CHANNEL_MENTIONS_DEFAULT +} + func GetDefaultChannelNotifyProps() StringMap { return StringMap{ - DESKTOP_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT, - MARK_UNREAD_NOTIFY_PROP: CHANNEL_MARK_UNREAD_ALL, - PUSH_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT, - EMAIL_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT, + DESKTOP_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT, + MARK_UNREAD_NOTIFY_PROP: CHANNEL_MARK_UNREAD_ALL, + PUSH_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT, + EMAIL_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT, + IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP: IGNORE_CHANNEL_MENTIONS_DEFAULT, } }