[MM-12667] Allow including deactivated users in bulk import and export (#10353)

* [MM-12667] Allow including deactivated users in bulk import and export

1. Added `deleteAt` for user type import line
2. Adding deactivated users data in bulk export
3. Importing deactivated users data in bulk import

Added/Updated relevant test cases

* Fixed export of replies to posts by deleted users.
Updated tests for same
This commit is contained in:
Sandeep Sukhani
2019-03-01 21:20:24 +05:30
committed by George Goldberg
parent 7ac5715a02
commit 7f9e1273d7
9 changed files with 96 additions and 11 deletions

View File

@@ -184,11 +184,6 @@ func (a *App) ExportAllUsers(writer io.Writer) *model.AppError {
for _, user := range users {
afterId = user.Id
// Skip deleted.
if user.DeleteAt != 0 {
continue
}
// Gathering here the exportable preferences to pass them on to ImportLineFromUser
exportedPrefs := make(map[string]*string)
allPrefs, err := a.GetPreferencesForUser(user.Id)

View File

@@ -68,6 +68,7 @@ func ImportLineFromUser(user *model.User, exportedPrefs map[string]*string) *Lin
ChannelDisplayMode: exportedPrefs["ChannelDisplayMode"],
TutorialStep: exportedPrefs["TutorialStep"],
EmailInterval: exportedPrefs["EmailInterval"],
DeleteAt: &user.DeleteAt,
},
}
}

View File

@@ -170,8 +170,13 @@ func TestExportAllUsers(t *testing.T) {
th1 := Setup(t).InitBasic()
defer th1.TearDown()
// Adding a user and deactivating it to check whether it gets included in bulk export
user := th1.CreateUser()
_, err := th1.App.UpdateActive(user, false)
require.Nil(t, err)
var b bytes.Buffer
err := th1.App.BulkExport(&b, "somefile", "somePath", "someDir")
err = th1.App.BulkExport(&b, "somefile", "somePath", "someDir")
require.Nil(t, err)
th2 := Setup(t)
@@ -192,4 +197,20 @@ func TestExportAllUsers(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, len(users1), len(users2))
assert.ElementsMatch(t, users1, users2)
// Checking whether deactivated users were included in bulk export
deletedUsers1, err := th1.App.GetUsers(&model.UserGetOptions{
Inactive: true,
Page: 0,
PerPage: 10,
})
assert.Nil(t, err)
deletedUsers2, err := th1.App.GetUsers(&model.UserGetOptions{
Inactive: true,
Page: 0,
PerPage: 10,
})
assert.Nil(t, err)
assert.Equal(t, len(deletedUsers1), len(deletedUsers2))
assert.ElementsMatch(t, deletedUsers1, deletedUsers2)
}

View File

@@ -369,6 +369,13 @@ func (a *App) ImportUser(data *UserImportData, dryRun bool) *model.AppError {
}
}
if data.DeleteAt != nil {
if user.DeleteAt != *data.DeleteAt {
user.DeleteAt = *data.DeleteAt
hasUserChanged = true
}
}
var roles string
if data.Roles != nil {
if user.Roles != *data.Roles {

View File

@@ -1300,6 +1300,51 @@ func TestImportImportUser(t *testing.T) {
assert.True(t, channelMember.SchemeUser)
assert.Equal(t, "", channelMember.ExplicitRoles)
// Test importing deleted user with a valid team & valid channel name in apply mode.
username = model.NewId()
deleteAt := model.GetMillis()
deletedUserData := &UserImportData{
Username: &username,
DeleteAt: &deleteAt,
Email: ptrStr(model.NewId() + "@example.com"),
Teams: &[]UserTeamImportData{
{
Name: &team.Name,
Roles: ptrStr("team_user"),
Channels: &[]UserChannelImportData{
{
Name: &channel.Name,
Roles: ptrStr("channel_user"),
},
},
},
},
}
err = th.App.ImportUser(deletedUserData, false)
assert.Nil(t, err)
user, err = th.App.GetUserByUsername(*deletedUserData.Username)
if err != nil {
t.Fatalf("Failed to get user from database.")
}
teamMember, err = th.App.GetTeamMember(team.Id, user.Id)
if err != nil {
t.Fatalf("Failed to get the team member")
}
assert.True(t, teamMember.SchemeUser)
assert.Equal(t, "", teamMember.ExplicitRoles)
channelMember, err = th.App.GetChannelMember(channel.Id, user.Id)
if err != nil {
t.Fatalf("Failed to get the channel member")
}
assert.True(t, channelMember.SchemeUser)
assert.Equal(t, "", channelMember.ExplicitRoles)
}
func TestImportUserDefaultNotifyProps(t *testing.T) {

View File

@@ -182,12 +182,13 @@ func TestImportBulkImport(t *testing.T) {
{"type": "channel", "channel": {"type": "O", "display_name": "xr6m6udffngark2uekvr3hoeny", "team": "` + teamName + `", "name": "` + channelName + `"}}
{"type": "user", "user": {"username": "` + username + `", "email": "` + username + `@example.com", "teams": [{"name": "` + teamName + `","theme": "` + teamTheme1 + `", "channels": [{"name": "` + channelName + `"}]}]}}
{"type": "user", "user": {"username": "` + username2 + `", "email": "` + username2 + `@example.com", "teams": [{"name": "` + teamName + `","theme": "` + teamTheme2 + `", "channels": [{"name": "` + channelName + `"}]}]}}
{"type": "user", "user": {"username": "` + username3 + `", "email": "` + username3 + `@example.com", "teams": [{"name": "` + teamName + `", "channels": [{"name": "` + channelName + `"}]}]}}
{"type": "user", "user": {"username": "` + username3 + `", "email": "` + username3 + `@example.com", "teams": [{"name": "` + teamName + `", "channels": [{"name": "` + channelName + `"}], "delete_at": 123456789016}]}}
{"type": "post", "post": {"team": "` + teamName + `", "channel": "` + channelName + `", "user": "` + username + `", "message": "Hello World", "create_at": 123456789012, "attachments":[{"path": "` + testImage + `"}]}}
{"type": "post", "post": {"team": "` + teamName + `", "channel": "` + channelName + `", "user": "` + username3 + `", "message": "Hey Everyone!", "create_at": 123456789013, "attachments":[{"path": "` + testImage + `"}]}}
{"type": "direct_channel", "direct_channel": {"members": ["` + username + `", "` + username2 + `"]}}
{"type": "direct_channel", "direct_channel": {"members": ["` + username + `", "` + username2 + `", "` + username3 + `"]}}
{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `"], "user": "` + username + `", "message": "Hello Direct Channel", "create_at": 123456789013}}
{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `", "` + username3 + `"], "user": "` + username + `", "message": "Hello Group Channel", "create_at": 123456789014}}
{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `"], "user": "` + username + `", "message": "Hello Direct Channel", "create_at": 123456789014}}
{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `", "` + username3 + `"], "user": "` + username + `", "message": "Hello Group Channel", "create_at": 123456789015}}
{"type": "emoji", "emoji": {"name": "` + emojiName + `", "image": "` + testImage + `"}}`
if err, line := th.App.BulkImport(strings.NewReader(data1), false, 2); err != nil || line != 0 {

View File

@@ -55,6 +55,7 @@ type UserImportData struct {
UseMarkdownPreview *string `json:"feature_enabled_markdown_preview,omitempty"`
UseFormatting *string `json:"formatting,omitempty"`
ShowUnreadSection *string `json:"show_unread_section,omitempty"`
DeleteAt *int64 `json:"delete_at,omitempty"`
Teams *[]UserTeamImportData `json:"teams,omitempty"`

View File

@@ -1327,7 +1327,6 @@ func (s *SqlPostStore) GetParentsForExportAfter(limit int, afterId string) store
AND p1.DeleteAt = 0
AND Channels.DeleteAt = 0
AND Teams.DeleteAt = 0
AND Users.DeleteAt = 0
ORDER BY
p1.Id
LIMIT
@@ -1356,7 +1355,6 @@ func (s *SqlPostStore) GetRepliesForExport(parentId string) store.StoreChannel {
WHERE
Posts.ParentId = :ParentId
AND Posts.DeleteAt = 0
AND Users.DeleteAt = 0
ORDER BY
Posts.Id`,
map[string]interface{}{"ParentId": parentId})

View File

@@ -1941,4 +1941,20 @@ func testPostStoreGetRepliesForExport(t *testing.T, ss store.Store) {
assert.Equal(t, reply1.Id, p2.Id)
assert.Equal(t, reply1.Message, p2.Message)
assert.Equal(t, reply1.Username, u1.Username)
// Checking whether replies by deleted user are exported
u1.DeleteAt = 1002
store.Must(ss.User().Update(&u1, false))
r1 = <-ss.Post().GetRepliesForExport(p1.Id)
assert.Nil(t, r1.Err)
d1 = r1.Data.([]*model.ReplyForExport)
assert.Len(t, d1, 1)
reply1 = d1[0]
assert.Equal(t, reply1.Id, p2.Id)
assert.Equal(t, reply1.Message, p2.Message)
assert.Equal(t, reply1.Username, u1.Username)
}