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.

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