[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>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
de50943d61
Коммит
2cd83d2f8d
@@ -4172,6 +4172,41 @@ func (c *Client4) DoPostActionWithCookie(postId, actionId, selected, cookieStr s
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// GetTopThreadsForTeamSince will return an ordered list of the top channels in a given team.
|
||||
func (c *Client4) GetTopThreadsForTeamSince(teamId string, timeRange string, page int, perPage int) (*TopThreadList, *Response, error) {
|
||||
query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage)
|
||||
r, err := c.DoAPIGet(c.teamRoute(teamId)+"/top/threads"+query, "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
var topThreads *TopThreadList
|
||||
if jsonErr := json.NewDecoder(r.Body).Decode(&topThreads); jsonErr != nil {
|
||||
return nil, nil, NewAppError("GetTopThreadsForTeamSince", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return topThreads, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// GetTopThreadsForUserSince will return an ordered list of your top channels in a given team.
|
||||
func (c *Client4) GetTopThreadsForUserSince(teamId string, timeRange string, page int, perPage int) (*TopThreadList, *Response, error) {
|
||||
query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage)
|
||||
|
||||
if teamId != "" {
|
||||
query += fmt.Sprintf("&team_id=%v", teamId)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIGet(c.usersRoute()+"/me/top/threads"+query, "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
var topThreads *TopThreadList
|
||||
if jsonErr := json.NewDecoder(r.Body).Decode(&topThreads); jsonErr != nil {
|
||||
return nil, nil, NewAppError("GetTopThreadsForUserSince", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return topThreads, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// OpenInteractiveDialog sends a WebSocket event to a user's clients to
|
||||
// open interactive dialogs, based on the provided trigger ID and other
|
||||
// provided data. Used with interactive message buttons, menus and
|
||||
|
||||
@@ -63,6 +63,33 @@ type TopChannel struct {
|
||||
MessageCount int64 `json:"message_count"`
|
||||
}
|
||||
|
||||
// Top Threads
|
||||
type TopThreadList struct {
|
||||
InsightsListData
|
||||
Items []*TopThread `json:"items"`
|
||||
}
|
||||
|
||||
type TopThread struct {
|
||||
PostId string `json:"-"`
|
||||
ReplyCount int64 `json:"-"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
DisplayName string `json:"channel_display_name"`
|
||||
Name string `json:"channel_name"`
|
||||
Participants StringArray `json:"participants"`
|
||||
UserId string `json:"-"`
|
||||
UserInformation *InsightUserInformation `json:"user_information"`
|
||||
Post *Post `json:"post"`
|
||||
}
|
||||
|
||||
type InsightUserInformation struct {
|
||||
Id string `json:"id"`
|
||||
LastPictureUpdate int64 `json:"last_picture_update"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
NickName string `json:"nickname"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type DurationPostCount struct {
|
||||
ChannelID string `db:"channelid"`
|
||||
// Duration is an ISO8601 date string representing either a day or a day and hour (ex. "2022-05-26" or "2022-05-26T14").
|
||||
@@ -202,3 +229,17 @@ func GetTopChannelListWithPagination(channels []*TopChannel, limit int) *TopChan
|
||||
|
||||
return &TopChannelList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: channels}
|
||||
}
|
||||
|
||||
// GetTopThreadListWithPagination adds a rank to each item in the given list of TopThread and checks if there is
|
||||
// another page that can be fetched based on the given limit and offset. The given list of TopThread is assumed to be
|
||||
// sorted by ReplyCount(score). Returns a TopThreadList.
|
||||
func GetTopThreadListWithPagination(threads []*TopThread, limit int) *TopThreadList {
|
||||
// Add pagination support
|
||||
var hasNext bool
|
||||
if (limit != 0) && (len(threads) == limit+1) {
|
||||
hasNext = true
|
||||
threads = threads[:len(threads)-1]
|
||||
}
|
||||
|
||||
return &TopThreadList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: threads}
|
||||
}
|
||||
|
||||
@@ -82,3 +82,41 @@ func TestGetTopChannelListWithPagination(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTopThreadListWithPagination(t *testing.T) {
|
||||
threads := []*TopThread{
|
||||
{PostId: NewId(), ReplyCount: 100},
|
||||
{PostId: NewId(), ReplyCount: 80},
|
||||
{PostId: NewId(), ReplyCount: 90},
|
||||
{PostId: NewId(), ReplyCount: 76},
|
||||
{PostId: NewId(), ReplyCount: 43},
|
||||
{PostId: NewId(), ReplyCount: 2},
|
||||
{PostId: NewId(), ReplyCount: 1},
|
||||
}
|
||||
hasNextTT := []struct {
|
||||
Description string
|
||||
Limit int
|
||||
Offset int
|
||||
Expected *TopThreadList
|
||||
}{
|
||||
{
|
||||
Description: "has one page",
|
||||
Limit: len(threads),
|
||||
Offset: 0,
|
||||
Expected: &TopThreadList{InsightsListData: InsightsListData{HasNext: false}, Items: threads},
|
||||
},
|
||||
{
|
||||
Description: "has more than one page",
|
||||
Limit: len(threads) - 1,
|
||||
Offset: 0,
|
||||
Expected: &TopThreadList{InsightsListData: InsightsListData{HasNext: true}, Items: threads},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range hasNextTT {
|
||||
t.Run(test.Description, func(t *testing.T) {
|
||||
actual := GetTopThreadListWithPagination(threads, test.Limit)
|
||||
assert.Equal(t, test.Expected.HasNext, actual.HasNext)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user