MM-63725 Populate multiple sidebar categories at once whenever possible (#31064)

* Remove redundant sidebar tests from TestChannelStore

* MM-63725 Refactor to split out getOrphanedSidebarChannels

* MM-63725 Populate multiple sidebar categories at once whenever possible

* Fix shadowing
Этот коммит содержится в:
Harrison Healey
2025-06-19 10:09:36 -04:00
коммит произвёл GitHub
родитель 3bd0a2ad91
Коммит a3f60f797b
2 изменённых файлов: 90 добавлений и 39 удалений

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

@@ -414,25 +414,88 @@ func (s SqlChannelStore) CreateSidebarCategory(userId, teamId string, newCategor
return result, nil
}
func (s SqlChannelStore) completePopulatingCategoryChannelsT(db sqlxExecutor, category *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, error) {
if category.Type == model.SidebarCategoryCustom || category.Type == model.SidebarCategoryFavorites {
return category, nil
// completePopulatingCategoryT ensures that any orphaned channels are always included in a sidebar category by adding
// orphaned channels to either the Channels category or DMs category. It has no effect on other types of categories.
func (s SqlChannelStore) completePopulatingCategoryT(db sqlxExecutor, category *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, error) {
populatedChannels, err := s.getOrphanedSidebarChannels(
db,
category.UserId,
category.TeamId,
category.Type == model.SidebarCategoryChannels,
category.Type == model.SidebarCategoryDirectMessages,
)
if err != nil {
return nil, errors.Wrap(err, "Failed to get orphaned sidebar channels")
}
for _, channel := range populatedChannels {
category.Channels = append(category.Channels, channel.Id)
}
return category, nil
}
// completePopulatingCategoriesT ensures that any orphaned channels are always included in a sidebar category by adding
// orphaned channels to either the Channels category or DMs category. It has no effect on other types of categories.
func (s SqlChannelStore) completePopulatingCategoriesT(db sqlxExecutor, userId string, teamId string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, error) {
// Find the channels and DMs categories to know what to get from the database
channelsIndex := -1
dmsIndex := -1
for i, category := range categories {
if category.Type == model.SidebarCategoryChannels {
channelsIndex = i
} else if category.Type == model.SidebarCategoryDirectMessages {
dmsIndex = i
}
}
populatedChannels, err := s.getOrphanedSidebarChannels(
db,
userId,
teamId,
channelsIndex != -1,
dmsIndex != -1,
)
if err != nil {
return nil, errors.Wrap(err, "Failed to get orphaned sidebar channels")
}
// Sort the returned channels into their corresponding category
for _, channel := range populatedChannels {
if channelsIndex != -1 && (channel.Type == model.ChannelTypeOpen || channel.Type == model.ChannelTypePrivate) {
categories[channelsIndex].Channels = append(categories[channelsIndex].Channels, channel.Id)
} else if dmsIndex != -1 && (channel.Type == model.ChannelTypeDirect || channel.Type == model.ChannelTypeGroup) {
categories[dmsIndex].Channels = append(categories[dmsIndex].Channels, channel.Id)
}
}
return categories, nil
}
type OrphanedSidebarChannel struct {
Id string
Type model.ChannelType
}
// getOrphanedSidebarChannels returns all of the user's channels on a given team that aren't explicitly in any category.
func (s SqlChannelStore) getOrphanedSidebarChannels(db sqlxExecutor, userId string, teamId string, selectChannels bool, selectDMs bool) ([]*OrphanedSidebarChannel, error) {
if !selectChannels && !selectDMs {
return nil, nil
}
isMySQL := s.DriverName() == model.DatabaseDriverMysql
var channelTypeFilter sq.Sqlizer
if category.Type == model.SidebarCategoryDirectMessages {
channelTypeFilter := sq.Or{}
if selectDMs {
// any DM/GM channels that aren't in any category should be returned as part of the Direct Messages category
channelTypeFilter = sq.Eq{"Channels.Type": []model.ChannelType{model.ChannelTypeDirect, model.ChannelTypeGroup}}
} else if category.Type == model.SidebarCategoryChannels {
channelTypeFilter = append(channelTypeFilter, sq.Eq{"Channels.Type": []model.ChannelType{model.ChannelTypeDirect, model.ChannelTypeGroup}})
}
if selectChannels {
// any public/private channels that are on the current team and aren't in any category should be returned as part of the Channels category
channelTypeFilter = sq.And{
channelTypeFilter = append(channelTypeFilter, sq.And{
sq.Eq{"Channels.Type": []model.ChannelType{model.ChannelTypeOpen, model.ChannelTypePrivate}},
sq.Eq{"Channels.TeamId": category.TeamId},
}
} else {
return nil, fmt.Errorf("invalid category type: %q", category.Type)
sq.Eq{"Channels.TeamId": teamId},
})
}
// A subquery that is true if the channel does not have a SidebarChannel entry for the current user on the current team
@@ -454,26 +517,26 @@ func (s SqlChannelStore) completePopulatingCategoryChannelsT(db sqlxExecutor, ca
doesNotHaveSidebarChannel = doesNotHaveSidebarChannel.Where(sq.And{
sq.Expr("SidebarChannels.ChannelId = ChannelMembers.ChannelId"),
sq.Eq{"SidebarCategories.UserId": category.UserId},
sq.Eq{"SidebarCategories.TeamId": category.TeamId},
sq.Eq{"SidebarCategories.UserId": userId},
sq.Eq{"SidebarCategories.TeamId": teamId},
})
channels := []string{}
channels := []*OrphanedSidebarChannel{}
var col string
if isMySQL {
// This is a materialization hint for MySQL to materialize
// the doesNotHaveSidebarChannel sub-query
// Without this hint, MySQL is unable to come up with this plan by itself.
col = "/*+ SEMIJOIN(@subq1 MATERIALIZATION) */ Id"
col = "/*+ SEMIJOIN(@subq1 MATERIALIZATION) */ Id, Channels.Type"
} else {
col = "Id"
col = "Id, Channels.Type"
}
sql, args, err := s.getQueryBuilder().
Select(col).
From("ChannelMembers").
LeftJoin("Channels ON Channels.Id=ChannelMembers.ChannelId").
Where(sq.And{
sq.Eq{"ChannelMembers.UserId": category.UserId},
sq.Eq{"ChannelMembers.UserId": userId},
channelTypeFilter,
sq.Eq{"Channels.DeleteAt": 0},
doesNotHaveSidebarChannel,
@@ -487,8 +550,7 @@ func (s SqlChannelStore) completePopulatingCategoryChannelsT(db sqlxExecutor, ca
return nil, errors.Wrap(err, "failed to get channel members")
}
category.Channels = append(channels, category.Channels...)
return category, nil
return channels, nil
}
func (s SqlChannelStore) GetSidebarCategory(categoryId string) (*model.SidebarCategoryWithChannels, error) {
@@ -525,7 +587,7 @@ func (s SqlChannelStore) getSidebarCategoryT(db sqlxExecutor, categoryId string)
result.Channels = append(result.Channels, *category.ChannelId)
}
}
return s.completePopulatingCategoryChannelsT(db, result)
return s.completePopulatingCategoryT(db, result)
}
func (s SqlChannelStore) getSidebarCategoriesT(db sqlxExecutor, userId string, opts *store.SidebarCategorySearchOpts) (*model.OrderedSidebarCategories, error) {
@@ -589,10 +651,9 @@ func (s SqlChannelStore) getSidebarCategoriesT(db sqlxExecutor, userId string, o
prevCategory.Channels = append(prevCategory.Channels, *category.ChannelId)
}
}
for _, category := range oc.Categories {
if _, err := s.completePopulatingCategoryChannelsT(db, category); err != nil {
return nil, err
}
if _, err := s.completePopulatingCategoriesT(db, userId, opts.TeamID, oc.Categories); err != nil {
return nil, err
}
return &oc, nil
@@ -731,6 +792,8 @@ func (s SqlChannelStore) UpdateSidebarCategories(userId, teamId string, categori
copy(destCategory.Channels, category.Channels)
destCategory.Muted = category.Muted
} else {
destCategory.Channels = make([]string, 0)
}
updatedCategories = append(updatedCategories, destCategory)
@@ -877,13 +940,8 @@ func (s SqlChannelStore) UpdateSidebarCategories(userId, teamId string, categori
}
// Ensure Channels are populated for Channels/Direct Messages category if they change
for i, updatedCategory := range updatedCategories {
populated, nErr := s.completePopulatingCategoryChannelsT(transaction, updatedCategory)
if nErr != nil {
return nil, nil, nErr
}
updatedCategories[i] = populated
if _, nErr := s.completePopulatingCategoriesT(transaction, userId, teamId, updatedCategories); nErr != nil {
return nil, nil, nErr
}
if err = transaction.Commit(); err != nil {

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

@@ -147,13 +147,6 @@ func TestChannelStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore
t.Run("ExportAllDirectChannelsDeletedChannel", func(t *testing.T) { testChannelStoreExportAllDirectChannelsDeletedChannel(t, rctx, ss, s) })
t.Run("GetChannelsBatchForIndexing", func(t *testing.T) { testChannelStoreGetChannelsBatchForIndexing(t, rctx, ss) })
t.Run("GroupSyncedChannelCount", func(t *testing.T) { testGroupSyncedChannelCount(t, rctx, ss) })
t.Run("CreateInitialSidebarCategories", func(t *testing.T) { testCreateInitialSidebarCategories(t, rctx, ss) })
t.Run("CreateSidebarCategory", func(t *testing.T) { testCreateSidebarCategory(t, rctx, ss) })
t.Run("GetSidebarCategory", func(t *testing.T) { testGetSidebarCategory(t, rctx, ss, s) })
t.Run("GetSidebarCategories", func(t *testing.T) { testGetSidebarCategories(t, rctx, ss) })
t.Run("UpdateSidebarCategories", func(t *testing.T) { testUpdateSidebarCategories(t, rctx, ss) })
t.Run("DeleteSidebarCategory", func(t *testing.T) { testDeleteSidebarCategory(t, rctx, ss, s) })
t.Run("UpdateSidebarChannelsByPreferences", func(t *testing.T) { testUpdateSidebarChannelsByPreferences(t, rctx, ss) })
t.Run("SetShared", func(t *testing.T) { testSetShared(t, rctx, ss) })
t.Run("GetTeamForChannel", func(t *testing.T) { testGetTeamForChannel(t, rctx, ss) })
t.Run("GetChannelsWithUnreadsAndWithMentions", func(t *testing.T) { testGetChannelsWithUnreadsAndWithMentions(t, rctx, ss) })