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.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  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(ctx context.Context, userID int64) (exists bool, sw *Stopwatch, err error) {
  109. sw = new(Stopwatch)
  110. exists, err = db.GetEngine(ctx).
  111. Where("user_id = ?", userID).
  112. Get(sw)
  113. return
  114. }
  115. // FinishIssueStopwatchIfPossible if stopwatch exist then finish it otherwise ignore
  116. func FinishIssueStopwatchIfPossible(ctx context.Context, user *user_model.User, issue *Issue) error {
  117. _, exists, err := getStopwatch(ctx, user.ID, issue.ID)
  118. if err != nil {
  119. return err
  120. }
  121. if !exists {
  122. return nil
  123. }
  124. return FinishIssueStopwatch(ctx, user, issue)
  125. }
  126. // CreateOrStopIssueStopwatch create an issue stopwatch if it's not exist, otherwise finish it
  127. func CreateOrStopIssueStopwatch(user *user_model.User, issue *Issue) error {
  128. _, exists, err := getStopwatch(db.DefaultContext, user.ID, issue.ID)
  129. if err != nil {
  130. return err
  131. }
  132. if exists {
  133. return FinishIssueStopwatch(db.DefaultContext, user, issue)
  134. }
  135. return CreateIssueStopwatch(db.DefaultContext, user, issue)
  136. }
  137. // FinishIssueStopwatch if stopwatch exist then finish it otherwise return an error
  138. func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
  139. sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
  140. if err != nil {
  141. return err
  142. }
  143. if !exists {
  144. return ErrIssueStopwatchNotExist{
  145. UserID: user.ID,
  146. IssueID: issue.ID,
  147. }
  148. }
  149. // Create tracked time out of the time difference between start date and actual date
  150. timediff := time.Now().Unix() - int64(sw.CreatedUnix)
  151. // Create TrackedTime
  152. tt := &TrackedTime{
  153. Created: time.Now(),
  154. IssueID: issue.ID,
  155. UserID: user.ID,
  156. Time: timediff,
  157. }
  158. if err := db.Insert(ctx, tt); err != nil {
  159. return err
  160. }
  161. if err := issue.LoadRepo(ctx); err != nil {
  162. return err
  163. }
  164. if _, err := CreateCommentCtx(ctx, &CreateCommentOptions{
  165. Doer: user,
  166. Issue: issue,
  167. Repo: issue.Repo,
  168. Content: util.SecToTime(timediff),
  169. Type: CommentTypeStopTracking,
  170. TimeID: tt.ID,
  171. }); err != nil {
  172. return err
  173. }
  174. _, err = db.DeleteByBean(ctx, sw)
  175. return err
  176. }
  177. // CreateIssueStopwatch creates a stopwatch if not exist, otherwise return an error
  178. func CreateIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
  179. if err := issue.LoadRepo(ctx); err != nil {
  180. return err
  181. }
  182. // if another stopwatch is running: stop it
  183. exists, sw, err := HasUserStopwatch(ctx, user.ID)
  184. if err != nil {
  185. return err
  186. }
  187. if exists {
  188. issue, err := getIssueByID(ctx, sw.IssueID)
  189. if err != nil {
  190. return err
  191. }
  192. if err := FinishIssueStopwatch(ctx, user, issue); err != nil {
  193. return err
  194. }
  195. }
  196. // Create stopwatch
  197. sw = &Stopwatch{
  198. UserID: user.ID,
  199. IssueID: issue.ID,
  200. }
  201. if err := db.Insert(ctx, sw); err != nil {
  202. return err
  203. }
  204. if err := issue.LoadRepo(ctx); err != nil {
  205. return err
  206. }
  207. if _, err := CreateCommentCtx(ctx, &CreateCommentOptions{
  208. Doer: user,
  209. Issue: issue,
  210. Repo: issue.Repo,
  211. Type: CommentTypeStartTracking,
  212. }); err != nil {
  213. return err
  214. }
  215. return nil
  216. }
  217. // CancelStopwatch removes the given stopwatch and logs it into issue's timeline.
  218. func CancelStopwatch(user *user_model.User, issue *Issue) error {
  219. ctx, committer, err := db.TxContext()
  220. if err != nil {
  221. return err
  222. }
  223. defer committer.Close()
  224. if err := cancelStopwatch(ctx, user, issue); err != nil {
  225. return err
  226. }
  227. return committer.Commit()
  228. }
  229. func cancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
  230. e := db.GetEngine(ctx)
  231. sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
  232. if err != nil {
  233. return err
  234. }
  235. if exists {
  236. if _, err := e.Delete(sw); err != nil {
  237. return err
  238. }
  239. if err := issue.LoadRepo(ctx); err != nil {
  240. return err
  241. }
  242. if _, err := CreateCommentCtx(ctx, &CreateCommentOptions{
  243. Doer: user,
  244. Issue: issue,
  245. Repo: issue.Repo,
  246. Type: CommentTypeCancelTracking,
  247. }); err != nil {
  248. return err
  249. }
  250. }
  251. return nil
  252. }