Refactor Milestone related (#11225)
This commit is contained in:
		
							parent
							
								
									ba40263fdd
								
							
						
					
					
						commit
						7257c39ddf
					
				|  | @ -69,25 +69,6 @@ func (m *Milestone) State() api.StateType { | ||||||
| 	return api.StateOpen | 	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.
 | // NewMilestone creates new milestone of repository.
 | ||||||
| func NewMilestone(m *Milestone) (err error) { | func NewMilestone(m *Milestone) (err error) { | ||||||
| 	sess := x.NewSession() | 	sess := x.NewSession() | ||||||
|  | @ -149,157 +130,6 @@ func GetMilestoneByID(id int64) (*Milestone, error) { | ||||||
| 	return &m, nil | 	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.
 | // UpdateMilestone updates information of given milestone.
 | ||||||
| func UpdateMilestone(m *Milestone, oldIsClosed bool) error { | func UpdateMilestone(m *Milestone, oldIsClosed bool) error { | ||||||
| 	sess := x.NewSession() | 	sess := x.NewSession() | ||||||
|  | @ -330,6 +160,22 @@ func UpdateMilestone(m *Milestone, oldIsClosed bool) error { | ||||||
| 	return sess.Commit() | 	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 { | 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=?", | 	_, 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, | 		milestoneID, | ||||||
|  | @ -337,35 +183,6 @@ func updateMilestoneCompleteness(e Engine, milestoneID int64) error { | ||||||
| 	return err | 	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.
 | // ChangeMilestoneStatus changes the milestone open/closed status.
 | ||||||
| func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) { | func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) { | ||||||
| 	sess := x.NewSession() | 	sess := x.NewSession() | ||||||
|  | @ -390,39 +207,6 @@ func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) { | ||||||
| 	return sess.Commit() | 	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 { | func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilestoneID int64) error { | ||||||
| 	if err := updateIssueCols(e, issue, "milestone_id"); err != nil { | 	if err := updateIssueCols(e, issue, "milestone_id"); err != nil { | ||||||
| 		return err | 		return err | ||||||
|  | @ -535,37 +319,66 @@ func DeleteMilestoneByRepoID(repoID, id int64) error { | ||||||
| 	return sess.Commit() | 	return sess.Commit() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CountMilestones map from repo conditions to number of milestones matching the options`
 | // MilestoneList is a list of milestones offering additional functionality
 | ||||||
| func CountMilestones(repoCond builder.Cond, isClosed bool) (map[int64]int64, error) { | type MilestoneList []*Milestone | ||||||
| 	sess := x.Where("is_closed = ?", isClosed) | 
 | ||||||
| 	if repoCond.IsValid() { | func (milestones MilestoneList) getMilestoneIDs() []int64 { | ||||||
| 		sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond)) | 	var ids = make([]int64, 0, len(milestones)) | ||||||
|  | 	for _, ms := range milestones { | ||||||
|  | 		ids = append(ids, ms.ID) | ||||||
|  | 	} | ||||||
|  | 	return ids | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 	countsSlice := make([]*struct { | // GetMilestonesByRepoID returns all opened milestones of a repository.
 | ||||||
| 		RepoID int64 | func GetMilestonesByRepoID(repoID int64, state api.StateType, listOptions ListOptions) (MilestoneList, error) { | ||||||
| 		Count  int64 | 	sess := x.Where("repo_id = ?", repoID) | ||||||
| 	}, 0, 10) | 
 | ||||||
| 	if err := sess.GroupBy("repo_id"). | 	switch state { | ||||||
| 		Select("repo_id AS repo_id, COUNT(*) AS count"). | 	case api.StateClosed: | ||||||
| 		Table("milestone"). | 		sess = sess.And("is_closed = ?", true) | ||||||
| 		Find(&countsSlice); err != nil { | 
 | ||||||
| 		return nil, err | 	case api.StateAll: | ||||||
|  | 		break | ||||||
|  | 
 | ||||||
|  | 	case api.StateOpen: | ||||||
|  | 		fallthrough | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		sess = sess.And("is_closed = ?", false) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	countMap := make(map[int64]int64, len(countsSlice)) | 	if listOptions.Page != 0 { | ||||||
| 	for _, c := range countsSlice { | 		sess = listOptions.setSessionPagination(sess) | ||||||
| 		countMap[c.RepoID] = c.Count |  | ||||||
| 	} |  | ||||||
| 	return countMap, nil |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| // CountMilestonesByRepoIDs map from repoIDs to number of milestones matching the options`
 | 	miles := make([]*Milestone, 0, listOptions.PageSize) | ||||||
| func CountMilestonesByRepoIDs(repoIDs []int64, isClosed bool) (map[int64]int64, error) { | 	return miles, sess.Asc("deadline_unix").Asc("id").Find(&miles) | ||||||
| 	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
 | // SearchMilestones search milestones
 | ||||||
|  | @ -606,6 +419,13 @@ func GetMilestonesByRepoIDs(repoIDs []int64, page int, isClosed bool, sortType s | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | //  ____  _        _
 | ||||||
|  | // / ___|| |_ __ _| |_ ___
 | ||||||
|  | // \___ \| __/ _` | __/ __|
 | ||||||
|  | //  ___) | || (_| | |_\__ \
 | ||||||
|  | // |____/ \__\__,_|\__|___/
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
| // MilestonesStats represents milestone statistic information.
 | // MilestonesStats represents milestone statistic information.
 | ||||||
| type MilestonesStats struct { | type MilestonesStats struct { | ||||||
| 	OpenCount, ClosedCount int64 | 	OpenCount, ClosedCount int64 | ||||||
|  | @ -616,8 +436,8 @@ func (m MilestonesStats) Total() int64 { | ||||||
| 	return m.OpenCount + m.ClosedCount | 	return m.OpenCount + m.ClosedCount | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetMilestonesStats returns milestone statistic information for dashboard by given conditions.
 | // GetMilestonesStatsByRepoCond returns milestone statistic information for dashboard by given conditions.
 | ||||||
| func GetMilestonesStats(repoCond builder.Cond) (*MilestonesStats, error) { | func GetMilestonesStatsByRepoCond(repoCond builder.Cond) (*MilestonesStats, error) { | ||||||
| 	var err error | 	var err error | ||||||
| 	stats := &MilestonesStats{} | 	stats := &MilestonesStats{} | ||||||
| 
 | 
 | ||||||
|  | @ -641,3 +461,158 @@ func GetMilestonesStats(repoCond builder.Cond) (*MilestonesStats, error) { | ||||||
| 
 | 
 | ||||||
| 	return stats, nil | 	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) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -7,13 +7,12 @@ package models | ||||||
| import ( | import ( | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" |  | ||||||
| 
 | 
 | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| 	"xorm.io/builder" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"xorm.io/builder" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestMilestone_State(t *testing.T) { | 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()) | 	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) { | func TestNewMilestone(t *testing.T) { | ||||||
| 	assert.NoError(t, PrepareTestDatabase()) | 	assert.NoError(t, PrepareTestDatabase()) | ||||||
| 	milestone := &Milestone{ | 	milestone := &Milestone{ | ||||||
|  | @ -201,25 +178,6 @@ func TestCountRepoClosedMilestones(t *testing.T) { | ||||||
| 	assert.EqualValues(t, 0, count) | 	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) { | func TestChangeMilestoneStatus(t *testing.T) { | ||||||
| 	assert.NoError(t, PrepareTestDatabase()) | 	assert.NoError(t, PrepareTestDatabase()) | ||||||
| 	milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) | 	milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) | ||||||
|  | @ -301,12 +259,12 @@ func TestCountMilestonesByRepoIDs(t *testing.T) { | ||||||
| 	repo1OpenCount, repo1ClosedCount := milestonesCount(1) | 	repo1OpenCount, repo1ClosedCount := milestonesCount(1) | ||||||
| 	repo2OpenCount, repo2ClosedCount := milestonesCount(2) | 	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.NoError(t, err) | ||||||
| 	assert.EqualValues(t, repo1OpenCount, openCounts[1]) | 	assert.EqualValues(t, repo1OpenCount, openCounts[1]) | ||||||
| 	assert.EqualValues(t, repo2OpenCount, openCounts[2]) | 	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.NoError(t, err) | ||||||
| 	assert.EqualValues(t, repo1ClosedCount, closedCounts[1]) | 	assert.EqualValues(t, repo1ClosedCount, closedCounts[1]) | ||||||
| 	assert.EqualValues(t, repo2ClosedCount, closedCounts[2]) | 	assert.EqualValues(t, repo2ClosedCount, closedCounts[2]) | ||||||
|  | @ -368,10 +326,27 @@ func TestLoadTotalTrackedTime(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func TestGetMilestonesStats(t *testing.T) { | func TestGetMilestonesStats(t *testing.T) { | ||||||
| 	assert.NoError(t, PrepareTestDatabase()) | 	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) | 	repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | ||||||
| 	repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*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.NoError(t, err) | ||||||
| 	assert.EqualValues(t, repo1.NumOpenMilestones+repo2.NumOpenMilestones, milestoneStats.OpenCount) | 	assert.EqualValues(t, repo1.NumOpenMilestones+repo2.NumOpenMilestones, milestoneStats.OpenCount) | ||||||
| 	assert.EqualValues(t, repo1.NumClosedMilestones+repo2.NumClosedMilestones, milestoneStats.ClosedCount) | 	assert.EqualValues(t, repo1.NumClosedMilestones+repo2.NumClosedMilestones, milestoneStats.ClosedCount) | ||||||
|  |  | ||||||
|  | @ -56,7 +56,7 @@ func ToAPIIssue(issue *models.Issue) *api.Issue { | ||||||
| 		return &api.Issue{} | 		return &api.Issue{} | ||||||
| 	} | 	} | ||||||
| 	if issue.Milestone != nil { | 	if issue.Milestone != nil { | ||||||
| 		apiIssue.Milestone = issue.Milestone.APIFormat() | 		apiIssue.Milestone = ToAPIMilestone(issue.Milestone) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := issue.LoadAssignees(); err != nil { | 	if err := issue.LoadAssignees(); err != nil { | ||||||
|  | @ -141,3 +141,22 @@ func ToLabelList(labels []*models.Label) []*api.Label { | ||||||
| 	} | 	} | ||||||
| 	return result | 	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 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -6,9 +6,11 @@ package convert | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
|  | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| 
 | 
 | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
|  | @ -22,3 +24,25 @@ func TestLabel_ToLabel(t *testing.T) { | ||||||
| 		Color: "abcdef", | 		Color: "abcdef", | ||||||
| 	}, ToLabel(label)) | 	}, 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)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
|  | 	"code.gitea.io/gitea/modules/convert" | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| 	"code.gitea.io/gitea/routers/api/v1/utils" | 	"code.gitea.io/gitea/routers/api/v1/utils" | ||||||
|  | @ -58,7 +59,7 @@ func ListMilestones(ctx *context.APIContext) { | ||||||
| 
 | 
 | ||||||
| 	apiMilestones := make([]*api.Milestone, len(milestones)) | 	apiMilestones := make([]*api.Milestone, len(milestones)) | ||||||
| 	for i := range milestones { | 	for i := range milestones { | ||||||
| 		apiMilestones[i] = milestones[i].APIFormat() | 		apiMilestones[i] = convert.ToAPIMilestone(milestones[i]) | ||||||
| 	} | 	} | ||||||
| 	ctx.JSON(http.StatusOK, &apiMilestones) | 	ctx.JSON(http.StatusOK, &apiMilestones) | ||||||
| } | } | ||||||
|  | @ -100,7 +101,7 @@ func GetMilestone(ctx *context.APIContext) { | ||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	ctx.JSON(http.StatusOK, milestone.APIFormat()) | 	ctx.JSON(http.StatusOK, convert.ToAPIMilestone(milestone)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CreateMilestone create a milestone for a repository
 | // 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) | 		ctx.Error(http.StatusInternalServerError, "NewMilestone", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	ctx.JSON(http.StatusCreated, milestone.APIFormat()) | 	ctx.JSON(http.StatusCreated, convert.ToAPIMilestone(milestone)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // EditMilestone modify a milestone for a repository
 | // EditMilestone modify a milestone for a repository
 | ||||||
|  | @ -213,7 +214,7 @@ func EditMilestone(ctx *context.APIContext, form api.EditMilestoneOption) { | ||||||
| 		ctx.ServerError("UpdateMilestone", err) | 		ctx.ServerError("UpdateMilestone", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	ctx.JSON(http.StatusOK, milestone.APIFormat()) | 	ctx.JSON(http.StatusOK, convert.ToAPIMilestone(milestone)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DeleteMilestone delete a milestone for a repository
 | // DeleteMilestone delete a milestone for a repository
 | ||||||
|  |  | ||||||
|  | @ -15,6 +15,8 @@ import ( | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
|  | 
 | ||||||
|  | 	"xorm.io/builder" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
|  | @ -30,13 +32,13 @@ func Milestones(ctx *context.Context) { | ||||||
| 	ctx.Data["PageIsMilestones"] = true | 	ctx.Data["PageIsMilestones"] = true | ||||||
| 
 | 
 | ||||||
| 	isShowClosed := ctx.Query("state") == "closed" | 	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 { | 	if err != nil { | ||||||
| 		ctx.ServerError("MilestoneStats", err) | 		ctx.ServerError("MilestoneStats", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	ctx.Data["OpenCount"] = openCount | 	ctx.Data["OpenCount"] = stats.OpenCount | ||||||
| 	ctx.Data["ClosedCount"] = closedCount | 	ctx.Data["ClosedCount"] = stats.ClosedCount | ||||||
| 
 | 
 | ||||||
| 	sortType := ctx.Query("sort") | 	sortType := ctx.Query("sort") | ||||||
| 	page := ctx.QueryInt("page") | 	page := ctx.QueryInt("page") | ||||||
|  | @ -46,9 +48,9 @@ func Milestones(ctx *context.Context) { | ||||||
| 
 | 
 | ||||||
| 	var total int | 	var total int | ||||||
| 	if !isShowClosed { | 	if !isShowClosed { | ||||||
| 		total = int(openCount) | 		total = int(stats.OpenCount) | ||||||
| 	} else { | 	} else { | ||||||
| 		total = int(closedCount) | 		total = int(stats.ClosedCount) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	miles, err := models.GetMilestones(ctx.Repo.Repository.ID, page, isShowClosed, sortType) | 	miles, err := models.GetMilestones(ctx.Repo.Repository.ID, page, isShowClosed, sortType) | ||||||
|  |  | ||||||
|  | @ -224,7 +224,7 @@ func Milestones(ctx *context.Context) { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	counts, err := models.CountMilestones(userRepoCond, isShowClosed) | 	counts, err := models.CountMilestonesByRepoCond(userRepoCond, isShowClosed) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.ServerError("CountMilestonesByRepoIDs", err) | 		ctx.ServerError("CountMilestonesByRepoIDs", err) | ||||||
| 		return | 		return | ||||||
|  | @ -267,7 +267,7 @@ func Milestones(ctx *context.Context) { | ||||||
| 		i++ | 		i++ | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	milestoneStats, err := models.GetMilestonesStats(repoCond) | 	milestoneStats, err := models.GetMilestonesStatsByRepoCond(repoCond) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.ServerError("GetMilestoneStats", err) | 		ctx.ServerError("GetMilestoneStats", err) | ||||||
| 		return | 		return | ||||||
|  | @ -277,7 +277,7 @@ func Milestones(ctx *context.Context) { | ||||||
| 	if len(repoIDs) == 0 { | 	if len(repoIDs) == 0 { | ||||||
| 		totalMilestoneStats = milestoneStats | 		totalMilestoneStats = milestoneStats | ||||||
| 	} else { | 	} else { | ||||||
| 		totalMilestoneStats, err = models.GetMilestonesStats(userRepoCond) | 		totalMilestoneStats, err = models.GetMilestonesStatsByRepoCond(userRepoCond) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.ServerError("GetMilestoneStats", err) | 			ctx.ServerError("GetMilestoneStats", err) | ||||||
| 			return | 			return | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue