MM-31339: Send only one direct message reply within one calendar day. (#17181)
* don't send auto response if already responded today * update query to get posts from channel for given user and Updatetime requires value in milli seconds * regenerate mocks and layers * update function to return true/false on existence of auto responded post in channel and add tests * add store tests * bubble up error and propagate upstream * fix error handling logic * use require instead of assert Co-authored-by: Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com> * rename variable for better redability and logging fixes * update comment explaining function * use new function to generate test ids * add comments to clarify NewTestId copies * add translations for error id * fix translation Co-authored-by: Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com> Co-authored-by: Mattermod <mattermod@users.noreply.github.com> Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com>
Этот коммит содержится в:
@@ -5448,6 +5448,24 @@ func (s *OpenTracingLayerPostStore) GetSingle(id string, inclDeleted bool) (*mod
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.HasAutoResponsePostByUserSince")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PostStore.HasAutoResponsePostByUserSince(options, userId)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPostStore) InvalidateLastPostTimeCache(channelID string) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.InvalidateLastPostTimeCache")
|
||||
|
||||
@@ -5884,6 +5884,26 @@ func (s *RetryLayerPostStore) GetSingle(id string, inclDeleted bool) (*model.Pos
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PostStore.HasAutoResponsePostByUserSince(options, 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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostStore) InvalidateLastPostTimeCache(channelID string) {
|
||||
|
||||
s.PostStore.InvalidateLastPostTimeCache(channelID)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/mattermost/gorp"
|
||||
@@ -958,6 +959,36 @@ func (s *SqlPostStore) GetPostsSince(options model.GetPostsSinceOptions, allowFr
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) {
|
||||
query := `
|
||||
SELECT 1
|
||||
FROM
|
||||
Posts
|
||||
WHERE
|
||||
UpdateAt >= :Time
|
||||
AND
|
||||
ChannelId = :ChannelId
|
||||
AND
|
||||
UserId = :UserId
|
||||
AND
|
||||
Type = :Type
|
||||
LIMIT 1`
|
||||
|
||||
exist, err := s.GetReplica().SelectInt(query, map[string]interface{}{
|
||||
"ChannelId": options.ChannelId,
|
||||
"Time": options.Time,
|
||||
"UserId": userId,
|
||||
"Type": model.POST_AUTO_RESPONDER,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err,
|
||||
"failed to check if autoresponse posts in channelId=%s for userId=%s since %s", options.ChannelId, userId, time.Unix(options.Time, 0).Format(time.RFC3339))
|
||||
}
|
||||
|
||||
return exist > 0, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, _ /* allowFromCache */ bool) ([]*model.Post, error) {
|
||||
if options.Limit < 0 || options.Limit > 1000 {
|
||||
return nil, store.NewErrInvalidInput("Post", "<options.Limit>", options.Limit)
|
||||
|
||||
@@ -334,6 +334,7 @@ type PostStore interface {
|
||||
GetDirectPostParentsForExportAfter(limit int, afterID string) ([]*model.DirectPostForExport, error)
|
||||
SearchPostsInTeamForUser(paramsList []*model.SearchParams, userID, teamID string, page, perPage int) (*model.PostSearchResults, error)
|
||||
GetOldestEntityCreationTime() (int64, error)
|
||||
HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error)
|
||||
GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, allowFromCache bool) ([]*model.Post, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -607,6 +607,27 @@ func (_m *PostStore) GetSingle(id string, inclDeleted bool) (*model.Post, error)
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// HasAutoResponsePostByUserSince provides a mock function with given fields: options, userId
|
||||
func (_m *PostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) {
|
||||
ret := _m.Called(options, userId)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(model.GetPostsSinceOptions, string) bool); ok {
|
||||
r0 = rf(options, userId)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(model.GetPostsSinceOptions, string) error); ok {
|
||||
r1 = rf(options, userId)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// InvalidateLastPostTimeCache provides a mock function with given fields: channelID
|
||||
func (_m *PostStore) InvalidateLastPostTimeCache(channelID string) {
|
||||
_m.Called(channelID)
|
||||
|
||||
@@ -56,7 +56,7 @@ func TestPostStore(t *testing.T, ss store.Store, s SqlStore) {
|
||||
t.Run("GetDirectPostParentsForExportAfterDeleted", func(t *testing.T) { testPostStoreGetDirectPostParentsForExportAfterDeleted(t, ss, s) })
|
||||
t.Run("GetDirectPostParentsForExportAfterBatched", func(t *testing.T) { testPostStoreGetDirectPostParentsForExportAfterBatched(t, ss, s) })
|
||||
t.Run("GetForThread", func(t *testing.T) { testPostStoreGetForThread(t, ss) })
|
||||
|
||||
t.Run("HasAutoResponsePostByUserSince", func(t *testing.T) { testHasAutoResponsePostByUserSince(t, ss) })
|
||||
}
|
||||
|
||||
func testPostStoreSave(t *testing.T, ss store.Store) {
|
||||
@@ -2970,3 +2970,46 @@ func testPostStoreGetDirectPostParentsForExportAfterBatched(t *testing.T, ss sto
|
||||
// Manually truncate Channels table until testlib can handle cleanups
|
||||
s.GetMaster().Exec("TRUNCATE Channels")
|
||||
}
|
||||
|
||||
func testHasAutoResponsePostByUserSince(t *testing.T, ss store.Store) {
|
||||
t.Run("should return posts created after the given time", func(t *testing.T) {
|
||||
channelId := model.NewId()
|
||||
userId := model.NewId()
|
||||
|
||||
_, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: channelId,
|
||||
UserId: userId,
|
||||
Message: "message",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
post2, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: channelId,
|
||||
UserId: userId,
|
||||
Message: "message",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
post3, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: channelId,
|
||||
UserId: userId,
|
||||
Message: "auto response message",
|
||||
Type: model.POST_AUTO_RESPONDER,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
exists, err := ss.Post().HasAutoResponsePostByUserSince(model.GetPostsSinceOptions{ChannelId: channelId, Time: post2.CreateAt}, userId)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
|
||||
err = ss.Post().Delete(post3.Id, time.Now().Unix(), userId)
|
||||
require.NoError(t, err)
|
||||
|
||||
exists, err = ss.Post().HasAutoResponsePostByUserSince(model.GetPostsSinceOptions{ChannelId: channelId, Time: post2.CreateAt}, userId)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, exists)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
)
|
||||
|
||||
// This function has a copy of it in app/helper_test
|
||||
// NewTestId is used for testing as a replacement for model.NewId(). It is a [A-Z0-9] string 26
|
||||
// characters long. It replaces every odd character with a digit.
|
||||
func NewTestId() string {
|
||||
|
||||
@@ -4938,6 +4938,22 @@ func (s *TimerLayerPostStore) GetSingle(id string, inclDeleted bool) (*model.Pos
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) {
|
||||
start := timemodule.Now()
|
||||
|
||||
result, err := s.PostStore.HasAutoResponsePostByUserSince(options, userId)
|
||||
|
||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PostStore.HasAutoResponsePostByUserSince", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostStore) InvalidateLastPostTimeCache(channelID string) {
|
||||
start := timemodule.Now()
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user