PLT-2992 Added the ability to use different themes for each team (#3411)
* Cleaned up user_settings_theme.jsx and import_theme_modal.jsx * Made ImportThemeModal use a callback to return the theme to the user settings modal instead of saving it directly * Moved user theme from model to preferences * Added serverside API to delete preferences TODO update package with client stuff * Changed constants.jsx so that Preferences and ActionTypes can be imported on their own * Updated ThemeProps migration code to properly rename solarized code themes * Fixed warnings thrown by AppDispatcher * Added clientside UI to support team-specific themes * Removed debugging code from test * Fixed setting a user's theme when they haven't set their theme before
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
8e810bc2eb
Коммит
caabfbcdd5
@@ -26,7 +26,7 @@ func NewSqlPreferenceStore(sqlStore *SqlStore) PreferenceStore {
|
||||
table.ColMap("UserId").SetMaxSize(26)
|
||||
table.ColMap("Category").SetMaxSize(32)
|
||||
table.ColMap("Name").SetMaxSize(32)
|
||||
table.ColMap("Value").SetMaxSize(128)
|
||||
table.ColMap("Value").SetMaxSize(2000)
|
||||
}
|
||||
|
||||
return s
|
||||
@@ -100,6 +100,8 @@ func (s SqlPreferenceStore) Save(preferences *model.Preferences) StoreChannel {
|
||||
func (s SqlPreferenceStore) save(transaction *gorp.Transaction, preference *model.Preference) StoreResult {
|
||||
result := StoreResult{}
|
||||
|
||||
preference.PreUpdate()
|
||||
|
||||
if result.Err = preference.IsValid(); result.Err != nil {
|
||||
return result
|
||||
}
|
||||
@@ -304,3 +306,26 @@ func (s SqlPreferenceStore) IsFeatureEnabled(feature, userId string) StoreChanne
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPreferenceStore) Delete(userId, category, name string) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
if _, err := s.GetMaster().Exec(
|
||||
`DELETE FROM
|
||||
Preferences
|
||||
WHERE
|
||||
UserId = :UserId
|
||||
AND Category = :Category
|
||||
AND Name = :Name`, map[string]interface{}{"UserId": userId, "Category": category, "Name": name}); err != nil {
|
||||
result.Err = model.NewLocAppError("SqlPreferenceStore.Delete", "store.sql_preference.delete.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ func TestPreferenceGetAll(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferenceDelete(t *testing.T) {
|
||||
func TestPreferenceDeleteByUser(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
userId := model.NewId()
|
||||
@@ -367,3 +367,28 @@ func TestDeleteUnusedFeatures(t *testing.T) {
|
||||
t.Fatalf("Found %d features with value 'true', expected to find at least %d features", val, 2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferenceDelete(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
preference := model.Preference{
|
||||
UserId: model.NewId(),
|
||||
Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||
Name: model.NewId(),
|
||||
Value: "value1a",
|
||||
}
|
||||
|
||||
Must(store.Preference().Save(&model.Preferences{preference}))
|
||||
|
||||
if prefs := Must(store.Preference().GetAll(preference.UserId)).(model.Preferences); len([]model.Preference(prefs)) != 1 {
|
||||
t.Fatal("should've returned 1 preference")
|
||||
}
|
||||
|
||||
if result := <-store.Preference().Delete(preference.UserId, preference.Category, preference.Name); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if prefs := Must(store.Preference().GetAll(preference.UserId)).(model.Preferences); len([]model.Preference(prefs)) != 0 {
|
||||
t.Fatal("should've returned no preferences")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
@@ -40,7 +42,6 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
|
||||
table.ColMap("Roles").SetMaxSize(64)
|
||||
table.ColMap("Props").SetMaxSize(4000)
|
||||
table.ColMap("NotifyProps").SetMaxSize(2000)
|
||||
table.ColMap("ThemeProps").SetMaxSize(2000)
|
||||
table.ColMap("Locale").SetMaxSize(5)
|
||||
table.ColMap("MfaSecret").SetMaxSize(128)
|
||||
}
|
||||
@@ -53,27 +54,66 @@ func (us SqlUserStore) UpgradeSchemaIfNeeded() {
|
||||
us.CreateColumnIfNotExists("Users", "Locale", "varchar(5)", "character varying(5)", model.DEFAULT_LOCALE)
|
||||
|
||||
// ADDED for 3.2 REMOVE for 3.6
|
||||
var data []*model.User
|
||||
if _, err := us.GetReplica().Select(&data, "SELECT * FROM Users WHERE ThemeProps LIKE '%solarized%'"); err == nil {
|
||||
for _, user := range data {
|
||||
shouldUpdate := false
|
||||
if user.ThemeProps["codeTheme"] == "solarized_dark" {
|
||||
user.ThemeProps["codeTheme"] = "solarized-dark"
|
||||
shouldUpdate = true
|
||||
} else if user.ThemeProps["codeTheme"] == "solarized_light" {
|
||||
user.ThemeProps["codeTheme"] = "solarized-light"
|
||||
shouldUpdate = true
|
||||
}
|
||||
if us.DoesColumnExist("Users", "ThemeProps") {
|
||||
params := map[string]interface{}{
|
||||
"Category": model.PREFERENCE_CATEGORY_THEME,
|
||||
"Name": "",
|
||||
}
|
||||
|
||||
if shouldUpdate {
|
||||
if result := <-us.Update(user, true); result.Err != nil {
|
||||
return
|
||||
}
|
||||
transaction, err := us.GetMaster().Begin()
|
||||
if err != nil {
|
||||
themeMigrationFailed(err)
|
||||
}
|
||||
|
||||
// increase size of Value column of Preferences table to match the size of the ThemeProps column
|
||||
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||
if _, err := transaction.Exec("ALTER TABLE Preferences ALTER COLUMN Value TYPE varchar(2000)"); err != nil {
|
||||
themeMigrationFailed(err)
|
||||
}
|
||||
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||
if _, err := transaction.Exec("ALTER TABLE Preferences MODIFY Value text"); err != nil {
|
||||
themeMigrationFailed(err)
|
||||
}
|
||||
}
|
||||
|
||||
// copy data across
|
||||
if _, err := transaction.Exec(
|
||||
`INSERT INTO
|
||||
Preferences(UserId, Category, Name, Value)
|
||||
SELECT
|
||||
Id, '`+model.PREFERENCE_CATEGORY_THEME+`', '', ThemeProps
|
||||
FROM
|
||||
Users`, params); err != nil {
|
||||
themeMigrationFailed(err)
|
||||
}
|
||||
|
||||
// delete old data
|
||||
if _, err := transaction.Exec("ALTER TABLE Users DROP COLUMN ThemeProps"); err != nil {
|
||||
themeMigrationFailed(err)
|
||||
}
|
||||
|
||||
if err := transaction.Commit(); err != nil {
|
||||
themeMigrationFailed(err)
|
||||
}
|
||||
|
||||
// rename solarized_* code themes to solarized-* to match client changes in 3.0
|
||||
var data model.Preferences
|
||||
if _, err := us.GetReplica().Select(&data, "SELECT * FROM Preferences WHERE Category = '"+model.PREFERENCE_CATEGORY_THEME+"' AND Value LIKE '%solarized_%'"); err == nil {
|
||||
for i := range data {
|
||||
data[i].Value = strings.Replace(data[i].Value, "solarized_", "solarized-", -1)
|
||||
}
|
||||
|
||||
us.Preference().Save(&data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func themeMigrationFailed(err error) {
|
||||
l4g.Critical(utils.T("store.sql_user.migrate_theme.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
panic(fmt.Sprintf(utils.T("store.sql_user.migrate_theme.critical"), err.Error()))
|
||||
}
|
||||
|
||||
func (us SqlUserStore) CreateIndexesIfNotExists() {
|
||||
us.CreateIndexIfNotExists("idx_users_email", "Users", "Email")
|
||||
}
|
||||
|
||||
@@ -243,6 +243,7 @@ type PreferenceStore interface {
|
||||
Get(userId string, category string, name string) StoreChannel
|
||||
GetCategory(userId string, category string) StoreChannel
|
||||
GetAll(userId string) StoreChannel
|
||||
Delete(userId, category, name string) StoreChannel
|
||||
PermanentDeleteByUser(userId string) StoreChannel
|
||||
IsFeatureEnabled(feature, userId string) StoreChannel
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user