[MM-44084] Feature: Top threads insights (#20195)

* Add route endpoints, model, store functions, and tests for top threads

* Run make store-layers

* Make the following changes

 - Fix top user threads query
 - Fix passing parameters in api4/insights.go to handler in app
 - Add top user threads test

* Add post-message, user_id, participants information to insights results

* model.TopThread.UserID -> model.TopThread.UserId, for compatibility with MySQL

* Rename name -> channel_name

* Add user information to response

* Link post in response, filter out deleted root posts from top threads

* Handle thread delete cases, add app tests for threads insights

* lint: fix typo

* lint: rename asserts

* lint: require.nil -> require.NoError

* Add integration tests for thread insights

* Add embeds and images to top posts

* Add license checks for top threads endpoints

* Query users in batch to populate post-creator

* Make the following changes

 - Add license to test server in api4/
 - Add tests for threads insights
    - top team threads shouldn't include threads from other teams, DMs
    - Test duration constraint
    - Pagination testing for top threads in model/insights_test.go

* Add i18n-extract

* i18n fixes

* Add username, nickname to user_information

* Hide message, user_id, post_id, reply_count in depth=1 of top threads response

* Fix tests using response.reply_count to use response.post.reply_count

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Shivashis Padhi
2022-06-20 19:57:17 +05:30
коммит произвёл GitHub
родитель de50943d61
Коммит 2cd83d2f8d
17 изменённых файлов: 1460 добавлений и 2 удалений

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

@@ -935,3 +935,209 @@ func (s *SqlThreadStore) GetThreadUnreadReplyCount(threadMembership *model.Threa
return unreadReplies, nil
}
// Top threads in all public channels and private channels userID is a member of. Returns a list of threads ranked by interactions.
func (s *SqlThreadStore) GetTopThreadsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) {
var args []interface{}
query := `select
threads_list.PostId,
threads_list.ReplyCount,
threads_list.ChannelId,
threads_list.DisplayName,
threads_list.Name,
threads_list.Participants,
p.UserId
from((
SELECT
t.PostId,
t.ReplyCount,
t.ChannelId,
t.Participants,
c.DisplayName,
c.Name
FROM
Threads t
LEFT JOIN PublicChannels c ON t.ChannelId = c.Id
WHERE
t.threaddeleteat IS NULL
AND t.LastReplyAt > ?
AND c.TeamId = ?
GROUP BY
t.PostId,
c.DisplayName,
c.Name,
t.Participants
)
UNION
ALL (
SELECT
t.PostId,
t.ReplyCount,
t.ChannelId,
t.Participants,
c.DisplayName,
c.Name
FROM
Threads t
LEFT JOIN ChannelMembers cm ON t.ChannelId = cm.ChannelId
LEFT JOIN Channels c ON t.ChannelId = c.Id
WHERE
t.threaddeleteat IS NULL
AND cm.UserId = ?
AND c.Type = 'P'
AND c.TeamId = ?
AND t.LastReplyAt > ?
GROUP BY
t.PostId,
c.DisplayName,
c.Name,
t.Participants
)) as threads_list
LEFT JOIN Posts as p on p.Id = threads_list.PostId
ORDER BY ReplyCount DESC
limit ? offset ?`
args = append(args, since, teamID, userID, teamID, since, limit+1, offset)
topThreads := make([]*model.TopThread, 0)
err := s.GetReplicaX().Select(&topThreads, query, args...)
if err != nil {
return nil, errors.Wrapf(err, "failed to get top threads=%s", teamID)
}
topThreads, err = postProcessTopThreads(topThreads, s, teamID)
if err != nil {
return nil, err
}
return model.GetTopThreadListWithPagination(topThreads, limit), nil
}
func (s *SqlThreadStore) GetTopThreadsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) {
var args []interface{}
// gets all threads within the team which user follows.
query := `select
threads_list.PostId,
threads_list.ReplyCount,
threads_list.ChannelId,
threads_list.DisplayName,
threads_list.Name,
threads_list.Participants,
p.UserId
from((
SELECT
t.PostId,
t.ReplyCount,
t.ChannelId,
t.Participants,
c.DisplayName,
c.Name
FROM
Threads t
LEFT JOIN PublicChannels c ON t.ChannelId = c.Id
LEFT JOIN ThreadMemberships as tm on t.PostId = tm.PostId
WHERE
t.threaddeleteat IS NULL
AND t.LastReplyAt > ?
AND c.TeamId = ?
AND tm.UserId = ?
AND tm.Following = TRUE
GROUP BY
t.PostId,
c.DisplayName,
c.Name,
t.Participants
)
UNION
ALL (
SELECT
t.PostId,
t.ReplyCount,
t.ChannelId,
t.Participants,
c.DisplayName,
c.Name
FROM
Threads t
LEFT JOIN ChannelMembers cm ON t.ChannelId = cm.ChannelId
LEFT JOIN Channels c ON t.ChannelId = c.Id
LEFT JOIN ThreadMemberships as tm on t.PostId = tm.PostId
WHERE
cm.UserId = ?
AND c.Type = 'P'
AND c.TeamId = ?
AND t.threaddeleteat IS NULL
AND t.LastReplyAt > ?
AND tm.UserId = ?
AND tm.Following = TRUE
GROUP BY
t.PostId,
c.DisplayName,
c.Name,
t.Participants
)) as threads_list
LEFT JOIN Posts as p on p.Id = threads_list.PostId
ORDER BY ReplyCount DESC
limit ? offset ?`
args = append(args, since, teamID, userID, userID, teamID, since, userID, limit+1, offset)
topThreads := make([]*model.TopThread, 0)
err := s.GetReplicaX().Select(&topThreads, query, args...)
if err != nil {
return nil, errors.Wrapf(err, "failed to get top threads=%s", teamID)
}
topThreads, err = postProcessTopThreads(topThreads, s, teamID)
if err != nil {
return nil, err
}
return model.GetTopThreadListWithPagination(topThreads, limit), nil
}
func userContains(userIDs []string, searchedUserID string) bool {
for _, userID := range userIDs {
if userID == searchedUserID {
return true
}
}
return false
}
func postProcessTopThreads(topThreads []*model.TopThread, s *SqlThreadStore, teamID string) ([]*model.TopThread, error) {
// create list of userIDs
var userIDs []string
for _, topThread := range topThreads {
userID := topThread.UserId
if !userContains(userIDs, userID) {
userIDs = append(userIDs, userID)
}
}
usersMap := map[string]*model.User{}
users, err := s.User().GetProfileByIds(context.Background(), userIDs, &store.UserGetByIdsOpts{}, true)
if err != nil {
return nil, errors.Wrapf(err, "failed to get users for top threads in team=%s", teamID)
}
for _, user := range users {
usersMap[user.Id] = user
}
// resolve user, root post for each top thread
for _, topThread := range topThreads {
postCreator := usersMap[topThread.UserId]
topThread.UserInformation = &model.InsightUserInformation{
Id: postCreator.Id,
LastPictureUpdate: postCreator.LastPictureUpdate,
FirstName: postCreator.FirstName,
LastName: postCreator.LastName,
Username: postCreator.Username,
NickName: postCreator.Nickname,
}
post, err := s.Post().GetSingle(topThread.PostId, false)
if err != nil {
return nil, errors.Wrapf(err, "failed to get extended post for post id=%s", topThread.PostId)
}
topThread.Post = post
}
return topThreads, nil
}