PLT-7123: Add tutorial, flags and favorites to bulk importer. (#7000)
Этот коммит содержится в:
коммит произвёл
Joram Wilander
родитель
f30c0b5d38
Коммит
02abaabf1f
161
app/import.go
161
app/import.go
@@ -16,8 +16,8 @@ import (
|
|||||||
|
|
||||||
l4g "github.com/alecthomas/log4go"
|
l4g "github.com/alecthomas/log4go"
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
"github.com/mattermost/platform/utils"
|
|
||||||
"github.com/mattermost/platform/store"
|
"github.com/mattermost/platform/store"
|
||||||
|
"github.com/mattermost/platform/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Import Data Models
|
// Import Data Models
|
||||||
@@ -66,12 +66,11 @@ type UserImportData struct {
|
|||||||
Teams *[]UserTeamImportData `json:"teams"`
|
Teams *[]UserTeamImportData `json:"teams"`
|
||||||
|
|
||||||
Theme *string `json:"theme"`
|
Theme *string `json:"theme"`
|
||||||
SelectedFont *string `json:"display_font"`
|
|
||||||
UseMilitaryTime *string `json:"military_time"`
|
UseMilitaryTime *string `json:"military_time"`
|
||||||
NameFormat *string `json:"teammate_name_display"`
|
|
||||||
CollapsePreviews *string `json:"link_previews"`
|
CollapsePreviews *string `json:"link_previews"`
|
||||||
MessageDisplay *string `json:"message_display"`
|
MessageDisplay *string `json:"message_display"`
|
||||||
ChannelDisplayMode *string `json:"channel_display_mode"`
|
ChannelDisplayMode *string `json:"channel_display_mode"`
|
||||||
|
TutorialStep *string `json:"tutorial_step"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserTeamImportData struct {
|
type UserTeamImportData struct {
|
||||||
@@ -84,6 +83,7 @@ type UserChannelImportData struct {
|
|||||||
Name *string `json:"name"`
|
Name *string `json:"name"`
|
||||||
Roles *string `json:"roles"`
|
Roles *string `json:"roles"`
|
||||||
NotifyProps *UserChannelNotifyPropsImportData `json:"notify_props"`
|
NotifyProps *UserChannelNotifyPropsImportData `json:"notify_props"`
|
||||||
|
Favorite *bool `json:"favorite"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserChannelNotifyPropsImportData struct {
|
type UserChannelNotifyPropsImportData struct {
|
||||||
@@ -98,10 +98,13 @@ type PostImportData struct {
|
|||||||
|
|
||||||
Message *string `json:"message"`
|
Message *string `json:"message"`
|
||||||
CreateAt *int64 `json:"create_at"`
|
CreateAt *int64 `json:"create_at"`
|
||||||
|
|
||||||
|
FlaggedBy *[]string `json:"flagged_by"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DirectChannelImportData struct {
|
type DirectChannelImportData struct {
|
||||||
Members *[]string `json:"members"`
|
Members *[]string `json:"members"`
|
||||||
|
FavoritedBy *[]string `json:"favorited_by"`
|
||||||
|
|
||||||
Header *string `json:"header"`
|
Header *string `json:"header"`
|
||||||
}
|
}
|
||||||
@@ -112,6 +115,8 @@ type DirectPostImportData struct {
|
|||||||
|
|
||||||
Message *string `json:"message"`
|
Message *string `json:"message"`
|
||||||
CreateAt *int64 `json:"create_at"`
|
CreateAt *int64 `json:"create_at"`
|
||||||
|
|
||||||
|
FlaggedBy *[]string `json:"flagged_by"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LineImportWorkerData struct {
|
type LineImportWorkerData struct {
|
||||||
@@ -599,15 +604,6 @@ func ImportUser(data *UserImportData, dryRun bool) *model.AppError {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.SelectedFont != nil {
|
|
||||||
preferences = append(preferences, model.Preference{
|
|
||||||
UserId: user.Id,
|
|
||||||
Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
|
|
||||||
Name: "selected_font",
|
|
||||||
Value: *data.SelectedFont,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.UseMilitaryTime != nil {
|
if data.UseMilitaryTime != nil {
|
||||||
preferences = append(preferences, model.Preference{
|
preferences = append(preferences, model.Preference{
|
||||||
UserId: user.Id,
|
UserId: user.Id,
|
||||||
@@ -617,15 +613,6 @@ func ImportUser(data *UserImportData, dryRun bool) *model.AppError {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.NameFormat != nil {
|
|
||||||
preferences = append(preferences, model.Preference{
|
|
||||||
UserId: user.Id,
|
|
||||||
Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
|
|
||||||
Name: "name_format",
|
|
||||||
Value: *data.NameFormat,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.CollapsePreviews != nil {
|
if data.CollapsePreviews != nil {
|
||||||
preferences = append(preferences, model.Preference{
|
preferences = append(preferences, model.Preference{
|
||||||
UserId: user.Id,
|
UserId: user.Id,
|
||||||
@@ -653,6 +640,15 @@ func ImportUser(data *UserImportData, dryRun bool) *model.AppError {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.TutorialStep != nil {
|
||||||
|
preferences = append(preferences, model.Preference{
|
||||||
|
UserId: user.Id,
|
||||||
|
Category: model.PREFERENCE_CATEGORY_TUTORIAL_STEPS,
|
||||||
|
Name: user.Id,
|
||||||
|
Value: *data.TutorialStep,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if len(preferences) > 0 {
|
if len(preferences) > 0 {
|
||||||
if result := <-Srv.Store.Preference().Save(&preferences); result.Err != nil {
|
if result := <-Srv.Store.Preference().Save(&preferences); result.Err != nil {
|
||||||
return model.NewAppError("BulkImport", "app.import.import_user.save_preferences.error", nil, "", http.StatusInternalServerError)
|
return model.NewAppError("BulkImport", "app.import.import_user.save_preferences.error", nil, "", http.StatusInternalServerError)
|
||||||
@@ -713,6 +709,8 @@ func ImportUserChannels(user *model.User, team *model.Team, teamMember *model.Te
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var preferences model.Preferences
|
||||||
|
|
||||||
// Loop through all channels.
|
// Loop through all channels.
|
||||||
for _, cdata := range *data {
|
for _, cdata := range *data {
|
||||||
channel, err := GetChannelByName(*cdata.Name, team.Id)
|
channel, err := GetChannelByName(*cdata.Name, team.Id)
|
||||||
@@ -757,6 +755,21 @@ func ImportUserChannels(user *model.User, team *model.Team, teamMember *model.Te
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cdata.Favorite != nil && *cdata.Favorite == true {
|
||||||
|
preferences = append(preferences, model.Preference{
|
||||||
|
UserId: user.Id,
|
||||||
|
Category: model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL,
|
||||||
|
Name: channel.Id,
|
||||||
|
Value: "true",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(preferences) > 0 {
|
||||||
|
if result := <-Srv.Store.Preference().Save(&preferences); result.Err != nil {
|
||||||
|
return model.NewAppError("BulkImport", "app.import.import_user_channels.save_preferences.error", nil, "", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -943,6 +956,33 @@ func ImportPost(data *PostImportData, dryRun bool) *model.AppError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.FlaggedBy != nil {
|
||||||
|
var preferences model.Preferences
|
||||||
|
|
||||||
|
for _, username := range *data.FlaggedBy {
|
||||||
|
var user *model.User
|
||||||
|
|
||||||
|
if result := <-Srv.Store.User().GetByUsername(username); result.Err != nil {
|
||||||
|
return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": username}, "", http.StatusBadRequest)
|
||||||
|
} else {
|
||||||
|
user = result.Data.(*model.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences = append(preferences, model.Preference{
|
||||||
|
UserId: user.Id,
|
||||||
|
Category: model.PREFERENCE_CATEGORY_FLAGGED_POST,
|
||||||
|
Name: post.Id,
|
||||||
|
Value: "true",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(preferences) > 0 {
|
||||||
|
if result := <-Srv.Store.Preference().Save(&preferences); result.Err != nil {
|
||||||
|
return model.NewAppError("BulkImport", "app.import.import_post.save_preferences.error", nil, "", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -985,10 +1025,12 @@ func ImportDirectChannel(data *DirectChannelImportData, dryRun bool) *model.AppE
|
|||||||
}
|
}
|
||||||
|
|
||||||
var userIds []string
|
var userIds []string
|
||||||
|
userMap := make(map[string]string)
|
||||||
for _, username := range *data.Members {
|
for _, username := range *data.Members {
|
||||||
if result := <-Srv.Store.User().GetByUsername(username); result.Err == nil {
|
if result := <-Srv.Store.User().GetByUsername(username); result.Err == nil {
|
||||||
user := result.Data.(*model.User)
|
user := result.Data.(*model.User)
|
||||||
userIds = append(userIds, user.Id)
|
userIds = append(userIds, user.Id)
|
||||||
|
userMap[username] = user.Id
|
||||||
} else {
|
} else {
|
||||||
return model.NewAppError("BulkImport", "app.import.import_direct_channel.member_not_found.error", nil, "", http.StatusBadRequest)
|
return model.NewAppError("BulkImport", "app.import.import_direct_channel.member_not_found.error", nil, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
@@ -1012,20 +1054,32 @@ func ImportDirectChannel(data *DirectChannelImportData, dryRun bool) *model.AppE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var preferences model.Preferences
|
||||||
|
|
||||||
for _, userId := range userIds {
|
for _, userId := range userIds {
|
||||||
preferences := model.Preferences{
|
preferences = append(preferences, model.Preference{
|
||||||
model.Preference{
|
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW,
|
Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||||
Name: channel.Id,
|
Name: channel.Id,
|
||||||
Value: "true",
|
Value: "true",
|
||||||
},
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.FavoritedBy != nil {
|
||||||
|
for _, favoriter := range *data.FavoritedBy {
|
||||||
|
preferences = append(preferences, model.Preference{
|
||||||
|
UserId: userMap[favoriter],
|
||||||
|
Category: model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL,
|
||||||
|
Name: channel.Id,
|
||||||
|
Value: "true",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if result := <-Srv.Store.Preference().Save(&preferences); result.Err != nil {
|
if result := <-Srv.Store.Preference().Save(&preferences); result.Err != nil {
|
||||||
result.Err.StatusCode = http.StatusBadRequest
|
result.Err.StatusCode = http.StatusBadRequest
|
||||||
return result.Err
|
return result.Err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if data.Header != nil {
|
if data.Header != nil {
|
||||||
channel.Header = *data.Header
|
channel.Header = *data.Header
|
||||||
@@ -1054,6 +1108,21 @@ func validateDirectChannelImportData(data *DirectChannelImportData) *model.AppEr
|
|||||||
return model.NewLocAppError("BulkImport", "app.import.validate_direct_channel_import_data.header_length.error", nil, "")
|
return model.NewLocAppError("BulkImport", "app.import.validate_direct_channel_import_data.header_length.error", nil, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.FavoritedBy != nil {
|
||||||
|
for _, favoriter := range *data.FavoritedBy {
|
||||||
|
found := false
|
||||||
|
for _, member := range *data.Members {
|
||||||
|
if favoriter == member {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return model.NewLocAppError("BulkImport", "app.import.validate_direct_channel_import_data.unknown_favoriter.error", map[string]interface{}{"Username": favoriter}, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1138,6 +1207,33 @@ func ImportDirectPost(data *DirectPostImportData, dryRun bool) *model.AppError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.FlaggedBy != nil {
|
||||||
|
var preferences model.Preferences
|
||||||
|
|
||||||
|
for _, username := range *data.FlaggedBy {
|
||||||
|
var user *model.User
|
||||||
|
|
||||||
|
if result := <-Srv.Store.User().GetByUsername(username); result.Err != nil {
|
||||||
|
return model.NewAppError("BulkImport", "app.import.import_direct_post.user_not_found.error", map[string]interface{}{"Username": username}, "", http.StatusBadRequest)
|
||||||
|
} else {
|
||||||
|
user = result.Data.(*model.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences = append(preferences, model.Preference{
|
||||||
|
UserId: user.Id,
|
||||||
|
Category: model.PREFERENCE_CATEGORY_FLAGGED_POST,
|
||||||
|
Name: post.Id,
|
||||||
|
Value: "true",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(preferences) > 0 {
|
||||||
|
if result := <-Srv.Store.Preference().Save(&preferences); result.Err != nil {
|
||||||
|
return model.NewAppError("BulkImport", "app.import.import_direct_post.save_preferences.error", nil, "", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1170,6 +1266,21 @@ func validateDirectPostImportData(data *DirectPostImportData) *model.AppError {
|
|||||||
return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.create_at_zero.error", nil, "", http.StatusBadRequest)
|
return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.create_at_zero.error", nil, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.FlaggedBy != nil {
|
||||||
|
for _, flagger := range *data.FlaggedBy {
|
||||||
|
found := false
|
||||||
|
for _, member := range *data.ChannelMembers {
|
||||||
|
if flagger == member {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return model.NewLocAppError("BulkImport", "app.import.validate_direct_post_import_data.unknown_flagger.error", map[string]interface{}{"Username": flagger}, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,30 @@ func ptrBool(b bool) *bool {
|
|||||||
return &b
|
return &b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkPreference(t *testing.T, userId string, category string, name string, value string) {
|
||||||
|
if res := <-Srv.Store.Preference().GetCategory(userId, category); res.Err != nil {
|
||||||
|
debug.PrintStack()
|
||||||
|
t.Fatalf("Failed to get preferences for user %v with category %v", userId, category)
|
||||||
|
} else {
|
||||||
|
preferences := res.Data.(model.Preferences)
|
||||||
|
found := false
|
||||||
|
for _, preference := range preferences {
|
||||||
|
if preference.Name == name {
|
||||||
|
found = true
|
||||||
|
if preference.Value != value {
|
||||||
|
debug.PrintStack()
|
||||||
|
t.Fatalf("Preference for user %v in category %v with name %v has value %v, expected %v", userId, category, name, preference.Value, value)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
debug.PrintStack()
|
||||||
|
t.Fatalf("Did not find preference for user %v in category %v with name %v", userId, category, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestImportValidateTeamImportData(t *testing.T) {
|
func TestImportValidateTeamImportData(t *testing.T) {
|
||||||
|
|
||||||
// Test with minimum required valid properties.
|
// Test with minimum required valid properties.
|
||||||
@@ -649,6 +673,38 @@ func TestImportValidateDirectChannelImportData(t *testing.T) {
|
|||||||
if err := validateDirectChannelImportData(&data); err == nil {
|
if err := validateDirectChannelImportData(&data); err == nil {
|
||||||
t.Fatal("Validation should have failed due to invalid number of members.")
|
t.Fatal("Validation should have failed due to invalid number of members.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test with invalid FavoritedBy
|
||||||
|
member1 := model.NewId()
|
||||||
|
member2 := model.NewId()
|
||||||
|
data = DirectChannelImportData{
|
||||||
|
Members: &[]string{
|
||||||
|
member1,
|
||||||
|
member2,
|
||||||
|
},
|
||||||
|
FavoritedBy: &[]string{
|
||||||
|
member1,
|
||||||
|
model.NewId(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := validateDirectChannelImportData(&data); err == nil {
|
||||||
|
t.Fatal("Validation should have failed due to non-member favorited.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with valid FavoritedBy
|
||||||
|
data = DirectChannelImportData{
|
||||||
|
Members: &[]string{
|
||||||
|
member1,
|
||||||
|
member2,
|
||||||
|
},
|
||||||
|
FavoritedBy: &[]string{
|
||||||
|
member1,
|
||||||
|
member2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := validateDirectChannelImportData(&data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImportValidateDirectPostImportData(t *testing.T) {
|
func TestImportValidateDirectPostImportData(t *testing.T) {
|
||||||
@@ -799,6 +855,44 @@ func TestImportValidateDirectPostImportData(t *testing.T) {
|
|||||||
if err := validateDirectPostImportData(&data); err == nil {
|
if err := validateDirectPostImportData(&data); err == nil {
|
||||||
t.Fatal("Should have failed due to 0 create-at value.")
|
t.Fatal("Should have failed due to 0 create-at value.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test with invalid FlaggedBy
|
||||||
|
member1 := model.NewId()
|
||||||
|
member2 := model.NewId()
|
||||||
|
data = DirectPostImportData{
|
||||||
|
ChannelMembers: &[]string{
|
||||||
|
member1,
|
||||||
|
member2,
|
||||||
|
},
|
||||||
|
FlaggedBy: &[]string{
|
||||||
|
member1,
|
||||||
|
model.NewId(),
|
||||||
|
},
|
||||||
|
User: ptrStr("username"),
|
||||||
|
Message: ptrStr("message"),
|
||||||
|
CreateAt: ptrInt64(model.GetMillis()),
|
||||||
|
}
|
||||||
|
if err := validateDirectPostImportData(&data); err == nil {
|
||||||
|
t.Fatal("Validation should have failed due to non-member flagged.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with valid FlaggedBy
|
||||||
|
data = DirectPostImportData{
|
||||||
|
ChannelMembers: &[]string{
|
||||||
|
member1,
|
||||||
|
member2,
|
||||||
|
},
|
||||||
|
FlaggedBy: &[]string{
|
||||||
|
member1,
|
||||||
|
member2,
|
||||||
|
},
|
||||||
|
User: ptrStr("username"),
|
||||||
|
Message: ptrStr("message"),
|
||||||
|
CreateAt: ptrInt64(model.GetMillis()),
|
||||||
|
}
|
||||||
|
if err := validateDirectPostImportData(&data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImportImportTeam(t *testing.T) {
|
func TestImportImportTeam(t *testing.T) {
|
||||||
@@ -1544,6 +1638,7 @@ func TestImportImportUser(t *testing.T) {
|
|||||||
Desktop: ptrStr(model.USER_NOTIFY_MENTION),
|
Desktop: ptrStr(model.USER_NOTIFY_MENTION),
|
||||||
MarkUnread: ptrStr(model.USER_NOTIFY_MENTION),
|
MarkUnread: ptrStr(model.USER_NOTIFY_MENTION),
|
||||||
},
|
},
|
||||||
|
Favorite: ptrBool(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1565,6 +1660,8 @@ func TestImportImportUser(t *testing.T) {
|
|||||||
t.Fatalf("Channel member properties not as expected")
|
t.Fatalf("Channel member properties not as expected")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, channel.Id, "true")
|
||||||
|
|
||||||
// No more new member objects.
|
// No more new member objects.
|
||||||
if tmc, err := GetTeamMembers(team.Id, 0, 1000); err != nil {
|
if tmc, err := GetTeamMembers(team.Id, 0, 1000); err != nil {
|
||||||
t.Fatalf("Failed to get Team Member Count")
|
t.Fatalf("Failed to get Team Member Count")
|
||||||
@@ -1584,11 +1681,11 @@ func TestImportImportUser(t *testing.T) {
|
|||||||
Username: &username,
|
Username: &username,
|
||||||
Email: ptrStr(model.NewId() + "@example.com"),
|
Email: ptrStr(model.NewId() + "@example.com"),
|
||||||
Theme: ptrStr(`{"awayIndicator":"#DCBD4E","buttonBg":"#23A2FF","buttonColor":"#FFFFFF","centerChannelBg":"#ffffff","centerChannelColor":"#333333","codeTheme":"github","image":"/static/files/a4a388b38b32678e83823ef1b3e17766.png","linkColor":"#2389d7","mentionBj":"#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"}`),
|
Theme: ptrStr(`{"awayIndicator":"#DCBD4E","buttonBg":"#23A2FF","buttonColor":"#FFFFFF","centerChannelBg":"#ffffff","centerChannelColor":"#333333","codeTheme":"github","image":"/static/files/a4a388b38b32678e83823ef1b3e17766.png","linkColor":"#2389d7","mentionBj":"#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"}`),
|
||||||
SelectedFont: ptrStr("Roboto Slab"),
|
|
||||||
UseMilitaryTime: ptrStr("true"),
|
UseMilitaryTime: ptrStr("true"),
|
||||||
CollapsePreviews: ptrStr("true"),
|
CollapsePreviews: ptrStr("true"),
|
||||||
MessageDisplay: ptrStr("compact"),
|
MessageDisplay: ptrStr("compact"),
|
||||||
ChannelDisplayMode: ptrStr("centered"),
|
ChannelDisplayMode: ptrStr("centered"),
|
||||||
|
TutorialStep: ptrStr("3"),
|
||||||
}
|
}
|
||||||
if err := ImportUser(&data, false); err != nil {
|
if err := ImportUser(&data, false); err != nil {
|
||||||
t.Fatalf("Should have succeeded.")
|
t.Fatalf("Should have succeeded.")
|
||||||
@@ -1600,97 +1697,35 @@ func TestImportImportUser(t *testing.T) {
|
|||||||
t.Fatalf("Failed to get user from database.")
|
t.Fatalf("Failed to get user from database.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if res := <-Srv.Store.Preference().GetCategory(user.Id, model.PREFERENCE_CATEGORY_THEME); res.Err != nil {
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_THEME, "", *data.Theme)
|
||||||
t.Fatalf("Failed to get theme category preferences")
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "use_military_time", *data.UseMilitaryTime)
|
||||||
} else {
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "collapse_previews", *data.CollapsePreviews)
|
||||||
preferences := res.Data.(model.Preferences)
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "message_display", *data.MessageDisplay)
|
||||||
for _, preference := range preferences {
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "channel_display_mode", *data.ChannelDisplayMode)
|
||||||
if preference.Name == "" && preference.Value != *data.Theme {
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, user.Id, *data.TutorialStep)
|
||||||
t.Fatalf("Preference does not match.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if res := <-Srv.Store.Preference().GetCategory(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS); res.Err != nil {
|
|
||||||
t.Fatalf("Failed to get display category preferences")
|
|
||||||
} else {
|
|
||||||
preferences := res.Data.(model.Preferences)
|
|
||||||
for _, preference := range preferences {
|
|
||||||
if preference.Name == "selected_font" && preference.Value != *data.SelectedFont {
|
|
||||||
t.Fatalf("Preference does not match.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if preference.Name == "use_military_time" && preference.Value != *data.UseMilitaryTime {
|
|
||||||
t.Fatalf("Preference does not match.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if preference.Name == "collapse_previews" && preference.Value != *data.CollapsePreviews {
|
|
||||||
t.Fatalf("Preference does not match.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if preference.Name == "message_display" && preference.Value != *data.MessageDisplay {
|
|
||||||
t.Fatalf("Preference does not match.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if preference.Name == "channel_display_mode" && preference.Value != *data.ChannelDisplayMode {
|
|
||||||
t.Fatalf("Preference does not match.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change those preferences.
|
// Change those preferences.
|
||||||
data = UserImportData{
|
data = UserImportData{
|
||||||
Username: &username,
|
Username: &username,
|
||||||
Email: ptrStr(model.NewId() + "@example.com"),
|
Email: ptrStr(model.NewId() + "@example.com"),
|
||||||
Theme: ptrStr(`{"awayIndicator":"#123456","buttonBg":"#23A2FF","buttonColor":"#FFFFFF","centerChannelBg":"#ffffff","centerChannelColor":"#333333","codeTheme":"github","image":"/static/files/a4a388b38b32678e83823ef1b3e17766.png","linkColor":"#2389d7","mentionBj":"#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"}`),
|
Theme: ptrStr(`{"awayIndicator":"#123456","buttonBg":"#23A2FF","buttonColor":"#FFFFFF","centerChannelBg":"#ffffff","centerChannelColor":"#333333","codeTheme":"github","image":"/static/files/a4a388b38b32678e83823ef1b3e17766.png","linkColor":"#2389d7","mentionBj":"#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"}`),
|
||||||
SelectedFont: ptrStr("Lato"),
|
|
||||||
UseMilitaryTime: ptrStr("false"),
|
UseMilitaryTime: ptrStr("false"),
|
||||||
CollapsePreviews: ptrStr("false"),
|
CollapsePreviews: ptrStr("false"),
|
||||||
MessageDisplay: ptrStr("clean"),
|
MessageDisplay: ptrStr("clean"),
|
||||||
ChannelDisplayMode: ptrStr("full"),
|
ChannelDisplayMode: ptrStr("full"),
|
||||||
|
TutorialStep: ptrStr("2"),
|
||||||
}
|
}
|
||||||
if err := ImportUser(&data, false); err != nil {
|
if err := ImportUser(&data, false); err != nil {
|
||||||
t.Fatalf("Should have succeeded.")
|
t.Fatalf("Should have succeeded.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check their values again.
|
// Check their values again.
|
||||||
if res := <-Srv.Store.Preference().GetCategory(user.Id, model.PREFERENCE_CATEGORY_THEME); res.Err != nil {
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_THEME, "", *data.Theme)
|
||||||
t.Fatalf("Failed to get theme category preferences")
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "use_military_time", *data.UseMilitaryTime)
|
||||||
} else {
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "collapse_previews", *data.CollapsePreviews)
|
||||||
preferences := res.Data.(model.Preferences)
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "message_display", *data.MessageDisplay)
|
||||||
for _, preference := range preferences {
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "channel_display_mode", *data.ChannelDisplayMode)
|
||||||
if preference.Name == "" && preference.Value != *data.Theme {
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, user.Id, *data.TutorialStep)
|
||||||
t.Fatalf("Preference does not match.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if res := <-Srv.Store.Preference().GetCategory(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS); res.Err != nil {
|
|
||||||
t.Fatalf("Failed to get display category preferences")
|
|
||||||
} else {
|
|
||||||
preferences := res.Data.(model.Preferences)
|
|
||||||
for _, preference := range preferences {
|
|
||||||
if preference.Name == "selected_font" && preference.Value != *data.SelectedFont {
|
|
||||||
t.Fatalf("Preference does not match.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if preference.Name == "use_military_time" && preference.Value != *data.UseMilitaryTime {
|
|
||||||
t.Fatalf("Preference does not match.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if preference.Name == "collapse_previews" && preference.Value != *data.CollapsePreviews {
|
|
||||||
t.Fatalf("Preference does not match.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if preference.Name == "message_display" && preference.Value != *data.MessageDisplay {
|
|
||||||
t.Fatalf("Preference does not match.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if preference.Name == "channel_display_mode" && preference.Value != *data.ChannelDisplayMode {
|
|
||||||
t.Fatalf("Preference does not match.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AssertAllPostsCount(t *testing.T, initialCount int64, change int64, teamName string) {
|
func AssertAllPostsCount(t *testing.T, initialCount int64, change int64, teamName string) {
|
||||||
@@ -1937,6 +1972,51 @@ func TestImportImportPost(t *testing.T) {
|
|||||||
t.Fatalf("Hashtags not as expected: %s", post.Hashtags)
|
t.Fatalf("Hashtags not as expected: %s", post.Hashtags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Post with flags.
|
||||||
|
username2 := model.NewId()
|
||||||
|
ImportUser(&UserImportData{
|
||||||
|
Username: &username2,
|
||||||
|
Email: ptrStr(model.NewId() + "@example.com"),
|
||||||
|
}, false)
|
||||||
|
user2, err := GetUserByUsername(username2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get user from database.")
|
||||||
|
}
|
||||||
|
|
||||||
|
flagsTime := hashtagTime + 1
|
||||||
|
data = &PostImportData{
|
||||||
|
Team: &teamName,
|
||||||
|
Channel: &channelName,
|
||||||
|
User: &username,
|
||||||
|
Message: ptrStr("Message with Favorites"),
|
||||||
|
CreateAt: &flagsTime,
|
||||||
|
FlaggedBy: &[]string{
|
||||||
|
username,
|
||||||
|
username2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := ImportPost(data, false); err != nil {
|
||||||
|
t.Fatalf("Expected success.")
|
||||||
|
}
|
||||||
|
AssertAllPostsCount(t, initialPostCount, 5, team.Id)
|
||||||
|
|
||||||
|
// Check the post values.
|
||||||
|
if result := <-Srv.Store.Post().GetPostsCreatedAt(channel.Id, flagsTime); result.Err != nil {
|
||||||
|
t.Fatal(result.Err.Error())
|
||||||
|
} else {
|
||||||
|
posts := result.Data.([]*model.Post)
|
||||||
|
if len(posts) != 1 {
|
||||||
|
t.Fatal("Unexpected number of posts found.")
|
||||||
|
}
|
||||||
|
post := posts[0]
|
||||||
|
if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id {
|
||||||
|
t.Fatal("Post properties not as expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPreference(t, user.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true")
|
||||||
|
checkPreference(t, user2.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImportImportDirectChannel(t *testing.T) {
|
func TestImportImportDirectChannel(t *testing.T) {
|
||||||
@@ -2114,6 +2194,26 @@ func TestImportImportDirectChannel(t *testing.T) {
|
|||||||
t.Fatal("Channel header has not been updated successfully.")
|
t.Fatal("Channel header has not been updated successfully.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Import a channel with some favorites.
|
||||||
|
data.Members = &[]string{
|
||||||
|
th.BasicUser.Username,
|
||||||
|
th.BasicUser2.Username,
|
||||||
|
}
|
||||||
|
data.FavoritedBy = &[]string{
|
||||||
|
th.BasicUser.Username,
|
||||||
|
th.BasicUser2.Username,
|
||||||
|
}
|
||||||
|
if err := ImportDirectChannel(&data, false); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if channel, err := createDirectChannel(th.BasicUser.Id, th.BasicUser2.Id); err == nil || err.Id != store.CHANNEL_EXISTS_ERROR {
|
||||||
|
t.Fatal("Should have got store.CHANNEL_EXISTS_ERROR")
|
||||||
|
} else {
|
||||||
|
checkPreference(t, th.BasicUser.Id, model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, channel.Id, "true")
|
||||||
|
checkPreference(t, th.BasicUser2.Id, model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, channel.Id, "true")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func AssertChannelCount(t *testing.T, channelType string, expectedCount int64) {
|
func AssertChannelCount(t *testing.T, channelType string, expectedCount int64) {
|
||||||
@@ -2290,6 +2390,38 @@ func TestImportImportDirectPost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test with some flags.
|
||||||
|
data = &DirectPostImportData{
|
||||||
|
ChannelMembers: &[]string{
|
||||||
|
th.BasicUser.Username,
|
||||||
|
th.BasicUser2.Username,
|
||||||
|
},
|
||||||
|
FlaggedBy: &[]string{
|
||||||
|
th.BasicUser.Username,
|
||||||
|
th.BasicUser2.Username,
|
||||||
|
},
|
||||||
|
User: ptrStr(th.BasicUser.Username),
|
||||||
|
Message: ptrStr("Message"),
|
||||||
|
CreateAt: ptrInt64(model.GetMillis()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ImportDirectPost(data, false); err != nil {
|
||||||
|
t.Fatalf("Expected success: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the post values.
|
||||||
|
if result := <-Srv.Store.Post().GetPostsCreatedAt(directChannel.Id, *data.CreateAt); result.Err != nil {
|
||||||
|
t.Fatal(result.Err.Error())
|
||||||
|
} else {
|
||||||
|
posts := result.Data.([]*model.Post)
|
||||||
|
if len(posts) != 1 {
|
||||||
|
t.Fatal("Unexpected number of posts found.")
|
||||||
|
}
|
||||||
|
post := posts[0]
|
||||||
|
checkPreference(t, th.BasicUser.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true")
|
||||||
|
checkPreference(t, th.BasicUser2.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true")
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------ Group Channel -------------------------
|
// ------------------ Group Channel -------------------------
|
||||||
|
|
||||||
// Create the GROUP channel.
|
// Create the GROUP channel.
|
||||||
@@ -2460,6 +2592,39 @@ func TestImportImportDirectPost(t *testing.T) {
|
|||||||
t.Fatalf("Hashtags not as expected: %s", post.Hashtags)
|
t.Fatalf("Hashtags not as expected: %s", post.Hashtags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test with some flags.
|
||||||
|
data = &DirectPostImportData{
|
||||||
|
ChannelMembers: &[]string{
|
||||||
|
th.BasicUser.Username,
|
||||||
|
th.BasicUser2.Username,
|
||||||
|
user3.Username,
|
||||||
|
},
|
||||||
|
FlaggedBy: &[]string{
|
||||||
|
th.BasicUser.Username,
|
||||||
|
th.BasicUser2.Username,
|
||||||
|
},
|
||||||
|
User: ptrStr(th.BasicUser.Username),
|
||||||
|
Message: ptrStr("Message"),
|
||||||
|
CreateAt: ptrInt64(model.GetMillis()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ImportDirectPost(data, false); err != nil {
|
||||||
|
t.Fatalf("Expected success: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the post values.
|
||||||
|
if result := <-Srv.Store.Post().GetPostsCreatedAt(groupChannel.Id, *data.CreateAt); result.Err != nil {
|
||||||
|
t.Fatal(result.Err.Error())
|
||||||
|
} else {
|
||||||
|
posts := result.Data.([]*model.Post)
|
||||||
|
if len(posts) != 1 {
|
||||||
|
t.Fatal("Unexpected number of posts found.")
|
||||||
|
}
|
||||||
|
post := posts[0]
|
||||||
|
checkPreference(t, th.BasicUser.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true")
|
||||||
|
checkPreference(t, th.BasicUser2.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImportImportLine(t *testing.T) {
|
func TestImportImportLine(t *testing.T) {
|
||||||
|
|||||||
20
i18n/en.json
20
i18n/en.json
@@ -3075,6 +3075,22 @@
|
|||||||
"id": "app.import.import_direct_post.user_not_found.error",
|
"id": "app.import.import_direct_post.user_not_found.error",
|
||||||
"translation": "Post user does not exist"
|
"translation": "Post user does not exist"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "app.import.import_direct_post.save_preferences.error",
|
||||||
|
"translation": "Error importing direct post. Failed to save preferences."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.import.import_user_channels.save_preferences.error",
|
||||||
|
"translation": "Error importing user channel memberships. Failed to save preferences."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.import.validate_direct_channel_import_data.unknown_favoriter.error",
|
||||||
|
"translation": "Direct channel can only be favorited by members. \"{{.Username}}\" is not a member."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.import.validate_direct_post_import_data.unknown_flagger.error",
|
||||||
|
"translation": "Direct post can only be flagged by members of the channel it is in. \"{{.Username}}\" is not a member."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "app.import.import_line.null_channel.error",
|
"id": "app.import.import_line.null_channel.error",
|
||||||
"translation": "Import data line has type \"channel\" but the channel object is null."
|
"translation": "Import data line has type \"channel\" but the channel object is null."
|
||||||
@@ -3115,6 +3131,10 @@
|
|||||||
"id": "app.import.import_post.user_not_found.error",
|
"id": "app.import.import_post.user_not_found.error",
|
||||||
"translation": "Error importing post. User with username \"{{.Username}}\" could not be found."
|
"translation": "Error importing post. User with username \"{{.Username}}\" could not be found."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "app.import.import_post.save_preferences.error",
|
||||||
|
"translation": "Error importing post. Failed to save preferences."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "app.import.validate_channel_import_data.create_at_zero.error",
|
"id": "app.import.validate_channel_import_data.create_at_zero.error",
|
||||||
"translation": "Channel create_at must not be zero if provided."
|
"translation": "Channel create_at must not be zero if provided."
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const (
|
|||||||
PREFERENCE_CATEGORY_TUTORIAL_STEPS = "tutorial_step"
|
PREFERENCE_CATEGORY_TUTORIAL_STEPS = "tutorial_step"
|
||||||
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_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