You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

271 lines
8.4 KiB

  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "fmt"
  7. "time"
  8. "github.com/go-xorm/xorm"
  9. )
  10. // ActivityStats represets issue and pull request information.
  11. type ActivityStats struct {
  12. OpenedPRs PullRequestList
  13. OpenedPRAuthorCount int64
  14. MergedPRs PullRequestList
  15. MergedPRAuthorCount int64
  16. OpenedIssues IssueList
  17. OpenedIssueAuthorCount int64
  18. ClosedIssues IssueList
  19. ClosedIssueAuthorCount int64
  20. UnresolvedIssues IssueList
  21. PublishedReleases []*Release
  22. PublishedReleaseAuthorCount int64
  23. }
  24. // GetActivityStats return stats for repository at given time range
  25. func GetActivityStats(repoID int64, timeFrom time.Time, releases, issues, prs bool) (*ActivityStats, error) {
  26. stats := &ActivityStats{}
  27. if releases {
  28. if err := stats.FillReleases(repoID, timeFrom); err != nil {
  29. return nil, fmt.Errorf("FillReleases: %v", err)
  30. }
  31. }
  32. if prs {
  33. if err := stats.FillPullRequests(repoID, timeFrom); err != nil {
  34. return nil, fmt.Errorf("FillPullRequests: %v", err)
  35. }
  36. }
  37. if issues {
  38. if err := stats.FillIssues(repoID, timeFrom); err != nil {
  39. return nil, fmt.Errorf("FillIssues: %v", err)
  40. }
  41. }
  42. if err := stats.FillUnresolvedIssues(repoID, timeFrom, issues, prs); err != nil {
  43. return nil, fmt.Errorf("FillUnresolvedIssues: %v", err)
  44. }
  45. return stats, nil
  46. }
  47. // ActivePRCount returns total active pull request count
  48. func (stats *ActivityStats) ActivePRCount() int {
  49. return stats.OpenedPRCount() + stats.MergedPRCount()
  50. }
  51. // OpenedPRCount returns opened pull request count
  52. func (stats *ActivityStats) OpenedPRCount() int {
  53. return len(stats.OpenedPRs)
  54. }
  55. // OpenedPRPerc returns opened pull request percents from total active
  56. func (stats *ActivityStats) OpenedPRPerc() int {
  57. return int(float32(stats.OpenedPRCount()) / float32(stats.ActivePRCount()) * 100.0)
  58. }
  59. // MergedPRCount returns merged pull request count
  60. func (stats *ActivityStats) MergedPRCount() int {
  61. return len(stats.MergedPRs)
  62. }
  63. // MergedPRPerc returns merged pull request percent from total active
  64. func (stats *ActivityStats) MergedPRPerc() int {
  65. return int(float32(stats.MergedPRCount()) / float32(stats.ActivePRCount()) * 100.0)
  66. }
  67. // ActiveIssueCount returns total active issue count
  68. func (stats *ActivityStats) ActiveIssueCount() int {
  69. return stats.OpenedIssueCount() + stats.ClosedIssueCount()
  70. }
  71. // OpenedIssueCount returns open issue count
  72. func (stats *ActivityStats) OpenedIssueCount() int {
  73. return len(stats.OpenedIssues)
  74. }
  75. // OpenedIssuePerc returns open issue count percent from total active
  76. func (stats *ActivityStats) OpenedIssuePerc() int {
  77. return int(float32(stats.OpenedIssueCount()) / float32(stats.ActiveIssueCount()) * 100.0)
  78. }
  79. // ClosedIssueCount returns closed issue count
  80. func (stats *ActivityStats) ClosedIssueCount() int {
  81. return len(stats.ClosedIssues)
  82. }
  83. // ClosedIssuePerc returns closed issue count percent from total active
  84. func (stats *ActivityStats) ClosedIssuePerc() int {
  85. return int(float32(stats.ClosedIssueCount()) / float32(stats.ActiveIssueCount()) * 100.0)
  86. }
  87. // UnresolvedIssueCount returns unresolved issue and pull request count
  88. func (stats *ActivityStats) UnresolvedIssueCount() int {
  89. return len(stats.UnresolvedIssues)
  90. }
  91. // PublishedReleaseCount returns published release count
  92. func (stats *ActivityStats) PublishedReleaseCount() int {
  93. return len(stats.PublishedReleases)
  94. }
  95. // FillPullRequests returns pull request information for activity page
  96. func (stats *ActivityStats) FillPullRequests(repoID int64, fromTime time.Time) error {
  97. var err error
  98. var count int64
  99. // Merged pull requests
  100. sess := pullRequestsForActivityStatement(repoID, fromTime, true)
  101. sess.OrderBy("pull_request.merged_unix DESC")
  102. stats.MergedPRs = make(PullRequestList, 0)
  103. if err = sess.Find(&stats.MergedPRs); err != nil {
  104. return err
  105. }
  106. if err = stats.MergedPRs.LoadAttributes(); err != nil {
  107. return err
  108. }
  109. // Merged pull request authors
  110. sess = pullRequestsForActivityStatement(repoID, fromTime, true)
  111. if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("pull_request").Get(&count); err != nil {
  112. return err
  113. }
  114. stats.MergedPRAuthorCount = count
  115. // Opened pull requests
  116. sess = pullRequestsForActivityStatement(repoID, fromTime, false)
  117. sess.OrderBy("issue.created_unix ASC")
  118. stats.OpenedPRs = make(PullRequestList, 0)
  119. if err = sess.Find(&stats.OpenedPRs); err != nil {
  120. return err
  121. }
  122. if err = stats.OpenedPRs.LoadAttributes(); err != nil {
  123. return err
  124. }
  125. // Opened pull request authors
  126. sess = pullRequestsForActivityStatement(repoID, fromTime, false)
  127. if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("pull_request").Get(&count); err != nil {
  128. return err
  129. }
  130. stats.OpenedPRAuthorCount = count
  131. return nil
  132. }
  133. func pullRequestsForActivityStatement(repoID int64, fromTime time.Time, merged bool) *xorm.Session {
  134. sess := x.Where("pull_request.base_repo_id=?", repoID).
  135. Join("INNER", "issue", "pull_request.issue_id = issue.id")
  136. if merged {
  137. sess.And("pull_request.has_merged = ?", true)
  138. sess.And("pull_request.merged_unix >= ?", fromTime.Unix())
  139. } else {
  140. sess.And("issue.is_closed = ?", false)
  141. sess.And("issue.created_unix >= ?", fromTime.Unix())
  142. }
  143. return sess
  144. }
  145. // FillIssues returns issue information for activity page
  146. func (stats *ActivityStats) FillIssues(repoID int64, fromTime time.Time) error {
  147. var err error
  148. var count int64
  149. // Closed issues
  150. sess := issuesForActivityStatement(repoID, fromTime, true, false)
  151. sess.OrderBy("issue.closed_unix DESC")
  152. stats.ClosedIssues = make(IssueList, 0)
  153. if err = sess.Find(&stats.ClosedIssues); err != nil {
  154. return err
  155. }
  156. // Closed issue authors
  157. sess = issuesForActivityStatement(repoID, fromTime, true, false)
  158. if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil {
  159. return err
  160. }
  161. stats.ClosedIssueAuthorCount = count
  162. // New issues
  163. sess = issuesForActivityStatement(repoID, fromTime, false, false)
  164. sess.OrderBy("issue.created_unix ASC")
  165. stats.OpenedIssues = make(IssueList, 0)
  166. if err = sess.Find(&stats.OpenedIssues); err != nil {
  167. return err
  168. }
  169. // Opened issue authors
  170. sess = issuesForActivityStatement(repoID, fromTime, false, false)
  171. if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil {
  172. return err
  173. }
  174. stats.OpenedIssueAuthorCount = count
  175. return nil
  176. }
  177. // FillUnresolvedIssues returns unresolved issue and pull request information for activity page
  178. func (stats *ActivityStats) FillUnresolvedIssues(repoID int64, fromTime time.Time, issues, prs bool) error {
  179. // Check if we need to select anything
  180. if !issues && !prs {
  181. return nil
  182. }
  183. sess := issuesForActivityStatement(repoID, fromTime, false, true)
  184. if !issues || !prs {
  185. sess.And("issue.is_pull = ?", prs)
  186. }
  187. sess.OrderBy("issue.updated_unix DESC")
  188. stats.UnresolvedIssues = make(IssueList, 0)
  189. return sess.Find(&stats.UnresolvedIssues)
  190. }
  191. func issuesForActivityStatement(repoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session {
  192. sess := x.Where("issue.repo_id = ?", repoID).
  193. And("issue.is_closed = ?", closed)
  194. if !unresolved {
  195. sess.And("issue.is_pull = ?", false)
  196. if closed {
  197. sess.And("issue.closed_unix >= ?", fromTime.Unix())
  198. } else {
  199. sess.And("issue.created_unix >= ?", fromTime.Unix())
  200. }
  201. } else {
  202. sess.And("issue.created_unix < ?", fromTime.Unix())
  203. sess.And("issue.updated_unix >= ?", fromTime.Unix())
  204. }
  205. return sess
  206. }
  207. // FillReleases returns release information for activity page
  208. func (stats *ActivityStats) FillReleases(repoID int64, fromTime time.Time) error {
  209. var err error
  210. var count int64
  211. // Published releases list
  212. sess := releasesForActivityStatement(repoID, fromTime)
  213. sess.OrderBy("release.created_unix DESC")
  214. stats.PublishedReleases = make([]*Release, 0)
  215. if err = sess.Find(&stats.PublishedReleases); err != nil {
  216. return err
  217. }
  218. // Published releases authors
  219. sess = releasesForActivityStatement(repoID, fromTime)
  220. if _, err = sess.Select("count(distinct release.publisher_id) as `count`").Table("release").Get(&count); err != nil {
  221. return err
  222. }
  223. stats.PublishedReleaseAuthorCount = count
  224. return nil
  225. }
  226. func releasesForActivityStatement(repoID int64, fromTime time.Time) *xorm.Session {
  227. return x.Where("release.repo_id = ?", repoID).
  228. And("release.is_draft = ?", false).
  229. And("release.created_unix >= ?", fromTime.Unix())
  230. }