2018-11-29 09:46:30 +08:00
// Copyright 2018 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2018-11-29 09:46:30 +08:00
package repo
import (
2023-10-18 09:03:42 +09:00
"fmt"
2021-04-05 17:30:52 +02:00
"net/http"
2021-11-16 18:18:25 +00:00
"net/url"
2018-11-29 09:46:30 +08:00
"time"
2021-09-24 19:32:56 +08:00
"code.gitea.io/gitea/models/db"
2022-04-08 17:11:15 +08:00
issues_model "code.gitea.io/gitea/models/issues"
2018-11-29 09:46:30 +08:00
"code.gitea.io/gitea/modules/base"
2021-04-20 06:25:08 +08:00
"code.gitea.io/gitea/modules/markup"
2018-11-29 09:46:30 +08:00
"code.gitea.io/gitea/modules/markup/markdown"
2024-03-02 16:42:31 +01:00
"code.gitea.io/gitea/modules/optional"
2018-11-29 09:46:30 +08:00
"code.gitea.io/gitea/modules/setting"
2019-08-15 22:46:21 +08:00
"code.gitea.io/gitea/modules/timeutil"
2021-01-26 23:36:53 +08:00
"code.gitea.io/gitea/modules/web"
2024-02-27 15:12:22 +08:00
"code.gitea.io/gitea/services/context"
2021-04-06 20:44:05 +01:00
"code.gitea.io/gitea/services/forms"
2023-05-09 07:30:14 +08:00
"code.gitea.io/gitea/services/issue"
2020-05-12 23:54:35 +02:00
"xorm.io/builder"
2018-11-29 09:46:30 +08:00
)
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
2021-08-11 02:31:13 +02:00
isShowClosed := ctx . FormString ( "state" ) == "closed"
sortType := ctx . FormString ( "sort" )
2021-08-11 17:08:52 +02:00
keyword := ctx . FormTrim ( "q" )
2021-07-29 09:42:15 +08:00
page := ctx . FormInt ( "page" )
2018-11-29 09:46:30 +08:00
if page <= 1 {
page = 1
}
2023-12-11 16:56:48 +08:00
miles , total , err := db . FindAndCount [ issues_model . Milestone ] ( ctx , issues_model . FindMilestoneOptions {
2021-09-24 19:32:56 +08:00
ListOptions : db . ListOptions {
2020-07-28 13:30:40 +02:00
Page : page ,
PageSize : setting . UI . IssuePagingNum ,
} ,
RepoID : ctx . Repo . Repository . ID ,
2024-03-02 16:42:31 +01:00
IsClosed : optional . Some ( isShowClosed ) ,
2020-07-28 13:30:40 +02:00
SortType : sortType ,
2021-04-08 19:53:59 +08:00
Name : keyword ,
2020-07-28 13:30:40 +02:00
} )
2018-11-29 09:46:30 +08:00
if err != nil {
ctx . ServerError ( "GetMilestones" , err )
return
}
2023-07-16 12:43:51 +09:00
2023-09-16 16:39:12 +02:00
stats , err := issues_model . GetMilestonesStatsByRepoCondAndKw ( ctx , builder . And ( builder . Eq { "id" : ctx . Repo . Repository . ID } ) , keyword )
2023-07-16 12:43:51 +09:00
if err != nil {
ctx . ServerError ( "GetMilestoneStats" , err )
return
}
ctx . Data [ "OpenCount" ] = stats . OpenCount
ctx . Data [ "ClosedCount" ] = stats . ClosedCount
2023-10-18 09:03:42 +09:00
linkStr := "%s/milestones?state=%s&q=%s&sort=%s"
ctx . Data [ "OpenLink" ] = fmt . Sprintf ( linkStr , ctx . Repo . RepoLink , "open" ,
url . QueryEscape ( keyword ) , url . QueryEscape ( sortType ) )
ctx . Data [ "ClosedLink" ] = fmt . Sprintf ( linkStr , ctx . Repo . RepoLink , "closed" ,
url . QueryEscape ( keyword ) , url . QueryEscape ( sortType ) )
2023-07-16 12:43:51 +09:00
2022-12-10 10:46:31 +08:00
if ctx . Repo . Repository . IsTimetrackerEnabled ( ctx ) {
2023-12-11 16:56:48 +08:00
if err := issues_model . MilestoneList ( miles ) . LoadTotalTrackedTimes ( ctx ) ; err != nil {
2018-11-29 09:46:30 +08:00
ctx . ServerError ( "LoadTotalTrackedTimes" , err )
return
}
}
for _ , m := range miles {
2021-04-20 06:25:08 +08:00
m . RenderedContent , err = markdown . RenderString ( & markup . RenderContext {
2024-01-15 09:49:24 +01:00
Links : markup . Links {
Base : ctx . Repo . RepoLink ,
} ,
Metas : ctx . Repo . Repository . ComposeMetas ( ctx ) ,
GitRepo : ctx . Repo . GitRepo ,
2024-05-30 15:04:01 +08:00
Repo : ctx . Repo . Repository ,
2024-01-15 09:49:24 +01:00
Ctx : ctx ,
2021-04-20 06:25:08 +08:00
} , m . Content )
if err != nil {
ctx . ServerError ( "RenderString" , err )
return
}
2018-11-29 09:46:30 +08:00
}
ctx . Data [ "Milestones" ] = miles
if isShowClosed {
ctx . Data [ "State" ] = "closed"
} else {
ctx . Data [ "State" ] = "open"
}
ctx . Data [ "SortType" ] = sortType
2021-04-08 19:53:59 +08:00
ctx . Data [ "Keyword" ] = keyword
2018-11-29 09:46:30 +08:00
ctx . Data [ "IsShowClosed" ] = isShowClosed
2019-04-20 06:15:19 +02:00
2021-08-12 14:43:08 +02:00
pager := context . NewPagination ( int ( total ) , setting . UI . IssuePagingNum , page , 5 )
2024-03-16 20:07:56 +08:00
pager . AddParamString ( "state" , fmt . Sprint ( ctx . Data [ "State" ] ) )
pager . AddParamString ( "q" , keyword )
2019-04-20 06:15:19 +02:00
ctx . Data [ "Page" ] = pager
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplMilestone )
2018-11-29 09:46:30 +08:00
}
// 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
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplMilestoneNew )
2018-11-29 09:46:30 +08:00
}
// NewMilestonePost response for creating milestone
2021-01-26 23:36:53 +08:00
func NewMilestonePost ( ctx * context . Context ) {
2021-04-06 20:44:05 +01:00
form := web . GetForm ( ctx ) . ( * forms . CreateMilestoneForm )
2018-11-29 09:46:30 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.milestones.new" )
ctx . Data [ "PageIsIssueList" ] = true
ctx . Data [ "PageIsMilestones" ] = true
if ctx . HasError ( ) {
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplMilestoneNew )
2018-11-29 09:46:30 +08:00
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
}
2019-01-01 14:56:47 -03:00
deadline = time . Date ( deadline . Year ( ) , deadline . Month ( ) , deadline . Day ( ) , 23 , 59 , 59 , 0 , deadline . Location ( ) )
2023-09-16 16:39:12 +02:00
if err = issues_model . NewMilestone ( ctx , & issues_model . Milestone {
2018-11-29 09:46:30 +08:00
RepoID : ctx . Repo . Repository . ID ,
Name : form . Title ,
Content : form . Content ,
2019-08-15 22:46:21 +08:00
DeadlineUnix : timeutil . TimeStamp ( deadline . Unix ( ) ) ,
2018-11-29 09:46:30 +08:00
} ) ; 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
2024-06-19 06:32:45 +08:00
m , err := issues_model . GetMilestoneByRepoID ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( ":id" ) )
2018-11-29 09:46:30 +08:00
if err != nil {
2022-04-08 17:11:15 +08:00
if issues_model . IsErrMilestoneNotExist ( err ) {
2018-11-29 09:46:30 +08:00
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
}
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplMilestoneNew )
2018-11-29 09:46:30 +08:00
}
// EditMilestonePost response for edting milestone
2021-01-26 23:36:53 +08:00
func EditMilestonePost ( ctx * context . Context ) {
2021-04-06 20:44:05 +01:00
form := web . GetForm ( ctx ) . ( * forms . CreateMilestoneForm )
2018-11-29 09:46:30 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.milestones.edit" )
ctx . Data [ "PageIsMilestones" ] = true
ctx . Data [ "PageIsEditMilestone" ] = true
if ctx . HasError ( ) {
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplMilestoneNew )
2018-11-29 09:46:30 +08:00
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
}
2019-01-01 14:56:47 -03:00
deadline = time . Date ( deadline . Year ( ) , deadline . Month ( ) , deadline . Day ( ) , 23 , 59 , 59 , 0 , deadline . Location ( ) )
2024-06-19 06:32:45 +08:00
m , err := issues_model . GetMilestoneByRepoID ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( ":id" ) )
2018-11-29 09:46:30 +08:00
if err != nil {
2022-04-08 17:11:15 +08:00
if issues_model . IsErrMilestoneNotExist ( err ) {
2018-11-29 09:46:30 +08:00
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetMilestoneByRepoID" , err )
}
return
}
m . Name = form . Title
m . Content = form . Content
2019-08-15 22:46:21 +08:00
m . DeadlineUnix = timeutil . TimeStamp ( deadline . Unix ( ) )
2023-09-16 16:39:12 +02:00
if err = issues_model . UpdateMilestone ( ctx , m , m . IsClosed ) ; err != nil {
2018-11-29 09:46:30 +08:00
ctx . ServerError ( "UpdateMilestone" , err )
return
}
ctx . Flash . Success ( ctx . Tr ( "repo.milestones.edit_success" , m . Name ) )
ctx . Redirect ( ctx . Repo . RepoLink + "/milestones" )
}
2020-08-17 04:07:38 +01:00
// ChangeMilestoneStatus response for change a milestone's status
func ChangeMilestoneStatus ( ctx * context . Context ) {
2023-10-23 20:34:17 +08:00
var toClose bool
2024-06-19 06:32:45 +08:00
switch ctx . PathParam ( ":action" ) {
2018-11-29 09:46:30 +08:00
case "open" :
2020-08-17 04:07:38 +01:00
toClose = false
2018-11-29 09:46:30 +08:00
case "close" :
2020-08-17 04:07:38 +01:00
toClose = true
2018-11-29 09:46:30 +08:00
default :
2023-10-23 20:34:17 +08:00
ctx . JSONRedirect ( ctx . Repo . RepoLink + "/milestones" )
return
2018-11-29 09:46:30 +08:00
}
2024-06-19 06:32:45 +08:00
id := ctx . PathParamInt64 ( ":id" )
2020-08-17 04:07:38 +01:00
2023-09-16 16:39:12 +02:00
if err := issues_model . ChangeMilestoneStatusByRepoIDAndID ( ctx , ctx . Repo . Repository . ID , id , toClose ) ; err != nil {
2022-04-08 17:11:15 +08:00
if issues_model . IsErrMilestoneNotExist ( err ) {
2020-08-17 04:07:38 +01:00
ctx . NotFound ( "" , err )
} else {
ctx . ServerError ( "ChangeMilestoneStatusByIDAndRepoID" , err )
}
return
}
2024-06-19 06:32:45 +08:00
ctx . JSONRedirect ( ctx . Repo . RepoLink + "/milestones?state=" + url . QueryEscape ( ctx . PathParam ( ":action" ) ) )
2018-11-29 09:46:30 +08:00
}
// DeleteMilestone delete a milestone
func DeleteMilestone ( ctx * context . Context ) {
2023-09-16 16:39:12 +02:00
if err := issues_model . DeleteMilestoneByRepoID ( ctx , ctx . Repo . Repository . ID , ctx . FormInt64 ( "id" ) ) ; err != nil {
2018-11-29 09:46:30 +08:00
ctx . Flash . Error ( "DeleteMilestoneByRepoID: " + err . Error ( ) )
} else {
ctx . Flash . Success ( ctx . Tr ( "repo.milestones.deletion_success" ) )
}
2023-07-26 14:04:01 +08:00
ctx . JSONRedirect ( ctx . Repo . RepoLink + "/milestones" )
2018-11-29 09:46:30 +08:00
}
// MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone
func MilestoneIssuesAndPulls ( ctx * context . Context ) {
2024-06-19 06:32:45 +08:00
milestoneID := ctx . PathParamInt64 ( ":id" )
2023-07-13 22:00:38 +02:00
projectID := ctx . FormInt64 ( "project" )
2022-04-08 17:11:15 +08:00
milestone , err := issues_model . GetMilestoneByRepoID ( ctx , ctx . Repo . Repository . ID , milestoneID )
2018-11-29 09:46:30 +08:00
if err != nil {
2022-04-08 17:11:15 +08:00
if issues_model . IsErrMilestoneNotExist ( err ) {
2019-08-15 00:43:50 +01:00
ctx . NotFound ( "GetMilestoneByID" , err )
return
}
2018-11-29 09:46:30 +08:00
ctx . ServerError ( "GetMilestoneByID" , err )
return
}
2021-04-20 06:25:08 +08:00
milestone . RenderedContent , err = markdown . RenderString ( & markup . RenderContext {
2024-01-15 09:49:24 +01:00
Links : markup . Links {
Base : ctx . Repo . RepoLink ,
} ,
Metas : ctx . Repo . Repository . ComposeMetas ( ctx ) ,
GitRepo : ctx . Repo . GitRepo ,
2024-05-30 15:04:01 +08:00
Repo : ctx . Repo . Repository ,
2024-01-15 09:49:24 +01:00
Ctx : ctx ,
2021-04-20 06:25:08 +08:00
} , milestone . Content )
if err != nil {
ctx . ServerError ( "RenderString" , err )
return
}
2020-06-25 21:21:13 -04:00
2018-11-29 09:46:30 +08:00
ctx . Data [ "Title" ] = milestone . Name
ctx . Data [ "Milestone" ] = milestone
2024-03-02 16:42:31 +01:00
issues ( ctx , milestoneID , projectID , optional . None [ bool ] ( ) )
2023-05-09 07:30:14 +08:00
2024-02-12 13:04:10 +08:00
ret := issue . ParseTemplatesFromDefaultBranch ( ctx . Repo . Repository , ctx . Repo . GitRepo )
ctx . Data [ "NewIssueChooseTemplate" ] = len ( ret . IssueTemplates ) > 0
2018-11-29 09:46:30 +08:00
2020-01-16 22:18:30 +08:00
ctx . Data [ "CanWriteIssues" ] = ctx . Repo . CanWriteIssuesOrPulls ( false )
ctx . Data [ "CanWritePulls" ] = ctx . Repo . CanWriteIssuesOrPulls ( true )
2019-03-15 10:50:27 -05:00
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplMilestoneIssues )
2018-11-29 09:46:30 +08:00
}