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

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