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.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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. 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: util.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["RequireDatetimepicker"] = true
  119. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  120. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  121. if err != nil {
  122. if models.IsErrMilestoneNotExist(err) {
  123. ctx.NotFound("", nil)
  124. } else {
  125. ctx.ServerError("GetMilestoneByRepoID", err)
  126. }
  127. return
  128. }
  129. ctx.Data["title"] = m.Name
  130. ctx.Data["content"] = m.Content
  131. if len(m.DeadlineString) > 0 {
  132. ctx.Data["deadline"] = m.DeadlineString
  133. }
  134. ctx.HTML(200, tplMilestoneNew)
  135. }
  136. // EditMilestonePost response for edting milestone
  137. func EditMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
  138. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  139. ctx.Data["PageIsMilestones"] = true
  140. ctx.Data["PageIsEditMilestone"] = true
  141. ctx.Data["RequireDatetimepicker"] = true
  142. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  143. if ctx.HasError() {
  144. ctx.HTML(200, tplMilestoneNew)
  145. return
  146. }
  147. if len(form.Deadline) == 0 {
  148. form.Deadline = "9999-12-31"
  149. }
  150. deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
  151. if err != nil {
  152. ctx.Data["Err_Deadline"] = true
  153. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
  154. return
  155. }
  156. deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location())
  157. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  158. if err != nil {
  159. if models.IsErrMilestoneNotExist(err) {
  160. ctx.NotFound("", nil)
  161. } else {
  162. ctx.ServerError("GetMilestoneByRepoID", err)
  163. }
  164. return
  165. }
  166. m.Name = form.Title
  167. m.Content = form.Content
  168. m.DeadlineUnix = util.TimeStamp(deadline.Unix())
  169. if err = models.UpdateMilestone(m); err != nil {
  170. ctx.ServerError("UpdateMilestone", err)
  171. return
  172. }
  173. ctx.Flash.Success(ctx.Tr("repo.milestones.edit_success", m.Name))
  174. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  175. }
  176. // ChangeMilestonStatus response for change a milestone's status
  177. func ChangeMilestonStatus(ctx *context.Context) {
  178. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  179. if err != nil {
  180. if models.IsErrMilestoneNotExist(err) {
  181. ctx.NotFound("", err)
  182. } else {
  183. ctx.ServerError("GetMilestoneByRepoID", err)
  184. }
  185. return
  186. }
  187. switch ctx.Params(":action") {
  188. case "open":
  189. if m.IsClosed {
  190. if err = models.ChangeMilestoneStatus(m, false); err != nil {
  191. ctx.ServerError("ChangeMilestoneStatus", err)
  192. return
  193. }
  194. }
  195. ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=open")
  196. case "close":
  197. if !m.IsClosed {
  198. m.ClosedDateUnix = util.TimeStampNow()
  199. if err = models.ChangeMilestoneStatus(m, true); err != nil {
  200. ctx.ServerError("ChangeMilestoneStatus", err)
  201. return
  202. }
  203. }
  204. ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=closed")
  205. default:
  206. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  207. }
  208. }
  209. // DeleteMilestone delete a milestone
  210. func DeleteMilestone(ctx *context.Context) {
  211. if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil {
  212. ctx.Flash.Error("DeleteMilestoneByRepoID: " + err.Error())
  213. } else {
  214. ctx.Flash.Success(ctx.Tr("repo.milestones.deletion_success"))
  215. }
  216. ctx.JSON(200, map[string]interface{}{
  217. "redirect": ctx.Repo.RepoLink + "/milestones",
  218. })
  219. }
  220. // MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone
  221. func MilestoneIssuesAndPulls(ctx *context.Context) {
  222. milestoneID := ctx.ParamsInt64(":id")
  223. milestone, err := models.GetMilestoneByID(milestoneID)
  224. if err != nil {
  225. ctx.ServerError("GetMilestoneByID", err)
  226. return
  227. }
  228. ctx.Data["Title"] = milestone.Name
  229. ctx.Data["Milestone"] = milestone
  230. issues(ctx, milestoneID, util.OptionalBoolNone)
  231. perm, err := models.GetUserRepoPermission(ctx.Repo.Repository, ctx.User)
  232. if err != nil {
  233. ctx.ServerError("GetUserRepoPermission", err)
  234. return
  235. }
  236. ctx.Data["CanWriteIssues"] = perm.CanWriteIssuesOrPulls(false)
  237. ctx.Data["CanWritePulls"] = perm.CanWriteIssuesOrPulls(true)
  238. ctx.HTML(200, tplMilestoneIssues)
  239. }