From 72c42968fa87e37750ed041ae4f6a97b2757e079 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Tue, 9 Jan 2018 23:43:25 +0100 Subject: [PATCH 01/18] Show total tracked time in issue and milestone list Show total tracked time at issue page Signed-off-by: Jonas Franz --- models/issue.go | 26 +++++++++++++++++++ models/issue_milestone.go | 24 +++++++++++++++++ models/issue_tracked_time.go | 6 ++++- options/locale/locale_en-US.ini | 2 +- templates/repo/issue/list.tmpl | 4 +++ templates/repo/issue/milestones.tmpl | 1 + .../repo/issue/view_content/sidebar.tmpl | 2 +- templates/user/dashboard/issues.tmpl | 4 +++ 8 files changed, 66 insertions(+), 3 deletions(-) diff --git a/models/issue.go b/models/issue.go index d10c521db6d8..66371e0b8e8d 100644 --- a/models/issue.go +++ b/models/issue.go @@ -68,6 +68,24 @@ func init() { issueTasksDonePat = regexp.MustCompile(issueTasksDoneRegexpStr) } +func (issue *Issue) totalTimes(e Engine) (string, error) { + times, err := GetTrackedTimes(FindTrackedTimesOptions{IssueID: issue.ID}) + if err != nil { + return "", err + } + var totalTime int64 + for _, trackedTime := range times { + totalTime += trackedTime.Time + } + return secToTime(totalTime), nil +} + +// TotalTimes returns the total amount of tracked time for the issue +func (issue *Issue) TotalTimes() string { + times, _ := issue.totalTimes(x) + return times +} + func (issue *Issue) loadRepo(e Engine) (err error) { if issue.Repo == nil { issue.Repo, err = getRepositoryByID(e, issue.RepoID) @@ -78,6 +96,14 @@ func (issue *Issue) loadRepo(e Engine) (err error) { return nil } +// IsTimetrackerEnabled returns true if the repo enables timetracking +func (issue *Issue) IsTimetrackerEnabled() bool { + if issue.loadRepo(x) != nil { + return false + } + return issue.Repo.IsTimetrackerEnabled() +} + // GetPullRequest returns the issue pull request func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) { if !issue.IsPull { diff --git a/models/issue_milestone.go b/models/issue_milestone.go index 761b598e9218..1b0e71fe50c9 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -86,6 +86,30 @@ func (m *Milestone) APIFormat() *api.Milestone { return apiMilestone } +func (m *Milestone) totalTimes(e Engine) (string, error) { + times, err := GetTrackedTimes(FindTrackedTimesOptions{MilestoneID: m.ID}) + if err != nil { + return "", err + } + var totalTime int64 + for _, trackedTime := range times { + totalTime += trackedTime.Time + } + return secToTime(totalTime), nil +} + +// TotalTimes returns the amount of tracked time of the issues inside the milestone +func (m *Milestone) TotalTimes() string { + times, _ := m.totalTimes(x) + return times +} + +// MustGetRepo returns milestone's repository by ignoring errors +func (m *Milestone) MustGetRepo() *Repository { + repo, _ := GetRepositoryByID(m.RepoID) + return repo +} + // NewMilestone creates new milestone of repository. func NewMilestone(m *Milestone) (err error) { sess := x.NewSession() diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index c314f8f44f72..fd3c212c2858 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -44,6 +44,7 @@ type FindTrackedTimesOptions struct { IssueID int64 UserID int64 RepositoryID int64 + MilestoneID int64 } // ToCond will convert each condition into a xorm-Cond @@ -58,12 +59,15 @@ func (opts *FindTrackedTimesOptions) ToCond() builder.Cond { 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}) + } return cond } // GetTrackedTimes returns all tracked times that fit to the given options. func GetTrackedTimes(options FindTrackedTimesOptions) (trackedTimes []*TrackedTime, err error) { - if options.RepositoryID > 0 { + if options.RepositoryID > 0 || options.MilestoneID > 0 { err = x.Join("INNER", "issue", "issue.id = tracked_time.issue_id").Where(options.ToCond()).Find(&trackedTimes) return } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index a1cb077599e7..90f3b3f26568 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -731,7 +731,7 @@ issues.add_time_minutes = Minutes issues.add_time_sum_to_small = No time was entered issues.cancel_tracking = Cancel issues.cancel_tracking_history = `cancelled time tracking %s` -issues.time_spent_total = Total time spent +issues.time_spent_from_all_authors = `Total time spent: %s` pulls.desc = Pulls management your code review and merge requests pulls.new = New Pull Request diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index 642458ff1e43..eb72ae35cfe2 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -198,6 +198,10 @@ {{.NumComments}} {{end}} + {{if and .IsTimetrackerEnabled .TotalTimes}} + {{.TotalTimes}} + {{end}} +

