[MM-44168] Add feature to restore archived user groups (#20369)

* Add undelete feature, add mocks, tests

* Rename undelete->restore

* make store-layers

* make app-layers

* Store -> Store()

* Lint fixes

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Shivashis Padhi
2022-11-17 14:53:54 +05:30
коммит произвёл GitHub
родитель 17332035fa
Коммит 5be8557247
14 изменённых файлов: 301 добавлений и 0 удалений

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

@@ -4512,6 +4512,24 @@ func (s *OpenTracingLayerGroupStore) PermittedSyncableAdmins(syncableID string,
return result, err
}
func (s *OpenTracingLayerGroupStore) Restore(groupID string) (*model.Group, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "GroupStore.Restore")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.GroupStore.Restore(groupID)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerGroupStore) TeamMembersMinusGroupMembers(teamID string, groupIDs []string, page int, perPage int) ([]*model.UserWithGroups, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "GroupStore.TeamMembersMinusGroupMembers")

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

@@ -5097,6 +5097,27 @@ func (s *RetryLayerGroupStore) PermittedSyncableAdmins(syncableID string, syncab
}
func (s *RetryLayerGroupStore) Restore(groupID string) (*model.Group, error) {
tries := 0
for {
result, err := s.GroupStore.Restore(groupID)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerGroupStore) TeamMembersMinusGroupMembers(teamID string, groupIDs []string, page int, perPage int) ([]*model.UserWithGroups, error) {
tries := 0

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

@@ -360,6 +360,25 @@ func (s *SqlGroupStore) Delete(groupID string) (*model.Group, error) {
return &group, nil
}
func (s *SqlGroupStore) Restore(groupID string) (*model.Group, error) {
var group model.Group
if err := s.GetReplicaX().Get(&group, "SELECT * from UserGroups WHERE Id = ? AND DeleteAt != 0", groupID); err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("Group", groupID)
}
return nil, errors.Wrapf(err, "failed to get Group with id=%s", groupID)
}
time := model.GetMillis()
if _, err := s.GetMasterX().Exec(`UPDATE UserGroups
SET DeleteAt=0, UpdateAt=?
WHERE Id=? AND DeleteAt!=0`, time, groupID); err != nil {
return nil, errors.Wrapf(err, "failed to update Group with id=%s", groupID)
}
return &group, nil
}
func (s *SqlGroupStore) GetMember(groupID, userID string) (*model.GroupMember, error) {
query, args, err := s.getQueryBuilder().
Select("*").

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

@@ -832,6 +832,7 @@ type GroupStore interface {
GetByUser(userID string) ([]*model.Group, error)
Update(group *model.Group) (*model.Group, error)
Delete(groupID string) (*model.Group, error)
Restore(groupID string) (*model.Group, error)
GetMemberUsers(groupID string) ([]*model.User, error)
GetMemberUsersPage(groupID string, page int, perPage int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, error)

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

@@ -32,6 +32,7 @@ func TestGroupStore(t *testing.T, ss store.Store) {
t.Run("GetByUser", func(t *testing.T) { testGroupStoreGetByUser(t, ss) })
t.Run("Update", func(t *testing.T) { testGroupStoreUpdate(t, ss) })
t.Run("Delete", func(t *testing.T) { testGroupStoreDelete(t, ss) })
t.Run("Restore", func(t *testing.T) { testGroupStoreRestore(t, ss) })
t.Run("GetMemberUsers", func(t *testing.T) { testGroupGetMemberUsers(t, ss) })
t.Run("GetMemberUsersPage", func(t *testing.T) { testGroupGetMemberUsersPage(t, ss) })
@@ -741,6 +742,59 @@ func testGroupStoreDelete(t *testing.T, ss store.Store) {
require.True(t, errors.As(err, &nfErr))
}
func testGroupStoreRestore(t *testing.T, ss store.Store) {
// Save a group
g1 := &model.Group{
Name: model.NewString(model.NewId()),
DisplayName: model.NewId(),
Description: model.NewId(),
Source: model.GroupSourceLdap,
RemoteId: model.NewString(model.NewId()),
}
d1, err := ss.Group().Create(g1)
require.NoError(t, err)
require.Len(t, d1.Id, 26)
// Check the group is retrievable
_, err = ss.Group().Get(d1.Id)
require.NoError(t, err)
// Delete the group
_, err = ss.Group().Delete(d1.Id)
require.NoError(t, err)
// Get the before count
d7, err := ss.Group().GetAllBySource(model.GroupSourceLdap)
require.NoError(t, err)
beforeCount := len(d7)
// restore the group
_, err = ss.Group().Restore(d1.Id)
require.NoError(t, err)
// Check the group is restored
d4, err := ss.Group().Get(d1.Id)
require.NoError(t, err)
require.Zero(t, d4.DeleteAt)
// Check the after count
d5, err := ss.Group().GetAllBySource(model.GroupSourceLdap)
require.NoError(t, err)
afterCount := len(d5)
require.Condition(t, func() bool { return beforeCount == afterCount-1 })
// Try and restore a nonexistent group
_, err = ss.Group().Delete(model.NewId())
require.Error(t, err)
var nfErr *store.ErrNotFound
require.True(t, errors.As(err, &nfErr))
// Cannot restore again
_, err = ss.Group().Restore(d1.Id)
require.True(t, errors.As(err, &nfErr))
}
func testGroupGetMemberUsers(t *testing.T, ss store.Store) {
// Save a group
g1 := &model.Group{

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

@@ -1012,6 +1012,29 @@ func (_m *GroupStore) PermittedSyncableAdmins(syncableID string, syncableType mo
return r0, r1
}
// Restore provides a mock function with given fields: groupID
func (_m *GroupStore) Restore(groupID string) (*model.Group, error) {
ret := _m.Called(groupID)
var r0 *model.Group
if rf, ok := ret.Get(0).(func(string) *model.Group); ok {
r0 = rf(groupID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Group)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(groupID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// TeamMembersMinusGroupMembers provides a mock function with given fields: teamID, groupIDs, page, perPage
func (_m *GroupStore) TeamMembersMinusGroupMembers(teamID string, groupIDs []string, page int, perPage int) ([]*model.UserWithGroups, error) {
ret := _m.Called(teamID, groupIDs, page, perPage)

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

@@ -4101,6 +4101,22 @@ func (s *TimerLayerGroupStore) PermittedSyncableAdmins(syncableID string, syncab
return result, err
}
func (s *TimerLayerGroupStore) Restore(groupID string) (*model.Group, error) {
start := time.Now()
result, err := s.GroupStore.Restore(groupID)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("GroupStore.Restore", success, elapsed)
}
return result, err
}
func (s *TimerLayerGroupStore) TeamMembersMinusGroupMembers(teamID string, groupIDs []string, page int, perPage int) ([]*model.UserWithGroups, error) {
start := time.Now()