mirror of
https://github.com/mattermost/mattermost.git
synced 2026-09-01 15:00:08 +08:00
Add user setting to disable auto-follow on channel-wide mentions (#36068)
* Add server config to disable auto-follow on channel-wide mentions Adds `ServiceSettings.ChannelMentionAutoFollowThreads` (default: true) which, when disabled, prevents @channel/@here/@all mentions in thread replies from automatically adding users as thread followers. Users still receive mention notifications; only the thread membership is skipped. * Refactor: move channel-mention auto-follow to per-user notification setting Replaces the server-level ServiceSettings.ChannelMentionAutoFollowThreads config with a per-user notification preference channel_mention_auto_follow_threads (default: true). Users can now opt out individually via Notification Settings -> "Auto-follow threads on channel-wide mentions" (placed above "Keywords that trigger notifications"), without requiring admin intervention. Behavior is unchanged for users who have not modified the setting. * Add additional test case * linter fixes and webapp snapshot update * update user setting description * em dash removed in description * Update E2E tests * prettier:fix --------- Co-authored-by: gtsaturyan <gtsaturyan@ozon.ru> Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>
This commit is contained in:
+1
@@ -34,6 +34,7 @@ describe('Verify Accessibility Support in different sections in Settings and Pro
|
||||
{key: 'desktopAndMobile', label: 'Desktop and mobile notifications', type: 'radio'},
|
||||
{key: 'desktopNotificationSound', label: 'Desktop notification sounds', type: 'radio'},
|
||||
{key: 'email', label: 'Email notifications', type: 'radio'},
|
||||
{key: 'channelMentionAutoFollow', label: 'Auto-follow threads on channel-wide mentions', type: 'radio'},
|
||||
{key: 'keywordsAndMentions', label: 'Keywords that trigger notifications', type: 'checkbox'},
|
||||
{key: 'keywordsAndHighlight', label: 'Keywords that get highlighted (without notifications)', type: 'checkbox'},
|
||||
{key: 'replyNotifications', label: 'Reply notifications', type: 'radio'},
|
||||
|
||||
+2
-2
@@ -46,7 +46,7 @@ describe('User Management', () => {
|
||||
|
||||
verifyManageUserSettingModal(testUser, true);
|
||||
|
||||
cy.get('#replyNotificationsTitle').should('be.visible').should('have.text', 'Reply notifications').click();
|
||||
cy.findByRole('heading', {name: 'Reply notifications'}).scrollIntoView().click();
|
||||
cy.get('#notificationCommentsNever').should('be.checked');
|
||||
cy.get('#notificationCommentsAny').check();
|
||||
cy.get('button#saveSetting').last().scrollIntoView().click();
|
||||
@@ -56,7 +56,7 @@ describe('User Management', () => {
|
||||
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
cy.get('[aria-label="Settings"]').click();
|
||||
cy.get('#replyNotificationsTitle').should('be.visible').should('have.text', 'Reply notifications').click();
|
||||
cy.findByRole('heading', {name: 'Reply notifications'}).scrollIntoView().click();
|
||||
cy.get('#notificationCommentsAny').should('be.checked');
|
||||
cy.apiLogout();
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ export default class NotificationsSettings {
|
||||
readonly desktopAndMobileEditButton;
|
||||
readonly desktopNotificationSoundEditButton;
|
||||
readonly emailEditButton;
|
||||
readonly channelMentionAutoFollowEditButton;
|
||||
readonly keywordsTriggerNotificationsEditButton;
|
||||
readonly keywordsGetHighlightedEditButton;
|
||||
|
||||
@@ -35,6 +36,9 @@ export default class NotificationsSettings {
|
||||
this.desktopAndMobileEditButton = container.locator('#desktopAndMobileEdit');
|
||||
this.desktopNotificationSoundEditButton = container.locator('#desktopNotificationSoundEdit');
|
||||
this.emailEditButton = container.locator('#emailEdit');
|
||||
this.channelMentionAutoFollowEditButton = container.getByRole('button', {
|
||||
name: 'Auto-follow threads on channel-wide mentions Edit',
|
||||
});
|
||||
this.keywordsTriggerNotificationsEditButton = container.locator('#keywordsAndMentionsEdit');
|
||||
this.keywordsGetHighlightedEditButton = container.locator('#keywordsAndHighlightEdit');
|
||||
|
||||
|
||||
+7
@@ -52,6 +52,10 @@ test(
|
||||
await page.keyboard.press('Tab');
|
||||
await pw.toBeFocusedWithFocusVisible(notificationsSettings.emailEditButton);
|
||||
|
||||
// # Press Tab to move focus to Auto-follow threads on channel-wide mentions button
|
||||
await page.keyboard.press('Tab');
|
||||
await pw.toBeFocusedWithFocusVisible(notificationsSettings.channelMentionAutoFollowEditButton);
|
||||
|
||||
// # Press Tab to move focus to Keywords that trigger notifications button
|
||||
await page.keyboard.press('Tab');
|
||||
await pw.toBeFocusedWithFocusVisible(notificationsSettings.keywordsTriggerNotificationsEditButton);
|
||||
@@ -110,6 +114,9 @@ test(
|
||||
- text: "\\"Bing\\" for messages"
|
||||
- heading "Email notifications" [level=4]
|
||||
- button "Email notifications Edit"
|
||||
- heading "Auto-follow threads on channel-wide mentions" [level=4]
|
||||
- button "Auto-follow threads on channel-wide mentions Edit"
|
||||
- text: "On"
|
||||
- heading "Keywords that trigger notifications" [level=4]
|
||||
- button "Keywords that trigger notifications Edit"
|
||||
- text: "\\"@${user.username}\\", \\"@channel\\", \\"@all\\", \\"@here\\""
|
||||
|
||||
@@ -252,12 +252,22 @@ func (a *App) SendNotifications(rctx request.CTX, post *model.Post, team *model.
|
||||
}
|
||||
if channel.Type != model.ChannelTypeDirect {
|
||||
rootMentions = getExplicitMentions(rootPost, keywords)
|
||||
for id := range rootMentions.Mentions {
|
||||
for id, mentionType := range rootMentions.Mentions {
|
||||
if mentionType == ChannelMention {
|
||||
if profile, ok := profileMap[id]; ok && profile.NotifyProps[model.ChannelMentionAutoFollowThreadsProp] == "false" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
threadParticipants[id] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for id := range mentions.Mentions {
|
||||
for id, mentionType := range mentions.Mentions {
|
||||
if mentionType == ChannelMention {
|
||||
if profile, ok := profileMap[id]; ok && profile.NotifyProps[model.ChannelMentionAutoFollowThreadsProp] == "false" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
threadParticipants[id] = true
|
||||
}
|
||||
|
||||
|
||||
@@ -3022,6 +3022,110 @@ func TestChannelAutoFollowThreads(t *testing.T) {
|
||||
assert.False(t, threadMembership.Following)
|
||||
}
|
||||
|
||||
func TestChannelMentionAutoFollowThreads(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
u1 := th.BasicUser
|
||||
u2 := th.BasicUser2
|
||||
u3 := th.CreateUser(t)
|
||||
th.LinkUserToTeam(t, u3, th.BasicTeam)
|
||||
c1 := th.BasicChannel
|
||||
th.AddUserToChannel(t, u2, c1)
|
||||
th.AddUserToChannel(t, u3, c1)
|
||||
|
||||
rootPost := &model.Post{
|
||||
ChannelId: c1.Id,
|
||||
Message: "root post by user3",
|
||||
UserId: u3.Id,
|
||||
}
|
||||
rpost, _, appErr := th.App.CreatePost(th.Context, rootPost, c1, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
t.Run("channel mention auto-follow enabled (default)", func(t *testing.T) {
|
||||
// u2 has default notify props (channel_mention_auto_follow_threads = "true")
|
||||
require.Equal(t, "true", u2.NotifyProps[model.ChannelMentionAutoFollowThreadsProp])
|
||||
|
||||
replyPost := &model.Post{
|
||||
ChannelId: c1.Id,
|
||||
Message: "@channel reply by user1",
|
||||
UserId: u1.Id,
|
||||
RootId: rpost.Id,
|
||||
}
|
||||
_, _, appErr = th.App.CreatePost(th.Context, replyPost, c1, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// u2 should be auto-following because channel_mention_auto_follow_threads is enabled
|
||||
threadMembership, getThreadErr := th.App.GetThreadMembershipForUser(u2.Id, rpost.Id)
|
||||
require.Nil(t, getThreadErr)
|
||||
require.NotNil(t, threadMembership)
|
||||
assert.True(t, threadMembership.Following)
|
||||
})
|
||||
|
||||
t.Run("channel mention auto-follow disabled for user", func(t *testing.T) {
|
||||
// Disable auto-follow for u2
|
||||
u2.NotifyProps[model.ChannelMentionAutoFollowThreadsProp] = "false"
|
||||
u2, appErr = th.App.UpdateUser(th.Context, u2, false)
|
||||
require.Nil(t, appErr)
|
||||
require.Equal(t, "false", u2.NotifyProps[model.ChannelMentionAutoFollowThreadsProp])
|
||||
|
||||
// Reset u2 membership so the prior sub-test doesn't interfere
|
||||
_, err := th.App.Srv().Store().Thread().MaintainMembership(u2.Id, rpost.Id, store.ThreadMembershipOpts{
|
||||
Following: false,
|
||||
UpdateFollowing: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
replyPost := &model.Post{
|
||||
ChannelId: c1.Id,
|
||||
Message: "@channel reply by user1 again",
|
||||
UserId: u1.Id,
|
||||
RootId: rpost.Id,
|
||||
}
|
||||
_, _, appErr = th.App.CreatePost(th.Context, replyPost, c1, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// u2 should NOT be auto-following because they opted out
|
||||
threadMembership, getThreadErr := th.App.GetThreadMembershipForUser(u2.Id, rpost.Id)
|
||||
require.Nil(t, getThreadErr)
|
||||
if threadMembership != nil {
|
||||
assert.False(t, threadMembership.Following)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("channel mention auto-follow undefined (old default)", func(t *testing.T) {
|
||||
// Remove the auto-follow setting for u2 to mimic a user created before this setting was added
|
||||
delete(u2.NotifyProps, model.ChannelMentionAutoFollowThreadsProp)
|
||||
u2, appErr = th.App.UpdateUser(th.Context, u2, false)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
_, ok := u2.NotifyProps[model.ChannelMentionAutoFollowThreadsProp]
|
||||
require.False(t, ok)
|
||||
|
||||
// Reset u2 membership so the prior sub-test doesn't interfere
|
||||
_, err := th.App.Srv().Store().Thread().MaintainMembership(u2.Id, rpost.Id, store.ThreadMembershipOpts{
|
||||
Following: false,
|
||||
UpdateFollowing: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
replyPost := &model.Post{
|
||||
ChannelId: c1.Id,
|
||||
Message: "@channel reply by user1",
|
||||
UserId: u1.Id,
|
||||
RootId: rpost.Id,
|
||||
}
|
||||
_, _, appErr = th.App.CreatePost(th.Context, replyPost, c1, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// u2 should be auto-following because channel_mention_auto_follow_threads isn't defined
|
||||
threadMembership, getThreadErr := th.App.GetThreadMembershipForUser(u2.Id, rpost.Id)
|
||||
require.Nil(t, getThreadErr)
|
||||
require.NotNil(t, threadMembership)
|
||||
assert.True(t, threadMembership.Following)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoveNotifications(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
+27
-25
@@ -23,31 +23,32 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
Me = "me"
|
||||
UserNotifyAll = "all"
|
||||
UserNotifyHere = "here"
|
||||
UserNotifyMention = "mention"
|
||||
UserNotifyNone = "none"
|
||||
DesktopNotifyProp = "desktop"
|
||||
DesktopSoundNotifyProp = "desktop_sound"
|
||||
MarkUnreadNotifyProp = "mark_unread"
|
||||
PushNotifyProp = "push"
|
||||
PushStatusNotifyProp = "push_status"
|
||||
EmailNotifyProp = "email"
|
||||
ChannelMentionsNotifyProp = "channel"
|
||||
CommentsNotifyProp = "comments"
|
||||
MentionKeysNotifyProp = "mention_keys"
|
||||
HighlightsNotifyProp = "highlight_keys"
|
||||
CommentsNotifyNever = "never"
|
||||
CommentsNotifyRoot = "root"
|
||||
CommentsNotifyAny = "any"
|
||||
CommentsNotifyCRT = "crt"
|
||||
FirstNameNotifyProp = "first_name"
|
||||
AutoResponderActiveNotifyProp = "auto_responder_active"
|
||||
AutoResponderMessageNotifyProp = "auto_responder_message"
|
||||
DesktopThreadsNotifyProp = "desktop_threads"
|
||||
PushThreadsNotifyProp = "push_threads"
|
||||
EmailThreadsNotifyProp = "email_threads"
|
||||
Me = "me"
|
||||
UserNotifyAll = "all"
|
||||
UserNotifyHere = "here"
|
||||
UserNotifyMention = "mention"
|
||||
UserNotifyNone = "none"
|
||||
DesktopNotifyProp = "desktop"
|
||||
DesktopSoundNotifyProp = "desktop_sound"
|
||||
MarkUnreadNotifyProp = "mark_unread"
|
||||
PushNotifyProp = "push"
|
||||
PushStatusNotifyProp = "push_status"
|
||||
EmailNotifyProp = "email"
|
||||
ChannelMentionsNotifyProp = "channel"
|
||||
CommentsNotifyProp = "comments"
|
||||
MentionKeysNotifyProp = "mention_keys"
|
||||
HighlightsNotifyProp = "highlight_keys"
|
||||
CommentsNotifyNever = "never"
|
||||
CommentsNotifyRoot = "root"
|
||||
CommentsNotifyAny = "any"
|
||||
CommentsNotifyCRT = "crt"
|
||||
FirstNameNotifyProp = "first_name"
|
||||
AutoResponderActiveNotifyProp = "auto_responder_active"
|
||||
AutoResponderMessageNotifyProp = "auto_responder_message"
|
||||
DesktopThreadsNotifyProp = "desktop_threads"
|
||||
PushThreadsNotifyProp = "push_threads"
|
||||
EmailThreadsNotifyProp = "email_threads"
|
||||
ChannelMentionAutoFollowThreadsProp = "channel_mention_auto_follow_threads"
|
||||
|
||||
DefaultLocale = "en"
|
||||
UserAuthServiceEmail = "email"
|
||||
@@ -607,6 +608,7 @@ func (u *User) SetDefaultNotifications() {
|
||||
u.NotifyProps[DesktopThreadsNotifyProp] = UserNotifyAll
|
||||
u.NotifyProps[EmailThreadsNotifyProp] = UserNotifyAll
|
||||
u.NotifyProps[PushThreadsNotifyProp] = UserNotifyAll
|
||||
u.NotifyProps[ChannelMentionAutoFollowThreadsProp] = "true"
|
||||
}
|
||||
|
||||
func (u *User) UpdateMentionKeysFromUsername(oldUsername string) {
|
||||
|
||||
+216
@@ -192,6 +192,42 @@ Object {
|
||||
<div
|
||||
class="divider-light"
|
||||
/>
|
||||
<div
|
||||
class="section-min"
|
||||
>
|
||||
<div
|
||||
class="section-min__header"
|
||||
>
|
||||
<h4
|
||||
class="section-min__title"
|
||||
id="channelMentionAutoFollowTitle"
|
||||
>
|
||||
Auto-follow threads on channel-wide mentions
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded="false"
|
||||
aria-labelledby="channelMentionAutoFollowTitle channelMentionAutoFollowEdit"
|
||||
class="color--link style--none section-min__edit"
|
||||
id="channelMentionAutoFollowEdit"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="icon-pencil-outline"
|
||||
title="Edit Icon"
|
||||
/>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="section-min__describe"
|
||||
id="channelMentionAutoFollowDesc"
|
||||
>
|
||||
On
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="divider-light"
|
||||
/>
|
||||
<div
|
||||
class="section-min"
|
||||
>
|
||||
@@ -503,6 +539,42 @@ Object {
|
||||
<div
|
||||
class="divider-light"
|
||||
/>
|
||||
<div
|
||||
class="section-min"
|
||||
>
|
||||
<div
|
||||
class="section-min__header"
|
||||
>
|
||||
<h4
|
||||
class="section-min__title"
|
||||
id="channelMentionAutoFollowTitle"
|
||||
>
|
||||
Auto-follow threads on channel-wide mentions
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded="false"
|
||||
aria-labelledby="channelMentionAutoFollowTitle channelMentionAutoFollowEdit"
|
||||
class="color--link style--none section-min__edit"
|
||||
id="channelMentionAutoFollowEdit"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="icon-pencil-outline"
|
||||
title="Edit Icon"
|
||||
/>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="section-min__describe"
|
||||
id="channelMentionAutoFollowDesc"
|
||||
>
|
||||
On
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="divider-light"
|
||||
/>
|
||||
<div
|
||||
class="section-min"
|
||||
>
|
||||
@@ -880,6 +952,42 @@ Object {
|
||||
<div
|
||||
class="divider-light"
|
||||
/>
|
||||
<div
|
||||
class="section-min"
|
||||
>
|
||||
<div
|
||||
class="section-min__header"
|
||||
>
|
||||
<h4
|
||||
class="section-min__title"
|
||||
id="channelMentionAutoFollowTitle"
|
||||
>
|
||||
Auto-follow threads on channel-wide mentions
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded="false"
|
||||
aria-labelledby="channelMentionAutoFollowTitle channelMentionAutoFollowEdit"
|
||||
class="color--link style--none section-min__edit"
|
||||
id="channelMentionAutoFollowEdit"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="icon-pencil-outline"
|
||||
title="Edit Icon"
|
||||
/>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="section-min__describe"
|
||||
id="channelMentionAutoFollowDesc"
|
||||
>
|
||||
On
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="divider-light"
|
||||
/>
|
||||
<div
|
||||
class="section-min"
|
||||
>
|
||||
@@ -1193,6 +1301,42 @@ Object {
|
||||
<div
|
||||
class="divider-light"
|
||||
/>
|
||||
<div
|
||||
class="section-min"
|
||||
>
|
||||
<div
|
||||
class="section-min__header"
|
||||
>
|
||||
<h4
|
||||
class="section-min__title"
|
||||
id="channelMentionAutoFollowTitle"
|
||||
>
|
||||
Auto-follow threads on channel-wide mentions
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded="false"
|
||||
aria-labelledby="channelMentionAutoFollowTitle channelMentionAutoFollowEdit"
|
||||
class="color--link style--none section-min__edit"
|
||||
id="channelMentionAutoFollowEdit"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="icon-pencil-outline"
|
||||
title="Edit Icon"
|
||||
/>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="section-min__describe"
|
||||
id="channelMentionAutoFollowDesc"
|
||||
>
|
||||
On
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="divider-light"
|
||||
/>
|
||||
<div
|
||||
class="section-min"
|
||||
>
|
||||
@@ -1572,6 +1716,42 @@ Object {
|
||||
<div
|
||||
class="divider-light"
|
||||
/>
|
||||
<div
|
||||
class="section-min"
|
||||
>
|
||||
<div
|
||||
class="section-min__header"
|
||||
>
|
||||
<h4
|
||||
class="section-min__title"
|
||||
id="channelMentionAutoFollowTitle"
|
||||
>
|
||||
Auto-follow threads on channel-wide mentions
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded="false"
|
||||
aria-labelledby="channelMentionAutoFollowTitle channelMentionAutoFollowEdit"
|
||||
class="color--link style--none section-min__edit"
|
||||
id="channelMentionAutoFollowEdit"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="icon-pencil-outline"
|
||||
title="Edit Icon"
|
||||
/>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="section-min__describe"
|
||||
id="channelMentionAutoFollowDesc"
|
||||
>
|
||||
On
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="divider-light"
|
||||
/>
|
||||
<div
|
||||
class="section-min"
|
||||
>
|
||||
@@ -1847,6 +2027,42 @@ Object {
|
||||
<div
|
||||
class="divider-light"
|
||||
/>
|
||||
<div
|
||||
class="section-min"
|
||||
>
|
||||
<div
|
||||
class="section-min__header"
|
||||
>
|
||||
<h4
|
||||
class="section-min__title"
|
||||
id="channelMentionAutoFollowTitle"
|
||||
>
|
||||
Auto-follow threads on channel-wide mentions
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded="false"
|
||||
aria-labelledby="channelMentionAutoFollowTitle channelMentionAutoFollowEdit"
|
||||
class="color--link style--none section-min__edit"
|
||||
id="channelMentionAutoFollowEdit"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="icon-pencil-outline"
|
||||
title="Edit Icon"
|
||||
/>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="section-min__describe"
|
||||
id="channelMentionAutoFollowDesc"
|
||||
>
|
||||
On
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="divider-light"
|
||||
/>
|
||||
<div
|
||||
class="section-min"
|
||||
>
|
||||
|
||||
+103
@@ -64,6 +64,7 @@ type State = {
|
||||
customKeysWithHighlightInputValue: string;
|
||||
firstNameKey: boolean;
|
||||
channelKey: boolean;
|
||||
channelMentionAutoFollow: boolean;
|
||||
autoResponderActive: boolean;
|
||||
autoResponderMessage: UserNotifyProps['auto_responder_message'];
|
||||
notifyCommentsLevel: UserNotifyProps['comments'];
|
||||
@@ -146,6 +147,7 @@ function getDefaultStateFromProps(props: Props): State {
|
||||
let usernameKey = false;
|
||||
let firstNameKey = false;
|
||||
let channelKey = false;
|
||||
let channelMentionAutoFollow = true;
|
||||
let isCustomKeysWithNotificationInputChecked = false;
|
||||
const customKeysWithNotification: MultiInputValue[] = [];
|
||||
const customKeysWithHighlight: MultiInputValue[] = [];
|
||||
@@ -182,6 +184,7 @@ function getDefaultStateFromProps(props: Props): State {
|
||||
|
||||
firstNameKey = props.user.notify_props?.first_name === 'true';
|
||||
channelKey = props.user.notify_props?.channel === 'true';
|
||||
channelMentionAutoFollow = props.user.notify_props?.channel_mention_auto_follow_threads !== 'false';
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -204,6 +207,7 @@ function getDefaultStateFromProps(props: Props): State {
|
||||
customKeysWithHighlightInputValue: '',
|
||||
firstNameKey,
|
||||
channelKey,
|
||||
channelMentionAutoFollow,
|
||||
autoResponderActive,
|
||||
autoResponderMessage,
|
||||
notifyCommentsLevel: comments,
|
||||
@@ -252,6 +256,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
data.auto_responder_message = this.state.autoResponderMessage;
|
||||
data.first_name = this.state.firstNameKey ? 'true' : 'false';
|
||||
data.channel = this.state.channelKey ? 'true' : 'false';
|
||||
data.channel_mention_auto_follow_threads = this.state.channelMentionAutoFollow ? 'true' : 'false';
|
||||
|
||||
if (this.state.desktopAndMobileSettingsDifferent) {
|
||||
data.push = this.state.pushActivity;
|
||||
@@ -359,6 +364,10 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
this.setState({channelKey: checked});
|
||||
};
|
||||
|
||||
handleChangeForChannelMentionAutoFollowRadio = (value: boolean) => {
|
||||
this.setState({channelMentionAutoFollow: value});
|
||||
};
|
||||
|
||||
handleChangeForCustomKeysWithNotificationCheckbox = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const {target: {checked}} = event;
|
||||
this.setState({isCustomKeysWithNotificationInputChecked: checked});
|
||||
@@ -487,6 +496,97 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
this.props.closeModal();
|
||||
};
|
||||
|
||||
createChannelMentionAutoFollowSection = () => {
|
||||
const serverError = this.state.serverError;
|
||||
const isSectionExpanded = this.props.activeSection === UserSettingsNotificationSections.CHANNEL_MENTION_AUTO_FOLLOW;
|
||||
|
||||
let max = null;
|
||||
if (isSectionExpanded) {
|
||||
max = (
|
||||
<SettingItemMax
|
||||
title={this.props.intl.formatMessage({id: 'user.settings.notifications.channelMentionAutoFollow.title', defaultMessage: 'Auto-follow threads on channel-wide mentions'})}
|
||||
inputs={
|
||||
<fieldset>
|
||||
<legend className='form-legend hidden-label'>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.channelMentionAutoFollow.title'
|
||||
defaultMessage='Auto-follow threads on channel-wide mentions'
|
||||
/>
|
||||
</legend>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
id='channelMentionAutoFollowOn'
|
||||
type='radio'
|
||||
name='channelMentionAutoFollow'
|
||||
checked={this.state.channelMentionAutoFollow}
|
||||
onChange={() => this.handleChangeForChannelMentionAutoFollowRadio(true)}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.channelMentionAutoFollow.on'
|
||||
defaultMessage='On'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
id='channelMentionAutoFollowOff'
|
||||
type='radio'
|
||||
name='channelMentionAutoFollow'
|
||||
checked={!this.state.channelMentionAutoFollow}
|
||||
onChange={() => this.handleChangeForChannelMentionAutoFollowRadio(false)}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.channelMentionAutoFollow.off'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
}
|
||||
extraInfo={
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.channelMentionAutoFollow.description'
|
||||
defaultMessage='When enabled, @channel, @all, and @here mentions auto-follow the thread. Disabling stops the auto-follow. Notifications may still trigger based on your keyword settings.'
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
submit={this.handleSubmit}
|
||||
saving={this.state.isSaving}
|
||||
serverError={serverError}
|
||||
updateSection={this.handleUpdateSection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const describe = this.state.channelMentionAutoFollow ? (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.channelMentionAutoFollow.on'
|
||||
defaultMessage='On'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.channelMentionAutoFollow.off'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<SettingItem
|
||||
title={this.props.intl.formatMessage({id: 'user.settings.notifications.channelMentionAutoFollow.title', defaultMessage: 'Auto-follow threads on channel-wide mentions'})}
|
||||
active={isSectionExpanded}
|
||||
describe={describe}
|
||||
section={UserSettingsNotificationSections.CHANNEL_MENTION_AUTO_FOLLOW}
|
||||
updateSection={this.handleUpdateSection}
|
||||
max={max}
|
||||
areAllSectionsInactive={this.props.activeSection === ''}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
createKeywordsWithNotificationSection = () => {
|
||||
const serverError = this.state.serverError;
|
||||
const user = this.props.user;
|
||||
@@ -976,6 +1076,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const channelMentionAutoFollowSection = this.createChannelMentionAutoFollowSection();
|
||||
const keywordsWithNotificationSection = this.createKeywordsWithNotificationSection();
|
||||
const keywordsWithHighlightSection = this.createKeywordsWithHighlightSection();
|
||||
const commentsSection = this.createCommentsSection();
|
||||
@@ -1080,6 +1181,8 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
threads={this.state.emailThreads || ''}
|
||||
/>
|
||||
<div className='divider-light'/>
|
||||
{channelMentionAutoFollowSection}
|
||||
<div className='divider-light'/>
|
||||
{keywordsWithNotificationSection}
|
||||
{(!this.props.isEnterpriseOrCloudOrSKUStarterFree && this.props.isEnterpriseReady) && (
|
||||
<>
|
||||
|
||||
@@ -7262,6 +7262,10 @@
|
||||
"user.settings.notifications.autoResponderEnabled": "Enabled",
|
||||
"user.settings.notifications.autoResponderHint": "Set a custom message that will be automatically sent in response to Direct Messages. Mentions in Public and Private Channels will not trigger the automated reply. Enabling Automatic Replies sets your status to Out of Office and disables email and push notifications.",
|
||||
"user.settings.notifications.autoResponderPlaceholder": "Message",
|
||||
"user.settings.notifications.channelMentionAutoFollow.description": "When enabled, @channel, @all, and @here mentions auto-follow the thread. Disabling stops the auto-follow. Notifications may still trigger based on your keyword settings.",
|
||||
"user.settings.notifications.channelMentionAutoFollow.off": "Off",
|
||||
"user.settings.notifications.channelMentionAutoFollow.on": "On",
|
||||
"user.settings.notifications.channelMentionAutoFollow.title": "Auto-follow threads on channel-wide mentions",
|
||||
"user.settings.notifications.channelWide": "Channel-wide mentions \"@channel\", \"@all\", \"@here\"",
|
||||
"user.settings.notifications.comments": "Reply notifications",
|
||||
"user.settings.notifications.commentsAny": "Trigger notifications on messages in reply threads that I start or participate in",
|
||||
|
||||
@@ -967,6 +967,7 @@ export const UserSettingsNotificationSections = {
|
||||
DESKTOP_AND_MOBILE: 'desktopAndMobile',
|
||||
DESKTOP_NOTIFICATION_SOUND: 'desktopNotificationSound',
|
||||
EMAIL: 'email',
|
||||
CHANNEL_MENTION_AUTO_FOLLOW: 'channelMentionAutoFollow',
|
||||
KEYWORDS_MENTIONS: 'keywordsAndMentions',
|
||||
KEYWORDS_HIGHLIGHT: 'keywordsAndHighlight',
|
||||
REPLY_NOTIFCATIONS: 'replyNotifications',
|
||||
|
||||
@@ -30,6 +30,7 @@ export type UserNotifyProps = {
|
||||
auto_responder_message?: string;
|
||||
calls_mobile_sound?: 'true' | 'false' | '';
|
||||
calls_mobile_notification_sound?: 'Dynamic' | 'Calm' | 'Urgent' | 'Cheerful' | '';
|
||||
channel_mention_auto_follow_threads?: 'true' | 'false';
|
||||
};
|
||||
|
||||
export type UserProfile = {
|
||||
|
||||
Reference in New Issue
Block a user