MM-20897 Add category muting (#16225)
* Make UpdateSidebarCategories return the original categories * MM-20897 Add category muting * Prevent muting the DMs category * Fix muted state not being stored in the database * Address feedback * Address some feedback * Fix unit tests * MM-20897 Mute/unmute channels in the database in bulk * Satisfy golangci-lint
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
7f6b43a15f
Коммит
2ebc8ec90f
@@ -953,7 +953,7 @@ type AppIface interface {
|
||||
TestLdap() *model.AppError
|
||||
TestSiteURL(siteURL string) *model.AppError
|
||||
Timezones() *timezones.Timezones
|
||||
ToggleMuteChannel(channelId string, userId string) *model.ChannelMember
|
||||
ToggleMuteChannel(channelId, userId string) (*model.ChannelMember, *model.AppError)
|
||||
TotalWebsocketConnections() int
|
||||
TriggerWebhook(payload *model.OutgoingWebhookPayload, hook *model.OutgoingWebhook, post *model.Post, channel *model.Channel)
|
||||
UnregisterPluginCommand(pluginId, teamId, trigger string)
|
||||
|
||||
143
app/channel.go
143
app/channel.go
@@ -1097,22 +1097,7 @@ func (a *App) UpdateChannelMemberRoles(channelId string, userId string, newRoles
|
||||
|
||||
member.ExplicitRoles = strings.Join(newExplicitRoles, " ")
|
||||
|
||||
member, nErr := a.Srv().Store.Channel().UpdateMember(member)
|
||||
if nErr != nil {
|
||||
var appErr *model.AppError
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
case errors.As(nErr, &appErr):
|
||||
return nil, appErr
|
||||
case errors.As(nErr, &nfErr):
|
||||
return nil, model.NewAppError("UpdateChannelMemberRoles", MISSING_CHANNEL_MEMBER_ERROR, nil, nfErr.Error(), http.StatusNotFound)
|
||||
default:
|
||||
return nil, model.NewAppError("UpdateChannelMemberRoles", "app.channel.get_member.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
a.InvalidateCacheForUser(userId)
|
||||
return member, nil
|
||||
return a.updateChannelMember(member)
|
||||
}
|
||||
|
||||
func (a *App) UpdateChannelMemberSchemeRoles(channelId string, userId string, isSchemeGuest bool, isSchemeUser bool, isSchemeAdmin bool) (*model.ChannelMember, *model.AppError) {
|
||||
@@ -1134,27 +1119,7 @@ func (a *App) UpdateChannelMemberSchemeRoles(channelId string, userId string, is
|
||||
member.ExplicitRoles = RemoveRoles([]string{model.CHANNEL_GUEST_ROLE_ID, model.CHANNEL_USER_ROLE_ID, model.CHANNEL_ADMIN_ROLE_ID}, member.ExplicitRoles)
|
||||
}
|
||||
|
||||
member, nErr := a.Srv().Store.Channel().UpdateMember(member)
|
||||
if nErr != nil {
|
||||
var appErr *model.AppError
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
case errors.As(nErr, &appErr):
|
||||
return nil, appErr
|
||||
case errors.As(nErr, &nfErr):
|
||||
return nil, model.NewAppError("UpdateChannelMemberSchemeRoles", MISSING_CHANNEL_MEMBER_ERROR, nil, nfErr.Error(), http.StatusNotFound)
|
||||
default:
|
||||
return nil, model.NewAppError("UpdateChannelMemberSchemeRoles", "app.channel.get_member.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the clients that the member notify props changed
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_MEMBER_UPDATED, "", "", userId, nil)
|
||||
message.Add("channelMember", member.ToJson())
|
||||
a.Publish(message)
|
||||
|
||||
a.InvalidateCacheForUser(userId)
|
||||
return member, nil
|
||||
return a.updateChannelMember(member)
|
||||
}
|
||||
|
||||
func (a *App) UpdateChannelMemberNotifyProps(data map[string]string, channelId string, userId string) (*model.ChannelMember, *model.AppError) {
|
||||
@@ -1185,6 +1150,17 @@ func (a *App) UpdateChannelMemberNotifyProps(data map[string]string, channelId s
|
||||
member.NotifyProps[model.IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP] = ignoreChannelMentions
|
||||
}
|
||||
|
||||
member, err = a.updateChannelMember(member)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a.invalidateCacheForChannelMembersNotifyProps(member.ChannelId)
|
||||
|
||||
return member, nil
|
||||
}
|
||||
|
||||
func (a *App) updateChannelMember(member *model.ChannelMember) (*model.ChannelMember, *model.AppError) {
|
||||
member, nErr := a.Srv().Store.Channel().UpdateMember(member)
|
||||
if nErr != nil {
|
||||
var appErr *model.AppError
|
||||
@@ -1193,18 +1169,19 @@ func (a *App) UpdateChannelMemberNotifyProps(data map[string]string, channelId s
|
||||
case errors.As(nErr, &appErr):
|
||||
return nil, appErr
|
||||
case errors.As(nErr, &nfErr):
|
||||
return nil, model.NewAppError("UpdateChannelMemberNotifyProps", MISSING_CHANNEL_MEMBER_ERROR, nil, nfErr.Error(), http.StatusNotFound)
|
||||
return nil, model.NewAppError("updateChannelMember", MISSING_CHANNEL_MEMBER_ERROR, nil, nfErr.Error(), http.StatusNotFound)
|
||||
default:
|
||||
return nil, model.NewAppError("UpdateChannelMemberNotifyProps", "app.channel.get_member.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
return nil, model.NewAppError("updateChannelMember", "app.channel.get_member.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
a.InvalidateCacheForUser(userId)
|
||||
a.invalidateCacheForChannelMembersNotifyProps(channelId)
|
||||
a.InvalidateCacheForUser(member.UserId)
|
||||
|
||||
// Notify the clients that the member notify props changed
|
||||
evt := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_MEMBER_UPDATED, "", "", userId, nil)
|
||||
evt := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_MEMBER_UPDATED, "", "", member.UserId, nil)
|
||||
evt.Add("channelMember", member.ToJson())
|
||||
a.Publish(evt)
|
||||
|
||||
return member, nil
|
||||
}
|
||||
|
||||
@@ -2788,20 +2765,84 @@ func (a *App) GetPinnedPosts(channelId string) (*model.PostList, *model.AppError
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (a *App) ToggleMuteChannel(channelId string, userId string) *model.ChannelMember {
|
||||
member, err := a.Srv().Store.Channel().GetMember(channelId, userId)
|
||||
func (a *App) ToggleMuteChannel(channelId, userId string) (*model.ChannelMember, *model.AppError) {
|
||||
member, nErr := a.Srv().Store.Channel().GetMember(channelId, userId)
|
||||
if nErr != nil {
|
||||
var appErr *model.AppError
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
case errors.As(nErr, &appErr):
|
||||
return nil, appErr
|
||||
case errors.As(nErr, &nfErr):
|
||||
return nil, model.NewAppError("ToggleMuteChannel", MISSING_CHANNEL_MEMBER_ERROR, nil, nfErr.Error(), http.StatusNotFound)
|
||||
default:
|
||||
return nil, model.NewAppError("ToggleMuteChannel", "app.channel.get_member.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
member.SetChannelMuted(!member.IsChannelMuted())
|
||||
|
||||
member, err := a.updateChannelMember(member)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if member.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] == model.CHANNEL_NOTIFY_MENTION {
|
||||
member.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] = model.CHANNEL_MARK_UNREAD_ALL
|
||||
} else {
|
||||
member.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] = model.CHANNEL_NOTIFY_MENTION
|
||||
a.invalidateCacheForChannelMembersNotifyProps(member.ChannelId)
|
||||
|
||||
return member, nil
|
||||
}
|
||||
|
||||
func (a *App) setChannelsMuted(channelIds []string, userId string, muted bool) ([]*model.ChannelMember, *model.AppError) {
|
||||
members, nErr := a.Srv().Store.Channel().GetMembersByChannelIds(channelIds, userId)
|
||||
if nErr != nil {
|
||||
var appErr *model.AppError
|
||||
switch {
|
||||
case errors.As(nErr, &appErr):
|
||||
return nil, appErr
|
||||
default:
|
||||
return nil, model.NewAppError("setChannelsMuted", "app.channel.get_member.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
a.Srv().Store.Channel().UpdateMember(member)
|
||||
return member
|
||||
var membersToUpdate []*model.ChannelMember
|
||||
for _, member := range *members {
|
||||
if muted == member.IsChannelMuted() {
|
||||
continue
|
||||
}
|
||||
|
||||
updatedMember := member
|
||||
updatedMember.SetChannelMuted(muted)
|
||||
|
||||
membersToUpdate = append(membersToUpdate, &updatedMember)
|
||||
}
|
||||
|
||||
if len(membersToUpdate) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
updated, nErr := a.Srv().Store.Channel().UpdateMultipleMembers(membersToUpdate)
|
||||
if nErr != nil {
|
||||
var appErr *model.AppError
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
case errors.As(nErr, &appErr):
|
||||
return nil, appErr
|
||||
case errors.As(nErr, &nfErr):
|
||||
return nil, model.NewAppError("setChannelsMuted", MISSING_CHANNEL_MEMBER_ERROR, nil, nfErr.Error(), http.StatusNotFound)
|
||||
default:
|
||||
return nil, model.NewAppError("setChannelsMuted", "app.channel.get_member.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
for _, member := range updated {
|
||||
a.invalidateCacheForChannelMembersNotifyProps(member.ChannelId)
|
||||
|
||||
evt := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_MEMBER_UPDATED, "", "", member.UserId, nil)
|
||||
evt.Add("channelMember", member.ToJson())
|
||||
a.Publish(evt)
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (a *App) FillInChannelProps(channel *model.Channel) *model.AppError {
|
||||
|
||||
@@ -144,14 +144,122 @@ func (a *App) UpdateSidebarCategoryOrder(userId, teamId string, categoryOrder []
|
||||
}
|
||||
|
||||
func (a *App) UpdateSidebarCategories(userId, teamId string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError) {
|
||||
result, err := a.Srv().Store.Channel().UpdateSidebarCategories(userId, teamId, categories)
|
||||
updatedCategories, originalCategories, err := a.Srv().Store.Channel().UpdateSidebarCategories(userId, teamId, categories)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("UpdateSidebarCategories", "app.channel.sidebar_categories.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_SIDEBAR_CATEGORY_UPDATED, teamId, "", userId, nil)
|
||||
a.Publish(message)
|
||||
return result, nil
|
||||
|
||||
a.muteChannelsForUpdatedCategories(userId, updatedCategories, originalCategories)
|
||||
|
||||
return updatedCategories, nil
|
||||
}
|
||||
|
||||
func (a *App) muteChannelsForUpdatedCategories(userId string, updatedCategories []*model.SidebarCategoryWithChannels, originalCategories []*model.SidebarCategoryWithChannels) {
|
||||
var channelsToMute []string
|
||||
var channelsToUnmute []string
|
||||
|
||||
// Mute or unmute all channels in categories that were muted or unmuted
|
||||
for i, updatedCategory := range updatedCategories {
|
||||
if i > len(originalCategories)-1 {
|
||||
// The two slices should be the same length, but double check that to be safe
|
||||
continue
|
||||
}
|
||||
|
||||
originalCategory := originalCategories[i]
|
||||
|
||||
if updatedCategory.Muted && !originalCategory.Muted {
|
||||
channelsToMute = append(channelsToMute, updatedCategory.Channels...)
|
||||
} else if !updatedCategory.Muted && originalCategory.Muted {
|
||||
channelsToUnmute = append(channelsToUnmute, updatedCategory.Channels...)
|
||||
}
|
||||
}
|
||||
|
||||
// Mute any channels moved from an unmuted category into a muted one and vice versa
|
||||
channelsDiff := diffChannelsBetweenCategories(updatedCategories, originalCategories)
|
||||
if len(channelsDiff) != 0 {
|
||||
makeCategoryMap := func(categories []*model.SidebarCategoryWithChannels) map[string]*model.SidebarCategoryWithChannels {
|
||||
result := make(map[string]*model.SidebarCategoryWithChannels)
|
||||
for _, category := range categories {
|
||||
result[category.Id] = category
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
updatedCategoriesById := makeCategoryMap(updatedCategories)
|
||||
originalCategoriesById := makeCategoryMap(originalCategories)
|
||||
|
||||
for channelId, diff := range channelsDiff {
|
||||
fromCategory := originalCategoriesById[diff.fromCategoryId]
|
||||
toCategory := updatedCategoriesById[diff.toCategoryId]
|
||||
|
||||
if toCategory.Muted && !fromCategory.Muted {
|
||||
channelsToMute = append(channelsToMute, channelId)
|
||||
} else if !toCategory.Muted && fromCategory.Muted {
|
||||
channelsToUnmute = append(channelsToUnmute, channelId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(channelsToMute) > 0 {
|
||||
_, err := a.setChannelsMuted(channelsToMute, userId, true)
|
||||
if err != nil {
|
||||
mlog.Error(
|
||||
"Failed to mute channels to match category",
|
||||
mlog.String("user_id", userId),
|
||||
mlog.Err(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if len(channelsToUnmute) > 0 {
|
||||
_, err := a.setChannelsMuted(channelsToUnmute, userId, false)
|
||||
if err != nil {
|
||||
mlog.Error(
|
||||
"Failed to unmute channels to match category",
|
||||
mlog.String("user_id", userId),
|
||||
mlog.Err(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type categoryChannelDiff struct {
|
||||
fromCategoryId string
|
||||
toCategoryId string
|
||||
}
|
||||
|
||||
func diffChannelsBetweenCategories(updatedCategories []*model.SidebarCategoryWithChannels, originalCategories []*model.SidebarCategoryWithChannels) map[string]*categoryChannelDiff {
|
||||
// mapChannelIdsToCategories returns a map of channel IDs to the IDs of the categories that they're a member of.
|
||||
mapChannelIdsToCategories := func(categories []*model.SidebarCategoryWithChannels) map[string]string {
|
||||
result := make(map[string]string)
|
||||
for _, category := range categories {
|
||||
for _, channelId := range category.Channels {
|
||||
result[channelId] = category.Id
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
updatedChannelIdsMap := mapChannelIdsToCategories(updatedCategories)
|
||||
originalChannelIdsMap := mapChannelIdsToCategories(originalCategories)
|
||||
|
||||
// Check for any channels that have changed categories. Note that we don't worry about any channels that have moved
|
||||
// outside of these categories since that heavily complicates things and doesn't currently happen in our apps.
|
||||
channelsDiff := make(map[string]*categoryChannelDiff)
|
||||
for channelId, originalCategoryId := range originalChannelIdsMap {
|
||||
updatedCategoryId := updatedChannelIdsMap[channelId]
|
||||
|
||||
if originalCategoryId != updatedCategoryId && updatedCategoryId != "" {
|
||||
channelsDiff[channelId] = &categoryChannelDiff{originalCategoryId, updatedCategoryId}
|
||||
}
|
||||
}
|
||||
|
||||
return channelsDiff
|
||||
}
|
||||
|
||||
func (a *App) DeleteSidebarCategory(userId, teamId, categoryId string) *model.AppError {
|
||||
|
||||
@@ -136,3 +136,524 @@ func TestGetSidebarCategories(t *testing.T) {
|
||||
assert.Equal(t, "app.channel.sidebar_categories.app_error", appErr.Id)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateSidebarCategories(t *testing.T) {
|
||||
t.Run("should mute and unmute all channels in a category when it is muted or unmuted", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
categories, err := th.App.GetSidebarCategories(th.BasicUser.Id, th.BasicTeam.Id)
|
||||
require.Nil(t, err)
|
||||
|
||||
channelsCategory := categories.Categories[1]
|
||||
|
||||
// Create some channels to be part of the channels category
|
||||
channel1 := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(th.BasicUser, channel1)
|
||||
|
||||
channel2 := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(th.BasicUser, channel2)
|
||||
|
||||
// Mute the category
|
||||
updated, err := th.App.UpdateSidebarCategories(th.BasicUser.Id, th.BasicTeam.Id, []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: channelsCategory.Id,
|
||||
Muted: true,
|
||||
},
|
||||
Channels: []string{channel1.Id, channel2.Id},
|
||||
},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
assert.True(t, updated[0].Muted)
|
||||
|
||||
// Confirm that the channels are now muted
|
||||
member1, err := th.App.GetChannelMember(channel1.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, member1.IsChannelMuted())
|
||||
member2, err := th.App.GetChannelMember(channel2.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, member2.IsChannelMuted())
|
||||
|
||||
// Unmute the category
|
||||
updated, err = th.App.UpdateSidebarCategories(th.BasicUser.Id, th.BasicTeam.Id, []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: channelsCategory.Id,
|
||||
Muted: false,
|
||||
},
|
||||
Channels: []string{channel1.Id, channel2.Id},
|
||||
},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
assert.False(t, updated[0].Muted)
|
||||
|
||||
// Confirm that the channels are now unmuted
|
||||
member1, err = th.App.GetChannelMember(channel1.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, member1.IsChannelMuted())
|
||||
member2, err = th.App.GetChannelMember(channel2.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, member2.IsChannelMuted())
|
||||
})
|
||||
|
||||
t.Run("should mute and unmute channels moved from an unmuted category to a muted one and back", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// Create some channels
|
||||
channel1 := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(th.BasicUser, channel1)
|
||||
|
||||
channel2 := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(th.BasicUser, channel2)
|
||||
|
||||
// And some categories
|
||||
mutedCategory, err := th.App.CreateSidebarCategory(th.BasicUser.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
DisplayName: "muted",
|
||||
Muted: true,
|
||||
},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.True(t, mutedCategory.Muted)
|
||||
|
||||
unmutedCategory, err := th.App.CreateSidebarCategory(th.BasicUser.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
DisplayName: "unmuted",
|
||||
Muted: false,
|
||||
},
|
||||
Channels: []string{channel1.Id, channel2.Id},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.False(t, unmutedCategory.Muted)
|
||||
|
||||
// Move the channels
|
||||
_, err = th.App.UpdateSidebarCategories(th.BasicUser.Id, th.BasicTeam.Id, []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: mutedCategory.Id,
|
||||
DisplayName: mutedCategory.DisplayName,
|
||||
Muted: mutedCategory.Muted,
|
||||
},
|
||||
Channels: []string{channel1.Id, channel2.Id},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: unmutedCategory.Id,
|
||||
DisplayName: unmutedCategory.DisplayName,
|
||||
Muted: unmutedCategory.Muted,
|
||||
},
|
||||
Channels: []string{},
|
||||
},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Confirm that the channels are now muted
|
||||
member1, err := th.App.GetChannelMember(channel1.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, member1.IsChannelMuted())
|
||||
member2, err := th.App.GetChannelMember(channel2.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, member2.IsChannelMuted())
|
||||
|
||||
// Move the channels back
|
||||
_, err = th.App.UpdateSidebarCategories(th.BasicUser.Id, th.BasicTeam.Id, []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: mutedCategory.Id,
|
||||
DisplayName: mutedCategory.DisplayName,
|
||||
Muted: mutedCategory.Muted,
|
||||
},
|
||||
Channels: []string{},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: unmutedCategory.Id,
|
||||
DisplayName: unmutedCategory.DisplayName,
|
||||
Muted: unmutedCategory.Muted,
|
||||
},
|
||||
Channels: []string{channel1.Id, channel2.Id},
|
||||
},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Confirm that the channels are now unmuted
|
||||
member1, err = th.App.GetChannelMember(channel1.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, member1.IsChannelMuted())
|
||||
member2, err = th.App.GetChannelMember(channel2.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, member2.IsChannelMuted())
|
||||
})
|
||||
|
||||
t.Run("should not mute or unmute channels moved between muted categories", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// Create some channels
|
||||
channel1 := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(th.BasicUser, channel1)
|
||||
|
||||
channel2 := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(th.BasicUser, channel2)
|
||||
|
||||
// And some categories
|
||||
category1, err := th.App.CreateSidebarCategory(th.BasicUser.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
DisplayName: "category1",
|
||||
Muted: true,
|
||||
},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.True(t, category1.Muted)
|
||||
|
||||
category2, err := th.App.CreateSidebarCategory(th.BasicUser.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
DisplayName: "category2",
|
||||
Muted: true,
|
||||
},
|
||||
Channels: []string{channel1.Id, channel2.Id},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.True(t, category2.Muted)
|
||||
|
||||
// Move the unmuted channels
|
||||
_, err = th.App.UpdateSidebarCategories(th.BasicUser.Id, th.BasicTeam.Id, []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: category1.Id,
|
||||
DisplayName: category1.DisplayName,
|
||||
Muted: category1.Muted,
|
||||
},
|
||||
Channels: []string{channel1.Id, channel2.Id},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: category2.Id,
|
||||
DisplayName: category2.DisplayName,
|
||||
Muted: category2.Muted,
|
||||
},
|
||||
Channels: []string{},
|
||||
},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Confirm that the channels are still unmuted
|
||||
member1, err := th.App.GetChannelMember(channel1.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, member1.IsChannelMuted())
|
||||
member2, err := th.App.GetChannelMember(channel2.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, member2.IsChannelMuted())
|
||||
|
||||
// Mute the channels manually
|
||||
_, err = th.App.ToggleMuteChannel(channel1.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.ToggleMuteChannel(channel2.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Move the muted channels back
|
||||
_, err = th.App.UpdateSidebarCategories(th.BasicUser.Id, th.BasicTeam.Id, []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: category1.Id,
|
||||
DisplayName: category1.DisplayName,
|
||||
Muted: category1.Muted,
|
||||
},
|
||||
Channels: []string{},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: category2.Id,
|
||||
DisplayName: category2.DisplayName,
|
||||
Muted: category2.Muted,
|
||||
},
|
||||
Channels: []string{channel1.Id, channel2.Id},
|
||||
},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Confirm that the channels are still muted
|
||||
member1, err = th.App.GetChannelMember(channel1.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, member1.IsChannelMuted())
|
||||
member2, err = th.App.GetChannelMember(channel2.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, member2.IsChannelMuted())
|
||||
})
|
||||
|
||||
t.Run("should not mute or unmute channels moved between unmuted categories", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// Create some channels
|
||||
channel1 := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(th.BasicUser, channel1)
|
||||
|
||||
channel2 := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(th.BasicUser, channel2)
|
||||
|
||||
// And some categories
|
||||
category1, err := th.App.CreateSidebarCategory(th.BasicUser.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
DisplayName: "category1",
|
||||
Muted: false,
|
||||
},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.False(t, category1.Muted)
|
||||
|
||||
category2, err := th.App.CreateSidebarCategory(th.BasicUser.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
DisplayName: "category2",
|
||||
Muted: false,
|
||||
},
|
||||
Channels: []string{channel1.Id, channel2.Id},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.False(t, category2.Muted)
|
||||
|
||||
// Move the unmuted channels
|
||||
_, err = th.App.UpdateSidebarCategories(th.BasicUser.Id, th.BasicTeam.Id, []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: category1.Id,
|
||||
DisplayName: category1.DisplayName,
|
||||
Muted: category1.Muted,
|
||||
},
|
||||
Channels: []string{channel1.Id, channel2.Id},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: category2.Id,
|
||||
DisplayName: category2.DisplayName,
|
||||
Muted: category2.Muted,
|
||||
},
|
||||
Channels: []string{},
|
||||
},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Confirm that the channels are still unmuted
|
||||
member1, err := th.App.GetChannelMember(channel1.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, member1.IsChannelMuted())
|
||||
member2, err := th.App.GetChannelMember(channel2.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, member2.IsChannelMuted())
|
||||
|
||||
// Mute the channels manually
|
||||
_, err = th.App.ToggleMuteChannel(channel1.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.ToggleMuteChannel(channel2.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Move the muted channels back
|
||||
_, err = th.App.UpdateSidebarCategories(th.BasicUser.Id, th.BasicTeam.Id, []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: category1.Id,
|
||||
DisplayName: category1.DisplayName,
|
||||
Muted: category1.Muted,
|
||||
},
|
||||
Channels: []string{},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: category2.Id,
|
||||
DisplayName: category2.DisplayName,
|
||||
Muted: category2.Muted,
|
||||
},
|
||||
Channels: []string{channel1.Id, channel2.Id},
|
||||
},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Confirm that the channels are still muted
|
||||
member1, err = th.App.GetChannelMember(channel1.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, member1.IsChannelMuted())
|
||||
member2, err = th.App.GetChannelMember(channel2.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, member2.IsChannelMuted())
|
||||
})
|
||||
}
|
||||
|
||||
func TestDiffChannelsBetweenCategories(t *testing.T) {
|
||||
t.Run("should return nothing when the categories contain identical channels", func(t *testing.T) {
|
||||
originalCategories := []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category1",
|
||||
DisplayName: "Category One",
|
||||
},
|
||||
Channels: []string{"channel1", "channel2", "channel3"},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category2",
|
||||
DisplayName: "Category Two",
|
||||
},
|
||||
Channels: []string{"channel4", "channel5"},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category3",
|
||||
DisplayName: "Category Three",
|
||||
},
|
||||
Channels: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
updatedCategories := []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category1",
|
||||
DisplayName: "Category Won",
|
||||
},
|
||||
Channels: []string{"channel1", "channel2", "channel3"},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category2",
|
||||
DisplayName: "Category Too",
|
||||
},
|
||||
Channels: []string{"channel4", "channel5"},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category3",
|
||||
DisplayName: "Category 🌲",
|
||||
},
|
||||
Channels: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
channelsDiff := diffChannelsBetweenCategories(updatedCategories, originalCategories)
|
||||
assert.Equal(t, map[string]*categoryChannelDiff{}, channelsDiff)
|
||||
})
|
||||
|
||||
t.Run("should return nothing when the categories contain identical channels", func(t *testing.T) {
|
||||
originalCategories := []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category1",
|
||||
DisplayName: "Category One",
|
||||
},
|
||||
Channels: []string{"channel1", "channel2", "channel3"},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category2",
|
||||
DisplayName: "Category Two",
|
||||
},
|
||||
Channels: []string{"channel4", "channel5"},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category3",
|
||||
DisplayName: "Category Three",
|
||||
},
|
||||
Channels: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
updatedCategories := []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category1",
|
||||
DisplayName: "Category Won",
|
||||
},
|
||||
Channels: []string{},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category2",
|
||||
DisplayName: "Category Too",
|
||||
},
|
||||
Channels: []string{"channel5", "channel2"},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category3",
|
||||
DisplayName: "Category 🌲",
|
||||
},
|
||||
Channels: []string{"channel4", "channel1", "channel3"},
|
||||
},
|
||||
}
|
||||
|
||||
channelsDiff := diffChannelsBetweenCategories(updatedCategories, originalCategories)
|
||||
assert.Equal(
|
||||
t,
|
||||
map[string]*categoryChannelDiff{
|
||||
"channel1": {
|
||||
fromCategoryId: "category1",
|
||||
toCategoryId: "category3",
|
||||
},
|
||||
"channel2": {
|
||||
fromCategoryId: "category1",
|
||||
toCategoryId: "category2",
|
||||
},
|
||||
"channel3": {
|
||||
fromCategoryId: "category1",
|
||||
toCategoryId: "category3",
|
||||
},
|
||||
"channel4": {
|
||||
fromCategoryId: "category2",
|
||||
toCategoryId: "category3",
|
||||
},
|
||||
},
|
||||
channelsDiff,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("should not return channels that are moved in our out of the categories implicitly", func(t *testing.T) {
|
||||
// This case could change to actually return the channels in the future, but we don't need to handle it right now
|
||||
originalCategories := []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category1",
|
||||
DisplayName: "Category One",
|
||||
},
|
||||
Channels: []string{"channel1", "channel2"},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category2",
|
||||
DisplayName: "Category Two",
|
||||
},
|
||||
Channels: []string{"channel3"},
|
||||
},
|
||||
}
|
||||
|
||||
updatedCategories := []*model.SidebarCategoryWithChannels{
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category1",
|
||||
DisplayName: "Category Won",
|
||||
},
|
||||
Channels: []string{"channel1", "channel3"},
|
||||
},
|
||||
{
|
||||
SidebarCategory: model.SidebarCategory{
|
||||
Id: "category2",
|
||||
DisplayName: "Category Too",
|
||||
},
|
||||
Channels: []string{"channel4"},
|
||||
},
|
||||
}
|
||||
|
||||
channelsDiff := diffChannelsBetweenCategories(updatedCategories, originalCategories)
|
||||
assert.Equal(
|
||||
t,
|
||||
map[string]*categoryChannelDiff{
|
||||
"channel3": {
|
||||
fromCategoryId: "category2",
|
||||
toCategoryId: "category1",
|
||||
},
|
||||
},
|
||||
channelsDiff,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -624,6 +624,57 @@ func TestAppUpdateChannelScheme(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetChannelsMuted(t *testing.T) {
|
||||
t.Run("should mute and unmute the given channels", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
channel1 := th.BasicChannel
|
||||
|
||||
channel2 := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(th.BasicUser, channel2)
|
||||
|
||||
// Ensure that both channels start unmuted
|
||||
member1, err := th.App.GetChannelMember(channel1.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
require.False(t, member1.IsChannelMuted())
|
||||
|
||||
member2, err := th.App.GetChannelMember(channel2.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
require.False(t, member2.IsChannelMuted())
|
||||
|
||||
// Mute both channels
|
||||
updated, err := th.App.setChannelsMuted([]string{channel1.Id, channel2.Id}, th.BasicUser.Id, true)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, updated[0].IsChannelMuted())
|
||||
assert.True(t, updated[1].IsChannelMuted())
|
||||
|
||||
// Verify that the channels are muted in the database
|
||||
member1, err = th.App.GetChannelMember(channel1.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
require.True(t, member1.IsChannelMuted())
|
||||
|
||||
member2, err = th.App.GetChannelMember(channel2.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
require.True(t, member2.IsChannelMuted())
|
||||
|
||||
// Unm both channels
|
||||
updated, err = th.App.setChannelsMuted([]string{channel1.Id, channel2.Id}, th.BasicUser.Id, false)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, updated[0].IsChannelMuted())
|
||||
assert.False(t, updated[1].IsChannelMuted())
|
||||
|
||||
// Verify that the channels are muted in the database
|
||||
member1, err = th.App.GetChannelMember(channel1.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
require.False(t, member1.IsChannelMuted())
|
||||
|
||||
member2, err = th.App.GetChannelMember(channel2.Id, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
require.False(t, member2.IsChannelMuted())
|
||||
})
|
||||
}
|
||||
|
||||
func TestFillInChannelProps(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -14212,7 +14212,7 @@ func (a *OpenTracingAppLayer) TestSiteURL(siteURL string) *model.AppError {
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) ToggleMuteChannel(channelId string, userId string) *model.ChannelMember {
|
||||
func (a *OpenTracingAppLayer) ToggleMuteChannel(channelId string, userId string) (*model.ChannelMember, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ToggleMuteChannel")
|
||||
|
||||
@@ -14224,9 +14224,14 @@ func (a *OpenTracingAppLayer) ToggleMuteChannel(channelId string, userId string)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.ToggleMuteChannel(channelId, userId)
|
||||
resultVar0, resultVar1 := a.app.ToggleMuteChannel(channelId, userId)
|
||||
|
||||
return resultVar0
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) TotalWebsocketConnections() int {
|
||||
|
||||
@@ -61,36 +61,23 @@ func (me *MuteProvider) DoCommand(a *app.App, args *model.CommandArgs, message s
|
||||
}
|
||||
}
|
||||
|
||||
channelMember := a.ToggleMuteChannel(channel.Id, args.UserId)
|
||||
if channelMember == nil {
|
||||
channelMember, err := a.ToggleMuteChannel(channel.Id, args.UserId)
|
||||
if err != nil {
|
||||
return &model.CommandResponse{Text: args.T("api.command_mute.not_member.error", map[string]interface{}{"Channel": channelName}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
|
||||
}
|
||||
|
||||
// Invalidate cache to allow cache lookups while sending notifications
|
||||
a.Srv().Store.Channel().InvalidateCacheForChannelMembersNotifyProps(channel.Id)
|
||||
|
||||
// Direct and Group messages won't have a nice channel title, omit it
|
||||
if channel.Type == model.CHANNEL_DIRECT || channel.Type == model.CHANNEL_GROUP {
|
||||
if channelMember.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] == model.CHANNEL_NOTIFY_MENTION {
|
||||
publishChannelMemberEvt(a, channelMember, args.UserId)
|
||||
return &model.CommandResponse{Text: args.T("api.command_mute.success_mute_direct_msg"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
|
||||
} else {
|
||||
publishChannelMemberEvt(a, channelMember, args.UserId)
|
||||
return &model.CommandResponse{Text: args.T("api.command_mute.success_unmute_direct_msg"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
|
||||
}
|
||||
}
|
||||
|
||||
if channelMember.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] == model.CHANNEL_NOTIFY_MENTION {
|
||||
publishChannelMemberEvt(a, channelMember, args.UserId)
|
||||
return &model.CommandResponse{Text: args.T("api.command_mute.success_mute", map[string]interface{}{"Channel": channel.DisplayName}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
|
||||
} else {
|
||||
publishChannelMemberEvt(a, channelMember, args.UserId)
|
||||
return &model.CommandResponse{Text: args.T("api.command_mute.success_unmute", map[string]interface{}{"Channel": channel.DisplayName}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
|
||||
}
|
||||
}
|
||||
|
||||
func publishChannelMemberEvt(a *app.App, channelMember *model.ChannelMember, userId string) {
|
||||
evt := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_MEMBER_UPDATED, "", "", userId, nil)
|
||||
evt.Add("channelMember", channelMember.ToJson())
|
||||
a.Publish(evt)
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user