MM-57326: [Shared Channels] Message priority, acknowledgement and persistent notifications need to be synced (#30736)

Этот коммит содержится в:
catalintomai
2025-06-16 02:30:21 +02:00
коммит произвёл GitHub
родитель fa1c77d9b0
Коммит 85391de22a
27 изменённых файлов: 2667 добавлений и 101 удалений

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

@@ -205,6 +205,26 @@ func (_m *MockAppIface) CreateUploadSession(c request.CTX, us *model.UploadSessi
return r0, r1
}
// DeleteAcknowledgementForPostWithModel provides a mock function with given fields: c, acknowledgement
func (_m *MockAppIface) DeleteAcknowledgementForPostWithModel(c request.CTX, acknowledgement *model.PostAcknowledgement) *model.AppError {
ret := _m.Called(c, acknowledgement)
if len(ret) == 0 {
panic("no return value specified for DeleteAcknowledgementForPostWithModel")
}
var r0 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, *model.PostAcknowledgement) *model.AppError); ok {
r0 = rf(c, acknowledgement)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AppError)
}
}
return r0
}
// DeletePost provides a mock function with given fields: c, postID, deleteByID
func (_m *MockAppIface) DeletePost(c request.CTX, postID string, deleteByID string) (*model.Post, *model.AppError) {
ret := _m.Called(c, postID, deleteByID)
@@ -289,6 +309,38 @@ func (_m *MockAppIface) FileReader(path string) (filestore.ReadCloseSeeker, *mod
return r0, r1
}
// GetAcknowledgementsForPost provides a mock function with given fields: postID
func (_m *MockAppIface) GetAcknowledgementsForPost(postID string) ([]*model.PostAcknowledgement, *model.AppError) {
ret := _m.Called(postID)
if len(ret) == 0 {
panic("no return value specified for GetAcknowledgementsForPost")
}
var r0 []*model.PostAcknowledgement
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(string) ([]*model.PostAcknowledgement, *model.AppError)); ok {
return rf(postID)
}
if rf, ok := ret.Get(0).(func(string) []*model.PostAcknowledgement); ok {
r0 = rf(postID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.PostAcknowledgement)
}
}
if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
r1 = rf(postID)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// GetOrCreateDirectChannel provides a mock function with given fields: c, userId, otherUserId, channelOptions
func (_m *MockAppIface) GetOrCreateDirectChannel(c request.CTX, userId string, otherUserId string, channelOptions ...model.ChannelOption) (*model.Channel, *model.AppError) {
_va := make([]interface{}, len(channelOptions))
@@ -508,6 +560,26 @@ func (_m *MockAppIface) PermanentDeleteChannel(c request.CTX, channel *model.Cha
return r0
}
// PreparePostForClient provides a mock function with given fields: c, post, isNewPost, includeDeleted, includePriority
func (_m *MockAppIface) PreparePostForClient(c request.CTX, post *model.Post, isNewPost bool, includeDeleted bool, includePriority bool) *model.Post {
ret := _m.Called(c, post, isNewPost, includeDeleted, includePriority)
if len(ret) == 0 {
panic("no return value specified for PreparePostForClient")
}
var r0 *model.Post
if rf, ok := ret.Get(0).(func(request.CTX, *model.Post, bool, bool, bool) *model.Post); ok {
r0 = rf(c, post, isNewPost, includeDeleted, includePriority)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Post)
}
}
return r0
}
// Publish provides a mock function with given fields: message
func (_m *MockAppIface) Publish(message *model.WebSocketEvent) {
_m.Called(message)
@@ -533,6 +605,70 @@ func (_m *MockAppIface) RemoveUserFromChannel(c request.CTX, userID string, remo
return r0
}
// SaveAcknowledgementForPostWithModel provides a mock function with given fields: c, acknowledgement
func (_m *MockAppIface) SaveAcknowledgementForPostWithModel(c request.CTX, acknowledgement *model.PostAcknowledgement) (*model.PostAcknowledgement, *model.AppError) {
ret := _m.Called(c, acknowledgement)
if len(ret) == 0 {
panic("no return value specified for SaveAcknowledgementForPostWithModel")
}
var r0 *model.PostAcknowledgement
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, *model.PostAcknowledgement) (*model.PostAcknowledgement, *model.AppError)); ok {
return rf(c, acknowledgement)
}
if rf, ok := ret.Get(0).(func(request.CTX, *model.PostAcknowledgement) *model.PostAcknowledgement); ok {
r0 = rf(c, acknowledgement)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.PostAcknowledgement)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, *model.PostAcknowledgement) *model.AppError); ok {
r1 = rf(c, acknowledgement)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// SaveAcknowledgementsForPost provides a mock function with given fields: c, postID, userIDs
func (_m *MockAppIface) SaveAcknowledgementsForPost(c request.CTX, postID string, userIDs []string) ([]*model.PostAcknowledgement, *model.AppError) {
ret := _m.Called(c, postID, userIDs)
if len(ret) == 0 {
panic("no return value specified for SaveAcknowledgementsForPost")
}
var r0 []*model.PostAcknowledgement
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, string, []string) ([]*model.PostAcknowledgement, *model.AppError)); ok {
return rf(c, postID, userIDs)
}
if rf, ok := ret.Get(0).(func(request.CTX, string, []string) []*model.PostAcknowledgement); ok {
r0 = rf(c, postID, userIDs)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.PostAcknowledgement)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, string, []string) *model.AppError); ok {
r1 = rf(c, postID, userIDs)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// SaveAndBroadcastStatus provides a mock function with given fields: status
func (_m *MockAppIface) SaveAndBroadcastStatus(status *model.Status) {
_m.Called(status)

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

@@ -78,6 +78,11 @@ type AppIface interface {
OnSharedChannelsAttachmentSyncMsg(fi *model.FileInfo, post *model.Post, rc *model.RemoteCluster) error
OnSharedChannelsProfileImageSyncMsg(user *model.User, rc *model.RemoteCluster) error
Publish(message *model.WebSocketEvent)
SaveAcknowledgementForPostWithModel(c request.CTX, acknowledgement *model.PostAcknowledgement) (*model.PostAcknowledgement, *model.AppError)
DeleteAcknowledgementForPostWithModel(c request.CTX, acknowledgement *model.PostAcknowledgement) *model.AppError
SaveAcknowledgementsForPost(c request.CTX, postID string, userIDs []string) ([]*model.PostAcknowledgement, *model.AppError)
GetAcknowledgementsForPost(postID string) ([]*model.PostAcknowledgement, *model.AppError)
PreparePostForClient(c request.CTX, post *model.Post, isNewPost, includeDeleted, includePriority bool) *model.Post
}
// errNotFound allows checking against Store.ErrNotFound errors without making Store a dependency.

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

@@ -82,10 +82,11 @@ func (scs *Service) processSyncMessage(c request.CTX, syncMsg *model.SyncMsg, rc
var err error
syncResp := model.SyncResponse{
UserErrors: make([]string, 0),
UsersSyncd: make([]string, 0),
PostErrors: make([]string, 0),
ReactionErrors: make([]string, 0),
UserErrors: make([]string, 0),
UsersSyncd: make([]string, 0),
PostErrors: make([]string, 0),
ReactionErrors: make([]string, 0),
AcknowledgementErrors: make([]string, 0),
}
// Check if feature flag is enabled for membership changes
@@ -103,6 +104,7 @@ func (scs *Service) processSyncMessage(c request.CTX, syncMsg *model.SyncMsg, rc
mlog.Int("user_count", len(syncMsg.Users)),
mlog.Int("post_count", len(syncMsg.Posts)),
mlog.Int("reaction_count", len(syncMsg.Reactions)),
mlog.Int("acknowledgement_count", len(syncMsg.Acknowledgements)),
mlog.Int("status_count", len(syncMsg.Statuses)),
mlog.Int("membership_change_count", len(syncMsg.MembershipChanges)),
)
@@ -234,6 +236,31 @@ func (scs *Service) processSyncMessage(c request.CTX, syncMsg *model.SyncMsg, rc
}
}
// add/remove acknowledgements
for _, acknowledgement := range syncMsg.Acknowledgements {
if _, err := scs.upsertSyncAcknowledgement(acknowledgement, targetChannel, rc); err != nil {
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error upserting sync acknowledgement",
mlog.String("remote", rc.Name),
mlog.String("user_id", acknowledgement.UserId),
mlog.String("post_id", acknowledgement.PostId),
mlog.Int("acknowledged_at", acknowledgement.AcknowledgedAt),
mlog.Err(err),
)
syncResp.AcknowledgementErrors = append(syncResp.AcknowledgementErrors, acknowledgement.PostId)
} else {
scs.server.Log().Log(mlog.LvlSharedChannelServiceDebug, "Acknowledgement upserted via sync",
mlog.String("remote", rc.Name),
mlog.String("user_id", acknowledgement.UserId),
mlog.String("post_id", acknowledgement.PostId),
mlog.Int("acknowledged_at", acknowledgement.AcknowledgedAt),
)
if syncResp.AcknowledgementsLastUpdateAt < acknowledgement.AcknowledgedAt {
syncResp.AcknowledgementsLastUpdateAt = acknowledgement.AcknowledgedAt
}
}
}
for _, status := range syncMsg.Statuses {
scs.app.SaveAndBroadcastStatus(status)
}
@@ -431,7 +458,6 @@ func (scs *Service) upsertSyncPost(post *model.Post, targetChannel *model.Channe
post.RemoteId = model.NewPointer(rc.RemoteId)
rctx := request.EmptyContext(scs.server.Log())
rpost, err := scs.server.GetStore().Post().GetSingle(rctx, post.Id, true)
if err != nil {
if _, ok := err.(errNotFound); !ok {
@@ -460,8 +486,7 @@ func (scs *Service) upsertSyncPost(post *model.Post, targetChannel *model.Channe
if appErr == nil {
scs.server.Log().Log(mlog.LvlSharedChannelServiceDebug, "Created sync post",
mlog.String("post_id", post.Id),
mlog.String("channel_id", post.ChannelId),
)
mlog.String("channel_id", post.ChannelId))
}
} else if post.DeleteAt > 0 {
// delete post
@@ -472,14 +497,37 @@ func (scs *Service) upsertSyncPost(post *model.Post, targetChannel *model.Channe
mlog.String("channel_id", post.ChannelId),
)
}
} else if post.EditAt > rpost.EditAt || post.Message != rpost.Message {
// update post
rpost, appErr = scs.app.UpdatePost(request.EmptyContext(scs.server.Log()), post, nil)
if appErr == nil {
scs.server.Log().Log(mlog.LvlSharedChannelServiceDebug, "Updated sync post",
mlog.String("post_id", post.Id),
mlog.String("channel_id", post.ChannelId),
)
} else if post.EditAt > rpost.EditAt || post.Message != rpost.Message || post.UpdateAt > rpost.UpdateAt || post.Metadata != nil {
var priority *model.PostPriority
var acknowledgements []*model.PostAcknowledgement
if post.Metadata != nil {
// Save the received priority
if post.Metadata.Priority != nil {
priority = post.Metadata.Priority
}
// Save the received acknowledgements
if post.Metadata.Acknowledgements != nil {
acknowledgements = post.Metadata.Acknowledgements
}
}
// First update the basic post
rpost, appErr = scs.app.UpdatePost(rctx, post, nil)
if appErr != nil {
rerr := errors.New(appErr.Error())
return nil, rerr
}
// Handle priority metadata separately if needed
if priority != nil {
rpost = scs.syncRemotePriorityMetadata(rctx, post, priority, rpost)
}
// Handle acknowledgements metadata separately if needed
if acknowledgements != nil {
rpost = scs.syncRemoteAcknowledgementsMetadata(rctx, post, acknowledgements, rpost)
}
} else {
// nothing to update
@@ -496,6 +544,105 @@ func (scs *Service) upsertSyncPost(post *model.Post, targetChannel *model.Channe
return rpost, rerr
}
// syncRemotePriorityMetadata handles syncing priority metadata from a remote post.
// It completely replaces existing priority settings with the ones from the remote post,
// regardless of update type.
func (scs *Service) syncRemotePriorityMetadata(rctx request.CTX, post *model.Post, priority *model.PostPriority, rpost *model.Post) *model.Post {
// First, create a new priority object with proper post and channel IDs
newPriority := &model.PostPriority{
PostId: post.Id,
ChannelId: post.ChannelId,
}
// Copy the priority values from the remote post
if priority.Priority != nil {
newPriority.Priority = priority.Priority
}
if priority.RequestedAck != nil {
newPriority.RequestedAck = priority.RequestedAck
}
if priority.PersistentNotifications != nil {
newPriority.PersistentNotifications = priority.PersistentNotifications
}
// Save the new priority - this will replace any existing priority for the post
savedPriority, priorityErr := scs.server.GetStore().PostPriority().Save(newPriority)
if priorityErr != nil {
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error saving post priority from remote",
mlog.String("post_id", post.Id),
mlog.String("channel_id", post.ChannelId),
mlog.Err(priorityErr),
)
} else {
// If the priority is successfully saved, ensure it's in the returned post
if rpost.Metadata == nil {
rpost.Metadata = &model.PostMetadata{}
}
// Use the saved priority from the database operation
rpost.Metadata.Priority = savedPriority
}
return rpost
}
// syncRemoteAcknowledgementsMetadata handles syncing acknowledgements metadata from a remote post.
// It replaces all existing acknowledgements with the ones from the remote post.
func (scs *Service) syncRemoteAcknowledgementsMetadata(rctx request.CTX, post *model.Post, acknowledgements []*model.PostAcknowledgement, rpost *model.Post) *model.Post {
// When syncing from remote, we completely replace the existing acknowledgements
// with the ones received from the remote, regardless of update type
// Get existing acknowledgements and delete them using batch operation
existingAcks, appErrGet := scs.app.GetAcknowledgementsForPost(post.Id)
if appErrGet != nil {
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error getting existing acknowledgements for remote sync",
mlog.String("post_id", post.Id),
mlog.Err(appErrGet),
)
} else if len(existingAcks) > 0 {
// Use batch delete for better performance
if nErr := scs.server.GetStore().PostAcknowledgement().BatchDelete(existingAcks); nErr != nil {
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error batch deleting acknowledgements for remote sync",
mlog.String("post_id", post.Id),
mlog.Int("count", len(existingAcks)),
mlog.Err(nErr),
)
}
}
// Extract all user IDs from acknowledgements for batch processing
userIDs := make([]string, 0, len(acknowledgements))
for _, ack := range acknowledgements {
userIDs = append(userIDs, ack.UserId)
}
// Use batch operation to save all acknowledgements at once
var savedAcks []*model.PostAcknowledgement
if len(userIDs) > 0 {
var appErrAck *model.AppError
savedAcks, appErrAck = scs.app.SaveAcknowledgementsForPost(rctx, post.Id, userIDs)
if appErrAck != nil {
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error syncing remote post acknowledgements",
mlog.String("post_id", post.Id),
mlog.Int("count", len(userIDs)),
mlog.Err(appErrAck),
)
// Fall back to original acknowledgements if batch save fails
savedAcks = acknowledgements
}
}
// Update acknowledgements in the returned post
if rpost.Metadata == nil {
rpost.Metadata = &model.PostMetadata{}
}
rpost.Metadata.Acknowledgements = savedAcks
return rpost
}
func (scs *Service) upsertSyncReaction(reaction *model.Reaction, targetChannel *model.Channel, rc *model.RemoteCluster) (*model.Reaction, error) {
savedReaction := reaction
var appErr *model.AppError
@@ -542,3 +689,54 @@ func (scs *Service) upsertSyncReaction(reaction *model.Reaction, targetChannel *
}
return savedReaction, retErr
}
func (scs *Service) upsertSyncAcknowledgement(acknowledgement *model.PostAcknowledgement, targetChannel *model.Channel, rc *model.RemoteCluster) (*model.PostAcknowledgement, error) {
savedAcknowledgement := acknowledgement
var appErr *model.AppError
// check that the acknowledgement's post is in the target channel. This ensures the acknowledgement can only be associated with a post
// that is in a channel shared with the remote.
rctx := request.EmptyContext(scs.server.Log())
post, err := scs.server.GetStore().Post().GetSingle(rctx, acknowledgement.PostId, true)
if err != nil {
return nil, fmt.Errorf("error fetching post for acknowledgement sync: %w", err)
}
if post.ChannelId != targetChannel.Id {
return nil, fmt.Errorf("acknowledgement sync failed: %w", ErrChannelIDMismatch)
}
existingAcknowledgement, err := scs.server.GetStore().PostAcknowledgement().GetSingle(acknowledgement.UserId, acknowledgement.PostId, rc.RemoteId)
if err != nil && !isNotFoundError(err) {
return nil, fmt.Errorf("error fetching acknowledgement for sync: %w", err)
}
if existingAcknowledgement == nil {
// acknowledgement does not exist; check that user belongs to remote and create acknowledgement
// this is not done for delete since deletion can be done by admins on the remote
user, err := scs.server.GetStore().User().Get(context.TODO(), acknowledgement.UserId)
if err != nil {
return nil, fmt.Errorf("error fetching user for acknowledgement sync: %w", err)
}
if user.GetRemoteID() != rc.RemoteId {
return nil, fmt.Errorf("acknowledgement sync failed: %w", ErrRemoteIDMismatch)
}
acknowledgement.RemoteId = model.NewPointer(rc.RemoteId)
acknowledgement.ChannelId = targetChannel.Id
savedAcknowledgement, appErr = scs.app.SaveAcknowledgementForPostWithModel(request.EmptyContext(scs.server.Log()), acknowledgement)
} else {
// make sure the acknowledgement being deleted is owned by the remote
if existingAcknowledgement.GetRemoteID() != rc.RemoteId {
return nil, fmt.Errorf("acknowledgement sync failed: %w", ErrRemoteIDMismatch)
}
if acknowledgement.AcknowledgedAt == 0 {
// Delete the acknowledgement
appErr = scs.app.DeleteAcknowledgementForPostWithModel(request.EmptyContext(scs.server.Log()), acknowledgement)
}
}
var retErr error
if appErr != nil {
retErr = errors.New(appErr.Error())
}
return savedAcknowledgement, retErr
}

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

@@ -469,6 +469,9 @@ func (scs *Service) handlePostError(postId string, task syncTask, rc *model.Remo
return
}
// Populate metadata for the retry post
post = scs.app.PreparePostForClient(request.EmptyContext(scs.server.Log()), post, false, false, true)
syncMsg := model.NewSyncMsg(task.channelID)
syncMsg.Posts = []*model.Post{post}

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

@@ -32,12 +32,13 @@ type syncData struct {
rc *model.RemoteCluster
scr *model.SharedChannelRemote
users map[string]*model.User
profileImages map[string]*model.User
posts []*model.Post
reactions []*model.Reaction
statuses []*model.Status
attachments []attachment
users map[string]*model.User
profileImages map[string]*model.User
posts []*model.Post
reactions []*model.Reaction
acknowledgements []*model.PostAcknowledgement
statuses []*model.Status
attachments []attachment
resultRepeat bool
resultNextCursor model.GetPostsSinceForSyncCursor
@@ -59,7 +60,7 @@ func newSyncData(task syncTask, rc *model.RemoteCluster, scr *model.SharedChanne
}
func (sd *syncData) isEmpty() bool {
return len(sd.users) == 0 && len(sd.profileImages) == 0 && len(sd.posts) == 0 && len(sd.reactions) == 0 && len(sd.attachments) == 0
return len(sd.users) == 0 && len(sd.profileImages) == 0 && len(sd.posts) == 0 && len(sd.reactions) == 0 && len(sd.acknowledgements) == 0 && len(sd.attachments) == 0
}
func (sd *syncData) isCursorChanged() bool {
@@ -75,6 +76,7 @@ func (sd *syncData) setDataFromMsg(msg *model.SyncMsg) {
sd.users = msg.Users
sd.posts = msg.Posts
sd.reactions = msg.Reactions
sd.acknowledgements = msg.Acknowledgements
sd.statuses = msg.Statuses
}
@@ -185,6 +187,11 @@ func (scs *Service) syncForRemote(task syncTask, rc *model.RemoteCluster) error
return fmt.Errorf("cannot fetch reactions for sync %v: %w", sd, err)
}
// fetch acknowledgements for posts
if err := scs.fetchAcknowledgementsForSync(sd); err != nil {
return fmt.Errorf("cannot fetch acknowledgements for sync %v: %w", sd, err)
}
// fetch users associated with posts & reactions
if err := scs.fetchPostUsersForSync(sd); err != nil {
return fmt.Errorf("cannot fetch post users for sync %v: %w", sd, err)
@@ -218,6 +225,7 @@ func (scs *Service) syncForRemote(task syncTask, rc *model.RemoteCluster) error
mlog.Int("images", len(sd.profileImages)),
mlog.Int("posts", len(sd.posts)),
mlog.Int("reactions", len(sd.reactions)),
mlog.Int("acknowledgements", len(sd.acknowledgements)),
mlog.Int("attachments", len(sd.attachments)),
)
@@ -319,6 +327,13 @@ func (scs *Service) fetchPostsForSync(sd *syncData) error {
sd.posts = appendPosts(sd.posts, posts, scs.server.GetStore().Post(), cursor.LastPostUpdateAt, scs.server.Log())
}
// Populate metadata for all posts before syncing
for i, post := range sd.posts {
if post != nil {
sd.posts[i] = scs.app.PreparePostForClient(request.EmptyContext(scs.server.Log()), post, false, false, true)
}
}
sd.resultNextCursor = nextCursor
sd.resultRepeat = count >= maxPostsPerSync
@@ -377,6 +392,28 @@ func (scs *Service) fetchReactionsForSync(sd *syncData) error {
return merr.ErrorOrNil()
}
// fetchAcknowledgementsForSync populates the sync data with any new acknowledgements since the last sync.
func (scs *Service) fetchAcknowledgementsForSync(sd *syncData) error {
start := time.Now()
defer func() {
if metrics := scs.server.GetMetrics(); metrics != nil {
metrics.ObserveSharedChannelsSyncCollectionStepDuration(sd.rc.RemoteId, "Acknowledgements", time.Since(start).Seconds())
}
}()
merr := merror.New()
for _, post := range sd.posts {
// any acknowledgements originating from the remote cluster are filtered out
acknowledgements, err := scs.server.GetStore().PostAcknowledgement().GetForPostSince(post.Id, sd.scr.LastPostUpdateAt, sd.rc.RemoteId, true)
if err != nil {
merr.Append(fmt.Errorf("could not get acknowledgements for post %s: %w", post.Id, err))
continue
}
sd.acknowledgements = append(sd.acknowledgements, acknowledgements...)
}
return merr.ErrorOrNil()
}
// fetchPostUsersForSync populates the sync data with all users associated with posts.
func (scs *Service) fetchPostUsersForSync(sd *syncData) error {
start := time.Now()
@@ -402,6 +439,10 @@ func (scs *Service) fetchPostUsersForSync(sd *syncData) error {
userIDs[reaction.UserId] = p2mm{}
}
for _, acknowledgement := range sd.acknowledgements {
userIDs[acknowledgement.UserId] = p2mm{}
}
for _, post := range sd.posts {
// add author
userIDs[post.UserId] = p2mm{}
@@ -495,7 +536,9 @@ func (scs *Service) filterPostsForSync(sd *syncData) {
// - new posts (EditAt == 0)
// - edited posts (EditAt >= LastPostUpdateAt)
// - deleted posts (DeleteAt > 0)
if p.EditAt > 0 && p.EditAt < sd.scr.LastPostUpdateAt && p.DeleteAt == 0 {
// - posts with metadata changes (acknowledgements/priority)
hasMetadataChanges := p.Metadata != nil && (p.Metadata.Acknowledgements != nil || p.Metadata.Priority != nil)
if p.EditAt > 0 && p.EditAt < sd.scr.LastPostUpdateAt && p.DeleteAt == 0 && !hasMetadataChanges {
continue
}
@@ -514,6 +557,7 @@ func (scs *Service) filterPostsForSync(sd *syncData) {
filtered = append(filtered, p)
}
sd.posts = filtered
}
@@ -552,6 +596,13 @@ func (scs *Service) sendSyncData(sd *syncData) error {
scs.updateCursorForRemote(sd.scr.Id, sd.rc, sd.resultNextCursor)
}
// send acknowledgements
if len(sd.acknowledgements) != 0 {
if err := scs.sendAcknowledgementSyncData(sd); err != nil {
merr.Append(fmt.Errorf("cannot send acknowledgement sync data: %w", err))
}
}
// send reactions
if len(sd.reactions) != 0 {
if err := scs.sendReactionSyncData(sd); err != nil {
@@ -679,6 +730,29 @@ func (scs *Service) sendReactionSyncData(sd *syncData) error {
})
}
// sendAcknowledgementSyncData sends the collected acknowledgement updates to the remote cluster.
func (scs *Service) sendAcknowledgementSyncData(sd *syncData) error {
start := time.Now()
defer func() {
if metrics := scs.server.GetMetrics(); metrics != nil {
metrics.ObserveSharedChannelsSyncSendStepDuration(sd.rc.RemoteId, "Acknowledgements", time.Since(start).Seconds())
}
}()
msg := model.NewSyncMsg(sd.task.channelID)
msg.Acknowledgements = sd.acknowledgements
return scs.sendSyncMsgToRemote(msg, sd.rc, func(syncResp model.SyncResponse, errResp error) {
if len(syncResp.AcknowledgementErrors) != 0 {
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Response indicates error for acknowledgement(s) sync",
mlog.String("channel_id", sd.task.channelID),
mlog.String("remote_id", sd.rc.RemoteId),
mlog.Array("acknowledgement_posts", syncResp.AcknowledgementErrors),
)
}
})
}
// sendStatusSyncData sends the collected status updates to the remote cluster.
func (scs *Service) sendStatusSyncData(sd *syncData) error {
msg := model.NewSyncMsg(sd.task.channelID)