{{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.HomeLink .Poster.Name | Safe}} {{$tasks := .GetTasks}} diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl index de52bd42f042..284afd5894fb 100644 --- a/templates/repo/issue/milestones.tmpl +++ b/templates/repo/issue/milestones.tmpl @@ -64,6 +64,7 @@ {{$.i18n.Tr "repo.issues.open_tab" .NumOpenIssues}} {{$.i18n.Tr "repo.issues.close_tab" .NumClosedIssues}} + {{if and .TotalTimes .MustGetRepo.IsTimetrackerEnabled}} {{.TotalTimes}}{{end}} {{if $.IsRepositoryWriter}} diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 23fd91b0bd83..1e2ae3a82122 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -171,7 +171,7 @@ {{if gt (len .WorkingUsers) 0}}

- {{.i18n.Tr "repo.issues.time_spent_total"}} + {{.i18n.Tr "repo.issues.time_spent_from_all_authors" $.Issue.TotalTimes | Safe}}
{{range $user, $trackedtime := .WorkingUsers}}
diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index 534df1177a80..1cb50f3ab28f 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -80,6 +80,10 @@ {{.NumComments}} {{end}} + {{if and .IsTimetrackerEnabled .TotalTimes}} + {{.TotalTimes}} + {{end}} +

{{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.HomeLink .Poster.Name | Safe}} {{if .Assignee}} From 187503832073280c4c994753778e1d69c307a000 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Wed, 10 Jan 2018 01:42:15 +0100 Subject: [PATCH 02/18] Optimizing TotalTimes by using SumInt Signed-off-by: Jonas Franz --- models/issue.go | 7 ++----- models/issue_milestone.go | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/models/issue.go b/models/issue.go index 66371e0b8e8d..db467fd8f74e 100644 --- a/models/issue.go +++ b/models/issue.go @@ -69,14 +69,11 @@ func init() { } func (issue *Issue) totalTimes(e Engine) (string, error) { - times, err := GetTrackedTimes(FindTrackedTimesOptions{IssueID: issue.ID}) + opts := FindTrackedTimesOptions{IssueID: issue.ID} + totalTime, err := e.Where(opts.ToCond()).SumInt(&TrackedTime{}, "time") if err != nil { return "", err } - var totalTime int64 - for _, trackedTime := range times { - totalTime += trackedTime.Time - } return secToTime(totalTime), nil } diff --git a/models/issue_milestone.go b/models/issue_milestone.go index 1b0e71fe50c9..273163b8e8a8 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -87,14 +87,11 @@ func (m *Milestone) APIFormat() *api.Milestone { } func (m *Milestone) totalTimes(e Engine) (string, error) { - times, err := GetTrackedTimes(FindTrackedTimesOptions{MilestoneID: m.ID}) + opts := FindTrackedTimesOptions{MilestoneID: m.ID} + totalTime, err := e.Where(opts.ToCond()).SumInt(&TrackedTime{}, "time") if err != nil { return "", err } - var totalTime int64 - for _, trackedTime := range times { - totalTime += trackedTime.Time - } return secToTime(totalTime), nil } From fda4dd41a51c469f99a2ded70445f9f5421be514 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 3 Feb 2018 13:04:22 +0100 Subject: [PATCH 03/18] Fixing wrong total times for milestones caused by a missing JOIN Adding unit tests for total times Signed-off-by: Jonas Franz --- models/issue.go | 2 +- models/issue_milestone.go | 2 +- models/issue_milestone_test.go | 8 ++++++++ models/issue_test.go | 8 ++++++++ models/issue_tracked_time.go | 15 ++++++++++----- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/models/issue.go b/models/issue.go index db467fd8f74e..70d8f5676aa2 100644 --- a/models/issue.go +++ b/models/issue.go @@ -70,7 +70,7 @@ func init() { func (issue *Issue) totalTimes(e Engine) (string, error) { opts := FindTrackedTimesOptions{IssueID: issue.ID} - totalTime, err := e.Where(opts.ToCond()).SumInt(&TrackedTime{}, "time") + totalTime, err := opts.ToSession(e).SumInt(&TrackedTime{}, "time") if err != nil { return "", err } diff --git a/models/issue_milestone.go b/models/issue_milestone.go index 273163b8e8a8..1a36d6aa9f94 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -88,7 +88,7 @@ func (m *Milestone) APIFormat() *api.Milestone { func (m *Milestone) totalTimes(e Engine) (string, error) { opts := FindTrackedTimesOptions{MilestoneID: m.ID} - totalTime, err := e.Where(opts.ToCond()).SumInt(&TrackedTime{}, "time") + totalTime, err := opts.ToSession(e).SumInt(&TrackedTime{}, "time") if err != nil { return "", err } diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index f7987d45adee..a81a22fd1e7d 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -251,3 +251,11 @@ func TestDeleteMilestoneByRepoID(t *testing.T) { assert.NoError(t, DeleteMilestoneByRepoID(NonexistentID, NonexistentID)) } + +func TestMilestone_TotalTimes(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + ms, err := GetMilestoneByRepoID(1, 1) + assert.NoError(t, err) + times := ms.TotalTimes() + assert.Equal(t, "1h 1min 2s", times) +} diff --git a/models/issue_test.go b/models/issue_test.go index 851fe684fbc7..71da44442964 100644 --- a/models/issue_test.go +++ b/models/issue_test.go @@ -279,3 +279,11 @@ func TestGetUserIssueStats(t *testing.T) { assert.Equal(t, test.ExpectedIssueStats, *stats) } } + +func TestIssue_TotalTimes(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + ms, err := GetIssueByID(2) + assert.NoError(t, err) + times := ms.TotalTimes() + assert.Equal(t, "1h 1min 2s", times) +} diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index fd3c212c2858..4280b083efce 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -11,6 +11,7 @@ import ( api "code.gitea.io/sdk/gitea" "github.com/go-xorm/builder" + "github.com/go-xorm/xorm" ) // TrackedTime represents a time that was spent for a specific issue. @@ -65,13 +66,17 @@ func (opts *FindTrackedTimesOptions) ToCond() builder.Cond { 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 Engine) *xorm.Session { + if opts.RepositoryID > 0 || opts.MilestoneID > 0 { + return e.Join("INNER", "issue", "issue.id = tracked_time.issue_id").Where(opts.ToCond()) + } + return x.Where(opts.ToCond()) +} + // GetTrackedTimes returns all tracked times that fit to the given options. func GetTrackedTimes(options FindTrackedTimesOptions) (trackedTimes []*TrackedTime, err error) { - if options.RepositoryID > 0 || options.MilestoneID > 0 { - err = x.Join("INNER", "issue", "issue.id = tracked_time.issue_id").Where(options.ToCond()).Find(&trackedTimes) - return - } - err = x.Where(options.ToCond()).Find(&trackedTimes) + err = options.ToSession(x).Find(&trackedTimes) return } From 983c1e1205d4409a2c76dc6d6c03479d68a0a14e Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 3 Feb 2018 15:38:31 +0100 Subject: [PATCH 04/18] Logging error instead of ignoring it Signed-off-by: Jonas Franz --- models/issue.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models/issue.go b/models/issue.go index 70d8f5676aa2..cf627fc2e5b2 100644 --- a/models/issue.go +++ b/models/issue.go @@ -95,7 +95,8 @@ func (issue *Issue) loadRepo(e Engine) (err error) { // IsTimetrackerEnabled returns true if the repo enables timetracking func (issue *Issue) IsTimetrackerEnabled() bool { - if issue.loadRepo(x) != nil { + if err := issue.loadRepo(x); err != nil { + log.Error(4, fmt.Sprintf("An error occured while checking if timetracker is enabled: %v", err)) return false } return issue.Repo.IsTimetrackerEnabled() From 26a1e021fb8ad8b5a2fb25de1082a6716f5bcafb Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 3 Feb 2018 16:20:58 +0100 Subject: [PATCH 05/18] Correcting spelling mistakes Signed-off-by: Jonas Franz --- models/issue.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/issue.go b/models/issue.go index cf627fc2e5b2..54f31b7aa921 100644 --- a/models/issue.go +++ b/models/issue.go @@ -96,7 +96,7 @@ func (issue *Issue) loadRepo(e Engine) (err error) { // IsTimetrackerEnabled returns true if the repo enables timetracking func (issue *Issue) IsTimetrackerEnabled() bool { if err := issue.loadRepo(x); err != nil { - log.Error(4, fmt.Sprintf("An error occured while checking if timetracker is enabled: %v", err)) + log.Error(4, fmt.Sprintf("An error occurred while checking if timetracker is enabled: %v", err)) return false } return issue.Repo.IsTimetrackerEnabled() From f1cfe62fff582b3723eded052e186377f6a9fa90 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 3 Feb 2018 18:58:09 +0100 Subject: [PATCH 06/18] Change error message to a short version Signed-off-by: Jonas Franz --- models/issue.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/issue.go b/models/issue.go index 54f31b7aa921..eb6027b14f5b 100644 --- a/models/issue.go +++ b/models/issue.go @@ -96,7 +96,7 @@ func (issue *Issue) loadRepo(e Engine) (err error) { // IsTimetrackerEnabled returns true if the repo enables timetracking func (issue *Issue) IsTimetrackerEnabled() bool { if err := issue.loadRepo(x); err != nil { - log.Error(4, fmt.Sprintf("An error occurred while checking if timetracker is enabled: %v", err)) + log.Error(4, fmt.Sprintf("loadRepo: %v", err)) return false } return issue.Repo.IsTimetrackerEnabled() From c5c812c5d0b7e5382bff884950a6034a8d1fd331 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 26 Feb 2018 23:02:47 +0100 Subject: [PATCH 07/18] Add error handling to TotalTimes Add variable for totalTimes Signed-off-by: Jonas Franz --- models/issue.go | 3 ++- templates/user/dashboard/issues.tmpl | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/models/issue.go b/models/issue.go index 00f4e3ba2964..d6fd3c15143f 100644 --- a/models/issue.go +++ b/models/issue.go @@ -80,7 +80,8 @@ func (issue *Issue) totalTimes(e Engine) (string, error) { // TotalTimes returns the total amount of tracked time for the issue func (issue *Issue) TotalTimes() string { - times, _ := issue.totalTimes(x) + times, err := issue.totalTimes(x) + log.Error(4,"TotalTimes: %v", err) return times } diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index 8481d671977f..520dec9a4f1a 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -79,9 +79,9 @@ {{if .NumComments}} {{.NumComments}} {{end}} - - {{if and .IsTimetrackerEnabled .TotalTimes}} - {{.TotalTimes}} + {{$totalTimes := .TotalTimes}} + {{if and .IsTimetrackerEnabled $totalTimes}} + {{$totalTimes}} {{end}}

From 8481f2dacc4d0c3a346c15709361f461b5ddd666 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sun, 11 Mar 2018 11:42:35 +0100 Subject: [PATCH 08/18] Introduce TotalTrackedTimes as variable of issue Load TotalTrackedTimes by loading attributes of IssueList Load TotalTrackedTimes by loading attributes of single issue Add Sec2Time as helper to use it in templates Signed-off-by: Jonas Franz --- models/issue.go | 20 ++++----- models/issue_list.go | 41 +++++++++++++++++++ models/issue_list_test.go | 5 +++ models/issue_milestone.go | 2 +- models/issue_stopwatch.go | 5 ++- models/issue_tracked_time.go | 4 +- modules/templates/helper.go | 1 + templates/repo/issue/list.tmpl | 4 +- .../repo/issue/view_content/sidebar.tmpl | 2 +- templates/user/dashboard/issues.tmpl | 5 +-- 10 files changed, 67 insertions(+), 22 deletions(-) diff --git a/models/issue.go b/models/issue.go index d6fd3c15143f..06d209f9a035 100644 --- a/models/issue.go +++ b/models/issue.go @@ -54,6 +54,7 @@ type Issue struct { Attachments []*Attachment `xorm:"-"` Comments []*Comment `xorm:"-"` Reactions ReactionList `xorm:"-"` + TotalTrackedTime int64 `xorm:"-"` } var ( @@ -69,20 +70,13 @@ func init() { issueTasksDonePat = regexp.MustCompile(issueTasksDoneRegexpStr) } -func (issue *Issue) totalTimes(e Engine) (string, error) { +func (issue *Issue) loadTotalTimes(e Engine) (err error) { opts := FindTrackedTimesOptions{IssueID: issue.ID} - totalTime, err := opts.ToSession(e).SumInt(&TrackedTime{}, "time") + issue.TotalTrackedTime, err = opts.ToSession(e).SumInt(&TrackedTime{}, "time") if err != nil { - return "", err + return err } - return secToTime(totalTime), nil -} - -// TotalTimes returns the total amount of tracked time for the issue -func (issue *Issue) TotalTimes() string { - times, err := issue.totalTimes(x) - log.Error(4,"TotalTimes: %v", err) - return times + return nil } func (issue *Issue) loadRepo(e Engine) (err error) { @@ -251,6 +245,10 @@ func (issue *Issue) loadAttributes(e Engine) (err error) { return err } + if err = issue.loadTotalTimes(e); err != nil { + return err + } + return issue.loadReactions(e) } diff --git a/models/issue_list.go b/models/issue_list.go index 4910915cd09a..f984b8d4788e 100644 --- a/models/issue_list.go +++ b/models/issue_list.go @@ -290,6 +290,43 @@ func (issues IssueList) loadComments(e Engine) (err error) { return nil } +func (issues IssueList) loadTotalTrackedTimes(e Engine) (err error) { + type totalTimesByIssue struct { + IssueID int64 + Time int64 + } + if len(issues) == 0 { + return nil + } + var trackedTimes = make(map[int64]int64, len(issues)) + + // select issue_id, sum(time) from tracked_time where issue_id in () group by issue_id + rows, err := e.Table("tracked_time"). + Select("issue_id, sum(time) as time"). + In("issue_id", issues.getIssueIDs()). + GroupBy("issue_id"). + Rows(new(totalTimesByIssue)) + if err != nil { + return err + } + + defer rows.Close() + + for rows.Next() { + var totalTime totalTimesByIssue + err = rows.Scan(&totalTime) + if err != nil { + return err + } + trackedTimes[totalTime.IssueID] = totalTime.Time + } + + for _, issue := range issues { + issue.TotalTrackedTime = trackedTimes[issue.ID] + } + return nil +} + // loadAttributes loads all attributes, expect for attachments and comments func (issues IssueList) loadAttributes(e Engine) (err error) { if _, err = issues.loadRepositories(e); err != nil { @@ -316,6 +353,10 @@ func (issues IssueList) loadAttributes(e Engine) (err error) { return } + if err = issues.loadTotalTrackedTimes(e); err != nil { + return + } + return nil } diff --git a/models/issue_list_test.go b/models/issue_list_test.go index 958e074662a8..f27440ec2e9b 100644 --- a/models/issue_list_test.go +++ b/models/issue_list_test.go @@ -61,5 +61,10 @@ func TestIssueList_LoadAttributes(t *testing.T) { for _, comment := range issue.Comments { assert.EqualValues(t, issue.ID, comment.IssueID) } + if issue.ID == int64(1) { + assert.Equal(t, int64(400), issue.TotalTrackedTime) + } else if issue.ID == int64(2) { + assert.Equal(t, int64(3662), issue.TotalTrackedTime) + } } } diff --git a/models/issue_milestone.go b/models/issue_milestone.go index 1a36d6aa9f94..506daa4453d5 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -92,7 +92,7 @@ func (m *Milestone) totalTimes(e Engine) (string, error) { if err != nil { return "", err } - return secToTime(totalTime), nil + return SecToTime(totalTime), nil } // TotalTimes returns the amount of tracked time of the issues inside the milestone diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 92b1bb9a5fce..178b76c5dd4b 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -69,7 +69,7 @@ func CreateOrStopIssueStopwatch(user *User, issue *Issue) error { Doer: user, Issue: issue, Repo: issue.Repo, - Content: secToTime(timediff), + Content: SecToTime(timediff), Type: CommentTypeStopTracking, }); err != nil { return err @@ -124,7 +124,8 @@ func CancelStopwatch(user *User, issue *Issue) error { return nil } -func secToTime(duration int64) string { +// SecToTime converts an amount of seconds to a human-readable string (example: 66s -> 1min 6s) +func SecToTime(duration int64) string { seconds := duration % 60 minutes := (duration / (60)) % 60 hours := duration / (60 * 60) diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index 4280b083efce..6592f06d732c 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -94,7 +94,7 @@ func AddTime(user *User, issue *Issue, time int64) (*TrackedTime, error) { Issue: issue, Repo: issue.Repo, Doer: user, - Content: secToTime(time), + Content: SecToTime(time), Type: CommentTypeAddTimeManual, }); err != nil { return nil, err @@ -124,7 +124,7 @@ func TotalTimes(options FindTrackedTimesOptions) (map[*User]string, error) { } return nil, err } - totalTimes[user] = secToTime(total) + totalTimes[user] = SecToTime(total) } return totalTimes, nil } diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 98900c75388c..2563933ddda0 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -181,6 +181,7 @@ func NewFuncMap() []template.FuncMap { }, "Printf": fmt.Sprintf, "Escape": Escape, + "Sec2Time": models.SecToTime, }} } diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index ab1dcd19e9c8..b5c18fdd793c 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -198,8 +198,8 @@ {{.NumComments}} {{end}} - {{if and .IsTimetrackerEnabled .TotalTimes}} - {{.TotalTimes}} + {{if and .IsTimetrackerEnabled .TotalTrackedTime}} + {{.TotalTrackedTime | Sec2Time}} {{end}}

diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 1e2ae3a82122..38d81b5c4457 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -171,7 +171,7 @@ {{if gt (len .WorkingUsers) 0}}

- {{.i18n.Tr "repo.issues.time_spent_from_all_authors" $.Issue.TotalTimes | Safe}} + {{.i18n.Tr "repo.issues.time_spent_from_all_authors" ($.Issue.TotalTrackedTime | Sec2Time) | Safe}}
{{range $user, $trackedtime := .WorkingUsers}}
diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index 520dec9a4f1a..2b4acfd15fff 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -79,9 +79,8 @@ {{if .NumComments}} {{.NumComments}} {{end}} - {{$totalTimes := .TotalTimes}} - {{if and .IsTimetrackerEnabled $totalTimes}} - {{$totalTimes}} + {{if and .IsTimetrackerEnabled .TotalTrackedTime}} + {{.TotalTrackedTime | Sec2Time}} {{end}}

From 4cac53ebe4b5f8417418f13f275b7796997d9be1 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sun, 11 Mar 2018 11:48:09 +0100 Subject: [PATCH 09/18] Fixed test + gofmt Signed-off-by: Jonas Franz --- models/issue.go | 8 ++++---- models/issue_list.go | 2 +- models/issue_test.go | 6 +++--- modules/templates/helper.go | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/models/issue.go b/models/issue.go index 06d209f9a035..47e8418a0488 100644 --- a/models/issue.go +++ b/models/issue.go @@ -51,10 +51,10 @@ type Issue struct { UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` ClosedUnix util.TimeStamp `xorm:"INDEX"` - Attachments []*Attachment `xorm:"-"` - Comments []*Comment `xorm:"-"` - Reactions ReactionList `xorm:"-"` - TotalTrackedTime int64 `xorm:"-"` + Attachments []*Attachment `xorm:"-"` + Comments []*Comment `xorm:"-"` + Reactions ReactionList `xorm:"-"` + TotalTrackedTime int64 `xorm:"-"` } var ( diff --git a/models/issue_list.go b/models/issue_list.go index f984b8d4788e..31d6034cc6f8 100644 --- a/models/issue_list.go +++ b/models/issue_list.go @@ -293,7 +293,7 @@ func (issues IssueList) loadComments(e Engine) (err error) { func (issues IssueList) loadTotalTrackedTimes(e Engine) (err error) { type totalTimesByIssue struct { IssueID int64 - Time int64 + Time int64 } if len(issues) == 0 { return nil diff --git a/models/issue_test.go b/models/issue_test.go index 71da44442964..d98debb17850 100644 --- a/models/issue_test.go +++ b/models/issue_test.go @@ -280,10 +280,10 @@ func TestGetUserIssueStats(t *testing.T) { } } -func TestIssue_TotalTimes(t *testing.T) { +func TestIssue_loadTotalTimes(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) ms, err := GetIssueByID(2) assert.NoError(t, err) - times := ms.TotalTimes() - assert.Equal(t, "1h 1min 2s", times) + assert.NoError(t, ms.loadTotalTimes(x)) + assert.Equal(t, int64(3662), ms.TotalTrackedTime) } diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 2563933ddda0..8dfa6dec8ab4 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -179,8 +179,8 @@ func NewFuncMap() []template.FuncMap { } return dict, nil }, - "Printf": fmt.Sprintf, - "Escape": Escape, + "Printf": fmt.Sprintf, + "Escape": Escape, "Sec2Time": models.SecToTime, }} } From ec0d41c02d9f1c20a39b256ddb8a3c3356f5d404 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 16 Apr 2018 22:37:32 +0200 Subject: [PATCH 10/18] Load TotalTrackedTimes via MilestoneList instead of single requests Signed-off-by: Jonas Franz --- models/issue_milestone.go | 84 ++++++++++++++++++++-------- models/issue_milestone_test.go | 11 ++-- routers/repo/issue.go | 6 ++ templates/repo/issue/milestones.tmpl | 2 +- 4 files changed, 73 insertions(+), 30 deletions(-) diff --git a/models/issue_milestone.go b/models/issue_milestone.go index 506daa4453d5..6de32ce9590b 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -29,6 +29,8 @@ type Milestone struct { DeadlineString string `xorm:"-"` DeadlineUnix util.TimeStamp ClosedDateUnix util.TimeStamp + + TotalTrackedTime int64 `xorm:"-"` } // BeforeInsert is invoked from XORM before inserting an object of this type. @@ -86,27 +88,6 @@ func (m *Milestone) APIFormat() *api.Milestone { return apiMilestone } -func (m *Milestone) totalTimes(e Engine) (string, error) { - opts := FindTrackedTimesOptions{MilestoneID: m.ID} - totalTime, err := opts.ToSession(e).SumInt(&TrackedTime{}, "time") - if err != nil { - return "", err - } - return SecToTime(totalTime), nil -} - -// TotalTimes returns the amount of tracked time of the issues inside the milestone -func (m *Milestone) TotalTimes() string { - times, _ := m.totalTimes(x) - return times -} - -// MustGetRepo returns milestone's repository by ignoring errors -func (m *Milestone) MustGetRepo() *Repository { - repo, _ := GetRepositoryByID(m.RepoID) - return repo -} - // NewMilestone creates new milestone of repository. func NewMilestone(m *Milestone) (err error) { sess := x.NewSession() @@ -144,14 +125,70 @@ func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) { return getMilestoneByRepoID(x, repoID, id) } +type MilestoneList []*Milestone + +func (milestones MilestoneList) loadTotalTrackedTimes(e Engine) error { + type totalTimesByMilestone struct { + MilestoneID int64 + Time int64 + } + if len(milestones) == 0 { + return nil + } + var trackedTimes = make(map[int64]int64, len(milestones)) + + // SELECT milestone_id, sum(time) as time FROM `issue` + // INNER JOIN `milestone` ON issue.milestone_id = milestone.id + // LEFT JOIN `tracked_time` ON tracked_time.issue_id = issue.id + // WHERE `milestone_id` IN (?) GROUP BY milestone_id + rows, err := e.Table("issue"). + Join("INNER", "milestone", "issue.milestone_id = milestone.id"). + Join("LEFT", "tracked_time", "tracked_time.issue_id = issue.id"). + Select("milestone_id, sum(time) as time"). + In("milestone_id", milestones.getMilestoneIDs()). + GroupBy("milestone_id"). + Rows(new(totalTimesByMilestone)) + if err != nil { + return err + } + + defer rows.Close() + + for rows.Next() { + var totalTime totalTimesByMilestone + err = rows.Scan(&totalTime) + if err != nil { + return err + } + trackedTimes[totalTime.MilestoneID] = totalTime.Time + } + + for _, milestone := range milestones { + milestone.TotalTrackedTime = trackedTimes[milestone.ID] + } + return nil +} + +func (milestones MilestoneList) LoadTotalTrackedTimes() error { + return milestones.loadTotalTrackedTimes(x) +} + +func (milestones MilestoneList) getMilestoneIDs() []int64 { + var ids = make([]int64, 0, len(milestones)) + for _, ms := range milestones { + ids = append(ids, ms.ID) + } + return ids +} + // GetMilestonesByRepoID returns all milestones of a repository. -func GetMilestonesByRepoID(repoID int64) ([]*Milestone, error) { +func GetMilestonesByRepoID(repoID int64) (MilestoneList, error) { miles := make([]*Milestone, 0, 10) return miles, x.Where("repo_id = ?", repoID).Find(&miles) } // GetMilestones returns a list of milestones of given repository and status. -func GetMilestones(repoID int64, page int, isClosed bool, sortType string) ([]*Milestone, error) { +func GetMilestones(repoID int64, page int, isClosed bool, sortType string) (MilestoneList, error) { miles := make([]*Milestone, 0, setting.UI.IssuePagingNum) sess := x.Where("repo_id = ? AND is_closed = ?", repoID, isClosed) if page > 0 { @@ -172,7 +209,6 @@ func GetMilestones(repoID int64, page int, isClosed bool, sortType string) ([]*M default: sess.Asc("deadline_unix") } - return miles, sess.Find(&miles) } diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index 6d6c01b5a4e9..cef567296d5d 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -254,10 +254,11 @@ func TestDeleteMilestoneByRepoID(t *testing.T) { assert.NoError(t, DeleteMilestoneByRepoID(NonexistentID, NonexistentID)) } -func TestMilestone_TotalTimes(t *testing.T) { +func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - ms, err := GetMilestoneByRepoID(1, 1) - assert.NoError(t, err) - times := ms.TotalTimes() - assert.Equal(t, "1h 1min 2s", times) + miles := MilestoneList{ + AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone), + } + assert.NoError(t, miles.LoadTotalTrackedTimes()) + assert.Equal(t, miles[0].TotalTrackedTime, 3661) } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 234937b1af75..51516b828c27 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -1139,6 +1139,12 @@ func Milestones(ctx *context.Context) { ctx.ServerError("GetMilestones", err) return } + if ctx.Repo.Repository.IsTimetrackerEnabled() { + if miles.LoadTotalTrackedTimes(); err != nil { + ctx.ServerError("LoadTotalTrackedTimes", err) + return + } + } for _, m := range miles { m.RenderedContent = string(markdown.Render([]byte(m.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas())) } diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl index 284afd5894fb..369da2e63011 100644 --- a/templates/repo/issue/milestones.tmpl +++ b/templates/repo/issue/milestones.tmpl @@ -64,7 +64,7 @@ {{$.i18n.Tr "repo.issues.open_tab" .NumOpenIssues}} {{$.i18n.Tr "repo.issues.close_tab" .NumClosedIssues}} - {{if and .TotalTimes .MustGetRepo.IsTimetrackerEnabled}} {{.TotalTimes}}{{end}} + {{if .TotalTrackedTime}} {{.TotalTrackedTime|Sec2Time}}{{end}}

{{if $.IsRepositoryWriter}} From 02dc1cf8647c97549c3943b5f7a830c059f44159 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 16 Apr 2018 22:42:50 +0200 Subject: [PATCH 11/18] Add documentation for MilestoneList Signed-off-by: Jonas Franz --- models/issue_milestone.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/models/issue_milestone.go b/models/issue_milestone.go index 6de32ce9590b..1229a74c3738 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -125,6 +125,7 @@ func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) { return getMilestoneByRepoID(x, repoID, id) } +// MilestoneList is a list of milestones offering additional functionality type MilestoneList []*Milestone func (milestones MilestoneList) loadTotalTrackedTimes(e Engine) error { @@ -169,6 +170,7 @@ func (milestones MilestoneList) loadTotalTrackedTimes(e Engine) error { return nil } +// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime func (milestones MilestoneList) LoadTotalTrackedTimes() error { return milestones.loadTotalTrackedTimes(x) } From 0ba269b70c290053afa12d8ff633908ca8f7eed8 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 16 Apr 2018 22:48:09 +0200 Subject: [PATCH 12/18] Add documentation for MilestoneList Signed-off-by: Jonas Franz --- models/issue_milestone.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/issue_milestone.go b/models/issue_milestone.go index 1229a74c3738..96b9d7f6c4dd 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -170,7 +170,7 @@ func (milestones MilestoneList) loadTotalTrackedTimes(e Engine) error { return nil } -// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime +// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request func (milestones MilestoneList) LoadTotalTrackedTimes() error { return milestones.loadTotalTrackedTimes(x) } From 70e9bbd4830c05e0064465e10e5eda955821f767 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 16 Apr 2018 23:04:50 +0200 Subject: [PATCH 13/18] Fix test Signed-off-by: Jonas Franz --- models/issue_milestone_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index cef567296d5d..30d1a90988d3 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -260,5 +260,5 @@ func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) { AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone), } assert.NoError(t, miles.LoadTotalTrackedTimes()) - assert.Equal(t, miles[0].TotalTrackedTime, 3661) + assert.Equal(t, miles[0].TotalTrackedTime, 3662) } From 221a327869f76437fa4face06b7dcd5804cb0e4a Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 19 Apr 2018 14:45:18 +0200 Subject: [PATCH 14/18] Change comment from SQL query to description Signed-off-by: Jonas Franz --- models/issue_milestone.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/models/issue_milestone.go b/models/issue_milestone.go index 96b9d7f6c4dd..1f852e5502f4 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -138,10 +138,7 @@ func (milestones MilestoneList) loadTotalTrackedTimes(e Engine) error { } var trackedTimes = make(map[int64]int64, len(milestones)) - // SELECT milestone_id, sum(time) as time FROM `issue` - // INNER JOIN `milestone` ON issue.milestone_id = milestone.id - // LEFT JOIN `tracked_time` ON tracked_time.issue_id = issue.id - // WHERE `milestone_id` IN (?) GROUP BY milestone_id + // Get total tracked time by milestone_id rows, err := e.Table("issue"). Join("INNER", "milestone", "issue.milestone_id = milestone.id"). Join("LEFT", "tracked_time", "tracked_time.issue_id = issue.id"). From ca5243cda677b095b9c29aae03a212e9491eddde Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 19 Apr 2018 22:07:15 +0200 Subject: [PATCH 15/18] Fix unit test by using int64 instead of int Signed-off-by: Jonas Franz --- models/issue_milestone_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index 30d1a90988d3..0b4b781bf516 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -260,5 +260,5 @@ func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) { AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone), } assert.NoError(t, miles.LoadTotalTrackedTimes()) - assert.Equal(t, miles[0].TotalTrackedTime, 3662) + assert.Equal(t, miles[0].TotalTrackedTime, int64(3662)) } From d6237809c472de482cc31fb954af1a8fd6ef54b4 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Thu, 19 Apr 2018 22:11:09 +0200 Subject: [PATCH 16/18] Fix unit test by using int64 instead of int Signed-off-by: Jonas Franz --- models/issue_milestone_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index 0b4b781bf516..692c789b50c4 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -259,6 +259,8 @@ func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) { miles := MilestoneList{ AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone), } + assert.NoError(t, miles.LoadTotalTrackedTimes()) + assert.Equal(t, miles[0].TotalTrackedTime, int64(3662)) } From 6a97bf716507eafe760bf5d974f6c7f8d0af8503 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Fri, 20 Apr 2018 20:07:26 +0200 Subject: [PATCH 17/18] Check if timetracker is enabled Signed-off-by: Jonas Franz --- models/issue.go | 7 ++++--- models/issue_list.go | 9 ++++++++- models/issue_milestone_test.go | 2 +- templates/repo/issue/list.tmpl | 2 +- templates/user/dashboard/issues.tmpl | 2 +- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/models/issue.go b/models/issue.go index 47e8418a0488..190b3875307d 100644 --- a/models/issue.go +++ b/models/issue.go @@ -244,9 +244,10 @@ func (issue *Issue) loadAttributes(e Engine) (err error) { if err = issue.loadComments(e); err != nil { return err } - - if err = issue.loadTotalTimes(e); err != nil { - return err + if issue.IsTimetrackerEnabled() { + if err = issue.loadTotalTimes(e); err != nil { + return err + } } return issue.loadReactions(e) diff --git a/models/issue_list.go b/models/issue_list.go index 31d6034cc6f8..a29fe3181058 100644 --- a/models/issue_list.go +++ b/models/issue_list.go @@ -300,10 +300,17 @@ func (issues IssueList) loadTotalTrackedTimes(e Engine) (err error) { } var trackedTimes = make(map[int64]int64, len(issues)) + var ids = make([]int64, 0, len(issues)) + for _, issue := range issues { + if issue.IsTimetrackerEnabled() { + ids = append(ids, issue.ID) + } + } + // select issue_id, sum(time) from tracked_time where issue_id in () group by issue_id rows, err := e.Table("tracked_time"). Select("issue_id, sum(time) as time"). - In("issue_id", issues.getIssueIDs()). + In("issue_id", ids). GroupBy("issue_id"). Rows(new(totalTimesByIssue)) if err != nil { diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index 692c789b50c4..c9b53f4f4a34 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -259,7 +259,7 @@ func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) { miles := MilestoneList{ AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone), } - + assert.NoError(t, miles.LoadTotalTrackedTimes()) assert.Equal(t, miles[0].TotalTrackedTime, int64(3662)) diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index 67c751fa4ef9..180a5dea6cfd 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -198,7 +198,7 @@ {{.NumComments}} {{end}} - {{if and .IsTimetrackerEnabled .TotalTrackedTime}} + {{if .TotalTrackedTime}} {{.TotalTrackedTime | Sec2Time}} {{end}} diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index 46bc11dff186..d0b6511b870a 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -79,7 +79,7 @@ {{if .NumComments}} {{.NumComments}} {{end}} - {{if and .IsTimetrackerEnabled .TotalTrackedTime}} + {{if .TotalTrackedTime}} {{.TotalTrackedTime | Sec2Time}} {{end}} From 62f9962e04d439d37d774b3b124e6b00bd69d4b0 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Sat, 21 Apr 2018 12:05:16 +0200 Subject: [PATCH 18/18] Fix test by enabling timetracking Signed-off-by: Jonas Franz --- models/issue_list.go | 2 +- models/issue_list_test.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/models/issue_list.go b/models/issue_list.go index a29fe3181058..01a1a15f4439 100644 --- a/models/issue_list.go +++ b/models/issue_list.go @@ -302,7 +302,7 @@ func (issues IssueList) loadTotalTrackedTimes(e Engine) (err error) { var ids = make([]int64, 0, len(issues)) for _, issue := range issues { - if issue.IsTimetrackerEnabled() { + if issue.Repo.IsTimetrackerEnabled() { ids = append(ids, issue.ID) } } diff --git a/models/issue_list_test.go b/models/issue_list_test.go index f27440ec2e9b..9197e0615aa4 100644 --- a/models/issue_list_test.go +++ b/models/issue_list_test.go @@ -7,6 +7,8 @@ package models import ( "testing" + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" ) @@ -29,7 +31,7 @@ func TestIssueList_LoadRepositories(t *testing.T) { func TestIssueList_LoadAttributes(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - + setting.Service.EnableTimetracking = true issueList := IssueList{ AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue), AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue),