From 7257c39ddfe9d9d424192e6bd307a70ed544f5be Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Tue, 12 May 2020 23:54:35 +0200 Subject: [PATCH] Refactor Milestone related (#11225) --- models/issue_milestone.go | 491 +++++++++++++++---------------- models/issue_milestone_test.go | 67 ++--- modules/convert/issue.go | 21 +- modules/convert/issue_test.go | 24 ++ routers/api/v1/repo/milestone.go | 9 +- routers/repo/milestone.go | 12 +- routers/user/home.go | 6 +- 7 files changed, 313 insertions(+), 317 deletions(-) diff --git a/models/issue_milestone.go b/models/issue_milestone.go index 274258e6a..464827445 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -69,25 +69,6 @@ func (m *Milestone) State() api.StateType { return api.StateOpen } -// APIFormat returns this Milestone in API format. -func (m *Milestone) APIFormat() *api.Milestone { - apiMilestone := &api.Milestone{ - ID: m.ID, - State: m.State(), - Title: m.Name, - Description: m.Content, - OpenIssues: m.NumOpenIssues, - ClosedIssues: m.NumClosedIssues, - } - if m.IsClosed { - apiMilestone.Closed = m.ClosedDateUnix.AsTimePtr() - } - if m.DeadlineUnix.Year() < 9999 { - apiMilestone.Deadline = m.DeadlineUnix.AsTimePtr() - } - return apiMilestone -} - // NewMilestone creates new milestone of repository. func NewMilestone(m *Milestone) (err error) { sess := x.NewSession() @@ -149,157 +130,6 @@ func GetMilestoneByID(id int64) (*Milestone, error) { return &m, nil } -// MilestoneList is a list of milestones offering additional functionality -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)) - - // 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"). - Where("tracked_time.deleted = ?", false). - 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 (m *Milestone) loadTotalTrackedTime(e Engine) error { - type totalTimesByMilestone struct { - MilestoneID int64 - Time int64 - } - totalTime := &totalTimesByMilestone{MilestoneID: m.ID} - has, err := e.Table("issue"). - Join("INNER", "milestone", "issue.milestone_id = milestone.id"). - Join("LEFT", "tracked_time", "tracked_time.issue_id = issue.id"). - Where("tracked_time.deleted = ?", false). - Select("milestone_id, sum(time) as time"). - Where("milestone_id = ?", m.ID). - GroupBy("milestone_id"). - Get(totalTime) - if err != nil { - return err - } else if !has { - return nil - } - m.TotalTrackedTime = totalTime.Time - return nil -} - -// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request -func (milestones MilestoneList) LoadTotalTrackedTimes() error { - return milestones.loadTotalTrackedTimes(x) -} - -// LoadTotalTrackedTime loads the tracked time for the milestone -func (m *Milestone) LoadTotalTrackedTime() error { - return m.loadTotalTrackedTime(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 opened milestones of a repository. -func GetMilestonesByRepoID(repoID int64, state api.StateType, listOptions ListOptions) (MilestoneList, error) { - sess := x.Where("repo_id = ?", repoID) - - switch state { - case api.StateClosed: - sess = sess.And("is_closed = ?", true) - - case api.StateAll: - break - - case api.StateOpen: - fallthrough - - default: - sess = sess.And("is_closed = ?", false) - } - - if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) - } - - miles := make([]*Milestone, 0, listOptions.PageSize) - return miles, sess.Asc("deadline_unix").Asc("id").Find(&miles) -} - -// GetMilestones returns a list of milestones of given repository and status. -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 { - sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum) - } - - switch sortType { - case "furthestduedate": - sess.Desc("deadline_unix") - case "leastcomplete": - sess.Asc("completeness") - case "mostcomplete": - sess.Desc("completeness") - case "leastissues": - sess.Asc("num_issues") - case "mostissues": - sess.Desc("num_issues") - default: - sess.Asc("deadline_unix") - } - return miles, sess.Find(&miles) -} - -func updateMilestone(e Engine, m *Milestone) error { - m.Name = strings.TrimSpace(m.Name) - _, err := e.ID(m.ID).AllCols(). - SetExpr("num_issues", builder.Select("count(*)").From("issue").Where( - builder.Eq{"milestone_id": m.ID}, - )). - SetExpr("num_closed_issues", builder.Select("count(*)").From("issue").Where( - builder.Eq{ - "milestone_id": m.ID, - "is_closed": true, - }, - )). - Update(m) - return err -} - // UpdateMilestone updates information of given milestone. func UpdateMilestone(m *Milestone, oldIsClosed bool) error { sess := x.NewSession() @@ -330,6 +160,22 @@ func UpdateMilestone(m *Milestone, oldIsClosed bool) error { return sess.Commit() } +func updateMilestone(e Engine, m *Milestone) error { + m.Name = strings.TrimSpace(m.Name) + _, err := e.ID(m.ID).AllCols(). + SetExpr("num_issues", builder.Select("count(*)").From("issue").Where( + builder.Eq{"milestone_id": m.ID}, + )). + SetExpr("num_closed_issues", builder.Select("count(*)").From("issue").Where( + builder.Eq{ + "milestone_id": m.ID, + "is_closed": true, + }, + )). + Update(m) + return err +} + func updateMilestoneCompleteness(e Engine, milestoneID int64) error { _, err := e.Exec("UPDATE `milestone` SET completeness=100*num_closed_issues/(CASE WHEN num_issues > 0 THEN num_issues ELSE 1 END) WHERE id=?", milestoneID, @@ -337,35 +183,6 @@ func updateMilestoneCompleteness(e Engine, milestoneID int64) error { return err } -func countRepoMilestones(e Engine, repoID int64) (int64, error) { - return e. - Where("repo_id=?", repoID). - Count(new(Milestone)) -} - -func countRepoClosedMilestones(e Engine, repoID int64) (int64, error) { - return e. - Where("repo_id=? AND is_closed=?", repoID, true). - Count(new(Milestone)) -} - -// CountRepoClosedMilestones returns number of closed milestones in given repository. -func CountRepoClosedMilestones(repoID int64) (int64, error) { - return countRepoClosedMilestones(x, repoID) -} - -// MilestoneStats returns number of open and closed milestones of given repository. -func MilestoneStats(repoID int64) (open int64, closed int64, err error) { - open, err = x. - Where("repo_id=? AND is_closed=?", repoID, false). - Count(new(Milestone)) - if err != nil { - return 0, 0, nil - } - closed, err = CountRepoClosedMilestones(repoID) - return open, closed, err -} - // ChangeMilestoneStatus changes the milestone open/closed status. func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) { sess := x.NewSession() @@ -390,39 +207,6 @@ func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) { return sess.Commit() } -func updateRepoMilestoneNum(e Engine, repoID int64) error { - _, err := e.Exec("UPDATE `repository` SET num_milestones=(SELECT count(*) FROM milestone WHERE repo_id=?),num_closed_milestones=(SELECT count(*) FROM milestone WHERE repo_id=? AND is_closed=?) WHERE id=?", - repoID, - repoID, - true, - repoID, - ) - return err -} - -func updateMilestoneTotalNum(e Engine, milestoneID int64) (err error) { - if _, err = e.Exec("UPDATE `milestone` SET num_issues=(SELECT count(*) FROM issue WHERE milestone_id=?) WHERE id=?", - milestoneID, - milestoneID, - ); err != nil { - return - } - - return updateMilestoneCompleteness(e, milestoneID) -} - -func updateMilestoneClosedNum(e Engine, milestoneID int64) (err error) { - if _, err = e.Exec("UPDATE `milestone` SET num_closed_issues=(SELECT count(*) FROM issue WHERE milestone_id=? AND is_closed=?) WHERE id=?", - milestoneID, - true, - milestoneID, - ); err != nil { - return - } - - return updateMilestoneCompleteness(e, milestoneID) -} - func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilestoneID int64) error { if err := updateIssueCols(e, issue, "milestone_id"); err != nil { return err @@ -535,37 +319,66 @@ func DeleteMilestoneByRepoID(repoID, id int64) error { return sess.Commit() } -// CountMilestones map from repo conditions to number of milestones matching the options` -func CountMilestones(repoCond builder.Cond, isClosed bool) (map[int64]int64, error) { - sess := x.Where("is_closed = ?", isClosed) - if repoCond.IsValid() { - sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond)) +// MilestoneList is a list of milestones offering additional functionality +type MilestoneList []*Milestone + +func (milestones MilestoneList) getMilestoneIDs() []int64 { + var ids = make([]int64, 0, len(milestones)) + for _, ms := range milestones { + ids = append(ids, ms.ID) } + return ids +} - countsSlice := make([]*struct { - RepoID int64 - Count int64 - }, 0, 10) - if err := sess.GroupBy("repo_id"). - Select("repo_id AS repo_id, COUNT(*) AS count"). - Table("milestone"). - Find(&countsSlice); err != nil { - return nil, err +// GetMilestonesByRepoID returns all opened milestones of a repository. +func GetMilestonesByRepoID(repoID int64, state api.StateType, listOptions ListOptions) (MilestoneList, error) { + sess := x.Where("repo_id = ?", repoID) + + switch state { + case api.StateClosed: + sess = sess.And("is_closed = ?", true) + + case api.StateAll: + break + + case api.StateOpen: + fallthrough + + default: + sess = sess.And("is_closed = ?", false) } - countMap := make(map[int64]int64, len(countsSlice)) - for _, c := range countsSlice { - countMap[c.RepoID] = c.Count + if listOptions.Page != 0 { + sess = listOptions.setSessionPagination(sess) } - return countMap, nil + + miles := make([]*Milestone, 0, listOptions.PageSize) + return miles, sess.Asc("deadline_unix").Asc("id").Find(&miles) } -// CountMilestonesByRepoIDs map from repoIDs to number of milestones matching the options` -func CountMilestonesByRepoIDs(repoIDs []int64, isClosed bool) (map[int64]int64, error) { - return CountMilestones( - builder.In("repo_id", repoIDs), - isClosed, - ) +// GetMilestones returns a list of milestones of given repository and status. +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 { + sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum) + } + + switch sortType { + case "furthestduedate": + sess.Desc("deadline_unix") + case "leastcomplete": + sess.Asc("completeness") + case "mostcomplete": + sess.Desc("completeness") + case "leastissues": + sess.Asc("num_issues") + case "mostissues": + sess.Desc("num_issues") + default: + sess.Asc("deadline_unix") + } + return miles, sess.Find(&miles) } // SearchMilestones search milestones @@ -606,6 +419,13 @@ func GetMilestonesByRepoIDs(repoIDs []int64, page int, isClosed bool, sortType s ) } +// ____ _ _ +// / ___|| |_ __ _| |_ ___ +// \___ \| __/ _` | __/ __| +// ___) | || (_| | |_\__ \ +// |____/ \__\__,_|\__|___/ +// + // MilestonesStats represents milestone statistic information. type MilestonesStats struct { OpenCount, ClosedCount int64 @@ -616,8 +436,8 @@ func (m MilestonesStats) Total() int64 { return m.OpenCount + m.ClosedCount } -// GetMilestonesStats returns milestone statistic information for dashboard by given conditions. -func GetMilestonesStats(repoCond builder.Cond) (*MilestonesStats, error) { +// GetMilestonesStatsByRepoCond returns milestone statistic information for dashboard by given conditions. +func GetMilestonesStatsByRepoCond(repoCond builder.Cond) (*MilestonesStats, error) { var err error stats := &MilestonesStats{} @@ -641,3 +461,158 @@ func GetMilestonesStats(repoCond builder.Cond) (*MilestonesStats, error) { return stats, nil } + +func countRepoMilestones(e Engine, repoID int64) (int64, error) { + return e. + Where("repo_id=?", repoID). + Count(new(Milestone)) +} + +func countRepoClosedMilestones(e Engine, repoID int64) (int64, error) { + return e. + Where("repo_id=? AND is_closed=?", repoID, true). + Count(new(Milestone)) +} + +// CountRepoClosedMilestones returns number of closed milestones in given repository. +func CountRepoClosedMilestones(repoID int64) (int64, error) { + return countRepoClosedMilestones(x, repoID) +} + +// CountMilestonesByRepoCond map from repo conditions to number of milestones matching the options` +func CountMilestonesByRepoCond(repoCond builder.Cond, isClosed bool) (map[int64]int64, error) { + sess := x.Where("is_closed = ?", isClosed) + if repoCond.IsValid() { + sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond)) + } + + countsSlice := make([]*struct { + RepoID int64 + Count int64 + }, 0, 10) + if err := sess.GroupBy("repo_id"). + Select("repo_id AS repo_id, COUNT(*) AS count"). + Table("milestone"). + Find(&countsSlice); err != nil { + return nil, err + } + + countMap := make(map[int64]int64, len(countsSlice)) + for _, c := range countsSlice { + countMap[c.RepoID] = c.Count + } + return countMap, nil +} + +func updateRepoMilestoneNum(e Engine, repoID int64) error { + _, err := e.Exec("UPDATE `repository` SET num_milestones=(SELECT count(*) FROM milestone WHERE repo_id=?),num_closed_milestones=(SELECT count(*) FROM milestone WHERE repo_id=? AND is_closed=?) WHERE id=?", + repoID, + repoID, + true, + repoID, + ) + return err +} + +func updateMilestoneTotalNum(e Engine, milestoneID int64) (err error) { + if _, err = e.Exec("UPDATE `milestone` SET num_issues=(SELECT count(*) FROM issue WHERE milestone_id=?) WHERE id=?", + milestoneID, + milestoneID, + ); err != nil { + return + } + + return updateMilestoneCompleteness(e, milestoneID) +} + +func updateMilestoneClosedNum(e Engine, milestoneID int64) (err error) { + if _, err = e.Exec("UPDATE `milestone` SET num_closed_issues=(SELECT count(*) FROM issue WHERE milestone_id=? AND is_closed=?) WHERE id=?", + milestoneID, + true, + milestoneID, + ); err != nil { + return + } + + return updateMilestoneCompleteness(e, milestoneID) +} + +// _____ _ _ _____ _ +// |_ _| __ __ _ ___| | _____ __| |_ _(_)_ __ ___ ___ ___ +// | || '__/ _` |/ __| |/ / _ \/ _` | | | | | '_ ` _ \ / _ \/ __| +// | || | | (_| | (__| < __/ (_| | | | | | | | | | | __/\__ \ +// |_||_| \__,_|\___|_|\_\___|\__,_| |_| |_|_| |_| |_|\___||___/ +// + +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)) + + // 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"). + Where("tracked_time.deleted = ?", false). + 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 (m *Milestone) loadTotalTrackedTime(e Engine) error { + type totalTimesByMilestone struct { + MilestoneID int64 + Time int64 + } + totalTime := &totalTimesByMilestone{MilestoneID: m.ID} + has, err := e.Table("issue"). + Join("INNER", "milestone", "issue.milestone_id = milestone.id"). + Join("LEFT", "tracked_time", "tracked_time.issue_id = issue.id"). + Where("tracked_time.deleted = ?", false). + Select("milestone_id, sum(time) as time"). + Where("milestone_id = ?", m.ID). + GroupBy("milestone_id"). + Get(totalTime) + if err != nil { + return err + } else if !has { + return nil + } + m.TotalTrackedTime = totalTime.Time + return nil +} + +// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request +func (milestones MilestoneList) LoadTotalTrackedTimes() error { + return milestones.loadTotalTrackedTimes(x) +} + +// LoadTotalTrackedTime loads the tracked time for the milestone +func (m *Milestone) LoadTotalTrackedTime() error { + return m.loadTotalTrackedTime(x) +} diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index da4e77ffe..07dd8d57c 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -7,13 +7,12 @@ package models import ( "sort" "testing" - "time" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" - "xorm.io/builder" "github.com/stretchr/testify/assert" + "xorm.io/builder" ) func TestMilestone_State(t *testing.T) { @@ -21,28 +20,6 @@ func TestMilestone_State(t *testing.T) { assert.Equal(t, api.StateClosed, (&Milestone{IsClosed: true}).State()) } -func TestMilestone_APIFormat(t *testing.T) { - milestone := &Milestone{ - ID: 3, - RepoID: 4, - Name: "milestoneName", - Content: "milestoneContent", - IsClosed: false, - NumOpenIssues: 5, - NumClosedIssues: 6, - DeadlineUnix: timeutil.TimeStamp(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC).Unix()), - } - assert.Equal(t, api.Milestone{ - ID: milestone.ID, - State: api.StateOpen, - Title: milestone.Name, - Description: milestone.Content, - OpenIssues: milestone.NumOpenIssues, - ClosedIssues: milestone.NumClosedIssues, - Deadline: milestone.DeadlineUnix.AsTimePtr(), - }, *milestone.APIFormat()) -} - func TestNewMilestone(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) milestone := &Milestone{ @@ -201,25 +178,6 @@ func TestCountRepoClosedMilestones(t *testing.T) { assert.EqualValues(t, 0, count) } -func TestMilestoneStats(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - test := func(repoID int64) { - repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) - open, closed, err := MilestoneStats(repoID) - assert.NoError(t, err) - assert.EqualValues(t, repo.NumMilestones-repo.NumClosedMilestones, open) - assert.EqualValues(t, repo.NumClosedMilestones, closed) - } - test(1) - test(2) - test(3) - - open, closed, err := MilestoneStats(NonexistentID) - assert.NoError(t, err) - assert.EqualValues(t, 0, open) - assert.EqualValues(t, 0, closed) -} - func TestChangeMilestoneStatus(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) @@ -301,12 +259,12 @@ func TestCountMilestonesByRepoIDs(t *testing.T) { repo1OpenCount, repo1ClosedCount := milestonesCount(1) repo2OpenCount, repo2ClosedCount := milestonesCount(2) - openCounts, err := CountMilestonesByRepoIDs([]int64{1, 2}, false) + openCounts, err := CountMilestonesByRepoCond(builder.In("repo_id", []int64{1, 2}), false) assert.NoError(t, err) assert.EqualValues(t, repo1OpenCount, openCounts[1]) assert.EqualValues(t, repo2OpenCount, openCounts[2]) - closedCounts, err := CountMilestonesByRepoIDs([]int64{1, 2}, true) + closedCounts, err := CountMilestonesByRepoCond(builder.In("repo_id", []int64{1, 2}), true) assert.NoError(t, err) assert.EqualValues(t, repo1ClosedCount, closedCounts[1]) assert.EqualValues(t, repo2ClosedCount, closedCounts[2]) @@ -368,10 +326,27 @@ func TestLoadTotalTrackedTime(t *testing.T) { func TestGetMilestonesStats(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) + + test := func(repoID int64) { + repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + stats, err := GetMilestonesStatsByRepoCond(builder.And(builder.Eq{"repo_id": repoID})) + assert.NoError(t, err) + assert.EqualValues(t, repo.NumMilestones-repo.NumClosedMilestones, stats.OpenCount) + assert.EqualValues(t, repo.NumClosedMilestones, stats.ClosedCount) + } + test(1) + test(2) + test(3) + + stats, err := GetMilestonesStatsByRepoCond(builder.And(builder.Eq{"repo_id": NonexistentID})) + assert.NoError(t, err) + assert.EqualValues(t, 0, stats.OpenCount) + assert.EqualValues(t, 0, stats.ClosedCount) + repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) - milestoneStats, err := GetMilestonesStats(builder.In("repo_id", []int64{repo1.ID, repo2.ID})) + milestoneStats, err := GetMilestonesStatsByRepoCond(builder.In("repo_id", []int64{repo1.ID, repo2.ID})) assert.NoError(t, err) assert.EqualValues(t, repo1.NumOpenMilestones+repo2.NumOpenMilestones, milestoneStats.OpenCount) assert.EqualValues(t, repo1.NumClosedMilestones+repo2.NumClosedMilestones, milestoneStats.ClosedCount) diff --git a/modules/convert/issue.go b/modules/convert/issue.go index d0985b6be..ab1f9f1e6 100644 --- a/modules/convert/issue.go +++ b/modules/convert/issue.go @@ -56,7 +56,7 @@ func ToAPIIssue(issue *models.Issue) *api.Issue { return &api.Issue{} } if issue.Milestone != nil { - apiIssue.Milestone = issue.Milestone.APIFormat() + apiIssue.Milestone = ToAPIMilestone(issue.Milestone) } if err := issue.LoadAssignees(); err != nil { @@ -141,3 +141,22 @@ func ToLabelList(labels []*models.Label) []*api.Label { } return result } + +// ToAPIMilestone converts Milestone into API Format +func ToAPIMilestone(m *models.Milestone) *api.Milestone { + apiMilestone := &api.Milestone{ + ID: m.ID, + State: m.State(), + Title: m.Name, + Description: m.Content, + OpenIssues: m.NumOpenIssues, + ClosedIssues: m.NumClosedIssues, + } + if m.IsClosed { + apiMilestone.Closed = m.ClosedDateUnix.AsTimePtr() + } + if m.DeadlineUnix.Year() < 9999 { + apiMilestone.Deadline = m.DeadlineUnix.AsTimePtr() + } + return apiMilestone +} diff --git a/modules/convert/issue_test.go b/modules/convert/issue_test.go index a7286d076..e5676293f 100644 --- a/modules/convert/issue_test.go +++ b/modules/convert/issue_test.go @@ -6,9 +6,11 @@ package convert import ( "testing" + "time" "code.gitea.io/gitea/models" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" ) @@ -22,3 +24,25 @@ func TestLabel_ToLabel(t *testing.T) { Color: "abcdef", }, ToLabel(label)) } + +func TestMilestone_APIFormat(t *testing.T) { + milestone := &models.Milestone{ + ID: 3, + RepoID: 4, + Name: "milestoneName", + Content: "milestoneContent", + IsClosed: false, + NumOpenIssues: 5, + NumClosedIssues: 6, + DeadlineUnix: timeutil.TimeStamp(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC).Unix()), + } + assert.Equal(t, api.Milestone{ + ID: milestone.ID, + State: api.StateOpen, + Title: milestone.Name, + Description: milestone.Content, + OpenIssues: milestone.NumOpenIssues, + ClosedIssues: milestone.NumClosedIssues, + Deadline: milestone.DeadlineUnix.AsTimePtr(), + }, *ToAPIMilestone(milestone)) +} diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go index 80d30e2c0..1bfd54df8 100644 --- a/routers/api/v1/repo/milestone.go +++ b/routers/api/v1/repo/milestone.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/routers/api/v1/utils" @@ -58,7 +59,7 @@ func ListMilestones(ctx *context.APIContext) { apiMilestones := make([]*api.Milestone, len(milestones)) for i := range milestones { - apiMilestones[i] = milestones[i].APIFormat() + apiMilestones[i] = convert.ToAPIMilestone(milestones[i]) } ctx.JSON(http.StatusOK, &apiMilestones) } @@ -100,7 +101,7 @@ func GetMilestone(ctx *context.APIContext) { } return } - ctx.JSON(http.StatusOK, milestone.APIFormat()) + ctx.JSON(http.StatusOK, convert.ToAPIMilestone(milestone)) } // CreateMilestone create a milestone for a repository @@ -147,7 +148,7 @@ func CreateMilestone(ctx *context.APIContext, form api.CreateMilestoneOption) { ctx.Error(http.StatusInternalServerError, "NewMilestone", err) return } - ctx.JSON(http.StatusCreated, milestone.APIFormat()) + ctx.JSON(http.StatusCreated, convert.ToAPIMilestone(milestone)) } // EditMilestone modify a milestone for a repository @@ -213,7 +214,7 @@ func EditMilestone(ctx *context.APIContext, form api.EditMilestoneOption) { ctx.ServerError("UpdateMilestone", err) return } - ctx.JSON(http.StatusOK, milestone.APIFormat()) + ctx.JSON(http.StatusOK, convert.ToAPIMilestone(milestone)) } // DeleteMilestone delete a milestone for a repository diff --git a/routers/repo/milestone.go b/routers/repo/milestone.go index 5fbf929f3..e30e6371f 100644 --- a/routers/repo/milestone.go +++ b/routers/repo/milestone.go @@ -15,6 +15,8 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + + "xorm.io/builder" ) const ( @@ -30,13 +32,13 @@ func Milestones(ctx *context.Context) { ctx.Data["PageIsMilestones"] = true isShowClosed := ctx.Query("state") == "closed" - openCount, closedCount, err := models.MilestoneStats(ctx.Repo.Repository.ID) + stats, err := models.GetMilestonesStatsByRepoCond(builder.And(builder.Eq{"id": ctx.Repo.Repository.ID})) if err != nil { ctx.ServerError("MilestoneStats", err) return } - ctx.Data["OpenCount"] = openCount - ctx.Data["ClosedCount"] = closedCount + ctx.Data["OpenCount"] = stats.OpenCount + ctx.Data["ClosedCount"] = stats.ClosedCount sortType := ctx.Query("sort") page := ctx.QueryInt("page") @@ -46,9 +48,9 @@ func Milestones(ctx *context.Context) { var total int if !isShowClosed { - total = int(openCount) + total = int(stats.OpenCount) } else { - total = int(closedCount) + total = int(stats.ClosedCount) } miles, err := models.GetMilestones(ctx.Repo.Repository.ID, page, isShowClosed, sortType) diff --git a/routers/user/home.go b/routers/user/home.go index 816968562..199694f23 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -224,7 +224,7 @@ func Milestones(ctx *context.Context) { } } - counts, err := models.CountMilestones(userRepoCond, isShowClosed) + counts, err := models.CountMilestonesByRepoCond(userRepoCond, isShowClosed) if err != nil { ctx.ServerError("CountMilestonesByRepoIDs", err) return @@ -267,7 +267,7 @@ func Milestones(ctx *context.Context) { i++ } - milestoneStats, err := models.GetMilestonesStats(repoCond) + milestoneStats, err := models.GetMilestonesStatsByRepoCond(repoCond) if err != nil { ctx.ServerError("GetMilestoneStats", err) return @@ -277,7 +277,7 @@ func Milestones(ctx *context.Context) { if len(repoIDs) == 0 { totalMilestoneStats = milestoneStats } else { - totalMilestoneStats, err = models.GetMilestonesStats(userRepoCond) + totalMilestoneStats, err = models.GetMilestonesStatsByRepoCond(userRepoCond) if err != nil { ctx.ServerError("GetMilestoneStats", err) return