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.

issue_list.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  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. "code.gitea.io/gitea/models/db"
  8. repo_model "code.gitea.io/gitea/models/repo"
  9. user_model "code.gitea.io/gitea/models/user"
  10. "xorm.io/builder"
  11. )
  12. // IssueList defines a list of issues
  13. type IssueList []*Issue
  14. const (
  15. // default variables number on IN () in SQL
  16. defaultMaxInSize = 50
  17. )
  18. func (issues IssueList) getRepoIDs() []int64 {
  19. repoIDs := make(map[int64]struct{}, len(issues))
  20. for _, issue := range issues {
  21. if _, ok := repoIDs[issue.RepoID]; !ok {
  22. repoIDs[issue.RepoID] = struct{}{}
  23. }
  24. }
  25. return keysInt64(repoIDs)
  26. }
  27. func (issues IssueList) loadRepositories(e db.Engine) ([]*Repository, error) {
  28. if len(issues) == 0 {
  29. return nil, nil
  30. }
  31. repoIDs := issues.getRepoIDs()
  32. repoMaps := make(map[int64]*Repository, len(repoIDs))
  33. left := len(repoIDs)
  34. for left > 0 {
  35. limit := defaultMaxInSize
  36. if left < limit {
  37. limit = left
  38. }
  39. err := e.
  40. In("id", repoIDs[:limit]).
  41. Find(&repoMaps)
  42. if err != nil {
  43. return nil, fmt.Errorf("find repository: %v", err)
  44. }
  45. left -= limit
  46. repoIDs = repoIDs[limit:]
  47. }
  48. for _, issue := range issues {
  49. issue.Repo = repoMaps[issue.RepoID]
  50. if issue.PullRequest != nil {
  51. issue.PullRequest.BaseRepo = issue.Repo
  52. }
  53. }
  54. return valuesRepository(repoMaps), nil
  55. }
  56. // LoadRepositories loads issues' all repositories
  57. func (issues IssueList) LoadRepositories() ([]*Repository, error) {
  58. return issues.loadRepositories(db.GetEngine(db.DefaultContext))
  59. }
  60. func (issues IssueList) getPosterIDs() []int64 {
  61. posterIDs := make(map[int64]struct{}, len(issues))
  62. for _, issue := range issues {
  63. if _, ok := posterIDs[issue.PosterID]; !ok {
  64. posterIDs[issue.PosterID] = struct{}{}
  65. }
  66. }
  67. return keysInt64(posterIDs)
  68. }
  69. func (issues IssueList) loadPosters(e db.Engine) error {
  70. if len(issues) == 0 {
  71. return nil
  72. }
  73. posterIDs := issues.getPosterIDs()
  74. posterMaps := make(map[int64]*user_model.User, len(posterIDs))
  75. left := len(posterIDs)
  76. for left > 0 {
  77. limit := defaultMaxInSize
  78. if left < limit {
  79. limit = left
  80. }
  81. err := e.
  82. In("id", posterIDs[:limit]).
  83. Find(&posterMaps)
  84. if err != nil {
  85. return err
  86. }
  87. left -= limit
  88. posterIDs = posterIDs[limit:]
  89. }
  90. for _, issue := range issues {
  91. if issue.PosterID <= 0 {
  92. continue
  93. }
  94. var ok bool
  95. if issue.Poster, ok = posterMaps[issue.PosterID]; !ok {
  96. issue.Poster = user_model.NewGhostUser()
  97. }
  98. }
  99. return nil
  100. }
  101. func (issues IssueList) getIssueIDs() []int64 {
  102. ids := make([]int64, 0, len(issues))
  103. for _, issue := range issues {
  104. ids = append(ids, issue.ID)
  105. }
  106. return ids
  107. }
  108. func (issues IssueList) loadLabels(e db.Engine) error {
  109. if len(issues) == 0 {
  110. return nil
  111. }
  112. type LabelIssue struct {
  113. Label *Label `xorm:"extends"`
  114. IssueLabel *IssueLabel `xorm:"extends"`
  115. }
  116. issueLabels := make(map[int64][]*Label, len(issues)*3)
  117. issueIDs := issues.getIssueIDs()
  118. left := len(issueIDs)
  119. for left > 0 {
  120. limit := defaultMaxInSize
  121. if left < limit {
  122. limit = left
  123. }
  124. rows, err := e.Table("label").
  125. Join("LEFT", "issue_label", "issue_label.label_id = label.id").
  126. In("issue_label.issue_id", issueIDs[:limit]).
  127. Asc("label.name").
  128. Rows(new(LabelIssue))
  129. if err != nil {
  130. return err
  131. }
  132. for rows.Next() {
  133. var labelIssue LabelIssue
  134. err = rows.Scan(&labelIssue)
  135. if err != nil {
  136. if err1 := rows.Close(); err1 != nil {
  137. return fmt.Errorf("IssueList.loadLabels: Close: %v", err1)
  138. }
  139. return err
  140. }
  141. issueLabels[labelIssue.IssueLabel.IssueID] = append(issueLabels[labelIssue.IssueLabel.IssueID], labelIssue.Label)
  142. }
  143. // When there are no rows left and we try to close it.
  144. // Since that is not relevant for us, we can safely ignore it.
  145. if err1 := rows.Close(); err1 != nil {
  146. return fmt.Errorf("IssueList.loadLabels: Close: %v", err1)
  147. }
  148. left -= limit
  149. issueIDs = issueIDs[limit:]
  150. }
  151. for _, issue := range issues {
  152. issue.Labels = issueLabels[issue.ID]
  153. }
  154. return nil
  155. }
  156. func (issues IssueList) getMilestoneIDs() []int64 {
  157. ids := make(map[int64]struct{}, len(issues))
  158. for _, issue := range issues {
  159. if _, ok := ids[issue.MilestoneID]; !ok {
  160. ids[issue.MilestoneID] = struct{}{}
  161. }
  162. }
  163. return keysInt64(ids)
  164. }
  165. func (issues IssueList) loadMilestones(e db.Engine) error {
  166. milestoneIDs := issues.getMilestoneIDs()
  167. if len(milestoneIDs) == 0 {
  168. return nil
  169. }
  170. milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
  171. left := len(milestoneIDs)
  172. for left > 0 {
  173. limit := defaultMaxInSize
  174. if left < limit {
  175. limit = left
  176. }
  177. err := e.
  178. In("id", milestoneIDs[:limit]).
  179. Find(&milestoneMaps)
  180. if err != nil {
  181. return err
  182. }
  183. left -= limit
  184. milestoneIDs = milestoneIDs[limit:]
  185. }
  186. for _, issue := range issues {
  187. issue.Milestone = milestoneMaps[issue.MilestoneID]
  188. }
  189. return nil
  190. }
  191. func (issues IssueList) loadAssignees(e db.Engine) error {
  192. if len(issues) == 0 {
  193. return nil
  194. }
  195. type AssigneeIssue struct {
  196. IssueAssignee *IssueAssignees `xorm:"extends"`
  197. Assignee *user_model.User `xorm:"extends"`
  198. }
  199. assignees := make(map[int64][]*user_model.User, len(issues))
  200. issueIDs := issues.getIssueIDs()
  201. left := len(issueIDs)
  202. for left > 0 {
  203. limit := defaultMaxInSize
  204. if left < limit {
  205. limit = left
  206. }
  207. rows, err := e.Table("issue_assignees").
  208. Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id").
  209. In("`issue_assignees`.issue_id", issueIDs[:limit]).
  210. Rows(new(AssigneeIssue))
  211. if err != nil {
  212. return err
  213. }
  214. for rows.Next() {
  215. var assigneeIssue AssigneeIssue
  216. err = rows.Scan(&assigneeIssue)
  217. if err != nil {
  218. if err1 := rows.Close(); err1 != nil {
  219. return fmt.Errorf("IssueList.loadAssignees: Close: %v", err1)
  220. }
  221. return err
  222. }
  223. assignees[assigneeIssue.IssueAssignee.IssueID] = append(assignees[assigneeIssue.IssueAssignee.IssueID], assigneeIssue.Assignee)
  224. }
  225. if err1 := rows.Close(); err1 != nil {
  226. return fmt.Errorf("IssueList.loadAssignees: Close: %v", err1)
  227. }
  228. left -= limit
  229. issueIDs = issueIDs[limit:]
  230. }
  231. for _, issue := range issues {
  232. issue.Assignees = assignees[issue.ID]
  233. }
  234. return nil
  235. }
  236. func (issues IssueList) getPullIssueIDs() []int64 {
  237. ids := make([]int64, 0, len(issues))
  238. for _, issue := range issues {
  239. if issue.IsPull && issue.PullRequest == nil {
  240. ids = append(ids, issue.ID)
  241. }
  242. }
  243. return ids
  244. }
  245. func (issues IssueList) loadPullRequests(e db.Engine) error {
  246. issuesIDs := issues.getPullIssueIDs()
  247. if len(issuesIDs) == 0 {
  248. return nil
  249. }
  250. pullRequestMaps := make(map[int64]*PullRequest, len(issuesIDs))
  251. left := len(issuesIDs)
  252. for left > 0 {
  253. limit := defaultMaxInSize
  254. if left < limit {
  255. limit = left
  256. }
  257. rows, err := e.
  258. In("issue_id", issuesIDs[:limit]).
  259. Rows(new(PullRequest))
  260. if err != nil {
  261. return err
  262. }
  263. for rows.Next() {
  264. var pr PullRequest
  265. err = rows.Scan(&pr)
  266. if err != nil {
  267. if err1 := rows.Close(); err1 != nil {
  268. return fmt.Errorf("IssueList.loadPullRequests: Close: %v", err1)
  269. }
  270. return err
  271. }
  272. pullRequestMaps[pr.IssueID] = &pr
  273. }
  274. if err1 := rows.Close(); err1 != nil {
  275. return fmt.Errorf("IssueList.loadPullRequests: Close: %v", err1)
  276. }
  277. left -= limit
  278. issuesIDs = issuesIDs[limit:]
  279. }
  280. for _, issue := range issues {
  281. issue.PullRequest = pullRequestMaps[issue.ID]
  282. }
  283. return nil
  284. }
  285. func (issues IssueList) loadAttachments(e db.Engine) (err error) {
  286. if len(issues) == 0 {
  287. return nil
  288. }
  289. attachments := make(map[int64][]*repo_model.Attachment, len(issues))
  290. issuesIDs := issues.getIssueIDs()
  291. left := len(issuesIDs)
  292. for left > 0 {
  293. limit := defaultMaxInSize
  294. if left < limit {
  295. limit = left
  296. }
  297. rows, err := e.Table("attachment").
  298. Join("INNER", "issue", "issue.id = attachment.issue_id").
  299. In("issue.id", issuesIDs[:limit]).
  300. Rows(new(repo_model.Attachment))
  301. if err != nil {
  302. return err
  303. }
  304. for rows.Next() {
  305. var attachment repo_model.Attachment
  306. err = rows.Scan(&attachment)
  307. if err != nil {
  308. if err1 := rows.Close(); err1 != nil {
  309. return fmt.Errorf("IssueList.loadAttachments: Close: %v", err1)
  310. }
  311. return err
  312. }
  313. attachments[attachment.IssueID] = append(attachments[attachment.IssueID], &attachment)
  314. }
  315. if err1 := rows.Close(); err1 != nil {
  316. return fmt.Errorf("IssueList.loadAttachments: Close: %v", err1)
  317. }
  318. left -= limit
  319. issuesIDs = issuesIDs[limit:]
  320. }
  321. for _, issue := range issues {
  322. issue.Attachments = attachments[issue.ID]
  323. }
  324. return nil
  325. }
  326. func (issues IssueList) loadComments(e db.Engine, cond builder.Cond) (err error) {
  327. if len(issues) == 0 {
  328. return nil
  329. }
  330. comments := make(map[int64][]*Comment, len(issues))
  331. issuesIDs := issues.getIssueIDs()
  332. left := len(issuesIDs)
  333. for left > 0 {
  334. limit := defaultMaxInSize
  335. if left < limit {
  336. limit = left
  337. }
  338. rows, err := e.Table("comment").
  339. Join("INNER", "issue", "issue.id = comment.issue_id").
  340. In("issue.id", issuesIDs[:limit]).
  341. Where(cond).
  342. Rows(new(Comment))
  343. if err != nil {
  344. return err
  345. }
  346. for rows.Next() {
  347. var comment Comment
  348. err = rows.Scan(&comment)
  349. if err != nil {
  350. if err1 := rows.Close(); err1 != nil {
  351. return fmt.Errorf("IssueList.loadComments: Close: %v", err1)
  352. }
  353. return err
  354. }
  355. comments[comment.IssueID] = append(comments[comment.IssueID], &comment)
  356. }
  357. if err1 := rows.Close(); err1 != nil {
  358. return fmt.Errorf("IssueList.loadComments: Close: %v", err1)
  359. }
  360. left -= limit
  361. issuesIDs = issuesIDs[limit:]
  362. }
  363. for _, issue := range issues {
  364. issue.Comments = comments[issue.ID]
  365. }
  366. return nil
  367. }
  368. func (issues IssueList) loadTotalTrackedTimes(e db.Engine) (err error) {
  369. type totalTimesByIssue struct {
  370. IssueID int64
  371. Time int64
  372. }
  373. if len(issues) == 0 {
  374. return nil
  375. }
  376. trackedTimes := make(map[int64]int64, len(issues))
  377. ids := make([]int64, 0, len(issues))
  378. for _, issue := range issues {
  379. if issue.Repo.IsTimetrackerEnabled() {
  380. ids = append(ids, issue.ID)
  381. }
  382. }
  383. left := len(ids)
  384. for left > 0 {
  385. limit := defaultMaxInSize
  386. if left < limit {
  387. limit = left
  388. }
  389. // select issue_id, sum(time) from tracked_time where issue_id in (<issue ids in current page>) group by issue_id
  390. rows, err := e.Table("tracked_time").
  391. Where("deleted = ?", false).
  392. Select("issue_id, sum(time) as time").
  393. In("issue_id", ids[:limit]).
  394. GroupBy("issue_id").
  395. Rows(new(totalTimesByIssue))
  396. if err != nil {
  397. return err
  398. }
  399. for rows.Next() {
  400. var totalTime totalTimesByIssue
  401. err = rows.Scan(&totalTime)
  402. if err != nil {
  403. if err1 := rows.Close(); err1 != nil {
  404. return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %v", err1)
  405. }
  406. return err
  407. }
  408. trackedTimes[totalTime.IssueID] = totalTime.Time
  409. }
  410. if err1 := rows.Close(); err1 != nil {
  411. return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %v", err1)
  412. }
  413. left -= limit
  414. ids = ids[limit:]
  415. }
  416. for _, issue := range issues {
  417. issue.TotalTrackedTime = trackedTimes[issue.ID]
  418. }
  419. return nil
  420. }
  421. // loadAttributes loads all attributes, expect for attachments and comments
  422. func (issues IssueList) loadAttributes(e db.Engine) error {
  423. if _, err := issues.loadRepositories(e); err != nil {
  424. return fmt.Errorf("issue.loadAttributes: loadRepositories: %v", err)
  425. }
  426. if err := issues.loadPosters(e); err != nil {
  427. return fmt.Errorf("issue.loadAttributes: loadPosters: %v", err)
  428. }
  429. if err := issues.loadLabels(e); err != nil {
  430. return fmt.Errorf("issue.loadAttributes: loadLabels: %v", err)
  431. }
  432. if err := issues.loadMilestones(e); err != nil {
  433. return fmt.Errorf("issue.loadAttributes: loadMilestones: %v", err)
  434. }
  435. if err := issues.loadAssignees(e); err != nil {
  436. return fmt.Errorf("issue.loadAttributes: loadAssignees: %v", err)
  437. }
  438. if err := issues.loadPullRequests(e); err != nil {
  439. return fmt.Errorf("issue.loadAttributes: loadPullRequests: %v", err)
  440. }
  441. if err := issues.loadTotalTrackedTimes(e); err != nil {
  442. return fmt.Errorf("issue.loadAttributes: loadTotalTrackedTimes: %v", err)
  443. }
  444. return nil
  445. }
  446. // LoadAttributes loads attributes of the issues, except for attachments and
  447. // comments
  448. func (issues IssueList) LoadAttributes() error {
  449. return issues.loadAttributes(db.GetEngine(db.DefaultContext))
  450. }
  451. // LoadAttachments loads attachments
  452. func (issues IssueList) LoadAttachments() error {
  453. return issues.loadAttachments(db.GetEngine(db.DefaultContext))
  454. }
  455. // LoadComments loads comments
  456. func (issues IssueList) LoadComments() error {
  457. return issues.loadComments(db.GetEngine(db.DefaultContext), builder.NewCond())
  458. }
  459. // LoadDiscussComments loads discuss comments
  460. func (issues IssueList) LoadDiscussComments() error {
  461. return issues.loadComments(db.GetEngine(db.DefaultContext), builder.Eq{"comment.type": CommentTypeComment})
  462. }
  463. // LoadPullRequests loads pull requests
  464. func (issues IssueList) LoadPullRequests() error {
  465. return issues.loadPullRequests(db.GetEngine(db.DefaultContext))
  466. }
  467. // GetApprovalCounts returns a map of issue ID to slice of approval counts
  468. // FIXME: only returns official counts due to double counting of non-official approvals
  469. func (issues IssueList) GetApprovalCounts() (map[int64][]*ReviewCount, error) {
  470. return issues.getApprovalCounts(db.GetEngine(db.DefaultContext))
  471. }
  472. func (issues IssueList) getApprovalCounts(e db.Engine) (map[int64][]*ReviewCount, error) {
  473. rCounts := make([]*ReviewCount, 0, 2*len(issues))
  474. ids := make([]int64, len(issues))
  475. for i, issue := range issues {
  476. ids[i] = issue.ID
  477. }
  478. sess := e.In("issue_id", ids)
  479. err := sess.Select("issue_id, type, count(id) as `count`").
  480. Where("official = ? AND dismissed = ?", true, false).
  481. GroupBy("issue_id, type").
  482. OrderBy("issue_id").
  483. Table("review").
  484. Find(&rCounts)
  485. if err != nil {
  486. return nil, err
  487. }
  488. approvalCountMap := make(map[int64][]*ReviewCount, len(issues))
  489. for _, c := range rCounts {
  490. approvalCountMap[c.IssueID] = append(approvalCountMap[c.IssueID], c)
  491. }
  492. return approvalCountMap, nil
  493. }