Mark category as read (#24003)
* Mark category as read * Fix lint and test * Fix tests * Fix test and remove wrong aria * Address server issues and add mark as read for unreads * Missing changes * Fix tests * fix tests * Add confirmation popup to mark as read category * Always use viewMultipleChannels and other fixes * Remove unneeded code * Fix test * Address feedback * Address feedback * Fix tests * Fix test * Fix tests * Update aria-haspopup depending on the number of channels to mark as viewed --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
c1c07ba1bb
Коммит
e9b3afecc2
@@ -2970,57 +2970,32 @@ func (a *App) SearchChannelsUserNotIn(c request.CTX, teamID string, userID strin
|
||||
}
|
||||
|
||||
func (a *App) MarkChannelsAsViewed(c request.CTX, channelIDs []string, userID string, currentSessionId string, collapsedThreadsSupported, isCRTEnabled bool) (map[string]int64, *model.AppError) {
|
||||
// I start looking for channels with notifications before I mark it as read, to clear the push notifications if needed
|
||||
channelsToClearPushNotifications := []string{}
|
||||
if a.canSendPushNotifications() {
|
||||
for _, channelID := range channelIDs {
|
||||
channel, errCh := a.Srv().Store().Channel().Get(channelID, true)
|
||||
if errCh != nil {
|
||||
c.Logger().Warn("Failed to get channel", mlog.Err(errCh))
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
|
||||
member, err := a.Srv().Store().Channel().GetMember(context.Background(), channelID, userID)
|
||||
if err != nil {
|
||||
c.Logger().Warn("Failed to get membership", mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
|
||||
notify := member.NotifyProps[model.PushNotifyProp]
|
||||
if notify == model.ChannelNotifyDefault {
|
||||
user, err := a.GetUser(userID)
|
||||
if err != nil {
|
||||
c.Logger().Warn("Failed to get user", mlog.String("user_id", userID), mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
notify = user.NotifyProps[model.PushNotifyProp]
|
||||
}
|
||||
if notify == model.UserNotifyAll {
|
||||
if count, err := a.Srv().Store().User().GetAnyUnreadPostCountForChannel(userID, channelID); err == nil {
|
||||
if count > 0 {
|
||||
channelsToClearPushNotifications = append(channelsToClearPushNotifications, channelID)
|
||||
}
|
||||
}
|
||||
} else if notify == model.UserNotifyMention || channel.Type == model.ChannelTypeDirect {
|
||||
if count, err := a.Srv().Store().User().GetUnreadCountForChannel(userID, channelID); err == nil {
|
||||
if count > 0 {
|
||||
channelsToClearPushNotifications = append(channelsToClearPushNotifications, channelID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
user, err := a.Srv().Store().User().Get(c.Context(), userID)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("MarkChannelsAsViewed", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
// We use channelsToView to later only update those, or early return if no channel is to be read
|
||||
channelsToView, channelsToClearPushNotifications, times, err := a.Srv().Store().Channel().GetChannelsWithUnreadsAndWithMentions(c.Context(), channelIDs, userID, user.NotifyProps)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("MarkChannelsAsViewed", "app.channel.get_channels_with_unreads_and_with_mentions.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
if len(channelsToView) == 0 {
|
||||
return times, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
updateThreads := *a.Config().ServiceSettings.ThreadAutoFollow && (!collapsedThreadsSupported || !isCRTEnabled)
|
||||
if updateThreads {
|
||||
err = a.Srv().Store().Thread().MarkAllAsReadByChannels(userID, channelIDs)
|
||||
err = a.Srv().Store().Thread().MarkAllAsReadByChannels(userID, channelsToView)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("MarkChannelsAsViewed", "app.channel.update_last_viewed_at.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return nil, model.NewAppError("MarkChannelsAsViewed", "app.thread.mark_all_as_read_by_channels.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
times, err := a.Srv().Store().Channel().UpdateLastViewedAt(channelIDs, userID)
|
||||
_, err = a.Srv().Store().Channel().UpdateLastViewedAt(channelsToView, userID)
|
||||
if err != nil {
|
||||
var invErr *store.ErrInvalidInput
|
||||
switch {
|
||||
@@ -3032,19 +3007,18 @@ func (a *App) MarkChannelsAsViewed(c request.CTX, channelIDs []string, userID st
|
||||
}
|
||||
|
||||
if *a.Config().ServiceSettings.EnableChannelViewedMessages {
|
||||
for _, channelID := range channelIDs {
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventChannelViewed, "", "", userID, nil, "")
|
||||
message.Add("channel_id", channelID)
|
||||
a.Publish(message)
|
||||
}
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventMultipleChannelsViewed, "", "", userID, nil, "")
|
||||
message.Add("channel_times", times)
|
||||
a.Publish(message)
|
||||
}
|
||||
|
||||
for _, channelID := range channelsToClearPushNotifications {
|
||||
a.clearPushNotification(currentSessionId, userID, channelID, "")
|
||||
}
|
||||
|
||||
if updateThreads && isCRTEnabled {
|
||||
timestamp := model.GetMillis()
|
||||
for _, channelID := range channelIDs {
|
||||
for _, channelID := range channelsToView {
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventThreadReadChanged, "", channelID, userID, nil, "")
|
||||
message.Add("timestamp", timestamp)
|
||||
a.Publish(message)
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -18,7 +17,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/app/users"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
|
||||
)
|
||||
|
||||
@@ -2107,48 +2105,6 @@ func TestPatchChannelModerationsForChannel(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestMarkChannelsAsViewedPanic verifies that returning an error from a.GetUser
|
||||
// does not cause a panic.
|
||||
func TestMarkChannelsAsViewedPanic(t *testing.T) {
|
||||
th := SetupWithStoreMock(t)
|
||||
defer th.TearDown()
|
||||
|
||||
mockStore := th.App.Srv().Store().(*mocks.Store)
|
||||
mockUserStore := mocks.UserStore{}
|
||||
mockUserStore.On("Get", context.Background(), "userID").Return(nil, model.NewAppError("SqlUserStore.Get", "app.user.get.app_error", nil, "user_id=userID", http.StatusInternalServerError))
|
||||
mockChannelStore := mocks.ChannelStore{}
|
||||
mockChannelStore.On("Get", "channelID", true).Return(&model.Channel{}, nil)
|
||||
mockChannelStore.On("GetMember", context.Background(), "channelID", "userID").Return(&model.ChannelMember{
|
||||
NotifyProps: model.StringMap{
|
||||
model.PushNotifyProp: model.ChannelNotifyDefault,
|
||||
}}, nil)
|
||||
times := map[string]int64{
|
||||
"userID": 1,
|
||||
}
|
||||
mockChannelStore.On("UpdateLastViewedAt", []string{"channelID"}, "userID").Return(times, nil)
|
||||
mockSessionStore := mocks.SessionStore{}
|
||||
mockOAuthStore := mocks.OAuthStore{}
|
||||
var err error
|
||||
th.App.ch.srv.userService, err = users.New(users.ServiceConfig{
|
||||
UserStore: &mockUserStore,
|
||||
SessionStore: &mockSessionStore,
|
||||
OAuthStore: &mockOAuthStore,
|
||||
ConfigFn: th.App.ch.srv.platform.Config,
|
||||
LicenseFn: th.App.ch.srv.License,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
mockPreferenceStore := mocks.PreferenceStore{}
|
||||
mockPreferenceStore.On("Get", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(&model.Preference{Value: "test"}, nil)
|
||||
mockStore.On("Channel").Return(&mockChannelStore)
|
||||
mockStore.On("Preference").Return(&mockPreferenceStore)
|
||||
mockThreadStore := mocks.ThreadStore{}
|
||||
mockThreadStore.On("MarkAllAsReadByChannels", "userID", []string{"channelID"}).Return(nil)
|
||||
mockStore.On("Thread").Return(&mockThreadStore)
|
||||
|
||||
_, appErr := th.App.MarkChannelsAsViewed(th.Context, []string{"channelID"}, "userID", th.Context.Session().Id, false, false)
|
||||
require.Nil(t, appErr)
|
||||
}
|
||||
|
||||
func TestClearChannelMembersCache(t *testing.T) {
|
||||
th := SetupWithStoreMock(t)
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -726,7 +726,7 @@ func (wc *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
|
||||
switch msg.EventType() {
|
||||
case model.WebsocketEventTyping,
|
||||
model.WebsocketEventStatusChange,
|
||||
model.WebsocketEventChannelViewed:
|
||||
model.WebsocketEventMultipleChannelsViewed:
|
||||
if time.Since(wc.lastLogTimeSlow) > websocketSuppressWarnThreshold {
|
||||
mlog.Warn(
|
||||
"websocket.slow: dropping message",
|
||||
|
||||
@@ -1113,26 +1113,6 @@ func TestCreatePostAsUser(t *testing.T) {
|
||||
require.Equal(t, channelMemberAfter.LastViewedAt, channelMemberBefore.LastViewedAt)
|
||||
})
|
||||
|
||||
t.Run("logs warning for user not in channel", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
user := th.CreateUser()
|
||||
th.LinkUserToTeam(user, th.BasicTeam)
|
||||
|
||||
post := &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: "test",
|
||||
UserId: user.Id,
|
||||
}
|
||||
|
||||
_, appErr := th.App.CreatePostAsUser(th.Context, post, "", true)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
require.NoError(t, th.TestLogger.Flush())
|
||||
|
||||
testlib.AssertLog(t, th.LogBuffer, mlog.LvlWarn.Name, "Failed to get membership")
|
||||
})
|
||||
|
||||
t.Run("does not log warning for bot user not in channel", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Ссылка в новой задаче
Block a user