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 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. "fmt"
  7. "time"
  8. "code.gitea.io/gitea/modules/timeutil"
  9. "xorm.io/xorm"
  10. )
  11. // Stopwatch represents a stopwatch for time tracking.
  12. type Stopwatch struct {
  13. ID int64 `xorm:"pk autoincr"`
  14. IssueID int64 `xorm:"INDEX"`
  15. UserID int64 `xorm:"INDEX"`
  16. CreatedUnix timeutil.TimeStamp `xorm:"created"`
  17. }
  18. // Seconds returns the amount of time passed since creation, based on local server time
  19. func (s Stopwatch) Seconds() int64 {
  20. return int64(timeutil.TimeStampNow() - s.CreatedUnix)
  21. }
  22. // Duration returns a human-readable duration string based on local server time
  23. func (s Stopwatch) Duration() string {
  24. return SecToTime(s.Seconds())
  25. }
  26. func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
  27. sw = new(Stopwatch)
  28. exists, err = e.
  29. Where("user_id = ?", userID).
  30. And("issue_id = ?", issueID).
  31. Get(sw)
  32. return
  33. }
  34. // GetUserStopwatches return list of all stopwatches of a user
  35. func GetUserStopwatches(userID int64, listOptions ListOptions) ([]*Stopwatch, error) {
  36. sws := make([]*Stopwatch, 0, 8)
  37. sess := x.Where("stopwatch.user_id = ?", userID)
  38. if listOptions.Page != 0 {
  39. sess = listOptions.setSessionPagination(sess)
  40. }
  41. err := sess.Find(&sws)
  42. if err != nil {
  43. return nil, err
  44. }
  45. return sws, nil
  46. }
  47. // CountUserStopwatches return count of all stopwatches of a user
  48. func CountUserStopwatches(userID int64) (int64, error) {
  49. return x.Where("user_id = ?", userID).Count(&Stopwatch{})
  50. }
  51. // StopwatchExists returns true if the stopwatch exists
  52. func StopwatchExists(userID, issueID int64) bool {
  53. _, exists, _ := getStopwatch(x, userID, issueID)
  54. return exists
  55. }
  56. // HasUserStopwatch returns true if the user has a stopwatch
  57. func HasUserStopwatch(userID int64) (exists bool, sw *Stopwatch, err error) {
  58. return hasUserStopwatch(x, userID)
  59. }
  60. func hasUserStopwatch(e Engine, userID int64) (exists bool, sw *Stopwatch, err error) {
  61. sw = new(Stopwatch)
  62. exists, err = e.
  63. Where("user_id = ?", userID).
  64. Get(sw)
  65. return
  66. }
  67. // CreateOrStopIssueStopwatch will create or remove a stopwatch and will log it into issue's timeline.
  68. func CreateOrStopIssueStopwatch(user *User, issue *Issue) error {
  69. sess := x.NewSession()
  70. defer sess.Close()
  71. if err := sess.Begin(); err != nil {
  72. return err
  73. }
  74. if err := createOrStopIssueStopwatch(sess, user, issue); err != nil {
  75. return err
  76. }
  77. return sess.Commit()
  78. }
  79. func createOrStopIssueStopwatch(e *xorm.Session, user *User, issue *Issue) error {
  80. sw, exists, err := getStopwatch(e, user.ID, issue.ID)
  81. if err != nil {
  82. return err
  83. }
  84. if err := issue.loadRepo(e); err != nil {
  85. return err
  86. }
  87. if exists {
  88. // Create tracked time out of the time difference between start date and actual date
  89. timediff := time.Now().Unix() - int64(sw.CreatedUnix)
  90. // Create TrackedTime
  91. tt := &TrackedTime{
  92. Created: time.Now(),
  93. IssueID: issue.ID,
  94. UserID: user.ID,
  95. Time: timediff,
  96. }
  97. if _, err := e.Insert(tt); err != nil {
  98. return err
  99. }
  100. if _, err := createComment(e, &CreateCommentOptions{
  101. Doer: user,
  102. Issue: issue,
  103. Repo: issue.Repo,
  104. Content: SecToTime(timediff),
  105. Type: CommentTypeStopTracking,
  106. TimeID: tt.ID,
  107. }); err != nil {
  108. return err
  109. }
  110. if _, err := e.Delete(sw); err != nil {
  111. return err
  112. }
  113. } else {
  114. // if another stopwatch is running: stop it
  115. exists, sw, err := hasUserStopwatch(e, user.ID)
  116. if err != nil {
  117. return err
  118. }
  119. if exists {
  120. issue, err := getIssueByID(e, sw.IssueID)
  121. if err != nil {
  122. return err
  123. }
  124. if err := createOrStopIssueStopwatch(e, user, issue); err != nil {
  125. return err
  126. }
  127. }
  128. // Create stopwatch
  129. sw = &Stopwatch{
  130. UserID: user.ID,
  131. IssueID: issue.ID,
  132. }
  133. if _, err := e.Insert(sw); err != nil {
  134. return err
  135. }
  136. if _, err := createComment(e, &CreateCommentOptions{
  137. Doer: user,
  138. Issue: issue,
  139. Repo: issue.Repo,
  140. Type: CommentTypeStartTracking,
  141. }); err != nil {
  142. return err
  143. }
  144. }
  145. return nil
  146. }
  147. // CancelStopwatch removes the given stopwatch and logs it into issue's timeline.
  148. func CancelStopwatch(user *User, issue *Issue) error {
  149. sess := x.NewSession()
  150. defer sess.Close()
  151. if err := sess.Begin(); err != nil {
  152. return err
  153. }
  154. if err := cancelStopwatch(sess, user, issue); err != nil {
  155. return err
  156. }
  157. return sess.Commit()
  158. }
  159. func cancelStopwatch(e *xorm.Session, user *User, issue *Issue) error {
  160. sw, exists, err := getStopwatch(e, user.ID, issue.ID)
  161. if err != nil {
  162. return err
  163. }
  164. if exists {
  165. if _, err := e.Delete(sw); err != nil {
  166. return err
  167. }
  168. if err := issue.loadRepo(e); err != nil {
  169. return err
  170. }
  171. if _, err := createComment(e, &CreateCommentOptions{
  172. Doer: user,
  173. Issue: issue,
  174. Repo: issue.Repo,
  175. Type: CommentTypeCancelTracking,
  176. }); err != nil {
  177. return err
  178. }
  179. }
  180. return nil
  181. }
  182. // SecToTime converts an amount of seconds to a human-readable string (example: 66s -> 1min 6s)
  183. func SecToTime(duration int64) string {
  184. seconds := duration % 60
  185. minutes := (duration / (60)) % 60
  186. hours := duration / (60 * 60)
  187. var hrs string
  188. if hours > 0 {
  189. hrs = fmt.Sprintf("%dh", hours)
  190. }
  191. if minutes > 0 {
  192. if hours == 0 {
  193. hrs = fmt.Sprintf("%dmin", minutes)
  194. } else {
  195. hrs = fmt.Sprintf("%s %dmin", hrs, minutes)
  196. }
  197. }
  198. if seconds > 0 {
  199. if hours == 0 && minutes == 0 {
  200. hrs = fmt.Sprintf("%ds", seconds)
  201. } else {
  202. hrs = fmt.Sprintf("%s %ds", hrs, seconds)
  203. }
  204. }
  205. return hrs
  206. }