[MM-42739] Insights - Top Channels API Endpoint (#19953)

* [MM-42739] Initial setup for top channels for team

* [MM-42739] Add initial tests

* [MM-42739] Update tests

* [MM-42739] Add top channels for user

* [MM-42739] Fix query

* [MM-42739] Update query

* [MM-42739] Improve query performance

* [MM-42739] Remove rank

* [MM-42739] Fix tests to use new time range today

* [MM-42739] Add tests for top channels for user

* [MM-42739] Add test for pagination

* Remove top channels by time struct

* [MM-42739] Update test names

* [MM-42739] Remove rank from top reactions

* [MM-42739] Return empty array instead of nil when result is empty

* [MM-42739] Add additional tests and update permissions check for teams

* [MM-42739] Add excluded channel tests for top reactions

* [MM-42739] Move insights to api4/insights and keep time range as string until required

* [MM-42739] Update queries only check DeleteAt after union

* [MM-42739] Improve query performance by using publicchannels table

* [MM-42739] Fix broken query after merge
Этот коммит содержится в:
Mylon Suren
2022-04-26 14:42:24 -04:00
коммит произвёл GitHub
родитель 9d37a6cc3d
Коммит 52ac449012
22 изменённых файлов: 1453 добавлений и 510 удалений

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

@@ -4097,3 +4097,161 @@ func (s SqlChannelStore) GetTeamForChannel(channelID string) (*model.Team, error
}
return &team, nil
}
// GetTopChannelsForTeamSince returns the filtered post counts of the following Channels sets:
// a) those that are private channels in the given user's membership graph on the given team, and
// b) those that are public channels in the given team.
func (s SqlChannelStore) GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
channels := make([]*model.TopChannel, 0)
var args []interface{}
postgresPropQuery := `AND (Posts.Props ->> 'from_bot' IS NULL OR Posts.Props ->> 'from_bot' = 'false')`
mySqlPropsQuery := `AND (JSON_EXTRACT(Posts.Props, '$.from_bot') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_bot') = 'false')`
query := `
SELECT
ID,
Type,
DisplayName,
Name,
TeamID,
MessageCount
FROM
((SELECT
Posts.ChannelId AS ID,
'O' AS Type,
PublicChannels.DisplayName AS DisplayName,
PublicChannels.Name AS Name,
PublicChannels.TeamId AS TeamID,
count(Posts.Id) AS MessageCount,
PublicChannels.DeleteAt AS DeleteAt
FROM
Posts
LEFT JOIN PublicChannels on Posts.ChannelId = PublicChannels.Id
WHERE
Posts.DeleteAt = 0
AND Posts.CreateAt > ?
AND Posts.Type = ''`
args = []interface{}{since}
if s.DriverName() == model.DatabaseDriverMysql {
query += mySqlPropsQuery
} else if s.DriverName() == model.DatabaseDriverPostgres {
query += postgresPropQuery
}
query += `
AND PublicChannels.TeamId = ?
GROUP BY
Posts.ChannelId,
PublicChannels.DisplayName,
PublicChannels.Name,
PublicChannels.TeamId,
PublicChannels.DeleteAt)
UNION ALL
(SELECT
Posts.ChannelId AS ID,
Channels.Type AS Type,
Channels.DisplayName AS DisplayName,
Channels.Name AS Name,
Channels.TeamId AS TeamID,
count(Posts.Id) AS MessageCount,
Channels.DeleteAt AS DeleteAt
FROM
Posts
LEFT JOIN Channels on Posts.ChannelId = Channels.Id
LEFT JOIN ChannelMembers on Posts.ChannelId = ChannelMembers.ChannelId
WHERE
Posts.DeleteAt = 0
AND Posts.CreateAt > ?
AND Posts.Type = ''`
args = append(args, teamID, since)
if s.DriverName() == model.DatabaseDriverMysql {
query += mySqlPropsQuery
} else if s.DriverName() == model.DatabaseDriverPostgres {
query += postgresPropQuery
}
query += `
AND Channels.TeamId = ?
AND Channels.Type = 'P'
AND ChannelMembers.UserId = ?
GROUP BY
Posts.ChannelId,
Channels.Type,
Channels.DisplayName,
Channels.Name,
Channels.TeamId,
Channels.DeleteAt)) AS A
WHERE
DeleteAt = 0
ORDER BY
MessageCount DESC,
Name ASC
LIMIT ?
OFFSET ?`
args = append(args, teamID, userID, limit+1, offset)
if err := s.GetReplicaX().Select(&channels, query, args...); err != nil {
return nil, errors.Wrap(err, "failed to get top Channels")
}
return model.GetTopChannelListWithPagination(channels, limit), nil
}
// GetTopChannelsForUserSince returns the filtered post counts of channels with with posts created by the user
// after the given timestamp within the given team (or across the workspace if no team is given). Excludes DM and GM channels.
func (s SqlChannelStore) GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
channels := make([]*model.TopChannel, 0)
var args []interface{}
var query string
query = `
SELECT
Posts.ChannelId AS ID,
Channels.Type AS Type,
Channels.DisplayName AS DisplayName,
Channels.Name AS Name,
Channels.TeamId AS TeamID,
count(Posts.Id) AS MessageCount
FROM
Posts
LEFT JOIN Channels on Posts.ChannelId = Channels.Id
LEFT JOIN ChannelMembers on Posts.ChannelId = ChannelMembers.ChannelId
WHERE
Posts.DeleteAt = 0
AND Posts.CreateAt > ?
AND Posts.Type = ''
AND Posts.UserID = ?
AND Channels.DeleteAt = 0
AND (Channels.Type = 'O' OR Channels.Type = 'P')
AND ChannelMembers.UserId = ?`
args = []interface{}{since, userID, userID}
if teamID != "" {
query += `
AND Channels.TeamID = ?`
args = append(args, teamID)
}
query += `
Group By
Posts.ChannelId,
Channels.Type,
Channels.DisplayName,
Channels.Name,
Channels.TeamId
ORDER BY
MessageCount DESC,
Name ASC
LIMIT ?
OFFSET ?`
args = append(args, limit+1, offset)
if err := s.GetReplicaX().Select(&channels, query, args...); err != nil {
return nil, errors.Wrap(err, "failed to get top Channels")
}
return model.GetTopChannelListWithPagination(channels, limit), nil
}

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

