2017-10-15 02:17:39 +03:00
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
2022-01-20 02:26:57 +03:00
"context"
2017-10-16 00:54:53 +03:00
"fmt"
2019-05-04 15:39:03 +03:00
"sort"
2017-10-15 02:17:39 +03:00
"time"
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2019-05-04 15:39:03 +03:00
"code.gitea.io/gitea/modules/git"
2019-10-17 12:26:49 +03:00
"xorm.io/xorm"
2017-10-15 02:17:39 +03:00
)
2019-05-04 15:39:03 +03:00
// ActivityAuthorData represents statistical git commit count data
type ActivityAuthorData struct {
Name string ` json:"name" `
Login string ` json:"login" `
AvatarLink string ` json:"avatar_link" `
2020-01-20 13:07:30 +03:00
HomeLink string ` json:"home_link" `
2019-05-04 15:39:03 +03:00
Commits int64 ` json:"commits" `
}
2017-10-15 02:17:39 +03:00
// ActivityStats represets issue and pull request information.
type ActivityStats struct {
OpenedPRs PullRequestList
OpenedPRAuthorCount int64
MergedPRs PullRequestList
MergedPRAuthorCount int64
OpenedIssues IssueList
OpenedIssueAuthorCount int64
ClosedIssues IssueList
ClosedIssueAuthorCount int64
UnresolvedIssues IssueList
PublishedReleases [ ] * Release
PublishedReleaseAuthorCount int64
2019-05-04 15:39:03 +03:00
Code * git . CodeActivityStats
2017-10-15 02:17:39 +03:00
}
2017-10-16 00:54:53 +03:00
// GetActivityStats return stats for repository at given time range
2022-01-20 02:26:57 +03:00
func GetActivityStats ( ctx context . Context , repo * repo_model . Repository , timeFrom time . Time , releases , issues , prs , code bool ) ( * ActivityStats , error ) {
2019-05-04 15:39:03 +03:00
stats := & ActivityStats { Code : & git . CodeActivityStats { } }
2017-10-16 00:54:53 +03:00
if releases {
2019-05-04 15:39:03 +03:00
if err := stats . FillReleases ( repo . ID , timeFrom ) ; err != nil {
2017-10-16 00:54:53 +03:00
return nil , fmt . Errorf ( "FillReleases: %v" , err )
}
}
if prs {
2019-05-04 15:39:03 +03:00
if err := stats . FillPullRequests ( repo . ID , timeFrom ) ; err != nil {
2017-10-16 00:54:53 +03:00
return nil , fmt . Errorf ( "FillPullRequests: %v" , err )
}
}
if issues {
2019-05-04 15:39:03 +03:00
if err := stats . FillIssues ( repo . ID , timeFrom ) ; err != nil {
2017-10-16 00:54:53 +03:00
return nil , fmt . Errorf ( "FillIssues: %v" , err )
}
}
2019-05-04 15:39:03 +03:00
if err := stats . FillUnresolvedIssues ( repo . ID , timeFrom , issues , prs ) ; err != nil {
2017-10-16 00:54:53 +03:00
return nil , fmt . Errorf ( "FillUnresolvedIssues: %v" , err )
}
2019-05-04 15:39:03 +03:00
if code {
2022-01-20 02:26:57 +03:00
gitRepo , closer , err := git . RepositoryFromContextOrOpen ( ctx , repo . RepoPath ( ) )
2019-05-04 15:39:03 +03:00
if err != nil {
return nil , fmt . Errorf ( "OpenRepository: %v" , err )
}
2022-01-20 02:26:57 +03:00
defer closer . Close ( )
2019-11-13 10:01:19 +03:00
2019-05-04 15:39:03 +03:00
code , err := gitRepo . GetCodeActivityStats ( timeFrom , repo . DefaultBranch )
if err != nil {
return nil , fmt . Errorf ( "FillFromGit: %v" , err )
}
stats . Code = code
}
2017-10-16 00:54:53 +03:00
return stats , nil
}
2019-05-04 15:39:03 +03:00
// GetActivityStatsTopAuthors returns top author stats for git commits for all branches
2022-01-20 02:26:57 +03:00
func GetActivityStatsTopAuthors ( ctx context . Context , repo * repo_model . Repository , timeFrom time . Time , count int ) ( [ ] * ActivityAuthorData , error ) {
gitRepo , closer , err := git . RepositoryFromContextOrOpen ( ctx , repo . RepoPath ( ) )
2019-05-04 15:39:03 +03:00
if err != nil {
return nil , fmt . Errorf ( "OpenRepository: %v" , err )
}
2022-01-20 02:26:57 +03:00
defer closer . Close ( )
2019-11-13 10:01:19 +03:00
2019-05-04 15:39:03 +03:00
code , err := gitRepo . GetCodeActivityStats ( timeFrom , "" )
if err != nil {
return nil , fmt . Errorf ( "FillFromGit: %v" , err )
}
if code . Authors == nil {
return nil , nil
}
users := make ( map [ int64 ] * ActivityAuthorData )
2020-01-20 13:07:30 +03:00
var unknownUserID int64
2021-11-24 12:49:20 +03:00
unknownUserAvatarLink := user_model . NewGhostUser ( ) . AvatarLink ( )
2020-01-20 13:07:30 +03:00
for _ , v := range code . Authors {
if len ( v . Email ) == 0 {
2019-05-04 15:39:03 +03:00
continue
}
2021-11-24 12:49:20 +03:00
u , err := user_model . GetUserByEmail ( v . Email )
if u == nil || user_model . IsErrUserNotExist ( err ) {
2020-01-20 13:07:30 +03:00
unknownUserID --
users [ unknownUserID ] = & ActivityAuthorData {
Name : v . Name ,
AvatarLink : unknownUserAvatarLink ,
Commits : v . Commits ,
}
2019-05-04 15:39:03 +03:00
continue
}
if err != nil {
return nil , err
}
if user , ok := users [ u . ID ] ; ! ok {
users [ u . ID ] = & ActivityAuthorData {
Name : u . DisplayName ( ) ,
Login : u . LowerName ,
AvatarLink : u . AvatarLink ( ) ,
2020-01-20 13:07:30 +03:00
HomeLink : u . HomeLink ( ) ,
Commits : v . Commits ,
2019-05-04 15:39:03 +03:00
}
} else {
2020-01-20 13:07:30 +03:00
user . Commits += v . Commits
2019-05-04 15:39:03 +03:00
}
}
v := make ( [ ] * ActivityAuthorData , 0 )
for _ , u := range users {
v = append ( v , u )
}
2019-06-12 22:41:28 +03:00
sort . Slice ( v , func ( i , j int ) bool {
2020-01-20 13:07:30 +03:00
return v [ i ] . Commits > v [ j ] . Commits
2019-05-04 15:39:03 +03:00
} )
cnt := count
if cnt > len ( v ) {
cnt = len ( v )
}
return v [ : cnt ] , nil
}
2017-10-15 02:17:39 +03:00
// ActivePRCount returns total active pull request count
func ( stats * ActivityStats ) ActivePRCount ( ) int {
return stats . OpenedPRCount ( ) + stats . MergedPRCount ( )
}
// OpenedPRCount returns opened pull request count
func ( stats * ActivityStats ) OpenedPRCount ( ) int {
return len ( stats . OpenedPRs )
}
// OpenedPRPerc returns opened pull request percents from total active
func ( stats * ActivityStats ) OpenedPRPerc ( ) int {
return int ( float32 ( stats . OpenedPRCount ( ) ) / float32 ( stats . ActivePRCount ( ) ) * 100.0 )
}
// MergedPRCount returns merged pull request count
func ( stats * ActivityStats ) MergedPRCount ( ) int {
return len ( stats . MergedPRs )
}
// MergedPRPerc returns merged pull request percent from total active
func ( stats * ActivityStats ) MergedPRPerc ( ) int {
return int ( float32 ( stats . MergedPRCount ( ) ) / float32 ( stats . ActivePRCount ( ) ) * 100.0 )
}
// ActiveIssueCount returns total active issue count
func ( stats * ActivityStats ) ActiveIssueCount ( ) int {
return stats . OpenedIssueCount ( ) + stats . ClosedIssueCount ( )
}
// OpenedIssueCount returns open issue count
func ( stats * ActivityStats ) OpenedIssueCount ( ) int {
return len ( stats . OpenedIssues )
}
// OpenedIssuePerc returns open issue count percent from total active
func ( stats * ActivityStats ) OpenedIssuePerc ( ) int {
return int ( float32 ( stats . OpenedIssueCount ( ) ) / float32 ( stats . ActiveIssueCount ( ) ) * 100.0 )
}
// ClosedIssueCount returns closed issue count
func ( stats * ActivityStats ) ClosedIssueCount ( ) int {
return len ( stats . ClosedIssues )
}
// ClosedIssuePerc returns closed issue count percent from total active
func ( stats * ActivityStats ) ClosedIssuePerc ( ) int {
return int ( float32 ( stats . ClosedIssueCount ( ) ) / float32 ( stats . ActiveIssueCount ( ) ) * 100.0 )
}
// UnresolvedIssueCount returns unresolved issue and pull request count
func ( stats * ActivityStats ) UnresolvedIssueCount ( ) int {
return len ( stats . UnresolvedIssues )
}
// PublishedReleaseCount returns published release count
func ( stats * ActivityStats ) PublishedReleaseCount ( ) int {
return len ( stats . PublishedReleases )
}
2017-10-16 00:54:53 +03:00
// FillPullRequests returns pull request information for activity page
func ( stats * ActivityStats ) FillPullRequests ( repoID int64 , fromTime time . Time ) error {
2017-10-15 02:17:39 +03:00
var err error
var count int64
// Merged pull requests
2017-10-16 00:54:53 +03:00
sess := pullRequestsForActivityStatement ( repoID , fromTime , true )
2017-10-15 02:17:39 +03:00
sess . OrderBy ( "pull_request.merged_unix DESC" )
stats . MergedPRs = make ( PullRequestList , 0 )
if err = sess . Find ( & stats . MergedPRs ) ; err != nil {
return err
}
if err = stats . MergedPRs . LoadAttributes ( ) ; err != nil {
return err
}
// Merged pull request authors
2017-10-16 00:54:53 +03:00
sess = pullRequestsForActivityStatement ( repoID , fromTime , true )
2017-10-15 02:17:39 +03:00
if _ , err = sess . Select ( "count(distinct issue.poster_id) as `count`" ) . Table ( "pull_request" ) . Get ( & count ) ; err != nil {
return err
}
stats . MergedPRAuthorCount = count
// Opened pull requests
2017-10-16 00:54:53 +03:00
sess = pullRequestsForActivityStatement ( repoID , fromTime , false )
2017-10-15 02:17:39 +03:00
sess . OrderBy ( "issue.created_unix ASC" )
stats . OpenedPRs = make ( PullRequestList , 0 )
if err = sess . Find ( & stats . OpenedPRs ) ; err != nil {
return err
}
if err = stats . OpenedPRs . LoadAttributes ( ) ; err != nil {
return err
}
// Opened pull request authors
2017-10-16 00:54:53 +03:00
sess = pullRequestsForActivityStatement ( repoID , fromTime , false )
2017-10-15 02:17:39 +03:00
if _ , err = sess . Select ( "count(distinct issue.poster_id) as `count`" ) . Table ( "pull_request" ) . Get ( & count ) ; err != nil {
return err
}
stats . OpenedPRAuthorCount = count
return nil
}
2017-10-16 00:54:53 +03:00
func pullRequestsForActivityStatement ( repoID int64 , fromTime time . Time , merged bool ) * xorm . Session {
2021-09-23 18:45:36 +03:00
sess := db . GetEngine ( db . DefaultContext ) . Where ( "pull_request.base_repo_id=?" , repoID ) .
2017-10-15 02:17:39 +03:00
Join ( "INNER" , "issue" , "pull_request.issue_id = issue.id" )
if merged {
sess . And ( "pull_request.has_merged = ?" , true )
sess . And ( "pull_request.merged_unix >= ?" , fromTime . Unix ( ) )
} else {
sess . And ( "issue.is_closed = ?" , false )
sess . And ( "issue.created_unix >= ?" , fromTime . Unix ( ) )
}
return sess
}
2017-10-16 00:54:53 +03:00
// FillIssues returns issue information for activity page
func ( stats * ActivityStats ) FillIssues ( repoID int64 , fromTime time . Time ) error {
2017-10-15 02:17:39 +03:00
var err error
var count int64
// Closed issues
2017-10-16 00:54:53 +03:00
sess := issuesForActivityStatement ( repoID , fromTime , true , false )
2018-02-19 05:39:26 +03:00
sess . OrderBy ( "issue.closed_unix DESC" )
2017-10-15 02:17:39 +03:00
stats . ClosedIssues = make ( IssueList , 0 )
if err = sess . Find ( & stats . ClosedIssues ) ; err != nil {
return err
}
// Closed issue authors
2017-10-16 00:54:53 +03:00
sess = issuesForActivityStatement ( repoID , fromTime , true , false )
2017-10-15 02:17:39 +03:00
if _ , err = sess . Select ( "count(distinct issue.poster_id) as `count`" ) . Table ( "issue" ) . Get ( & count ) ; err != nil {
return err
}
stats . ClosedIssueAuthorCount = count
// New issues
2017-10-16 00:54:53 +03:00
sess = issuesForActivityStatement ( repoID , fromTime , false , false )
2017-10-15 02:17:39 +03:00
sess . OrderBy ( "issue.created_unix ASC" )
stats . OpenedIssues = make ( IssueList , 0 )
if err = sess . Find ( & stats . OpenedIssues ) ; err != nil {
return err
}
// Opened issue authors
2017-10-16 00:54:53 +03:00
sess = issuesForActivityStatement ( repoID , fromTime , false , false )
2017-10-15 02:17:39 +03:00
if _ , err = sess . Select ( "count(distinct issue.poster_id) as `count`" ) . Table ( "issue" ) . Get ( & count ) ; err != nil {
return err
}
stats . OpenedIssueAuthorCount = count
return nil
}
2017-10-16 00:54:53 +03:00
// FillUnresolvedIssues returns unresolved issue and pull request information for activity page
func ( stats * ActivityStats ) FillUnresolvedIssues ( repoID int64 , fromTime time . Time , issues , prs bool ) error {
2017-10-15 02:17:39 +03:00
// Check if we need to select anything
if ! issues && ! prs {
return nil
}
2017-10-16 00:54:53 +03:00
sess := issuesForActivityStatement ( repoID , fromTime , false , true )
2017-10-15 02:17:39 +03:00
if ! issues || ! prs {
sess . And ( "issue.is_pull = ?" , prs )
}
sess . OrderBy ( "issue.updated_unix DESC" )
stats . UnresolvedIssues = make ( IssueList , 0 )
return sess . Find ( & stats . UnresolvedIssues )
}
2017-10-16 00:54:53 +03:00
func issuesForActivityStatement ( repoID int64 , fromTime time . Time , closed , unresolved bool ) * xorm . Session {
2021-09-23 18:45:36 +03:00
sess := db . GetEngine ( db . DefaultContext ) . Where ( "issue.repo_id = ?" , repoID ) .
2017-10-15 02:17:39 +03:00
And ( "issue.is_closed = ?" , closed )
if ! unresolved {
sess . And ( "issue.is_pull = ?" , false )
2018-02-19 05:39:26 +03:00
if closed {
sess . And ( "issue.closed_unix >= ?" , fromTime . Unix ( ) )
} else {
sess . And ( "issue.created_unix >= ?" , fromTime . Unix ( ) )
}
2017-10-15 02:17:39 +03:00
} else {
sess . And ( "issue.created_unix < ?" , fromTime . Unix ( ) )
sess . And ( "issue.updated_unix >= ?" , fromTime . Unix ( ) )
}
return sess
}
2017-10-16 00:54:53 +03:00
// FillReleases returns release information for activity page
func ( stats * ActivityStats ) FillReleases ( repoID int64 , fromTime time . Time ) error {
2017-10-15 02:17:39 +03:00
var err error
var count int64
// Published releases list
2017-10-16 00:54:53 +03:00
sess := releasesForActivityStatement ( repoID , fromTime )
2017-10-15 02:17:39 +03:00
sess . OrderBy ( "release.created_unix DESC" )
stats . PublishedReleases = make ( [ ] * Release , 0 )
if err = sess . Find ( & stats . PublishedReleases ) ; err != nil {
return err
}
// Published releases authors
2017-10-16 00:54:53 +03:00
sess = releasesForActivityStatement ( repoID , fromTime )
2017-10-15 02:17:39 +03:00
if _ , err = sess . Select ( "count(distinct release.publisher_id) as `count`" ) . Table ( "release" ) . Get ( & count ) ; err != nil {
return err
}
stats . PublishedReleaseAuthorCount = count
return nil
}
2017-10-16 00:54:53 +03:00
func releasesForActivityStatement ( repoID int64 , fromTime time . Time ) * xorm . Session {
2021-09-23 18:45:36 +03:00
return db . GetEngine ( db . DefaultContext ) . Where ( "release.repo_id = ?" , repoID ) .
2017-10-15 02:17:39 +03:00
And ( "release.is_draft = ?" , false ) .
And ( "release.created_unix >= ?" , fromTime . Unix ( ) )
}