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

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