[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)
}

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

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

@@ -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,10 +394,17 @@ 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,
Header: &header,
Participants: p,
Header: &header,
}
return imports.LineImportData{
Type: "direct_channel",

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

@@ -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 {