[MM 12458] Include user preferences in bulk export (#9784)

* First version of the preference exporter

* First working version of the preference export

* Removed useless debug line

* Added special case of empty string to harverst the omitempty feature

* Removed dangling line from rebase

* Moved exportablepreference in scope

* Fix go vet issue

* Fix formating

* Add simple test

* Add full test for BulkExportUsers

* Improve variable naming
Этот коммит содержится в:
Simone Salsi
2019-02-01 12:43:41 +01:00
коммит произвёл George Goldberg
родитель ee9395f1b6
Коммит 0f35c4b24d
4 изменённых файлов: 136 добавлений и 13 удалений

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

@@ -15,6 +15,41 @@ import (
"github.com/pkg/errors"
)
// We use this map to identify the exportable preferences.
// Here we link the preference category and name, to the name of the relevant filed in the import struct.
var exportablePreferences = map[ComparablePreference]string{{
Category: model.PREFERENCE_CATEGORY_THEME,
Name: "",
}: "Theme", {
Category: model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS,
Name: "feature_enabled_markdown_preview",
}: "UseMarkdownPreview", {
Category: model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS,
Name: "formatting",
}: "UseFormatting", {
Category: model.PREFERENCE_CATEGORY_SIDEBAR_SETTINGS,
Name: "show_unread_section",
}: "ShowUnreadSection", {
Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
Name: model.PREFERENCE_NAME_USE_MILITARY_TIME,
}: "UseMilitaryTime", {
Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
Name: model.PREFERENCE_NAME_COLLAPSE_SETTING,
}: "CollapsePreviews", {
Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
Name: model.PREFERENCE_NAME_MESSAGE_DISPLAY,
}: "MessageDisplay", {
Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
Name: "channel_display_mode",
}: "ChannelDisplayMode", {
Category: model.PREFERENCE_CATEGORY_TUTORIAL_STEPS,
Name: "",
}: "TutorialStep", {
Category: model.PREFERENCE_CATEGORY_NOTIFICATIONS,
Name: model.PREFERENCE_NAME_EMAIL_INTERVAL,
}: "EmailInterval",
}
func (a *App) BulkExport(writer io.Writer, file string, pathToEmojiDir string, dirNameToExportEmoji string) *model.AppError {
if err := a.ExportVersion(writer); err != nil {
return err
@@ -154,7 +189,45 @@ func (a *App) ExportAllUsers(writer io.Writer) *model.AppError {
continue
}
userLine := ImportLineFromUser(user)
// Gathering here the exportable preferences to pass them on to ImportLineFromUser
exportedPrefs := make(map[string]*string)
allPrefs, err := a.GetPreferencesForUser(user.Id)
if err != nil {
return err
}
for _, pref := range allPrefs {
// We need to manage the special cases
// Here we manage Tutorial steps
if pref.Category == model.PREFERENCE_CATEGORY_TUTORIAL_STEPS {
pref.Name = ""
// Then the email interval
} else if pref.Category == model.PREFERENCE_CATEGORY_NOTIFICATIONS && pref.Name == model.PREFERENCE_NAME_EMAIL_INTERVAL {
switch pref.Value {
case model.PREFERENCE_EMAIL_INTERVAL_NO_BATCHING_SECONDS:
pref.Value = model.PREFERENCE_EMAIL_INTERVAL_IMMEDIATELY
case model.PREFERENCE_EMAIL_INTERVAL_FIFTEEN_AS_SECONDS:
pref.Value = model.PREFERENCE_EMAIL_INTERVAL_FIFTEEN
case model.PREFERENCE_EMAIL_INTERVAL_HOUR_AS_SECONDS:
pref.Value = model.PREFERENCE_EMAIL_INTERVAL_HOUR
case "0":
pref.Value = ""
}
}
id, ok := exportablePreferences[ComparablePreference{
Category: pref.Category,
Name: pref.Name,
}]
if ok {
prefPtr := pref.Value
if prefPtr != "" {
exportedPrefs[id] = &prefPtr
} else {
exportedPrefs[id] = nil
}
}
}
userLine := ImportLineFromUser(user, exportedPrefs)
userLine.User.NotifyProps = a.buildUserNotifyProps(user.NotifyProps)

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

@@ -38,20 +38,36 @@ func ImportLineFromChannel(channel *model.ChannelForExport) *LineImportData {
}
}
func ImportLineFromUser(user *model.User) *LineImportData {
func ImportLineFromUser(user *model.User, exportedPrefs map[string]*string) *LineImportData {
// Bulk Importer doesn't accept "empty string" for AuthService.
var authService *string
if user.AuthService != "" {
authService = &user.AuthService
}
return &LineImportData{
Type: "user",
User: &UserImportData{
Username: &user.Username,
Email: &user.Email,
AuthService: &user.AuthService,
AuthData: user.AuthData,
Nickname: &user.Nickname,
FirstName: &user.FirstName,
LastName: &user.LastName,
Position: &user.Position,
Roles: &user.Roles,
Locale: &user.Locale,
Username: &user.Username,
Email: &user.Email,
AuthService: authService,
AuthData: user.AuthData,
Nickname: &user.Nickname,
FirstName: &user.FirstName,
LastName: &user.LastName,
Position: &user.Position,
Roles: &user.Roles,
Locale: &user.Locale,
UseMarkdownPreview: exportedPrefs["UseMarkdownPreview"],
UseFormatting: exportedPrefs["UseFormatting"],
ShowUnreadSection: exportedPrefs["ShowUnreadSection"],
Theme: exportedPrefs["Theme"],
UseMilitaryTime: exportedPrefs["UseMilitaryTime"],
CollapsePreviews: exportedPrefs["CollapsePreviews"],
MessageDisplay: exportedPrefs["MessageDisplay"],
ChannelDisplayMode: exportedPrefs["ChannelDisplayMode"],
TutorialStep: exportedPrefs["TutorialStep"],
EmailInterval: exportedPrefs["EmailInterval"],
},
}
}

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

@@ -1,14 +1,15 @@
package app
import (
"bytes"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
"github.com/stretchr/testify/require"
)
func TestReactionsOfPost(t *testing.T) {
@@ -164,3 +165,31 @@ func TestExportCustomEmoji(t *testing.T) {
t.Fatal(err)
}
}
func TestExportAllUsers(t *testing.T) {
th1 := Setup().InitBasic()
defer th1.TearDown()
var b bytes.Buffer
err := th1.App.BulkExport(&b, "somefile", "somePath", "someDir")
require.Nil(t, err)
th2 := Setup()
defer th2.TearDown()
err, i := th2.App.BulkImport(&b, false, 5)
assert.Nil(t, err)
assert.Equal(t, 0, i)
users1, err := th1.App.GetUsers(&model.UserGetOptions{
Page: 0,
PerPage: 10,
})
assert.Nil(t, err)
users2, err := th2.App.GetUsers(&model.UserGetOptions{
Page: 0,
PerPage: 10,
})
assert.Nil(t, err)
assert.Equal(t, len(users1), len(users2))
assert.ElementsMatch(t, users1, users2)
}

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

@@ -190,3 +190,8 @@ type LineImportWorkerError struct {
type AttachmentImportData struct {
Path *string `json:"path"`
}
type ComparablePreference struct {
Category string
Name string
}