[MM-55017] Add API method to get users for Admin Reporting (#25499)

* Add store method to get reporting data

* Some store changes

* Added app layer

* Added API call, some miscellaneous fixes

* Fix lint

* Fix serialized check

* Add API docs

* Fix user store tests leaking users

* Fix test

* PR feedback

* Add filtering for role/team/activated user, filter out bot users

* Fix mock

* Fix test

* Oops

* Switch to using struct filter

* More PR feedback

* Fix gen

* Fix test

* Fix API docs

* Fix test

* Fix possible SQL injection, some query optimization

* Fix migrations

* Oops

* Add role to API

* Fix check

* Add Client4 API call for load testing

* Fix test

* Update server/channels/store/storetest/user_store.go

Co-authored-by: Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>

* PR feedback

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>
Этот коммит содержится в:
Devin Binnie
2023-12-08 10:30:08 -05:00
коммит произвёл GitHub
родитель 7afc14de36
Коммит 109f4643c6
26 изменённых файлов: 2399 добавлений и 1 удалений

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

@@ -1918,6 +1918,54 @@ func (c *Client4) EnableUserAccessToken(ctx context.Context, tokenId string) (*R
return BuildResponse(r), nil
}
func (c *Client4) GetUsersForReporting(ctx context.Context, options *UserReportOptionsAPI) ([]*UserReport, *Response, error) {
values := url.Values{}
if options.SortColumn != "" {
values.Set("sort_column", options.SortColumn)
}
if options.PageSize > 0 {
values.Set("page_size", strconv.Itoa(options.PageSize))
}
if options.Team != "" {
values.Set("team_filter", options.Team)
}
if options.HideActive {
values.Set("hide_active", "true")
}
if options.HideInactive {
values.Set("hide_inactive", "true")
}
if options.SortDesc {
values.Set("sort_direction", "desc")
}
if options.LastSortColumnValue != "" {
values.Set("last_column_value", options.LastSortColumnValue)
}
if options.LastUserId != "" {
values.Set("last_id", options.LastUserId)
}
if options.Role != "" {
values.Set("role_filter", options.Role)
}
if options.HasNoTeam {
values.Set("has_no_team", "true")
}
if options.DateRange != "" {
values.Set("date_range", options.DateRange)
}
r, err := c.DoAPIGet(ctx, c.usersRoute()+"/report?"+values.Encode(), "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var list []*UserReport
if err := json.NewDecoder(r.Body).Decode(&list); err != nil {
return nil, nil, NewAppError("GetUsersForReporting", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return list, BuildResponse(r), nil
}
// Bots section
// CreateBot creates a bot in the system based on the provided bot struct.

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

@@ -48,6 +48,10 @@ const (
PushThreadsNotifyProp = "push_threads"
EmailThreadsNotifyProp = "email_threads"
ReportDurationLast30Days = "last_30_days"
ReportDurationPreviousMonth = "previous_month"
ReportDurationLast6Months = "last_6_months"
DefaultLocale = "en"
UserAuthServiceEmail = "email"
@@ -67,6 +71,10 @@ const (
DesktopTokenTTL = time.Minute * 3
)
var (
UserReportSortColumns = []string{"CreateAt", "Username", "FirstName", "LastName", "Nickname", "Email", "Roles"}
)
//msgp:tuple User
// User contains the details about the user.
@@ -1007,3 +1015,82 @@ type UsersWithGroupsAndCount struct {
Users []*UserWithGroups `json:"users"`
Count int64 `json:"total_count"`
}
type UserPostStats struct {
LastLogin int64 `json:"last_login_at,omitempty"`
LastStatusAt *int64 `json:"last_status_at,omitempty"`
LastPostDate *int64 `json:"last_post_date,omitempty"`
DaysActive *int `json:"days_active,omitempty"`
TotalPosts *int `json:"total_posts,omitempty"`
}
type UserReportQuery struct {
User
UserPostStats
}
type UserReport struct {
Id string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
CreateAt int64 `json:"create_at,omitempty"`
DisplayName string `json:"display_name"`
Roles string `json:"roles"`
UserPostStats
}
type UserReportOptionsWithoutDateRange struct {
SortColumn string
SortDesc bool
PageSize int
LastSortColumnValue string
LastUserId string
Role string
Team string
HasNoTeam bool
HideActive bool
HideInactive bool
}
type UserReportOptions struct {
UserReportOptionsWithoutDateRange
StartAt int64
EndAt int64
}
type UserReportOptionsAPI struct {
UserReportOptionsWithoutDateRange
DateRange string
}
func (u *UserReportOptionsAPI) ToBaseOptions(now time.Time) *UserReportOptions {
startAt := int64(0)
endAt := int64(0)
if u.DateRange == ReportDurationLast30Days {
startAt = now.AddDate(0, 0, -30).UnixMilli()
} else if u.DateRange == ReportDurationPreviousMonth {
startOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.Local)
startAt = startOfMonth.AddDate(0, -1, 0).UnixMilli()
endAt = startOfMonth.UnixMilli()
} else if u.DateRange == ReportDurationLast6Months {
startAt = now.AddDate(0, -6, -0).UnixMilli()
}
return &UserReportOptions{
UserReportOptionsWithoutDateRange: u.UserReportOptionsWithoutDateRange,
StartAt: startAt,
EndAt: endAt,
}
}
func (u *UserReportQuery) ToReport() *UserReport {
return &UserReport{
Id: u.Id,
Username: u.Username,
Email: u.Email,
CreateAt: u.CreateAt,
DisplayName: u.GetDisplayName(ShowNicknameFullName),
Roles: u.Roles,
UserPostStats: u.UserPostStats,
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу