From ba302cf512c4bf5ec799232671aeba931c70ca5c Mon Sep 17 00:00:00 2001 From: Eli Yukelzon Date: Tue, 27 Apr 2021 10:18:20 +0300 Subject: [PATCH] MM-35125 CRT: Mention badge on threads doesn't appear until refresh (#17504) --- app/notification.go | 10 +++++----- store/opentracinglayer/opentracinglayer.go | 18 +++++++++++++++++ store/retrylayer/retrylayer.go | 20 +++++++++++++++++++ store/sqlstore/thread_store.go | 15 ++++++++++++++ store/store.go | 2 ++ store/storetest/mocks/ThreadStore.go | 23 ++++++++++++++++++++++ store/timerlayer/timerlayer.go | 16 +++++++++++++++ 7 files changed, 99 insertions(+), 5 deletions(-) diff --git a/app/notification.go b/app/notification.go index 911614c6c7..5ef12a81cd 100644 --- a/app/notification.go +++ b/app/notification.go @@ -427,11 +427,11 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod // If this is a reply in a thread, notify participants if a.Config().FeatureFlags.CollapsedThreads && *a.Config().ServiceSettings.CollapsedThreads != model.COLLAPSED_THREADS_DISABLED && post.RootId != "" { - thread, err := a.Srv().Store.Thread().Get(post.RootId) + followers, err := a.Srv().Store.Thread().GetThreadFollowers(post.RootId) if err != nil { - return nil, errors.Wrapf(err, "cannot get thread %q", post.RootId) + return nil, errors.Wrapf(err, "cannot get thread %q followers", post.RootId) } - for _, uid := range thread.Participants { + for _, uid := range followers { sendEvent := *a.Config().ServiceSettings.CollapsedThreads == model.COLLAPSED_THREADS_DEFAULT_ON // check if a participant has overridden collapsed threads settings if preference, err := a.Srv().Store.Preference().Get(uid, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, model.PREFERENCE_NAME_COLLAPSED_THREADS_ENABLED); err == nil { @@ -439,9 +439,9 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod } if sendEvent { message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_THREAD_UPDATED, team.Id, "", uid, nil) - userThread, err := a.Srv().Store.Thread().GetThreadForUser(uid, channel.TeamId, thread.PostId, true) + userThread, err := a.Srv().Store.Thread().GetThreadForUser(uid, channel.TeamId, post.RootId, true) if err != nil { - return nil, errors.Wrapf(err, "cannot get thread %q for user %q", thread.PostId, uid) + return nil, errors.Wrapf(err, "cannot get thread %q for user %q", post.RootId, uid) } if userThread != nil { a.sanitizeProfiles(userThread.Participants, false) diff --git a/store/opentracinglayer/opentracinglayer.go b/store/opentracinglayer/opentracinglayer.go index 191bd86d20..cfea688307 100644 --- a/store/opentracinglayer/opentracinglayer.go +++ b/store/opentracinglayer/opentracinglayer.go @@ -8794,6 +8794,24 @@ func (s *OpenTracingLayerThreadStore) GetPosts(threadID string, since int64) ([] return result, err } +func (s *OpenTracingLayerThreadStore) GetThreadFollowers(threadID string) ([]string, error) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetThreadFollowers") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.ThreadStore.GetThreadFollowers(threadID) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + func (s *OpenTracingLayerThreadStore) GetThreadForUser(userID string, teamID string, threadId string, extended bool) (*model.ThreadResponse, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetThreadForUser") diff --git a/store/retrylayer/retrylayer.go b/store/retrylayer/retrylayer.go index b805bf71bb..227ddf63f4 100644 --- a/store/retrylayer/retrylayer.go +++ b/store/retrylayer/retrylayer.go @@ -9568,6 +9568,26 @@ func (s *RetryLayerThreadStore) GetPosts(threadID string, since int64) ([]*model } +func (s *RetryLayerThreadStore) GetThreadFollowers(threadID string) ([]string, error) { + + tries := 0 + for { + result, err := s.ThreadStore.GetThreadFollowers(threadID) + 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) GetThreadForUser(userID string, teamID string, threadId string, extended bool) (*model.ThreadResponse, error) { tries := 0 diff --git a/store/sqlstore/thread_store.go b/store/sqlstore/thread_store.go index cea2bfe871..2f5f4beeb3 100644 --- a/store/sqlstore/thread_store.go +++ b/store/sqlstore/thread_store.go @@ -307,6 +307,21 @@ func (s *SqlThreadStore) GetThreadsForUser(userId, teamId string, opts model.Get return result, nil } + +func (s *SqlThreadStore) GetThreadFollowers(threadID string) ([]string, error) { + var users []string + query, args, _ := s.getQueryBuilder(). + Select("ThreadMemberships.UserId"). + From("ThreadMemberships"). + Where(sq.Eq{"PostId": threadID}).ToSql() + _, err := s.GetReplica().Select(&users, query, args...) + + if err != nil { + return nil, err + } + return users, nil +} + func (s *SqlThreadStore) GetThreadForUser(userId, teamId, threadId string, extended bool) (*model.ThreadResponse, error) { type JoinedThread struct { PostId string diff --git a/store/store.go b/store/store.go index 109c16184a..0729c5345e 100644 --- a/store/store.go +++ b/store/store.go @@ -272,6 +272,8 @@ type ChannelMemberHistoryStore interface { PermanentDeleteBatch(endTime int64, limit int64) (int64, error) } type ThreadStore interface { + GetThreadFollowers(threadID string) ([]string, error) + SaveMultiple(thread []*model.Thread) ([]*model.Thread, int, error) Save(thread *model.Thread) (*model.Thread, error) Update(thread *model.Thread) (*model.Thread, error) diff --git a/store/storetest/mocks/ThreadStore.go b/store/storetest/mocks/ThreadStore.go index e32b7a08aa..36b5d654b5 100644 --- a/store/storetest/mocks/ThreadStore.go +++ b/store/storetest/mocks/ThreadStore.go @@ -157,6 +157,29 @@ func (_m *ThreadStore) GetPosts(threadID string, since int64) ([]*model.Post, er return r0, r1 } +// GetThreadFollowers provides a mock function with given fields: threadID +func (_m *ThreadStore) GetThreadFollowers(threadID string) ([]string, error) { + ret := _m.Called(threadID) + + var r0 []string + if rf, ok := ret.Get(0).(func(string) []string); ok { + r0 = rf(threadID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(threadID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetThreadForUser provides a mock function with given fields: userID, teamID, threadId, extended func (_m *ThreadStore) GetThreadForUser(userID string, teamID string, threadId string, extended bool) (*model.ThreadResponse, error) { ret := _m.Called(userID, teamID, threadId, extended) diff --git a/store/timerlayer/timerlayer.go b/store/timerlayer/timerlayer.go index 715e90484f..8b84cc4986 100644 --- a/store/timerlayer/timerlayer.go +++ b/store/timerlayer/timerlayer.go @@ -7926,6 +7926,22 @@ func (s *TimerLayerThreadStore) GetPosts(threadID string, since int64) ([]*model return result, err } +func (s *TimerLayerThreadStore) GetThreadFollowers(threadID string) ([]string, error) { + start := timemodule.Now() + + result, err := s.ThreadStore.GetThreadFollowers(threadID) + + 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.GetThreadFollowers", success, elapsed) + } + return result, err +} + func (s *TimerLayerThreadStore) GetThreadForUser(userID string, teamID string, threadId string, extended bool) (*model.ThreadResponse, error) { start := timemodule.Now()