Adds status sync to shared channels (#28020)

* Adds status sync to shared channels

To allow for status to be synced, this changes add a new type of
shared channel internal task. This task has its contents pre-fetched
and stored in the `existingMsg` property, and it is keyed with a user
ID besides a channel ID, so it doesn't conflict with channel-driven
synchronization tasks.

All status synchronizations are triggered from the app layer, so there
is no need of watching for new WebSocket events. Although right now
we're only syncing one user status per message, the changes account
for a list of statuses in case we want to batch them in the future.

The feature is gated by a configuration property and can be disabled
independently of the rest of Shared Channels if it's necessary. It is
backwards compatible as well, and should cause no problems with
servers running older Mattermost versions.

* Adds status sync error management and retry

* Adds DisableSharedChannelsStatusSync to the telemetry report

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Miguel de la Cruz
2024-09-10 23:39:07 +02:00
коммит произвёл GitHub
родитель c74f830b76
Коммит b898d13e55
15 изменённых файлов: 240 добавлений и 36 удалений

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

@@ -35,6 +35,7 @@ type syncData struct {
profileImages map[string]*model.User
posts []*model.Post
reactions []*model.Reaction
statuses []*model.Status
attachments []attachment
resultRepeat bool
@@ -68,6 +69,13 @@ func (sd *syncData) isCursorChanged() bool {
sd.scr.LastPostUpdateAt != sd.resultNextCursor.LastPostUpdateAt || sd.scr.LastPostUpdateID != sd.resultNextCursor.LastPostUpdateID
}
func (sd *syncData) setDataFromMsg(msg *model.SyncMsg) {
sd.users = msg.Users
sd.posts = msg.Posts
sd.reactions = msg.Reactions
sd.statuses = msg.Statuses
}
// syncForRemote updates a remote cluster with any new posts/reactions for a specific
// channel. If many changes are found, only the oldest X changes are sent and the channel
// is re-added to the task map. This ensures no channels are starved for updates even if some
@@ -115,21 +123,30 @@ func (scs *Service) syncForRemote(task syncTask, rc *model.RemoteCluster) error
return err
}
sd := newSyncData(task, rc, scr)
// if this is retrying a failed msg, just send it again.
if task.retryMsg != nil {
sd := newSyncData(task, rc, scr)
sd.users = task.retryMsg.Users
sd.posts = task.retryMsg.Posts
sd.reactions = task.retryMsg.Reactions
sd.setDataFromMsg(task.retryMsg)
return scs.sendSyncData(sd)
}
sd := newSyncData(task, rc, scr)
// if this has an already existing msg, just send it right away
if task.existingMsg != nil {
sd.setDataFromMsg(task.existingMsg)
return scs.sendSyncData(sd)
}
// if we don't have a channelID at this point, we cannot fetch new
// data from the database
if task.channelID == "" {
return fmt.Errorf("task doesn't have prefetched data nor a channel ID set")
}
// schedule another sync if the repeat flag is set at some point.
defer func(rpt *bool) {
if *rpt {
scs.addTask(newSyncTask(task.channelID, task.remoteID, nil))
scs.addTask(newSyncTask(task.channelID, task.userID, task.remoteID, nil, nil))
}
}(&sd.resultRepeat)
@@ -516,6 +533,13 @@ func (scs *Service) sendSyncData(sd *syncData) error {
}
}
// send statuses
if len(sd.statuses) != 0 {
if err := scs.sendStatusSyncData(sd); err != nil {
merr.Append(fmt.Errorf("cannot send status sync data: %w", err))
}
}
// send user profile images
if len(sd.profileImages) != 0 {
scs.sendProfileImageSyncData(sd)
@@ -624,6 +648,25 @@ func (scs *Service) sendReactionSyncData(sd *syncData) error {
})
}
// sendStatusSyncData sends the collected status updates to the remote cluster.
func (scs *Service) sendStatusSyncData(sd *syncData) error {
msg := model.NewSyncMsg(sd.task.channelID)
msg.Statuses = sd.statuses
return scs.sendSyncMsgToRemote(msg, sd.rc, func(syncResp model.SyncResponse, errResp error) {
if len(syncResp.StatusErrors) != 0 {
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Response indicates error from status(es) sync",
mlog.String("remote_id", sd.rc.RemoteId),
mlog.Array("user_ids", syncResp.StatusErrors),
)
for _, userID := range syncResp.StatusErrors {
scs.handleStatusError(userID, sd.task, sd.rc)
}
}
})
}
// sendProfileImageSyncData sends the collected user profile image updates to the remote cluster.
func (scs *Service) sendProfileImageSyncData(sd *syncData) {
for _, user := range sd.profileImages {