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.

273 lines
7.8 KiB

  1. // Copyright 2018 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 repo
  5. import (
  6. "time"
  7. "code.gitea.io/gitea/models"
  8. "code.gitea.io/gitea/modules/auth"
  9. "code.gitea.io/gitea/modules/base"
  10. "code.gitea.io/gitea/modules/context"
  11. "code.gitea.io/gitea/modules/markup/markdown"
  12. "code.gitea.io/gitea/modules/setting"
  13. "code.gitea.io/gitea/modules/timeutil"
  14. "code.gitea.io/gitea/modules/util"
  15. "xorm.io/builder"
  16. )
  17. const (
  18. tplMilestone base.TplName = "repo/issue/milestones"
  19. tplMilestoneNew base.TplName = "repo/issue/milestone_new"
  20. tplMilestoneIssues base.TplName = "repo/issue/milestone_issues"
  21. )
  22. // Milestones render milestones page
  23. func Milestones(ctx *context.Context) {
  24. ctx.Data["Title"] = ctx.Tr("repo.milestones")
  25. ctx.Data["PageIsIssueList"] = true
  26. ctx.Data["PageIsMilestones"] = true
  27. isShowClosed := ctx.Query("state") == "closed"
  28. stats, err := models.GetMilestonesStatsByRepoCond(builder.And(builder.Eq{"id": ctx.Repo.Repository.ID}))
  29. if err != nil {
  30. ctx.ServerError("MilestoneStats", err)
  31. return
  32. }
  33. ctx.Data["OpenCount"] = stats.OpenCount
  34. ctx.Data["ClosedCount"] = stats.ClosedCount
  35. sortType := ctx.Query("sort")
  36. page := ctx.QueryInt("page")
  37. if page <= 1 {
  38. page = 1
  39. }
  40. var total int
  41. if !isShowClosed {
  42. total = int(stats.OpenCount)
  43. } else {
  44. total = int(stats.ClosedCount)
  45. }
  46. miles, err := models.GetMilestones(ctx.Repo.Repository.ID, page, isShowClosed, sortType)
  47. if err != nil {
  48. ctx.ServerError("GetMilestones", err)
  49. return
  50. }
  51. if ctx.Repo.Repository.IsTimetrackerEnabled() {
  52. if err := miles.LoadTotalTrackedTimes(); err != nil {
  53. ctx.ServerError("LoadTotalTrackedTimes", err)
  54. return
  55. }
  56. }
  57. for _, m := range miles {
  58. m.RenderedContent = string(markdown.Render([]byte(m.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
  59. }
  60. ctx.Data["Milestones"] = miles
  61. if isShowClosed {
  62. ctx.Data["State"] = "closed"
  63. } else {
  64. ctx.Data["State"] = "open"
  65. }
  66. ctx.Data["SortType"] = sortType
  67. ctx.Data["IsShowClosed"] = isShowClosed
  68. pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5)
  69. pager.AddParam(ctx, "state", "State")
  70. ctx.Data["Page"] = pager
  71. ctx.HTML(200, tplMilestone)
  72. }
  73. // NewMilestone render creating milestone page
  74. func NewMilestone(ctx *context.Context) {
  75. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  76. ctx.Data["PageIsIssueList"] = true
  77. ctx.Data["PageIsMilestones"] = true
  78. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  79. ctx.HTML(200, tplMilestoneNew)
  80. }
  81. // NewMilestonePost response for creating milestone
  82. func NewMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
  83. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  84. ctx.Data["PageIsIssueList"] = true
  85. ctx.Data["PageIsMilestones"] = true
  86. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  87. if ctx.HasError() {
  88. ctx.HTML(200, tplMilestoneNew)
  89. return
  90. }
  91. if len(form.Deadline) == 0 {
  92. form.Deadline = "9999-12-31"
  93. }
  94. deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
  95. if err != nil {
  96. ctx.Data["Err_Deadline"] = true
  97. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
  98. return
  99. }
  100. deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location())
  101. if err = models.NewMilestone(&models.Milestone{
  102. RepoID: ctx.Repo.Repository.ID,
  103. Name: form.Title,
  104. Content: form.Content,
  105. DeadlineUnix: timeutil.TimeStamp(deadline.Unix()),
  106. }); err != nil {
  107. ctx.ServerError("NewMilestone", err)
  108. return
  109. }
  110. ctx.Flash.Success(ctx.Tr("repo.milestones.create_success", form.Title))
  111. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  112. }
  113. // EditMilestone render edting milestone page
  114. func EditMilestone(ctx *context.Context) {
  115. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  116. ctx.Data["PageIsMilestones"] = true
  117. ctx.Data["PageIsEditMilestone"] = true
  118. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  119. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  120. if err != nil {
  121. if models.IsErrMilestoneNotExist(err) {
  122. ctx.NotFound("", nil)
  123. } else {
  124. ctx.ServerError("GetMilestoneByRepoID", err)
  125. }
  126. return
  127. }
  128. ctx.Data["title"] = m.Name
  129. ctx.Data["content"] = m.Content
  130. if len(m.DeadlineString) > 0 {
  131. ctx.Data["deadline"] = m.DeadlineString
  132. }
  133. ctx.HTML(200, tplMilestoneNew)
  134. }
  135. // EditMilestonePost response for edting milestone
  136. func EditMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
  137. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  138. ctx.Data["PageIsMilestones"] = true
  139. ctx.Data["PageIsEditMilestone"] = true
  140. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  141. if ctx.HasError() {
  142. ctx.HTML(200, tplMilestoneNew)
  143. return
  144. }
  145. if len(form.Deadline) == 0 {
  146. form.Deadline = "9999-12-31"
  147. }
  148. deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
  149. if err != nil {
  150. ctx.Data["Err_Deadline"] = true
  151. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
  152. return
  153. }
  154. deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location())
  155. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  156. if err != nil {
  157. if models.IsErrMilestoneNotExist(err) {
  158. ctx.NotFound("", nil)
  159. } else {
  160. ctx.ServerError("GetMilestoneByRepoID", err)
  161. }
  162. return
  163. }
  164. m.Name = form.Title
  165. m.Content = form.Content
  166. m.DeadlineUnix = timeutil.TimeStamp(deadline.Unix())
  167. if err = models.UpdateMilestone(m, m.IsClosed); err != nil {
  168. ctx.ServerError("UpdateMilestone", err)
  169. return
  170. }
  171. ctx.Flash.Success(ctx.Tr("repo.milestones.edit_success", m.Name))
  172. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  173. }
  174. // ChangeMilestonStatus response for change a milestone's status
  175. func ChangeMilestonStatus(ctx *context.Context) {
  176. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  177. if err != nil {
  178. if models.IsErrMilestoneNotExist(err) {
  179. ctx.NotFound("", err)
  180. } else {
  181. ctx.ServerError("GetMilestoneByRepoID", err)
  182. }
  183. return
  184. }
  185. switch ctx.Params(":action") {
  186. case "open":
  187. if m.IsClosed {
  188. if err = models.ChangeMilestoneStatus(m, false); err != nil {
  189. ctx.ServerError("ChangeMilestoneStatus", err)
  190. return
  191. }
  192. }
  193. ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=open")
  194. case "close":
  195. if !m.IsClosed {
  196. m.ClosedDateUnix = timeutil.TimeStampNow()
  197. if err = models.ChangeMilestoneStatus(m, true); err != nil {
  198. ctx.ServerError("ChangeMilestoneStatus", err)
  199. return
  200. }
  201. }
  202. ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=closed")
  203. default:
  204. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  205. }
  206. }
  207. // DeleteMilestone delete a milestone
  208. func DeleteMilestone(ctx *context.Context) {
  209. if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil {
  210. ctx.Flash.Error("DeleteMilestoneByRepoID: " + err.Error())
  211. } else {
  212. ctx.Flash.Success(ctx.Tr("repo.milestones.deletion_success"))
  213. }
  214. ctx.JSON(200, map[string]interface{}{
  215. "redirect": ctx.Repo.RepoLink + "/milestones",
  216. })
  217. }
  218. // MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone
  219. func MilestoneIssuesAndPulls(ctx *context.Context) {
  220. milestoneID := ctx.ParamsInt64(":id")
  221. milestone, err := models.GetMilestoneByID(milestoneID)
  222. if err != nil {
  223. if models.IsErrMilestoneNotExist(err) {
  224. ctx.NotFound("GetMilestoneByID", err)
  225. return
  226. }
  227. ctx.ServerError("GetMilestoneByID", err)
  228. return
  229. }
  230. ctx.Data["Title"] = milestone.Name
  231. ctx.Data["Milestone"] = milestone
  232. issues(ctx, milestoneID, util.OptionalBoolNone)
  233. ctx.Data["CanWriteIssues"] = ctx.Repo.CanWriteIssuesOrPulls(false)
  234. ctx.Data["CanWritePulls"] = ctx.Repo.CanWriteIssuesOrPulls(true)
  235. ctx.HTML(200, tplMilestoneIssues)
  236. }