MM-35133 trigger sync user immediately after change (#17579)

- ensure changes to user profile sync immediately
- refactor sync send
Этот коммит содержится в:
Doug Lauder
2021-05-20 12:07:40 -04:00
коммит произвёл GitHub
родитель 36dd0005d2
Коммит 2b02b03497
31 изменённых файлов: 1591 добавлений и 914 удалений

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

@@ -1071,7 +1071,7 @@ type AppIface interface {
UpdateScheme(scheme *model.Scheme) (*model.Scheme, *model.AppError)
UpdateSessionsIsGuest(userID string, isGuest bool)
UpdateSharedChannel(sc *model.SharedChannel) (*model.SharedChannel, error)
UpdateSharedChannelRemoteNextSyncAt(id string, syncTime int64) error
UpdateSharedChannelRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error
UpdateSidebarCategories(userID, teamID string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError)
UpdateSidebarCategoryOrder(userID, teamID string, categoryOrder []string) *model.AppError
UpdateTeam(team *model.Team) (*model.Team, *model.AppError)

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

@@ -16427,9 +16427,9 @@ func (a *OpenTracingAppLayer) UpdateSharedChannel(sc *model.SharedChannel) (*mod
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) UpdateSharedChannelRemoteNextSyncAt(id string, syncTime int64) error {
func (a *OpenTracingAppLayer) UpdateSharedChannelRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateSharedChannelRemoteNextSyncAt")
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateSharedChannelRemoteCursor")
a.ctx = newCtx
a.app.Srv().Store.SetContext(newCtx)
@@ -16439,7 +16439,7 @@ func (a *OpenTracingAppLayer) UpdateSharedChannelRemoteNextSyncAt(id string, syn
}()
defer span.Finish()
resultVar0 := a.app.UpdateSharedChannelRemoteNextSyncAt(id, syncTime)
resultVar0 := a.app.UpdateSharedChannelRemoteCursor(id, cursor)
if resultVar0 != nil {
span.LogFields(spanlog.Error(resultVar0))

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

@@ -2190,8 +2190,8 @@ func TestSharedChannelSyncForPostActions(t *testing.T) {
}, channel, false, true)
require.Nil(t, err, "Creating a post should not error")
assert.Len(t, remoteClusterService.notifications, 1)
assert.Equal(t, channel.Id, remoteClusterService.notifications[0])
assert.Len(t, remoteClusterService.channelNotifications, 1)
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[0])
})
t.Run("updating a post in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
@@ -2217,9 +2217,9 @@ func TestSharedChannelSyncForPostActions(t *testing.T) {
_, err = th.App.UpdatePost(th.Context, post, true)
require.Nil(t, err, "Updating a post should not error")
assert.Len(t, remoteClusterService.notifications, 2)
assert.Equal(t, channel.Id, remoteClusterService.notifications[0])
assert.Equal(t, channel.Id, remoteClusterService.notifications[1])
assert.Len(t, remoteClusterService.channelNotifications, 2)
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[0])
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[1])
})
t.Run("deleting a post in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
@@ -2246,9 +2246,9 @@ func TestSharedChannelSyncForPostActions(t *testing.T) {
require.Nil(t, err, "Deleting a post should not error")
// one creation and two deletes
assert.Len(t, remoteClusterService.notifications, 3)
assert.Equal(t, channel.Id, remoteClusterService.notifications[0])
assert.Equal(t, channel.Id, remoteClusterService.notifications[1])
assert.Equal(t, channel.Id, remoteClusterService.notifications[2])
assert.Len(t, remoteClusterService.channelNotifications, 3)
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[0])
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[1])
assert.Equal(t, channel.Id, remoteClusterService.channelNotifications[2])
})
}

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

@@ -44,9 +44,9 @@ func TestSharedChannelSyncForReactionActions(t *testing.T) {
th.TearDown() // We need to enforce teardown because reaction instrumentation happens in a goroutine
assert.Len(t, sharedChannelService.notifications, 2)
assert.Equal(t, channel.Id, sharedChannelService.notifications[0])
assert.Equal(t, channel.Id, sharedChannelService.notifications[1])
assert.Len(t, sharedChannelService.channelNotifications, 2)
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[0])
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[1])
})
t.Run("removing a reaction in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
@@ -79,8 +79,8 @@ func TestSharedChannelSyncForReactionActions(t *testing.T) {
th.TearDown() // We need to enforce teardown because reaction instrumentation happens in a goroutine
assert.Len(t, sharedChannelService.notifications, 2)
assert.Equal(t, channel.Id, sharedChannelService.notifications[0])
assert.Equal(t, channel.Id, sharedChannelService.notifications[1])
assert.Len(t, sharedChannelService.channelNotifications, 2)
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[0])
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[1])
})
}

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

