2017-02-09 12:09:26 +05:30
// Copyright 2017 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.
2022-04-08 14:41:15 +05:30
package issues
2017-02-09 12:09:26 +05:30
import (
2021-11-19 19:09:57 +05:30
"context"
2018-05-16 19:31:55 +05:30
"fmt"
2020-01-27 21:53:21 +05:30
"strings"
2020-09-05 23:08:54 +05:30
"time"
2018-05-16 19:31:55 +05:30
2021-09-19 17:19:59 +05:30
"code.gitea.io/gitea/models/db"
2021-12-10 06:57:50 +05:30
repo_model "code.gitea.io/gitea/models/repo"
2017-02-09 12:09:26 +05:30
"code.gitea.io/gitea/modules/setting"
2019-05-11 15:51:34 +05:30
api "code.gitea.io/gitea/modules/structs"
2019-08-15 20:16:21 +05:30
"code.gitea.io/gitea/modules/timeutil"
2019-12-28 02:00:58 +05:30
"xorm.io/builder"
2017-02-09 12:09:26 +05:30
)
2022-04-08 14:41:15 +05:30
// ErrMilestoneNotExist represents a "MilestoneNotExist" kind of error.
type ErrMilestoneNotExist struct {
ID int64
RepoID int64
Name string
}
// IsErrMilestoneNotExist checks if an error is a ErrMilestoneNotExist.
func IsErrMilestoneNotExist ( err error ) bool {
_ , ok := err . ( ErrMilestoneNotExist )
return ok
}
func ( err ErrMilestoneNotExist ) Error ( ) string {
if len ( err . Name ) > 0 {
return fmt . Sprintf ( "milestone does not exist [name: %s, repo_id: %d]" , err . Name , err . RepoID )
}
return fmt . Sprintf ( "milestone does not exist [id: %d, repo_id: %d]" , err . ID , err . RepoID )
}
2017-02-09 12:09:26 +05:30
// Milestone represents a milestone of repository.
type Milestone struct {
2021-12-10 06:57:50 +05:30
ID int64 ` xorm:"pk autoincr" `
RepoID int64 ` xorm:"INDEX" `
Repo * repo_model . Repository ` xorm:"-" `
2017-02-09 12:09:26 +05:30
Name string
Content string ` xorm:"TEXT" `
RenderedContent string ` xorm:"-" `
IsClosed bool
NumIssues int
NumClosedIssues int
NumOpenIssues int ` xorm:"-" `
Completeness int // Percentage(1-100).
2018-05-07 15:20:27 +05:30
IsOverdue bool ` xorm:"-" `
2017-02-09 12:09:26 +05:30
2020-09-05 23:08:54 +05:30
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX updated" `
2019-08-15 20:16:21 +05:30
DeadlineUnix timeutil . TimeStamp
ClosedDateUnix timeutil . TimeStamp
2020-09-05 23:08:54 +05:30
DeadlineString string ` xorm:"-" `
2018-04-29 11:28:47 +05:30
TotalTrackedTime int64 ` xorm:"-" `
2020-09-05 23:08:54 +05:30
TimeSinceUpdate int64 ` xorm:"-" `
2017-02-09 12:09:26 +05:30
}
2021-09-19 17:19:59 +05:30
func init ( ) {
db . RegisterModel ( new ( Milestone ) )
}
2017-02-09 12:09:26 +05:30
// BeforeUpdate is invoked from XORM before updating this object.
func ( m * Milestone ) BeforeUpdate ( ) {
if m . NumIssues > 0 {
m . Completeness = m . NumClosedIssues * 100 / m . NumIssues
} else {
m . Completeness = 0
}
}
2017-10-01 22:22:35 +05:30
// AfterLoad is invoked from XORM after setting the value of a field of
2017-02-09 12:09:26 +05:30
// this object.
2017-10-01 22:22:35 +05:30
func ( m * Milestone ) AfterLoad ( ) {
2020-09-05 23:08:54 +05:30
if ! m . UpdatedUnix . IsZero ( ) {
m . TimeSinceUpdate = time . Now ( ) . Unix ( ) - m . UpdatedUnix . AsTime ( ) . Unix ( )
}
2017-10-01 22:22:35 +05:30
m . NumOpenIssues = m . NumIssues - m . NumClosedIssues
2017-12-11 10:07:04 +05:30
if m . DeadlineUnix . Year ( ) == 9999 {
2017-10-01 22:22:35 +05:30
return
}
2017-02-09 12:09:26 +05:30
2017-12-11 10:07:04 +05:30
m . DeadlineString = m . DeadlineUnix . Format ( "2006-01-02" )
2021-03-08 07:25:57 +05:30
if m . IsClosed {
m . IsOverdue = m . ClosedDateUnix >= m . DeadlineUnix
} else {
m . IsOverdue = timeutil . TimeStampNow ( ) >= m . DeadlineUnix
2017-02-09 12:09:26 +05:30
}
}
// State returns string representation of milestone status.
func ( m * Milestone ) State ( ) api . StateType {
if m . IsClosed {
return api . StateClosed
}
return api . StateOpen
}
// NewMilestone creates new milestone of repository.
func NewMilestone ( m * Milestone ) ( err error ) {
2021-11-21 21:11:00 +05:30
ctx , committer , err := db . TxContext ( )
if err != nil {
2017-02-09 12:09:26 +05:30
return err
}
2021-11-21 21:11:00 +05:30
defer committer . Close ( )
2017-02-09 12:09:26 +05:30
2020-01-27 21:53:21 +05:30
m . Name = strings . TrimSpace ( m . Name )
2021-11-21 21:11:00 +05:30
if err = db . Insert ( ctx , m ) ; err != nil {
2017-02-09 12:09:26 +05:30
return err
}
2021-11-21 21:11:00 +05:30
if _ , err = db . Exec ( ctx , "UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?" , m . RepoID ) ; err != nil {
2017-02-09 12:09:26 +05:30
return err
}
2021-11-21 21:11:00 +05:30
return committer . Commit ( )
2017-02-09 12:09:26 +05:30
}
2022-06-30 21:25:08 +05:30
// HasMilestoneByRepoID returns if the milestone exists in the repository.
func HasMilestoneByRepoID ( ctx context . Context , repoID , id int64 ) ( bool , error ) {
return db . GetEngine ( ctx ) . ID ( id ) . Where ( "repo_id=?" , repoID ) . Exist ( new ( Milestone ) )
}
2022-04-08 14:41:15 +05:30
// GetMilestoneByRepoID returns the milestone in a repository.
func GetMilestoneByRepoID ( ctx context . Context , repoID , id int64 ) ( * Milestone , error ) {
2020-04-30 09:45:39 +05:30
m := new ( Milestone )
2022-04-08 14:41:15 +05:30
has , err := db . GetEngine ( ctx ) . ID ( id ) . Where ( "repo_id=?" , repoID ) . Get ( m )
2017-02-09 12:09:26 +05:30
if err != nil {
return nil , err
} else if ! has {
2020-04-30 09:45:39 +05:30
return nil , ErrMilestoneNotExist { ID : id , RepoID : repoID }
2017-02-09 12:09:26 +05:30
}
return m , nil
}
2020-04-30 09:45:39 +05:30
// GetMilestoneByRepoIDANDName return a milestone if one exist by name and repo
func GetMilestoneByRepoIDANDName ( repoID int64 , name string ) ( * Milestone , error ) {
var mile Milestone
2021-09-23 21:15:36 +05:30
has , err := db . GetEngine ( db . DefaultContext ) . Where ( "repo_id=? AND name=?" , repoID , name ) . Get ( & mile )
2020-04-30 09:45:39 +05:30
if err != nil {
return nil , err
}
if ! has {
return nil , ErrMilestoneNotExist { Name : name , RepoID : repoID }
}
return & mile , nil
}
2017-02-09 12:09:26 +05:30
// UpdateMilestone updates information of given milestone.
2020-01-29 12:06:32 +05:30
func UpdateMilestone ( m * Milestone , oldIsClosed bool ) error {
2021-11-21 21:11:00 +05:30
ctx , committer , err := db . TxContext ( )
if err != nil {
2020-01-29 12:06:32 +05:30
return err
}
2021-11-21 21:11:00 +05:30
defer committer . Close ( )
2020-01-29 12:06:32 +05:30
if m . IsClosed && ! oldIsClosed {
m . ClosedDateUnix = timeutil . TimeStampNow ( )
}
2022-01-18 00:01:58 +05:30
if err := updateMilestone ( ctx , m ) ; err != nil {
2019-10-08 03:14:58 +05:30
return err
}
2020-01-29 12:06:32 +05:30
// if IsClosed changed, update milestone numbers of repository
if oldIsClosed != m . IsClosed {
2022-01-18 00:01:58 +05:30
if err := updateRepoMilestoneNum ( ctx , m . RepoID ) ; err != nil {
2020-01-29 12:06:32 +05:30
return err
}
}
2021-11-21 21:11:00 +05:30
return committer . Commit ( )
2019-10-08 03:14:58 +05:30
}
2022-01-18 00:01:58 +05:30
func updateMilestone ( ctx context . Context , m * Milestone ) error {
2020-05-13 03:24:35 +05:30
m . Name = strings . TrimSpace ( m . Name )
2022-01-18 00:01:58 +05:30
_ , err := db . GetEngine ( ctx ) . ID ( m . ID ) . AllCols ( ) . Update ( m )
2021-06-22 00:04:58 +05:30
if err != nil {
return err
}
2022-04-08 14:41:15 +05:30
return UpdateMilestoneCounters ( ctx , m . ID )
2021-06-22 00:04:58 +05:30
}
2022-04-08 14:41:15 +05:30
// UpdateMilestoneCounters calculates NumIssues, NumClosesIssues and Completeness
func UpdateMilestoneCounters ( ctx context . Context , id int64 ) error {
2022-01-18 00:01:58 +05:30
e := db . GetEngine ( ctx )
2021-06-22 00:04:58 +05:30
_ , err := e . ID ( id ) .
2020-05-13 03:24:35 +05:30
SetExpr ( "num_issues" , builder . Select ( "count(*)" ) . From ( "issue" ) . Where (
2021-06-22 00:04:58 +05:30
builder . Eq { "milestone_id" : id } ,
2020-05-13 03:24:35 +05:30
) ) .
SetExpr ( "num_closed_issues" , builder . Select ( "count(*)" ) . From ( "issue" ) . Where (
builder . Eq {
2021-06-22 00:04:58 +05:30
"milestone_id" : id ,
2020-05-13 03:24:35 +05:30
"is_closed" : true ,
} ,
) ) .
2021-06-22 00:04:58 +05:30
Update ( & Milestone { } )
if err != nil {
return err
}
_ , err = e . Exec ( "UPDATE `milestone` SET completeness=100*num_closed_issues/(CASE WHEN num_issues > 0 THEN num_issues ELSE 1 END) WHERE id=?" ,
id ,
2019-10-08 03:14:58 +05:30
)
return err
2017-02-09 12:09:26 +05:30
}
2020-08-17 08:37:38 +05:30
// ChangeMilestoneStatusByRepoIDAndID changes a milestone open/closed status if the milestone ID is in the repo.
func ChangeMilestoneStatusByRepoIDAndID ( repoID , milestoneID int64 , isClosed bool ) error {
2021-11-21 21:11:00 +05:30
ctx , committer , err := db . TxContext ( )
if err != nil {
2020-08-17 08:37:38 +05:30
return err
}
2021-11-21 21:11:00 +05:30
defer committer . Close ( )
2020-08-17 08:37:38 +05:30
m := & Milestone {
ID : milestoneID ,
RepoID : repoID ,
}
2022-01-18 00:01:58 +05:30
has , err := db . GetEngine ( ctx ) . ID ( milestoneID ) . Where ( "repo_id = ?" , repoID ) . Get ( m )
2020-08-17 08:37:38 +05:30
if err != nil {
return err
} else if ! has {
return ErrMilestoneNotExist { ID : milestoneID , RepoID : repoID }
}
2022-01-18 00:01:58 +05:30
if err := changeMilestoneStatus ( ctx , m , isClosed ) ; err != nil {
2020-08-17 08:37:38 +05:30
return err
}
2021-11-21 21:11:00 +05:30
return committer . Commit ( )
2020-08-17 08:37:38 +05:30
}
2017-02-09 12:09:26 +05:30
// ChangeMilestoneStatus changes the milestone open/closed status.
func ChangeMilestoneStatus ( m * Milestone , isClosed bool ) ( err error ) {
2021-11-21 21:11:00 +05:30
ctx , committer , err := db . TxContext ( )
if err != nil {
2017-02-09 12:09:26 +05:30
return err
}
2021-11-21 21:11:00 +05:30
defer committer . Close ( )
2017-02-09 12:09:26 +05:30
2022-01-18 00:01:58 +05:30
if err := changeMilestoneStatus ( ctx , m , isClosed ) ; err != nil {
2020-08-17 08:37:38 +05:30
return err
}
2021-11-21 21:11:00 +05:30
return committer . Commit ( )
2020-08-17 08:37:38 +05:30
}
2022-01-18 00:01:58 +05:30
func changeMilestoneStatus ( ctx context . Context , m * Milestone , isClosed bool ) error {
2017-02-09 12:09:26 +05:30
m . IsClosed = isClosed
2019-10-29 08:05:50 +05:30
if isClosed {
m . ClosedDateUnix = timeutil . TimeStampNow ( )
}
2022-01-18 00:01:58 +05:30
count , err := db . GetEngine ( ctx ) . ID ( m . ID ) . Where ( "repo_id = ? AND is_closed = ?" , m . RepoID , ! isClosed ) . Cols ( "is_closed" , "closed_date_unix" ) . Update ( m )
2020-08-17 08:37:38 +05:30
if err != nil {
2017-02-09 12:09:26 +05:30
return err
}
2020-08-17 08:37:38 +05:30
if count < 1 {
return nil
2017-12-18 19:36:51 +05:30
}
2022-01-18 00:01:58 +05:30
return updateRepoMilestoneNum ( ctx , m . RepoID )
2017-02-09 12:09:26 +05:30
}
// DeleteMilestoneByRepoID deletes a milestone from a repository.
func DeleteMilestoneByRepoID ( repoID , id int64 ) error {
2022-04-08 14:41:15 +05:30
m , err := GetMilestoneByRepoID ( db . DefaultContext , repoID , id )
2017-02-09 12:09:26 +05:30
if err != nil {
if IsErrMilestoneNotExist ( err ) {
return nil
}
return err
}
2021-12-10 06:57:50 +05:30
repo , err := repo_model . GetRepositoryByID ( m . RepoID )
2017-02-09 12:09:26 +05:30
if err != nil {
return err
}
2021-11-21 21:11:00 +05:30
ctx , committer , err := db . TxContext ( )
if err != nil {
2017-02-09 12:09:26 +05:30
return err
}
2021-11-21 21:11:00 +05:30
defer committer . Close ( )
sess := db . GetEngine ( ctx )
2017-02-09 12:09:26 +05:30
2017-10-05 10:13:04 +05:30
if _ , err = sess . ID ( m . ID ) . Delete ( new ( Milestone ) ) ; err != nil {
2017-02-09 12:09:26 +05:30
return err
}
2022-06-13 15:07:59 +05:30
numMilestones , err := CountMilestones ( ctx , GetMilestonesOption {
RepoID : repo . ID ,
State : api . StateAll ,
} )
2017-12-18 19:36:51 +05:30
if err != nil {
return err
}
2022-06-13 15:07:59 +05:30
numClosedMilestones , err := CountMilestones ( ctx , GetMilestonesOption {
RepoID : repo . ID ,
State : api . StateClosed ,
} )
2017-12-18 19:36:51 +05:30
if err != nil {
return err
}
repo . NumMilestones = int ( numMilestones )
repo . NumClosedMilestones = int ( numClosedMilestones )
2017-10-05 10:13:04 +05:30
if _ , err = sess . ID ( repo . ID ) . Cols ( "num_milestones, num_closed_milestones" ) . Update ( repo ) ; err != nil {
2017-02-09 12:09:26 +05:30
return err
}
2021-11-21 21:11:00 +05:30
if _ , err = db . Exec ( ctx , "UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?" , m . ID ) ; err != nil {
2017-02-09 12:09:26 +05:30
return err
}
2021-11-21 21:11:00 +05:30
return committer . Commit ( )
2017-02-09 12:09:26 +05:30
}
2019-12-15 19:50:08 +05:30
2020-05-13 03:24:35 +05:30
// MilestoneList is a list of milestones offering additional functionality
type MilestoneList [ ] * Milestone
func ( milestones MilestoneList ) getMilestoneIDs ( ) [ ] int64 {
2021-03-15 00:22:12 +05:30
ids := make ( [ ] int64 , 0 , len ( milestones ) )
2020-05-13 03:24:35 +05:30
for _ , ms := range milestones {
ids = append ( ids , ms . ID )
2020-03-31 13:17:00 +05:30
}
2020-05-13 03:24:35 +05:30
return ids
}
2019-12-15 19:50:08 +05:30
2020-07-28 17:00:40 +05:30
// GetMilestonesOption contain options to get milestones
type GetMilestonesOption struct {
2021-09-24 17:02:56 +05:30
db . ListOptions
2020-07-28 17:00:40 +05:30
RepoID int64
State api . StateType
Name string
SortType string
}
2021-08-12 18:13:08 +05:30
func ( opts GetMilestonesOption ) toCond ( ) builder . Cond {
cond := builder . NewCond ( )
if opts . RepoID != 0 {
cond = cond . And ( builder . Eq { "repo_id" : opts . RepoID } )
}
2020-05-13 03:24:35 +05:30
2020-07-28 17:00:40 +05:30
switch opts . State {
2020-05-13 03:24:35 +05:30
case api . StateClosed :
2021-08-12 18:13:08 +05:30
cond = cond . And ( builder . Eq { "is_closed" : true } )
2020-05-13 03:24:35 +05:30
case api . StateAll :
break
2020-07-28 17:00:40 +05:30
// api.StateOpen:
2020-05-13 03:24:35 +05:30
default :
2021-08-12 18:13:08 +05:30
cond = cond . And ( builder . Eq { "is_closed" : false } )
2019-12-15 19:50:08 +05:30
}
2020-07-28 17:00:40 +05:30
if len ( opts . Name ) != 0 {
2021-08-12 18:13:08 +05:30
cond = cond . And ( builder . Like { "name" , opts . Name } )
2019-12-15 19:50:08 +05:30
}
2020-05-13 03:24:35 +05:30
2021-08-12 18:13:08 +05:30
return cond
}
// GetMilestones returns milestones filtered by GetMilestonesOption's
func GetMilestones ( opts GetMilestonesOption ) ( MilestoneList , int64 , error ) {
2021-09-23 21:15:36 +05:30
sess := db . GetEngine ( db . DefaultContext ) . Where ( opts . toCond ( ) )
2021-08-12 18:13:08 +05:30
2020-07-28 17:00:40 +05:30
if opts . Page != 0 {
2021-09-24 17:02:56 +05:30
sess = db . SetSessionPagination ( sess , & opts )
2020-05-13 03:24:35 +05:30
}
2020-07-28 17:00:40 +05:30
switch opts . SortType {
2020-05-13 03:24:35 +05:30
case "furthestduedate" :
sess . Desc ( "deadline_unix" )
case "leastcomplete" :
sess . Asc ( "completeness" )
case "mostcomplete" :
sess . Desc ( "completeness" )
case "leastissues" :
sess . Asc ( "num_issues" )
case "mostissues" :
sess . Desc ( "num_issues" )
2020-07-28 17:00:40 +05:30
case "id" :
sess . Asc ( "id" )
2020-05-13 03:24:35 +05:30
default :
2020-07-28 17:00:40 +05:30
sess . Asc ( "deadline_unix" ) . Asc ( "id" )
2020-05-13 03:24:35 +05:30
}
2020-07-28 17:00:40 +05:30
miles := make ( [ ] * Milestone , 0 , opts . PageSize )
2021-08-12 18:13:08 +05:30
total , err := sess . FindAndCount ( & miles )
return miles , total , err
2020-03-31 13:17:00 +05:30
}
// SearchMilestones search milestones
2021-12-20 10:11:31 +05:30
func SearchMilestones ( repoCond builder . Cond , page int , isClosed bool , sortType , keyword string ) ( MilestoneList , error ) {
2019-12-15 19:50:08 +05:30
miles := make ( [ ] * Milestone , 0 , setting . UI . IssuePagingNum )
2021-09-23 21:15:36 +05:30
sess := db . GetEngine ( db . DefaultContext ) . Where ( "is_closed = ?" , isClosed )
2021-04-08 17:23:59 +05:30
if len ( keyword ) > 0 {
sess = sess . And ( builder . Like { "UPPER(name)" , strings . ToUpper ( keyword ) } )
}
2020-03-31 13:17:00 +05:30
if repoCond . IsValid ( ) {
sess . In ( "repo_id" , builder . Select ( "id" ) . From ( "repository" ) . Where ( repoCond ) )
}
2019-12-15 19:50:08 +05:30
if page > 0 {
sess = sess . Limit ( setting . UI . IssuePagingNum , ( page - 1 ) * setting . UI . IssuePagingNum )
}
switch sortType {
case "furthestduedate" :
sess . Desc ( "deadline_unix" )
case "leastcomplete" :
sess . Asc ( "completeness" )
case "mostcomplete" :
sess . Desc ( "completeness" )
case "leastissues" :
sess . Asc ( "num_issues" )
case "mostissues" :
sess . Desc ( "num_issues" )
default :
sess . Asc ( "deadline_unix" )
}
return miles , sess . Find ( & miles )
}
2020-03-31 13:17:00 +05:30
// GetMilestonesByRepoIDs returns a list of milestones of given repositories and status.
func GetMilestonesByRepoIDs ( repoIDs [ ] int64 , page int , isClosed bool , sortType string ) ( MilestoneList , error ) {
return SearchMilestones (
builder . In ( "repo_id" , repoIDs ) ,
page ,
isClosed ,
sortType ,
2021-04-08 17:23:59 +05:30
"" ,
2020-03-31 13:17:00 +05:30
)
}
2019-12-15 19:50:08 +05:30
// MilestonesStats represents milestone statistic information.
type MilestonesStats struct {
OpenCount , ClosedCount int64
}
2020-03-31 13:17:00 +05:30
// Total returns the total counts of milestones
func ( m MilestonesStats ) Total ( ) int64 {
return m . OpenCount + m . ClosedCount
}
2020-05-13 03:24:35 +05:30
// GetMilestonesStatsByRepoCond returns milestone statistic information for dashboard by given conditions.
func GetMilestonesStatsByRepoCond ( repoCond builder . Cond ) ( * MilestonesStats , error ) {
2019-12-15 19:50:08 +05:30
var err error
stats := & MilestonesStats { }
2021-09-23 21:15:36 +05:30
sess := db . GetEngine ( db . DefaultContext ) . Where ( "is_closed = ?" , false )
2020-03-31 13:17:00 +05:30
if repoCond . IsValid ( ) {
sess . And ( builder . In ( "repo_id" , builder . Select ( "id" ) . From ( "repository" ) . Where ( repoCond ) ) )
}
stats . OpenCount , err = sess . Count ( new ( Milestone ) )
2019-12-15 19:50:08 +05:30
if err != nil {
return nil , err
}
2020-03-31 13:17:00 +05:30
2021-09-23 21:15:36 +05:30
sess = db . GetEngine ( db . DefaultContext ) . Where ( "is_closed = ?" , true )
2020-03-31 13:17:00 +05:30
if repoCond . IsValid ( ) {
sess . And ( builder . In ( "repo_id" , builder . Select ( "id" ) . From ( "repository" ) . Where ( repoCond ) ) )
}
stats . ClosedCount , err = sess . Count ( new ( Milestone ) )
2019-12-15 19:50:08 +05:30
if err != nil {
return nil , err
}
return stats , nil
}
2020-05-13 03:24:35 +05:30
2021-04-08 17:23:59 +05:30
// GetMilestonesStatsByRepoCondAndKw returns milestone statistic information for dashboard by given repo conditions and name keyword.
func GetMilestonesStatsByRepoCondAndKw ( repoCond builder . Cond , keyword string ) ( * MilestonesStats , error ) {
var err error
stats := & MilestonesStats { }
2021-09-23 21:15:36 +05:30
sess := db . GetEngine ( db . DefaultContext ) . Where ( "is_closed = ?" , false )
2021-04-08 17:23:59 +05:30
if len ( keyword ) > 0 {
sess = sess . And ( builder . Like { "UPPER(name)" , strings . ToUpper ( keyword ) } )
}
if repoCond . IsValid ( ) {
sess . And ( builder . In ( "repo_id" , builder . Select ( "id" ) . From ( "repository" ) . Where ( repoCond ) ) )
}
stats . OpenCount , err = sess . Count ( new ( Milestone ) )
if err != nil {
return nil , err
}
2021-09-23 21:15:36 +05:30
sess = db . GetEngine ( db . DefaultContext ) . Where ( "is_closed = ?" , true )
2021-04-08 17:23:59 +05:30
if len ( keyword ) > 0 {
sess = sess . And ( builder . Like { "UPPER(name)" , strings . ToUpper ( keyword ) } )
}
if repoCond . IsValid ( ) {
sess . And ( builder . In ( "repo_id" , builder . Select ( "id" ) . From ( "repository" ) . Where ( repoCond ) ) )
}
stats . ClosedCount , err = sess . Count ( new ( Milestone ) )
if err != nil {
return nil , err
}
return stats , nil
}
2022-06-13 15:07:59 +05:30
// CountMilestones returns number of milestones in given repository with other options
func CountMilestones ( ctx context . Context , opts GetMilestonesOption ) ( int64 , error ) {
2022-05-20 19:38:52 +05:30
return db . GetEngine ( ctx ) .
2022-06-13 15:07:59 +05:30
Where ( opts . toCond ( ) ) .
2020-05-13 03:24:35 +05:30
Count ( new ( Milestone ) )
}
// CountMilestonesByRepoCond map from repo conditions to number of milestones matching the options`
func CountMilestonesByRepoCond ( repoCond builder . Cond , isClosed bool ) ( map [ int64 ] int64 , error ) {
2021-09-23 21:15:36 +05:30
sess := db . GetEngine ( db . DefaultContext ) . Where ( "is_closed = ?" , isClosed )
2020-05-13 03:24:35 +05:30
if repoCond . IsValid ( ) {
sess . In ( "repo_id" , builder . Select ( "id" ) . From ( "repository" ) . Where ( repoCond ) )
}
countsSlice := make ( [ ] * struct {
RepoID int64
Count int64
} , 0 , 10 )
if err := sess . GroupBy ( "repo_id" ) .
Select ( "repo_id AS repo_id, COUNT(*) AS count" ) .
Table ( "milestone" ) .
Find ( & countsSlice ) ; err != nil {
return nil , err
}
countMap := make ( map [ int64 ] int64 , len ( countsSlice ) )
for _ , c := range countsSlice {
countMap [ c . RepoID ] = c . Count
}
return countMap , nil
}
2021-04-08 17:23:59 +05:30
// CountMilestonesByRepoCondAndKw map from repo conditions and the keyword of milestones' name to number of milestones matching the options`
func CountMilestonesByRepoCondAndKw ( repoCond builder . Cond , keyword string , isClosed bool ) ( map [ int64 ] int64 , error ) {
2021-09-23 21:15:36 +05:30
sess := db . GetEngine ( db . DefaultContext ) . Where ( "is_closed = ?" , isClosed )
2021-04-08 17:23:59 +05:30
if len ( keyword ) > 0 {
sess = sess . And ( builder . Like { "UPPER(name)" , strings . ToUpper ( keyword ) } )
}
if repoCond . IsValid ( ) {
sess . In ( "repo_id" , builder . Select ( "id" ) . From ( "repository" ) . Where ( repoCond ) )
}
countsSlice := make ( [ ] * struct {
RepoID int64
Count int64
} , 0 , 10 )
if err := sess . GroupBy ( "repo_id" ) .
Select ( "repo_id AS repo_id, COUNT(*) AS count" ) .
Table ( "milestone" ) .
Find ( & countsSlice ) ; err != nil {
return nil , err
}
countMap := make ( map [ int64 ] int64 , len ( countsSlice ) )
for _ , c := range countsSlice {
countMap [ c . RepoID ] = c . Count
}
return countMap , nil
}
2022-01-18 00:01:58 +05:30
func updateRepoMilestoneNum ( ctx context . Context , repoID int64 ) error {
_ , err := db . GetEngine ( ctx ) . Exec ( "UPDATE `repository` SET num_milestones=(SELECT count(*) FROM milestone WHERE repo_id=?),num_closed_milestones=(SELECT count(*) FROM milestone WHERE repo_id=? AND is_closed=?) WHERE id=?" ,
2020-05-13 03:24:35 +05:30
repoID ,
repoID ,
true ,
repoID ,
)
return err
}
// _____ _ _ _____ _
// |_ _| __ __ _ ___| | _____ __| |_ _(_)_ __ ___ ___ ___
// | || '__/ _` |/ __| |/ / _ \/ _` | | | | | '_ ` _ \ / _ \/ __|
// | || | | (_| | (__| < __/ (_| | | | | | | | | | | __/\__ \
// |_||_| \__,_|\___|_|\_\___|\__,_| |_| |_|_| |_| |_|\___||___/
//
2022-05-20 19:38:52 +05:30
func ( milestones MilestoneList ) loadTotalTrackedTimes ( ctx context . Context ) error {
2020-05-13 03:24:35 +05:30
type totalTimesByMilestone struct {
MilestoneID int64
Time int64
}
if len ( milestones ) == 0 {
return nil
}
2021-03-15 00:22:12 +05:30
trackedTimes := make ( map [ int64 ] int64 , len ( milestones ) )
2020-05-13 03:24:35 +05:30
// Get total tracked time by milestone_id
2022-05-20 19:38:52 +05:30
rows , err := db . GetEngine ( ctx ) . Table ( "issue" ) .
2020-05-13 03:24:35 +05:30
Join ( "INNER" , "milestone" , "issue.milestone_id = milestone.id" ) .
Join ( "LEFT" , "tracked_time" , "tracked_time.issue_id = issue.id" ) .
Where ( "tracked_time.deleted = ?" , false ) .
Select ( "milestone_id, sum(time) as time" ) .
In ( "milestone_id" , milestones . getMilestoneIDs ( ) ) .
GroupBy ( "milestone_id" ) .
Rows ( new ( totalTimesByMilestone ) )
if err != nil {
return err
}
defer rows . Close ( )
for rows . Next ( ) {
var totalTime totalTimesByMilestone
err = rows . Scan ( & totalTime )
if err != nil {
return err
}
trackedTimes [ totalTime . MilestoneID ] = totalTime . Time
}
for _ , milestone := range milestones {
milestone . TotalTrackedTime = trackedTimes [ milestone . ID ]
}
return nil
}
2022-05-20 19:38:52 +05:30
func ( m * Milestone ) loadTotalTrackedTime ( ctx context . Context ) error {
2020-05-13 03:24:35 +05:30
type totalTimesByMilestone struct {
MilestoneID int64
Time int64
}
totalTime := & totalTimesByMilestone { MilestoneID : m . ID }
2022-05-20 19:38:52 +05:30
has , err := db . GetEngine ( ctx ) . Table ( "issue" ) .
2020-05-13 03:24:35 +05:30
Join ( "INNER" , "milestone" , "issue.milestone_id = milestone.id" ) .
Join ( "LEFT" , "tracked_time" , "tracked_time.issue_id = issue.id" ) .
Where ( "tracked_time.deleted = ?" , false ) .
Select ( "milestone_id, sum(time) as time" ) .
Where ( "milestone_id = ?" , m . ID ) .
GroupBy ( "milestone_id" ) .
Get ( totalTime )
if err != nil {
return err
} else if ! has {
return nil
}
m . TotalTrackedTime = totalTime . Time
return nil
}
// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request
func ( milestones MilestoneList ) LoadTotalTrackedTimes ( ) error {
2022-05-20 19:38:52 +05:30
return milestones . loadTotalTrackedTimes ( db . DefaultContext )
2020-05-13 03:24:35 +05:30
}
// LoadTotalTrackedTime loads the tracked time for the milestone
func ( m * Milestone ) LoadTotalTrackedTime ( ) error {
2022-05-20 19:38:52 +05:30
return m . loadTotalTrackedTime ( db . DefaultContext )
2020-05-13 03:24:35 +05:30
}