MM-46410: adds urgency on mention counts (#20999)
* MM-46410: adds urgency on mention counts We have introduced priority for posts in https://github.com/mattermost/mattermost-webapp/pull/10951. We do need to color the mention badges in the webapp with a prominent color when a mention is posted in an urgent message. A thread has urgent mentions if the root post is marked as urgent, and the replies contain mentions to the user viewing the thread. This PR adds a column, urgentmentioncount, in channelmembers. Furthermore when asking for team/thread mention counts, we also return urgent mention counts for the user. Adds a new table to hold posts priorities Refactors priority out of the props and into the new table We are nilifying Metadata when post.ForPlugin(), which didn't save Priority for a post when Boards was enabled. This commit copies metadata again to the post, so metadata are reinstated. Co-authored-by: Mattermod <mattermod@users.noreply.github.com> Co-authored-by: Vishal Choudhary <vish9812@gmail.com>
Этот коммит содержится в:
@@ -141,7 +141,7 @@ func createEphemeralPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
rp = model.AddPostActionCookies(rp, c.App.PostActionCookieSecret())
|
||||
rp = c.App.PreparePostForClientWithEmbedsAndImages(c.AppContext, rp, true, false)
|
||||
rp = c.App.PreparePostForClientWithEmbedsAndImages(c.AppContext, rp, true, false, true)
|
||||
rp, err := c.App.SanitizePostMetadataForUser(c.AppContext, rp, c.AppContext.Session().UserId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
@@ -420,7 +420,7 @@ func getPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
post = c.App.PreparePostForClientWithEmbedsAndImages(c.AppContext, post, false, false)
|
||||
post = c.App.PreparePostForClientWithEmbedsAndImages(c.AppContext, post, false, false, true)
|
||||
post, err = c.App.SanitizePostMetadataForUser(c.AppContext, post, c.AppContext.Session().UserId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
@@ -479,7 +479,7 @@ func getPostsByIds(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
post = c.App.PreparePostForClient(c.AppContext, post, false, false)
|
||||
post = c.App.PreparePostForClient(c.AppContext, post, false, false, true)
|
||||
post.StripActionIntegrations()
|
||||
posts = append(posts, post)
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ func TestGraphQLChannelMembers(t *testing.T) {
|
||||
MsgCount float64 `json:"msgCount"`
|
||||
MentionCount float64 `json:"mentionCount"`
|
||||
MentionCountRoot float64 `json:"mentionCountRoot"`
|
||||
UrgentMentionCount float64 `json:"urgentMentionCount"`
|
||||
MsgCountRoot float64 `json:"msgCountRoot"`
|
||||
NotifyProps model.StringMap `json:"notifyProps"`
|
||||
SchemeGuest bool `json:"schemeGuest"`
|
||||
@@ -101,6 +102,7 @@ func TestGraphQLChannelMembers(t *testing.T) {
|
||||
msgCount
|
||||
mentionCount
|
||||
mentionCountRoot
|
||||
urgentMentionCount
|
||||
msgCountRoot
|
||||
schemeGuest
|
||||
schemeUser
|
||||
@@ -181,6 +183,7 @@ func TestGraphQLChannelMembers(t *testing.T) {
|
||||
msgCount
|
||||
mentionCount
|
||||
mentionCountRoot
|
||||
urgentMentionCount
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -75,6 +75,7 @@ type ChannelMember {
|
||||
lastViewedAt : Float!
|
||||
msgCount : Float!
|
||||
mentionCount : Float!
|
||||
urgentMentionCount: Float!
|
||||
mentionCountRoot : Float!
|
||||
msgCountRoot : Float!
|
||||
notifyProps : StringMap!
|
||||
|
||||
@@ -5720,10 +5720,12 @@ func TestUpdatePassword(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetThreadsForUser(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
os.Setenv("MM_FEATUREFLAGS_POSTPRIORITY", "true")
|
||||
defer os.Unsetenv("MM_FEATUREFLAGS_POSTPRIORITY")
|
||||
os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true")
|
||||
defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS")
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.ThreadAutoFollow = true
|
||||
@@ -5820,7 +5822,49 @@ func TestGetThreadsForUser(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, uss.Threads, 1)
|
||||
require.Greater(t, uss.Threads[0].Post.DeleteAt, int64(0))
|
||||
})
|
||||
|
||||
t.Run("isUrgent, 1 thread", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
featureEnabled bool
|
||||
expected bool
|
||||
}{
|
||||
{featureEnabled: true, expected: true},
|
||||
{featureEnabled: false, expected: false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostPriority = tc.featureEnabled
|
||||
cfg.FeatureFlags.PostPriority = true
|
||||
})
|
||||
|
||||
client := th.Client
|
||||
|
||||
rpost, resp, err := client.CreatePost(&model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: "testMsg",
|
||||
Metadata: &model.PostMetadata{
|
||||
Priority: &model.PostPriority{
|
||||
Priority: model.NewString(model.PostPriorityUrgent),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
CheckCreatedStatus(t, resp)
|
||||
_, resp, err = client.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply", RootId: rpost.Id})
|
||||
require.NoError(t, err)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
defer th.App.Srv().Store().Post().PermanentDeleteByUser(th.BasicUser.Id)
|
||||
|
||||
uss, _, err := th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, uss.Threads, 1)
|
||||
require.Equal(t, uss.Threads[0].IsUrgent, tc.expected)
|
||||
}()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("paged, 30 threads", func(t *testing.T) {
|
||||
@@ -6515,13 +6559,19 @@ func TestThreadCounts(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSingleThreadGet(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
os.Setenv("MM_FEATUREFLAGS_POSTPRIORITY", "true")
|
||||
defer os.Unsetenv("MM_FEATUREFLAGS_POSTPRIORITY")
|
||||
os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true")
|
||||
defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS")
|
||||
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.ThreadAutoFollow = true
|
||||
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
||||
*cfg.ServiceSettings.PostPriority = true
|
||||
cfg.FeatureFlags.PostPriority = true
|
||||
})
|
||||
|
||||
client := th.Client
|
||||
@@ -6534,7 +6584,15 @@ func TestSingleThreadGet(t *testing.T) {
|
||||
postAndCheck(t, th.SystemAdminClient, &model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply", RootId: rpost.Id})
|
||||
|
||||
// create another thread to check that we are not returning it by mistake
|
||||
rpost2, _ := postAndCheck(t, client, &model.Post{ChannelId: th.BasicChannel2.Id, Message: "testMsg2"})
|
||||
rpost2, _ := postAndCheck(t, client, &model.Post{
|
||||
ChannelId: th.BasicChannel2.Id,
|
||||
Message: "testMsg2",
|
||||
Metadata: &model.PostMetadata{
|
||||
Priority: &model.PostPriority{
|
||||
Priority: model.NewString(model.PostPriorityUrgent),
|
||||
},
|
||||
},
|
||||
})
|
||||
postAndCheck(t, th.SystemAdminClient, &model.Post{ChannelId: th.BasicChannel2.Id, Message: "testReply", RootId: rpost2.Id})
|
||||
|
||||
// regular user should have two threads with 3 replies total
|
||||
@@ -6546,9 +6604,22 @@ func TestSingleThreadGet(t *testing.T) {
|
||||
require.Equal(t, threads.Threads[0].PostId, tr.PostId)
|
||||
require.Empty(t, tr.Participants[0].Username)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostPriority = false
|
||||
})
|
||||
|
||||
tr, _, err = th.Client.GetUserThread(th.BasicUser.Id, th.BasicTeam.Id, threads.Threads[0].PostId, true)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, tr.Participants[0].Username)
|
||||
require.Equal(t, false, tr.IsUrgent)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostPriority = true
|
||||
})
|
||||
|
||||
tr, _, err = th.Client.GetUserThread(th.BasicUser.Id, th.BasicTeam.Id, threads.Threads[0].PostId, true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, tr.IsUrgent)
|
||||
}
|
||||
|
||||
func TestMaintainUnreadMentionsInThread(t *testing.T) {
|
||||
|
||||
@@ -716,6 +716,8 @@ type AppIface interface {
|
||||
GetPreferenceByCategoryForUser(userID string, category string) (model.Preferences, *model.AppError)
|
||||
GetPreferencesForUser(userID string) (model.Preferences, *model.AppError)
|
||||
GetPrevPostIdFromPostList(postList *model.PostList, collapsedThreads bool) string
|
||||
GetPriorityForPost(postId string) (*model.PostPriority, *model.AppError)
|
||||
GetPriorityForPostList(list *model.PostList) (map[string]*model.PostPriority, *model.AppError)
|
||||
GetPrivateChannelsForTeam(c request.CTX, teamID string, offset int, limit int) (model.ChannelList, *model.AppError)
|
||||
GetProfileImage(user *model.User) ([]byte, bool, *model.AppError)
|
||||
GetPublicChannelsByIdsForTeam(c request.CTX, teamID string, channelIDs []string) (model.ChannelList, *model.AppError)
|
||||
@@ -927,8 +929,8 @@ type AppIface interface {
|
||||
PostUpdateChannelPurposeMessage(c request.CTX, userID string, channel *model.Channel, oldChannelPurpose string, newChannelPurpose string) *model.AppError
|
||||
PostWithProxyAddedToImageURLs(post *model.Post) *model.Post
|
||||
PostWithProxyRemovedFromImageURLs(post *model.Post) *model.Post
|
||||
PreparePostForClient(c request.CTX, originalPost *model.Post, isNewPost, isEditPost bool) *model.Post
|
||||
PreparePostForClientWithEmbedsAndImages(c request.CTX, originalPost *model.Post, isNewPost, isEditPost bool) *model.Post
|
||||
PreparePostForClient(c request.CTX, originalPost *model.Post, isNewPost, isEditPost, includePriority bool) *model.Post
|
||||
PreparePostForClientWithEmbedsAndImages(c request.CTX, originalPost *model.Post, isNewPost, isEditPost, includePriority bool) *model.Post
|
||||
PreparePostListForClient(c request.CTX, originalList *model.PostList) *model.PostList
|
||||
ProcessSlackText(text string) string
|
||||
Publish(message *model.WebSocketEvent)
|
||||
|
||||
@@ -2609,12 +2609,12 @@ func (a *App) MarkChannelAsUnreadFromPost(c request.CTX, postID string, userID s
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unreadMentions, unreadMentionsRoot, err := a.countMentionsFromPost(c, user, post)
|
||||
unreadMentions, unreadMentionsRoot, urgentMentions, err := a.countMentionsFromPost(c, user, post)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channelUnread, nErr := a.Srv().Store().Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, unreadMentionsRoot, true)
|
||||
channelUnread, nErr := a.Srv().Store().Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, unreadMentionsRoot, urgentMentions, true)
|
||||
if nErr != nil {
|
||||
return channelUnread, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
|
||||
}
|
||||
@@ -2641,7 +2641,7 @@ func (a *App) markChannelAsUnreadFromPostCRTUnsupported(c request.CTX, postID st
|
||||
threadId = post.Id
|
||||
}
|
||||
|
||||
unreadMentions, unreadMentionsRoot, appErr := a.countMentionsFromPost(c, user, post)
|
||||
unreadMentions, unreadMentionsRoot, urgentMentions, appErr := a.countMentionsFromPost(c, user, post)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
@@ -2650,7 +2650,7 @@ func (a *App) markChannelAsUnreadFromPostCRTUnsupported(c request.CTX, postID st
|
||||
// In CRT Supported Client: badge on channel only sums mentions in root posts including and below the post that was marked.
|
||||
// In CRT Unsupported Client: badge on channel sums mentions in all posts (root & replies) including and below the post that was marked unread.
|
||||
if post.RootId == "" {
|
||||
channelUnread, nErr := a.Srv().Store().Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, unreadMentionsRoot, true)
|
||||
channelUnread, nErr := a.Srv().Store().Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, unreadMentionsRoot, urgentMentions, true)
|
||||
if nErr != nil {
|
||||
return channelUnread, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
|
||||
}
|
||||
@@ -2706,7 +2706,7 @@ func (a *App) markChannelAsUnreadFromPostCRTUnsupported(c request.CTX, postID st
|
||||
if mErr != nil {
|
||||
return nil, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, "", http.StatusInternalServerError).Wrap(mErr)
|
||||
}
|
||||
thread, mErr := a.Srv().Store().Thread().GetThreadForUser(threadMembership, true)
|
||||
thread, mErr := a.Srv().Store().Thread().GetThreadForUser(threadMembership, true, a.isPostPriorityEnabled())
|
||||
if mErr != nil {
|
||||
return nil, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, "", http.StatusInternalServerError).Wrap(mErr)
|
||||
}
|
||||
@@ -2724,7 +2724,7 @@ func (a *App) markChannelAsUnreadFromPostCRTUnsupported(c request.CTX, postID st
|
||||
}
|
||||
}
|
||||
|
||||
channelUnread, nErr := a.Srv().Store().Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, 0, false)
|
||||
channelUnread, nErr := a.Srv().Store().Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, 0, 0, false)
|
||||
if nErr != nil {
|
||||
return channelUnread, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
|
||||
}
|
||||
@@ -2741,6 +2741,7 @@ func (a *App) sendWebSocketPostUnreadEvent(c request.CTX, channelUnread *model.C
|
||||
}
|
||||
message.Add("mention_count", channelUnread.MentionCount)
|
||||
message.Add("mention_count_root", channelUnread.MentionCountRoot)
|
||||
message.Add("urgent_mention_count", channelUnread.UrgentMentionCount)
|
||||
message.Add("last_viewed_at", channelUnread.LastViewedAt)
|
||||
message.Add("post_id", postID)
|
||||
a.Publish(message)
|
||||
|
||||
@@ -305,7 +305,8 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
|
||||
mentionedUsersList = append(mentionedUsersList, id)
|
||||
}
|
||||
|
||||
nErr := a.Srv().Store().Channel().IncrementMentionCount(post.ChannelId, mentionedUsersList, post.RootId == "")
|
||||
nErr := a.Srv().Store().Channel().IncrementMentionCount(post.ChannelId, mentionedUsersList, post.RootId == "", post.IsUrgent())
|
||||
|
||||
if nErr != nil {
|
||||
mlog.Warn(
|
||||
"Failed to update mention count",
|
||||
@@ -596,7 +597,7 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
|
||||
}
|
||||
threadMembership = tm
|
||||
}
|
||||
userThread, err := a.Srv().Store().Thread().GetThreadForUser(threadMembership, true)
|
||||
userThread, err := a.Srv().Store().Thread().GetThreadForUser(threadMembership, true, a.isPostPriorityEnabled())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot get thread %q for user %q", post.RootId, uid)
|
||||
}
|
||||
|
||||
@@ -8118,6 +8118,50 @@ func (a *OpenTracingAppLayer) GetPrevPostIdFromPostList(postList *model.PostList
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetPriorityForPost(postId string) (*model.PostPriority, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetPriorityForPost")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetPriorityForPost(postId)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetPriorityForPostList(list *model.PostList) (map[string]*model.PostPriority, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetPriorityForPostList")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetPriorityForPostList(list)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetPrivateChannelsForTeam(c request.CTX, teamID string, offset int, limit int) (model.ChannelList, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetPrivateChannelsForTeam")
|
||||
@@ -13023,7 +13067,7 @@ func (a *OpenTracingAppLayer) PostWithProxyRemovedFromImageURLs(post *model.Post
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) PreparePostForClient(c request.CTX, originalPost *model.Post, isNewPost bool, isEditPost bool) *model.Post {
|
||||
func (a *OpenTracingAppLayer) PreparePostForClient(c request.CTX, originalPost *model.Post, isNewPost bool, isEditPost bool, includePriority bool) *model.Post {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PreparePostForClient")
|
||||
|
||||
@@ -13035,12 +13079,12 @@ func (a *OpenTracingAppLayer) PreparePostForClient(c request.CTX, originalPost *
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.PreparePostForClient(c, originalPost, isNewPost, isEditPost)
|
||||
resultVar0 := a.app.PreparePostForClient(c, originalPost, isNewPost, isEditPost, includePriority)
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) PreparePostForClientWithEmbedsAndImages(c request.CTX, originalPost *model.Post, isNewPost bool, isEditPost bool) *model.Post {
|
||||
func (a *OpenTracingAppLayer) PreparePostForClientWithEmbedsAndImages(c request.CTX, originalPost *model.Post, isNewPost bool, isEditPost bool, includePriority bool) *model.Post {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PreparePostForClientWithEmbedsAndImages")
|
||||
|
||||
@@ -13052,7 +13096,7 @@ func (a *OpenTracingAppLayer) PreparePostForClientWithEmbedsAndImages(c request.
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.PreparePostForClientWithEmbedsAndImages(c, originalPost, isNewPost, isEditPost)
|
||||
resultVar0 := a.app.PreparePostForClientWithEmbedsAndImages(c, originalPost, isNewPost, isEditPost, includePriority)
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
84
app/post.go
84
app/post.go
@@ -259,7 +259,15 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
|
||||
}
|
||||
}
|
||||
|
||||
if !a.isPostPriorityEnabled() && post.GetPriority() != nil {
|
||||
post.Metadata.Priority = nil
|
||||
}
|
||||
|
||||
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
||||
var metadata *model.PostMetadata
|
||||
if post.Metadata != nil {
|
||||
metadata = post.Metadata.Copy()
|
||||
}
|
||||
var rejectionError *model.AppError
|
||||
pluginContext := pluginContext(c)
|
||||
pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
|
||||
@@ -273,8 +281,12 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
|
||||
return false
|
||||
}
|
||||
if replacementPost != nil {
|
||||
// the original post's metadata (if there ever was any) is lost, and will be rebuilt.
|
||||
post = replacementPost
|
||||
if post.Metadata != nil && metadata != nil {
|
||||
post.Metadata.Priority = metadata.Priority
|
||||
} else {
|
||||
post.Metadata = metadata
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -343,7 +355,9 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
|
||||
|
||||
// Normally, we would let the API layer call PreparePostForClient, but we do it here since it also needs
|
||||
// to be done when we send the post over the websocket in handlePostEvents
|
||||
rpost = a.PreparePostForClient(c, rpost, true, false)
|
||||
// PS: we don't want to include PostPriority from the db to avoid the replica lag,
|
||||
// so we just return the one that was passed with post
|
||||
rpost = a.PreparePostForClient(c, rpost, true, false, false)
|
||||
|
||||
// Make sure poster is following the thread
|
||||
if *a.Config().ServiceSettings.ThreadAutoFollow && rpost.RootId != "" {
|
||||
@@ -515,7 +529,7 @@ func (a *App) SendEphemeralPost(c request.CTX, userID string, post *model.Post)
|
||||
|
||||
post.GenerateActionIds()
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventEphemeralMessage, "", post.ChannelId, userID, nil, "")
|
||||
post = a.PreparePostForClientWithEmbedsAndImages(c, post, true, false)
|
||||
post = a.PreparePostForClientWithEmbedsAndImages(c, post, true, false, true)
|
||||
post = model.AddPostActionCookies(post, a.PostActionCookieSecret())
|
||||
|
||||
postJSON, jsonErr := post.ToJSON()
|
||||
@@ -538,7 +552,7 @@ func (a *App) UpdateEphemeralPost(c request.CTX, userID string, post *model.Post
|
||||
|
||||
post.GenerateActionIds()
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventPostEdited, "", post.ChannelId, userID, nil, "")
|
||||
post = a.PreparePostForClientWithEmbedsAndImages(c, post, true, false)
|
||||
post = a.PreparePostForClientWithEmbedsAndImages(c, post, true, false, true)
|
||||
post = model.AddPostActionCookies(post, a.PostActionCookieSecret())
|
||||
postJSON, jsonErr := post.ToJSON()
|
||||
if jsonErr != nil {
|
||||
@@ -682,7 +696,7 @@ func (a *App) UpdatePost(c *request.Context, post *model.Post, safeUpdate bool)
|
||||
})
|
||||
}
|
||||
|
||||
rpost = a.PreparePostForClientWithEmbedsAndImages(c, rpost, false, true)
|
||||
rpost = a.PreparePostForClientWithEmbedsAndImages(c, rpost, false, true, true)
|
||||
|
||||
// Ensure IsFollowing is nil since this updated post will be broadcast to all users
|
||||
// and we don't want to have to populate it for every single user and broadcast to each
|
||||
@@ -1705,7 +1719,7 @@ func (a *App) countThreadMentions(c request.CTX, user *model.User, post *model.P
|
||||
|
||||
posts, nErr := a.Srv().Store().Post().GetPostsByThread(post.Id, timestamp)
|
||||
if nErr != nil {
|
||||
return 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
|
||||
return 0, model.NewAppError("countThreadMentions", "app.channel.count_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
|
||||
}
|
||||
|
||||
count := 0
|
||||
@@ -1732,7 +1746,7 @@ func (a *App) countThreadMentions(c request.CTX, user *model.User, post *model.P
|
||||
|
||||
groups, nErr := a.getGroupsAllowedForReferenceInChannel(channel, team)
|
||||
if nErr != nil {
|
||||
return 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
|
||||
return 0, model.NewAppError("countThreadMentions", "app.channel.count_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
|
||||
}
|
||||
|
||||
for _, p := range posts {
|
||||
@@ -1749,25 +1763,33 @@ func (a *App) countThreadMentions(c request.CTX, user *model.User, post *model.P
|
||||
|
||||
// countMentionsFromPost returns the number of posts in the post's channel that mention the user after and including the
|
||||
// given post.
|
||||
func (a *App) countMentionsFromPost(c request.CTX, user *model.User, post *model.Post) (int, int, *model.AppError) {
|
||||
func (a *App) countMentionsFromPost(c request.CTX, user *model.User, post *model.Post) (int, int, int, *model.AppError) {
|
||||
channel, err := a.GetChannel(c, post.ChannelId)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
if channel.Type == model.ChannelTypeDirect {
|
||||
// In a DM channel, every post made by the other user is a mention
|
||||
count, countRoot, nErr := a.Srv().Store().Channel().CountPostsAfter(post.ChannelId, post.CreateAt-1, channel.GetOtherUserIdForDM(user.Id))
|
||||
if nErr != nil {
|
||||
return 0, 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
|
||||
return 0, 0, 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
|
||||
}
|
||||
|
||||
return count, countRoot, nil
|
||||
var urgentCount int
|
||||
if a.isPostPriorityEnabled() {
|
||||
urgentCount, nErr = a.Srv().Store().Channel().CountUrgentPostsAfter(post.ChannelId, post.CreateAt-1, channel.GetOtherUserIdForDM(user.Id))
|
||||
if nErr != nil {
|
||||
return 0, 0, 0, model.NewAppError("countMentionsFromPost", "app.channel.count_urgent_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
|
||||
}
|
||||
}
|
||||
|
||||
return count, countRoot, urgentCount, nil
|
||||
}
|
||||
|
||||
channelMember, err := a.GetChannelMember(c, channel.Id, user.Id)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
keywords := addMentionKeywordsForUser(
|
||||
@@ -1785,15 +1807,25 @@ func (a *App) countMentionsFromPost(c request.CTX, user *model.User, post *model
|
||||
|
||||
thread, err := a.GetPostThread(post.Id, model.GetPostsOptions{}, user.Id)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
count := 0
|
||||
countRoot := 0
|
||||
urgentCount := 0
|
||||
if isPostMention(user, post, keywords, thread.Posts, mentionedByThread, checkForCommentMentions) {
|
||||
count += 1
|
||||
if post.RootId == "" {
|
||||
countRoot += 1
|
||||
if a.isPostPriorityEnabled() {
|
||||
priority, err := a.GetPriorityForPost(post.Id)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
if priority != nil && *priority.Priority == model.PostPriorityUrgent {
|
||||
urgentCount += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1807,18 +1839,32 @@ func (a *App) countMentionsFromPost(c request.CTX, user *model.User, post *model
|
||||
PerPage: perPage,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
mentionPostIds := make([]string, 0)
|
||||
for _, postID := range postList.Order {
|
||||
if isPostMention(user, postList.Posts[postID], keywords, postList.Posts, mentionedByThread, checkForCommentMentions) {
|
||||
count += 1
|
||||
if postList.Posts[postID].RootId == "" {
|
||||
mentionPostIds = append(mentionPostIds, postID)
|
||||
countRoot += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if a.isPostPriorityEnabled() {
|
||||
priorityList, nErr := a.Srv().Store().PostPriority().GetForPosts(mentionPostIds)
|
||||
if err != nil {
|
||||
return 0, 0, 0, model.NewAppError("countMentionsFromPost", "app.channel.get_priority_for_posts.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
|
||||
}
|
||||
for _, priority := range priorityList {
|
||||
if *priority.Priority == model.PostPriorityUrgent {
|
||||
urgentCount += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(postList.Order) < perPage {
|
||||
break
|
||||
}
|
||||
@@ -1826,7 +1872,7 @@ func (a *App) countMentionsFromPost(c request.CTX, user *model.User, post *model
|
||||
page += 1
|
||||
}
|
||||
|
||||
return count, countRoot, nil
|
||||
return count, countRoot, urgentCount, nil
|
||||
}
|
||||
|
||||
func isCommentMention(user *model.User, post *model.Post, otherPosts map[string]*model.Post, mentionedByThread map[string]bool) bool {
|
||||
@@ -2025,7 +2071,7 @@ func (a *App) SetPostReminder(postID, userID string, targetTime int64) *model.Ap
|
||||
}
|
||||
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventEphemeralMessage, "", ephemeralPost.ChannelId, userID, nil, "")
|
||||
ephemeralPost = a.PreparePostForClientWithEmbedsAndImages(request.EmptyContext(a.Log()), ephemeralPost, true, false)
|
||||
ephemeralPost = a.PreparePostForClientWithEmbedsAndImages(request.EmptyContext(a.Log()), ephemeralPost, true, false, true)
|
||||
ephemeralPost = model.AddPostActionCookies(ephemeralPost, a.PostActionCookieSecret())
|
||||
|
||||
postJSON, jsonErr := ephemeralPost.ToJSON()
|
||||
@@ -2107,7 +2153,7 @@ func (a *App) CheckPostReminders() {
|
||||
|
||||
func includeEmbedsAndImages(a *App, c request.CTX, topThreadList *model.TopThreadList, userID string) (*model.TopThreadList, error) {
|
||||
for _, topThread := range topThreadList.Items {
|
||||
topThread.Post = a.PreparePostForClientWithEmbedsAndImages(c, topThread.Post, false, false)
|
||||
topThread.Post = a.PreparePostForClientWithEmbedsAndImages(c, topThread.Post, false, false, true)
|
||||
sanitizedPost, err := a.SanitizePostMetadataForUser(c, topThread.Post, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -2116,3 +2162,7 @@ func includeEmbedsAndImages(a *App, c request.CTX, topThreadList *model.TopThrea
|
||||
}
|
||||
return topThreadList, nil
|
||||
}
|
||||
|
||||
func (a *App) isPostPriorityEnabled() bool {
|
||||
return a.Config().FeatureFlags.PostPriority && *a.Config().ServiceSettings.PostPriority
|
||||
}
|
||||
|
||||
@@ -56,11 +56,20 @@ func (a *App) PreparePostListForClient(c request.CTX, originalList *model.PostLi
|
||||
}
|
||||
|
||||
for id, originalPost := range originalList.Posts {
|
||||
post := a.PreparePostForClientWithEmbedsAndImages(c, originalPost, false, false)
|
||||
post := a.PreparePostForClientWithEmbedsAndImages(c, originalPost, false, false, false)
|
||||
|
||||
list.Posts[id] = post
|
||||
}
|
||||
|
||||
if a.isPostPriorityEnabled() {
|
||||
priority, _ := a.GetPriorityForPostList(list)
|
||||
for _, id := range list.Order {
|
||||
if _, ok := priority[id]; ok {
|
||||
list.Posts[id].Metadata.Priority = priority[id]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
@@ -90,7 +99,7 @@ func (a *App) OverrideIconURLIfEmoji(c request.CTX, post *model.Post) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) PreparePostForClient(c request.CTX, originalPost *model.Post, isNewPost, isEditPost bool) *model.Post {
|
||||
func (a *App) PreparePostForClient(c request.CTX, originalPost *model.Post, isNewPost, isEditPost, includePriority bool) *model.Post {
|
||||
post := originalPost.Clone()
|
||||
|
||||
// Proxy image links before constructing metadata so that requests go through the proxy
|
||||
@@ -123,11 +132,20 @@ func (a *App) PreparePostForClient(c request.CTX, originalPost *model.Post, isNe
|
||||
post.Metadata.Files = fileInfos
|
||||
}
|
||||
|
||||
if includePriority && a.isPostPriorityEnabled() && post.RootId == "" {
|
||||
// Post's Priority if any
|
||||
if priority, err := a.GetPriorityForPost(post.Id); err != nil {
|
||||
mlog.Warn("Failed to get post priority for a post", mlog.String("post_id", post.Id), mlog.Err(err))
|
||||
} else {
|
||||
post.Metadata.Priority = priority
|
||||
}
|
||||
}
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
func (a *App) PreparePostForClientWithEmbedsAndImages(c request.CTX, originalPost *model.Post, isNewPost, isEditPost bool) *model.Post {
|
||||
post := a.PreparePostForClient(c, originalPost, isNewPost, isEditPost)
|
||||
func (a *App) PreparePostForClientWithEmbedsAndImages(c request.CTX, originalPost *model.Post, isNewPost, isEditPost, includePriority bool) *model.Post {
|
||||
post := a.PreparePostForClient(c, originalPost, isNewPost, isEditPost, includePriority)
|
||||
post = a.getEmbedsAndImages(c, post, isNewPost)
|
||||
return post
|
||||
}
|
||||
@@ -562,7 +580,7 @@ func (a *App) getLinkMetadata(c request.CTX, requestURL string, timestamp int64,
|
||||
permalink = &model.Permalink{PreviewPost: model.NewPreviewPost(referencedPost, referencedTeam, referencedChannel)}
|
||||
} else {
|
||||
// referencedPost does not contain a permalink: we get its metadata
|
||||
referencedPostWithMetadata := a.PreparePostForClientWithEmbedsAndImages(c, referencedPost, false, false)
|
||||
referencedPostWithMetadata := a.PreparePostForClientWithEmbedsAndImages(c, referencedPost, false, false, false)
|
||||
permalink = &model.Permalink{PreviewPost: model.NewPreviewPost(referencedPostWithMetadata, referencedTeam, referencedChannel)}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -125,7 +125,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
Message: message,
|
||||
}
|
||||
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, true)
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, true, false)
|
||||
|
||||
t.Run("doesn't mutate provided post", func(t *testing.T) {
|
||||
assert.NotEqual(t, clientPost, post, "should've returned a new post")
|
||||
@@ -151,7 +151,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
|
||||
post := th.CreatePost(th.BasicChannel)
|
||||
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
|
||||
|
||||
assert.False(t, clientPost == post, "should've returned a new post")
|
||||
assert.Equal(t, clientPost, post, "shouldn't have changed any metadata")
|
||||
@@ -167,7 +167,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
reaction3 := th.AddReactionToPost(post, th.BasicUser2, "ice_cream")
|
||||
post.HasReactions = true
|
||||
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
|
||||
|
||||
assert.Len(t, clientPost.Metadata.Reactions, 3, "should've populated Reactions")
|
||||
assert.Equal(t, reaction1, clientPost.Metadata.Reactions[0], "first reaction is incorrect")
|
||||
@@ -194,7 +194,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
|
||||
var clientPost *model.Post
|
||||
assert.Eventually(t, func() bool {
|
||||
clientPost = th.App.PreparePostForClient(th.Context, post, false, false)
|
||||
clientPost = th.App.PreparePostForClient(th.Context, post, false, false, false)
|
||||
return assert.ObjectsAreEqual([]*model.FileInfo{fileInfo}, clientPost.Metadata.Files)
|
||||
}, time.Second, 10*time.Millisecond)
|
||||
|
||||
@@ -230,7 +230,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
th.AddReactionToPost(post, th.BasicUser2, "angry")
|
||||
post.HasReactions = true
|
||||
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
|
||||
|
||||
t.Run("populates emojis", func(t *testing.T) {
|
||||
assert.ElementsMatch(t, []*model.Emoji{}, clientPost.Metadata.Emojis, "should've populated empty Emojis")
|
||||
@@ -275,7 +275,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
th.AddReactionToPost(post, th.BasicUser2, "angry")
|
||||
post.HasReactions = true
|
||||
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
|
||||
|
||||
t.Run("populates emojis", func(t *testing.T) {
|
||||
assert.ElementsMatch(t, []*model.Emoji{emoji1, emoji2, emoji3, emoji4}, clientPost.Metadata.Emojis, "should've populated post.Emojis")
|
||||
@@ -307,7 +307,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
post.AddProp(model.PostPropsOverrideIconURL, url)
|
||||
post.AddProp(model.PostPropsOverrideIconEmoji, emoji)
|
||||
|
||||
return th.App.PreparePostForClient(th.Context, post, false, false)
|
||||
return th.App.PreparePostForClient(th.Context, post, false, false, false)
|
||||
}
|
||||
|
||||
emoji := "basketball"
|
||||
@@ -361,7 +361,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
}, th.BasicChannel, false, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
|
||||
|
||||
t.Run("populates image dimensions", func(t *testing.T) {
|
||||
imageDimensions := clientPost.Metadata.Images
|
||||
@@ -394,7 +394,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
post.AddProp(model.PostPropsOverrideIconEmoji, true)
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
_ = th.App.PreparePostForClient(th.Context, post, false, false)
|
||||
_ = th.App.PreparePostForClient(th.Context, post, false, false, false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -424,7 +424,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
}, th.BasicChannel, false, true)
|
||||
require.Nil(t, err)
|
||||
post.Metadata.Embeds = nil
|
||||
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, post, false, false)
|
||||
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, post, false, false, false)
|
||||
|
||||
// Reminder that only the first link gets an embed and dimensions
|
||||
|
||||
@@ -459,7 +459,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
}, th.BasicChannel, false, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
|
||||
firstEmbed := clientPost.Metadata.Embeds[0]
|
||||
ogData := firstEmbed.Data.(*opengraph.OpenGraph)
|
||||
|
||||
@@ -502,7 +502,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
}, th.BasicChannel, false, true)
|
||||
require.Nil(t, err)
|
||||
post.Metadata.Embeds = nil
|
||||
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, post, false, false)
|
||||
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, post, false, false, false)
|
||||
|
||||
t.Run("populates embeds", func(t *testing.T) {
|
||||
assert.ElementsMatch(t, []*model.PostEmbed{
|
||||
@@ -547,7 +547,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
// DeleteAt isn't set on the post returned by App.DeletePost
|
||||
post.DeleteAt = model.GetMillis()
|
||||
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
|
||||
|
||||
assert.NotEqual(t, nil, clientPost.Metadata, "should've populated Metadata“")
|
||||
assert.Equal(t, "", clientPost.Message, "should've cleaned post content")
|
||||
@@ -582,7 +582,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
}, th.BasicChannel, false, true)
|
||||
require.Nil(t, err)
|
||||
previewPost.Metadata.Embeds = nil
|
||||
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false)
|
||||
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false, false)
|
||||
firstEmbed := clientPost.Metadata.Embeds[0]
|
||||
preview := firstEmbed.Data.(*model.PreviewPost)
|
||||
require.Equal(t, referencedPost.Id, preview.PostID)
|
||||
@@ -641,7 +641,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
previewPost.Metadata.Embeds = nil
|
||||
|
||||
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false)
|
||||
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false, false)
|
||||
firstEmbed := clientPost.Metadata.Embeds[0]
|
||||
preview := firstEmbed.Data.(*model.PreviewPost)
|
||||
|
||||
@@ -679,7 +679,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
previewPost.Metadata.Embeds = nil
|
||||
|
||||
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false)
|
||||
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false, false)
|
||||
firstEmbed := clientPost.Metadata.Embeds[0]
|
||||
preview := firstEmbed.Data.(*model.PreviewPost)
|
||||
referencedPostFirstEmbed := preview.Post.Metadata.Embeds[0]
|
||||
@@ -726,7 +726,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
previewPost.Metadata.Embeds = nil
|
||||
|
||||
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false)
|
||||
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false, false)
|
||||
firstEmbed := clientPost.Metadata.Embeds[0]
|
||||
preview := firstEmbed.Data.(*model.PreviewPost)
|
||||
referencedPostMetadata := preview.Post.Metadata
|
||||
@@ -761,7 +761,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
}, th.BasicChannel, false, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
clientPost := th.App.PreparePostForClient(th.Context, previewPost, false, false)
|
||||
clientPost := th.App.PreparePostForClient(th.Context, previewPost, false, false, false)
|
||||
firstEmbed := clientPost.Metadata.Embeds[0]
|
||||
preview := firstEmbed.Data.(*model.PreviewPost)
|
||||
require.Equal(t, referencedPost.Id, preview.PostID)
|
||||
@@ -770,13 +770,13 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
*cfg.ServiceSettings.EnablePermalinkPreviews = false
|
||||
})
|
||||
|
||||
th.App.PreparePostForClient(th.Context, previewPost, false, false)
|
||||
th.App.PreparePostForClient(th.Context, previewPost, false, false, false)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.EnablePermalinkPreviews = true
|
||||
})
|
||||
|
||||
clientPost2 := th.App.PreparePostForClient(th.Context, previewPost, false, false)
|
||||
clientPost2 := th.App.PreparePostForClient(th.Context, previewPost, false, false, false)
|
||||
firstEmbed2 := clientPost2.Metadata.Embeds[0]
|
||||
preview2 := firstEmbed2.Data.(*model.PreviewPost)
|
||||
require.Equal(t, referencedPost.Id, preview2.PostID)
|
||||
@@ -828,7 +828,7 @@ func testProxyLinkedImage(t *testing.T, th *TestHelper, shouldProxy bool) {
|
||||
Message: fmt.Sprintf(postTemplate, imageURL),
|
||||
}
|
||||
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
|
||||
|
||||
if shouldProxy {
|
||||
assert.Equal(t, fmt.Sprintf(postTemplate, imageURL), post.Message, "should not have mutated original post")
|
||||
@@ -876,7 +876,7 @@ func testProxyOpenGraphImage(t *testing.T, th *TestHelper, shouldProxy bool) {
|
||||
require.Nil(t, err)
|
||||
|
||||
post.Metadata.Embeds = nil
|
||||
embeds := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, post, false, false).Metadata.Embeds
|
||||
embeds := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, post, false, false, false).Metadata.Embeds
|
||||
require.Len(t, embeds, 1, "should have one embed")
|
||||
|
||||
embed := embeds[0]
|
||||
|
||||
34
app/post_priority.go
Обычный файл
34
app/post_priority.go
Обычный файл
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func (a *App) GetPriorityForPost(postId string) (*model.PostPriority, *model.AppError) {
|
||||
priority, err := a.Srv().Store().PostPriority().GetForPost(postId)
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, model.NewAppError("GetPriorityForPost", "app.post_prority.get_for_post.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
return priority, nil
|
||||
}
|
||||
|
||||
func (a *App) GetPriorityForPostList(list *model.PostList) (map[string]*model.PostPriority, *model.AppError) {
|
||||
priority, err := a.Srv().Store().PostPriority().GetForPosts(list.Order)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetPriorityForPost", "app.post_prority.get_for_post.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
priorityMap := make(map[string]*model.PostPriority)
|
||||
for _, p := range priority {
|
||||
priorityMap[p.PostId] = p
|
||||
}
|
||||
|
||||
return priorityMap, nil
|
||||
}
|
||||
@@ -1598,7 +1598,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
}, channel, false, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
@@ -1637,7 +1637,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
|
||||
// post1 and post3 should mention the user
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
@@ -1676,7 +1676,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
|
||||
// post2 and post3 should mention the user
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
@@ -1713,7 +1713,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
}, channel, false, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
@@ -1755,7 +1755,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
}, channel, false, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
@@ -1809,7 +1809,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
|
||||
// post2 should mention the user
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
@@ -1863,7 +1863,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
|
||||
// post2 and post5 should mention the user
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
@@ -1912,7 +1912,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
|
||||
// should be mentioned by post2 and post3
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
@@ -1942,12 +1942,12 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
}, channel, false, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
|
||||
count, _, err = th.App.countMentionsFromPost(th.Context, user1, post1)
|
||||
count, _, _, err = th.App.countMentionsFromPost(th.Context, user1, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
@@ -1984,7 +1984,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
|
||||
// post1 and post3 should mention the user, but we only count post3
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post2)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post2)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
@@ -2015,7 +2015,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
|
||||
// post2 should mention the user
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
@@ -2062,7 +2062,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
|
||||
// post4 should mention the user
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post3)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post3)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
@@ -2102,7 +2102,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
|
||||
// post3 should mention the user
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
@@ -2138,11 +2138,70 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
|
||||
// Every post should mention the user
|
||||
|
||||
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, numPosts, count)
|
||||
})
|
||||
|
||||
t.Run("should count urgent mentions", func(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_POSTPRIORITY", "true")
|
||||
defer os.Unsetenv("MM_FEATUREFLAGS_POSTPRIORITY")
|
||||
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostPriority = true
|
||||
cfg.FeatureFlags.PostPriority = true
|
||||
})
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
user2.NotifyProps[model.MentionKeysNotifyProp] = "apple"
|
||||
|
||||
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: fmt.Sprintf("@%s", user2.Username),
|
||||
Metadata: &model.PostMetadata{
|
||||
Priority: &model.PostPriority{
|
||||
Priority: model.NewString(model.PostPriorityUrgent),
|
||||
},
|
||||
},
|
||||
}, channel, false, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.CreatePost(th.Context, &model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: fmt.Sprintf("@%s", user2.Username),
|
||||
}, channel, false, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.CreatePost(th.Context, &model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "apple",
|
||||
Metadata: &model.PostMetadata{
|
||||
Priority: &model.PostPriority{
|
||||
Priority: model.NewString(model.PostPriorityUrgent),
|
||||
},
|
||||
},
|
||||
}, channel, false, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
// all posts mention the user but only post1, post3 are urgent
|
||||
|
||||
_, _, count, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFillInPostProps(t *testing.T) {
|
||||
|
||||
@@ -1747,6 +1747,7 @@ func (a *App) GetTeamsUnreadForUser(excludeTeamId string, userID string, include
|
||||
MsgCountRoot: 0,
|
||||
ThreadCount: 0,
|
||||
ThreadMentionCount: 0,
|
||||
ThreadUrgentMentionCount: 0,
|
||||
TeamId: id,
|
||||
})
|
||||
}
|
||||
@@ -1755,7 +1756,7 @@ func (a *App) GetTeamsUnreadForUser(excludeTeamId string, userID string, include
|
||||
includeCollapsedThreads = includeCollapsedThreads && *a.Config().ServiceSettings.CollapsedThreads != model.CollapsedThreadsDisabled
|
||||
|
||||
if includeCollapsedThreads {
|
||||
teamUnreads, err := a.Srv().Store().Thread().GetTeamsUnreadForUser(userID, teamIDs)
|
||||
teamUnreads, err := a.Srv().Store().Thread().GetTeamsUnreadForUser(userID, teamIDs, a.isPostPriorityEnabled())
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetTeamsUnreadForUser", "app.team.get_unread.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
@@ -1763,6 +1764,7 @@ func (a *App) GetTeamsUnreadForUser(excludeTeamId string, userID string, include
|
||||
if _, ok := teamUnreads[teamID]; ok {
|
||||
member.ThreadCount = teamUnreads[teamID].ThreadCount
|
||||
member.ThreadMentionCount = teamUnreads[teamID].ThreadMentionCount
|
||||
member.ThreadUrgentMentionCount = teamUnreads[teamID].ThreadUrgentMentionCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
app/user.go
20
app/user.go
@@ -2391,6 +2391,10 @@ func (a *App) ConvertBotToUser(c request.CTX, bot *model.Bot, userPatch *model.U
|
||||
func (a *App) GetThreadsForUser(userID, teamID string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError) {
|
||||
var result model.Threads
|
||||
var eg errgroup.Group
|
||||
postPriorityIsEnabled := a.isPostPriorityEnabled()
|
||||
if postPriorityIsEnabled {
|
||||
options.IncludeIsUrgent = true
|
||||
}
|
||||
|
||||
if !options.ThreadsOnly {
|
||||
eg.Go(func() error {
|
||||
@@ -2427,6 +2431,18 @@ func (a *App) GetThreadsForUser(userID, teamID string, options model.GetUserThre
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if postPriorityIsEnabled {
|
||||
eg.Go(func() error {
|
||||
totalUnreadUrgentMentions, err := a.Srv().Store().Thread().GetTotalUnreadUrgentMentions(userID, teamID, options)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to count urgent mentioned threads for user id=%s", userID)
|
||||
}
|
||||
result.TotalUnreadUrgentMentions = totalUnreadUrgentMentions
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if !options.TotalsOnly {
|
||||
@@ -2469,7 +2485,7 @@ func (a *App) GetThreadMembershipForUser(userId, threadId string) (*model.Thread
|
||||
}
|
||||
|
||||
func (a *App) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, *model.AppError) {
|
||||
thread, err := a.Srv().Store().Thread().GetThreadForUser(threadMembership, extended)
|
||||
thread, err := a.Srv().Store().Thread().GetThreadForUser(threadMembership, extended, a.isPostPriorityEnabled())
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetThreadForUser", "app.user.get_threads_for_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
@@ -2551,7 +2567,7 @@ func (a *App) UpdateThreadFollowForUserFromChannelAdd(c request.CTX, userID, tea
|
||||
}
|
||||
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventThreadUpdated, teamID, "", userID, nil, "")
|
||||
userThread, err := a.Srv().Store().Thread().GetThreadForUser(tm, true)
|
||||
userThread, err := a.Srv().Store().Thread().GetThreadForUser(tm, true, a.isPostPriorityEnabled())
|
||||
|
||||
if err != nil {
|
||||
var errNotFound *store.ErrNotFound
|
||||
|
||||
@@ -192,6 +192,8 @@ db/migrations/mysql/000095_remove_posts_parentid.down.sql
|
||||
db/migrations/mysql/000095_remove_posts_parentid.up.sql
|
||||
db/migrations/mysql/000096_threads_threadteamid.down.sql
|
||||
db/migrations/mysql/000096_threads_threadteamid.up.sql
|
||||
db/migrations/mysql/000097_create_posts_priority.down.sql
|
||||
db/migrations/mysql/000097_create_posts_priority.up.sql
|
||||
db/migrations/postgres/000001_create_teams.down.sql
|
||||
db/migrations/postgres/000001_create_teams.up.sql
|
||||
db/migrations/postgres/000002_create_team_members.down.sql
|
||||
@@ -384,3 +386,5 @@ db/migrations/postgres/000095_remove_posts_parentid.down.sql
|
||||
db/migrations/postgres/000095_remove_posts_parentid.up.sql
|
||||
db/migrations/postgres/000096_threads_threadteamid.down.sql
|
||||
db/migrations/postgres/000096_threads_threadteamid.up.sql
|
||||
db/migrations/postgres/000097_create_posts_priority.down.sql
|
||||
db/migrations/postgres/000097_create_posts_priority.up.sql
|
||||
|
||||
16
db/migrations/mysql/000097_create_posts_priority.down.sql
Обычный файл
16
db/migrations/mysql/000097_create_posts_priority.down.sql
Обычный файл
@@ -0,0 +1,16 @@
|
||||
DROP TABLE IF EXISTS PostsPriority;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'ChannelMembers'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'UrgentMentionCount'
|
||||
) > 0,
|
||||
'ALTER TABLE ChannelMembers DROP COLUMN UrgentMentionCount;',
|
||||
'SELECT 1'
|
||||
));
|
||||
|
||||
PREPARE alterIfExists FROM @preparedStatement;
|
||||
EXECUTE alterIfExists;
|
||||
DEALLOCATE PREPARE alterIfExists;
|
||||
23
db/migrations/mysql/000097_create_posts_priority.up.sql
Обычный файл
23
db/migrations/mysql/000097_create_posts_priority.up.sql
Обычный файл
@@ -0,0 +1,23 @@
|
||||
CREATE TABLE IF NOT EXISTS PostsPriority (
|
||||
PostId varchar(26) NOT NULL,
|
||||
ChannelId varchar(26) NOT NULL,
|
||||
Priority varchar(32) NOT NULL,
|
||||
RequestedAck tinyint(1),
|
||||
PersistentNotifications tinyint(1),
|
||||
PRIMARY KEY (PostId)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
NOT EXISTS(
|
||||
SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'ChannelMembers'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'UrgentMentionCount'
|
||||
),
|
||||
'ALTER TABLE ChannelMembers ADD COLUMN UrgentMentionCount bigint(20);',
|
||||
'SELECT 1;'
|
||||
));
|
||||
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
3
db/migrations/postgres/000097_create_posts_priority.down.sql
Обычный файл
3
db/migrations/postgres/000097_create_posts_priority.down.sql
Обычный файл
@@ -0,0 +1,3 @@
|
||||
DROP TABLE IF EXISTS postspriority;
|
||||
|
||||
ALTER TABLE channelmembers DROP COLUMN IF EXISTS urgentmentioncount;
|
||||
9
db/migrations/postgres/000097_create_posts_priority.up.sql
Обычный файл
9
db/migrations/postgres/000097_create_posts_priority.up.sql
Обычный файл
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE IF NOT EXISTS postspriority (
|
||||
postid VARCHAR(26) PRIMARY KEY,
|
||||
channelid VARCHAR(26) NOT NULL,
|
||||
priority VARCHAR(32) NOT NULL,
|
||||
requestedack boolean,
|
||||
persistentnotifications boolean
|
||||
);
|
||||
|
||||
ALTER TABLE channelmembers ADD COLUMN IF NOT EXISTS urgentmentioncount bigint;
|
||||
12
i18n/en.json
12
i18n/en.json
@@ -4551,6 +4551,10 @@
|
||||
"id": "app.channel.count_posts_since.app_error",
|
||||
"translation": "Unable to count messages since given date."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.count_urgent_posts_since.app_error",
|
||||
"translation": "Unable to count urgent posts since given date."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.create_channel.internal_error",
|
||||
"translation": "Unable to save channel."
|
||||
@@ -4679,6 +4683,10 @@
|
||||
"id": "app.channel.get_pinnedpost_count.app_error",
|
||||
"translation": "Unable to get the channel pinned post count."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.get_priority_for_posts.app_error",
|
||||
"translation": "Unable to get the priority for posts"
|
||||
},
|
||||
{
|
||||
"id": "app.channel.get_private_channels.get.app_error",
|
||||
"translation": "Unable to get private channels."
|
||||
@@ -6111,6 +6119,10 @@
|
||||
"id": "app.post.update.app_error",
|
||||
"translation": "Unable to update the Post."
|
||||
},
|
||||
{
|
||||
"id": "app.post_prority.get_for_post.app_error",
|
||||
"translation": "Unable to get postpriority for post"
|
||||
},
|
||||
{
|
||||
"id": "app.post_reminder_dm",
|
||||
"translation": "Hi there, here's your reminder about this message from @{{.Username}}: {{.SiteURL}}/{{.TeamName}}/pl/{{.PostId}}"
|
||||
|
||||
@@ -27,6 +27,7 @@ type ChannelUnread struct {
|
||||
MsgCount int64 `json:"msg_count"`
|
||||
MentionCount int64 `json:"mention_count"`
|
||||
MentionCountRoot int64 `json:"mention_count_root"`
|
||||
UrgentMentionCount int64 `json:"urgent_mention_count"`
|
||||
MsgCountRoot int64 `json:"msg_count_root"`
|
||||
NotifyProps StringMap `json:"-"`
|
||||
}
|
||||
@@ -38,6 +39,7 @@ type ChannelUnreadAt struct {
|
||||
MsgCount int64 `json:"msg_count"`
|
||||
MentionCount int64 `json:"mention_count"`
|
||||
MentionCountRoot int64 `json:"mention_count_root"`
|
||||
UrgentMentionCount int64 `json:"urgent_mention_count"`
|
||||
MsgCountRoot int64 `json:"msg_count_root"`
|
||||
LastViewedAt int64 `json:"last_viewed_at"`
|
||||
NotifyProps StringMap `json:"-"`
|
||||
@@ -51,6 +53,7 @@ type ChannelMember struct {
|
||||
MsgCount int64 `json:"msg_count"`
|
||||
MentionCount int64 `json:"mention_count"`
|
||||
MentionCountRoot int64 `json:"mention_count_root"`
|
||||
UrgentMentionCount int64 `json:"urgent_mention_count"`
|
||||
MsgCountRoot int64 `json:"msg_count_root"`
|
||||
NotifyProps StringMap `json:"notify_props"`
|
||||
LastUpdateAt int64 `json:"last_update_at"`
|
||||
@@ -69,6 +72,7 @@ func (o *ChannelMember) Auditable() map[string]interface{} {
|
||||
"msg_count": o.MsgCount,
|
||||
"mention_count": o.MentionCount,
|
||||
"mention_count_root": o.MentionCountRoot,
|
||||
"urgent_mention_count": o.UrgentMentionCount,
|
||||
"msg_count_root": o.MsgCountRoot,
|
||||
"notify_props": o.NotifyProps,
|
||||
"last_update_at": o.LastUpdateAt,
|
||||
@@ -100,6 +104,10 @@ func (o *ChannelMember) MentionCountRoot_() float64 {
|
||||
return float64(o.MentionCountRoot)
|
||||
}
|
||||
|
||||
func (o *ChannelMember) UrgentMentionCount_() float64 {
|
||||
return float64(o.UrgentMentionCount)
|
||||
}
|
||||
|
||||
func (o *ChannelMember) MsgCountRoot_() float64 {
|
||||
return float64(o.MsgCountRoot)
|
||||
}
|
||||
|
||||
@@ -71,6 +71,10 @@ const (
|
||||
PostPropsGroupHighlightDisabled = "disable_group_highlight"
|
||||
|
||||
PostPropsPreviewedPost = "previewed_post"
|
||||
|
||||
PostPriorityUrgent = "urgent"
|
||||
PostPropsRequestedAck = "requested_ack"
|
||||
PostPropsPersistentNotifications = "persistent_notifications"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -158,6 +162,15 @@ type PostReminder struct {
|
||||
UserId string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type PostPriority struct {
|
||||
Priority *string `json:"priority"`
|
||||
RequestedAck *bool `json:"requested_ack"`
|
||||
PersistentNotifications *bool `json:"persistent_notifications"`
|
||||
// These fields are only used internally for interacting with DB.
|
||||
PostId string `json:",omitempty"`
|
||||
ChannelId string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type SearchParameter struct {
|
||||
Terms *string `json:"terms"`
|
||||
IsOrSearch *bool `json:"is_or_search"`
|
||||
@@ -306,6 +319,7 @@ type GetPostsOptions struct {
|
||||
FromCreateAt int64 // CreateAt after which to send the items
|
||||
Direction string // Only accepts up|down. Indicates the order in which to send the items.
|
||||
IncludeDeleted bool
|
||||
IncludePostPriority bool
|
||||
}
|
||||
|
||||
type PostCountOptions struct {
|
||||
@@ -770,3 +784,20 @@ func (o *Post) GetPreviewedPostProp() string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (o *Post) GetPriority() *PostPriority {
|
||||
if o.Metadata != nil && o.Metadata.Priority != nil {
|
||||
return o.Metadata.Priority
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Post) IsUrgent() bool {
|
||||
postPriority := o.GetPriority()
|
||||
if postPriority == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return *postPriority.Priority == PostPriorityUrgent
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ type PostMetadata struct {
|
||||
|
||||
// Reactions holds reactions made to the post.
|
||||
Reactions []*Reaction `json:"reactions,omitempty"`
|
||||
|
||||
// Reactions holds reactions made to the post.
|
||||
Priority *PostPriority `json:"priority,omitempty"`
|
||||
}
|
||||
|
||||
type PostImage struct {
|
||||
@@ -54,11 +57,23 @@ func (p *PostMetadata) Copy() *PostMetadata {
|
||||
reactionsCopy := make([]*Reaction, len(p.Reactions))
|
||||
copy(reactionsCopy, p.Reactions)
|
||||
|
||||
var postPriorityCopy *PostPriority
|
||||
if p.Priority != nil {
|
||||
postPriorityCopy = &PostPriority{
|
||||
Priority: p.Priority.Priority,
|
||||
RequestedAck: p.Priority.RequestedAck,
|
||||
PersistentNotifications: p.Priority.PersistentNotifications,
|
||||
PostId: p.Priority.PostId,
|
||||
ChannelId: p.Priority.ChannelId,
|
||||
}
|
||||
}
|
||||
|
||||
return &PostMetadata{
|
||||
Embeds: embedsCopy,
|
||||
Emojis: emojisCopy,
|
||||
Files: filesCopy,
|
||||
Images: imagesCopy,
|
||||
Reactions: reactionsCopy,
|
||||
Priority: postPriorityCopy,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ type TeamUnread struct {
|
||||
MsgCountRoot int64 `json:"msg_count_root"`
|
||||
ThreadCount int64 `json:"thread_count"`
|
||||
ThreadMentionCount int64 `json:"thread_mention_count"`
|
||||
ThreadUrgentMentionCount int64 `json:"thread_urgent_mention_count"`
|
||||
}
|
||||
|
||||
//msgp:ignore TeamMemberForExport
|
||||
|
||||
@@ -41,6 +41,7 @@ type ThreadResponse struct {
|
||||
Post *Post `json:"post"`
|
||||
UnreadReplies int64 `json:"unread_replies"`
|
||||
UnreadMentions int64 `json:"unread_mentions"`
|
||||
IsUrgent bool `json:"is_urgent"`
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
}
|
||||
|
||||
@@ -48,6 +49,7 @@ type Threads struct {
|
||||
Total int64 `json:"total"`
|
||||
TotalUnreadThreads int64 `json:"total_unread_threads"`
|
||||
TotalUnreadMentions int64 `json:"total_unread_mentions"`
|
||||
TotalUnreadUrgentMentions int64 `json:"total_unread_urgent_mentions"`
|
||||
Threads []*ThreadResponse `json:"threads"`
|
||||
}
|
||||
|
||||
@@ -81,6 +83,9 @@ type GetUserThreadsOpts struct {
|
||||
|
||||
// TeamOnly will only fetch threads and unreads for the specified team and excludes DMs/GMs
|
||||
TeamOnly bool
|
||||
|
||||
// IncludeIsUrgent will return IsUrgent field as well to assert is the thread is urgent or not
|
||||
IncludeIsUrgent bool
|
||||
}
|
||||
|
||||
func (o *Thread) Etag() string {
|
||||
|
||||
@@ -37,6 +37,7 @@ type OpenTracingLayer struct {
|
||||
OAuthStore store.OAuthStore
|
||||
PluginStore store.PluginStore
|
||||
PostStore store.PostStore
|
||||
PostPriorityStore store.PostPriorityStore
|
||||
PreferenceStore store.PreferenceStore
|
||||
ProductNoticesStore store.ProductNoticesStore
|
||||
ReactionStore store.ReactionStore
|
||||
@@ -131,6 +132,10 @@ func (s *OpenTracingLayer) Post() store.PostStore {
|
||||
return s.PostStore
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayer) PostPriority() store.PostPriorityStore {
|
||||
return s.PostPriorityStore
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayer) Preference() store.PreferenceStore {
|
||||
return s.PreferenceStore
|
||||
}
|
||||
@@ -301,6 +306,11 @@ type OpenTracingLayerPostStore struct {
|
||||
Root *OpenTracingLayer
|
||||
}
|
||||
|
||||
type OpenTracingLayerPostPriorityStore struct {
|
||||
store.PostPriorityStore
|
||||
Root *OpenTracingLayer
|
||||
}
|
||||
|
||||
type OpenTracingLayerPreferenceStore struct {
|
||||
store.PreferenceStore
|
||||
Root *OpenTracingLayer
|
||||
@@ -702,6 +712,24 @@ func (s *OpenTracingLayerChannelStore) CountPostsAfter(channelID string, timesta
|
||||
return result, resultVar1, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerChannelStore) CountUrgentPostsAfter(channelID string, timestamp int64, userID string) (int, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.CountUrgentPostsAfter")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.ChannelStore.CountUrgentPostsAfter(channelID, timestamp, userID)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerChannelStore) CreateDirectChannel(userID *model.User, otherUserID *model.User, channelOptions ...model.ChannelOption) (*model.Channel, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.CreateDirectChannel")
|
||||
@@ -1867,7 +1895,7 @@ func (s *OpenTracingLayerChannelStore) GroupSyncedChannelCount() (int64, error)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error {
|
||||
func (s *OpenTracingLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool, isUrgent bool) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.IncrementMentionCount")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
@@ -1876,7 +1904,7 @@ func (s *OpenTracingLayerChannelStore) IncrementMentionCount(channelID string, u
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot)
|
||||
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot, isUrgent)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
@@ -2439,7 +2467,7 @@ func (s *OpenTracingLayerChannelStore) UpdateLastViewedAt(channelIds []string, u
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
|
||||
func (s *OpenTracingLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, urgentMentionCount int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.UpdateLastViewedAtPost")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
@@ -2448,7 +2476,7 @@ func (s *OpenTracingLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot)
|
||||
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
@@ -6516,6 +6544,42 @@ func (s *OpenTracingLayerPostStore) Update(newPost *model.Post, oldPost *model.P
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPostPriorityStore) GetForPost(postId string) (*model.PostPriority, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostPriorityStore.GetForPost")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PostPriorityStore.GetForPost(postId)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPostPriorityStore) GetForPosts(ids []string) ([]*model.PostPriority, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostPriorityStore.GetForPosts")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PostPriorityStore.GetForPosts(ids)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPreferenceStore) CleanupFlagsBatch(limit int64) (int64, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PreferenceStore.CleanupFlagsBatch")
|
||||
@@ -9872,7 +9936,7 @@ func (s *OpenTracingLayerThreadStore) GetMembershipsForUser(userId string, teamI
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string) (map[string]*model.TeamUnread, error) {
|
||||
func (s *OpenTracingLayerThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string, includeUrgentMentionCount bool) (map[string]*model.TeamUnread, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetTeamsUnreadForUser")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
@@ -9881,7 +9945,7 @@ func (s *OpenTracingLayerThreadStore) GetTeamsUnreadForUser(userID string, teamI
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.ThreadStore.GetTeamsUnreadForUser(userID, teamIDs)
|
||||
result, err := s.ThreadStore.GetTeamsUnreadForUser(userID, teamIDs, includeUrgentMentionCount)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
@@ -9908,7 +9972,7 @@ func (s *OpenTracingLayerThreadStore) GetThreadFollowers(threadID string, fetchO
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, error) {
|
||||
func (s *OpenTracingLayerThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool, postPriorityIsEnabled bool) (*model.ThreadResponse, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetThreadForUser")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
@@ -9917,7 +9981,7 @@ func (s *OpenTracingLayerThreadStore) GetThreadForUser(threadMembership *model.T
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.ThreadStore.GetThreadForUser(threadMembership, extended)
|
||||
result, err := s.ThreadStore.GetThreadForUser(threadMembership, extended, postPriorityIsEnabled)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
@@ -10052,6 +10116,24 @@ func (s *OpenTracingLayerThreadStore) GetTotalUnreadThreads(userId string, teamI
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerThreadStore) GetTotalUnreadUrgentMentions(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetTotalUnreadUrgentMentions")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.ThreadStore.GetTotalUnreadUrgentMentions(userId, teamID, opts)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerThreadStore) MaintainMembership(userID string, postID string, opts store.ThreadMembershipOpts) (*model.ThreadMembership, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.MaintainMembership")
|
||||
@@ -12509,6 +12591,7 @@ func New(childStore store.Store, ctx context.Context) *OpenTracingLayer {
|
||||
newStore.OAuthStore = &OpenTracingLayerOAuthStore{OAuthStore: childStore.OAuth(), Root: &newStore}
|
||||
newStore.PluginStore = &OpenTracingLayerPluginStore{PluginStore: childStore.Plugin(), Root: &newStore}
|
||||
newStore.PostStore = &OpenTracingLayerPostStore{PostStore: childStore.Post(), Root: &newStore}
|
||||
newStore.PostPriorityStore = &OpenTracingLayerPostPriorityStore{PostPriorityStore: childStore.PostPriority(), Root: &newStore}
|
||||
newStore.PreferenceStore = &OpenTracingLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
|
||||
newStore.ProductNoticesStore = &OpenTracingLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}
|
||||
newStore.ReactionStore = &OpenTracingLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore}
|
||||
|
||||
@@ -40,6 +40,7 @@ type RetryLayer struct {
|
||||
OAuthStore store.OAuthStore
|
||||
PluginStore store.PluginStore
|
||||
PostStore store.PostStore
|
||||
PostPriorityStore store.PostPriorityStore
|
||||
PreferenceStore store.PreferenceStore
|
||||
ProductNoticesStore store.ProductNoticesStore
|
||||
ReactionStore store.ReactionStore
|
||||
@@ -134,6 +135,10 @@ func (s *RetryLayer) Post() store.PostStore {
|
||||
return s.PostStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) PostPriority() store.PostPriorityStore {
|
||||
return s.PostPriorityStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) Preference() store.PreferenceStore {
|
||||
return s.PreferenceStore
|
||||
}
|
||||
@@ -304,6 +309,11 @@ type RetryLayerPostStore struct {
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerPostPriorityStore struct {
|
||||
store.PostPriorityStore
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerPreferenceStore struct {
|
||||
store.PreferenceStore
|
||||
Root *RetryLayer
|
||||
@@ -762,6 +772,27 @@ func (s *RetryLayerChannelStore) CountPostsAfter(channelID string, timestamp int
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelStore) CountUrgentPostsAfter(channelID string, timestamp int64, userID string) (int, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.ChannelStore.CountUrgentPostsAfter(channelID, timestamp, userID)
|
||||
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
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelStore) CreateDirectChannel(userID *model.User, otherUserID *model.User, channelOptions ...model.ChannelOption) (*model.Channel, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -2112,11 +2143,11 @@ func (s *RetryLayerChannelStore) GroupSyncedChannelCount() (int64, error) {
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error {
|
||||
func (s *RetryLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool, isUrgent bool) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot)
|
||||
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot, isUrgent)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -2706,11 +2737,11 @@ func (s *RetryLayerChannelStore) UpdateLastViewedAt(channelIds []string, userID
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
|
||||
func (s *RetryLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, urgentMentionCount int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot)
|
||||
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
@@ -7389,6 +7420,48 @@ func (s *RetryLayerPostStore) Update(newPost *model.Post, oldPost *model.Post) (
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostPriorityStore) GetForPost(postId string) (*model.PostPriority, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PostPriorityStore.GetForPost(postId)
|
||||
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
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostPriorityStore) GetForPosts(ids []string) ([]*model.PostPriority, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PostPriorityStore.GetForPosts(ids)
|
||||
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
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPreferenceStore) CleanupFlagsBatch(limit int64) (int64, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -11286,11 +11359,11 @@ func (s *RetryLayerThreadStore) GetMembershipsForUser(userId string, teamID stri
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string) (map[string]*model.TeamUnread, error) {
|
||||
func (s *RetryLayerThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string, includeUrgentMentionCount bool) (map[string]*model.TeamUnread, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.ThreadStore.GetTeamsUnreadForUser(userID, teamIDs)
|
||||
result, err := s.ThreadStore.GetTeamsUnreadForUser(userID, teamIDs, includeUrgentMentionCount)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
@@ -11328,11 +11401,11 @@ func (s *RetryLayerThreadStore) GetThreadFollowers(threadID string, fetchOnlyAct
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, error) {
|
||||
func (s *RetryLayerThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool, postPriorityIsEnabled bool) (*model.ThreadResponse, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.ThreadStore.GetThreadForUser(threadMembership, extended)
|
||||
result, err := s.ThreadStore.GetThreadForUser(threadMembership, extended, postPriorityIsEnabled)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
@@ -11496,6 +11569,27 @@ func (s *RetryLayerThreadStore) GetTotalUnreadThreads(userId string, teamID stri
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerThreadStore) GetTotalUnreadUrgentMentions(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.ThreadStore.GetTotalUnreadUrgentMentions(userId, teamID, opts)
|
||||
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
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerThreadStore) MaintainMembership(userID string, postID string, opts store.ThreadMembershipOpts) (*model.ThreadMembership, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -14261,6 +14355,7 @@ func New(childStore store.Store) *RetryLayer {
|
||||
newStore.OAuthStore = &RetryLayerOAuthStore{OAuthStore: childStore.OAuth(), Root: &newStore}
|
||||
newStore.PluginStore = &RetryLayerPluginStore{PluginStore: childStore.Plugin(), Root: &newStore}
|
||||
newStore.PostStore = &RetryLayerPostStore{PostStore: childStore.Post(), Root: &newStore}
|
||||
newStore.PostPriorityStore = &RetryLayerPostPriorityStore{PostPriorityStore: childStore.PostPriority(), Root: &newStore}
|
||||
newStore.PreferenceStore = &RetryLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
|
||||
newStore.ProductNoticesStore = &RetryLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}
|
||||
newStore.ReactionStore = &RetryLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore}
|
||||
|
||||
@@ -54,6 +54,7 @@ func genStore() *mocks.Store {
|
||||
mock.On("UserTermsOfService").Return(&mocks.UserTermsOfServiceStore{})
|
||||
mock.On("Webhook").Return(&mocks.WebhookStore{})
|
||||
mock.On("NotifyAdmin").Return(&mocks.NotifyAdminStore{})
|
||||
mock.On("PostPriority").Return(&mocks.PostPriorityStore{})
|
||||
return mock
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ type channelMember struct {
|
||||
LastViewedAt int64
|
||||
MsgCount int64
|
||||
MentionCount int64
|
||||
UrgentMentionCount int64
|
||||
NotifyProps model.StringMap
|
||||
LastUpdateAt int64
|
||||
SchemeUser sql.NullBool
|
||||
@@ -65,6 +66,7 @@ func NewMapFromChannelMemberModel(cm *model.ChannelMember) map[string]any {
|
||||
"MsgCount": cm.MsgCount,
|
||||
"MentionCount": cm.MentionCount,
|
||||
"MentionCountRoot": cm.MentionCountRoot,
|
||||
"UrgentMentionCount": cm.UrgentMentionCount,
|
||||
"MsgCountRoot": cm.MsgCountRoot,
|
||||
"NotifyProps": cm.NotifyProps,
|
||||
"LastUpdateAt": cm.LastUpdateAt,
|
||||
@@ -82,6 +84,7 @@ type channelMemberWithSchemeRoles struct {
|
||||
MsgCount int64
|
||||
MentionCount int64
|
||||
MentionCountRoot int64
|
||||
UrgentMentionCount int64
|
||||
NotifyProps model.StringMap
|
||||
LastUpdateAt int64
|
||||
SchemeGuest sql.NullBool
|
||||
@@ -106,7 +109,7 @@ type channelMemberWithTeamWithSchemeRoles struct {
|
||||
type channelMemberWithTeamWithSchemeRolesList []channelMemberWithTeamWithSchemeRoles
|
||||
|
||||
func channelMemberSliceColumns() []string {
|
||||
return []string{"ChannelId", "UserId", "Roles", "LastViewedAt", "MsgCount", "MsgCountRoot", "MentionCount", "MentionCountRoot", "NotifyProps", "LastUpdateAt", "SchemeUser", "SchemeAdmin", "SchemeGuest"}
|
||||
return []string{"ChannelId", "UserId", "Roles", "LastViewedAt", "MsgCount", "MsgCountRoot", "MentionCount", "MentionCountRoot", "UrgentMentionCount", "NotifyProps", "LastUpdateAt", "SchemeUser", "SchemeAdmin", "SchemeGuest"}
|
||||
}
|
||||
|
||||
func channelMemberToSlice(member *model.ChannelMember) []any {
|
||||
@@ -119,6 +122,7 @@ func channelMemberToSlice(member *model.ChannelMember) []any {
|
||||
resultSlice = append(resultSlice, member.MsgCountRoot)
|
||||
resultSlice = append(resultSlice, member.MentionCount)
|
||||
resultSlice = append(resultSlice, member.MentionCountRoot)
|
||||
resultSlice = append(resultSlice, member.UrgentMentionCount)
|
||||
resultSlice = append(resultSlice, model.MapToJSON(member.NotifyProps))
|
||||
resultSlice = append(resultSlice, member.LastUpdateAt)
|
||||
resultSlice = append(resultSlice, member.SchemeUser)
|
||||
@@ -252,6 +256,7 @@ func (db channelMemberWithSchemeRoles) ToModel() *model.ChannelMember {
|
||||
MsgCountRoot: db.MsgCountRoot,
|
||||
MentionCount: db.MentionCount,
|
||||
MentionCountRoot: db.MentionCountRoot,
|
||||
UrgentMentionCount: db.UrgentMentionCount,
|
||||
NotifyProps: db.NotifyProps,
|
||||
LastUpdateAt: db.LastUpdateAt,
|
||||
SchemeAdmin: rolesResult.schemeAdmin,
|
||||
@@ -315,6 +320,7 @@ func (db channelMemberWithTeamWithSchemeRoles) ToModel() *model.ChannelMemberWit
|
||||
MsgCountRoot: db.MsgCountRoot,
|
||||
MentionCount: db.MentionCount,
|
||||
MentionCountRoot: db.MentionCountRoot,
|
||||
UrgentMentionCount: db.UrgentMentionCount,
|
||||
NotifyProps: db.NotifyProps,
|
||||
LastUpdateAt: db.LastUpdateAt,
|
||||
SchemeAdmin: rolesResult.schemeAdmin,
|
||||
@@ -471,7 +477,20 @@ func newSqlChannelStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface
|
||||
func (s *SqlChannelStore) initializeQueries() {
|
||||
s.channelMembersForTeamWithSchemeSelectQuery = s.getQueryBuilder().
|
||||
Select(
|
||||
"ChannelMembers.*",
|
||||
"ChannelMembers.ChannelId",
|
||||
"ChannelMembers.UserId",
|
||||
"ChannelMembers.Roles",
|
||||
"ChannelMembers.LastViewedAt",
|
||||
"ChannelMembers.MsgCount",
|
||||
"ChannelMembers.MentionCount",
|
||||
"ChannelMembers.MentionCountRoot",
|
||||
"COALESCE(ChannelMembers.UrgentMentionCount, 0) AS UrgentMentionCount",
|
||||
"ChannelMembers.MsgCountRoot",
|
||||
"ChannelMembers.NotifyProps",
|
||||
"ChannelMembers.LastUpdateAt",
|
||||
"ChannelMembers.SchemeUser",
|
||||
"ChannelMembers.SchemeAdmin",
|
||||
"ChannelMembers.SchemeGuest",
|
||||
"TeamScheme.DefaultChannelGuestRole TeamSchemeDefaultGuestRole",
|
||||
"TeamScheme.DefaultChannelUserRole TeamSchemeDefaultUserRole",
|
||||
"TeamScheme.DefaultChannelAdminRole TeamSchemeDefaultAdminRole",
|
||||
@@ -779,7 +798,7 @@ func (s SqlChannelStore) GetChannelUnread(channelId, userId string) (*model.Chan
|
||||
var unreadChannel model.ChannelUnread
|
||||
err := s.GetReplicaX().Get(&unreadChannel,
|
||||
`SELECT
|
||||
Channels.TeamId TeamId, Channels.Id ChannelId, (Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount, (Channels.TotalMsgCountRoot - ChannelMembers.MsgCountRoot) MsgCountRoot, ChannelMembers.MentionCount MentionCount, ChannelMembers.MentionCountRoot MentionCountRoot, ChannelMembers.NotifyProps NotifyProps
|
||||
Channels.TeamId TeamId, Channels.Id ChannelId, (Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount, (Channels.TotalMsgCountRoot - ChannelMembers.MsgCountRoot) MsgCountRoot, ChannelMembers.MentionCount MentionCount, ChannelMembers.MentionCountRoot MentionCountRoot, COALESCE(ChannelMembers.UrgentMentionCount, 0) UrgentMentionCount, ChannelMembers.NotifyProps NotifyProps
|
||||
FROM
|
||||
Channels, ChannelMembers
|
||||
WHERE
|
||||
@@ -1612,7 +1631,20 @@ func (s SqlChannelStore) GetDeleted(teamId string, offset int, limit int, userId
|
||||
|
||||
var channelMembersWithSchemeSelectQuery = `
|
||||
SELECT
|
||||
ChannelMembers.*,
|
||||
ChannelMembers.ChannelId,
|
||||
ChannelMembers.UserId,
|
||||
ChannelMembers.Roles,
|
||||
ChannelMembers.LastViewedAt,
|
||||
ChannelMembers.MsgCount,
|
||||
ChannelMembers.MentionCount,
|
||||
ChannelMembers.MentionCountRoot,
|
||||
COALESCE(ChannelMembers.UrgentMentionCount, 0) AS UrgentMentionCount,
|
||||
ChannelMembers.MsgCountRoot,
|
||||
ChannelMembers.NotifyProps,
|
||||
ChannelMembers.LastUpdateAt,
|
||||
ChannelMembers.SchemeUser,
|
||||
ChannelMembers.SchemeAdmin,
|
||||
ChannelMembers.SchemeGuest,
|
||||
COALESCE(Teams.DisplayName, '') TeamDisplayName,
|
||||
COALESCE(Teams.Name, '') TeamName,
|
||||
COALESCE(Teams.UpdateAt, 0) TeamUpdateAt,
|
||||
@@ -2048,7 +2080,20 @@ func (s SqlChannelStore) GetMemberForPost(postId string, userId string) (*model.
|
||||
var dbMember channelMemberWithSchemeRoles
|
||||
query := `
|
||||
SELECT
|
||||
ChannelMembers.*,
|
||||
ChannelMembers.ChannelId,
|
||||
ChannelMembers.UserId,
|
||||
ChannelMembers.Roles,
|
||||
ChannelMembers.LastViewedAt,
|
||||
ChannelMembers.MsgCount,
|
||||
ChannelMembers.MentionCount,
|
||||
ChannelMembers.MentionCountRoot,
|
||||
COALESCE(ChannelMembers.UrgentMentionCount, 0) AS UrgentMentionCount,
|
||||
ChannelMembers.MsgCountRoot,
|
||||
ChannelMembers.NotifyProps,
|
||||
ChannelMembers.LastUpdateAt,
|
||||
ChannelMembers.SchemeUser,
|
||||
ChannelMembers.SchemeAdmin,
|
||||
ChannelMembers.SchemeGuest,
|
||||
TeamScheme.DefaultChannelGuestRole TeamSchemeDefaultGuestRole,
|
||||
TeamScheme.DefaultChannelUserRole TeamSchemeDefaultUserRole,
|
||||
TeamScheme.DefaultChannelAdminRole TeamSchemeDefaultAdminRole,
|
||||
@@ -2438,6 +2483,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string)
|
||||
Update("ChannelMembers cm").
|
||||
Set("MentionCount", 0).
|
||||
Set("MentionCountRoot", 0).
|
||||
Set("UrgentMentionCount", 0).
|
||||
Set("MsgCount", sq.Expr("greatest(cm.MsgCount, c.TotalMsgCount)")).
|
||||
Set("MsgCountRoot", sq.Expr("greatest(cm.MsgCountRoot, c.TotalMsgCountRoot)")).
|
||||
Set("LastViewedAt", sq.Expr("greatest(cm.LastViewedAt, c.LastPostAt)")).
|
||||
@@ -2497,6 +2543,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string)
|
||||
updateQuery := s.getQueryBuilder().Update("ChannelMembers").
|
||||
Set("MentionCount", 0).
|
||||
Set("MentionCountRoot", 0).
|
||||
Set("UrgentMentionCount", 0).
|
||||
Set("MsgCount", msgCountQuery).
|
||||
Set("MsgCountRoot", msgCountQueryRoot).
|
||||
Set("LastViewedAt", lastViewedQuery).
|
||||
@@ -2518,6 +2565,31 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string)
|
||||
return times, nil
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) CountUrgentPostsAfter(channelId string, timestamp int64, userId string) (int, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select("count(*)").
|
||||
From("PostsPriority").
|
||||
Join("Posts ON Posts.Id = PostsPriority.PostId").
|
||||
Where(sq.And{
|
||||
sq.Eq{"PostsPriority.Priority": model.PostPriorityUrgent},
|
||||
sq.Eq{"Posts.ChannelId": channelId},
|
||||
sq.Gt{"Posts.CreateAt": timestamp},
|
||||
sq.Eq{"Posts.DeleteAt": 0},
|
||||
})
|
||||
|
||||
if userId != "" {
|
||||
query = query.Where(sq.Eq{"Posts.UserId": userId})
|
||||
}
|
||||
|
||||
var urgent int64
|
||||
err := s.GetReplicaX().GetBuilder(&urgent, query)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "failed to count urgent Posts")
|
||||
}
|
||||
|
||||
return int(urgent), nil
|
||||
}
|
||||
|
||||
// CountPostsAfter returns the number of posts in the given channel created after but not including the given timestamp. If given a non-empty user ID, only counts posts made by that user.
|
||||
func (s SqlChannelStore) CountPostsAfter(channelId string, timestamp int64, userId string) (int, int, error) {
|
||||
joinLeavePostTypes := []string{
|
||||
@@ -2566,13 +2638,14 @@ func (s SqlChannelStore) CountPostsAfter(channelId string, timestamp int64, user
|
||||
if err != nil {
|
||||
return 0, 0, errors.Wrap(err, "failed to count root Posts")
|
||||
}
|
||||
|
||||
return int(unread), int(unreadRoot), nil
|
||||
}
|
||||
|
||||
// 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
|
||||
// an updated model.ChannelUnreadAt that can be returned to the client.
|
||||
func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount, mentionCountRoot int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
|
||||
func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount, mentionCountRoot, urgentMentionCount int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
|
||||
unreadDate := unreadPost.CreateAt - 1
|
||||
|
||||
unread, unreadRoot, err := s.CountPostsAfter(unreadPost.ChannelId, unreadDate, "")
|
||||
@@ -2587,6 +2660,7 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s
|
||||
params := map[string]any{
|
||||
"mentions": mentionCount,
|
||||
"mentionsroot": mentionCountRoot,
|
||||
"urgentmentions": urgentMentionCount,
|
||||
"unreadcount": unread,
|
||||
"unreadcountroot": unreadRoot,
|
||||
"lastviewedat": unreadDate,
|
||||
@@ -2603,6 +2677,7 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s
|
||||
SET
|
||||
MentionCount = :mentions,
|
||||
MentionCountRoot = :mentionsroot,
|
||||
UrgentMentionCount = :urgentmentions,
|
||||
MsgCount = (SELECT TotalMsgCount FROM Channels WHERE ID = :channelid) - :unreadcount,
|
||||
MsgCountRoot = (SELECT TotalMsgCountRoot FROM Channels WHERE ID = :channelid) - :unreadcountroot,
|
||||
LastViewedAt = :lastviewedat,
|
||||
@@ -2625,6 +2700,7 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s
|
||||
cm.MsgCountRoot MsgCountRoot,
|
||||
cm.MentionCount MentionCount,
|
||||
cm.MentionCountRoot MentionCountRoot,
|
||||
COALESCE(cm.UrgentMentionCount, 0) UrgentMentionCount,
|
||||
cm.LastViewedAt LastViewedAt,
|
||||
cm.NotifyProps NotifyProps
|
||||
FROM
|
||||
@@ -2643,7 +2719,7 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) IncrementMentionCount(channelId string, userIDs []string, isRoot bool) error {
|
||||
func (s SqlChannelStore) IncrementMentionCount(channelId string, userIDs []string, isRoot bool, isUrgent bool) error {
|
||||
now := model.GetMillis()
|
||||
|
||||
rootInc := 0
|
||||
@@ -2651,10 +2727,16 @@ func (s SqlChannelStore) IncrementMentionCount(channelId string, userIDs []strin
|
||||
rootInc = 1
|
||||
}
|
||||
|
||||
urgentInc := 0
|
||||
if isUrgent {
|
||||
urgentInc = 1
|
||||
}
|
||||
|
||||
sql, args, err := s.getQueryBuilder().
|
||||
Update("ChannelMembers").
|
||||
Set("MentionCount", sq.Expr("MentionCount + 1")).
|
||||
Set("MentionCountRoot", sq.Expr("MentionCountRoot + ?", rootInc)).
|
||||
Set("UrgentMentionCount", sq.Expr("UrgentMentionCount + ?", urgentInc)).
|
||||
Set("LastUpdateAt", now).
|
||||
Where(sq.Eq{
|
||||
"UserId": userIDs,
|
||||
@@ -2832,7 +2914,21 @@ func (s SqlChannelStore) GetMembersForUser(teamID string, userID string) (model.
|
||||
|
||||
func (s SqlChannelStore) GetMembersForUserWithCursor(userID, teamID string, opts *store.ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select("ChannelMembers.*",
|
||||
Select(
|
||||
"ChannelMembers.ChannelId",
|
||||
"ChannelMembers.UserId",
|
||||
"ChannelMembers.Roles",
|
||||
"ChannelMembers.LastViewedAt",
|
||||
"ChannelMembers.MsgCount",
|
||||
"ChannelMembers.MentionCount",
|
||||
"ChannelMembers.MentionCountRoot",
|
||||
"COALESCE(ChannelMembers.UrgentMentionCount, 0) AS UrgentMentionCount",
|
||||
"ChannelMembers.MsgCountRoot",
|
||||
"ChannelMembers.NotifyProps",
|
||||
"ChannelMembers.LastUpdateAt",
|
||||
"ChannelMembers.SchemeUser",
|
||||
"ChannelMembers.SchemeAdmin",
|
||||
"ChannelMembers.SchemeGuest",
|
||||
"TeamScheme.DefaultChannelGuestRole TeamSchemeDefaultGuestRole",
|
||||
"TeamScheme.DefaultChannelUserRole TeamSchemeDefaultUserRole",
|
||||
"TeamScheme.DefaultChannelAdminRole TeamSchemeDefaultAdminRole",
|
||||
@@ -3790,6 +3886,7 @@ func (s SqlChannelStore) MigrateChannelMembers(fromChannelId string, fromUserId
|
||||
LastViewedAt=:LastViewedAt,
|
||||
MsgCount=:MsgCount,
|
||||
MentionCount=:MentionCount,
|
||||
UrgentMentionCount=:UrgentMentionCount,
|
||||
NotifyProps=:NotifyProps,
|
||||
LastUpdateAt=:LastUpdateAt,
|
||||
SchemeUser=:SchemeUser,
|
||||
@@ -3932,6 +4029,7 @@ func (s SqlChannelStore) GetChannelMembersForExport(userId string, teamId string
|
||||
ChannelMembers.MsgCount,
|
||||
ChannelMembers.MentionCount,
|
||||
ChannelMembers.MentionCountRoot,
|
||||
COALESCE(ChannelMembers.UrgentMentionCount, 0) AS UrgentMentionCount,
|
||||
ChannelMembers.NotifyProps,
|
||||
ChannelMembers.LastUpdateAt,
|
||||
ChannelMembers.SchemeUser,
|
||||
@@ -3981,7 +4079,7 @@ func (s SqlChannelStore) GetAllDirectChannelsForExportAfter(limit int, afterId s
|
||||
channelIds = append(channelIds, channel.Id)
|
||||
}
|
||||
query = s.getQueryBuilder().
|
||||
Select("u.Username as Username, ChannelId, UserId, cm.Roles as Roles, LastViewedAt, MsgCount, MentionCount, MentionCountRoot, cm.NotifyProps as NotifyProps, LastUpdateAt, SchemeUser, SchemeAdmin, (SchemeGuest IS NOT NULL AND SchemeGuest) as SchemeGuest").
|
||||
Select("u.Username as Username, ChannelId, UserId, cm.Roles as Roles, LastViewedAt, MsgCount, MentionCount, MentionCountRoot, COALESCE(UrgentMentionCount, 0) UrgentMentionCount, cm.NotifyProps as NotifyProps, LastUpdateAt, SchemeUser, SchemeAdmin, (SchemeGuest IS NOT NULL AND SchemeGuest) as SchemeGuest").
|
||||
From("ChannelMembers cm").
|
||||
Join("Users u ON ( u.Id = cm.UserId )").
|
||||
Where(sq.And{
|
||||
|
||||
63
store/sqlstore/post_priority_store.go
Обычный файл
63
store/sqlstore/post_priority_store.go
Обычный файл
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
sq "github.com/mattermost/squirrel"
|
||||
)
|
||||
|
||||
type SqlPostPriorityStore struct {
|
||||
*SqlStore
|
||||
}
|
||||
|
||||
func newSqlPostPriorityStore(sqlStore *SqlStore) store.PostPriorityStore {
|
||||
return &SqlPostPriorityStore{
|
||||
SqlStore: sqlStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SqlPostPriorityStore) GetForPost(postId string) (*model.PostPriority, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select("Priority", "RequestedAck", "PersistentNotifications").
|
||||
From("PostsPriority").
|
||||
Where(sq.Eq{"PostId": postId})
|
||||
|
||||
var postPriority model.PostPriority
|
||||
err := s.GetReplicaX().GetBuilder(&postPriority, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &postPriority, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostPriorityStore) GetForPosts(postIds []string) ([]*model.PostPriority, error) {
|
||||
var priority []*model.PostPriority
|
||||
|
||||
perPage := 200
|
||||
for i := 0; i < len(postIds); i += perPage {
|
||||
j := i + perPage
|
||||
if len(postIds) < j {
|
||||
j = len(postIds)
|
||||
}
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Select("PostId", "Priority", "RequestedAck", "PersistentNotifications").
|
||||
From("PostsPriority").
|
||||
Where(sq.Eq{"PostId": postIds[i:j]})
|
||||
|
||||
var priorityBatch []*model.PostPriority
|
||||
err := s.GetReplicaX().SelectBuilder(&priority, query)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
priority = append(priority, priorityBatch...)
|
||||
}
|
||||
|
||||
return priority, nil
|
||||
}
|
||||
14
store/sqlstore/post_priority_store_test.go
Обычный файл
14
store/sqlstore/post_priority_store_test.go
Обычный файл
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/store/storetest"
|
||||
)
|
||||
|
||||
func TestPostPriorityStore(t *testing.T) {
|
||||
StoreTestWithSqlStore(t, storetest.TestPostPriorityStore)
|
||||
}
|
||||
@@ -219,6 +219,10 @@ func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, er
|
||||
return nil, -1, errors.Wrap(err, "update thread from posts failed")
|
||||
}
|
||||
|
||||
if err = s.savePostsPriority(transaction, posts); err != nil {
|
||||
return nil, -1, errors.Wrap(err, "failed to save PostPriority")
|
||||
}
|
||||
|
||||
if err = transaction.Commit(); err != nil {
|
||||
// don't need to rollback here since the transaction is already closed
|
||||
return posts, -1, errors.Wrap(err, "commit_transaction")
|
||||
@@ -2920,6 +2924,24 @@ func (s *SqlPostStore) updateThreadAfterReplyDeletion(transaction *sqlxTxWrapper
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) savePostsPriority(transaction *sqlxTxWrapper, posts []*model.Post) error {
|
||||
for _, post := range posts {
|
||||
if post.GetPriority() != nil {
|
||||
postPriority := &model.PostPriority{
|
||||
PostId: post.Id,
|
||||
ChannelId: post.ChannelId,
|
||||
Priority: post.Metadata.Priority.Priority,
|
||||
RequestedAck: post.Metadata.Priority.RequestedAck,
|
||||
PersistentNotifications: post.Metadata.Priority.PersistentNotifications,
|
||||
}
|
||||
if _, err := transaction.NamedExec(`INSERT INTO PostsPriority (PostId, ChannelId, Priority, RequestedAck, PersistentNotifications) VALUES (:PostId, :ChannelId, :Priority, :RequestedAck, :PersistentNotifications)`, postPriority); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) updateThreadsFromPosts(transaction *sqlxTxWrapper, posts []*model.Post) error {
|
||||
postsByRoot := map[string][]*model.Post{}
|
||||
var rootIds []string
|
||||
|
||||
@@ -109,6 +109,7 @@ type SqlStoreStores struct {
|
||||
linkMetadata store.LinkMetadataStore
|
||||
sharedchannel store.SharedChannelStore
|
||||
notifyAdmin store.NotifyAdminStore
|
||||
postPriority store.PostPriorityStore
|
||||
}
|
||||
|
||||
type SqlStore struct {
|
||||
@@ -214,6 +215,7 @@ func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlS
|
||||
store.stores.group = newSqlGroupStore(store)
|
||||
store.stores.productNotices = newSqlProductNoticesStore(store)
|
||||
store.stores.notifyAdmin = newSqlNotifyAdminStore(store)
|
||||
store.stores.postPriority = newSqlPostPriorityStore(store)
|
||||
|
||||
store.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures()
|
||||
|
||||
@@ -955,6 +957,10 @@ func (ss *SqlStore) SharedChannel() store.SharedChannelStore {
|
||||
return ss.stores.sharedchannel
|
||||
}
|
||||
|
||||
func (ss *SqlStore) PostPriority() store.PostPriorityStore {
|
||||
return ss.stores.postPriority
|
||||
}
|
||||
|
||||
func (ss *SqlStore) DropAllTables() {
|
||||
if ss.DriverName() == model.DatabaseDriverPostgres {
|
||||
ss.masterX.Exec(`DO
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
sq "github.com/mattermost/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
@@ -30,6 +30,7 @@ type JoinedThread struct {
|
||||
Participants model.StringArray
|
||||
ThreadDeleteAt int64
|
||||
TeamId string
|
||||
IsUrgent bool
|
||||
model.Post
|
||||
}
|
||||
|
||||
@@ -51,6 +52,7 @@ func (thread *JoinedThread) toThreadResponse(users map[string]*model.User) *mode
|
||||
Participants: threadParticipants,
|
||||
Post: thread.Post.ToNilIfInvalid(),
|
||||
DeleteAt: thread.ThreadDeleteAt,
|
||||
IsUrgent: thread.IsUrgent,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +215,46 @@ func (s *SqlThreadStore) GetTotalUnreadMentions(userId, teamId string, opts mode
|
||||
return totalUnreadMentions, nil
|
||||
}
|
||||
|
||||
// GetTotalUnreadUrgentMentions counts the number of unread mentions for the given user, optionally
|
||||
// constrained to the given team + DMs/GMs.
|
||||
func (s *SqlThreadStore) GetTotalUnreadUrgentMentions(userId, teamId string, opts model.GetUserThreadsOpts) (int64, error) {
|
||||
var totalUnreadUrgentMentions int64
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Select("COALESCE(SUM(ThreadMemberships.UnreadMentions),0)").
|
||||
From("ThreadMemberships").
|
||||
Join("PostsPriority ON PostsPriority.PostId = ThreadMemberships.PostId").
|
||||
Where(sq.Eq{
|
||||
"ThreadMemberships.UserId": userId,
|
||||
"ThreadMemberships.Following": true,
|
||||
"PostsPriority.Priority": model.PostPriorityUrgent,
|
||||
})
|
||||
|
||||
if teamId != "" || !opts.Deleted {
|
||||
query = query.Join("Threads ON Threads.PostId = ThreadMemberships.PostId")
|
||||
}
|
||||
|
||||
if teamId != "" {
|
||||
query = query.
|
||||
Where(sq.Or{
|
||||
sq.Eq{"Threads.ThreadTeamId": teamId},
|
||||
sq.Eq{"Threads.ThreadTeamId": ""},
|
||||
})
|
||||
}
|
||||
|
||||
if !opts.Deleted {
|
||||
query = query.
|
||||
Where(sq.Eq{"COALESCE(Threads.ThreadDeleteAt, 0)": 0})
|
||||
}
|
||||
|
||||
err := s.GetReplicaX().GetBuilder(&totalUnreadUrgentMentions, query)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "failed to count unread urgent mentions for user id=%s", userId)
|
||||
}
|
||||
|
||||
return totalUnreadUrgentMentions, nil
|
||||
}
|
||||
|
||||
func (s *SqlThreadStore) GetThreadsForUser(userId, teamId string, opts model.GetUserThreadsOpts) ([]*model.ThreadResponse, error) {
|
||||
pageSize := uint64(30)
|
||||
if opts.PageSize != 0 {
|
||||
@@ -243,6 +285,17 @@ func (s *SqlThreadStore) GetThreadsForUser(userId, teamId string, opts model.Get
|
||||
Where(sq.Eq{"ThreadMemberships.UserId": userId}).
|
||||
Where(sq.Eq{"ThreadMemberships.Following": true})
|
||||
|
||||
if opts.IncludeIsUrgent {
|
||||
urgencyCase := sq.
|
||||
Case().
|
||||
When(sq.Eq{"PostsPriority.Priority": model.PostPriorityUrgent}, "true").
|
||||
Else("false")
|
||||
|
||||
query = query.
|
||||
Column(sq.Alias(urgencyCase, "IsUrgent")).
|
||||
LeftJoin("PostsPriority ON PostsPriority.PostId = Threads.PostId")
|
||||
}
|
||||
|
||||
// If a team is specified, constrain to channels in that team or DMs/GMs without
|
||||
// a team at all.
|
||||
if teamId != "" {
|
||||
@@ -322,7 +375,7 @@ func (s *SqlThreadStore) GetThreadsForUser(userId, teamId string, opts model.Get
|
||||
|
||||
// GetTeamsUnreadForUser returns the total unread threads and unread mentions
|
||||
// for a user from all teams.
|
||||
func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string) (map[string]*model.TeamUnread, error) {
|
||||
func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string, includeUrgentMentionCount bool) (map[string]*model.TeamUnread, error) {
|
||||
fetchConditions := sq.And{
|
||||
sq.Eq{"ThreadMemberships.UserId": userID},
|
||||
sq.Eq{"ThreadMemberships.Following": true},
|
||||
@@ -330,8 +383,7 @@ func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string)
|
||||
sq.Eq{"COALESCE(Threads.ThreadDeleteAt, 0)": 0},
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var err1, err2 error
|
||||
var eg errgroup.Group
|
||||
|
||||
unreadThreads := []struct {
|
||||
Count int64
|
||||
@@ -341,13 +393,15 @@ func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string)
|
||||
Count int64
|
||||
TeamId string
|
||||
}{}
|
||||
unreadUrgentMentions := []struct {
|
||||
Count int64
|
||||
TeamId string
|
||||
}{}
|
||||
|
||||
// Running these concurrently hasn't shown any major downside
|
||||
// than running them serially. So using a bit of perf boost.
|
||||
// In any case, they will be replaced by computed columns later.
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
eg.Go(func() error {
|
||||
repliesQuery := s.getQueryBuilder().
|
||||
Select("COUNT(Threads.PostId) AS Count, ThreadTeamId AS TeamId").
|
||||
From("Threads").
|
||||
@@ -356,15 +410,10 @@ func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string)
|
||||
Where("Threads.LastReplyAt > ThreadMemberships.LastViewed").
|
||||
GroupBy("Threads.ThreadTeamId")
|
||||
|
||||
err := s.GetReplicaX().SelectBuilder(&unreadThreads, repliesQuery)
|
||||
if err != nil {
|
||||
err1 = errors.Wrap(err, "failed to get total unread threads")
|
||||
}
|
||||
}()
|
||||
return errors.Wrap(s.GetReplicaX().SelectBuilder(&unreadThreads, repliesQuery), "failed to get total unread threads")
|
||||
})
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
eg.Go(func() error {
|
||||
mentionsQuery := s.getQueryBuilder().
|
||||
Select("COALESCE(SUM(ThreadMemberships.UnreadMentions),0) AS Count, ThreadTeamId AS TeamId").
|
||||
From("ThreadMemberships").
|
||||
@@ -372,20 +421,27 @@ func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string)
|
||||
Where(fetchConditions).
|
||||
GroupBy("Threads.ThreadTeamId")
|
||||
|
||||
err := s.GetReplicaX().SelectBuilder(&unreadMentions, mentionsQuery)
|
||||
if err != nil {
|
||||
err2 = errors.Wrap(err, "failed to get total unread mentions")
|
||||
return errors.Wrap(s.GetReplicaX().SelectBuilder(&unreadMentions, mentionsQuery), "failed to get total unread mentions")
|
||||
})
|
||||
|
||||
if includeUrgentMentionCount {
|
||||
eg.Go(func() error {
|
||||
urgentMentionsQuery := s.getQueryBuilder().
|
||||
Select("COALESCE(SUM(ThreadMemberships.UnreadMentions),0) AS Count, ThreadTeamId AS TeamId").
|
||||
From("ThreadMemberships").
|
||||
LeftJoin("Threads ON Threads.PostId = ThreadMemberships.PostId").
|
||||
Join("PostsPriority ON PostsPriority.PostId = ThreadMemberships.PostId").
|
||||
Where(sq.Eq{"PostsPriority.Priority": model.PostPriorityUrgent}).
|
||||
Where(fetchConditions).
|
||||
GroupBy("Threads.ThreadTeamId")
|
||||
|
||||
return errors.Wrap(s.GetReplicaX().SelectBuilder(&unreadUrgentMentions, urgentMentionsQuery), "failed to get total unread urgent mentions")
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for them to be over
|
||||
wg.Wait()
|
||||
|
||||
if err1 != nil {
|
||||
return nil, err1
|
||||
}
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make(map[string]*model.TeamUnread)
|
||||
@@ -405,6 +461,15 @@ func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, item := range unreadUrgentMentions {
|
||||
if _, ok := res[item.TeamId]; ok {
|
||||
res[item.TeamId].ThreadUrgentMentionCount = item.Count
|
||||
} else {
|
||||
res[item.TeamId] = &model.TeamUnread{
|
||||
ThreadUrgentMentionCount: item.Count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
@@ -436,7 +501,7 @@ func (s *SqlThreadStore) GetThreadFollowers(threadID string, fetchOnlyActive boo
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (s *SqlThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, error) {
|
||||
func (s *SqlThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended, postPriorityEnabled bool) (*model.ThreadResponse, error) {
|
||||
if !threadMembership.Following {
|
||||
return nil, nil // in case the thread is not followed anymore - return nil error to be interpreted as 404
|
||||
}
|
||||
@@ -462,6 +527,17 @@ func (s *SqlThreadStore) GetThreadForUser(threadMembership *model.ThreadMembersh
|
||||
LeftJoin("Posts ON Posts.Id = Threads.PostId").
|
||||
Where(sq.Eq{"Threads.PostId": threadMembership.PostId})
|
||||
|
||||
if postPriorityEnabled {
|
||||
urgencyCase := sq.
|
||||
Case().
|
||||
When(sq.Eq{"PostsPriority.Priority": model.PostPriorityUrgent}, "true").
|
||||
Else("false")
|
||||
|
||||
query = query.
|
||||
Column(sq.Alias(urgencyCase, "IsUrgent")).
|
||||
LeftJoin("PostsPriority ON PostsPriority.PostId = Threads.PostId")
|
||||
}
|
||||
|
||||
err := s.GetReplicaX().GetBuilder(&thread, query)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
|
||||
@@ -84,6 +84,7 @@ type Store interface {
|
||||
SetContext(context context.Context)
|
||||
Context() context.Context
|
||||
NotifyAdmin() NotifyAdminStore
|
||||
PostPriority() PostPriorityStore
|
||||
}
|
||||
|
||||
type RetentionPolicyStore interface {
|
||||
@@ -240,9 +241,10 @@ type ChannelStore interface {
|
||||
PermanentDeleteMembersByUser(userID string) error
|
||||
PermanentDeleteMembersByChannel(channelID string) error
|
||||
UpdateLastViewedAt(channelIds []string, userID string) (map[string]int64, error)
|
||||
UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount, mentionCountRoot int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error)
|
||||
UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount, mentionCountRoot, urgentMentionCount int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error)
|
||||
CountPostsAfter(channelID string, timestamp int64, userID string) (int, int, error)
|
||||
IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error
|
||||
CountUrgentPostsAfter(channelID string, timestamp int64, userID string) (int, error)
|
||||
IncrementMentionCount(channelID string, userIDs []string, isRoot, isUrgent bool) error
|
||||
AnalyticsTypeCount(teamID string, channelType model.ChannelType) (int64, error)
|
||||
GetMembersForUser(teamID string, userID string) (model.ChannelMembers, error)
|
||||
GetTeamMembersForChannel(channelID string) ([]string, error)
|
||||
@@ -322,9 +324,10 @@ type ThreadStore interface {
|
||||
GetTotalUnreadThreads(userId, teamID string, opts model.GetUserThreadsOpts) (int64, error)
|
||||
GetTotalThreads(userId, teamID string, opts model.GetUserThreadsOpts) (int64, error)
|
||||
GetTotalUnreadMentions(userId, teamID string, opts model.GetUserThreadsOpts) (int64, error)
|
||||
GetTotalUnreadUrgentMentions(userId, teamID string, opts model.GetUserThreadsOpts) (int64, error)
|
||||
GetThreadsForUser(userId, teamID string, opts model.GetUserThreadsOpts) ([]*model.ThreadResponse, error)
|
||||
GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, error)
|
||||
GetTeamsUnreadForUser(userID string, teamIDs []string) (map[string]*model.TeamUnread, error)
|
||||
GetThreadForUser(threadMembership *model.ThreadMembership, extended, postPriorityIsEnabled bool) (*model.ThreadResponse, error)
|
||||
GetTeamsUnreadForUser(userID string, teamIDs []string, includeUrgentMentionCount bool) (map[string]*model.TeamUnread, error)
|
||||
|
||||
MarkAllAsRead(userID string, threadIds []string) error
|
||||
MarkAllAsReadByTeam(userID, teamID string) error
|
||||
@@ -970,6 +973,11 @@ type SharedChannelStore interface {
|
||||
UpdateAttachmentLastSyncAt(id string, syncTime int64) error
|
||||
}
|
||||
|
||||
type PostPriorityStore interface {
|
||||
GetForPost(postId string) (*model.PostPriority, error)
|
||||
GetForPosts(ids []string) ([]*model.PostPriority, error)
|
||||
}
|
||||
|
||||
// ChannelSearchOpts contains options for searching channels.
|
||||
//
|
||||
// NotAssociatedToGroup will exclude channels that have associated, active GroupChannels records.
|
||||
|
||||
@@ -104,6 +104,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlStore) {
|
||||
t.Run("GetMembersForUserWithCursor", func(t *testing.T) { testChannelStoreGetMembersForUserWithCursor(t, ss) })
|
||||
t.Run("GetMembersForUserWithPagination", func(t *testing.T) { testChannelStoreGetMembersForUserWithPagination(t, ss) })
|
||||
t.Run("CountPostsAfter", func(t *testing.T) { testCountPostsAfter(t, ss) })
|
||||
t.Run("CountUrgentPostsAfter", func(t *testing.T) { testCountUrgentPostsAfter(t, ss) })
|
||||
t.Run("UpdateLastViewedAt", func(t *testing.T) { testChannelStoreUpdateLastViewedAt(t, ss) })
|
||||
t.Run("IncrementMentionCount", func(t *testing.T) { testChannelStoreIncrementMentionCount(t, ss) })
|
||||
t.Run("UpdateChannelMember", func(t *testing.T) { testUpdateChannelMember(t, ss) })
|
||||
@@ -4833,6 +4834,66 @@ func testCountPostsAfter(t *testing.T, ss store.Store) {
|
||||
})
|
||||
}
|
||||
|
||||
func testCountUrgentPostsAfter(t *testing.T, ss store.Store) {
|
||||
t.Run("should count all posts with or without the given user ID", func(t *testing.T) {
|
||||
userId1 := model.NewId()
|
||||
userId2 := model.NewId()
|
||||
|
||||
channelId := model.NewId()
|
||||
|
||||
p1, err := ss.Post().Save(&model.Post{
|
||||
UserId: userId1,
|
||||
ChannelId: channelId,
|
||||
CreateAt: 1000,
|
||||
Metadata: &model.PostMetadata{
|
||||
Priority: &model.PostPriority{
|
||||
Priority: model.NewString(model.PostPriorityUrgent),
|
||||
RequestedAck: model.NewBool(false),
|
||||
PersistentNotifications: model.NewBool(false),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
UserId: userId1,
|
||||
ChannelId: channelId,
|
||||
CreateAt: 1001,
|
||||
Metadata: &model.PostMetadata{
|
||||
Priority: &model.PostPriority{
|
||||
Priority: model.NewString("important"),
|
||||
RequestedAck: model.NewBool(false),
|
||||
PersistentNotifications: model.NewBool(false),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
UserId: userId2,
|
||||
ChannelId: channelId,
|
||||
CreateAt: 1002,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
count, err := ss.Channel().CountUrgentPostsAfter(channelId, p1.CreateAt-1, "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
count, err = ss.Channel().CountUrgentPostsAfter(channelId, p1.CreateAt, "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
|
||||
count, err = ss.Channel().CountUrgentPostsAfter(channelId, p1.CreateAt-1, userId1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
count, err = ss.Channel().CountUrgentPostsAfter(channelId, p1.CreateAt, userId1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
})
|
||||
}
|
||||
|
||||
func testChannelStoreUpdateLastViewedAt(t *testing.T, ss store.Store) {
|
||||
o1 := model.Channel{}
|
||||
o1.TeamId = model.NewId()
|
||||
@@ -4912,16 +4973,16 @@ func testChannelStoreIncrementMentionCount(t *testing.T, ss store.Store) {
|
||||
_, err := ss.Channel().SaveMember(&m1)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ss.Channel().IncrementMentionCount(m1.ChannelId, []string{m1.UserId}, false)
|
||||
err = ss.Channel().IncrementMentionCount(m1.ChannelId, []string{m1.UserId}, false, false)
|
||||
require.NoError(t, err, "failed to update")
|
||||
|
||||
err = ss.Channel().IncrementMentionCount(m1.ChannelId, []string{"missing id"}, false)
|
||||
err = ss.Channel().IncrementMentionCount(m1.ChannelId, []string{"missing id"}, false, false)
|
||||
require.NoError(t, err, "failed to update")
|
||||
|
||||
err = ss.Channel().IncrementMentionCount("missing id", []string{m1.UserId}, false)
|
||||
err = ss.Channel().IncrementMentionCount("missing id", []string{m1.UserId}, false, false)
|
||||
require.NoError(t, err, "failed to update")
|
||||
|
||||
err = ss.Channel().IncrementMentionCount("missing id", []string{"missing id"}, false)
|
||||
err = ss.Channel().IncrementMentionCount("missing id", []string{"missing id"}, false, false)
|
||||
require.NoError(t, err, "failed to update")
|
||||
}
|
||||
|
||||
|
||||
@@ -192,6 +192,27 @@ func (_m *ChannelStore) CountPostsAfter(channelID string, timestamp int64, userI
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// CountUrgentPostsAfter provides a mock function with given fields: channelID, timestamp, userID
|
||||
func (_m *ChannelStore) CountUrgentPostsAfter(channelID string, timestamp int64, userID string) (int, error) {
|
||||
ret := _m.Called(channelID, timestamp, userID)
|
||||
|
||||
var r0 int
|
||||
if rf, ok := ret.Get(0).(func(string, int64, string) int); ok {
|
||||
r0 = rf(channelID, timestamp, userID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, int64, string) error); ok {
|
||||
r1 = rf(channelID, timestamp, userID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CreateDirectChannel provides a mock function with given fields: userID, otherUserID, channelOptions
|
||||
func (_m *ChannelStore) CreateDirectChannel(userID *model.User, otherUserID *model.User, channelOptions ...model.ChannelOption) (*model.Channel, error) {
|
||||
_va := make([]interface{}, len(channelOptions))
|
||||
@@ -1646,13 +1667,13 @@ func (_m *ChannelStore) GroupSyncedChannelCount() (int64, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// IncrementMentionCount provides a mock function with given fields: channelID, userIDs, isRoot
|
||||
func (_m *ChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error {
|
||||
ret := _m.Called(channelID, userIDs, isRoot)
|
||||
// IncrementMentionCount provides a mock function with given fields: channelID, userIDs, isRoot, isUrgent
|
||||
func (_m *ChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool, isUrgent bool) error {
|
||||
ret := _m.Called(channelID, userIDs, isRoot, isUrgent)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, []string, bool) error); ok {
|
||||
r0 = rf(channelID, userIDs, isRoot)
|
||||
if rf, ok := ret.Get(0).(func(string, []string, bool, bool) error); ok {
|
||||
r0 = rf(channelID, userIDs, isRoot, isUrgent)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
@@ -2192,13 +2213,13 @@ func (_m *ChannelStore) UpdateLastViewedAt(channelIds []string, userID string) (
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateLastViewedAtPost provides a mock function with given fields: unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot
|
||||
func (_m *ChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
|
||||
ret := _m.Called(unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot)
|
||||
// UpdateLastViewedAtPost provides a mock function with given fields: unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot
|
||||
func (_m *ChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, urgentMentionCount int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
|
||||
ret := _m.Called(unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot)
|
||||
|
||||
var r0 *model.ChannelUnreadAt
|
||||
if rf, ok := ret.Get(0).(func(*model.Post, string, int, int, bool) *model.ChannelUnreadAt); ok {
|
||||
r0 = rf(unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot)
|
||||
if rf, ok := ret.Get(0).(func(*model.Post, string, int, int, int, bool) *model.ChannelUnreadAt); ok {
|
||||
r0 = rf(unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.ChannelUnreadAt)
|
||||
@@ -2206,8 +2227,8 @@ func (_m *ChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID st
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*model.Post, string, int, int, bool) error); ok {
|
||||
r1 = rf(unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot)
|
||||
if rf, ok := ret.Get(1).(func(*model.Post, string, int, int, int, bool) error); ok {
|
||||
r1 = rf(unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
61
store/storetest/mocks/PostPriorityStore.go
Обычный файл
61
store/storetest/mocks/PostPriorityStore.go
Обычный файл
@@ -0,0 +1,61 @@
|
||||
// Code generated by mockery v2.10.4. DO NOT EDIT.
|
||||
|
||||
// Regenerate this file using `make store-mocks`.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/v6/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// PostPriorityStore is an autogenerated mock type for the PostPriorityStore type
|
||||
type PostPriorityStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetForPost provides a mock function with given fields: postId
|
||||
func (_m *PostPriorityStore) GetForPost(postId string) (*model.PostPriority, error) {
|
||||
ret := _m.Called(postId)
|
||||
|
||||
var r0 *model.PostPriority
|
||||
if rf, ok := ret.Get(0).(func(string) *model.PostPriority); ok {
|
||||
r0 = rf(postId)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.PostPriority)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(postId)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetForPosts provides a mock function with given fields: ids
|
||||
func (_m *PostPriorityStore) GetForPosts(ids []string) ([]*model.PostPriority, error) {
|
||||
ret := _m.Called(ids)
|
||||
|
||||
var r0 []*model.PostPriority
|
||||
if rf, ok := ret.Get(0).(func([]string) []*model.PostPriority); ok {
|
||||
r0 = rf(ids)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.PostPriority)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func([]string) error); ok {
|
||||
r1 = rf(ids)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
@@ -475,6 +475,22 @@ func (_m *Store) Post() store.PostStore {
|
||||
return r0
|
||||
}
|
||||
|
||||
// PostPriority provides a mock function with given fields:
|
||||
func (_m *Store) PostPriority() store.PostPriorityStore {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 store.PostPriorityStore
|
||||
if rf, ok := ret.Get(0).(func() store.PostPriorityStore); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(store.PostPriorityStore)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Preference provides a mock function with given fields:
|
||||
func (_m *Store) Preference() store.PreferenceStore {
|
||||
ret := _m.Called()
|
||||
|
||||
@@ -119,13 +119,13 @@ func (_m *ThreadStore) GetMembershipsForUser(userId string, teamID string) ([]*m
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetTeamsUnreadForUser provides a mock function with given fields: userID, teamIDs
|
||||
func (_m *ThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string) (map[string]*model.TeamUnread, error) {
|
||||
ret := _m.Called(userID, teamIDs)
|
||||
// GetTeamsUnreadForUser provides a mock function with given fields: userID, teamIDs, includeUrgentMentionCount
|
||||
func (_m *ThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string, includeUrgentMentionCount bool) (map[string]*model.TeamUnread, error) {
|
||||
ret := _m.Called(userID, teamIDs, includeUrgentMentionCount)
|
||||
|
||||
var r0 map[string]*model.TeamUnread
|
||||
if rf, ok := ret.Get(0).(func(string, []string) map[string]*model.TeamUnread); ok {
|
||||
r0 = rf(userID, teamIDs)
|
||||
if rf, ok := ret.Get(0).(func(string, []string, bool) map[string]*model.TeamUnread); ok {
|
||||
r0 = rf(userID, teamIDs, includeUrgentMentionCount)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]*model.TeamUnread)
|
||||
@@ -133,8 +133,8 @@ func (_m *ThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string) (m
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, []string) error); ok {
|
||||
r1 = rf(userID, teamIDs)
|
||||
if rf, ok := ret.Get(1).(func(string, []string, bool) error); ok {
|
||||
r1 = rf(userID, teamIDs, includeUrgentMentionCount)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@@ -165,13 +165,13 @@ func (_m *ThreadStore) GetThreadFollowers(threadID string, fetchOnlyActive bool)
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetThreadForUser provides a mock function with given fields: threadMembership, extended
|
||||
func (_m *ThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, error) {
|
||||
ret := _m.Called(threadMembership, extended)
|
||||
// GetThreadForUser provides a mock function with given fields: threadMembership, extended, postPriorityIsEnabled
|
||||
func (_m *ThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool, postPriorityIsEnabled bool) (*model.ThreadResponse, error) {
|
||||
ret := _m.Called(threadMembership, extended, postPriorityIsEnabled)
|
||||
|
||||
var r0 *model.ThreadResponse
|
||||
if rf, ok := ret.Get(0).(func(*model.ThreadMembership, bool) *model.ThreadResponse); ok {
|
||||
r0 = rf(threadMembership, extended)
|
||||
if rf, ok := ret.Get(0).(func(*model.ThreadMembership, bool, bool) *model.ThreadResponse); ok {
|
||||
r0 = rf(threadMembership, extended, postPriorityIsEnabled)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.ThreadResponse)
|
||||
@@ -179,8 +179,8 @@ func (_m *ThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*model.ThreadMembership, bool) error); ok {
|
||||
r1 = rf(threadMembership, extended)
|
||||
if rf, ok := ret.Get(1).(func(*model.ThreadMembership, bool, bool) error); ok {
|
||||
r1 = rf(threadMembership, extended, postPriorityIsEnabled)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@@ -341,6 +341,27 @@ func (_m *ThreadStore) GetTotalUnreadThreads(userId string, teamID string, opts
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetTotalUnreadUrgentMentions provides a mock function with given fields: userId, teamID, opts
|
||||
func (_m *ThreadStore) GetTotalUnreadUrgentMentions(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) {
|
||||
ret := _m.Called(userId, teamID, opts)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(string, string, model.GetUserThreadsOpts) int64); ok {
|
||||
r0 = rf(userId, teamID, opts)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, model.GetUserThreadsOpts) error); ok {
|
||||
r1 = rf(userId, teamID, opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MaintainMembership provides a mock function with given fields: userID, postID, opts
|
||||
func (_m *ThreadStore) MaintainMembership(userID string, postID string, opts store.ThreadMembershipOpts) (*model.ThreadMembership, error) {
|
||||
ret := _m.Called(userID, postID, opts)
|
||||
|
||||
72
store/storetest/post_priority_store.go
Обычный файл
72
store/storetest/post_priority_store.go
Обычный файл
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package storetest
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPostPriorityStore(t *testing.T, ss store.Store, s SqlStore) {
|
||||
t.Run("GetForPost", func(t *testing.T) { testPostPriorityStoreGetForPost(t, ss) })
|
||||
}
|
||||
|
||||
func testPostPriorityStoreGetForPost(t *testing.T, ss store.Store) {
|
||||
|
||||
t.Run("Save post priority when in post's metadata", func(t *testing.T) {
|
||||
p1 := model.Post{}
|
||||
p1.ChannelId = model.NewId()
|
||||
p1.UserId = model.NewId()
|
||||
p1.Message = NewTestId()
|
||||
p1.Metadata = &model.PostMetadata{
|
||||
Priority: &model.PostPriority{
|
||||
Priority: model.NewString("important"),
|
||||
RequestedAck: model.NewBool(true),
|
||||
PersistentNotifications: model.NewBool(false),
|
||||
},
|
||||
}
|
||||
|
||||
p2 := model.Post{}
|
||||
p2.ChannelId = model.NewId()
|
||||
p2.UserId = model.NewId()
|
||||
p2.Message = NewTestId()
|
||||
p2.Metadata = &model.PostMetadata{
|
||||
Priority: &model.PostPriority{
|
||||
Priority: model.NewString(model.PostPriorityUrgent),
|
||||
RequestedAck: model.NewBool(false),
|
||||
PersistentNotifications: model.NewBool(true),
|
||||
},
|
||||
}
|
||||
|
||||
p3 := model.Post{}
|
||||
p3.ChannelId = model.NewId()
|
||||
p3.UserId = model.NewId()
|
||||
p3.Message = NewTestId()
|
||||
|
||||
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, -1, errIdx)
|
||||
|
||||
pp1, err := ss.PostPriority().GetForPost(p1.Id)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "important", *pp1.Priority)
|
||||
assert.Equal(t, true, *pp1.RequestedAck)
|
||||
assert.Equal(t, false, *pp1.PersistentNotifications)
|
||||
|
||||
pp2, err := ss.PostPriority().GetForPost(p2.Id)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, model.PostPriorityUrgent, *pp2.Priority)
|
||||
assert.Equal(t, false, *pp2.RequestedAck)
|
||||
assert.Equal(t, true, *pp2.PersistentNotifications)
|
||||
|
||||
_, err = ss.PostPriority().GetForPost(p3.Id)
|
||||
assert.True(t, errors.Is(err, sql.ErrNoRows))
|
||||
})
|
||||
}
|
||||
@@ -238,6 +238,31 @@ func testPostStoreSave(t *testing.T, ss store.Store) {
|
||||
assert.Greater(t, rchannel3.LastPostAt, rchannel2.LastPostAt)
|
||||
assert.Equal(t, int64(3), rchannel3.TotalMsgCount)
|
||||
})
|
||||
|
||||
t.Run("Save post with priority metadata set", func(t *testing.T) {
|
||||
o1 := model.Post{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.UserId = model.NewId()
|
||||
o1.Message = NewTestId()
|
||||
|
||||
o1.Metadata = &model.PostMetadata{
|
||||
Priority: &model.PostPriority{
|
||||
Priority: model.NewString("important"),
|
||||
RequestedAck: model.NewBool(true),
|
||||
PersistentNotifications: model.NewBool(false),
|
||||
},
|
||||
}
|
||||
|
||||
p, err := ss.Post().Save(&o1)
|
||||
require.NoError(t, err, "couldn't save item")
|
||||
assert.Equal(t, int64(0), p.ReplyCount)
|
||||
|
||||
pp, err := ss.PostPriority().GetForPost(p.Id)
|
||||
require.NoError(t, err, "couldn't save item")
|
||||
assert.Equal(t, "important", *pp.Priority)
|
||||
assert.Equal(t, true, *pp.RequestedAck)
|
||||
assert.Equal(t, false, *pp.PersistentNotifications)
|
||||
})
|
||||
}
|
||||
|
||||
func testPostStoreSaveMultiple(t *testing.T, ss store.Store) {
|
||||
|
||||
@@ -56,6 +56,7 @@ type Store struct {
|
||||
ProductNoticesStore mocks.ProductNoticesStore
|
||||
context context.Context
|
||||
NotifyAdminStore mocks.NotifyAdminStore
|
||||
PostPriorityStore mocks.PostPriorityStore
|
||||
}
|
||||
|
||||
func (s *Store) SetContext(context context.Context) { s.context = context }
|
||||
@@ -100,6 +101,7 @@ func (s *Store) NotifyAdmin() store.NotifyAdminStore { return &s.NotifyAdmin
|
||||
func (s *Store) Group() store.GroupStore { return &s.GroupStore }
|
||||
func (s *Store) LinkMetadata() store.LinkMetadataStore { return &s.LinkMetadataStore }
|
||||
func (s *Store) SharedChannel() store.SharedChannelStore { return &s.SharedChannelStore }
|
||||
func (s *Store) PostPriority() store.PostPriorityStore { return &s.PostPriorityStore }
|
||||
func (s *Store) MarkSystemRanUnitTests() { /* do nothing */ }
|
||||
func (s *Store) Close() { /* do nothing */ }
|
||||
func (s *Store) LockToMaster() { /* do nothing */ }
|
||||
@@ -158,5 +160,6 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool {
|
||||
&s.ProductNoticesStore,
|
||||
&s.SharedChannelStore,
|
||||
&s.NotifyAdminStore,
|
||||
&s.PostPriorityStore,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestThreadStore(t *testing.T, ss store.Store, s SqlStore) {
|
||||
}
|
||||
|
||||
func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
makeSomePosts := func() []*model.Post {
|
||||
makeSomePosts := func(urgent bool) []*model.Post {
|
||||
|
||||
u1 := model.User{
|
||||
Email: MakeEmail(),
|
||||
@@ -61,6 +61,16 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
o.UserId = u.Id
|
||||
o.Message = NewTestId()
|
||||
|
||||
if urgent {
|
||||
o.Metadata = &model.PostMetadata{
|
||||
Priority: &model.PostPriority{
|
||||
Priority: model.NewString(model.PostPriorityUrgent),
|
||||
RequestedAck: model.NewBool(false),
|
||||
PersistentNotifications: model.NewBool(false),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
otmp, err3 := ss.Post().Save(&o)
|
||||
require.NoError(t, err3)
|
||||
o2 := model.Post{}
|
||||
@@ -100,7 +110,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
return newPosts
|
||||
}
|
||||
t.Run("Save replies creates a thread", func(t *testing.T) {
|
||||
newPosts := makeSomePosts()
|
||||
newPosts := makeSomePosts(false)
|
||||
thread, err := ss.Thread().Get(newPosts[0].Id)
|
||||
require.NoError(t, err, "couldn't get thread")
|
||||
require.NotNil(t, thread)
|
||||
@@ -133,7 +143,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
})
|
||||
|
||||
t.Run("Delete a reply updates count on a thread", func(t *testing.T) {
|
||||
newPosts := makeSomePosts()
|
||||
newPosts := makeSomePosts(false)
|
||||
thread, err := ss.Thread().Get(newPosts[0].Id)
|
||||
require.NoError(t, err, "couldn't get thread")
|
||||
require.NotNil(t, thread)
|
||||
@@ -307,7 +317,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
})
|
||||
|
||||
t.Run("Thread membership 'viewed' timestamp is updated properly", func(t *testing.T) {
|
||||
newPosts := makeSomePosts()
|
||||
newPosts := makeSomePosts(false)
|
||||
|
||||
opts := store.ThreadMembershipOpts{
|
||||
Following: true,
|
||||
@@ -341,7 +351,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
})
|
||||
|
||||
t.Run("Thread membership 'viewed' timestamp is updated properly for new membership", func(t *testing.T) {
|
||||
newPosts := makeSomePosts()
|
||||
newPosts := makeSomePosts(false)
|
||||
|
||||
opts := store.ThreadMembershipOpts{
|
||||
Following: true,
|
||||
@@ -356,7 +366,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
})
|
||||
|
||||
t.Run("Updating post does not make thread unread", func(t *testing.T) {
|
||||
newPosts := makeSomePosts()
|
||||
newPosts := makeSomePosts(false)
|
||||
opts := store.ThreadMembershipOpts{
|
||||
Following: true,
|
||||
IncrementMentions: false,
|
||||
@@ -366,14 +376,14 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
}
|
||||
m, err := ss.Thread().MaintainMembership(newPosts[0].UserId, newPosts[0].Id, opts)
|
||||
require.NoError(t, err)
|
||||
th, err := ss.Thread().GetThreadForUser(m, false)
|
||||
th, err := ss.Thread().GetThreadForUser(m, false, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(2), th.UnreadReplies)
|
||||
|
||||
m.LastViewed = newPosts[2].UpdateAt + 1
|
||||
_, err = ss.Thread().UpdateMembership(m)
|
||||
require.NoError(t, err)
|
||||
th, err = ss.Thread().GetThreadForUser(m, false)
|
||||
th, err = ss.Thread().GetThreadForUser(m, false, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(0), th.UnreadReplies)
|
||||
|
||||
@@ -382,13 +392,13 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
_, err = ss.Post().Update(editedPost, newPosts[2])
|
||||
require.NoError(t, err)
|
||||
|
||||
th, err = ss.Thread().GetThreadForUser(m, false)
|
||||
th, err = ss.Thread().GetThreadForUser(m, false, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(0), th.UnreadReplies)
|
||||
})
|
||||
|
||||
t.Run("Empty participantID should not appear in thread response", func(t *testing.T) {
|
||||
newPosts := makeSomePosts()
|
||||
newPosts := makeSomePosts(false)
|
||||
opts := store.ThreadMembershipOpts{
|
||||
Following: true,
|
||||
IncrementMentions: false,
|
||||
@@ -399,7 +409,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
m, err := ss.Thread().MaintainMembership("", newPosts[0].Id, opts)
|
||||
require.NoError(t, err)
|
||||
m.UserId = newPosts[0].UserId
|
||||
th, err := ss.Thread().GetThreadForUser(m, true)
|
||||
th, err := ss.Thread().GetThreadForUser(m, true, false)
|
||||
require.NoError(t, err)
|
||||
for _, user := range th.Participants {
|
||||
require.NotNil(t, user)
|
||||
@@ -407,7 +417,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
})
|
||||
t.Run("Get unread reply counts for thread", func(t *testing.T) {
|
||||
t.Skip("MM-41797")
|
||||
newPosts := makeSomePosts()
|
||||
newPosts := makeSomePosts(false)
|
||||
opts := store.ThreadMembershipOpts{
|
||||
Following: true,
|
||||
IncrementMentions: false,
|
||||
@@ -435,6 +445,36 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(2), unreads)
|
||||
})
|
||||
|
||||
testCases := []bool{true, false}
|
||||
|
||||
for _, isUrgent := range testCases {
|
||||
t.Run("Return is urgent for user thread/s", func(t *testing.T) {
|
||||
newPosts := makeSomePosts(isUrgent)
|
||||
opts := store.ThreadMembershipOpts{
|
||||
Following: true,
|
||||
IncrementMentions: false,
|
||||
UpdateFollowing: true,
|
||||
UpdateViewedTimestamp: true,
|
||||
UpdateParticipants: false,
|
||||
}
|
||||
|
||||
userID := newPosts[0].UserId
|
||||
_, e := ss.Thread().MaintainMembership(userID, newPosts[0].Id, opts)
|
||||
require.NoError(t, e)
|
||||
|
||||
m, e := ss.Thread().GetMembershipForUser(userID, newPosts[0].Id)
|
||||
require.NoError(t, e)
|
||||
|
||||
th, e := ss.Thread().GetThreadForUser(m, false, true)
|
||||
require.NoError(t, e)
|
||||
require.Equal(t, isUrgent, th.IsUrgent)
|
||||
|
||||
threads, e := ss.Thread().GetThreadsForUser(userID, "", model.GetUserThreadsOpts{IncludeIsUrgent: true})
|
||||
require.NoError(t, e)
|
||||
require.Equal(t, isUrgent, threads[0].IsUrgent)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func threadStoreCreateReply(t *testing.T, ss store.Store, channelID, postID, userID string, createAt int64) *model.Post {
|
||||
@@ -660,7 +700,7 @@ func testGetTeamsUnreadForUser(t *testing.T, ss store.Store) {
|
||||
threadStoreCreateReply(t, ss, channel1.Id, post.Id, post.UserId, model.GetMillis())
|
||||
createThreadMembership(userID, post.Id)
|
||||
|
||||
teamsUnread, err := ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id})
|
||||
teamsUnread, err := ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id}, true)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, teamsUnread, 1)
|
||||
assert.Equal(t, int64(1), teamsUnread[team1.Id].ThreadCount)
|
||||
@@ -674,7 +714,7 @@ func testGetTeamsUnreadForUser(t *testing.T, ss store.Store) {
|
||||
threadStoreCreateReply(t, ss, channel1.Id, post.Id, post.UserId, model.GetMillis())
|
||||
createThreadMembership(userID, post.Id)
|
||||
|
||||
teamsUnread, err = ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id})
|
||||
teamsUnread, err = ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id}, true)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, teamsUnread, 1)
|
||||
assert.Equal(t, int64(2), teamsUnread[team1.Id].ThreadCount)
|
||||
@@ -693,16 +733,24 @@ func testGetTeamsUnreadForUser(t *testing.T, ss store.Store) {
|
||||
Type: model.ChannelTypeOpen,
|
||||
}, -1)
|
||||
require.NoError(t, err)
|
||||
|
||||
post2, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: channel2.Id,
|
||||
UserId: userID,
|
||||
Message: model.NewRandomString(10),
|
||||
Metadata: &model.PostMetadata{
|
||||
Priority: &model.PostPriority{
|
||||
Priority: model.NewString(model.PostPriorityUrgent),
|
||||
RequestedAck: model.NewBool(false),
|
||||
PersistentNotifications: model.NewBool(false),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
threadStoreCreateReply(t, ss, channel2.Id, post2.Id, post2.UserId, model.GetMillis())
|
||||
createThreadMembership(userID, post2.Id)
|
||||
|
||||
teamsUnread, err = ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id, team2.Id})
|
||||
teamsUnread, err = ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id, team2.Id}, true)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, teamsUnread, 2)
|
||||
assert.Equal(t, int64(2), teamsUnread[team1.Id].ThreadCount)
|
||||
@@ -715,11 +763,12 @@ func testGetTeamsUnreadForUser(t *testing.T, ss store.Store) {
|
||||
_, err = ss.Thread().MaintainMembership(userID, post2.Id, opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
teamsUnread, err = ss.Thread().GetTeamsUnreadForUser(userID, []string{team2.Id})
|
||||
teamsUnread, err = ss.Thread().GetTeamsUnreadForUser(userID, []string{team2.Id}, true)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, teamsUnread, 1)
|
||||
assert.Equal(t, int64(1), teamsUnread[team2.Id].ThreadCount)
|
||||
assert.Equal(t, int64(1), teamsUnread[team2.Id].ThreadMentionCount)
|
||||
assert.Equal(t, int64(1), teamsUnread[team2.Id].ThreadUrgentMentionCount)
|
||||
}
|
||||
|
||||
type byPostId []*model.Post
|
||||
@@ -831,6 +880,13 @@ func testVarious(t *testing.T, ss store.Store) {
|
||||
ChannelId: team1channel1.Id,
|
||||
UserId: user1ID,
|
||||
Message: model.NewRandomString(10),
|
||||
Metadata: &model.PostMetadata{
|
||||
Priority: &model.PostPriority{
|
||||
Priority: model.NewString(model.PostPriorityUrgent),
|
||||
RequestedAck: model.NewBool(false),
|
||||
PersistentNotifications: model.NewBool(false),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1032,6 +1088,33 @@ func testVarious(t *testing.T, ss store.Store) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetTotalUnreadUrgentMentions", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Description string
|
||||
UserID string
|
||||
TeamID string
|
||||
Options model.GetUserThreadsOpts
|
||||
ExpectedThreads []*model.Post
|
||||
}{
|
||||
{"all teams, user1", user1ID, "", model.GetUserThreadsOpts{}, []*model.Post{
|
||||
team1channel1post3,
|
||||
}},
|
||||
{"team1, user1", user1ID, team1.Id, model.GetUserThreadsOpts{}, []*model.Post{
|
||||
team1channel1post3,
|
||||
}},
|
||||
{"team2, user1", user1ID, team2.Id, model.GetUserThreadsOpts{}, []*model.Post{}},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
totalUnreadUrgentMentions, err := ss.Thread().GetTotalUnreadUrgentMentions(testCase.UserID, testCase.TeamID, testCase.Options)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, int64(len(testCase.ExpectedThreads)), totalUnreadUrgentMentions)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
assertThreadPosts := func(t *testing.T, threads []*model.ThreadResponse, expectedPosts []*model.Post) {
|
||||
t.Helper()
|
||||
|
||||
@@ -1166,7 +1249,7 @@ func testMarkAllAsReadByChannels(t *testing.T, ss store.Store) {
|
||||
assertThreadReplyCount := func(t *testing.T, userID string, count int64) {
|
||||
t.Helper()
|
||||
|
||||
teamsUnread, err := ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id})
|
||||
teamsUnread, err := ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id}, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, teamsUnread, 1, "unexpected unread teams count")
|
||||
assert.Equal(t, count, teamsUnread[team1.Id].ThreadCount, "unexpected thread count")
|
||||
@@ -1623,7 +1706,7 @@ func testMarkAllAsReadByTeam(t *testing.T, ss store.Store) {
|
||||
assertThreadReplyCount := func(t *testing.T, userID, teamID string, count int64, message string) {
|
||||
t.Helper()
|
||||
|
||||
teamsUnread, err := ss.Thread().GetTeamsUnreadForUser(userID, []string{teamID})
|
||||
teamsUnread, err := ss.Thread().GetTeamsUnreadForUser(userID, []string{teamID}, true)
|
||||
require.NoError(t, err)
|
||||
require.Lenf(t, teamsUnread, 1, "unexpected unread teams count: %s", message)
|
||||
assert.Equalf(t, count, teamsUnread[teamID].ThreadCount, "unexpected thread count: %s", message)
|
||||
|
||||
@@ -2468,7 +2468,7 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
|
||||
// Post one message with mention to open channel
|
||||
_, nErr = ss.Post().Save(&p1)
|
||||
require.NoError(t, nErr)
|
||||
nErr = ss.Channel().IncrementMentionCount(c1.Id, []string{u2.Id, u3.Id}, false)
|
||||
nErr = ss.Channel().IncrementMentionCount(c1.Id, []string{u2.Id, u3.Id}, false, false)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Post 2 messages without mention to direct channel
|
||||
@@ -2479,7 +2479,7 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
|
||||
|
||||
_, nErr = ss.Post().Save(&p2)
|
||||
require.NoError(t, nErr)
|
||||
nErr = ss.Channel().IncrementMentionCount(c2.Id, []string{u2.Id}, false)
|
||||
nErr = ss.Channel().IncrementMentionCount(c2.Id, []string{u2.Id}, false, false)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
p3 := model.Post{}
|
||||
@@ -2489,7 +2489,7 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
|
||||
_, nErr = ss.Post().Save(&p3)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
nErr = ss.Channel().IncrementMentionCount(c2.Id, []string{u2.Id}, false)
|
||||
nErr = ss.Channel().IncrementMentionCount(c2.Id, []string{u2.Id}, false, false)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
badge, unreadCountErr := ss.User().GetUnreadCount(u2.Id, false)
|
||||
@@ -2501,7 +2501,7 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
|
||||
require.Equal(t, int64(1), badge, "should have 1 unread message")
|
||||
|
||||
// Increment root mentions by 1
|
||||
nErr = ss.Channel().IncrementMentionCount(c1.Id, []string{u3.Id}, true)
|
||||
nErr = ss.Channel().IncrementMentionCount(c1.Id, []string{u3.Id}, true, false)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// CRT is enabled, only root mentions are counted
|
||||
|
||||
@@ -36,6 +36,7 @@ type TimerLayer struct {
|
||||
OAuthStore store.OAuthStore
|
||||
PluginStore store.PluginStore
|
||||
PostStore store.PostStore
|
||||
PostPriorityStore store.PostPriorityStore
|
||||
PreferenceStore store.PreferenceStore
|
||||
ProductNoticesStore store.ProductNoticesStore
|
||||
ReactionStore store.ReactionStore
|
||||
@@ -130,6 +131,10 @@ func (s *TimerLayer) Post() store.PostStore {
|
||||
return s.PostStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) PostPriority() store.PostPriorityStore {
|
||||
return s.PostPriorityStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) Preference() store.PreferenceStore {
|
||||
return s.PreferenceStore
|
||||
}
|
||||
@@ -300,6 +305,11 @@ type TimerLayerPostStore struct {
|
||||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerPostPriorityStore struct {
|
||||
store.PostPriorityStore
|
||||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerPreferenceStore struct {
|
||||
store.PreferenceStore
|
||||
Root *TimerLayer
|
||||
@@ -671,6 +681,22 @@ func (s *TimerLayerChannelStore) CountPostsAfter(channelID string, timestamp int
|
||||
return result, resultVar1, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelStore) CountUrgentPostsAfter(channelID string, timestamp int64, userID string) (int, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.ChannelStore.CountUrgentPostsAfter(channelID, timestamp, userID)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.CountUrgentPostsAfter", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelStore) CreateDirectChannel(userID *model.User, otherUserID *model.User, channelOptions ...model.ChannelOption) (*model.Channel, error) {
|
||||
start := time.Now()
|
||||
|
||||
@@ -1711,10 +1737,10 @@ func (s *TimerLayerChannelStore) GroupSyncedChannelCount() (int64, error) {
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error {
|
||||
func (s *TimerLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool, isUrgent bool) error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot)
|
||||
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot, isUrgent)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
@@ -2248,10 +2274,10 @@ func (s *TimerLayerChannelStore) UpdateLastViewedAt(channelIds []string, userID
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
|
||||
func (s *TimerLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, urgentMentionCount int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot)
|
||||
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
@@ -5891,6 +5917,38 @@ func (s *TimerLayerPostStore) Update(newPost *model.Post, oldPost *model.Post) (
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostPriorityStore) GetForPost(postId string) (*model.PostPriority, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PostPriorityStore.GetForPost(postId)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PostPriorityStore.GetForPost", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostPriorityStore) GetForPosts(ids []string) ([]*model.PostPriority, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PostPriorityStore.GetForPosts(ids)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PostPriorityStore.GetForPosts", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPreferenceStore) CleanupFlagsBatch(limit int64) (int64, error) {
|
||||
start := time.Now()
|
||||
|
||||
@@ -8881,10 +8939,10 @@ func (s *TimerLayerThreadStore) GetMembershipsForUser(userId string, teamID stri
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string) (map[string]*model.TeamUnread, error) {
|
||||
func (s *TimerLayerThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string, includeUrgentMentionCount bool) (map[string]*model.TeamUnread, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.ThreadStore.GetTeamsUnreadForUser(userID, teamIDs)
|
||||
result, err := s.ThreadStore.GetTeamsUnreadForUser(userID, teamIDs, includeUrgentMentionCount)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
@@ -8913,10 +8971,10 @@ func (s *TimerLayerThreadStore) GetThreadFollowers(threadID string, fetchOnlyAct
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, error) {
|
||||
func (s *TimerLayerThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool, postPriorityIsEnabled bool) (*model.ThreadResponse, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.ThreadStore.GetThreadForUser(threadMembership, extended)
|
||||
result, err := s.ThreadStore.GetThreadForUser(threadMembership, extended, postPriorityIsEnabled)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
@@ -9041,6 +9099,22 @@ func (s *TimerLayerThreadStore) GetTotalUnreadThreads(userId string, teamID stri
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerThreadStore) GetTotalUnreadUrgentMentions(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.ThreadStore.GetTotalUnreadUrgentMentions(userId, teamID, opts)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ThreadStore.GetTotalUnreadUrgentMentions", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerThreadStore) MaintainMembership(userID string, postID string, opts store.ThreadMembershipOpts) (*model.ThreadMembership, error) {
|
||||
start := time.Now()
|
||||
|
||||
@@ -11270,6 +11344,7 @@ func New(childStore store.Store, metrics einterfaces.MetricsInterface) *TimerLay
|
||||
newStore.OAuthStore = &TimerLayerOAuthStore{OAuthStore: childStore.OAuth(), Root: &newStore}
|
||||
newStore.PluginStore = &TimerLayerPluginStore{PluginStore: childStore.Plugin(), Root: &newStore}
|
||||
newStore.PostStore = &TimerLayerPostStore{PostStore: childStore.Post(), Root: &newStore}
|
||||
newStore.PostPriorityStore = &TimerLayerPostPriorityStore{PostPriorityStore: childStore.PostPriority(), Root: &newStore}
|
||||
newStore.PreferenceStore = &TimerLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
|
||||
newStore.ProductNoticesStore = &TimerLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}
|
||||
newStore.ReactionStore = &TimerLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore}
|
||||
|
||||
Ссылка в новой задаче
Block a user