MM-12251: Add flag to MoveChannel to remove all deactivated users. (#9515)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
7b338c161b
Коммит
9a4f3ce1e5
@@ -1670,7 +1670,13 @@ func (a *App) PermanentDeleteChannel(channel *model.Channel) *model.AppError {
|
|||||||
|
|
||||||
// This function is intended for use from the CLI. It is not robust against people joining the channel while the move
|
// This function is intended for use from the CLI. It is not robust against people joining the channel while the move
|
||||||
// is in progress, and therefore should not be used from the API without first fixing this potential race condition.
|
// is in progress, and therefore should not be used from the API without first fixing this potential race condition.
|
||||||
func (a *App) MoveChannel(team *model.Team, channel *model.Channel, user *model.User) *model.AppError {
|
func (a *App) MoveChannel(team *model.Team, channel *model.Channel, user *model.User, removeDeactivatedMembers bool) *model.AppError {
|
||||||
|
if removeDeactivatedMembers {
|
||||||
|
if result := <-a.Srv.Store.Channel().RemoveAllDeactivatedMembers(channel.Id); result.Err != nil {
|
||||||
|
return result.Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check that all channel members are in the destination team.
|
// Check that all channel members are in the destination team.
|
||||||
channelMembers, err := a.GetChannelMembersPage(channel.Id, 0, 10000000)
|
channelMembers, err := a.GetChannelMembersPage(channel.Id, 0, 10000000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func TestMoveChannel(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := th.App.MoveChannel(targetTeam, channel1, th.BasicUser); err == nil {
|
if err := th.App.MoveChannel(targetTeam, channel1, th.BasicUser, false); err == nil {
|
||||||
t.Fatal("Should have failed due to mismatched members.")
|
t.Fatal("Should have failed due to mismatched members.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +106,34 @@ func TestMoveChannel(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := th.App.MoveChannel(targetTeam, channel1, th.BasicUser); err != nil {
|
if err := th.App.MoveChannel(targetTeam, channel1, th.BasicUser, false); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test moving a channel with a deactivated user who isn't in the destination team.
|
||||||
|
// It should fail, unless removeDeactivatedMembers is true.
|
||||||
|
deacivatedUser := th.CreateUser()
|
||||||
|
channel2 := th.CreateChannel(sourceTeam)
|
||||||
|
|
||||||
|
if _, err := th.App.AddUserToTeam(sourceTeam.Id, deacivatedUser.Id, ""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := th.App.AddUserToChannel(th.BasicUser, channel2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := th.App.AddUserToChannel(deacivatedUser, channel2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := th.App.UpdateActive(deacivatedUser, false); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := th.App.MoveChannel(targetTeam, channel2, th.BasicUser, false); err == nil {
|
||||||
|
t.Fatal("Should have failed due to mismatched deacivated member.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := th.App.MoveChannel(targetTeam, channel2, th.BasicUser, true); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ func init() {
|
|||||||
ChannelCreateCmd.Flags().Bool("private", false, "Create a private channel.")
|
ChannelCreateCmd.Flags().Bool("private", false, "Create a private channel.")
|
||||||
|
|
||||||
MoveChannelsCmd.Flags().String("username", "", "Required. Username who is moving the channel.")
|
MoveChannelsCmd.Flags().String("username", "", "Required. Username who is moving the channel.")
|
||||||
|
MoveChannelsCmd.Flags().Bool("remove-deactivated-users", false, "Automatically remove any deactivated users from the channel before moving it.")
|
||||||
|
|
||||||
DeleteChannelsCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the channels.")
|
DeleteChannelsCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the channels.")
|
||||||
|
|
||||||
@@ -366,6 +367,8 @@ func moveChannelsCmdF(command *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
user := getUserFromUserArg(a, username)
|
user := getUserFromUserArg(a, username)
|
||||||
|
|
||||||
|
removeDeactivatedMembers, _ := command.Flags().GetBool("remove-deactivated-users")
|
||||||
|
|
||||||
channels := getChannelsFromChannelArgs(a, args[1:])
|
channels := getChannelsFromChannelArgs(a, args[1:])
|
||||||
for i, channel := range channels {
|
for i, channel := range channels {
|
||||||
if channel == nil {
|
if channel == nil {
|
||||||
@@ -373,7 +376,7 @@ func moveChannelsCmdF(command *cobra.Command, args []string) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
originTeamID := channel.TeamId
|
originTeamID := channel.TeamId
|
||||||
if err := moveChannel(a, team, channel, user); err != nil {
|
if err := moveChannel(a, team, channel, user, removeDeactivatedMembers); err != nil {
|
||||||
CommandPrintErrorln("Unable to move channel '" + channel.Name + "' error: " + err.Error())
|
CommandPrintErrorln("Unable to move channel '" + channel.Name + "' error: " + err.Error())
|
||||||
} else {
|
} else {
|
||||||
CommandPrettyPrintln("Moved channel '" + channel.Name + "' to " + team.Name + "(" + team.Id + ") from " + originTeamID + ".")
|
CommandPrettyPrintln("Moved channel '" + channel.Name + "' to " + team.Name + "(" + team.Id + ") from " + originTeamID + ".")
|
||||||
@@ -383,10 +386,10 @@ func moveChannelsCmdF(command *cobra.Command, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveChannel(a *app.App, team *model.Team, channel *model.Channel, user *model.User) *model.AppError {
|
func moveChannel(a *app.App, team *model.Team, channel *model.Channel, user *model.User, removeDeactivatedMembers bool) *model.AppError {
|
||||||
oldTeamId := channel.TeamId
|
oldTeamId := channel.TeamId
|
||||||
|
|
||||||
if err := a.MoveChannel(team, channel, user); err != nil {
|
if err := a.MoveChannel(team, channel, user, removeDeactivatedMembers); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4834,6 +4834,10 @@
|
|||||||
"id": "plugin.api.update_user_status.bad_status",
|
"id": "plugin.api.update_user_status.bad_status",
|
||||||
"translation": "Unable to set the user status. Unknown user status."
|
"translation": "Unable to set the user status. Unknown user status."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "store.sql_channel.remove_all_deactivated_members.app_error",
|
||||||
|
"translation": "We could not remove the deactivated users from the channel"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "store.sql.convert_string_array",
|
"id": "store.sql.convert_string_array",
|
||||||
"translation": "FromDb: Unable to convert StringArray to *string"
|
"translation": "FromDb: Unable to convert StringArray to *string"
|
||||||
|
|||||||
@@ -1478,6 +1478,32 @@ func (s SqlChannelStore) RemoveMember(channelId string, userId string) store.Sto
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) RemoveAllDeactivatedMembers(channelId string) store.StoreChannel {
|
||||||
|
return store.Do(func(result *store.StoreResult) {
|
||||||
|
query := `
|
||||||
|
DELETE
|
||||||
|
FROM
|
||||||
|
ChannelMembers
|
||||||
|
WHERE
|
||||||
|
UserId IN (
|
||||||
|
SELECT
|
||||||
|
Id
|
||||||
|
FROM
|
||||||
|
Users
|
||||||
|
WHERE
|
||||||
|
Users.DeleteAt != 0
|
||||||
|
)
|
||||||
|
AND
|
||||||
|
ChannelMembers.ChannelId = :ChannelId
|
||||||
|
`
|
||||||
|
|
||||||
|
_, err := s.GetMaster().Exec(query, map[string]interface{}{"ChannelId": channelId})
|
||||||
|
if err != nil {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.RemoveAllDeactivatedMembers", "store.sql_channel.remove_all_deactivated_members.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s SqlChannelStore) PermanentDeleteMembersByUser(userId string) store.StoreChannel {
|
func (s SqlChannelStore) PermanentDeleteMembersByUser(userId string) store.StoreChannel {
|
||||||
return store.Do(func(result *store.StoreResult) {
|
return store.Do(func(result *store.StoreResult) {
|
||||||
if _, err := s.GetMaster().Exec("DELETE FROM ChannelMembers WHERE UserId = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
|
if _, err := s.GetMaster().Exec("DELETE FROM ChannelMembers WHERE UserId = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ type ChannelStore interface {
|
|||||||
IsExperimentalPublicChannelsMaterializationEnabled() bool
|
IsExperimentalPublicChannelsMaterializationEnabled() bool
|
||||||
GetAllChannelsForExportAfter(limit int, afterId string) StoreChannel
|
GetAllChannelsForExportAfter(limit int, afterId string) StoreChannel
|
||||||
GetChannelMembersForExport(userId string, teamId string) StoreChannel
|
GetChannelMembersForExport(userId string, teamId string) StoreChannel
|
||||||
|
RemoveAllDeactivatedMembers(channelId string) StoreChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChannelMemberHistoryStore interface {
|
type ChannelMemberHistoryStore interface {
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
|
|||||||
t.Run("MaterializedPublicChannels", func(t *testing.T) { testMaterializedPublicChannels(t, ss, s) })
|
t.Run("MaterializedPublicChannels", func(t *testing.T) { testMaterializedPublicChannels(t, ss, s) })
|
||||||
t.Run("GetAllChannelsForExportAfter", func(t *testing.T) { testChannelStoreGetAllChannelsForExportAfter(t, ss) })
|
t.Run("GetAllChannelsForExportAfter", func(t *testing.T) { testChannelStoreGetAllChannelsForExportAfter(t, ss) })
|
||||||
t.Run("GetChannelMembersForExport", func(t *testing.T) { testChannelStoreGetChannelMembersForExport(t, ss) })
|
t.Run("GetChannelMembersForExport", func(t *testing.T) { testChannelStoreGetChannelMembersForExport(t, ss) })
|
||||||
|
t.Run("RemoveAllDeactivatedMembers", func(t *testing.T) { testChannelStoreRemoveAllDeactivatedMembers(t, ss) })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2806,3 +2807,75 @@ func testChannelStoreGetChannelMembersForExport(t *testing.T, ss store.Store) {
|
|||||||
assert.Equal(t, c1.Id, cmfe1.ChannelId)
|
assert.Equal(t, c1.Id, cmfe1.ChannelId)
|
||||||
assert.Equal(t, u1.Id, cmfe1.UserId)
|
assert.Equal(t, u1.Id, cmfe1.UserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testChannelStoreRemoveAllDeactivatedMembers(t *testing.T, ss store.Store) {
|
||||||
|
// Set up all the objects needed in the store.
|
||||||
|
t1 := model.Team{}
|
||||||
|
t1.DisplayName = "Name"
|
||||||
|
t1.Name = model.NewId()
|
||||||
|
t1.Email = MakeEmail()
|
||||||
|
t1.Type = model.TEAM_OPEN
|
||||||
|
store.Must(ss.Team().Save(&t1))
|
||||||
|
|
||||||
|
c1 := model.Channel{}
|
||||||
|
c1.TeamId = t1.Id
|
||||||
|
c1.DisplayName = "Channel1"
|
||||||
|
c1.Name = "zz" + model.NewId() + "b"
|
||||||
|
c1.Type = model.CHANNEL_OPEN
|
||||||
|
store.Must(ss.Channel().Save(&c1, -1))
|
||||||
|
|
||||||
|
u1 := model.User{}
|
||||||
|
u1.Email = MakeEmail()
|
||||||
|
u1.Nickname = model.NewId()
|
||||||
|
store.Must(ss.User().Save(&u1))
|
||||||
|
|
||||||
|
u2 := model.User{}
|
||||||
|
u2.Email = MakeEmail()
|
||||||
|
u2.Nickname = model.NewId()
|
||||||
|
store.Must(ss.User().Save(&u2))
|
||||||
|
|
||||||
|
u3 := model.User{}
|
||||||
|
u3.Email = MakeEmail()
|
||||||
|
u3.Nickname = model.NewId()
|
||||||
|
store.Must(ss.User().Save(&u3))
|
||||||
|
|
||||||
|
m1 := model.ChannelMember{}
|
||||||
|
m1.ChannelId = c1.Id
|
||||||
|
m1.UserId = u1.Id
|
||||||
|
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||||
|
store.Must(ss.Channel().SaveMember(&m1))
|
||||||
|
|
||||||
|
m2 := model.ChannelMember{}
|
||||||
|
m2.ChannelId = c1.Id
|
||||||
|
m2.UserId = u2.Id
|
||||||
|
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||||
|
store.Must(ss.Channel().SaveMember(&m2))
|
||||||
|
|
||||||
|
m3 := model.ChannelMember{}
|
||||||
|
m3.ChannelId = c1.Id
|
||||||
|
m3.UserId = u3.Id
|
||||||
|
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||||
|
store.Must(ss.Channel().SaveMember(&m3))
|
||||||
|
|
||||||
|
// Get all the channel members. Check there are 3.
|
||||||
|
r1 := <-ss.Channel().GetMembers(c1.Id, 0, 1000)
|
||||||
|
assert.Nil(t, r1.Err)
|
||||||
|
d1 := r1.Data.(*model.ChannelMembers)
|
||||||
|
assert.Len(t, *d1, 3)
|
||||||
|
|
||||||
|
// Deactivate users 1 & 2.
|
||||||
|
u1.DeleteAt = model.GetMillis()
|
||||||
|
u2.DeleteAt = model.GetMillis()
|
||||||
|
require.Nil(t, (<-ss.User().Update(&u1, true)).Err)
|
||||||
|
require.Nil(t, (<-ss.User().Update(&u2, true)).Err)
|
||||||
|
|
||||||
|
// Remove all deactivated users from the channel.
|
||||||
|
assert.Nil(t, (<-ss.Channel().RemoveAllDeactivatedMembers(c1.Id)).Err)
|
||||||
|
|
||||||
|
// Get all the channel members. Check there is now only 1: m3.
|
||||||
|
r2 := <-ss.Channel().GetMembers(c1.Id, 0, 1000)
|
||||||
|
assert.Nil(t, r1.Err)
|
||||||
|
d2 := r2.Data.(*model.ChannelMembers)
|
||||||
|
assert.Len(t, *d2, 1)
|
||||||
|
assert.Equal(t, (*d2)[0].UserId, u3.Id)
|
||||||
|
}
|
||||||
|
|||||||
@@ -779,6 +779,22 @@ func (_m *ChannelStore) PermanentDeleteMembersByUser(userId string) store.StoreC
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveAllDeactivatedMembers provides a mock function with given fields: channelId
|
||||||
|
func (_m *ChannelStore) RemoveAllDeactivatedMembers(channelId string) store.StoreChannel {
|
||||||
|
ret := _m.Called(channelId)
|
||||||
|
|
||||||
|
var r0 store.StoreChannel
|
||||||
|
if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
|
||||||
|
r0 = rf(channelId)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(store.StoreChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveMember provides a mock function with given fields: channelId, userId
|
// RemoveMember provides a mock function with given fields: channelId, userId
|
||||||
func (_m *ChannelStore) RemoveMember(channelId string, userId string) store.StoreChannel {
|
func (_m *ChannelStore) RemoveMember(channelId string, userId string) store.StoreChannel {
|
||||||
ret := _m.Called(channelId, userId)
|
ret := _m.Called(channelId, userId)
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user