[MM-59519] preserve DM/GM unread/read state over export and import (#27707)

Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2024-08-26 17:04:48 +02:00
коммит произвёл GitHub
родитель c2c37cea20
Коммит 4b3de1861f
14 изменённых файлов: 1138 добавлений и 556 удалений

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

@@ -815,7 +815,7 @@ func (a *App) exportAllDirectChannels(ctx request.CTX, job *model.Job, writer io
afterId = channel.Id
// Skip if there are no active members in the channel
if len(*channel.Members) == 0 {
if len(channel.Members) == 0 {
continue
}
@@ -829,7 +829,12 @@ func (a *App) exportAllDirectChannels(ctx request.CTX, job *model.Job, writer io
return err
}
channelLine := ImportLineFromDirectChannel(channel, favoritedBy)
shownBy, err := a.buildShownByList(channel)
if err != nil {
return err
}
channelLine := ImportLineFromDirectChannel(channel, favoritedBy, shownBy)
if err := a.exportWriteLine(writer, channelLine); err != nil {
return err
}
@@ -862,6 +867,55 @@ func (a *App) buildFavoritedByList(channelID string) ([]string, *model.AppError)
return userIDs, nil
}
func (a *App) buildShownByList(channel *model.DirectChannelForExport) ([]string, *model.AppError) {
shownBy := make([]string, 0)
switch channel.Type {
case model.ChannelTypeGroup:
for _, member := range channel.Members {
prefs, err := a.Srv().Store().Preference().GetCategory(member.UserId, model.PreferenceCategoryGroupChannelShow)
if err != nil {
return nil, model.NewAppError("buildShownByList", "app.preference.get_category.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
for i := range prefs {
if prefs[i].Name == channel.Id && prefs[i].Value == "true" {
user, err := a.Srv().Store().User().Get(context.Background(), member.UserId)
if err != nil {
return nil, model.NewAppError("buildShownByList", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
shownBy = append(shownBy, user.Username)
}
}
}
case model.ChannelTypeDirect:
for i, member := range channel.Members {
otherMember := member // in case it's a channel with self
if len(channel.Members) == 2 {
// since the are only two members, the other member is should be the remainder of i+1/2
otherMember = channel.Members[(i+1)%2]
}
prefs, err := a.Srv().Store().Preference().GetCategoryAndName(model.PreferenceCategoryDirectChannelShow, otherMember.UserId)
if err != nil {
return nil, model.NewAppError("buildShownByList", "app.preference.get_category.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
for _, pref := range prefs {
if pref.Value == "true" && pref.UserId == member.UserId {
user, err := a.Srv().Store().User().Get(context.Background(), member.UserId)
if err != nil {
return nil, model.NewAppError("buildShownByList", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
shownBy = append(shownBy, user.Username)
}
}
}
}
return shownBy, nil
}
func (a *App) exportAllDirectPosts(ctx request.CTX, job *model.Job, writer io.Writer, withAttachments, includeArchivedChannels bool) ([]imports.AttachmentImportData, *model.AppError) {
var attachments []imports.AttachmentImportData
afterId := strings.Repeat("0", 26)

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

@@ -40,17 +40,17 @@ func ImportLineFromChannel(channel *model.ChannelForExport) *imports.LineImportD
}
}
func ImportLineFromDirectChannel(channel *model.DirectChannelForExport, favoritedBy []string) *imports.LineImportData {
channelMembers := *channel.Members
func ImportLineFromDirectChannel(channel *model.DirectChannelForExport, favoritedBy, shownBy []string) *imports.LineImportData {
channelMembers := channel.Members
if len(channelMembers) == 1 {
channelMembers = []string{channelMembers[0], channelMembers[0]}
channelMembers = []*model.ChannelMemberForExport{channelMembers[0], channelMembers[0]}
}
line := &imports.LineImportData{
Type: "direct_channel",
DirectChannel: &imports.DirectChannelImportData{
Header: &channel.Header,
Members: &channelMembers,
Header: &channel.Header,
Participants: importDirectChannelMembersFromChannelMembers(channelMembers),
},
}
@@ -58,9 +58,82 @@ func ImportLineFromDirectChannel(channel *model.DirectChannelForExport, favorite
line.DirectChannel.FavoritedBy = &favoritedBy
}
if len(shownBy) != 0 {
line.DirectChannel.ShownBy = &shownBy
}
return line
}
func importDirectChannelMembersFromChannelMembers(members []*model.ChannelMemberForExport) []*imports.DirectChannelMemberImportData {
importedMembers := make([]*imports.DirectChannelMemberImportData, len(members))
for i, member := range members {
props := member.NotifyProps
notifyProps := imports.UserChannelNotifyPropsImportData{}
desktop, exist := props[model.DesktopNotifyProp]
if exist {
notifyProps.Desktop = &desktop
}
mobile, exist := props[model.PushNotifyProp]
if exist {
notifyProps.Mobile = &mobile
}
email, exist := props[model.EmailNotifyProp]
if exist {
notifyProps.Email = &email
}
ignoreMentions, exist := props[model.IgnoreChannelMentionsNotifyProp]
if exist {
notifyProps.IgnoreChannelMentions = &ignoreMentions
}
channelAutoFallow, exist := props[model.ChannelAutoFollowThreads]
if exist {
notifyProps.ChannelAutoFollowThreads = &channelAutoFallow
}
markUnread, exist := props[model.MarkUnreadNotifyProp]
if exist {
notifyProps.MarkUnread = &markUnread
}
dcm := &imports.DirectChannelMemberImportData{
Username: &member.Username,
NotifyProps: &notifyProps,
}
if member.SchemeUser {
dcm.SchemeUser = &member.SchemeUser
}
if member.SchemeAdmin {
dcm.SchemeAdmin = &member.SchemeAdmin
}
if member.SchemeGuest {
dcm.SchemeGuest = &member.SchemeGuest
}
if member.LastViewedAt != 0 {
dcm.LastViewedAt = &member.LastViewedAt
}
if member.MentionCount != 0 {
dcm.MentionCount = &member.MentionCount
}
if member.MentionCountRoot != 0 {
dcm.MentionCountRoot = &member.MentionCountRoot
}
if member.MsgCount != 0 {
dcm.MsgCount = &member.MsgCount
}
if member.MsgCountRoot != 0 {
dcm.MsgCountRoot = &member.MsgCountRoot
}
if member.UrgentMentionCount != 0 {
dcm.UrgentMentionCount = &member.UrgentMentionCount
}
importedMembers[i] = dcm
}
return importedMembers
}
func ImportLineFromUser(user *model.User, exportedPrefs map[string]*string) *imports.LineImportData {
// Bulk Importer doesn't accept "empty string" for AuthService.
var authService *string

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

@@ -269,8 +269,9 @@ func TestExportDMChannel(t *testing.T) {
// Ensure the Members of the imported DM channel is the same was from the exported
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
require.NoError(t, nErr)
require.Equal(t, 1, len(channels))
assert.ElementsMatch(t, []string{th1.BasicUser.Username, th1.BasicUser2.Username}, *channels[0].Members)
require.Len(t, channels, 1)
require.Len(t, channels[0].Members, 2)
assert.ElementsMatch(t, []string{th1.BasicUser.Username, th1.BasicUser2.Username}, []string{channels[0].Members[0].Username, channels[0].Members[1].Username})
// Ensure the favorited channel was retained
fav, nErr := th2.App.Srv().Store().Preference().Get(th2.BasicUser2.Id, model.PreferenceCategoryFavoriteChannel, channels[0].Id)
@@ -340,8 +341,8 @@ func TestExportDMChannelToSelf(t *testing.T) {
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
require.NoError(t, nErr)
assert.Equal(t, 1, len(channels))
assert.Equal(t, 1, len((*channels[0].Members)))
assert.Equal(t, th1.BasicUser.Username, (*channels[0].Members)[0])
assert.Equal(t, 1, len((channels[0].Members)))
assert.Equal(t, th1.BasicUser.Username, channels[0].Members[0].Username)
}
func TestExportGMChannel(t *testing.T) {
@@ -416,8 +417,8 @@ func TestExportGMandDMChannels(t *testing.T) {
// Adding some determinism so its possible to assert on slice index
sort.Slice(channels, func(i, j int) bool { return channels[i].Type > channels[j].Type })
assert.Equal(t, 2, len(channels))
assert.ElementsMatch(t, []string{th1.BasicUser.Username, user1.Username, user2.Username}, *channels[0].Members)
assert.ElementsMatch(t, []string{th1.BasicUser.Username, th1.BasicUser2.Username}, *channels[1].Members)
assert.ElementsMatch(t, []string{th1.BasicUser.Username, user1.Username, user2.Username}, []string{channels[0].Members[0].Username, channels[0].Members[1].Username, channels[0].Members[2].Username})
assert.ElementsMatch(t, []string{th1.BasicUser.Username, th1.BasicUser2.Username}, []string{channels[1].Members[0].Username, channels[1].Members[1].Username})
}
func TestExportDMandGMPost(t *testing.T) {

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

@@ -1758,40 +1758,187 @@ func (a *App) importDirectChannel(rctx request.CTX, data *imports.DirectChannelI
return nil
}
var members []string
if data.Participants != nil {
members = make([]string, len(data.Participants))
for i, member := range data.Participants {
members[i] = *member.Username
}
} else if data.Members != nil {
members = make([]string, len(*data.Members))
copy(members, *data.Members)
} else {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.no_members.error", nil, "", http.StatusBadRequest)
}
var userIDs []string
userMap, err := a.getUsersByUsernames(*data.Members)
userMap, err := a.getUsersByUsernames(members)
if err != nil {
return err
}
for _, user := range *data.Members {
for _, user := range members {
userIDs = append(userIDs, userMap[strings.ToLower(user)].Id)
}
var channel *model.Channel
if len(userIDs) == 2 {
ch, err := a.createDirectChannel(rctx, userIDs[0], userIDs[1])
if err != nil && err.Id != store.ChannelExistsError {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_direct_channel.error", nil, "", http.StatusBadRequest).Wrap(err)
ch, err2 := a.createDirectChannel(rctx, userIDs[0], userIDs[1])
if err2 != nil && err2.Id != store.ChannelExistsError {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_direct_channel.error", nil, "", http.StatusBadRequest).Wrap(err2)
}
channel = ch
} else {
ch, err := a.createGroupChannel(rctx, userIDs)
if err != nil && err.Id != store.ChannelExistsError {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_group_channel.error", nil, "", http.StatusBadRequest).Wrap(err)
ch, err2 := a.createGroupChannel(rctx, userIDs)
if err2 != nil && err2.Id != store.ChannelExistsError {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_group_channel.error", nil, "", http.StatusBadRequest).Wrap(err2)
}
channel = ch
}
totalMembers, err := a.GetChannelMemberCount(rctx, channel.Id)
if err != nil {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.get_channel_members.error", nil, "", http.StatusBadRequest).Wrap(err)
}
var ems = make([]model.ChannelMember, 0, totalMembers)
var page int
for int64(len(ems)) < totalMembers {
res, err := a.GetChannelMembersPage(rctx, channel.Id, page, 100)
if err != nil {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.get_channel_members.error", nil, "", http.StatusBadRequest).Wrap(err)
}
ems = append(ems, res...)
page++
}
existingMembers := make(map[string]model.ChannelMember)
for _, member := range ems {
existingMembers[member.UserId] = member
}
newChannelMembers := make([]*model.ChannelMember, 0)
for _, member := range data.Participants {
m := &model.ChannelMember{
NotifyProps: model.GetDefaultChannelNotifyProps(),
}
if member.LastViewedAt != nil {
m.LastViewedAt = *member.LastViewedAt
}
if member.MsgCount != nil {
m.MsgCount = *member.MsgCount
}
if member.MentionCount != nil {
m.MentionCount = *member.MentionCount
}
if member.MentionCountRoot != nil {
m.MentionCountRoot = *member.MentionCountRoot
}
if member.UrgentMentionCount != nil {
m.UrgentMentionCount = *member.UrgentMentionCount
}
if member.MsgCountRoot != nil {
m.MsgCountRoot = *member.MsgCountRoot
}
if member.SchemeUser != nil {
m.SchemeUser = *member.SchemeUser
}
if member.SchemeAdmin != nil {
m.SchemeAdmin = *member.SchemeAdmin
}
if member.SchemeGuest != nil {
m.SchemeGuest = *member.SchemeGuest
}
if member.NotifyProps != nil {
if member.NotifyProps.Desktop != nil {
if value, ok := m.NotifyProps[model.DesktopNotifyProp]; !ok || value != *member.NotifyProps.Desktop {
m.NotifyProps[model.DesktopNotifyProp] = *member.NotifyProps.Desktop
}
}
if member.NotifyProps.MarkUnread != nil {
if value, ok := m.NotifyProps[model.DesktopSoundNotifyProp]; !ok || value != *member.NotifyProps.MarkUnread {
m.NotifyProps[model.MarkUnreadNotifyProp] = *member.NotifyProps.MarkUnread
}
}
if member.NotifyProps.Mobile != nil {
if value, ok := m.NotifyProps[model.PushNotifyProp]; !ok || value != *member.NotifyProps.Mobile {
m.NotifyProps[model.PushNotifyProp] = *member.NotifyProps.Mobile
}
}
if member.NotifyProps.Email != nil {
if value, ok := m.NotifyProps[model.EmailNotifyProp]; !ok || value != *member.NotifyProps.Email {
m.NotifyProps[model.EmailNotifyProp] = *member.NotifyProps.Email
}
}
if member.NotifyProps.IgnoreChannelMentions != nil {
if value, ok := m.NotifyProps[model.IgnoreChannelMentionsNotifyProp]; !ok || value != *member.NotifyProps.IgnoreChannelMentions {
m.NotifyProps[model.IgnoreChannelMentionsNotifyProp] = *member.NotifyProps.IgnoreChannelMentions
}
}
if member.NotifyProps.ChannelAutoFollowThreads != nil {
if value, ok := m.NotifyProps[model.ChannelAutoFollowThreads]; !ok || value != *member.NotifyProps.ChannelAutoFollowThreads {
m.NotifyProps[model.ChannelAutoFollowThreads] = *member.NotifyProps.ChannelAutoFollowThreads
}
}
}
u := userMap[strings.ToLower(*member.Username)]
if existing, ok := existingMembers[u.Id]; ok {
// Decide which membership is newer. We have LastViewedAt in the import data, which should
// give us a good idea of which membership is newer.
if existing.LastViewedAt > m.LastViewedAt {
continue
}
}
m.UserId = u.Id
m.ChannelId = channel.Id
newChannelMembers = append(newChannelMembers, m)
}
// the channel memberships are already created in the channel creation
// we always going to update the channel memberships
if len(newChannelMembers) > 0 {
_, nErr := a.Srv().Store().Channel().UpdateMultipleMembers(newChannelMembers)
if nErr != nil {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_group_channel.error", nil, "", http.StatusBadRequest).Wrap(nErr)
}
}
var preferences model.Preferences
for _, userID := range userIDs {
preferences = append(preferences, model.Preference{
UserId: userID,
Category: model.PreferenceCategoryDirectChannelShow,
Name: channel.Id,
Value: "true",
})
if data.ShownBy != nil {
for _, username := range *data.ShownBy {
switch channel.Type {
case model.ChannelTypeDirect:
otherUserId := userMap[strings.ToLower(username)].Id
for uname, user := range userMap {
if uname != username {
otherUserId = user.Id
break
}
}
preferences = append(preferences, model.Preference{
UserId: userMap[strings.ToLower(username)].Id,
Category: model.PreferenceCategoryDirectChannelShow,
Name: otherUserId,
Value: "true",
})
case model.ChannelTypeGroup:
preferences = append(preferences, model.Preference{
UserId: userMap[strings.ToLower(username)].Id,
Category: model.PreferenceCategoryGroupChannelShow,
Name: channel.Id,
Value: "true",
})
}
}
}
if data.FavoritedBy != nil {
@@ -1805,14 +1952,16 @@ func (a *App) importDirectChannel(rctx request.CTX, data *imports.DirectChannelI
}
}
if err := a.Srv().Store().Preference().Save(preferences); err != nil {
var appErr *model.AppError
switch {
case errors.As(err, &appErr):
appErr.StatusCode = http.StatusBadRequest
return appErr
default:
return model.NewAppError("importDirectChannel", "app.preference.save.updating.app_error", nil, "", http.StatusBadRequest).Wrap(err)
if len(preferences) > 0 {
if err := a.Srv().Store().Preference().Save(preferences); err != nil {
var appErr *model.AppError
switch {
case errors.As(err, &appErr):
appErr.StatusCode = http.StatusBadRequest
return appErr
default:
return model.NewAppError("importDirectChannel", "app.preference.save.updating.app_error", nil, "", http.StatusBadRequest).Wrap(err)
}
}
}

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

@@ -3161,6 +3161,7 @@ func TestImportImportPost(t *testing.T) {
func TestImportImportDirectChannel(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
user3 := th.CreateUser()
// Check how many channels are in the database.
directChannelCount, err := th.App.Srv().Store().Channel().AnalyticsTypeCount("", model.ChannelTypeDirect)
@@ -3169,161 +3170,363 @@ func TestImportImportDirectChannel(t *testing.T) {
groupChannelCount, err := th.App.Srv().Store().Channel().AnalyticsTypeCount("", model.ChannelTypeGroup)
require.NoError(t, err, "Failed to get group channel count.")
// Do an invalid channel in dry-run mode.
data := imports.DirectChannelImportData{
Members: &[]string{
model.NewId(),
},
Header: ptrStr("Channel Header"),
// We need to generate the dataset twice to test the same data with different formats.
generateDataset := func(data imports.DirectChannelImportData) map[string]imports.DirectChannelImportData {
members := make([]string, len(data.Participants))
for i, member := range data.Participants {
members[i] = *member.Username
}
return map[string]imports.DirectChannelImportData{
"Participants": data,
"Members": {
Members: &members,
},
}
}
err = th.App.importDirectChannel(th.Context, &data, true)
require.Error(t, err)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
t.Run("Invalid channel in dry-run mode", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(model.NewId()),
},
},
Header: ptrStr("Channel Header"),
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
err = th.App.importDirectChannel(th.Context, &data, true)
require.Error(t, err)
// Do a valid DIRECT channel with a nonexistent member in dry-run mode.
data.Members = &[]string{
model.NewId(),
model.NewId(),
}
appErr := th.App.importDirectChannel(th.Context, &data, true)
require.Nil(t, appErr)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
})
}
})
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
t.Run("Valid DIRECT channel with a nonexistent member in dry-run mode", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(model.NewId()),
},
{
Username: model.NewString(model.NewId()),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, true)
require.Nil(t, appErr)
// Do a valid GROUP channel with a nonexistent member in dry-run mode.
data.Members = &[]string{
model.NewId(),
model.NewId(),
model.NewId(),
}
appErr = th.App.importDirectChannel(th.Context, &data, true)
require.Nil(t, appErr)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
})
}
})
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
t.Run("Valid GROUP channel with a nonexistent member in dry-run mode", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(model.NewId()),
},
{
Username: model.NewString(model.NewId()),
},
{
Username: model.NewString(model.NewId()),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, true)
require.Nil(t, appErr)
// Do an invalid channel in apply mode.
data.Members = &[]string{
model.NewId(),
}
err = th.App.importDirectChannel(th.Context, &data, false)
require.Error(t, err)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
})
}
})
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
t.Run("Invalid channel in apply mode", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(model.NewId()),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
err = th.App.importDirectChannel(th.Context, &data, false)
require.Error(t, err)
// Do a valid DIRECT channel.
data.Members = &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
}
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
})
}
})
// Check that one more DIRECT channel is in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
t.Run("Valid DIRECT channel ", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Do the same DIRECT channel again.
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Check that one more DIRECT channel is in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
// Do the same DIRECT channel again.
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Update the channel's HEADER
data.Header = ptrStr("New Channel Header 2")
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
// Update the channel's HEADER
data.Header = ptrStr("New Channel Header 2")
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Get the channel to check that the header was updated.
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
require.Equal(t, channel.Header, *data.Header)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
// Do a GROUP channel with an extra invalid member.
user3 := th.CreateUser()
data.Members = &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
model.NewId(),
}
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.NotNil(t, appErr)
// Get the channel to check that the header was updated.
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
require.Equal(t, channel.Header, *data.Header)
})
}
})
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
t.Run("GROUP channel with an extra invalid member", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
{
Username: model.NewString(user3.Username),
},
{
Username: model.NewString(model.NewId()),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.NotNil(t, appErr)
// Do a valid GROUP channel.
data.Members = &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
}
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
})
}
})
// Check that one more GROUP channel is in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1)
t.Run("Valid GROUP channel", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
{
Username: model.NewString(user3.Username),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Do the same DIRECT channel again.
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Check that one more GROUP channel is in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1)
// Do the same DIRECT channel again.
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Update the channel's HEADER
data.Header = ptrStr("New Channel Header 3")
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1)
// Update the channel's HEADER
data.Header = ptrStr("New Channel Header 3")
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Get the channel to check that the header was updated.
userIDs := []string{
th.BasicUser.Id,
th.BasicUser2.Id,
user3.Id,
}
channel, appErr = th.App.createGroupChannel(th.Context, userIDs)
require.Equal(t, appErr.Id, store.ChannelExistsError)
require.Equal(t, channel.Header, *data.Header)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1)
// Import a channel with some favorites.
data.Members = &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
}
data.FavoritedBy = &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
}
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Get the channel to check that the header was updated.
userIDs := []string{
th.BasicUser.Id,
th.BasicUser2.Id,
user3.Id,
}
channel, appErr := th.App.createGroupChannel(th.Context, userIDs)
require.Equal(t, appErr.Id, store.ChannelExistsError)
require.Equal(t, channel.Header, *data.Header)
})
}
})
channel, appErr = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
checkPreference(t, th.App, th.BasicUser.Id, model.PreferenceCategoryFavoriteChannel, channel.Id, "true")
checkPreference(t, th.App, th.BasicUser2.Id, model.PreferenceCategoryFavoriteChannel, channel.Id, "true")
t.Run("Import a channel with some favorites", func(t *testing.T) {
dataset := generateDataset(imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
data.FavoritedBy = &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
}
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
checkPreference(t, th.App, th.BasicUser.Id, model.PreferenceCategoryFavoriteChannel, channel.Id, "true")
checkPreference(t, th.App, th.BasicUser2.Id, model.PreferenceCategoryFavoriteChannel, channel.Id, "true")
})
}
})
t.Run("Import a DM channel and user last view should be imported", func(t *testing.T) {
lastView := model.GetMillis()
data := imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
LastViewedAt: ptrInt64(lastView),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
},
}
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
members, appErr := th.App.GetChannelMembersPage(th.Context, channel.Id, 0, 100)
require.Nil(t, appErr)
require.Len(t, members, 2)
for _, member := range members {
if member.UserId == th.BasicUser.Id {
require.Equal(t, member.LastViewedAt, lastView)
}
}
})
t.Run("Import a DM channel and preserve if the channel was shown to users", func(t *testing.T) {
data := imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
},
ShownBy: &[]string{
th.BasicUser.Username,
},
}
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
members, appErr := th.App.GetChannelMembersPage(th.Context, channel.Id, 0, 100)
require.Nil(t, appErr)
require.Len(t, members, 2)
for _, member := range members {
if member.UserId == th.BasicUser.Id {
checkPreference(t, th.App, th.BasicUser.Id, model.PreferenceCategoryDirectChannelShow, th.BasicUser2.Id, "true")
}
}
})
t.Run("Import a GM channel and preserve if the channel was shown to users", func(t *testing.T) {
data := imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
{
Username: model.NewString(user3.Username),
},
},
ShownBy: &[]string{
th.BasicUser.Username,
},
}
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
channel, appErr := th.App.GetGroupChannel(th.Context, []string{th.BasicUser.Id, th.BasicUser2.Id, user3.Id})
require.Nil(t, appErr)
members, appErr := th.App.GetChannelMembersPage(th.Context, channel.Id, 0, 100)
require.Nil(t, appErr)
require.Len(t, members, 3)
for _, member := range members {
if member.UserId == th.BasicUser.Id {
checkPreference(t, th.App, th.BasicUser.Id, model.PreferenceCategoryGroupChannelShow, channel.Id, "true")
}
}
})
}
func TestImportImportDirectPost(t *testing.T) {
@@ -3332,9 +3535,13 @@ func TestImportImportDirectPost(t *testing.T) {
// Create the DIRECT channel.
channelData := imports.DirectChannelImportData{
Members: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
},
}
appErr := th.App.importDirectChannel(th.Context, &channelData, false)
@@ -3688,10 +3895,16 @@ func TestImportImportDirectPost(t *testing.T) {
// Create the GROUP channel.
user3 := th.CreateUser()
channelData = imports.DirectChannelImportData{
Members: &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
{
Username: model.NewString(user3.Username),
},
},
}
appErr = th.App.importDirectChannel(th.Context, &channelData, false)

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

@@ -123,10 +123,27 @@ type UserChannelImportData struct {
LastViewedAt *int64 `json:"last_viewed_at,omitempty"`
}
type DirectChannelMemberImportData struct {
Username *string `json:"username"`
NotifyProps *UserChannelNotifyPropsImportData `json:"notify_props,omitempty"`
SchemeUser *bool `json:"scheme_user,omitempty"`
SchemeAdmin *bool `json:"scheme_admin,omitempty"`
SchemeGuest *bool `json:"scheme_guest,omitempty"`
MentionCount *int64 `json:"mention_count,omitempty"`
MentionCountRoot *int64 `json:"mention_count_root,omitempty"`
UrgentMentionCount *int64 `json:"urgend_mention_count,omitempty"`
MsgCount *int64 `json:"msg_count,omitempty"`
MsgCountRoot *int64 `json:"msg_count_root,omitempty"`
LastViewedAt *int64 `json:"last_viewed_at,omitempty"`
}
type UserChannelNotifyPropsImportData struct {
Desktop *string `json:"desktop"`
Mobile *string `json:"mobile"`
MarkUnread *string `json:"mark_unread"`
Desktop *string `json:"desktop"`
Mobile *string `json:"mobile"`
MarkUnread *string `json:"mark_unread"`
Email *string `json:"email,omitempty"`
IgnoreChannelMentions *string `json:"ignore_channel_mentions,omitempty"`
ChannelAutoFollowThreads *string `json:"channel_auto_follow_threads,omitempty"`
}
type EmojiImportData struct {
@@ -173,8 +190,10 @@ type PostImportData struct {
}
type DirectChannelImportData struct {
Members *[]string `json:"members"`
FavoritedBy *[]string `json:"favorited_by"`
Members *[]string `json:"members,omitempty"`
Participants []*DirectChannelMemberImportData `json:"participants,omitempty"`
FavoritedBy *[]string `json:"favorited_by,omitempty"`
ShownBy *[]string `json:"shown_by,omitempty"`
Header *string `json:"header"`
}

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

@@ -489,11 +489,19 @@ func ValidatePostImportData(data *PostImportData, maxPostSize int) *model.AppErr
}
func ValidateDirectChannelImportData(data *DirectChannelImportData) *model.AppError {
if data.Members == nil {
if data.Participants == nil && data.Members == nil {
return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_required.error", nil, "", http.StatusBadRequest)
}
if len(*data.Members) != 2 {
if data.Participants != nil && len(data.Participants) != 2 {
if len(data.Participants) < model.ChannelGroupMinUsers {
return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_too_few.error", nil, "", http.StatusBadRequest)
} else if len(data.Participants) > model.ChannelGroupMaxUsers {
return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_too_many.error", nil, "", http.StatusBadRequest)
}
}
if data.Members != nil && len(*data.Members) != 2 {
if len(*data.Members) < model.ChannelGroupMinUsers {
return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_too_few.error", nil, "", http.StatusBadRequest)
} else if len(*data.Members) > model.ChannelGroupMaxUsers {
@@ -508,12 +516,20 @@ func ValidateDirectChannelImportData(data *DirectChannelImportData) *model.AppEr
if data.FavoritedBy != nil {
for _, favoriter := range *data.FavoritedBy {
found := false
for _, member := range *data.Members {
if favoriter == member {
for _, member := range data.Participants {
if favoriter == *member.Username {
found = true
break
}
}
if data.Members != nil {
for _, member := range *data.Members {
if favoriter == member {
found = true
break
}
}
}
if !found {
return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.unknown_favoriter.error", map[string]any{"Username": favoriter}, "", http.StatusBadRequest)
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу