MM-35298: Follow thread when added to channel (#19311)

* MM-35298: Follow thread when added to channel

* return better error if thread doesn't exist

* update test for possible race

* use correct comparision operator
Этот коммит содержится в:
Ashish Bhate
2022-01-26 01:14:18 +05:30
коммит произвёл GitHub
родитель e2e049e05e
Коммит 7026818f80
6 изменённых файлов: 149 добавлений и 0 удалений

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

@@ -1698,6 +1698,14 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if postRootId != "" {
err := c.App.UpdateThreadFollowForUserFromChannelAdd(cm.UserId, channel.TeamId, postRootId)
if err != nil {
c.Err = err
return
}
}
auditRec.Success()
auditRec.AddMeta("add_user_id", cm.UserId)
c.LogAudit("name=" + channel.Name + " user_id=" + cm.UserId)

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

@@ -3012,6 +3012,63 @@ func TestAddChannelMember(t *testing.T) {
})
}
func TestAddChannelMemberFromThread(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
team := th.BasicTeam
user := th.BasicUser
user2 := th.BasicUser2
user3 := th.CreateUserWithClient(th.SystemAdminClient)
_, _, err := th.SystemAdminClient.AddTeamMember(team.Id, user3.Id)
require.NoError(t, err)
publicChannel := th.CreatePublicChannel()
_, resp, err := th.Client.AddChannelMember(publicChannel.Id, user.Id)
require.NoError(t, err)
CheckCreatedStatus(t, resp)
_, resp, err = th.Client.AddChannelMember(publicChannel.Id, user2.Id)
require.NoError(t, err)
CheckCreatedStatus(t, resp)
post := &model.Post{
ChannelId: publicChannel.Id,
Message: "A root post",
UserId: user.Id,
}
rpost, _, err := th.SystemAdminClient.CreatePost(post)
require.NoError(t, err)
_, _, err = th.SystemAdminClient.CreatePost(
&model.Post{
ChannelId: publicChannel.Id,
Message: "A reply post with mention @" + user3.Username,
UserId: user2.Id,
RootId: rpost.Id,
})
require.NoError(t, err)
_, _, err = th.SystemAdminClient.CreatePost(
&model.Post{
ChannelId: publicChannel.Id,
Message: "Another reply post with mention @" + user3.Username,
UserId: user2.Id,
RootId: rpost.Id,
})
require.NoError(t, err)
// Simulate adding a user to a channel from a thread
_, _, err = th.SystemAdminClient.AddChannelMemberWithRootId(publicChannel.Id, user3.Id, rpost.Id)
require.NoError(t, err)
// Threadmembership should exist for added user
ut, _, err := th.SystemAdminClient.GetUserThread(user3.Id, team.Id, rpost.Id, false)
require.NoError(t, err)
// Should have two mentions. There might be a race condition
// here between the "added user to the channel" message and the GetUserThread call
require.LessOrEqual(t, int64(2), ut.UnreadMentions)
}
func TestAddChannelMemberAddMyself(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()

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

@@ -1063,6 +1063,7 @@ type AppIface interface {
UpdateTeamPrivacy(teamID string, teamType string, allowOpenInvite bool) *model.AppError
UpdateTeamScheme(team *model.Team) (*model.Team, *model.AppError)
UpdateThreadFollowForUser(userID, teamID, threadID string, state bool) *model.AppError
UpdateThreadFollowForUserFromChannelAdd(userID, teamID, threadID string) *model.AppError
UpdateThreadReadForUser(currentSessionId, userID, teamID, threadID string, timestamp int64) (*model.ThreadResponse, *model.AppError)
UpdateThreadsReadForUser(userID, teamID string) *model.AppError
UpdateUser(user *model.User, sendNotifications bool) (*model.User, *model.AppError)

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

@@ -16634,6 +16634,28 @@ func (a *OpenTracingAppLayer) UpdateThreadFollowForUser(userID string, teamID st
return resultVar0
}
func (a *OpenTracingAppLayer) UpdateThreadFollowForUserFromChannelAdd(userID string, teamID string, threadID string) *model.AppError {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateThreadFollowForUserFromChannelAdd")
a.ctx = newCtx
a.app.Srv().Store.SetContext(newCtx)
defer func() {
a.app.Srv().Store.SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0 := a.app.UpdateThreadFollowForUserFromChannelAdd(userID, teamID, threadID)
if resultVar0 != nil {
span.LogFields(spanlog.Error(resultVar0))
ext.Error.Set(span, true)
}
return resultVar0
}
func (a *OpenTracingAppLayer) UpdateThreadReadForUser(currentSessionId string, userID string, teamID string, threadID string, timestamp int64) (*model.ThreadResponse, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateThreadReadForUser")

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

@@ -2301,6 +2301,64 @@ func (a *App) UpdateThreadFollowForUser(userID, teamID, threadID string, state b
return nil
}
func (a *App) UpdateThreadFollowForUserFromChannelAdd(userID, teamID, threadID string) *model.AppError {
opts := store.ThreadMembershipOpts{
Following: true,
IncrementMentions: false,
UpdateFollowing: true,
UpdateViewedTimestamp: false,
UpdateParticipants: false,
}
tm, err := a.Srv().Store.Thread().MaintainMembership(userID, threadID, opts)
if err != nil {
return model.NewAppError("UpdateThreadFollowForUserFromChannelAdd", "app.user.update_thread_follow_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
}
post, appErr := a.GetSinglePost(threadID)
if appErr != nil {
return appErr
}
user, appErr := a.GetUser(userID)
if appErr != nil {
return appErr
}
tm.UnreadMentions, appErr = a.countThreadMentions(user, post, teamID, post.CreateAt-1)
if appErr != nil {
return appErr
}
tm.LastViewed = post.CreateAt - 1
_, err = a.Srv().Store.Thread().UpdateMembership(tm)
if err != nil {
return model.NewAppError("UpdateThreadFollowForUserFromChannelAdd", "app.user.update_thread_follow_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
}
message := model.NewWebSocketEvent(model.WebsocketEventThreadUpdated, teamID, "", userID, nil)
userThread, err := a.Srv().Store.Thread().GetThreadForUser(teamID, tm, true)
if err != nil {
var errNotFound *store.ErrNotFound
if errors.As(err, &errNotFound) {
return nil
}
return model.NewAppError("UpdateThreadFollowForUserFromChannelAdd", "app.user.update_thread_follow_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
}
a.sanitizeProfiles(userThread.Participants, false)
userThread.Post.SanitizeProps()
sanitizedPost, appErr := a.SanitizePostMetadataForUser(userThread.Post, userID)
if appErr != nil {
return appErr
}
userThread.Post = sanitizedPost
payload, jsonErr := json.Marshal(userThread)
if jsonErr != nil {
mlog.Warn("Failed to encode thread to JSON")
}
message.Add("thread", string(payload))
a.Publish(message)
return nil
}
func (a *App) UpdateThreadReadForUser(currentSessionId, userID, teamID, threadID string, timestamp int64) (*model.ThreadResponse, *model.AppError) {
user, err := a.GetUser(userID)
if err != nil {

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

@@ -425,6 +425,9 @@ func (s *SqlThreadStore) GetThreadForUser(teamId string, threadMembership *model
err := s.GetReplica().SelectOne(&thread, query, args...)
if err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("Thread", threadMembership.PostId)
}
return nil, err
}