// Copyright 2018 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package repo import ( "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" ) const ( tplMilestone base.TplName = "repo/issue/milestones" tplMilestoneNew base.TplName = "repo/issue/milestone_new" tplMilestoneIssues base.TplName = "repo/issue/milestone_issues" ) // Milestones render milestones page func Milestones(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.milestones") ctx.Data["PageIsIssueList"] = true ctx.Data["PageIsMilestones"] = true isShowClosed := ctx.Query("state") == "closed" openCount, closedCount, err := models.MilestoneStats(ctx.Repo.Repository.ID) if err != nil { ctx.ServerError("MilestoneStats", err) return } ctx.Data["OpenCount"] = openCount ctx.Data["ClosedCount"] = closedCount sortType := ctx.Query("sort") page := ctx.QueryInt("page") if page <= 1 { page = 1 } var total int if !isShowClosed { total = int(openCount) } else { total = int(closedCount) } miles, err := models.GetMilestones(ctx.Repo.Repository.ID, page, isShowClosed, sortType) if err != nil { ctx.ServerError("GetMilestones", err) return } if ctx.Repo.Repository.IsTimetrackerEnabled() { if err := miles.LoadTotalTrackedTimes(); err != nil { ctx.ServerError("LoadTotalTrackedTimes", err) return } } for _, m := range miles { m.RenderedContent = string(markdown.Render([]byte(m.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas())) } ctx.Data["Milestones"] = miles if isShowClosed { ctx.Data["State"] = "closed" } else { ctx.Data["State"] = "open" } ctx.Data["SortType"] = sortType ctx.Data["IsShowClosed"] = isShowClosed pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5) pager.AddParam(ctx, "state", "State") ctx.Data["Page"] = pager ctx.HTML(200, tplMilestone) } // NewMilestone render creating milestone page func NewMilestone(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.milestones.new") ctx.Data["PageIsIssueList"] = true ctx.Data["PageIsMilestones"] = true ctx.Data["RequireDatetimepicker"] = true ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language()) ctx.HTML(200, tplMilestoneNew) } // NewMilestonePost response for creating milestone func NewMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) { ctx.Data["Title"] = ctx.Tr("repo.milestones.new") ctx.Data["PageIsIssueList"] = true ctx.Data["PageIsMilestones"] = true ctx.Data["RequireDatetimepicker"] = true ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language()) if ctx.HasError() { ctx.HTML(200, tplMilestoneNew) return } if len(form.Deadline) == 0 { form.Deadline = "9999-12-31" } deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local) if err != nil { ctx.Data["Err_Deadline"] = true ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form) return } deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location()) if err = models.NewMilestone(&models.Milestone{ RepoID: ctx.Repo.Repository.ID, Name: form.Title, Content: form.Content, DeadlineUnix: timeutil.TimeStamp(deadline.Unix()), }); err != nil { ctx.ServerError("NewMilestone", err) return } ctx.Flash.Success(ctx.Tr("repo.milestones.create_success", form.Title)) ctx.Redirect(ctx.Repo.RepoLink + "/milestones") } // EditMilestone render edting milestone page func EditMilestone(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.milestones.edit") ctx.Data["PageIsMilestones"] = true ctx.Data["PageIsEditMilestone"] = true ctx.Data["RequireDatetimepicker"] = true ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language()) m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) if err != nil { if models.IsErrMilestoneNotExist(err) { ctx.NotFound("", nil) } else { ctx.ServerError("GetMilestoneByRepoID", err) } return } ctx.Data["title"] = m.Name ctx.Data["content"] = m.Content if len(m.DeadlineString) > 0 { ctx.Data["deadline"] = m.DeadlineString } ctx.HTML(200, tplMilestoneNew) } // EditMilestonePost response for edting milestone func EditMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) { ctx.Data["Title"] = ctx.Tr("repo.milestones.edit") ctx.Data["PageIsMilestones"] = true ctx.Data["PageIsEditMilestone"] = true ctx.Data["RequireDatetimepicker"] = true ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language()) if ctx.HasError() { ctx.HTML(200, tplMilestoneNew) return } if len(form.Deadline) == 0 { form.Deadline = "9999-12-31" } deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local) if err != nil { ctx.Data["Err_Deadline"] = true ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form) return } deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location()) m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) if err != nil { if models.IsErrMilestoneNotExist(err) { ctx.NotFound("", nil) } else { ctx.ServerError("GetMilestoneByRepoID", err) } return } m.Name = form.Title m.Content = form.Content m.DeadlineUnix = timeutil.TimeStamp(deadline.Unix()) if err = models.UpdateMilestone(m); err != nil { ctx.ServerError("UpdateMilestone", err) return } ctx.Flash.Success(ctx.Tr("repo.milestones.edit_success", m.Name)) ctx.Redirect(ctx.Repo.RepoLink + "/milestones") } // ChangeMilestonStatus response for change a milestone's status func ChangeMilestonStatus(ctx *context.Context) { m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) if err != nil { if models.IsErrMilestoneNotExist(err) { ctx.NotFound("", err) } else { ctx.ServerError("GetMilestoneByRepoID", err) } return } switch ctx.Params(":action") { case "open": if m.IsClosed { if err = models.ChangeMilestoneStatus(m, false); err != nil { ctx.ServerError("ChangeMilestoneStatus", err) return } } ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=open") case "close": if !m.IsClosed { m.ClosedDateUnix = timeutil.TimeStampNow() if err = models.ChangeMilestoneStatus(m, true); err != nil { ctx.ServerError("ChangeMilestoneStatus", err) return } } ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=closed") default: ctx.Redirect(ctx.Repo.RepoLink + "/milestones") } } // DeleteMilestone delete a milestone func DeleteMilestone(ctx *context.Context) { if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil { ctx.Flash.Error("DeleteMilestoneByRepoID: " + err.Error()) } else { ctx.Flash.Success(ctx.Tr("repo.milestones.deletion_success")) } ctx.JSON(200, map[string]interface{}{ "redirect": ctx.Repo.RepoLink + "/milestones", }) } // MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone func MilestoneIssuesAndPulls(ctx *context.Context) { milestoneID := ctx.ParamsInt64(":id") milestone, err := models.GetMilestoneByID(milestoneID) if err != nil { if models.IsErrMilestoneNotExist(err) { ctx.NotFound("GetMilestoneByID", err) return } ctx.ServerError("GetMilestoneByID", err) return } ctx.Data["Title"] = milestone.Name ctx.Data["Milestone"] = milestone issues(ctx, milestoneID, util.OptionalBoolNone) perm, err := models.GetUserRepoPermission(ctx.Repo.Repository, ctx.User) if err != nil { ctx.ServerError("GetUserRepoPermission", err) return } ctx.Data["CanWriteIssues"] = perm.CanWriteIssuesOrPulls(false) ctx.Data["CanWritePulls"] = perm.CanWriteIssuesOrPulls(true) ctx.HTML(200, tplMilestoneIssues) }