* MM-11424 Extend bulk import to support themes across teams(#9305) Also added: Advanced Settings: +'feature_enabled_markdown_preview' +'formatting' Sidebar Settings: +'show_unread_section' * MM-11424 (PR review) user teams' theme validator test updated * MM-11424 (PR review) added test with valid JSON of invalid theme(by structure) JSON string contains numeric and JSON object fields, not just a correct "string":"string" map
Этот коммит содержится в:
коммит произвёл
George Goldberg
родитель
35c148d255
Коммит
517faccc33
@@ -555,6 +555,33 @@ func (a *App) ImportUser(data *UserImportData, dryRun bool) *model.AppError {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.UseMarkdownPreview != nil {
|
||||||
|
preferences = append(preferences, model.Preference{
|
||||||
|
UserId: savedUser.Id,
|
||||||
|
Category: model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS,
|
||||||
|
Name: "feature_enabled_markdown_preview",
|
||||||
|
Value: *data.UseMarkdownPreview,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.UseFormatting != nil {
|
||||||
|
preferences = append(preferences, model.Preference{
|
||||||
|
UserId: savedUser.Id,
|
||||||
|
Category: model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS,
|
||||||
|
Name: "formatting",
|
||||||
|
Value: *data.UseFormatting,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.ShowUnreadSection != nil {
|
||||||
|
preferences = append(preferences, model.Preference{
|
||||||
|
UserId: savedUser.Id,
|
||||||
|
Category: model.PREFERENCE_CATEGORY_SIDEBAR_SETTINGS,
|
||||||
|
Name: "show_unread_section",
|
||||||
|
Value: *data.ShowUnreadSection,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if len(preferences) > 0 {
|
if len(preferences) > 0 {
|
||||||
if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil {
|
if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil {
|
||||||
return model.NewAppError("BulkImport", "app.import.import_user.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError)
|
return model.NewAppError("BulkImport", "app.import.import_user.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError)
|
||||||
@@ -569,12 +596,23 @@ func (a *App) ImportUserTeams(user *model.User, data *[]UserTeamImportData) *mod
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var teamThemePreferences model.Preferences
|
||||||
for _, tdata := range *data {
|
for _, tdata := range *data {
|
||||||
team, err := a.GetTeamByName(*tdata.Name)
|
team, err := a.GetTeamByName(*tdata.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Team-specific theme Preferences.
|
||||||
|
if tdata.Theme != nil {
|
||||||
|
teamThemePreferences = append(teamThemePreferences, model.Preference{
|
||||||
|
UserId: user.Id,
|
||||||
|
Category: model.PREFERENCE_CATEGORY_THEME,
|
||||||
|
Name: team.Id,
|
||||||
|
Value: *tdata.Theme,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var roles string
|
var roles string
|
||||||
isSchemeUser := true
|
isSchemeUser := true
|
||||||
isSchemeAdmin := false
|
isSchemeAdmin := false
|
||||||
@@ -622,6 +660,12 @@ func (a *App) ImportUserTeams(user *model.User, data *[]UserTeamImportData) *mod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(teamThemePreferences) > 0 {
|
||||||
|
if result := <-a.Srv.Store.Preference().Save(&teamThemePreferences); result.Err != nil {
|
||||||
|
return model.NewAppError("BulkImport", "app.import.import_user_teams.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1016,6 +1016,7 @@ func TestImportImportUser(t *testing.T) {
|
|||||||
data.Teams = &[]UserTeamImportData{
|
data.Teams = &[]UserTeamImportData{
|
||||||
{
|
{
|
||||||
Name: &teamName,
|
Name: &teamName,
|
||||||
|
Theme: ptrStr(`{"awayIndicator":"#DBBD4E","buttonBg":"#23A1FF","buttonColor":"#FFFFFF","centerChannelBg":"#ffffff","centerChannelColor":"#333333","codeTheme":"github","image":"/static/files/a4a388b38b32678e83823ef1b3e17766.png","linkColor":"#2389d7","mentionBg":"#2389d7","mentionColor":"#ffffff","mentionHighlightBg":"#fff2bb","mentionHighlightLink":"#2f81b7","newMessageSeparator":"#FF8800","onlineIndicator":"#7DBE00","sidebarBg":"#fafafa","sidebarHeaderBg":"#3481B9","sidebarHeaderTextColor":"#ffffff","sidebarText":"#333333","sidebarTextActiveBorder":"#378FD2","sidebarTextActiveColor":"#111111","sidebarTextHoverBg":"#e6f2fa","sidebarUnreadText":"#333333","type":"Mattermost"}`),
|
||||||
Roles: ptrStr("team_user team_admin"),
|
Roles: ptrStr("team_user team_admin"),
|
||||||
Channels: &[]UserChannelImportData{
|
Channels: &[]UserChannelImportData{
|
||||||
{
|
{
|
||||||
@@ -1049,6 +1050,7 @@ func TestImportImportUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, channel.Id, "true")
|
checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, channel.Id, "true")
|
||||||
|
checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_THEME, team.Id, *(*data.Teams)[0].Theme)
|
||||||
|
|
||||||
// No more new member objects.
|
// No more new member objects.
|
||||||
if tmc, err := th.App.GetTeamMembers(team.Id, 0, 1000); err != nil {
|
if tmc, err := th.App.GetTeamMembers(team.Id, 0, 1000); err != nil {
|
||||||
@@ -1074,6 +1076,9 @@ func TestImportImportUser(t *testing.T) {
|
|||||||
MessageDisplay: ptrStr("compact"),
|
MessageDisplay: ptrStr("compact"),
|
||||||
ChannelDisplayMode: ptrStr("centered"),
|
ChannelDisplayMode: ptrStr("centered"),
|
||||||
TutorialStep: ptrStr("3"),
|
TutorialStep: ptrStr("3"),
|
||||||
|
UseMarkdownPreview: ptrStr("true"),
|
||||||
|
UseFormatting: ptrStr("true"),
|
||||||
|
ShowUnreadSection: ptrStr("true"),
|
||||||
}
|
}
|
||||||
if err := th.App.ImportUser(&data, false); err != nil {
|
if err := th.App.ImportUser(&data, false); err != nil {
|
||||||
t.Fatalf("Should have succeeded.")
|
t.Fatalf("Should have succeeded.")
|
||||||
@@ -1091,6 +1096,9 @@ func TestImportImportUser(t *testing.T) {
|
|||||||
checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "message_display", *data.MessageDisplay)
|
checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "message_display", *data.MessageDisplay)
|
||||||
checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "channel_display_mode", *data.ChannelDisplayMode)
|
checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "channel_display_mode", *data.ChannelDisplayMode)
|
||||||
checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, user.Id, *data.TutorialStep)
|
checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, user.Id, *data.TutorialStep)
|
||||||
|
checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "feature_enabled_markdown_preview", *data.UseMarkdownPreview)
|
||||||
|
checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS, "formatting", *data.UseFormatting)
|
||||||
|
checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_SIDEBAR_SETTINGS, "show_unread_section", *data.ShowUnreadSection)
|
||||||
|
|
||||||
// Change those preferences.
|
// Change those preferences.
|
||||||
data = UserImportData{
|
data = UserImportData{
|
||||||
|
|||||||
@@ -173,13 +173,15 @@ func TestImportBulkImport(t *testing.T) {
|
|||||||
emojiName := model.NewId()
|
emojiName := model.NewId()
|
||||||
testsDir, _ := utils.FindDir("tests")
|
testsDir, _ := utils.FindDir("tests")
|
||||||
testImage := filepath.Join(testsDir, "test.png")
|
testImage := filepath.Join(testsDir, "test.png")
|
||||||
|
teamTheme1 := `{\"awayIndicator\":\"#DBBD4E\",\"buttonBg\":\"#23A1FF\",\"buttonColor\":\"#FFFFFF\",\"centerChannelBg\":\"#ffffff\",\"centerChannelColor\":\"#333333\",\"codeTheme\":\"github\",\"image\":\"/static/files/a4a388b38b32678e83823ef1b3e17766.png\",\"linkColor\":\"#2389d7\",\"mentionBg\":\"#2389d7\",\"mentionColor\":\"#ffffff\",\"mentionHighlightBg\":\"#fff2bb\",\"mentionHighlightLink\":\"#2f81b7\",\"newMessageSeparator\":\"#FF8800\",\"onlineIndicator\":\"#7DBE00\",\"sidebarBg\":\"#fafafa\",\"sidebarHeaderBg\":\"#3481B9\",\"sidebarHeaderTextColor\":\"#ffffff\",\"sidebarText\":\"#333333\",\"sidebarTextActiveBorder\":\"#378FD2\",\"sidebarTextActiveColor\":\"#111111\",\"sidebarTextHoverBg\":\"#e6f2fa\",\"sidebarUnreadText\":\"#333333\",\"type\":\"Mattermost\"}`
|
||||||
|
teamTheme2 := `{\"awayIndicator\":\"#DBBD4E\",\"buttonBg\":\"#23A100\",\"buttonColor\":\"#EEEEEE\",\"centerChannelBg\":\"#ffffff\",\"centerChannelColor\":\"#333333\",\"codeTheme\":\"github\",\"image\":\"/static/files/a4a388b38b32678e83823ef1b3e17766.png\",\"linkColor\":\"#2389d7\",\"mentionBg\":\"#2389d7\",\"mentionColor\":\"#ffffff\",\"mentionHighlightBg\":\"#fff2bb\",\"mentionHighlightLink\":\"#2f81b7\",\"newMessageSeparator\":\"#FF8800\",\"onlineIndicator\":\"#7DBE00\",\"sidebarBg\":\"#fafafa\",\"sidebarHeaderBg\":\"#3481B9\",\"sidebarHeaderTextColor\":\"#ffffff\",\"sidebarText\":\"#333333\",\"sidebarTextActiveBorder\":\"#378FD2\",\"sidebarTextActiveColor\":\"#222222\",\"sidebarTextHoverBg\":\"#e6f2fa\",\"sidebarUnreadText\":\"#444444\",\"type\":\"Mattermost\"}`
|
||||||
|
|
||||||
// Run bulk import with a valid 1 of everything.
|
// Run bulk import with a valid 1 of everything.
|
||||||
data1 := `{"type": "version", "version": 1}
|
data1 := `{"type": "version", "version": 1}
|
||||||
{"type": "team", "team": {"type": "O", "display_name": "lskmw2d7a5ao7ppwqh5ljchvr4", "name": "` + teamName + `"}}
|
{"type": "team", "team": {"type": "O", "display_name": "lskmw2d7a5ao7ppwqh5ljchvr4", "name": "` + teamName + `"}}
|
||||||
{"type": "channel", "channel": {"type": "O", "display_name": "xr6m6udffngark2uekvr3hoeny", "team": "` + teamName + `", "name": "` + channelName + `"}}
|
{"type": "channel", "channel": {"type": "O", "display_name": "xr6m6udffngark2uekvr3hoeny", "team": "` + teamName + `", "name": "` + channelName + `"}}
|
||||||
{"type": "user", "user": {"username": "` + username + `", "email": "` + username + `@example.com", "teams": [{"name": "` + teamName + `", "channels": [{"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 + `", "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 + `"}]}]}}
|
||||||
{"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": "` + username + `", "message": "Hello World", "create_at": 123456789012, "attachments":[{"path": "` + testImage + `"}]}}
|
||||||
{"type": "direct_channel", "direct_channel": {"members": ["` + username + `", "` + username2 + `"]}}
|
{"type": "direct_channel", "direct_channel": {"members": ["` + username + `", "` + username2 + `"]}}
|
||||||
|
|||||||
@@ -40,18 +40,21 @@ type ChannelImportData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserImportData struct {
|
type UserImportData struct {
|
||||||
ProfileImage *string `json:"profile_image"`
|
ProfileImage *string `json:"profile_image"`
|
||||||
Username *string `json:"username"`
|
Username *string `json:"username"`
|
||||||
Email *string `json:"email"`
|
Email *string `json:"email"`
|
||||||
AuthService *string `json:"auth_service"`
|
AuthService *string `json:"auth_service"`
|
||||||
AuthData *string `json:"auth_data"`
|
AuthData *string `json:"auth_data"`
|
||||||
Password *string `json:"password"`
|
Password *string `json:"password"`
|
||||||
Nickname *string `json:"nickname"`
|
Nickname *string `json:"nickname"`
|
||||||
FirstName *string `json:"first_name"`
|
FirstName *string `json:"first_name"`
|
||||||
LastName *string `json:"last_name"`
|
LastName *string `json:"last_name"`
|
||||||
Position *string `json:"position"`
|
Position *string `json:"position"`
|
||||||
Roles *string `json:"roles"`
|
Roles *string `json:"roles"`
|
||||||
Locale *string `json:"locale"`
|
Locale *string `json:"locale"`
|
||||||
|
UseMarkdownPreview *string `json:"feature_enabled_markdown_preview"`
|
||||||
|
UseFormatting *string `json:"formatting"`
|
||||||
|
ShowUnreadSection *string `json:"show_unread_section"`
|
||||||
|
|
||||||
Teams *[]UserTeamImportData `json:"teams"`
|
Teams *[]UserTeamImportData `json:"teams"`
|
||||||
|
|
||||||
@@ -82,6 +85,7 @@ type UserNotifyPropsImportData struct {
|
|||||||
type UserTeamImportData struct {
|
type UserTeamImportData struct {
|
||||||
Name *string `json:"name"`
|
Name *string `json:"name"`
|
||||||
Roles *string `json:"roles"`
|
Roles *string `json:"roles"`
|
||||||
|
Theme *string `json:"theme"`
|
||||||
Channels *[]UserChannelImportData `json:"channels"`
|
Channels *[]UserChannelImportData `json:"channels"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
@@ -267,6 +269,18 @@ func validateUserImportData(data *UserImportData) *model.AppError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.UseMarkdownPreview != nil && !model.IsValidTrueOrFalseString(*data.UseMarkdownPreview) {
|
||||||
|
return model.NewAppError("BulkImport", "app.import.validate_user_import_data.advanced_props_feature_markdown_preview.error", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.UseFormatting != nil && !model.IsValidTrueOrFalseString(*data.UseFormatting) {
|
||||||
|
return model.NewAppError("BulkImport", "app.import.validate_user_import_data.advanced_props_formatting.error", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.ShowUnreadSection != nil && !model.IsValidTrueOrFalseString(*data.ShowUnreadSection) {
|
||||||
|
return model.NewAppError("BulkImport", "app.import.validate_user_import_data.advanced_props_show_unread_section.error", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
if data.Teams != nil {
|
if data.Teams != nil {
|
||||||
return validateUserTeamsImportData(data.Teams)
|
return validateUserTeamsImportData(data.Teams)
|
||||||
} else {
|
} else {
|
||||||
@@ -293,6 +307,13 @@ func validateUserTeamsImportData(data *[]UserTeamImportData) *model.AppError {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tdata.Theme != nil && 0 < len(strings.Trim(*tdata.Theme, " \t\r")) {
|
||||||
|
var unused map[string]string
|
||||||
|
if err := json.NewDecoder(strings.NewReader(*tdata.Theme)).Decode(&unused); err != nil {
|
||||||
|
return model.NewAppError("BulkImport", "app.import.validate_user_teams_import_data.invalid_team_theme.error", nil, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -692,6 +692,25 @@ func TestImportValidateUserTeamsImportData(t *testing.T) {
|
|||||||
if err := validateUserTeamsImportData(&data); err != nil {
|
if err := validateUserTeamsImportData(&data); err != nil {
|
||||||
t.Fatal("Should have succeeded with valid roles.")
|
t.Fatal("Should have succeeded with valid roles.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid (with JSON string of theme)
|
||||||
|
data[0].Theme = ptrStr(`{"awayIndicator":"#DBBD4E","buttonBg":"#23A1FF","buttonColor":"#FFFFFF","centerChannelBg":"#ffffff","centerChannelColor":"#333333","codeTheme":"github","image":"/static/files/a4a388b38b32678e83823ef1b3e17766.png","linkColor":"#2389d7","mentionBg":"#2389d7","mentionColor":"#ffffff","mentionHighlightBg":"#fff2bb","mentionHighlightLink":"#2f81b7","newMessageSeparator":"#FF8800","onlineIndicator":"#7DBE00","sidebarBg":"#fafafa","sidebarHeaderBg":"#3481B9","sidebarHeaderTextColor":"#ffffff","sidebarText":"#333333","sidebarTextActiveBorder":"#378FD2","sidebarTextActiveColor":"#111111","sidebarTextHoverBg":"#e6f2fa","sidebarUnreadText":"#333333","type":"Mattermost"}`)
|
||||||
|
if err := validateUserTeamsImportData(&data); err != nil {
|
||||||
|
t.Fatal("Should have succeeded with valid theme.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid (invalid JSON string of theme)
|
||||||
|
data[0].Theme = ptrStr(`This is the invalid string which cannot be marshalled to JSON object :) + {"#DBBD4E","buttonBg", "#23A1FF", buttonColor`)
|
||||||
|
if err := validateUserTeamsImportData(&data); err == nil {
|
||||||
|
t.Fatal("Should have fail with invalid JSON string of theme.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid (valid JSON but invalid theme description)
|
||||||
|
data[0].Theme = ptrStr(`{"somekey": 25, "json_obj1": {"color": "#DBBD4E","buttonBg": "#23A1FF"}}`)
|
||||||
|
if err := validateUserTeamsImportData(&data); err == nil {
|
||||||
|
t.Fatal("Should have fail with valid JSON which contains invalid string of theme description.")
|
||||||
|
}
|
||||||
|
data[0].Theme = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImportValidateUserChannelsImportData(t *testing.T) {
|
func TestImportValidateUserChannelsImportData(t *testing.T) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const (
|
|||||||
PREFERENCE_CATEGORY_ADVANCED_SETTINGS = "advanced_settings"
|
PREFERENCE_CATEGORY_ADVANCED_SETTINGS = "advanced_settings"
|
||||||
PREFERENCE_CATEGORY_FLAGGED_POST = "flagged_post"
|
PREFERENCE_CATEGORY_FLAGGED_POST = "flagged_post"
|
||||||
PREFERENCE_CATEGORY_FAVORITE_CHANNEL = "favorite_channel"
|
PREFERENCE_CATEGORY_FAVORITE_CHANNEL = "favorite_channel"
|
||||||
|
PREFERENCE_CATEGORY_SIDEBAR_SETTINGS = "sidebar_settings"
|
||||||
|
|
||||||
PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings"
|
PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings"
|
||||||
PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews"
|
PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews"
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user