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_tracked_time.go 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  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. "time"
  7. "code.gitea.io/gitea/modules/setting"
  8. "xorm.io/builder"
  9. )
  10. // TrackedTime represents a time that was spent for a specific issue.
  11. type TrackedTime struct {
  12. ID int64 `xorm:"pk autoincr"`
  13. IssueID int64 `xorm:"INDEX"`
  14. Issue *Issue `xorm:"-"`
  15. UserID int64 `xorm:"INDEX"`
  16. User *User `xorm:"-"`
  17. Created time.Time `xorm:"-"`
  18. CreatedUnix int64 `xorm:"created"`
  19. Time int64 `xorm:"NOT NULL"`
  20. Deleted bool `xorm:"NOT NULL DEFAULT false"`
  21. }
  22. // TrackedTimeList is a List of TrackedTime's
  23. type TrackedTimeList []*TrackedTime
  24. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
  25. func (t *TrackedTime) AfterLoad() {
  26. t.Created = time.Unix(t.CreatedUnix, 0).In(setting.DefaultUILocation)
  27. }
  28. // LoadAttributes load Issue, User
  29. func (t *TrackedTime) LoadAttributes() (err error) {
  30. return t.loadAttributes(x)
  31. }
  32. func (t *TrackedTime) loadAttributes(e Engine) (err error) {
  33. if t.Issue == nil {
  34. t.Issue, err = getIssueByID(e, t.IssueID)
  35. if err != nil {
  36. return
  37. }
  38. err = t.Issue.loadRepo(e)
  39. if err != nil {
  40. return
  41. }
  42. }
  43. if t.User == nil {
  44. t.User, err = getUserByID(e, t.UserID)
  45. if err != nil {
  46. return
  47. }
  48. }
  49. return
  50. }
  51. // LoadAttributes load Issue, User
  52. func (tl TrackedTimeList) LoadAttributes() (err error) {
  53. for _, t := range tl {
  54. if err = t.LoadAttributes(); err != nil {
  55. return err
  56. }
  57. }
  58. return
  59. }
  60. // FindTrackedTimesOptions represent the filters for tracked times. If an ID is 0 it will be ignored.
  61. type FindTrackedTimesOptions struct {
  62. ListOptions
  63. IssueID int64
  64. UserID int64
  65. RepositoryID int64
  66. MilestoneID int64
  67. CreatedAfterUnix int64
  68. CreatedBeforeUnix int64
  69. }
  70. // toCond will convert each condition into a xorm-Cond
  71. func (opts *FindTrackedTimesOptions) toCond() builder.Cond {
  72. cond := builder.NewCond().And(builder.Eq{"tracked_time.deleted": false})
  73. if opts.IssueID != 0 {
  74. cond = cond.And(builder.Eq{"issue_id": opts.IssueID})
  75. }
  76. if opts.UserID != 0 {
  77. cond = cond.And(builder.Eq{"user_id": opts.UserID})
  78. }
  79. if opts.RepositoryID != 0 {
  80. cond = cond.And(builder.Eq{"issue.repo_id": opts.RepositoryID})
  81. }
  82. if opts.MilestoneID != 0 {
  83. cond = cond.And(builder.Eq{"issue.milestone_id": opts.MilestoneID})
  84. }
  85. if opts.CreatedAfterUnix != 0 {
  86. cond = cond.And(builder.Gte{"tracked_time.created_unix": opts.CreatedAfterUnix})
  87. }
  88. if opts.CreatedBeforeUnix != 0 {
  89. cond = cond.And(builder.Lte{"tracked_time.created_unix": opts.CreatedBeforeUnix})
  90. }
  91. return cond
  92. }
  93. // toSession will convert the given options to a xorm Session by using the conditions from toCond and joining with issue table if required
  94. func (opts *FindTrackedTimesOptions) toSession(e Engine) Engine {
  95. sess := e
  96. if opts.RepositoryID > 0 || opts.MilestoneID > 0 {
  97. sess = e.Join("INNER", "issue", "issue.id = tracked_time.issue_id")
  98. }
  99. sess = sess.Where(opts.toCond())
  100. if opts.Page != 0 {
  101. sess = opts.setEnginePagination(sess)
  102. }
  103. return sess
  104. }
  105. func getTrackedTimes(e Engine, options *FindTrackedTimesOptions) (trackedTimes TrackedTimeList, err error) {
  106. err = options.toSession(e).Find(&trackedTimes)
  107. return
  108. }
  109. // GetTrackedTimes returns all tracked times that fit to the given options.
  110. func GetTrackedTimes(opts *FindTrackedTimesOptions) (TrackedTimeList, error) {
  111. return getTrackedTimes(x, opts)
  112. }
  113. // CountTrackedTimes returns count of tracked times that fit to the given options.
  114. func CountTrackedTimes(opts *FindTrackedTimesOptions) (int64, error) {
  115. sess := x.Where(opts.toCond())
  116. if opts.RepositoryID > 0 || opts.MilestoneID > 0 {
  117. sess = sess.Join("INNER", "issue", "issue.id = tracked_time.issue_id")
  118. }
  119. return sess.Count(&TrackedTime{})
  120. }
  121. func getTrackedSeconds(e Engine, opts FindTrackedTimesOptions) (trackedSeconds int64, err error) {
  122. return opts.toSession(e).SumInt(&TrackedTime{}, "time")
  123. }
  124. // GetTrackedSeconds return sum of seconds
  125. func GetTrackedSeconds(opts FindTrackedTimesOptions) (int64, error) {
  126. return getTrackedSeconds(x, opts)
  127. }
  128. // AddTime will add the given time (in seconds) to the issue
  129. func AddTime(user *User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) {
  130. sess := x.NewSession()
  131. defer sess.Close()
  132. if err := sess.Begin(); err != nil {
  133. return nil, err
  134. }
  135. t, err := addTime(sess, user, issue, amount, created)
  136. if err != nil {
  137. return nil, err
  138. }
  139. if err := issue.loadRepo(sess); err != nil {
  140. return nil, err
  141. }
  142. if _, err := createComment(sess, &CreateCommentOptions{
  143. Issue: issue,
  144. Repo: issue.Repo,
  145. Doer: user,
  146. Content: SecToTime(amount),
  147. Type: CommentTypeAddTimeManual,
  148. TimeID: t.ID,
  149. }); err != nil {
  150. return nil, err
  151. }
  152. return t, sess.Commit()
  153. }
  154. func addTime(e Engine, user *User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) {
  155. if created.IsZero() {
  156. created = time.Now()
  157. }
  158. tt := &TrackedTime{
  159. IssueID: issue.ID,
  160. UserID: user.ID,
  161. Time: amount,
  162. Created: created,
  163. }
  164. if _, err := e.Insert(tt); err != nil {
  165. return nil, err
  166. }
  167. return tt, nil
  168. }
  169. // TotalTimes returns the spent time for each user by an issue
  170. func TotalTimes(options *FindTrackedTimesOptions) (map[*User]string, error) {
  171. trackedTimes, err := GetTrackedTimes(options)
  172. if err != nil {
  173. return nil, err
  174. }
  175. // Adding total time per user ID
  176. totalTimesByUser := make(map[int64]int64)
  177. for _, t := range trackedTimes {
  178. totalTimesByUser[t.UserID] += t.Time
  179. }
  180. totalTimes := make(map[*User]string)
  181. // Fetching User and making time human readable
  182. for userID, total := range totalTimesByUser {
  183. user, err := GetUserByID(userID)
  184. if err != nil {
  185. if IsErrUserNotExist(err) {
  186. continue
  187. }
  188. return nil, err
  189. }
  190. totalTimes[user] = SecToTime(total)
  191. }
  192. return totalTimes, nil
  193. }
  194. // DeleteIssueUserTimes deletes times for issue
  195. func DeleteIssueUserTimes(issue *Issue, user *User) error {
  196. sess := x.NewSession()
  197. defer sess.Close()
  198. if err := sess.Begin(); err != nil {
  199. return err
  200. }
  201. opts := FindTrackedTimesOptions{
  202. IssueID: issue.ID,
  203. UserID: user.ID,
  204. }
  205. removedTime, err := deleteTimes(sess, opts)
  206. if err != nil {
  207. return err
  208. }
  209. if removedTime == 0 {
  210. return ErrNotExist{}
  211. }
  212. if err := issue.loadRepo(sess); err != nil {
  213. return err
  214. }
  215. if _, err := createComment(sess, &CreateCommentOptions{
  216. Issue: issue,
  217. Repo: issue.Repo,
  218. Doer: user,
  219. Content: "- " + SecToTime(removedTime),
  220. Type: CommentTypeDeleteTimeManual,
  221. }); err != nil {
  222. return err
  223. }
  224. return sess.Commit()
  225. }
  226. // DeleteTime delete a specific Time
  227. func DeleteTime(t *TrackedTime) error {
  228. sess := x.NewSession()
  229. defer sess.Close()
  230. if err := sess.Begin(); err != nil {
  231. return err
  232. }
  233. if err := t.loadAttributes(sess); err != nil {
  234. return err
  235. }
  236. if err := deleteTime(sess, t); err != nil {
  237. return err
  238. }
  239. if _, err := createComment(sess, &CreateCommentOptions{
  240. Issue: t.Issue,
  241. Repo: t.Issue.Repo,
  242. Doer: t.User,
  243. Content: "- " + SecToTime(t.Time),
  244. Type: CommentTypeDeleteTimeManual,
  245. }); err != nil {
  246. return err
  247. }
  248. return sess.Commit()
  249. }
  250. func deleteTimes(e Engine, opts FindTrackedTimesOptions) (removedTime int64, err error) {
  251. removedTime, err = getTrackedSeconds(e, opts)
  252. if err != nil || removedTime == 0 {
  253. return
  254. }
  255. _, err = opts.toSession(e).Table("tracked_time").Cols("deleted").Update(&TrackedTime{Deleted: true})
  256. return
  257. }
  258. func deleteTime(e Engine, t *TrackedTime) error {
  259. if t.Deleted {
  260. return ErrNotExist{ID: t.ID}
  261. }
  262. t.Deleted = true
  263. _, err := e.ID(t.ID).Cols("deleted").Update(t)
  264. return err
  265. }
  266. // GetTrackedTimeByID returns raw TrackedTime without loading attributes by id
  267. func GetTrackedTimeByID(id int64) (*TrackedTime, error) {
  268. time := new(TrackedTime)
  269. has, err := x.ID(id).Get(time)
  270. if err != nil {
  271. return nil, err
  272. } else if !has {
  273. return nil, ErrNotExist{ID: id}
  274. }
  275. return time, nil
  276. }