MM-29703 Mark threads as read when channels are marked (#15994)

Co-authored-by: Jesús Espino <jespinog@gmail.com>
Этот коммит содержится в:
Eli Yukelzon
2020-10-30 17:00:21 +02:00
коммит произвёл GitHub
родитель 1729239385
Коммит fe352ab57f
20 изменённых файлов: 451 добавлений и 92 удалений

Просмотреть файл

@@ -2322,7 +2322,7 @@ func (a *App) SetActiveChannel(userId string, channelId string) *model.AppError
} }
func (a *App) UpdateChannelLastViewedAt(channelIds []string, userId string) *model.AppError { func (a *App) UpdateChannelLastViewedAt(channelIds []string, userId string) *model.AppError {
if _, err := a.Srv().Store.Channel().UpdateLastViewedAt(channelIds, userId); err != nil { if _, err := a.Srv().Store.Channel().UpdateLastViewedAt(channelIds, userId, *a.Config().ServiceSettings.ThreadAutoFollow); err != nil {
var invErr *store.ErrInvalidInput var invErr *store.ErrInvalidInput
switch { switch {
case errors.As(err, &invErr): case errors.As(err, &invErr):
@@ -2360,7 +2360,7 @@ func (a *App) MarkChannelAsUnreadFromPost(postID string, userID string) (*model.
return nil, err return nil, err
} }
channelUnread, nErr := a.Srv().Store.Channel().UpdateLastViewedAtPost(post, userID, unreadMentions) channelUnread, nErr := a.Srv().Store.Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, *a.Config().ServiceSettings.ThreadAutoFollow)
if nErr != nil { if nErr != nil {
return channelUnread, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, nErr.Error(), http.StatusInternalServerError) return channelUnread, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, nErr.Error(), http.StatusInternalServerError)
} }
@@ -2531,7 +2531,7 @@ func (a *App) MarkChannelsAsViewed(channelIds []string, userId string, currentSe
} }
} }
} }
times, err := a.Srv().Store.Channel().UpdateLastViewedAt(channelIds, userId) times, err := a.Srv().Store.Channel().UpdateLastViewedAt(channelIds, userId, *a.Config().ServiceSettings.ThreadAutoFollow)
if err != nil { if err != nil {
var invErr *store.ErrInvalidInput var invErr *store.ErrInvalidInput
switch { switch {

Просмотреть файл

@@ -1869,7 +1869,7 @@ func TestMarkChannelsAsViewedPanic(t *testing.T) {
times := map[string]int64{ times := map[string]int64{
"userID": 1, "userID": 1,
} }
mockChannelStore.On("UpdateLastViewedAt", []string{"channelID"}, "userID").Return(times, nil) mockChannelStore.On("UpdateLastViewedAt", []string{"channelID"}, "userID", true).Return(times, nil)
mockStore.On("User").Return(&mockUserStore) mockStore.On("User").Return(&mockUserStore)
mockStore.On("Channel").Return(&mockChannelStore) mockStore.On("Channel").Return(&mockChannelStore)

Просмотреть файл

@@ -184,7 +184,7 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
umc := make(chan *model.AppError, 1) umc := make(chan *model.AppError, 1)
go func(userId string) { go func(userId string) {
defer close(umc) defer close(umc)
nErr := a.Srv().Store.Channel().IncrementMentionCount(post.ChannelId, userId) nErr := a.Srv().Store.Channel().IncrementMentionCount(post.ChannelId, userId, *a.Config().ServiceSettings.ThreadAutoFollow)
if nErr != nil { if nErr != nil {
umc <- model.NewAppError("SendNotifications", "app.channel.increment_mention_count.app_error", nil, nErr.Error(), http.StatusInternalServerError) umc <- model.NewAppError("SendNotifications", "app.channel.increment_mention_count.app_error", nil, nErr.Error(), http.StatusInternalServerError)
return return

Просмотреть файл

@@ -71,13 +71,13 @@ func (_m *LdapInterface) DoLogin(id string, password string) (*model.User, *mode
return r0, r1 return r0, r1
} }
// FirstLoginSync provides a mock function with given fields: userID, userAuthService, userAuthData, email // FirstLoginSync provides a mock function with given fields: user, userAuthService, userAuthData, email
func (_m *LdapInterface) FirstLoginSync(userID string, userAuthService string, userAuthData string, email string) *model.AppError { func (_m *LdapInterface) FirstLoginSync(user *model.User, userAuthService string, userAuthData string, email string) *model.AppError {
ret := _m.Called(userID, userAuthService, userAuthData, email) ret := _m.Called(user, userAuthService, userAuthData, email)
var r0 *model.AppError var r0 *model.AppError
if rf, ok := ret.Get(0).(func(string, string, string, string) *model.AppError); ok { if rf, ok := ret.Get(0).(func(*model.User, string, string, string) *model.AppError); ok {
r0 = rf(userID, userAuthService, userAuthData, email) r0 = rf(user, userAuthService, userAuthData, email)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AppError) r0 = ret.Get(0).(*model.AppError)

Просмотреть файл

@@ -17,7 +17,7 @@ type OauthProvider struct {
} }
// GetUserFromJson provides a mock function with given fields: data // GetUserFromJson provides a mock function with given fields: data
func (_m *OauthProvider) GetUserFromJson(data io.Reader) *model.User { func (_m *OauthProvider) GetUserFromJson(data io.Reader) (*model.User, error) {
ret := _m.Called(data) ret := _m.Called(data)
var r0 *model.User var r0 *model.User
@@ -29,5 +29,12 @@ func (_m *OauthProvider) GetUserFromJson(data io.Reader) *model.User {
} }
} }
return r0 var r1 error
if rf, ok := ret.Get(1).(func(io.Reader) error); ok {
r1 = rf(data)
} else {
r1 = ret.Error(1)
}
return r0, r1
} }

Просмотреть файл

@@ -9,6 +9,7 @@ import (
type Thread struct { type Thread struct {
PostId string `json:"id"` PostId string `json:"id"`
ChannelId string `json:"channel_id"`
ReplyCount int64 `json:"reply_count"` ReplyCount int64 `json:"reply_count"`
LastReplyAt int64 `json:"last_reply_at"` LastReplyAt int64 `json:"last_reply_at"`
Participants StringArray `json:"participants"` Participants StringArray `json:"participants"`

Просмотреть файл

@@ -1538,7 +1538,7 @@ func (s *OpenTracingLayerChannelStore) GroupSyncedChannelCount() (int64, error)
return result, err return result, err
} }
func (s *OpenTracingLayerChannelStore) IncrementMentionCount(channelId string, userId string) error { func (s *OpenTracingLayerChannelStore) IncrementMentionCount(channelId string, userId string, updateThreads bool) error {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.IncrementMentionCount") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.IncrementMentionCount")
s.Root.Store.SetContext(newCtx) s.Root.Store.SetContext(newCtx)
@@ -1547,7 +1547,7 @@ func (s *OpenTracingLayerChannelStore) IncrementMentionCount(channelId string, u
}() }()
defer span.Finish() defer span.Finish()
err := s.ChannelStore.IncrementMentionCount(channelId, userId) err := s.ChannelStore.IncrementMentionCount(channelId, userId, updateThreads)
if err != nil { if err != nil {
span.LogFields(spanlog.Error(err)) span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true) ext.Error.Set(span, true)
@@ -2074,7 +2074,7 @@ func (s *OpenTracingLayerChannelStore) Update(channel *model.Channel) (*model.Ch
return result, err return result, err
} }
func (s *OpenTracingLayerChannelStore) UpdateLastViewedAt(channelIds []string, userId string) (map[string]int64, error) { func (s *OpenTracingLayerChannelStore) UpdateLastViewedAt(channelIds []string, userId string, updateThreads bool) (map[string]int64, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.UpdateLastViewedAt") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.UpdateLastViewedAt")
s.Root.Store.SetContext(newCtx) s.Root.Store.SetContext(newCtx)
@@ -2083,7 +2083,7 @@ func (s *OpenTracingLayerChannelStore) UpdateLastViewedAt(channelIds []string, u
}() }()
defer span.Finish() defer span.Finish()
result, err := s.ChannelStore.UpdateLastViewedAt(channelIds, userId) result, err := s.ChannelStore.UpdateLastViewedAt(channelIds, userId, updateThreads)
if err != nil { if err != nil {
span.LogFields(spanlog.Error(err)) span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true) ext.Error.Set(span, true)
@@ -2092,7 +2092,7 @@ func (s *OpenTracingLayerChannelStore) UpdateLastViewedAt(channelIds []string, u
return result, err return result, err
} }
func (s *OpenTracingLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int) (*model.ChannelUnreadAt, error) { func (s *OpenTracingLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, updateThreads bool) (*model.ChannelUnreadAt, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.UpdateLastViewedAtPost") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.UpdateLastViewedAtPost")
s.Root.Store.SetContext(newCtx) s.Root.Store.SetContext(newCtx)
@@ -2101,7 +2101,7 @@ func (s *OpenTracingLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.
}() }()
defer span.Finish() defer span.Finish()
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount) result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, updateThreads)
if err != nil { if err != nil {
span.LogFields(spanlog.Error(err)) span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true) ext.Error.Set(span, true)
@@ -7612,6 +7612,24 @@ func (s *OpenTracingLayerTermsOfServiceStore) Save(termsOfService *model.TermsOf
return result, err return result, err
} }
func (s *OpenTracingLayerThreadStore) CollectThreadsWithNewerReplies(userId string, channelIds []string, timestamp int64) ([]string, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.CollectThreadsWithNewerReplies")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.ThreadStore.CollectThreadsWithNewerReplies(userId, channelIds, timestamp)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerThreadStore) CreateMembershipIfNeeded(userId string, postId string) error { func (s *OpenTracingLayerThreadStore) CreateMembershipIfNeeded(userId string, postId string) error {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.CreateMembershipIfNeeded") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.CreateMembershipIfNeeded")
@@ -7810,6 +7828,24 @@ func (s *OpenTracingLayerThreadStore) UpdateMembership(membership *model.ThreadM
return result, err return result, err
} }
func (s *OpenTracingLayerThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.UpdateUnreadsByChannel")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
err := s.ThreadStore.UpdateUnreadsByChannel(userId, changedThreads, timestamp)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return err
}
func (s *OpenTracingLayerTokenStore) Cleanup() { func (s *OpenTracingLayerTokenStore) Cleanup() {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TokenStore.Cleanup") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TokenStore.Cleanup")

Просмотреть файл

@@ -1670,11 +1670,11 @@ func (s *RetryLayerChannelStore) GroupSyncedChannelCount() (int64, error) {
} }
func (s *RetryLayerChannelStore) IncrementMentionCount(channelId string, userId string) error { func (s *RetryLayerChannelStore) IncrementMentionCount(channelId string, userId string, updateThreads bool) error {
tries := 0 tries := 0
for { for {
err := s.ChannelStore.IncrementMentionCount(channelId, userId) err := s.ChannelStore.IncrementMentionCount(channelId, userId, updateThreads)
if err == nil { if err == nil {
return nil return nil
} }
@@ -2198,11 +2198,11 @@ func (s *RetryLayerChannelStore) Update(channel *model.Channel) (*model.Channel,
} }
func (s *RetryLayerChannelStore) UpdateLastViewedAt(channelIds []string, userId string) (map[string]int64, error) { func (s *RetryLayerChannelStore) UpdateLastViewedAt(channelIds []string, userId string, updateThreads bool) (map[string]int64, error) {
tries := 0 tries := 0
for { for {
result, err := s.ChannelStore.UpdateLastViewedAt(channelIds, userId) result, err := s.ChannelStore.UpdateLastViewedAt(channelIds, userId, updateThreads)
if err == nil { if err == nil {
return result, nil return result, nil
} }
@@ -2218,11 +2218,11 @@ func (s *RetryLayerChannelStore) UpdateLastViewedAt(channelIds []string, userId
} }
func (s *RetryLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int) (*model.ChannelUnreadAt, error) { func (s *RetryLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, updateThreads bool) (*model.ChannelUnreadAt, error) {
tries := 0 tries := 0
for { for {
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount) result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, updateThreads)
if err == nil { if err == nil {
return result, nil return result, nil
} }
@@ -7642,6 +7642,26 @@ func (s *RetryLayerTermsOfServiceStore) Save(termsOfService *model.TermsOfServic
} }
func (s *RetryLayerThreadStore) CollectThreadsWithNewerReplies(userId string, channelIds []string, timestamp int64) ([]string, error) {
tries := 0
for {
result, err := s.ThreadStore.CollectThreadsWithNewerReplies(userId, channelIds, timestamp)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
}
}
func (s *RetryLayerThreadStore) CreateMembershipIfNeeded(userId string, postId string) error { func (s *RetryLayerThreadStore) CreateMembershipIfNeeded(userId string, postId string) error {
tries := 0 tries := 0
@@ -7862,6 +7882,26 @@ func (s *RetryLayerThreadStore) UpdateMembership(membership *model.ThreadMembers
} }
func (s *RetryLayerThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64) error {
tries := 0
for {
err := s.ThreadStore.UpdateUnreadsByChannel(userId, changedThreads, timestamp)
if err == nil {
return nil
}
if !isRepeatableError(err) {
return err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return err
}
}
}
func (s *RetryLayerTokenStore) Cleanup() { func (s *RetryLayerTokenStore) Cleanup() {
s.TokenStore.Cleanup() s.TokenStore.Cleanup()

Просмотреть файл

@@ -2046,7 +2046,17 @@ func (s SqlChannelStore) PermanentDeleteMembersByUser(userId string) error {
return nil return nil
} }
func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string) (map[string]int64, error) { func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string, updateThreads bool) (map[string]int64, error) {
var threadsToUpdate []string
now := model.GetMillis()
if updateThreads {
var err error
threadsToUpdate, err = s.Thread().CollectThreadsWithNewerReplies(userId, channelIds, now)
if err != nil {
return nil, err
}
}
keys, props := MapStringsToQueryParams(channelIds, "Channel") keys, props := MapStringsToQueryParams(channelIds, "Channel")
props["UserId"] = userId props["UserId"] = userId
@@ -2089,11 +2099,15 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string)
for _, t := range lastPostAtTimes { for _, t := range lastPostAtTimes {
times[t.Id] = t.LastPostAt times[t.Id] = t.LastPostAt
} }
if updateThreads {
s.Thread().UpdateUnreadsByChannel(userId, threadsToUpdate, now)
}
return times, nil return times, nil
} }
msgCountQuery := "" msgCountQuery := ""
lastViewedQuery := "" lastViewedQuery := ""
for index, t := range lastPostAtTimes { for index, t := range lastPostAtTimes {
times[t.Id] = t.LastPostAt times[t.Id] = t.LastPostAt
@@ -2121,6 +2135,9 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string)
return nil, errors.Wrapf(err, "failed to update ChannelMembers with userId=%s and channelId in %v", userId, channelIds) return nil, errors.Wrapf(err, "failed to update ChannelMembers with userId=%s and channelId in %v", userId, channelIds)
} }
if updateThreads {
s.Thread().UpdateUnreadsByChannel(userId, threadsToUpdate, now)
}
return times, nil return times, nil
} }
@@ -2168,8 +2185,16 @@ func (s SqlChannelStore) CountPostsAfter(channelId string, timestamp int64, user
// UpdateLastViewedAtPost updates a ChannelMember as if the user last read the channel at the time of the given post. // UpdateLastViewedAtPost updates a ChannelMember as if the user last read the channel at the time of the given post.
// If the provided mentionCount is -1, the given post and all posts after it are considered to be mentions. Returns // If the provided mentionCount is -1, the given post and all posts after it are considered to be mentions. Returns
// an updated model.ChannelUnreadAt that can be returned to the client. // an updated model.ChannelUnreadAt that can be returned to the client.
func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int) (*model.ChannelUnreadAt, error) { func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, updateThreads bool) (*model.ChannelUnreadAt, error) {
var threadsToUpdate []string
unreadDate := unreadPost.CreateAt - 1 unreadDate := unreadPost.CreateAt - 1
if updateThreads {
var err error
threadsToUpdate, err = s.Thread().CollectThreadsWithNewerReplies(userID, []string{unreadPost.ChannelId}, unreadDate)
if err != nil {
return nil, err
}
}
unread, err := s.CountPostsAfter(unreadPost.ChannelId, unreadDate, "") unread, err := s.CountPostsAfter(unreadPost.ChannelId, unreadDate, "")
if err != nil { if err != nil {
@@ -2225,10 +2250,24 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s
if err = s.GetMaster().SelectOne(result, chanUnreadQuery, params); err != nil { if err = s.GetMaster().SelectOne(result, chanUnreadQuery, params); err != nil {
return nil, errors.Wrapf(err, "failed to get ChannelMember with channelId=%s", unreadPost.ChannelId) return nil, errors.Wrapf(err, "failed to get ChannelMember with channelId=%s", unreadPost.ChannelId)
} }
if updateThreads {
s.Thread().UpdateUnreadsByChannel(userID, threadsToUpdate, unreadDate)
}
return result, nil return result, nil
} }
func (s SqlChannelStore) IncrementMentionCount(channelId string, userId string) error { func (s SqlChannelStore) IncrementMentionCount(channelId string, userId string, updateThreads bool) error {
now := model.GetMillis()
var threadsToUpdate []string
if updateThreads {
var err error
threadsToUpdate, err = s.Thread().CollectThreadsWithNewerReplies(userId, []string{channelId}, now)
if err != nil {
return err
}
}
_, err := s.GetMaster().Exec( _, err := s.GetMaster().Exec(
`UPDATE `UPDATE
ChannelMembers ChannelMembers
@@ -2238,11 +2277,13 @@ func (s SqlChannelStore) IncrementMentionCount(channelId string, userId string)
WHERE WHERE
UserId = :UserId UserId = :UserId
AND ChannelId = :ChannelId`, AND ChannelId = :ChannelId`,
map[string]interface{}{"ChannelId": channelId, "UserId": userId, "LastUpdateAt": model.GetMillis()}) map[string]interface{}{"ChannelId": channelId, "UserId": userId, "LastUpdateAt": now})
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to Update ChannelMembers with channelId=%s and userId=%s", channelId, userId) return errors.Wrapf(err, "failed to Update ChannelMembers with channelId=%s and userId=%s", channelId, userId)
} }
if updateThreads {
s.Thread().UpdateUnreadsByChannel(userId, threadsToUpdate, now)
}
return nil return nil
} }

Просмотреть файл

@@ -1987,6 +1987,7 @@ func (s *SqlPostStore) updateThreadsFromPosts(transaction *gorp.Transaction, pos
// no metadata entry, create one // no metadata entry, create one
if err := transaction.Insert(&model.Thread{ if err := transaction.Insert(&model.Thread{
PostId: rootId, PostId: rootId,
ChannelId: posts[0].ChannelId,
ReplyCount: count, ReplyCount: count,
LastReplyAt: now, LastReplyAt: now,
Participants: participants, Participants: participants,

Просмотреть файл

@@ -5,11 +5,12 @@ package sqlstore
import ( import (
"database/sql" "database/sql"
"time"
"github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/store" "github.com/mattermost/mattermost-server/v5/store"
"github.com/mattermost/mattermost-server/v5/utils" "github.com/mattermost/mattermost-server/v5/utils"
"github.com/pkg/errors" "github.com/pkg/errors"
"time"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
) )
@@ -29,6 +30,7 @@ func newSqlThreadStore(sqlStore SqlStore) store.ThreadStore {
for _, db := range sqlStore.GetAllConns() { for _, db := range sqlStore.GetAllConns() {
tableThreads := db.AddTableWithName(model.Thread{}, "Threads").SetKeys(false, "PostId") tableThreads := db.AddTableWithName(model.Thread{}, "Threads").SetKeys(false, "PostId")
tableThreads.ColMap("PostId").SetMaxSize(26) tableThreads.ColMap("PostId").SetMaxSize(26)
tableThreads.ColMap("ChannelId").SetMaxSize(26)
tableThreads.ColMap("Participants").SetMaxSize(0) tableThreads.ColMap("Participants").SetMaxSize(0)
tableThreadMemberships := db.AddTableWithName(model.ThreadMembership{}, "ThreadMemberships").SetKeys(false, "PostId", "UserId") tableThreadMemberships := db.AddTableWithName(model.ThreadMembership{}, "ThreadMemberships").SetKeys(false, "PostId", "UserId")
tableThreadMemberships.ColMap("PostId").SetMaxSize(26) tableThreadMemberships.ColMap("PostId").SetMaxSize(26)
@@ -39,12 +41,13 @@ func newSqlThreadStore(sqlStore SqlStore) store.ThreadStore {
} }
func threadSliceColumns() []string { func threadSliceColumns() []string {
return []string{"PostId", "LastReplyAt", "ReplyCount", "Participants"} return []string{"PostId", "ChannelId", "LastReplyAt", "ReplyCount", "Participants"}
} }
func threadToSlice(thread *model.Thread) []interface{} { func threadToSlice(thread *model.Thread) []interface{} {
return []interface{}{ return []interface{}{
thread.PostId, thread.PostId,
thread.ChannelId,
thread.LastReplyAt, thread.LastReplyAt,
thread.ReplyCount, thread.ReplyCount,
thread.Participants, thread.Participants,
@@ -52,13 +55,10 @@ func threadToSlice(thread *model.Thread) []interface{} {
} }
func (s *SqlThreadStore) createIndexesIfNotExists() { func (s *SqlThreadStore) createIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_threads_last_reply_at", "Threads", "LastReplyAt")
s.CreateIndexIfNotExists("idx_threads_post_id", "Threads", "PostId")
s.CreateIndexIfNotExists("idx_thread_memberships_last_update_at", "ThreadMemberships", "LastUpdated") s.CreateIndexIfNotExists("idx_thread_memberships_last_update_at", "ThreadMemberships", "LastUpdated")
s.CreateIndexIfNotExists("idx_thread_memberships_last_view_at", "ThreadMemberships", "LastViewed") s.CreateIndexIfNotExists("idx_thread_memberships_last_view_at", "ThreadMemberships", "LastViewed")
s.CreateIndexIfNotExists("idx_thread_memberships_post_id", "ThreadMemberships", "PostId")
s.CreateIndexIfNotExists("idx_thread_memberships_user_id", "ThreadMemberships", "UserId") s.CreateIndexIfNotExists("idx_thread_memberships_user_id", "ThreadMemberships", "UserId")
s.CreateIndexIfNotExists("idx_threads_channel_id", "Threads", "ChannelId")
} }
func (s *SqlThreadStore) SaveMultiple(threads []*model.Thread) ([]*model.Thread, int, error) { func (s *SqlThreadStore) SaveMultiple(threads []*model.Thread) ([]*model.Thread, int, error) {
@@ -188,3 +188,41 @@ func (s *SqlThreadStore) CreateMembershipIfNeeded(userId, postId string) error {
}) })
return err return err
} }
func (s *SqlThreadStore) CollectThreadsWithNewerReplies(userId string, channelIds []string, timestamp int64) ([]string, error) {
var changedThreads []string
query, args, _ := s.getQueryBuilder().
Select("Threads.PostId").
From("Threads").
LeftJoin("ChannelMembers ON ChannelMembers.ChannelId=Threads.ChannelId").
Where(sq.And{
sq.Eq{"Threads.ChannelId": channelIds},
sq.Eq{"ChannelMembers.UserId": userId},
sq.Or{
sq.Expr("Threads.LastReplyAt >= ChannelMembers.LastViewedAt"),
sq.GtOrEq{"Threads.LastReplyAt": timestamp},
},
}).
ToSql()
if _, err := s.GetReplica().Select(&changedThreads, query, args...); err != nil {
return nil, errors.Wrap(err, "failed to fetch threads")
}
return changedThreads, nil
}
func (s *SqlThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64) error {
if len(changedThreads) == 0 {
return nil
}
updateQuery, updateArgs, _ := s.getQueryBuilder().
Update("ThreadMemberships").
Where(sq.Eq{"UserId": userId, "PostId": changedThreads}).
Set("LastUpdated", timestamp).
Set("LastViewed", timestamp).
ToSql()
if _, err := s.GetMaster().Exec(updateQuery, updateArgs...); err != nil {
return errors.Wrap(err, "failed to update thread membership")
}
return nil
}

Просмотреть файл

@@ -925,6 +925,16 @@ func upgradeDatabaseToVersion529(sqlStore SqlStore) {
sqlStore.AlterColumnTypeIfExists("SidebarChannels", "CategoryId", "VARCHAR(128)", "VARCHAR(128)") sqlStore.AlterColumnTypeIfExists("SidebarChannels", "CategoryId", "VARCHAR(128)", "VARCHAR(128)")
sqlStore.AlterColumnDefaultIfExists("SidebarChannels", "CategoryId", model.NewString(""), nil) sqlStore.AlterColumnDefaultIfExists("SidebarChannels", "CategoryId", model.NewString(""), nil)
sqlStore.CreateColumnIfNotExistsNoDefault("Threads", "ChannelId", "VARCHAR(26)", "VARCHAR(26)")
updateThreadChannelsQuery := "UPDATE Threads INNER JOIN Posts ON Posts.Id=Threads.PostId SET Threads.ChannelId=Posts.ChannelId WHERE Threads.ChannelId IS NULL"
if sqlStore.DriverName() == model.DATABASE_DRIVER_POSTGRES {
updateThreadChannelsQuery = "UPDATE Threads SET ChannelId=Posts.ChannelId FROM Posts WHERE Posts.Id=Threads.PostId AND Threads.ChannelId IS NULL"
}
if _, err := sqlStore.GetMaster().Exec(updateThreadChannelsQuery); err != nil {
mlog.Error("Error updating ChannelId in Threads table", mlog.Err(err))
}
saveSchemaVersion(sqlStore, VERSION_5_29_0) saveSchemaVersion(sqlStore, VERSION_5_29_0)
} }
} }

Просмотреть файл

@@ -191,10 +191,10 @@ type ChannelStore interface {
RemoveMembers(channelId string, userIds []string) error RemoveMembers(channelId string, userIds []string) error
PermanentDeleteMembersByUser(userId string) error PermanentDeleteMembersByUser(userId string) error
PermanentDeleteMembersByChannel(channelId string) error PermanentDeleteMembersByChannel(channelId string) error
UpdateLastViewedAt(channelIds []string, userId string) (map[string]int64, error) UpdateLastViewedAt(channelIds []string, userId string, updateThreads bool) (map[string]int64, error)
UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int) (*model.ChannelUnreadAt, error) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, updateThreads bool) (*model.ChannelUnreadAt, error)
CountPostsAfter(channelId string, timestamp int64, userId string) (int, error) CountPostsAfter(channelId string, timestamp int64, userId string) (int, error)
IncrementMentionCount(channelId string, userId string) error IncrementMentionCount(channelId string, userId string, updateThreads bool) error
AnalyticsTypeCount(teamId string, channelType string) (int64, error) AnalyticsTypeCount(teamId string, channelType string) (int64, error)
GetMembersForUser(teamId string, userId string) (*model.ChannelMembers, error) GetMembersForUser(teamId string, userId string) (*model.ChannelMembers, error)
GetMembersForUserWithPagination(teamId, userId string, page, perPage int) (*model.ChannelMembers, error) GetMembersForUserWithPagination(teamId, userId string, page, perPage int) (*model.ChannelMembers, error)
@@ -259,6 +259,8 @@ type ThreadStore interface {
GetMembershipForUser(userId, postId string) (*model.ThreadMembership, error) GetMembershipForUser(userId, postId string) (*model.ThreadMembership, error)
DeleteMembershipForUser(userId, postId string) error DeleteMembershipForUser(userId, postId string) error
CreateMembershipIfNeeded(userId, postId string) error CreateMembershipIfNeeded(userId, postId string) error
CollectThreadsWithNewerReplies(userId string, channelIds []string, timestamp int64) ([]string, error)
UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64) error
} }
type PostStore interface { type PostStore interface {

Просмотреть файл

@@ -4157,11 +4157,11 @@ func testChannelStoreUpdateLastViewedAt(t *testing.T, ss store.Store) {
require.Nil(t, err) require.Nil(t, err)
var times map[string]int64 var times map[string]int64
times, err = ss.Channel().UpdateLastViewedAt([]string{m1.ChannelId}, m1.UserId) times, err = ss.Channel().UpdateLastViewedAt([]string{m1.ChannelId}, m1.UserId, false)
require.Nil(t, err, "failed to update ", err) require.Nil(t, err, "failed to update ", err)
require.Equal(t, o1.LastPostAt, times[o1.Id], "last viewed at time incorrect") require.Equal(t, o1.LastPostAt, times[o1.Id], "last viewed at time incorrect")
times, err = ss.Channel().UpdateLastViewedAt([]string{m1.ChannelId, m2.ChannelId}, m1.UserId) times, err = ss.Channel().UpdateLastViewedAt([]string{m1.ChannelId, m2.ChannelId}, m1.UserId, false)
require.Nil(t, err, "failed to update ", err) require.Nil(t, err, "failed to update ", err)
require.Equal(t, o2.LastPostAt, times[o2.Id], "last viewed at time incorrect") require.Equal(t, o2.LastPostAt, times[o2.Id], "last viewed at time incorrect")
@@ -4177,7 +4177,7 @@ func testChannelStoreUpdateLastViewedAt(t *testing.T, ss store.Store) {
assert.Equal(t, o2.LastPostAt, rm2.LastUpdateAt) assert.Equal(t, o2.LastPostAt, rm2.LastUpdateAt)
assert.Equal(t, o2.TotalMsgCount, rm2.MsgCount) assert.Equal(t, o2.TotalMsgCount, rm2.MsgCount)
_, err = ss.Channel().UpdateLastViewedAt([]string{m1.ChannelId}, "missing id") _, err = ss.Channel().UpdateLastViewedAt([]string{m1.ChannelId}, "missing id", false)
require.Nil(t, err, "failed to update") require.Nil(t, err, "failed to update")
} }
@@ -4198,16 +4198,16 @@ func testChannelStoreIncrementMentionCount(t *testing.T, ss store.Store) {
_, err := ss.Channel().SaveMember(&m1) _, err := ss.Channel().SaveMember(&m1)
require.Nil(t, err) require.Nil(t, err)
err = ss.Channel().IncrementMentionCount(m1.ChannelId, m1.UserId) err = ss.Channel().IncrementMentionCount(m1.ChannelId, m1.UserId, false)
require.Nil(t, err, "failed to update") require.Nil(t, err, "failed to update")
err = ss.Channel().IncrementMentionCount(m1.ChannelId, "missing id") err = ss.Channel().IncrementMentionCount(m1.ChannelId, "missing id", false)
require.Nil(t, err, "failed to update") require.Nil(t, err, "failed to update")
err = ss.Channel().IncrementMentionCount("missing id", m1.UserId) err = ss.Channel().IncrementMentionCount("missing id", m1.UserId, false)
require.Nil(t, err, "failed to update") require.Nil(t, err, "failed to update")
err = ss.Channel().IncrementMentionCount("missing id", "missing id") err = ss.Channel().IncrementMentionCount("missing id", "missing id", false)
require.Nil(t, err, "failed to update") require.Nil(t, err, "failed to update")
} }

Просмотреть файл

@@ -1252,13 +1252,13 @@ func (_m *ChannelStore) GroupSyncedChannelCount() (int64, error) {
return r0, r1 return r0, r1
} }
// IncrementMentionCount provides a mock function with given fields: channelId, userId // IncrementMentionCount provides a mock function with given fields: channelId, userId, updateThreads
func (_m *ChannelStore) IncrementMentionCount(channelId string, userId string) error { func (_m *ChannelStore) IncrementMentionCount(channelId string, userId string, updateThreads bool) error {
ret := _m.Called(channelId, userId) ret := _m.Called(channelId, userId, updateThreads)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(string, string) error); ok { if rf, ok := ret.Get(0).(func(string, string, bool) error); ok {
r0 = rf(channelId, userId) r0 = rf(channelId, userId, updateThreads)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
} }
@@ -1752,13 +1752,13 @@ func (_m *ChannelStore) Update(channel *model.Channel) (*model.Channel, error) {
return r0, r1 return r0, r1
} }
// UpdateLastViewedAt provides a mock function with given fields: channelIds, userId // UpdateLastViewedAt provides a mock function with given fields: channelIds, userId, updateThreads
func (_m *ChannelStore) UpdateLastViewedAt(channelIds []string, userId string) (map[string]int64, error) { func (_m *ChannelStore) UpdateLastViewedAt(channelIds []string, userId string, updateThreads bool) (map[string]int64, error) {
ret := _m.Called(channelIds, userId) ret := _m.Called(channelIds, userId, updateThreads)
var r0 map[string]int64 var r0 map[string]int64
if rf, ok := ret.Get(0).(func([]string, string) map[string]int64); ok { if rf, ok := ret.Get(0).(func([]string, string, bool) map[string]int64); ok {
r0 = rf(channelIds, userId) r0 = rf(channelIds, userId, updateThreads)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]int64) r0 = ret.Get(0).(map[string]int64)
@@ -1766,8 +1766,8 @@ func (_m *ChannelStore) UpdateLastViewedAt(channelIds []string, userId string) (
} }
var r1 error var r1 error
if rf, ok := ret.Get(1).(func([]string, string) error); ok { if rf, ok := ret.Get(1).(func([]string, string, bool) error); ok {
r1 = rf(channelIds, userId) r1 = rf(channelIds, userId, updateThreads)
} else { } else {
r1 = ret.Error(1) r1 = ret.Error(1)
} }
@@ -1775,13 +1775,13 @@ func (_m *ChannelStore) UpdateLastViewedAt(channelIds []string, userId string) (
return r0, r1 return r0, r1
} }
// UpdateLastViewedAtPost provides a mock function with given fields: unreadPost, userID, mentionCount // UpdateLastViewedAtPost provides a mock function with given fields: unreadPost, userID, mentionCount, updateThreads
func (_m *ChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int) (*model.ChannelUnreadAt, error) { func (_m *ChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, updateThreads bool) (*model.ChannelUnreadAt, error) {
ret := _m.Called(unreadPost, userID, mentionCount) ret := _m.Called(unreadPost, userID, mentionCount, updateThreads)
var r0 *model.ChannelUnreadAt var r0 *model.ChannelUnreadAt
if rf, ok := ret.Get(0).(func(*model.Post, string, int) *model.ChannelUnreadAt); ok { if rf, ok := ret.Get(0).(func(*model.Post, string, int, bool) *model.ChannelUnreadAt); ok {
r0 = rf(unreadPost, userID, mentionCount) r0 = rf(unreadPost, userID, mentionCount, updateThreads)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.ChannelUnreadAt) r0 = ret.Get(0).(*model.ChannelUnreadAt)
@@ -1789,8 +1789,8 @@ func (_m *ChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID st
} }
var r1 error var r1 error
if rf, ok := ret.Get(1).(func(*model.Post, string, int) error); ok { if rf, ok := ret.Get(1).(func(*model.Post, string, int, bool) error); ok {
r1 = rf(unreadPost, userID, mentionCount) r1 = rf(unreadPost, userID, mentionCount, updateThreads)
} else { } else {
r1 = ret.Error(1) r1 = ret.Error(1)
} }

Просмотреть файл

@@ -14,6 +14,29 @@ type ThreadStore struct {
mock.Mock mock.Mock
} }
// CollectThreadsWithNewerReplies provides a mock function with given fields: userId, channelIds, timestamp
func (_m *ThreadStore) CollectThreadsWithNewerReplies(userId string, channelIds []string, timestamp int64) ([]string, error) {
ret := _m.Called(userId, channelIds, timestamp)
var r0 []string
if rf, ok := ret.Get(0).(func(string, []string, int64) []string); ok {
r0 = rf(userId, channelIds, timestamp)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, []string, int64) error); ok {
r1 = rf(userId, channelIds, timestamp)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateMembershipIfNeeded provides a mock function with given fields: userId, postId // CreateMembershipIfNeeded provides a mock function with given fields: userId, postId
func (_m *ThreadStore) CreateMembershipIfNeeded(userId string, postId string) error { func (_m *ThreadStore) CreateMembershipIfNeeded(userId string, postId string) error {
ret := _m.Called(userId, postId) ret := _m.Called(userId, postId)
@@ -246,3 +269,17 @@ func (_m *ThreadStore) UpdateMembership(membership *model.ThreadMembership) (*mo
return r0, r1 return r0, r1
} }
// UpdateUnreadsByChannel provides a mock function with given fields: userId, changedThreads, timestamp
func (_m *ThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64) error {
ret := _m.Called(userId, changedThreads, timestamp)
var r0 error
if rf, ok := ret.Get(0).(func(string, []string, int64) error); ok {
r0 = rf(userId, changedThreads, timestamp)
} else {
r0 = ret.Error(0)
}
return r0
}

Просмотреть файл

@@ -6,8 +6,10 @@ package storetest
import ( import (
"github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/store" "github.com/mattermost/mattermost-server/v5/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"testing" "testing"
"time"
) )
func TestThreadStore(t *testing.T, ss store.Store, s SqlSupplier) { func TestThreadStore(t *testing.T, ss store.Store, s SqlSupplier) {
@@ -16,42 +18,72 @@ func TestThreadStore(t *testing.T, ss store.Store, s SqlSupplier) {
func testThreadStorePopulation(t *testing.T, ss store.Store) { func testThreadStorePopulation(t *testing.T, ss store.Store) {
makeSomePosts := func() []*model.Post { makeSomePosts := func() []*model.Post {
o1 := model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.RootId = model.NewId()
o1.Message = "zz" + model.NewId() + "b"
u1 := model.User{
Email: MakeEmail(),
Username: model.NewId(),
}
u, err := ss.User().Save(&u1)
require.Nil(t, err)
c, err2 := ss.Channel().Save(&model.Channel{
DisplayName: model.NewId(),
Type: model.CHANNEL_OPEN,
Name: model.NewId(),
}, 999)
require.NoError(t, err2)
_, err44 := ss.Channel().SaveMember(&model.ChannelMember{
ChannelId: c.Id,
UserId: u1.Id,
NotifyProps: model.GetDefaultChannelNotifyProps(),
MsgCount: 90,
})
require.NoError(t, err44)
o := model.Post{}
o.ChannelId = c.Id
o.UserId = u.Id
o.Message = "zz" + model.NewId() + "b"
otmp, err3 := ss.Post().Save(&o)
require.NoError(t, err3)
o2 := model.Post{} o2 := model.Post{}
o2.ChannelId = model.NewId() o2.ChannelId = c.Id
o2.UserId = model.NewId() o2.UserId = model.NewId()
o2.RootId = o1.RootId o2.RootId = otmp.Id
o2.Message = "zz" + model.NewId() + "b" o2.Message = "zz" + model.NewId() + "b"
o3 := model.Post{} o3 := model.Post{}
o3.ChannelId = model.NewId() o3.ChannelId = c.Id
o3.UserId = model.NewId() o3.UserId = u.Id
o3.RootId = model.NewId() o3.RootId = otmp.Id
o3.Message = "zz" + model.NewId() + "b" o3.Message = "zz" + model.NewId() + "b"
o4 := model.Post{} o4 := model.Post{}
o4.ChannelId = model.NewId() o4.ChannelId = c.Id
o4.UserId = model.NewId() o4.UserId = model.NewId()
o4.Message = "zz" + model.NewId() + "b" o4.Message = "zz" + model.NewId() + "b"
newPosts, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&o1, &o2, &o3, &o4}) newPosts, errIdx, err3 := ss.Post().SaveMultiple([]*model.Post{&o2, &o3, &o4})
require.Nil(t, err, "couldn't save item")
olist, _ := ss.Post().Get(otmp.Id, true)
o1 := olist.Posts[olist.Order[0]]
newPosts = append([]*model.Post{o1}, newPosts...)
require.Nil(t, err3, "couldn't save item")
require.Equal(t, -1, errIdx) require.Equal(t, -1, errIdx)
require.Len(t, newPosts, 4) require.Len(t, newPosts, 4)
require.Equal(t, int64(2), newPosts[0].ReplyCount) require.Equal(t, int64(2), newPosts[0].ReplyCount)
require.Equal(t, int64(2), newPosts[1].ReplyCount) require.Equal(t, int64(2), newPosts[1].ReplyCount)
require.Equal(t, int64(1), newPosts[2].ReplyCount) require.Equal(t, int64(2), newPosts[2].ReplyCount)
require.Equal(t, int64(0), newPosts[3].ReplyCount) require.Equal(t, int64(0), newPosts[3].ReplyCount)
return newPosts return newPosts
} }
t.Run("Save replies creates a thread", func(t *testing.T) { t.Run("Save replies creates a thread", func(t *testing.T) {
newPosts := makeSomePosts() newPosts := makeSomePosts()
thread, err := ss.Thread().Get(newPosts[0].RootId) thread, err := ss.Thread().Get(newPosts[0].Id)
require.Nil(t, err, "couldn't get thread") require.Nil(t, err, "couldn't get thread")
require.NotNil(t, thread) require.NotNil(t, thread)
require.Equal(t, int64(2), thread.ReplyCount) require.Equal(t, int64(2), thread.ReplyCount)
@@ -60,13 +92,13 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
o5 := model.Post{} o5 := model.Post{}
o5.ChannelId = model.NewId() o5.ChannelId = model.NewId()
o5.UserId = model.NewId() o5.UserId = model.NewId()
o5.RootId = newPosts[0].RootId o5.RootId = newPosts[0].Id
o5.Message = "zz" + model.NewId() + "b" o5.Message = "zz" + model.NewId() + "b"
_, _, err = ss.Post().SaveMultiple([]*model.Post{&o5}) _, _, err = ss.Post().SaveMultiple([]*model.Post{&o5})
require.Nil(t, err, "couldn't save item") require.Nil(t, err, "couldn't save item")
thread, err = ss.Thread().Get(newPosts[0].RootId) thread, err = ss.Thread().Get(newPosts[0].Id)
require.Nil(t, err, "couldn't get thread") require.Nil(t, err, "couldn't get thread")
require.NotNil(t, thread) require.NotNil(t, thread)
require.Equal(t, int64(3), thread.ReplyCount) require.Equal(t, int64(3), thread.ReplyCount)
@@ -75,7 +107,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
t.Run("Delete a reply updates count on a thread", func(t *testing.T) { t.Run("Delete a reply updates count on a thread", func(t *testing.T) {
newPosts := makeSomePosts() newPosts := makeSomePosts()
thread, err := ss.Thread().Get(newPosts[0].RootId) thread, err := ss.Thread().Get(newPosts[0].Id)
require.Nil(t, err, "couldn't get thread") require.Nil(t, err, "couldn't get thread")
require.NotNil(t, thread) require.NotNil(t, thread)
require.Equal(t, int64(2), thread.ReplyCount) require.Equal(t, int64(2), thread.ReplyCount)
@@ -84,7 +116,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
err = ss.Post().Delete(newPosts[1].Id, 1234, model.NewId()) err = ss.Post().Delete(newPosts[1].Id, 1234, model.NewId())
require.Nil(t, err, "couldn't delete post") require.Nil(t, err, "couldn't delete post")
thread, err = ss.Thread().Get(newPosts[0].RootId) thread, err = ss.Thread().Get(newPosts[0].Id)
require.Nil(t, err, "couldn't get thread") require.Nil(t, err, "couldn't get thread")
require.NotNil(t, thread) require.NotNil(t, thread)
require.Equal(t, int64(1), thread.ReplyCount) require.Equal(t, int64(1), thread.ReplyCount)
@@ -197,4 +229,84 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
thread2, _ := ss.Thread().Get(rootPost.Id) thread2, _ := ss.Thread().Get(rootPost.Id)
require.Nil(t, thread2) require.Nil(t, thread2)
}) })
t.Run("Thread last updated is changed when channel is updated after UpdateLastViewedAtPost", func(t *testing.T) {
newPosts := makeSomePosts()
require.Nil(t, ss.Thread().CreateMembershipIfNeeded(newPosts[0].UserId, newPosts[0].Id))
m, err1 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
require.Nil(t, err1)
m.LastUpdated -= 1000
_, err := ss.Thread().UpdateMembership(m)
require.Nil(t, err)
_, err = ss.Channel().UpdateLastViewedAtPost(newPosts[0], newPosts[0].UserId, 0, true)
require.Nil(t, err)
assert.Eventually(t, func() bool {
m2, err2 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
require.Nil(t, err2)
return m2.LastUpdated > m.LastUpdated
}, time.Second, 10*time.Millisecond)
})
t.Run("Thread last updated is changed when channel is updated after IncrementMentionCount", func(t *testing.T) {
newPosts := makeSomePosts()
require.Nil(t, ss.Thread().CreateMembershipIfNeeded(newPosts[0].UserId, newPosts[0].Id))
m, err1 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
require.Nil(t, err1)
m.LastUpdated -= 1000
_, err := ss.Thread().UpdateMembership(m)
require.Nil(t, err)
err = ss.Channel().IncrementMentionCount(newPosts[0].ChannelId, newPosts[0].UserId, true)
require.Nil(t, err)
assert.Eventually(t, func() bool {
m2, err2 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
require.Nil(t, err2)
return m2.LastUpdated > m.LastUpdated
}, time.Second, 10*time.Millisecond)
})
t.Run("Thread last updated is changed when channel is updated after UpdateLastViewedAt", func(t *testing.T) {
newPosts := makeSomePosts()
require.Nil(t, ss.Thread().CreateMembershipIfNeeded(newPosts[0].UserId, newPosts[0].Id))
m, err1 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
require.Nil(t, err1)
m.LastUpdated -= 1000
_, err := ss.Thread().UpdateMembership(m)
require.Nil(t, err)
_, err = ss.Channel().UpdateLastViewedAt([]string{newPosts[0].ChannelId}, newPosts[0].UserId, true)
require.Nil(t, err)
assert.Eventually(t, func() bool {
m2, err2 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
require.Nil(t, err2)
return m2.LastUpdated > m.LastUpdated
}, time.Second, 10*time.Millisecond)
})
t.Run("Thread last updated is changed when channel is updated after UpdateLastViewedAtPost for mark unread", func(t *testing.T) {
newPosts := makeSomePosts()
require.Nil(t, ss.Thread().CreateMembershipIfNeeded(newPosts[0].UserId, newPosts[0].Id))
m, err1 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
require.Nil(t, err1)
m.LastUpdated += 1000
_, err := ss.Thread().UpdateMembership(m)
require.Nil(t, err)
_, err = ss.Channel().UpdateLastViewedAtPost(newPosts[0], newPosts[0].UserId, 0, true)
require.Nil(t, err)
assert.Eventually(t, func() bool {
m2, err2 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
require.Nil(t, err2)
return m2.LastUpdated < m.LastUpdated
}, time.Second, 10*time.Millisecond)
})
} }

Просмотреть файл

@@ -2172,7 +2172,7 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
// Post one message with mention to open channel // Post one message with mention to open channel
_, nErr = ss.Post().Save(&p1) _, nErr = ss.Post().Save(&p1)
require.Nil(t, nErr) require.Nil(t, nErr)
nErr = ss.Channel().IncrementMentionCount(c1.Id, u2.Id) nErr = ss.Channel().IncrementMentionCount(c1.Id, u2.Id, false)
require.Nil(t, nErr) require.Nil(t, nErr)
// Post 2 messages without mention to direct channel // Post 2 messages without mention to direct channel
@@ -2183,7 +2183,7 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
_, nErr = ss.Post().Save(&p2) _, nErr = ss.Post().Save(&p2)
require.Nil(t, nErr) require.Nil(t, nErr)
nErr = ss.Channel().IncrementMentionCount(c2.Id, u2.Id) nErr = ss.Channel().IncrementMentionCount(c2.Id, u2.Id, false)
require.Nil(t, nErr) require.Nil(t, nErr)
p3 := model.Post{} p3 := model.Post{}
@@ -2193,7 +2193,7 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
_, nErr = ss.Post().Save(&p3) _, nErr = ss.Post().Save(&p3)
require.Nil(t, nErr) require.Nil(t, nErr)
nErr = ss.Channel().IncrementMentionCount(c2.Id, u2.Id) nErr = ss.Channel().IncrementMentionCount(c2.Id, u2.Id, false)
require.Nil(t, nErr) require.Nil(t, nErr)
badge, unreadCountErr := ss.User().GetUnreadCount(u2.Id) badge, unreadCountErr := ss.User().GetUnreadCount(u2.Id)

Просмотреть файл

@@ -1415,10 +1415,10 @@ func (s *TimerLayerChannelStore) GroupSyncedChannelCount() (int64, error) {
return result, err return result, err
} }
func (s *TimerLayerChannelStore) IncrementMentionCount(channelId string, userId string) error { func (s *TimerLayerChannelStore) IncrementMentionCount(channelId string, userId string, updateThreads bool) error {
start := timemodule.Now() start := timemodule.Now()
err := s.ChannelStore.IncrementMentionCount(channelId, userId) err := s.ChannelStore.IncrementMentionCount(channelId, userId, updateThreads)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil { if s.Root.Metrics != nil {
@@ -1920,10 +1920,10 @@ func (s *TimerLayerChannelStore) Update(channel *model.Channel) (*model.Channel,
return result, err return result, err
} }
func (s *TimerLayerChannelStore) UpdateLastViewedAt(channelIds []string, userId string) (map[string]int64, error) { func (s *TimerLayerChannelStore) UpdateLastViewedAt(channelIds []string, userId string, updateThreads bool) (map[string]int64, error) {
start := timemodule.Now() start := timemodule.Now()
result, err := s.ChannelStore.UpdateLastViewedAt(channelIds, userId) result, err := s.ChannelStore.UpdateLastViewedAt(channelIds, userId, updateThreads)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil { if s.Root.Metrics != nil {
@@ -1936,10 +1936,10 @@ func (s *TimerLayerChannelStore) UpdateLastViewedAt(channelIds []string, userId
return result, err return result, err
} }
func (s *TimerLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int) (*model.ChannelUnreadAt, error) { func (s *TimerLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, updateThreads bool) (*model.ChannelUnreadAt, error) {
start := timemodule.Now() start := timemodule.Now()
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount) result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, updateThreads)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil { if s.Root.Metrics != nil {
@@ -6872,6 +6872,22 @@ func (s *TimerLayerTermsOfServiceStore) Save(termsOfService *model.TermsOfServic
return result, err return result, err
} }
func (s *TimerLayerThreadStore) CollectThreadsWithNewerReplies(userId string, channelIds []string, timestamp int64) ([]string, error) {
start := timemodule.Now()
result, err := s.ThreadStore.CollectThreadsWithNewerReplies(userId, channelIds, timestamp)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ThreadStore.CollectThreadsWithNewerReplies", success, elapsed)
}
return result, err
}
func (s *TimerLayerThreadStore) CreateMembershipIfNeeded(userId string, postId string) error { func (s *TimerLayerThreadStore) CreateMembershipIfNeeded(userId string, postId string) error {
start := timemodule.Now() start := timemodule.Now()
@@ -7048,6 +7064,22 @@ func (s *TimerLayerThreadStore) UpdateMembership(membership *model.ThreadMembers
return result, err return result, err
} }
func (s *TimerLayerThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64) error {
start := timemodule.Now()
err := s.ThreadStore.UpdateUnreadsByChannel(userId, changedThreads, timestamp)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ThreadStore.UpdateUnreadsByChannel", success, elapsed)
}
return err
}
func (s *TimerLayerTokenStore) Cleanup() { func (s *TimerLayerTokenStore) Cleanup() {
start := timemodule.Now() start := timemodule.Now()

Просмотреть файл

@@ -55,6 +55,8 @@ func TestMfaRequired(t *testing.T) {
th.App.SetSession(&model.Session{Id: "abc", UserId: "userid"}) th.App.SetSession(&model.Session{Id: "abc", UserId: "userid"})
th.App.UpdateConfig(func(cfg *model.Config) { th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.AnnouncementSettings.UserNoticesEnabled = false
*cfg.AnnouncementSettings.AdminNoticesEnabled = false
*cfg.ServiceSettings.EnableMultifactorAuthentication = true *cfg.ServiceSettings.EnableMultifactorAuthentication = true
*cfg.ServiceSettings.EnforceMultifactorAuthentication = true *cfg.ServiceSettings.EnforceMultifactorAuthentication = true
}) })