[MM 12464] Include DM/GM Channels and Their Posts in the Bulk Export (#10421)
* transplant the existing PR into the working tree * start addressing review comments * move existing direct channel export code into this branch * modify channel exporter to use squirell and populate members in two steps * use squirrel to build sql queries for channel and dm/gm export methods * remove debug helpers and use Username instead of UserId * unit test for DM Channel exporter * add more unit tests for channel export * add test for DM/GM post export * checkpoint with failing test for postgres * use getQueryBuilder to make sure squirrel uses the correct formatting for each database * add a test for post export * fix shadowed vars that broke the build * address review comments and add tests to support it * address review comments and add a mlog call * s/Info/Debug/ * address review comments in post_store * address review comments in channel_store * address review comments in export * address review comment in post_store: drop GroupBy * address review comment on supplier: move getQueryBuilder to sqlstore * address review comments: explicit TearDown * address review comments: improve test coverage * address review comments: make sure public and private channels are excluded * address review comments: improve test coverage * address review comments: make sure Channels table gets truncated after each test * more cleanups and better assertions * wrap PostStore in a StoreTestWithSqlSupplier * last minute changes: improve post export test coverage and check members * address review comments: make sure all posts have their channel members set * address review comments: make sure all posts have their ChannelMembers exported correctly * gofmt fix * sort channels so it's possible to assert on index
Этот коммит содержится в:
коммит произвёл
Jesús Espino
родитель
eb49713c96
Коммит
9abd4dd7dc
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/mattermost/gorp"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/mattermost/mattermost-server/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
@@ -2502,3 +2503,66 @@ func (s SqlChannelStore) GetChannelMembersForExport(userId string, teamId string
|
||||
result.Data = members
|
||||
})
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) GetAllDirectChannelsForExportAfter(limit int, afterId string) store.StoreChannel {
|
||||
return store.Do(func(result *store.StoreResult) {
|
||||
var directChannelsForExport []*model.DirectChannelForExport
|
||||
query := s.getQueryBuilder().
|
||||
Select("Channels.*").
|
||||
From("Channels").
|
||||
Where(sq.And{
|
||||
sq.Gt{"Channels.Id": afterId},
|
||||
sq.Eq{"Channels.DeleteAt": int(0)},
|
||||
sq.Eq{"Channels.Type": []string{"D", "G"}},
|
||||
}).
|
||||
OrderBy("Channels.Id").
|
||||
Limit(uint64(limit))
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetAllDirectChannelsForExportAfter", "store.sql_channel.get_all_direct.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = s.GetReplica().Select(&directChannelsForExport, queryString, args...); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetAllDirectChannelsForExportAfter", "store.sql_channel.get_all_direct.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var channelIds []string
|
||||
for _, channel := range directChannelsForExport {
|
||||
channelIds = append(channelIds, channel.Id)
|
||||
}
|
||||
query = s.getQueryBuilder().
|
||||
Select("*").
|
||||
From("ChannelMembers cm").
|
||||
Join("Users u ON ( u.Id = cm.UserId )").
|
||||
Where(sq.And{
|
||||
sq.Eq{"cm.ChannelId": channelIds},
|
||||
sq.Eq{"u.DeleteAt": int(0)},
|
||||
})
|
||||
|
||||
queryString, args, err = query.ToSql()
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetAllDirectChannelsForExportAfter", "store.sql_channel.get_all_direct.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var channelMembers []*model.ChannelMemberForExport
|
||||
if _, err := s.GetReplica().Select(&channelMembers, queryString, args...); err != nil {
|
||||
result.Err = model.NewAppError("SqlTeamStore.GetAllDirectChannelsForExportAfter", "store.sql_channel.get_all_direct.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// Populate each channel with its members
|
||||
dmChannelsMap := make(map[string]*model.DirectChannelForExport)
|
||||
for _, channel := range directChannelsForExport {
|
||||
channel.Members = &[]string{}
|
||||
dmChannelsMap[channel.Id] = channel
|
||||
}
|
||||
for _, member := range channelMembers {
|
||||
members := dmChannelsMap[member.ChannelId].Members
|
||||
*members = append(*members, member.Username)
|
||||
}
|
||||
result.Data = directChannelsForExport
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/mattermost/mattermost-server/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
@@ -1366,3 +1367,77 @@ func (s *SqlPostStore) GetRepliesForExport(parentId string) store.StoreChannel {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) GetDirectPostParentsForExportAfter(limit int, afterId string) store.StoreChannel {
|
||||
return store.Do(func(result *store.StoreResult) {
|
||||
query := s.getQueryBuilder().
|
||||
Select("p.*", "Users.Username as User").
|
||||
From("Posts p").
|
||||
Join("Channels ON p.ChannelId = Channels.Id").
|
||||
Join("Users ON p.UserId = Users.Id").
|
||||
Where(sq.And{
|
||||
sq.Gt{"p.Id": afterId},
|
||||
sq.Eq{"p.ParentId": string("")},
|
||||
sq.Eq{"p.DeleteAt": int(0)},
|
||||
sq.Eq{"Channels.DeleteAt": int(0)},
|
||||
sq.Eq{"Users.DeleteAt": int(0)},
|
||||
sq.Eq{"Channels.Type": []string{"D", "G"}},
|
||||
}).
|
||||
OrderBy("p.Id").
|
||||
Limit(uint64(limit))
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var posts []*model.DirectPostForExport
|
||||
if _, err = s.GetReplica().Select(&posts, queryString, args...); err != nil {
|
||||
result.Err = model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
var channelIds []string
|
||||
for _, post := range posts {
|
||||
channelIds = append(channelIds, post.ChannelId)
|
||||
}
|
||||
query = s.getQueryBuilder().
|
||||
Select("*").
|
||||
From("ChannelMembers cm").
|
||||
Join("Users u ON ( u.Id = cm.UserId )").
|
||||
Where(sq.Eq{
|
||||
"cm.ChannelId": channelIds,
|
||||
})
|
||||
|
||||
queryString, args, err = query.ToSql()
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var channelMembers []*model.ChannelMemberForExport
|
||||
if _, err := s.GetReplica().Select(&channelMembers, queryString, args...); err != nil {
|
||||
result.Err = model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// Build a map of channels and their posts
|
||||
postsChannelMap := make(map[string][]*model.DirectPostForExport)
|
||||
for _, post := range posts {
|
||||
post.ChannelMembers = &[]string{}
|
||||
postsChannelMap[post.ChannelId] = append(postsChannelMap[post.ChannelId], post)
|
||||
}
|
||||
|
||||
// Build a map of channels and their members
|
||||
channelMembersMap := make(map[string][]string)
|
||||
for _, member := range channelMembers {
|
||||
channelMembersMap[member.ChannelId] = append(channelMembersMap[member.ChannelId], member.Username)
|
||||
}
|
||||
|
||||
// Populate each post ChannelMembers extracting it from the channelMembersMap
|
||||
for channelId := range channelMembersMap {
|
||||
for _, post := range postsChannelMap[channelId] {
|
||||
*post.ChannelMembers = channelMembersMap[channelId]
|
||||
}
|
||||
}
|
||||
result.Data = posts
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,5 +10,5 @@ import (
|
||||
)
|
||||
|
||||
func TestPostStore(t *testing.T) {
|
||||
StoreTest(t, storetest.TestPostStore)
|
||||
StoreTestWithSqlSupplier(t, storetest.TestPostStore)
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/mattermost/gorp"
|
||||
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
@@ -98,4 +98,5 @@ type SqlStore interface {
|
||||
TermsOfService() store.TermsOfServiceStore
|
||||
UserTermsOfService() store.UserTermsOfServiceStore
|
||||
LinkMetadata() store.LinkMetadataStore
|
||||
getQueryBuilder() sq.StatementBuilderType
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/dyatlov/go-opengraph/opengraph"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/lib/pq"
|
||||
@@ -1051,6 +1052,14 @@ func (ss *SqlSupplier) DropAllTables() {
|
||||
ss.master.TruncateTables()
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) getQueryBuilder() sq.StatementBuilderType {
|
||||
builder := sq.StatementBuilder.PlaceholderFormat(sq.Question)
|
||||
if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
|
||||
builder = builder.PlaceholderFormat(sq.Dollar)
|
||||
}
|
||||
return builder
|
||||
}
|
||||
|
||||
type mattermConverter struct{}
|
||||
|
||||
func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {
|
||||
|
||||
@@ -67,15 +67,11 @@ func NewSqlUserStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) st
|
||||
metrics: metrics,
|
||||
}
|
||||
|
||||
us.usersQuery = sq.
|
||||
us.usersQuery = us.getQueryBuilder().
|
||||
Select("u.*", "b.UserId IS NOT NULL AS IsBot").
|
||||
From("Users u").
|
||||
LeftJoin("Bots b ON ( b.UserId = u.Id )")
|
||||
|
||||
if us.DriverName() == model.DATABASE_DRIVER_POSTGRES {
|
||||
us.usersQuery = us.usersQuery.PlaceholderFormat(sq.Dollar)
|
||||
}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.User{}, "Users").SetKeys(false, "Id")
|
||||
table.ColMap("Id").SetMaxSize(26)
|
||||
|
||||
Ссылка в новой задаче
Block a user