You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

issue_stopwatch.go 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "context"
  7. "fmt"
  8. "time"
  9. "code.gitea.io/gitea/models/db"
  10. user_model "code.gitea.io/gitea/models/user"
  11. "code.gitea.io/gitea/modules/timeutil"
  12. "code.gitea.io/gitea/modules/util"
  13. )
  14. // ErrIssueStopwatchNotExist represents an error that stopwatch is not exist
  15. type ErrIssueStopwatchNotExist struct {
  16. UserID int64
  17. IssueID int64
  18. }
  19. func (err ErrIssueStopwatchNotExist) Error() string {
  20. return fmt.Sprintf("issue stopwatch doesn't exist[uid: %d, issue_id: %d", err.UserID, err.IssueID)
  21. }
  22. // ErrIssueStopwatchAlreadyExist represents an error that stopwatch is already exist
  23. type ErrIssueStopwatchAlreadyExist struct {
  24. UserID int64
  25. IssueID int64
  26. }
  27. func (err ErrIssueStopwatchAlreadyExist) Error() string {
  28. return fmt.Sprintf("issue stopwatch already exists[uid: %d, issue_id: %d", err.UserID, err.IssueID)
  29. }
  30. // Stopwatch represents a stopwatch for time tracking.
  31. type Stopwatch struct {
  32. ID int64 `xorm:"pk autoincr"`
  33. IssueID int64 `xorm:"INDEX"`
  34. UserID int64 `xorm:"INDEX"`
  35. CreatedUnix timeutil.TimeStamp `xorm:"created"`
  36. }
  37. func init() {
  38. db.RegisterModel(new(Stopwatch))
  39. }
  40. // Seconds returns the amount of time passed since creation, based on local server time
  41. func (s Stopwatch) Seconds() int64 {
  42. return int64(timeutil.TimeStampNow() - s.CreatedUnix)
  43. }
  44. // Duration returns a human-readable duration string based on local server time
  45. func (s Stopwatch) Duration() string {
  46. return util.SecToTime(s.Seconds())
  47. }
  48. func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
  49. sw = new(Stopwatch)
  50. exists, err = db.GetEngine(ctx).
  51. Where("user_id = ?", userID).
  52. And("issue_id = ?", issueID).
  53. Get(sw)
  54. return
  55. }
  56. // UserIDCount is a simple coalition of UserID and Count
  57. type UserStopwatch struct {
  58. UserID int64
  59. StopWatches []*Stopwatch
  60. }
  61. // GetUIDsAndNotificationCounts between the two provided times
  62. func GetUIDsAndStopwatch() ([]*UserStopwatch, error) {
  63. sws := []*Stopwatch{}
  64. if err := db.GetEngine(db.DefaultContext).Find(&sws); err != nil {
  65. return nil, err
  66. }
  67. if len(sws) == 0 {
  68. return []*UserStopwatch{}, nil
  69. }
  70. lastUserID := int64(-1)
  71. res := []*UserStopwatch{}
  72. for _, sw := range sws {
  73. if lastUserID == sw.UserID {
  74. lastUserStopwatch := res[len(res)-1]
  75. lastUserStopwatch.StopWatches = append(lastUserStopwatch.StopWatches, sw)
  76. } else {
  77. res = append(res, &UserStopwatch{
  78. UserID: sw.UserID,
  79. StopWatches: []*Stopwatch{sw},
  80. })
  81. }
  82. }
  83. return res, nil
  84. }
  85. // GetUserStopwatches return list of all stopwatches of a user
  86. func GetUserStopwatches(userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) {
  87. sws := make([]*Stopwatch, 0, 8)
  88. sess := db.GetEngine(db.DefaultContext).Where("stopwatch.user_id = ?", userID)
  89. if listOptions.Page != 0 {
  90. sess = db.SetSessionPagination(sess, &listOptions)
  91. }
  92. err := sess.Find(&sws)
  93. if err != nil {
  94. return nil, err
  95. }
  96. return sws, nil
  97. }
  98. // CountUserStopwatches return count of all stopwatches of a user
  99. func CountUserStopwatches(userID int64) (int64, error) {
  100. return db.GetEngine(db.DefaultContext).Where("user_id = ?", userID).Count(&Stopwatch{})
  101. }
  102. // StopwatchExists returns true if the stopwatch exists
  103. func StopwatchExists(userID, issueID int64) bool {
  104. _, exists, _ := getStopwatch(db.DefaultContext, userID, issueID)
  105. return exists
  106. }
  107. // HasUserStopwatch returns true if the user has a stopwatch
  108. func HasUserStopwatch(userID int64) (exists bool, sw *Stopwatch, err error) {
  109. return hasUserStopwatch(db.GetEngine(db.DefaultContext), userID)
  110. }
  111. func hasUserStopwatch(e db.Engine, userID int64) (exists bool, sw *Stopwatch, err error) {
  112. sw = new(Stopwatch)
  113. exists, err = e.
  114. Where("user_id = ?", userID).
  115. Get(sw)
  116. return
  117. }
  118. // FinishIssueStopwatchIfPossible if stopwatch exist then finish it otherwise ignore
  119. func FinishIssueStopwatchIfPossible(ctx context.Context, user *user_model.User, issue *Issue) error {
  120. _, exists, err := getStopwatch(ctx, user.ID, issue.ID)
  121. if err != nil {
  122. return err
  123. }
  124. if !exists {
  125. return nil
  126. }
  127. return FinishIssueStopwatch(ctx, user, issue)
  128. }
  129. // CreateOrStopIssueStopwatch create an issue stopwatch if it's not exist, otherwise finish it
  130. func CreateOrStopIssueStopwatch(user *user_model.User, issue *Issue) error {
  131. _, exists, err := getStopwatch(db.DefaultContext, user.ID, issue.ID)
  132. if err != nil {
  133. return err
  134. }
  135. if exists {
  136. return FinishIssueStopwatch(db.DefaultContext, user, issue)
  137. }
  138. return CreateIssueStopwatch(db.DefaultContext, user, issue)
  139. }
  140. // FinishIssueStopwatch if stopwatch exist then finish it otherwise return an error
  141. func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
  142. sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
  143. if err != nil {
  144. return err
  145. }
  146. if !exists {
  147. return ErrIssueStopwatchNotExist{
  148. UserID: user.ID,
  149. IssueID: issue.ID,
  150. }
  151. }
  152. // Create tracked time out of the time difference between start date and actual date
  153. timediff := time.Now().Unix() - int64(sw.CreatedUnix)
  154. // Create TrackedTime
  155. tt := &TrackedTime{
  156. Created: time.Now(),
  157. IssueID: issue.ID,
  158. UserID: user.ID,
  159. Time: timediff,
  160. }
  161. if err := db.Insert(ctx, tt); err != nil {
  162. return err
  163. }
  164. if err := issue.LoadRepo(ctx); err != nil {
  165. return err
  166. }
  167. if _, err := CreateCommentCtx(ctx, &CreateCommentOptions{
  168. Doer: user,
  169. Issue: issue,
  170. Repo: issue.Repo,
  171. Content: util.SecToTime(timediff),
  172. Type: CommentTypeStopTracking,
  173. TimeID: tt.ID,
  174. }); err != nil {
  175. return err
  176. }
  177. _, err = db.GetEngine(ctx).Delete(sw)
  178. return err
  179. }
  180. // CreateIssueStopwatch creates a stopwatch if not exist, otherwise return an error
  181. func CreateIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
  182. e := db.GetEngine(ctx)
  183. if err := issue.LoadRepo(ctx); err != nil {
  184. return err
  185. }
  186. // if another stopwatch is running: stop it
  187. exists, sw, err := hasUserStopwatch(e, user.ID)
  188. if err != nil {
  189. return err
  190. }
  191. if exists {
  192. issue, err := getIssueByID(e, sw.IssueID)
  193. if err != nil {
  194. return err
  195. }
  196. if err := FinishIssueStopwatch(ctx, user, issue); err != nil {
  197. return err
  198. }
  199. }
  200. // Create stopwatch
  201. sw = &Stopwatch{
  202. UserID: user.ID,
  203. IssueID: issue.ID,
  204. }
  205. if err := db.Insert(ctx, sw); err != nil {
  206. return err
  207. }
  208. if err := issue.LoadRepo(ctx); err != nil {
  209. return err
  210. }
  211. if _, err := CreateCommentCtx(ctx, &CreateCommentOptions{
  212. Doer: user,
  213. Issue: issue,
  214. Repo: issue.Repo,
  215. Type: CommentTypeStartTracking,
  216. }); err != nil {
  217. return err
  218. }
  219. return nil
  220. }
  221. // CancelStopwatch removes the given stopwatch and logs it into issue's timeline.
  222. func CancelStopwatch(user *user_model.User, issue *Issue) error {
  223. ctx, committer, err := db.TxContext()
  224. if err != nil {
  225. return err
  226. }
  227. defer committer.Close()
  228. if err := cancelStopwatch(ctx, user, issue); err != nil {
  229. return err
  230. }
  231. return committer.Commit()
  232. }
  233. func cancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
  234. e := db.GetEngine(ctx)
  235. sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
  236. if err != nil {
  237. return err
  238. }
  239. if exists {
  240. if _, err := e.Delete(sw); err != nil {
  241. return err
  242. }
  243. if err := issue.LoadRepo(ctx); err != nil {
  244. return err
  245. }
  246. if _, err := CreateCommentCtx(ctx, &CreateCommentOptions{
  247. Doer: user,
  248. Issue: issue,
  249. Repo: issue.Repo,
  250. Type: CommentTypeCancelTracking,
  251. }); err != nil {
  252. return err
  253. }
  254. }
  255. return nil
  256. }