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

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