2017-09-12 09:48:13 +03:00
// 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.
package models
import (
"time"
2017-12-14 02:45:31 +03:00
"code.gitea.io/gitea/modules/setting"
2017-11-01 05:25:14 +03:00
2019-06-23 18:22:43 +03:00
"xorm.io/builder"
2017-09-12 09:48:13 +03:00
)
// TrackedTime represents a time that was spent for a specific issue.
type TrackedTime struct {
2019-12-27 23:30:58 +03:00
ID int64 ` xorm:"pk autoincr" `
IssueID int64 ` xorm:"INDEX" `
Issue * Issue ` xorm:"-" `
UserID int64 ` xorm:"INDEX" `
User * User ` xorm:"-" `
Created time . Time ` xorm:"-" `
CreatedUnix int64 ` xorm:"created" `
Time int64 ` xorm:"NOT NULL" `
Deleted bool ` xorm:"NOT NULL DEFAULT false" `
2017-09-12 09:48:13 +03:00
}
2019-12-27 23:30:58 +03:00
// TrackedTimeList is a List of TrackedTime's
type TrackedTimeList [ ] * TrackedTime
2017-10-01 19:52:35 +03:00
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func ( t * TrackedTime ) AfterLoad ( ) {
2019-08-15 17:46:21 +03:00
t . Created = time . Unix ( t . CreatedUnix , 0 ) . In ( setting . DefaultUILocation )
2017-09-12 09:48:13 +03:00
}
2019-12-27 23:30:58 +03:00
// LoadAttributes load Issue, User
func ( t * TrackedTime ) LoadAttributes ( ) ( err error ) {
return t . loadAttributes ( x )
}
func ( t * TrackedTime ) loadAttributes ( e Engine ) ( err error ) {
if t . Issue == nil {
t . Issue , err = getIssueByID ( e , t . IssueID )
if err != nil {
return
}
err = t . Issue . loadRepo ( e )
if err != nil {
return
}
}
if t . User == nil {
t . User , err = getUserByID ( e , t . UserID )
if err != nil {
return
}
}
return
}
// LoadAttributes load Issue, User
func ( tl TrackedTimeList ) LoadAttributes ( ) ( err error ) {
for _ , t := range tl {
if err = t . LoadAttributes ( ) ; err != nil {
return err
}
}
return
}
2017-09-12 09:48:13 +03:00
// FindTrackedTimesOptions represent the filters for tracked times. If an ID is 0 it will be ignored.
type FindTrackedTimesOptions struct {
2020-01-24 22:00:29 +03:00
ListOptions
2020-01-09 00:14:00 +03:00
IssueID int64
UserID int64
RepositoryID int64
MilestoneID int64
CreatedAfterUnix int64
CreatedBeforeUnix int64
2017-09-12 09:48:13 +03:00
}
// ToCond will convert each condition into a xorm-Cond
func ( opts * FindTrackedTimesOptions ) ToCond ( ) builder . Cond {
2019-12-27 23:30:58 +03:00
cond := builder . NewCond ( ) . And ( builder . Eq { "tracked_time.deleted" : false } )
2017-09-12 09:48:13 +03:00
if opts . IssueID != 0 {
cond = cond . And ( builder . Eq { "issue_id" : opts . IssueID } )
}
if opts . UserID != 0 {
cond = cond . And ( builder . Eq { "user_id" : opts . UserID } )
}
if opts . RepositoryID != 0 {
cond = cond . And ( builder . Eq { "issue.repo_id" : opts . RepositoryID } )
}
2018-04-29 08:58:47 +03:00
if opts . MilestoneID != 0 {
cond = cond . And ( builder . Eq { "issue.milestone_id" : opts . MilestoneID } )
}
2020-01-09 00:14:00 +03:00
if opts . CreatedAfterUnix != 0 {
cond = cond . And ( builder . Gte { "tracked_time.created_unix" : opts . CreatedAfterUnix } )
}
if opts . CreatedBeforeUnix != 0 {
cond = cond . And ( builder . Lte { "tracked_time.created_unix" : opts . CreatedBeforeUnix } )
}
2017-09-12 09:48:13 +03:00
return cond
}
2018-04-29 08:58:47 +03:00
// ToSession will convert the given options to a xorm Session by using the conditions from ToCond and joining with issue table if required
2020-01-24 22:00:29 +03:00
func ( opts * FindTrackedTimesOptions ) ToSession ( e Engine ) Engine {
sess := e
2018-04-29 08:58:47 +03:00
if opts . RepositoryID > 0 || opts . MilestoneID > 0 {
2020-01-24 22:00:29 +03:00
sess = e . Join ( "INNER" , "issue" , "issue.id = tracked_time.issue_id" )
2018-04-29 08:58:47 +03:00
}
2020-01-24 22:00:29 +03:00
sess = sess . Where ( opts . ToCond ( ) )
if opts . Page != 0 {
sess = opts . setEnginePagination ( sess )
}
return sess
2018-04-29 08:58:47 +03:00
}
2019-12-27 23:30:58 +03:00
func getTrackedTimes ( e Engine , options FindTrackedTimesOptions ) ( trackedTimes TrackedTimeList , err error ) {
err = options . ToSession ( e ) . Find ( & trackedTimes )
2017-09-12 09:48:13 +03:00
return
}
2019-12-27 23:30:58 +03:00
// GetTrackedTimes returns all tracked times that fit to the given options.
func GetTrackedTimes ( opts FindTrackedTimesOptions ) ( TrackedTimeList , error ) {
return getTrackedTimes ( x , opts )
}
func getTrackedSeconds ( e Engine , opts FindTrackedTimesOptions ) ( trackedSeconds int64 , err error ) {
return opts . ToSession ( e ) . SumInt ( & TrackedTime { } , "time" )
}
// GetTrackedSeconds return sum of seconds
func GetTrackedSeconds ( opts FindTrackedTimesOptions ) ( int64 , error ) {
return getTrackedSeconds ( x , opts )
}
2017-09-12 09:48:13 +03:00
// AddTime will add the given time (in seconds) to the issue
2019-12-27 23:30:58 +03:00
func AddTime ( user * User , issue * Issue , amount int64 , created time . Time ) ( * TrackedTime , error ) {
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return nil , err
2017-09-12 09:48:13 +03:00
}
2019-12-27 23:30:58 +03:00
t , err := addTime ( sess , user , issue , amount , created )
if err != nil {
2017-09-12 09:48:13 +03:00
return nil , err
}
2019-12-27 23:30:58 +03:00
if err := issue . loadRepo ( sess ) ; err != nil {
2018-12-13 18:55:43 +03:00
return nil , err
}
2019-12-27 23:30:58 +03:00
if _ , err := createComment ( sess , & CreateCommentOptions {
2017-09-12 09:48:13 +03:00
Issue : issue ,
Repo : issue . Repo ,
Doer : user ,
2019-12-27 23:30:58 +03:00
Content : SecToTime ( amount ) ,
2017-09-12 09:48:13 +03:00
Type : CommentTypeAddTimeManual ,
} ) ; err != nil {
return nil , err
}
2019-12-27 23:30:58 +03:00
return t , sess . Commit ( )
}
func addTime ( e Engine , user * User , issue * Issue , amount int64 , created time . Time ) ( * TrackedTime , error ) {
if created . IsZero ( ) {
created = time . Now ( )
}
tt := & TrackedTime {
IssueID : issue . ID ,
UserID : user . ID ,
Time : amount ,
Created : created ,
}
if _ , err := e . Insert ( tt ) ; err != nil {
return nil , err
}
2017-09-12 09:48:13 +03:00
return tt , nil
}
// TotalTimes returns the spent time for each user by an issue
func TotalTimes ( options FindTrackedTimesOptions ) ( map [ * User ] string , error ) {
trackedTimes , err := GetTrackedTimes ( options )
if err != nil {
return nil , err
}
//Adding total time per user ID
totalTimesByUser := make ( map [ int64 ] int64 )
for _ , t := range trackedTimes {
totalTimesByUser [ t . UserID ] += t . Time
}
totalTimes := make ( map [ * User ] string )
//Fetching User and making time human readable
for userID , total := range totalTimesByUser {
user , err := GetUserByID ( userID )
if err != nil {
if IsErrUserNotExist ( err ) {
continue
}
return nil , err
}
2018-04-29 08:58:47 +03:00
totalTimes [ user ] = SecToTime ( total )
2017-09-12 09:48:13 +03:00
}
return totalTimes , nil
}
2019-12-27 23:30:58 +03:00
// DeleteIssueUserTimes deletes times for issue
func DeleteIssueUserTimes ( issue * Issue , user * User ) error {
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return err
}
opts := FindTrackedTimesOptions {
IssueID : issue . ID ,
UserID : user . ID ,
}
removedTime , err := deleteTimes ( sess , opts )
if err != nil {
return err
}
if removedTime == 0 {
return ErrNotExist { }
}
if err := issue . loadRepo ( sess ) ; err != nil {
return err
}
if _ , err := createComment ( sess , & CreateCommentOptions {
Issue : issue ,
Repo : issue . Repo ,
Doer : user ,
Content : "- " + SecToTime ( removedTime ) ,
Type : CommentTypeDeleteTimeManual ,
} ) ; err != nil {
return err
}
return sess . Commit ( )
}
// DeleteTime delete a specific Time
func DeleteTime ( t * TrackedTime ) error {
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return err
}
2020-05-09 17:18:44 +03:00
if err := t . loadAttributes ( sess ) ; err != nil {
return err
}
2019-12-27 23:30:58 +03:00
if err := deleteTime ( sess , t ) ; err != nil {
return err
}
if _ , err := createComment ( sess , & CreateCommentOptions {
Issue : t . Issue ,
Repo : t . Issue . Repo ,
Doer : t . User ,
Content : "- " + SecToTime ( t . Time ) ,
Type : CommentTypeDeleteTimeManual ,
} ) ; err != nil {
return err
}
return sess . Commit ( )
}
func deleteTimes ( e Engine , opts FindTrackedTimesOptions ) ( removedTime int64 , err error ) {
removedTime , err = getTrackedSeconds ( e , opts )
if err != nil || removedTime == 0 {
return
}
_ , err = opts . ToSession ( e ) . Table ( "tracked_time" ) . Cols ( "deleted" ) . Update ( & TrackedTime { Deleted : true } )
return
}
func deleteTime ( e Engine , t * TrackedTime ) error {
if t . Deleted {
return ErrNotExist { ID : t . ID }
}
t . Deleted = true
_ , err := e . ID ( t . ID ) . Cols ( "deleted" ) . Update ( t )
return err
}
// GetTrackedTimeByID returns raw TrackedTime without loading attributes by id
func GetTrackedTimeByID ( id int64 ) ( * TrackedTime , error ) {
2020-05-09 17:18:44 +03:00
time := new ( TrackedTime )
has , err := x . ID ( id ) . Get ( time )
2019-12-27 23:30:58 +03:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrNotExist { ID : id }
}
return time , nil
}