@@ -159,6 +159,7 @@ type Server struct {
telemetryService *telemetry.TelemetryService
serviceMux sync.RWMutex
remoteClusterService remotecluster.RemoteClusterServiceIFace
sharedChannelService SharedChannelServiceIFace
@@ -873,16 +874,19 @@ func (s *Server) startInterClusterServices(license *model.License, app *App) err
var err error
s.remoteClusterService, err = remotecluster.NewRemoteClusterService(s)
rcs, err := remotecluster.NewRemoteClusterService(s)
if err != nil {
return err
}
if err = s.remoteClusterService.Start(); err != nil {
s.remoteClusterService = nil
if err = rcs.Start(); err != nil {
return err
}
s.serviceMux.Lock()
s.remoteClusterService = rcs
s.serviceMux.Unlock()
// Shared Channels service
// License check
@@ -897,15 +901,19 @@ func (s *Server) startInterClusterServices(license *model.License, app *App) err
return nil
}
s.sharedChannelService, err = sharedchannel.NewSharedChannelService(s, app)
scs, err := sharedchannel.NewSharedChannelService(s, app)
if err != nil {
return err
}
if err = s.sharedChannelService.Start(); err != nil {
s.remoteClusterService = nil
if err = scs.Start(); err != nil {
return err
}
s.serviceMux.Lock()
s.sharedChannelService = scs
s.serviceMux.Unlock()
return nil
}
@@ -967,11 +975,18 @@ func (s *Server) Shutdown() {
mlog.Warn("Unable to cleanly shutdown telemetry client", mlog.Err(err))
}
s.serviceMux.RLock()
if s.sharedChannelService != nil {
if err = s.sharedChannelService.Shutdown(); err != nil {
mlog.Error("Error shutting down shared channel services", mlog.Err(err))
}
}
if s.remoteClusterService != nil {
if err = s.remoteClusterService.Shutdown(); err != nil {
mlog.Error("Error shutting down intercluster services", mlog.Err(err))
}
}
s.serviceMux.RUnlock()
s.StopHTTPServer()
s.stopLocalModeServer()
@@ -1992,12 +2007,16 @@ func (s *Server) GetStore() store.Store {
// GetRemoteClusterService returns the `RemoteClusterService` instantiated by the server.
// May be nil if the service is not enabled via license.
func (s *Server) GetRemoteClusterService() remotecluster.RemoteClusterServiceIFace {
s.serviceMux.RLock()
defer s.serviceMux.RUnlock()
return s.remoteClusterService
}
// GetSharedChannelSyncService returns the `SharedChannelSyncService` instantiated by the server.
// May be nil if the service is not enabled via license.
func (s *Server) GetSharedChannelSyncService() SharedChannelServiceIFace {
s.serviceMux.RLock()
defer s.serviceMux.RUnlock()
return s.sharedChannelService
}
@@ -2010,12 +2029,16 @@ func (s *Server) GetMetrics() einterfaces.MetricsInterface {
// SetRemoteClusterService sets the `RemoteClusterService` to be used by the server.
// For testing only.
func (s *Server) SetRemoteClusterService(remoteClusterService remotecluster.RemoteClusterServiceIFace) {
s.serviceMux.Lock()
defer s.serviceMux.Unlock()
s.remoteClusterService = remoteClusterService
}
// SetSharedChannelSyncService sets the `SharedChannelSyncService` to be used by the server.
// For testing only.
func (s *Server) SetSharedChannelSyncService(sharedChannelService SharedChannelServiceIFace) {
s.serviceMux.Lock()
defer s.serviceMux.Unlock()
s.sharedChannelService = sharedChannelService
}

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

@@ -133,8 +133,8 @@ func (a *App) GetRemoteClusterForUser(remoteID string, userID string) (*model.Re
return rc, nil
}
func (a *App) UpdateSharedChannelRemoteNextSyncAt(id string, syncTime int64) error {
return a.Srv().Store.SharedChannel().UpdateRemoteNextSyncAt(id, syncTime)
func (a *App) UpdateSharedChannelRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error {
return a.Srv().Store.SharedChannel().UpdateRemoteCursor(id, cursor)
}
func (a *App) DeleteSharedChannelRemote(id string) (bool, error) {
@@ -153,3 +153,13 @@ func (a *App) GetSharedChannelRemotesStatus(channelID string) ([]*model.SharedCh
func (a *App) NotifySharedChannelUserUpdate(user *model.User) {
a.sendUpdatedUserEvent(*user)
}
// onUserProfileChange is called when a user's profile has changed
// (username, email, profile image, ...)
func (a *App) onUserProfileChange(userID string) {
syncService := a.Srv().GetSharedChannelSyncService()
if syncService == nil || !syncService.Active() {
return
}
syncService.NotifyUserProfileChanged(userID)
}

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

@@ -18,10 +18,10 @@ func TestServerSyncSharedChannelHandler(t *testing.T) {
mockService := NewMockSharedChannelService(nil)
mockService.active = false
th.App.srv.sharedChannelService = mockService
th.App.srv.SetSharedChannelSyncService(mockService)
th.App.srv.SharedChannelSyncHandler(&model.WebSocketEvent{})
assert.Empty(t, mockService.notifications)
assert.Empty(t, mockService.channelNotifications)
})
t.Run("sync service active and broadcast envelope has ineligible event, it does nothing", func(t *testing.T) {
@@ -30,13 +30,13 @@ func TestServerSyncSharedChannelHandler(t *testing.T) {
mockService := NewMockSharedChannelService(nil)
mockService.active = true
th.App.srv.sharedChannelService = mockService
th.App.srv.SetSharedChannelSyncService(mockService)
channel := th.CreateChannel(th.BasicTeam, WithShared(true))
websocketEvent := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_ADDED_TO_TEAM, model.NewId(), channel.Id, "", nil)
th.App.srv.SharedChannelSyncHandler(websocketEvent)
assert.Empty(t, mockService.notifications)
assert.Empty(t, mockService.channelNotifications)
})
t.Run("sync service active and broadcast envelope has eligible event but channel does not exist, it does nothing", func(t *testing.T) {
@@ -45,12 +45,12 @@ func TestServerSyncSharedChannelHandler(t *testing.T) {
mockService := NewMockSharedChannelService(nil)
mockService.active = true
th.App.srv.sharedChannelService = mockService
th.App.srv.SetSharedChannelSyncService(mockService)
websocketEvent := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POSTED, model.NewId(), model.NewId(), "", nil)
th.App.srv.SharedChannelSyncHandler(websocketEvent)
assert.Empty(t, mockService.notifications)
assert.Empty(t, mockService.channelNotifications)
})
t.Run("sync service active when received eligible event, it triggers a shared channel content sync", func(t *testing.T) {
@@ -59,13 +59,13 @@ func TestServerSyncSharedChannelHandler(t *testing.T) {
mockService := NewMockSharedChannelService(nil)
mockService.active = true
th.App.srv.sharedChannelService = mockService
th.App.srv.SetSharedChannelSyncService(mockService)
channel := th.CreateChannel(th.BasicTeam, WithShared(true))
websocketEvent := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POSTED, model.NewId(), channel.Id, "", nil)
th.App.srv.SharedChannelSyncHandler(websocketEvent)
assert.Len(t, mockService.notifications, 1)
assert.Equal(t, channel.Id, mockService.notifications[0])
assert.Len(t, mockService.channelNotifications, 1)
assert.Equal(t, channel.Id, mockService.channelNotifications[0])
})
}

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

@@ -13,6 +13,7 @@ type SharedChannelServiceIFace interface {
Shutdown() error
Start() error
NotifyChannelChanged(channelId string)
NotifyUserProfileChanged(userID string)
SendChannelInvite(channel *model.Channel, userId string, rc *model.RemoteCluster, options ...sharedchannel.InviteOption) error
Active() bool
}
@@ -26,7 +27,7 @@ func MockOptionSharedChannelServiceWithActive(active bool) MockOptionSharedChann
}
func NewMockSharedChannelService(service SharedChannelServiceIFace, options ...MockOptionSharedChannelService) *mockSharedChannelService {
mrcs := &mockSharedChannelService{service, true, []string{}, 0}
mrcs := &mockSharedChannelService{service, true, []string{}, []string{}, 0}
for _, option := range options {
option(mrcs)
}
@@ -35,13 +36,18 @@ func NewMockSharedChannelService(service SharedChannelServiceIFace, options ...M
type mockSharedChannelService struct {
SharedChannelServiceIFace
active bool
notifications []string
numInvitations int
active bool
channelNotifications []string
userProfileNotifications []string
numInvitations int
}
func (mrcs *mockSharedChannelService) NotifyChannelChanged(channelId string) {
mrcs.notifications = append(mrcs.notifications, channelId)
mrcs.channelNotifications = append(mrcs.channelNotifications, channelId)
}
func (mrcs *mockSharedChannelService) NotifyUserProfileChanged(userId string) {
mrcs.userProfileNotifications = append(mrcs.userProfileNotifications, userId)
}
func (mrcs *mockSharedChannelService) Shutdown() error {

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

@@ -981,6 +981,7 @@ func (a *App) SetProfileImageFromFile(userID string, file io.Reader) *model.AppE
mlog.Warn("Error with updating last picture update", mlog.Err(err))
}
a.invalidateUserCacheAndPublish(userID)
a.onUserProfileChange(userID)
return nil
}
@@ -1311,6 +1312,7 @@ func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User,
}
a.InvalidateCacheForUser(user.Id)
a.onUserProfileChange(user.Id)
return userUpdate.New, nil
}