Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

issue_tracked_time.go 8.5KB

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