@@ -232,7 +232,7 @@ func (s *SqlReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int
// a) those created by anyone in private channels in the given user's membership graph on the given team, and
// b) those created by anyone in public channels on the given team.
func (s *SqlReactionStore) GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
var reactions []*model.TopReaction
reactions := make([]*model.TopReaction, 0)
query := `
SELECT
@@ -241,34 +241,41 @@ func (s *SqlReactionStore) GetTopForTeamSince(teamID string, userID string, sinc
FROM ((
SELECT
EmojiName,
count(EmojiName) AS EmojiCount
count(EmojiName) AS EmojiCount,
Reactions.DeleteAt AS DeleteAt,
Reactions.CreateAt AS CreateAt
FROM
ChannelMembers
INNER JOIN Channels ON ChannelMembers.ChannelId = Channels.Id
INNER JOIN Posts ON Channels.Id = Posts.ChannelId
INNER JOIN Reactions ON Posts.Id = Reactions.PostId
WHERE
Reactions.DeleteAt = 0
AND ChannelMembers.UserId = ?
ChannelMembers.UserId = ?
AND Channels.Type = 'P'
AND Channels.TeamId = ?
AND Reactions.CreateAt > ?
GROUP BY
Reactions.EmojiName)
Reactions.EmojiName,
Reactions.DeleteAt,
Reactions.CreateAt)
UNION ALL (
SELECT
EmojiName,
count(EmojiName) AS EmojiCount
count(EmojiName) AS EmojiCount,
Reactions.DeleteAt AS DeleteAt,
Reactions.CreateAt AS CreateAt
FROM
Reactions
INNER JOIN Posts ON Reactions.PostId = Posts.Id
INNER JOIN PublicChannels ON Posts.ChannelId = PublicChannels.Id
WHERE
Reactions.DeleteAt = 0
AND PublicChannels.TeamId = ?
AND Reactions.CreateAt > ?
PublicChannels.TeamId = ?
GROUP BY
Reactions.EmojiName)) AS A
Reactions.EmojiName,
Reactions.DeleteAt,
Reactions.CreateAt)) AS A
WHERE
DeleteAt = 0
AND CreateAt > ?
GROUP BY
EmojiName
ORDER BY
@@ -277,18 +284,18 @@ func (s *SqlReactionStore) GetTopForTeamSince(teamID string, userID string, sinc
LIMIT ?
OFFSET ?`
if err := s.GetReplicaX().Select(&reactions, query, userID, teamID, since, teamID, since, limit+1, offset); err != nil {
if err := s.GetReplicaX().Select(&reactions, query, userID, teamID, teamID, since, limit+1, offset); err != nil {
return nil, errors.Wrap(err, "failed to get top Reactions")
}
return model.GetTopReactionListWithRankAndPagination(reactions, limit, offset), nil
return model.GetTopReactionListWithPagination(reactions, limit), nil
}
// GetTopForUserSince returns the instance counts of the following Reactions sets:
// a) those created by the given user in any channel type on the given team (across the workspace if no team is given), and
// b) those created by the given user in DM or group channels.
func (s *SqlReactionStore) GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
var reactions []*model.TopReaction
reactions := make([]*model.TopReaction, 0)
var args []interface{}
var query string
@@ -339,7 +346,7 @@ func (s *SqlReactionStore) GetTopForUserSince(userID string, teamID string, sinc
return nil, errors.Wrap(err, "failed to get top Reactions")
}
return model.GetTopReactionListWithRankAndPagination(reactions, limit, offset), nil
return model.GetTopReactionListWithPagination(reactions, limit), nil
}
func (s *SqlReactionStore) saveReactionAndUpdatePost(transaction *sqlxTxWrapper, reaction *model.Reaction) error {