[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
Этот коммит содержится в:
коммит произвёл
George Goldberg
родитель
ee9395f1b6
Коммит
0f35c4b24d
@@ -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
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user