[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,
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,41 +1758,188 @@ 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 {
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: userID,
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 {
for _, favoriter := range *data.FavoritedBy {
@@ -1805,6 +1952,7 @@ func (a *App) importDirectChannel(rctx request.CTX, data *imports.DirectChannelI
}
}
if len(preferences) > 0 {
if err := a.Srv().Store().Preference().Save(preferences); err != nil {
var appErr *model.AppError
switch {
@@ -1815,6 +1963,7 @@ func (a *App) importDirectChannel(rctx request.CTX, data *imports.DirectChannelI
return model.NewAppError("importDirectChannel", "app.preference.save.updating.app_error", nil, "", http.StatusBadRequest).Wrap(err)
}
}
}
if data.Header != nil {
channel.Header = *data.Header

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

@@ -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,62 +3170,125 @@ 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(),
// 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,
},
}
}
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)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
// Do a valid DIRECT channel with a nonexistent member in dry-run mode.
data.Members = &[]string{
model.NewId(),
model.NewId(),
})
}
})
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)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
// 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)
})
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)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
// Do an invalid channel in apply mode.
data.Members = &[]string{
model.NewId(),
})
}
})
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)
// Check that no more channels are in the DB.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
// Do a valid DIRECT channel.
data.Members = &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
})
}
appErr = th.App.importDirectChannel(th.Context, &data, false)
})
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)
// Check that one more DIRECT channel is in the DB.
@@ -3252,29 +3316,56 @@ func TestImportImportDirectChannel(t *testing.T) {
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
require.Equal(t, channel.Header, *data.Header)
// 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)
})
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)
// 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 valid GROUP channel.
data.Members = &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
user3.Username,
})
}
appErr = th.App.importDirectChannel(th.Context, &data, false)
})
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)
// Check that one more GROUP channel is in the DB.
@@ -3304,26 +3395,138 @@ func TestImportImportDirectChannel(t *testing.T) {
th.BasicUser2.Id,
user3.Id,
}
channel, appErr = th.App.createGroupChannel(th.Context, userIDs)
channel, appErr := th.App.createGroupChannel(th.Context, userIDs)
require.Equal(t, appErr.Id, store.ChannelExistsError)
require.Equal(t, channel.Header, *data.Header)
// Import a channel with some favorites.
data.Members = &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
})
}
})
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)
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)
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"`
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.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)
}

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

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

@@ -4088,7 +4088,7 @@ func (s SqlChannelStore) GetAllDirectChannelsForExportAfter(limit int, afterId s
channelIds = append(channelIds, channel.Id)
}
query = s.getQueryBuilder().
Select("u.Username as Username, ChannelId, UserId, cm.Roles as Roles, LastViewedAt, MsgCount, MentionCount, MentionCountRoot, COALESCE(UrgentMentionCount, 0) UrgentMentionCount, cm.NotifyProps as NotifyProps, LastUpdateAt, SchemeUser, SchemeAdmin, (SchemeGuest IS NOT NULL AND SchemeGuest) as SchemeGuest").
Select("u.Username as Username, ChannelId, UserId, cm.Roles as Roles, LastViewedAt, MsgCount, MsgCountRoot, MentionCount, MentionCountRoot, COALESCE(UrgentMentionCount, 0) UrgentMentionCount, cm.NotifyProps as NotifyProps, LastUpdateAt, SchemeUser, SchemeAdmin, (SchemeGuest IS NOT NULL AND SchemeGuest) as SchemeGuest").
From("ChannelMembers cm").
Join("Users u ON ( u.Id = cm.UserId )").
Where(sq.Eq{"cm.ChannelId": channelIds})
@@ -4106,12 +4106,11 @@ func (s SqlChannelStore) GetAllDirectChannelsForExportAfter(limit int, afterId s
// Populate each channel with its members
dmChannelsMap := make(map[string]*model.DirectChannelForExport)
for _, channel := range directChannelsForExport {
channel.Members = &[]string{}
channel.Members = []*model.ChannelMemberForExport{}
dmChannelsMap[channel.Id] = channel
}
for _, member := range channelMembers {
members := dmChannelsMap[member.ChannelId].Members
*members = append(*members, member.Username)
dmChannelsMap[member.ChannelId].Members = append(dmChannelsMap[member.ChannelId].Members, member)
}
return directChannelsForExport, nil

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

@@ -7852,7 +7852,7 @@ func testChannelStoreExportAllDirectChannelsDeletedChannel(t *testing.T, rctx re
d1, nErr = ss.Channel().GetAllDirectChannelsForExportAfter(10000, strings.Repeat("0", 26), true)
assert.NoError(t, nErr)
assert.Len(t, d1, 1)
assert.Len(t, *d1[0].Members, 2)
assert.Len(t, d1[0].Members, 2)
// Manually truncate Channels table until testlib can handle cleanups
s.GetMasterX().Exec("TRUNCATE Channels")

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

@@ -832,7 +832,17 @@ func (v *Validator) validateDirectChannel(info ImportFileInfo, line imports.Line
}
}
if data.Members != nil {
if data.Participants != nil {
for i, member := range data.Participants {
if _, ok := v.users[*member.Username]; !ok {
return &ImportValidationError{
ImportFileInfo: info,
FieldName: fmt.Sprintf("direct_channel.members[%d]", i),
Err: fmt.Errorf("reference to unknown user %q", *member.Username),
}
}
}
} else if data.Members != nil {
for i, member := range *data.Members {
if _, ok := v.users[member]; !ok {
return &ImportValidationError{

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

@@ -394,9 +394,16 @@ func createPost(team string, channel string, allUsers []string, createAt int64)
func createDirectChannel(members []string) imports.LineImportData {
header := fake.Sentence()
var p []*imports.DirectChannelMemberImportData
for _, m := range members {
p = append(p, &imports.DirectChannelMemberImportData{
Username: model.NewString(m),
})
}
channel := imports.DirectChannelImportData{
Members: &members,
Participants: p,
Header: &header,
}
return imports.LineImportData{

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

@@ -5174,6 +5174,14 @@
"id": "app.import.import_direct_channel.create_group_channel.error",
"translation": "Failed to create group channel"
},
{
"id": "app.import.import_direct_channel.get_channel_members.error",
"translation": "Failed to get channel members for direct channel"
},
{
"id": "app.import.import_direct_channel.no_members.error",
"translation": "There are no members for the direct channel"
},
{
"id": "app.import.import_direct_channel.update_header_failed.error",
"translation": "Failed to update direct channel header"

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

@@ -122,7 +122,7 @@ type ChannelForExport struct {
type DirectChannelForExport struct {
Channel
Members *[]string
Members []*ChannelMemberForExport
}
type ChannelModeration struct {