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 удалений

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

@@ -1057,6 +1057,7 @@ type AppIface interface {
SaveAcknowledgementForPost(c request.CTX, postID, userID string) (*model.PostAcknowledgement, *model.AppError)
SaveAdminNotification(userId string, notifyData *model.NotifyAdminToUpgradeRequest) *model.AppError
SaveAdminNotifyData(data *model.NotifyAdminData) (*model.NotifyAdminData, *model.AppError)
SaveAndBroadcastStatus(status *model.Status)
SaveBrandImage(rctx request.CTX, imageData *multipart.FileHeader) *model.AppError
SaveComplianceReport(rctx request.CTX, job *model.Compliance) (*model.Compliance, *model.AppError)
SaveReactionForPost(c request.CTX, reaction *model.Reaction) (*model.Reaction, *model.AppError)

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

@@ -15305,6 +15305,21 @@ func (a *OpenTracingAppLayer) SaveAdminNotifyData(data *model.NotifyAdminData) (
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) SaveAndBroadcastStatus(status *model.Status) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SaveAndBroadcastStatus")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
a.app.SaveAndBroadcastStatus(status)
}
func (a *OpenTracingAppLayer) SaveBrandImage(rctx request.CTX, imageData *multipart.FileHeader) *model.AppError {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SaveBrandImage")

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

@@ -14,6 +14,7 @@ type SharedChannelServiceIFace interface {
Start() error
NotifyChannelChanged(channelId string)
NotifyUserProfileChanged(userID string)
NotifyUserStatusChanged(status *model.Status)
SendChannelInvite(channel *model.Channel, userId string, rc *model.RemoteCluster, options ...sharedchannel.InviteOption) error
Active() bool
InviteRemoteToChannel(channelID, remoteID, userID string, shareIfNotShared bool) error

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

@@ -279,6 +279,9 @@ func (ps *PlatformService) SetStatusLastActivityAt(userID string, activityAt int
ps.AddStatusCacheSkipClusterSend(status)
ps.SetStatusAwayIfNeeded(userID, false)
if ps.sharedChannelService != nil {
ps.sharedChannelService.NotifyUserStatusChanged(status)
}
}
func (ps *PlatformService) UpdateLastActivityAtIfNeeded(session model.Session) {
@@ -346,6 +349,9 @@ func (ps *PlatformService) SetStatusOnline(userID string, manual bool) {
mlog.Error("Failed to save status", mlog.String("user_id", userID), mlog.Err(err), mlog.String("user_id", userID))
}
}
if ps.sharedChannelService != nil {
ps.sharedChannelService.NotifyUserStatusChanged(status)
}
}
if broadcast {
@@ -366,6 +372,9 @@ func (ps *PlatformService) SetStatusOffline(userID string, manual bool) {
status = &model.Status{UserId: userID, Status: model.StatusOffline, Manual: manual, LastActivityAt: model.GetMillis(), ActiveChannel: ""}
ps.SaveAndBroadcastStatus(status)
if ps.sharedChannelService != nil {
ps.sharedChannelService.NotifyUserStatusChanged(status)
}
}
func (ps *PlatformService) SetStatusAwayIfNeeded(userID string, manual bool) {
@@ -398,19 +407,22 @@ func (ps *PlatformService) SetStatusAwayIfNeeded(userID string, manual bool) {
status.ActiveChannel = ""
ps.SaveAndBroadcastStatus(status)
if ps.sharedChannelService != nil {
ps.sharedChannelService.NotifyUserStatusChanged(status)
}
}
// SetStatusDoNotDisturbTimed takes endtime in unix epoch format in UTC
// and sets status of given userId to dnd which will be restored back after endtime
func (ps *PlatformService) SetStatusDoNotDisturbTimed(userId string, endtime int64) {
func (ps *PlatformService) SetStatusDoNotDisturbTimed(userID string, endtime int64) {
if !*ps.Config().ServiceSettings.EnableUserStatuses {
return
}
status, err := ps.GetStatus(userId)
status, err := ps.GetStatus(userID)
if err != nil {
status = &model.Status{UserId: userId, Status: model.StatusOffline, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
status = &model.Status{UserId: userID, Status: model.StatusOffline, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
}
status.PrevStatus = status.Status
@@ -420,6 +432,9 @@ func (ps *PlatformService) SetStatusDoNotDisturbTimed(userId string, endtime int
status.DNDEndTime = endtime
ps.SaveAndBroadcastStatus(status)
if ps.sharedChannelService != nil {
ps.sharedChannelService.NotifyUserStatusChanged(status)
}
}
func (ps *PlatformService) SetStatusDoNotDisturb(userID string) {
@@ -437,6 +452,9 @@ func (ps *PlatformService) SetStatusDoNotDisturb(userID string) {
status.Manual = true
ps.SaveAndBroadcastStatus(status)
if ps.sharedChannelService != nil {
ps.sharedChannelService.NotifyUserStatusChanged(status)
}
}
func (ps *PlatformService) SetStatusOutOfOffice(userID string) {
@@ -454,6 +472,9 @@ func (ps *PlatformService) SetStatusOutOfOffice(userID string) {
status.Manual = true
ps.SaveAndBroadcastStatus(status)
if ps.sharedChannelService != nil {
ps.sharedChannelService.NotifyUserStatusChanged(status)
}
}
func (ps *PlatformService) isUserAway(lastActivityAt int64) bool {

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

@@ -19,7 +19,7 @@ import (
func (a *App) getSharedChannelsService() (SharedChannelServiceIFace, error) {
scService := a.Srv().GetSharedChannelSyncService()
if scService == nil || !scService.Active() {
return nil, model.NewAppError("InviteRemoteToChannel", "api.command_share.service_disabled",
return nil, model.NewAppError("getSharedChannelsService", "api.command_share.service_disabled",
nil, "", http.StatusBadRequest)
}
return scService, nil

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

@@ -15,6 +15,7 @@ type SharedChannelServiceIFace interface {
Start() error
NotifyChannelChanged(channelId string)
NotifyUserProfileChanged(userID string)
NotifyUserStatusChanged(status *model.Status)
SendChannelInvite(channel *model.Channel, userId string, rc *model.RemoteCluster, options ...sharedchannel.InviteOption) error
Active() bool
InviteRemoteToChannel(channelID, remoteID, userID string, shareIfNotShared bool) error

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

@@ -50,6 +50,10 @@ func (a *App) SetStatusOutOfOffice(userID string) {
a.Srv().Platform().SetStatusOutOfOffice(userID)
}
func (a *App) SaveAndBroadcastStatus(status *model.Status) {
a.Srv().Platform().SaveAndBroadcastStatus(status)
}
func (a *App) GetStatusFromCache(userID string) *model.Status {
return a.Srv().Platform().GetStatusFromCache(userID)
}
@@ -66,9 +70,14 @@ func (a *App) UpdateDNDStatusOfUsers() {
mlog.Warn("Failed to fetch dnd statues from store", mlog.String("err", err.Error()))
return
}
scs, _ := a.getSharedChannelsService()
for i := range statuses {
a.Srv().Platform().AddStatusCache(statuses[i])
a.Srv().Platform().BroadcastStatus(statuses[i])
if scs != nil {
scs.NotifyUserStatusChanged(statuses[i])
}
}
}