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.

milestone.go 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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/util"
  14. "github.com/Unknwon/paginater"
  15. )
  16. const (
  17. tplMilestone base.TplName = "repo/issue/milestones"
  18. tplMilestoneNew base.TplName = "repo/issue/milestone_new"
  19. tplMilestoneEdit base.TplName = "repo/issue/milestone_edit"
  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. openCount, closedCount, err := models.MilestoneStats(ctx.Repo.Repository.ID)
  29. if err != nil {
  30. ctx.ServerError("MilestoneStats", err)
  31. return
  32. }
  33. ctx.Data["OpenCount"] = openCount
  34. ctx.Data["ClosedCount"] = 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(openCount)
  43. } else {
  44. total = int(closedCount)
  45. }
  46. ctx.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5)
  47. miles, err := models.GetMilestones(ctx.Repo.Repository.ID, page, isShowClosed, sortType)
  48. if err != nil {
  49. ctx.ServerError("GetMilestones", err)
  50. return
  51. }
  52. if ctx.Repo.Repository.IsTimetrackerEnabled() {
  53. if miles.LoadTotalTrackedTimes(); err != nil {
  54. ctx.ServerError("LoadTotalTrackedTimes", err)
  55. return
  56. }
  57. }
  58. for _, m := range miles {
  59. m.RenderedContent = string(markdown.Render([]byte(m.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
  60. }
  61. ctx.Data["Milestones"] = miles
  62. if isShowClosed {
  63. ctx.Data["State"] = "closed"
  64. } else {
  65. ctx.Data["State"] = "open"
  66. }
  67. ctx.Data["SortType"] = sortType
  68. ctx.Data["IsShowClosed"] = isShowClosed
  69. ctx.HTML(200, tplMilestone)
  70. }
  71. // NewMilestone render creating milestone page
  72. func NewMilestone(ctx *context.Context) {
  73. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  74. ctx.Data["PageIsIssueList"] = true
  75. ctx.Data["PageIsMilestones"] = true
  76. ctx.Data["RequireDatetimepicker"] = true
  77. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  78. ctx.HTML(200, tplMilestoneNew)
  79. }
  80. // NewMilestonePost response for creating milestone
  81. func NewMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
  82. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  83. ctx.Data["PageIsIssueList"] = true
  84. ctx.Data["PageIsMilestones"] = true
  85. ctx.Data["RequireDatetimepicker"] = 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. if err = models.NewMilestone(&models.Milestone{
  101. RepoID: ctx.Repo.Repository.ID,
  102. Name: form.Title,
  103. Content: form.Content,
  104. DeadlineUnix: util.TimeStamp(deadline.Unix()),
  105. }); err != nil {
  106. ctx.ServerError("NewMilestone", err)
  107. return
  108. }
  109. ctx.Flash.Success(ctx.Tr("repo.milestones.create_success", form.Title))
  110. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  111. }
  112. // EditMilestone render edting milestone page
  113. func EditMilestone(ctx *context.Context) {
  114. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  115. ctx.Data["PageIsMilestones"] = true
  116. ctx.Data["PageIsEditMilestone"] = true
  117. ctx.Data["RequireDatetimepicker"] = 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["RequireDatetimepicker"] = true
  141. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  142. if ctx.HasError() {
  143. ctx.HTML(200, tplMilestoneNew)
  144. return
  145. }
  146. if len(form.Deadline) == 0 {
  147. form.Deadline = "9999-12-31"
  148. }
  149. deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
  150. if err != nil {
  151. ctx.Data["Err_Deadline"] = true
  152. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
  153. return
  154. }
  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 = util.TimeStamp(deadline.Unix())
  167. if err = models.UpdateMilestone(m); 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 = util.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. ctx.ServerError("GetMilestoneByID", err)
  224. return
  225. }
  226. ctx.Data["Title"] = milestone.Name
  227. ctx.Data["Milestone"] = milestone
  228. issues(ctx, milestoneID, util.OptionalBoolNone)
  229. ctx.HTML(200, tplMilestoneIssues)
  230. }