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>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
c74f830b76
Коммит
b898d13e55
@@ -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 {
|
||||
|
||||
Ссылка в новой задаче
Block a user