code.gitea.io/gitea@v1.21.7/routers/web/repo/milestone.go (about) 1 // Copyright 2018 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package repo 5 6 import ( 7 "net/http" 8 "net/url" 9 "time" 10 11 "code.gitea.io/gitea/models/db" 12 issues_model "code.gitea.io/gitea/models/issues" 13 "code.gitea.io/gitea/modules/base" 14 "code.gitea.io/gitea/modules/context" 15 "code.gitea.io/gitea/modules/markup" 16 "code.gitea.io/gitea/modules/markup/markdown" 17 "code.gitea.io/gitea/modules/setting" 18 "code.gitea.io/gitea/modules/structs" 19 "code.gitea.io/gitea/modules/timeutil" 20 "code.gitea.io/gitea/modules/util" 21 "code.gitea.io/gitea/modules/web" 22 "code.gitea.io/gitea/services/forms" 23 "code.gitea.io/gitea/services/issue" 24 25 "xorm.io/builder" 26 ) 27 28 const ( 29 tplMilestone base.TplName = "repo/issue/milestones" 30 tplMilestoneNew base.TplName = "repo/issue/milestone_new" 31 tplMilestoneIssues base.TplName = "repo/issue/milestone_issues" 32 ) 33 34 // Milestones render milestones page 35 func Milestones(ctx *context.Context) { 36 ctx.Data["Title"] = ctx.Tr("repo.milestones") 37 ctx.Data["PageIsIssueList"] = true 38 ctx.Data["PageIsMilestones"] = true 39 40 isShowClosed := ctx.FormString("state") == "closed" 41 sortType := ctx.FormString("sort") 42 keyword := ctx.FormTrim("q") 43 page := ctx.FormInt("page") 44 if page <= 1 { 45 page = 1 46 } 47 48 state := structs.StateOpen 49 if isShowClosed { 50 state = structs.StateClosed 51 } 52 53 miles, total, err := issues_model.GetMilestones(issues_model.GetMilestonesOption{ 54 ListOptions: db.ListOptions{ 55 Page: page, 56 PageSize: setting.UI.IssuePagingNum, 57 }, 58 RepoID: ctx.Repo.Repository.ID, 59 State: state, 60 SortType: sortType, 61 Name: keyword, 62 }) 63 if err != nil { 64 ctx.ServerError("GetMilestones", err) 65 return 66 } 67 68 stats, err := issues_model.GetMilestonesStatsByRepoCondAndKw(ctx, builder.And(builder.Eq{"id": ctx.Repo.Repository.ID}), keyword) 69 if err != nil { 70 ctx.ServerError("GetMilestoneStats", err) 71 return 72 } 73 ctx.Data["OpenCount"] = stats.OpenCount 74 ctx.Data["ClosedCount"] = stats.ClosedCount 75 76 if ctx.Repo.Repository.IsTimetrackerEnabled(ctx) { 77 if err := miles.LoadTotalTrackedTimes(ctx); err != nil { 78 ctx.ServerError("LoadTotalTrackedTimes", err) 79 return 80 } 81 } 82 for _, m := range miles { 83 m.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ 84 Links: markup.Links{ 85 Base: ctx.Repo.RepoLink, 86 }, 87 Metas: ctx.Repo.Repository.ComposeMetas(), 88 GitRepo: ctx.Repo.GitRepo, 89 Ctx: ctx, 90 }, m.Content) 91 if err != nil { 92 ctx.ServerError("RenderString", err) 93 return 94 } 95 } 96 ctx.Data["Milestones"] = miles 97 98 if isShowClosed { 99 ctx.Data["State"] = "closed" 100 } else { 101 ctx.Data["State"] = "open" 102 } 103 104 ctx.Data["SortType"] = sortType 105 ctx.Data["Keyword"] = keyword 106 ctx.Data["IsShowClosed"] = isShowClosed 107 108 pager := context.NewPagination(int(total), setting.UI.IssuePagingNum, page, 5) 109 pager.AddParam(ctx, "state", "State") 110 pager.AddParam(ctx, "q", "Keyword") 111 ctx.Data["Page"] = pager 112 113 ctx.HTML(http.StatusOK, tplMilestone) 114 } 115 116 // NewMilestone render creating milestone page 117 func NewMilestone(ctx *context.Context) { 118 ctx.Data["Title"] = ctx.Tr("repo.milestones.new") 119 ctx.Data["PageIsIssueList"] = true 120 ctx.Data["PageIsMilestones"] = true 121 ctx.HTML(http.StatusOK, tplMilestoneNew) 122 } 123 124 // NewMilestonePost response for creating milestone 125 func NewMilestonePost(ctx *context.Context) { 126 form := web.GetForm(ctx).(*forms.CreateMilestoneForm) 127 ctx.Data["Title"] = ctx.Tr("repo.milestones.new") 128 ctx.Data["PageIsIssueList"] = true 129 ctx.Data["PageIsMilestones"] = true 130 131 if ctx.HasError() { 132 ctx.HTML(http.StatusOK, tplMilestoneNew) 133 return 134 } 135 136 if len(form.Deadline) == 0 { 137 form.Deadline = "9999-12-31" 138 } 139 deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local) 140 if err != nil { 141 ctx.Data["Err_Deadline"] = true 142 ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form) 143 return 144 } 145 146 deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location()) 147 if err = issues_model.NewMilestone(ctx, &issues_model.Milestone{ 148 RepoID: ctx.Repo.Repository.ID, 149 Name: form.Title, 150 Content: form.Content, 151 DeadlineUnix: timeutil.TimeStamp(deadline.Unix()), 152 }); err != nil { 153 ctx.ServerError("NewMilestone", err) 154 return 155 } 156 157 ctx.Flash.Success(ctx.Tr("repo.milestones.create_success", form.Title)) 158 ctx.Redirect(ctx.Repo.RepoLink + "/milestones") 159 } 160 161 // EditMilestone render edting milestone page 162 func EditMilestone(ctx *context.Context) { 163 ctx.Data["Title"] = ctx.Tr("repo.milestones.edit") 164 ctx.Data["PageIsMilestones"] = true 165 ctx.Data["PageIsEditMilestone"] = true 166 167 m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) 168 if err != nil { 169 if issues_model.IsErrMilestoneNotExist(err) { 170 ctx.NotFound("", nil) 171 } else { 172 ctx.ServerError("GetMilestoneByRepoID", err) 173 } 174 return 175 } 176 ctx.Data["title"] = m.Name 177 ctx.Data["content"] = m.Content 178 if len(m.DeadlineString) > 0 { 179 ctx.Data["deadline"] = m.DeadlineString 180 } 181 ctx.HTML(http.StatusOK, tplMilestoneNew) 182 } 183 184 // EditMilestonePost response for edting milestone 185 func EditMilestonePost(ctx *context.Context) { 186 form := web.GetForm(ctx).(*forms.CreateMilestoneForm) 187 ctx.Data["Title"] = ctx.Tr("repo.milestones.edit") 188 ctx.Data["PageIsMilestones"] = true 189 ctx.Data["PageIsEditMilestone"] = true 190 191 if ctx.HasError() { 192 ctx.HTML(http.StatusOK, tplMilestoneNew) 193 return 194 } 195 196 if len(form.Deadline) == 0 { 197 form.Deadline = "9999-12-31" 198 } 199 deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local) 200 if err != nil { 201 ctx.Data["Err_Deadline"] = true 202 ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form) 203 return 204 } 205 206 deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location()) 207 m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) 208 if err != nil { 209 if issues_model.IsErrMilestoneNotExist(err) { 210 ctx.NotFound("", nil) 211 } else { 212 ctx.ServerError("GetMilestoneByRepoID", err) 213 } 214 return 215 } 216 m.Name = form.Title 217 m.Content = form.Content 218 m.DeadlineUnix = timeutil.TimeStamp(deadline.Unix()) 219 if err = issues_model.UpdateMilestone(ctx, m, m.IsClosed); err != nil { 220 ctx.ServerError("UpdateMilestone", err) 221 return 222 } 223 224 ctx.Flash.Success(ctx.Tr("repo.milestones.edit_success", m.Name)) 225 ctx.Redirect(ctx.Repo.RepoLink + "/milestones") 226 } 227 228 // ChangeMilestoneStatus response for change a milestone's status 229 func ChangeMilestoneStatus(ctx *context.Context) { 230 var toClose bool 231 switch ctx.Params(":action") { 232 case "open": 233 toClose = false 234 case "close": 235 toClose = true 236 default: 237 ctx.JSONRedirect(ctx.Repo.RepoLink + "/milestones") 238 return 239 } 240 id := ctx.ParamsInt64(":id") 241 242 if err := issues_model.ChangeMilestoneStatusByRepoIDAndID(ctx, ctx.Repo.Repository.ID, id, toClose); err != nil { 243 if issues_model.IsErrMilestoneNotExist(err) { 244 ctx.NotFound("", err) 245 } else { 246 ctx.ServerError("ChangeMilestoneStatusByIDAndRepoID", err) 247 } 248 return 249 } 250 ctx.JSONRedirect(ctx.Repo.RepoLink + "/milestones?state=" + url.QueryEscape(ctx.Params(":action"))) 251 } 252 253 // DeleteMilestone delete a milestone 254 func DeleteMilestone(ctx *context.Context) { 255 if err := issues_model.DeleteMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil { 256 ctx.Flash.Error("DeleteMilestoneByRepoID: " + err.Error()) 257 } else { 258 ctx.Flash.Success(ctx.Tr("repo.milestones.deletion_success")) 259 } 260 261 ctx.JSONRedirect(ctx.Repo.RepoLink + "/milestones") 262 } 263 264 // MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone 265 func MilestoneIssuesAndPulls(ctx *context.Context) { 266 milestoneID := ctx.ParamsInt64(":id") 267 projectID := ctx.FormInt64("project") 268 milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID) 269 if err != nil { 270 if issues_model.IsErrMilestoneNotExist(err) { 271 ctx.NotFound("GetMilestoneByID", err) 272 return 273 } 274 275 ctx.ServerError("GetMilestoneByID", err) 276 return 277 } 278 279 milestone.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ 280 Links: markup.Links{ 281 Base: ctx.Repo.RepoLink, 282 }, 283 Metas: ctx.Repo.Repository.ComposeMetas(), 284 GitRepo: ctx.Repo.GitRepo, 285 Ctx: ctx, 286 }, milestone.Content) 287 if err != nil { 288 ctx.ServerError("RenderString", err) 289 return 290 } 291 292 ctx.Data["Title"] = milestone.Name 293 ctx.Data["Milestone"] = milestone 294 295 issues(ctx, milestoneID, projectID, util.OptionalBoolNone) 296 297 ret := issue.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) 298 ctx.Data["NewIssueChooseTemplate"] = len(ret.IssueTemplates) > 0 299 300 ctx.Data["CanWriteIssues"] = ctx.Repo.CanWriteIssuesOrPulls(false) 301 ctx.Data["CanWritePulls"] = ctx.Repo.CanWriteIssuesOrPulls(true) 302 303 ctx.HTML(http.StatusOK, tplMilestoneIssues) 304 }