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

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