issue.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. // Copyright 2014 The Gogs 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. "errors"
  7. "strings"
  8. "time"
  9. )
  10. var (
  11. ErrIssueNotExist = errors.New("Issue does not exist")
  12. )
  13. // Issue represents an issue or pull request of repository.
  14. type Issue struct {
  15. Id int64
  16. Index int64 // Index in one repository.
  17. Name string
  18. RepoId int64 `xorm:"INDEX"`
  19. Repo *Repository `xorm:"-"`
  20. PosterId int64
  21. Poster *User `xorm:"-"`
  22. MilestoneId int64
  23. AssigneeId int64
  24. IsPull bool // Indicates whether is a pull request or not.
  25. IsClosed bool
  26. Labels string `xorm:"TEXT"`
  27. Content string `xorm:"TEXT"`
  28. RenderedContent string `xorm:"-"`
  29. Priority int
  30. NumComments int
  31. Deadline time.Time
  32. Created time.Time `xorm:"CREATED"`
  33. Updated time.Time `xorm:"UPDATED"`
  34. }
  35. func (i *Issue) GetPoster() (err error) {
  36. i.Poster, err = GetUserById(i.PosterId)
  37. return err
  38. }
  39. // IssseUser represents an issue-user relation.
  40. type IssseUser struct {
  41. Id int64
  42. Iid int64 // Issue ID.
  43. Rid int64 // Repository ID.
  44. Uid int64 // User ID.
  45. IsRead bool
  46. IsAssigned bool
  47. IsMentioned bool
  48. IsClosed bool
  49. }
  50. // CreateIssue creates new issue for repository.
  51. func NewIssue(issue *Issue) (err error) {
  52. sess := orm.NewSession()
  53. defer sess.Close()
  54. if err = sess.Begin(); err != nil {
  55. return err
  56. }
  57. if _, err = sess.Insert(issue); err != nil {
  58. sess.Rollback()
  59. return err
  60. }
  61. rawSql := "UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?"
  62. if _, err = sess.Exec(rawSql, issue.RepoId); err != nil {
  63. sess.Rollback()
  64. return err
  65. }
  66. return sess.Commit()
  67. }
  68. // GetIssueByIndex returns issue by given index in repository.
  69. func GetIssueByIndex(rid, index int64) (*Issue, error) {
  70. issue := &Issue{RepoId: rid, Index: index}
  71. has, err := orm.Get(issue)
  72. if err != nil {
  73. return nil, err
  74. } else if !has {
  75. return nil, ErrIssueNotExist
  76. }
  77. return issue, nil
  78. }
  79. // GetIssues returns a list of issues by given conditions.
  80. func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labels, sortType string) ([]Issue, error) {
  81. sess := orm.Limit(20, (page-1)*20)
  82. if rid > 0 {
  83. sess.Where("repo_id=?", rid).And("is_closed=?", isClosed)
  84. } else {
  85. sess.Where("is_closed=?", isClosed)
  86. }
  87. if uid > 0 {
  88. sess.And("assignee_id=?", uid)
  89. } else if pid > 0 {
  90. sess.And("poster_id=?", pid)
  91. }
  92. if mid > 0 {
  93. sess.And("milestone_id=?", mid)
  94. }
  95. if len(labels) > 0 {
  96. for _, label := range strings.Split(labels, ",") {
  97. sess.And("labels like '%$" + label + "|%'")
  98. }
  99. }
  100. switch sortType {
  101. case "oldest":
  102. sess.Asc("created")
  103. case "recentupdate":
  104. sess.Desc("updated")
  105. case "leastupdate":
  106. sess.Asc("updated")
  107. case "mostcomment":
  108. sess.Desc("num_comments")
  109. case "leastcomment":
  110. sess.Asc("num_comments")
  111. default:
  112. sess.Desc("created")
  113. }
  114. var issues []Issue
  115. err := sess.Find(&issues)
  116. return issues, err
  117. }
  118. // PairsContains returns true when pairs list contains given issue.
  119. func PairsContains(ius []*IssseUser, issueId int64) bool {
  120. for i := range ius {
  121. if ius[i].Iid == issueId {
  122. return true
  123. }
  124. }
  125. return false
  126. }
  127. // GetIssueUserPairs returns all issue-user pairs by given repository and user.
  128. func GetIssueUserPairs(rid, uid int64, isClosed bool) ([]*IssseUser, error) {
  129. ius := make([]*IssseUser, 0, 10)
  130. err := orm.Find(&ius, &IssseUser{Rid: rid, Uid: uid, IsClosed: isClosed})
  131. return ius, err
  132. }
  133. // GetUserIssueCount returns the number of issues that were created by given user in repository.
  134. func GetUserIssueCount(uid, rid int64) int64 {
  135. count, _ := orm.Where("poster_id=?", uid).And("repo_id=?", rid).Count(new(Issue))
  136. return count
  137. }
  138. // IssueStats represents issue statistic information.
  139. type IssueStats struct {
  140. OpenCount, ClosedCount int64
  141. AllCount int64
  142. AssignCount int64
  143. CreateCount int64
  144. MentionCount int64
  145. }
  146. // Filter modes.
  147. const (
  148. FM_ASSIGN = iota + 1
  149. FM_CREATE
  150. FM_MENTION
  151. )
  152. // GetIssueStats returns issue statistic information by given condition.
  153. func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStats {
  154. stats := &IssueStats{}
  155. issue := new(Issue)
  156. sess := orm.Where("repo_id=?", rid)
  157. tmpSess := sess
  158. stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
  159. *tmpSess = *sess
  160. stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
  161. if isShowClosed {
  162. stats.AllCount = stats.ClosedCount
  163. } else {
  164. stats.AllCount = stats.OpenCount
  165. }
  166. if filterMode != FM_MENTION {
  167. sess = orm.Where("repo_id=?", rid)
  168. switch filterMode {
  169. case FM_ASSIGN:
  170. sess.And("assignee_id=?", uid)
  171. case FM_CREATE:
  172. sess.And("poster_id=?", uid)
  173. default:
  174. goto nofilter
  175. }
  176. *tmpSess = *sess
  177. stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
  178. *tmpSess = *sess
  179. stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
  180. } else {
  181. sess := orm.Where("rid=?", rid).And("uid=?", uid).And("is_mentioned=?", true)
  182. tmpSess := sess
  183. stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(new(IssseUser))
  184. *tmpSess = *sess
  185. stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(new(IssseUser))
  186. }
  187. nofilter:
  188. stats.AssignCount, _ = orm.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("assignee_id=?", uid).Count(issue)
  189. stats.CreateCount, _ = orm.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("poster_id=?", uid).Count(issue)
  190. stats.MentionCount, _ = orm.Where("rid=?", rid).And("uid=?", uid).And("is_closed=?", isShowClosed).And("is_mentioned=?", true).Count(new(IssseUser))
  191. return stats
  192. }
  193. // GetUserIssueStats returns issue statistic information for dashboard by given condition.
  194. func GetUserIssueStats(uid int64, filterMode int) *IssueStats {
  195. stats := &IssueStats{}
  196. issue := new(Issue)
  197. iu := new(IssseUser)
  198. sess := orm.Where("uid=?", uid)
  199. tmpSess := sess
  200. if filterMode == 0 {
  201. stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(iu)
  202. *tmpSess = *sess
  203. stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(iu)
  204. }
  205. switch filterMode {
  206. case FM_ASSIGN:
  207. sess.And("is_assigned=?", true)
  208. *tmpSess = *sess
  209. stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(iu)
  210. *tmpSess = *sess
  211. stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(iu)
  212. case FM_CREATE:
  213. sess.Where("poster_id=?", uid)
  214. *tmpSess = *sess
  215. stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
  216. *tmpSess = *sess
  217. stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
  218. }
  219. stats.AssignCount, _ = orm.Where("assignee_id=?", uid).And("is_closed=?", false).Count(issue)
  220. stats.CreateCount, _ = orm.Where("poster_id=?", uid).And("is_closed=?", false).Count(issue)
  221. return stats
  222. }
  223. // UpdateIssue updates information of issue.
  224. func UpdateIssue(issue *Issue) error {
  225. _, err := orm.AllCols().Update(issue)
  226. return err
  227. }
  228. // Label represents a list of labels of repository for issues.
  229. type Label struct {
  230. Id int64
  231. RepoId int64 `xorm:"INDEX"`
  232. Names string
  233. Colors string
  234. }
  235. // Milestone represents a milestone of repository.
  236. type Milestone struct {
  237. Id int64
  238. Name string
  239. RepoId int64 `xorm:"INDEX"`
  240. IsClosed bool
  241. Content string
  242. NumIssues int
  243. DueDate time.Time
  244. Created time.Time `xorm:"CREATED"`
  245. }
  246. // Issue types.
  247. const (
  248. IT_PLAIN = iota // Pure comment.
  249. IT_REOPEN // Issue reopen status change prompt.
  250. IT_CLOSE // Issue close status change prompt.
  251. )
  252. // Comment represents a comment in commit and issue page.
  253. type Comment struct {
  254. Id int64
  255. Type int
  256. PosterId int64
  257. Poster *User `xorm:"-"`
  258. IssueId int64
  259. CommitId int64
  260. Line int64
  261. Content string
  262. Created time.Time `xorm:"CREATED"`
  263. }
  264. // CreateComment creates comment of issue or commit.
  265. func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType int, content string) error {
  266. sess := orm.NewSession()
  267. defer sess.Close()
  268. sess.Begin()
  269. if _, err := sess.Insert(&Comment{PosterId: userId, Type: cmtType, IssueId: issueId,
  270. CommitId: commitId, Line: line, Content: content}); err != nil {
  271. sess.Rollback()
  272. return err
  273. }
  274. // Check comment type.
  275. switch cmtType {
  276. case IT_PLAIN:
  277. rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
  278. if _, err := sess.Exec(rawSql, issueId); err != nil {
  279. sess.Rollback()
  280. return err
  281. }
  282. case IT_REOPEN:
  283. rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?"
  284. if _, err := sess.Exec(rawSql, repoId); err != nil {
  285. sess.Rollback()
  286. return err
  287. }
  288. case IT_CLOSE:
  289. rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?"
  290. if _, err := sess.Exec(rawSql, repoId); err != nil {
  291. sess.Rollback()
  292. return err
  293. }
  294. }
  295. return sess.Commit()
  296. }
  297. // GetIssueComments returns list of comment by given issue id.
  298. func GetIssueComments(issueId int64) ([]Comment, error) {
  299. comments := make([]Comment, 0, 10)
  300. err := orm.Asc("created").Find(&comments, &Comment{IssueId: issueId})
  301. return comments, err
  302. }