Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

issue_tracked_time.go 7.8KB

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