forgejo/models/issue_tracked_time.go
zeripath 9302eba971
DBContext is just a Context (#17100)
* DBContext is just a Context

This PR removes some of the specialness from the DBContext and makes it context
This allows us to simplify the GetEngine code to wrap around any context in future
and means that we can change our loadRepo(e Engine) functions to simply take contexts.

Signed-off-by: Andrew Thornton <art27@cantab.net>

* fix unit tests

Signed-off-by: Andrew Thornton <art27@cantab.net>

* another place that needs to set the initial context

Signed-off-by: Andrew Thornton <art27@cantab.net>

* avoid race

Signed-off-by: Andrew Thornton <art27@cantab.net>

* change attachment error

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-09-23 23:45:36 +08:00

328 lines
8.2 KiB
Go

// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"time"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/setting"
"xorm.io/builder"
)
// TrackedTime represents a time that was spent for a specific issue.
type TrackedTime struct {
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"`
UserID int64 `xorm:"INDEX"`
User *User `xorm:"-"`
Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"created"`
Time int64 `xorm:"NOT NULL"`
Deleted bool `xorm:"NOT NULL DEFAULT false"`
}
func init() {
db.RegisterModel(new(TrackedTime))
}
// TrackedTimeList is a List of TrackedTime's
type TrackedTimeList []*TrackedTime
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (t *TrackedTime) AfterLoad() {
t.Created = time.Unix(t.CreatedUnix, 0).In(setting.DefaultUILocation)
}
// LoadAttributes load Issue, User
func (t *TrackedTime) LoadAttributes() (err error) {
return t.loadAttributes(db.GetEngine(db.DefaultContext))
}
func (t *TrackedTime) loadAttributes(e db.Engine) (err error) {
if t.Issue == nil {
t.Issue, err = getIssueByID(e, t.IssueID)
if err != nil {
return
}
err = t.Issue.loadRepo(e)
if err != nil {
return
}
}
if t.User == nil {
t.User, err = getUserByID(e, t.UserID)
if err != nil {
return
}
}
return
}
// LoadAttributes load Issue, User
func (tl TrackedTimeList) LoadAttributes() (err error) {
for _, t := range tl {
if err = t.LoadAttributes(); err != nil {
return err
}
}
return
}
// FindTrackedTimesOptions represent the filters for tracked times. If an ID is 0 it will be ignored.
type FindTrackedTimesOptions struct {
ListOptions
IssueID int64
UserID int64
RepositoryID int64
MilestoneID int64
CreatedAfterUnix int64
CreatedBeforeUnix int64
}
// toCond will convert each condition into a xorm-Cond
func (opts *FindTrackedTimesOptions) toCond() builder.Cond {
cond := builder.NewCond().And(builder.Eq{"tracked_time.deleted": false})
if opts.IssueID != 0 {
cond = cond.And(builder.Eq{"issue_id": opts.IssueID})
}
if opts.UserID != 0 {
cond = cond.And(builder.Eq{"user_id": opts.UserID})
}
if opts.RepositoryID != 0 {
cond = cond.And(builder.Eq{"issue.repo_id": opts.RepositoryID})
}
if opts.MilestoneID != 0 {
cond = cond.And(builder.Eq{"issue.milestone_id": opts.MilestoneID})
}
if opts.CreatedAfterUnix != 0 {
cond = cond.And(builder.Gte{"tracked_time.created_unix": opts.CreatedAfterUnix})
}
if opts.CreatedBeforeUnix != 0 {
cond = cond.And(builder.Lte{"tracked_time.created_unix": opts.CreatedBeforeUnix})
}
return cond
}
// toSession will convert the given options to a xorm Session by using the conditions from toCond and joining with issue table if required
func (opts *FindTrackedTimesOptions) toSession(e db.Engine) db.Engine {
sess := e
if opts.RepositoryID > 0 || opts.MilestoneID > 0 {
sess = e.Join("INNER", "issue", "issue.id = tracked_time.issue_id")
}
sess = sess.Where(opts.toCond())
if opts.Page != 0 {
sess = setEnginePagination(sess, opts)
}
return sess
}
func getTrackedTimes(e db.Engine, options *FindTrackedTimesOptions) (trackedTimes TrackedTimeList, err error) {
err = options.toSession(e).Find(&trackedTimes)
return
}
// GetTrackedTimes returns all tracked times that fit to the given options.
func GetTrackedTimes(opts *FindTrackedTimesOptions) (TrackedTimeList, error) {
return getTrackedTimes(db.GetEngine(db.DefaultContext), opts)
}
// CountTrackedTimes returns count of tracked times that fit to the given options.
func CountTrackedTimes(opts *FindTrackedTimesOptions) (int64, error) {
sess := db.GetEngine(db.DefaultContext).Where(opts.toCond())
if opts.RepositoryID > 0 || opts.MilestoneID > 0 {
sess = sess.Join("INNER", "issue", "issue.id = tracked_time.issue_id")
}
return sess.Count(&TrackedTime{})
}
func getTrackedSeconds(e db.Engine, opts FindTrackedTimesOptions) (trackedSeconds int64, err error) {
return opts.toSession(e).SumInt(&TrackedTime{}, "time")
}
// GetTrackedSeconds return sum of seconds
func GetTrackedSeconds(opts FindTrackedTimesOptions) (int64, error) {
return getTrackedSeconds(db.GetEngine(db.DefaultContext), opts)
}
// AddTime will add the given time (in seconds) to the issue
func AddTime(user *User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) {
sess := db.NewSession(db.DefaultContext)
defer sess.Close()
if err := sess.Begin(); err != nil {
return nil, err
}
t, err := addTime(sess, user, issue, amount, created)
if err != nil {
return nil, err
}
if err := issue.loadRepo(sess); err != nil {
return nil, err
}
if _, err := createComment(sess, &CreateCommentOptions{
Issue: issue,
Repo: issue.Repo,
Doer: user,
Content: SecToTime(amount),
Type: CommentTypeAddTimeManual,
TimeID: t.ID,
}); err != nil {
return nil, err
}
return t, sess.Commit()
}
func addTime(e db.Engine, user *User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) {
if created.IsZero() {
created = time.Now()
}
tt := &TrackedTime{
IssueID: issue.ID,
UserID: user.ID,
Time: amount,
Created: created,
}
if _, err := e.Insert(tt); err != nil {
return nil, err
}
return tt, nil
}
// TotalTimes returns the spent time for each user by an issue
func TotalTimes(options *FindTrackedTimesOptions) (map[*User]string, error) {
trackedTimes, err := GetTrackedTimes(options)
if err != nil {
return nil, err
}
// Adding total time per user ID
totalTimesByUser := make(map[int64]int64)
for _, t := range trackedTimes {
totalTimesByUser[t.UserID] += t.Time
}
totalTimes := make(map[*User]string)
// Fetching User and making time human readable
for userID, total := range totalTimesByUser {
user, err := GetUserByID(userID)
if err != nil {
if IsErrUserNotExist(err) {
continue
}
return nil, err
}
totalTimes[user] = SecToTime(total)
}
return totalTimes, nil
}
// DeleteIssueUserTimes deletes times for issue
func DeleteIssueUserTimes(issue *Issue, user *User) error {
sess := db.NewSession(db.DefaultContext)
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
opts := FindTrackedTimesOptions{
IssueID: issue.ID,
UserID: user.ID,
}
removedTime, err := deleteTimes(sess, opts)
if err != nil {
return err
}
if removedTime == 0 {
return ErrNotExist{}
}
if err := issue.loadRepo(sess); err != nil {
return err
}
if _, err := createComment(sess, &CreateCommentOptions{
Issue: issue,
Repo: issue.Repo,
Doer: user,
Content: "- " + SecToTime(removedTime),
Type: CommentTypeDeleteTimeManual,
}); err != nil {
return err
}
return sess.Commit()
}
// DeleteTime delete a specific Time
func DeleteTime(t *TrackedTime) error {
sess := db.NewSession(db.DefaultContext)
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if err := t.loadAttributes(sess); err != nil {
return err
}
if err := deleteTime(sess, t); err != nil {
return err
}
if _, err := createComment(sess, &CreateCommentOptions{
Issue: t.Issue,
Repo: t.Issue.Repo,
Doer: t.User,
Content: "- " + SecToTime(t.Time),
Type: CommentTypeDeleteTimeManual,
}); err != nil {
return err
}
return sess.Commit()
}
func deleteTimes(e db.Engine, opts FindTrackedTimesOptions) (removedTime int64, err error) {
removedTime, err = getTrackedSeconds(e, opts)
if err != nil || removedTime == 0 {
return
}
_, err = opts.toSession(e).Table("tracked_time").Cols("deleted").Update(&TrackedTime{Deleted: true})
return
}
func deleteTime(e db.Engine, t *TrackedTime) error {
if t.Deleted {
return ErrNotExist{ID: t.ID}
}
t.Deleted = true
_, err := e.ID(t.ID).Cols("deleted").Update(t)
return err
}
// GetTrackedTimeByID returns raw TrackedTime without loading attributes by id
func GetTrackedTimeByID(id int64) (*TrackedTime, error) {
time := new(TrackedTime)
has, err := db.GetEngine(db.DefaultContext).ID(id).Get(time)
if err != nil {
return nil, err
} else if !has {
return nil, ErrNotExist{ID: id}
}
return time, nil
}