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 10KB

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