MM-62751: [Shared Channels] Allow remote users to be discoverable in the create DM/GM modal (#30918)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
476b46d1d7
Коммит
c46ed6c681
@@ -9998,6 +9998,27 @@ func (s *RetryLayerRemoteClusterStore) Update(rc *model.RemoteCluster) (*model.R
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRemoteClusterStore) UpdateLastGlobalUserSyncAt(remoteID string, syncAt int64) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.RemoteClusterStore.UpdateLastGlobalUserSyncAt(remoteID, syncAt)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRemoteClusterStore) UpdateTopics(remoteClusterID string, topics string) (*model.RemoteCluster, error) {
|
||||
|
||||
tries := 0
|
||||
|
||||
@@ -44,6 +44,7 @@ func remoteClusterFields(prefix string) []string {
|
||||
prefix + "CreatorId",
|
||||
prefix + "PluginID",
|
||||
prefix + "Options",
|
||||
prefix + "LastGlobalUserSyncAt",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +100,8 @@ func (s sqlRemoteClusterStore) Update(remoteCluster *model.RemoteCluster) (*mode
|
||||
DefaultTeamId = :DefaultTeamId,
|
||||
Topics = :Topics,
|
||||
PluginID = :PluginID,
|
||||
Options = :Options
|
||||
Options = :Options,
|
||||
LastGlobalUserSyncAt = :LastGlobalUserSyncAt
|
||||
WHERE RemoteId = :RemoteId AND Name = :Name`
|
||||
|
||||
if _, err := s.GetMaster().NamedExec(query, remoteCluster); err != nil {
|
||||
@@ -310,3 +312,24 @@ func (s sqlRemoteClusterStore) SetLastPingAt(remoteClusterId string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s sqlRemoteClusterStore) UpdateLastGlobalUserSyncAt(remoteID string, syncAt int64) error {
|
||||
query := s.getQueryBuilder().
|
||||
Update("RemoteClusters").
|
||||
Set("LastGlobalUserSyncAt", syncAt).
|
||||
Where(sq.Eq{"RemoteId": remoteID})
|
||||
|
||||
result, err := s.GetMaster().ExecBuilder(query)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to update LastGlobalUserSyncAt for RemoteCluster")
|
||||
}
|
||||
|
||||
count, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to determine rows affected")
|
||||
}
|
||||
if count == 0 {
|
||||
return fmt.Errorf("remote cluster not found: %s", remoteID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -584,8 +584,15 @@ func (us SqlUserStore) GetEtagForAllProfiles() string {
|
||||
|
||||
func (us SqlUserStore) GetAllProfiles(options *model.UserGetOptions) ([]*model.User, error) {
|
||||
isPostgreSQL := us.DriverName() == model.DatabaseDriverPostgres
|
||||
|
||||
// Determine ordering based on Sort option - default to Username ASC for backwards compatibility
|
||||
orderBy := "Users.Username ASC"
|
||||
if options.Sort == "update_at_asc" {
|
||||
orderBy = "Users.UpdateAt ASC"
|
||||
}
|
||||
|
||||
query := us.usersQuery.
|
||||
OrderBy("Users.Username ASC").
|
||||
OrderBy(orderBy).
|
||||
Offset(uint64(options.Page * options.PerPage)).Limit(uint64(options.PerPage))
|
||||
|
||||
query = applyViewRestrictionsFilter(query, options.ViewRestrictions, true)
|
||||
@@ -599,6 +606,10 @@ func (us SqlUserStore) GetAllProfiles(options *model.UserGetOptions) ([]*model.U
|
||||
query = query.Where("Users.DeleteAt = 0")
|
||||
}
|
||||
|
||||
if options.UpdatedAfter > 0 {
|
||||
query = query.Where(sq.Gt{"Users.UpdateAt": options.UpdatedAfter})
|
||||
}
|
||||
|
||||
users := []*model.User{}
|
||||
if err := us.GetReplica().SelectBuilder(&users, query); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get User profiles")
|
||||
|
||||
@@ -562,6 +562,7 @@ type RemoteClusterStore interface {
|
||||
GetAll(offset, limit int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error)
|
||||
UpdateTopics(remoteClusterID string, topics string) (*model.RemoteCluster, error)
|
||||
SetLastPingAt(remoteClusterID string) error
|
||||
UpdateLastGlobalUserSyncAt(remoteID string, syncAt int64) error
|
||||
}
|
||||
|
||||
type ComplianceStore interface {
|
||||
|
||||
@@ -210,6 +210,24 @@ func (_m *RemoteClusterStore) Update(rc *model.RemoteCluster) (*model.RemoteClus
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateLastGlobalUserSyncAt provides a mock function with given fields: remoteID, syncAt
|
||||
func (_m *RemoteClusterStore) UpdateLastGlobalUserSyncAt(remoteID string, syncAt int64) error {
|
||||
ret := _m.Called(remoteID, syncAt)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateLastGlobalUserSyncAt")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
||||
r0 = rf(remoteID, syncAt)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateTopics provides a mock function with given fields: remoteClusterID, topics
|
||||
func (_m *RemoteClusterStore) UpdateTopics(remoteClusterID string, topics string) (*model.RemoteCluster, error) {
|
||||
ret := _m.Called(remoteClusterID, topics)
|
||||
|
||||
@@ -32,6 +32,7 @@ func TestSharedChannelStore(t *testing.T, rctx request.CTX, ss store.Store, s Sq
|
||||
t.Run("HasRemote", func(t *testing.T) { testHasRemote(t, rctx, ss) })
|
||||
t.Run("GetRemoteForUser", func(t *testing.T) { testGetRemoteForUser(t, rctx, ss) })
|
||||
t.Run("UpdateSharedChannelRemoteNextSyncAt", func(t *testing.T) { testUpdateSharedChannelRemoteCursor(t, rctx, ss) })
|
||||
t.Run("UpdateGlobalUserSyncCursor", func(t *testing.T) { testUpdateGlobalUserSyncCursor(t, rctx, ss) })
|
||||
t.Run("DeleteSharedChannelRemote", func(t *testing.T) { testDeleteSharedChannelRemote(t, rctx, ss) })
|
||||
|
||||
t.Run("SaveSharedChannelUser", func(t *testing.T) { testSaveSharedChannelUser(t, rctx, ss) })
|
||||
@@ -932,6 +933,38 @@ func testUpdateSharedChannelRemoteCursor(t *testing.T, rctx request.CTX, ss stor
|
||||
})
|
||||
}
|
||||
|
||||
func testUpdateGlobalUserSyncCursor(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
// Create a remote cluster first
|
||||
rc := &model.RemoteCluster{
|
||||
RemoteId: model.NewId(),
|
||||
SiteURL: "http://example.com",
|
||||
CreatorId: model.NewId(),
|
||||
Name: "test",
|
||||
}
|
||||
rcSaved, err := ss.RemoteCluster().Save(rc)
|
||||
require.NoError(t, err, "couldn't save remote cluster", err)
|
||||
|
||||
futureTimestamp := model.GetMillis() + 3600000 // 1 hour in the future
|
||||
|
||||
t.Run("Update global user sync cursor for remote", func(t *testing.T) {
|
||||
err := ss.RemoteCluster().UpdateLastGlobalUserSyncAt(rcSaved.RemoteId, futureTimestamp)
|
||||
require.NoError(t, err, "update global user sync cursor should not error", err)
|
||||
|
||||
// Verify that the LastGlobalUserSyncAt field was updated in the RemoteCluster table
|
||||
// Small sleep to ensure the transaction is committed
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
updatedRC, err := ss.RemoteCluster().Get(rcSaved.RemoteId, false)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, updatedRC.LastGlobalUserSyncAt, "LastGlobalUserSyncAt should not be zero")
|
||||
require.Equal(t, futureTimestamp, updatedRC.LastGlobalUserSyncAt)
|
||||
})
|
||||
|
||||
t.Run("Update global user sync cursor for non-existent remote", func(t *testing.T) {
|
||||
err := ss.RemoteCluster().UpdateLastGlobalUserSyncAt(model.NewId(), futureTimestamp)
|
||||
require.Error(t, err, "update non-existent remote should error", err)
|
||||
})
|
||||
}
|
||||
|
||||
func testDeleteSharedChannelRemote(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
channel, err := createTestChannel(ss, rctx, "test_remote_delete")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -610,6 +610,32 @@ func testUserStoreGetAllProfiles(t *testing.T, rctx request.CTX, ss store.Store)
|
||||
}, actual)
|
||||
})
|
||||
|
||||
t.Run("filter by UpdatedAfter", func(t *testing.T) {
|
||||
// Update a user to ensure we have a recent update time
|
||||
updateTime := model.GetMillis()
|
||||
u2.FirstName = "Updated"
|
||||
_, updateErr := ss.User().Update(rctx, u2, false)
|
||||
require.NoError(t, updateErr)
|
||||
|
||||
// Query with the UpdatedAfter filter
|
||||
actual, userErr := ss.User().GetAllProfiles(&model.UserGetOptions{
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
UpdatedAfter: updateTime - 1, // Subtract 1 to ensure we capture the update
|
||||
})
|
||||
require.NoError(t, userErr)
|
||||
require.Contains(t, actual, sanitized(u2), "User updated after the specified time should be in the results")
|
||||
|
||||
// Query with a future time, should return no results
|
||||
actual, userErr = ss.User().GetAllProfiles(&model.UserGetOptions{
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
UpdatedAfter: updateTime + 10000, // Future time
|
||||
})
|
||||
require.NoError(t, userErr)
|
||||
require.NotContains(t, actual, sanitized(u2), "Users updated before the specified future time should not be in the results")
|
||||
})
|
||||
|
||||
u8, err := ss.User().Save(rctx, &model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: "u8" + model.NewId(),
|
||||
|
||||
@@ -7909,6 +7909,22 @@ func (s *TimerLayerRemoteClusterStore) Update(rc *model.RemoteCluster) (*model.R
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerRemoteClusterStore) UpdateLastGlobalUserSyncAt(remoteID string, syncAt int64) error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.RemoteClusterStore.UpdateLastGlobalUserSyncAt(remoteID, syncAt)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("RemoteClusterStore.UpdateLastGlobalUserSyncAt", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerRemoteClusterStore) UpdateTopics(remoteClusterID string, topics string) (*model.RemoteCluster, error) {
|
||||
start := time.Now()
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user