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.7 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. // ChangeMilestoneStatus response for change a milestone's status
  183. func ChangeMilestoneStatus(ctx *context.Context) {
  184. toClose := false
  185. switch ctx.Params(":action") {
  186. case "open":
  187. toClose = false
  188. case "close":
  189. toClose = true
  190. default:
  191. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  192. }
  193. id := ctx.ParamsInt64(":id")
  194. if err := models.ChangeMilestoneStatusByRepoIDAndID(ctx.Repo.Repository.ID, id, toClose); err != nil {
  195. if models.IsErrMilestoneNotExist(err) {
  196. ctx.NotFound("", err)
  197. } else {
  198. ctx.ServerError("ChangeMilestoneStatusByIDAndRepoID", err)
  199. }
  200. return
  201. }
  202. ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=" + ctx.Params(":action"))
  203. }
  204. // DeleteMilestone delete a milestone
  205. func DeleteMilestone(ctx *context.Context) {
  206. if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil {
  207. ctx.Flash.Error("DeleteMilestoneByRepoID: " + err.Error())
  208. } else {
  209. ctx.Flash.Success(ctx.Tr("repo.milestones.deletion_success"))
  210. }
  211. ctx.JSON(200, map[string]interface{}{
  212. "redirect": ctx.Repo.RepoLink + "/milestones",
  213. })
  214. }
  215. // MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone
  216. func MilestoneIssuesAndPulls(ctx *context.Context) {
  217. milestoneID := ctx.ParamsInt64(":id")
  218. milestone, err := models.GetMilestoneByID(milestoneID)
  219. if err != nil {
  220. if models.IsErrMilestoneNotExist(err) {
  221. ctx.NotFound("GetMilestoneByID", err)
  222. return
  223. }
  224. ctx.ServerError("GetMilestoneByID", err)
  225. return
  226. }
  227. milestone.RenderedContent = string(markdown.Render([]byte(milestone.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
  228. ctx.Data["Title"] = milestone.Name
  229. ctx.Data["Milestone"] = milestone
  230. issues(ctx, milestoneID, 0, util.OptionalBoolNone)
  231. ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0
  232. ctx.Data["CanWriteIssues"] = ctx.Repo.CanWriteIssuesOrPulls(false)
  233. ctx.Data["CanWritePulls"] = ctx.Repo.CanWriteIssuesOrPulls(true)
  234. ctx.HTML(200, tplMilestoneIssues)
  235. }