123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- // Copyright 2023 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package issues
-
- import (
- "context"
- "strings"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/optional"
-
- "xorm.io/builder"
- )
-
- // MilestoneList is a list of milestones offering additional functionality
- type MilestoneList []*Milestone
-
- func (milestones MilestoneList) getMilestoneIDs() []int64 {
- ids := make([]int64, 0, len(milestones))
- for _, ms := range milestones {
- ids = append(ids, ms.ID)
- }
- return ids
- }
-
- // FindMilestoneOptions contain options to get milestones
- type FindMilestoneOptions struct {
- db.ListOptions
- RepoID int64
- IsClosed optional.Option[bool]
- Name string
- SortType string
- RepoCond builder.Cond
- RepoIDs []int64
- }
-
- func (opts FindMilestoneOptions) ToConds() builder.Cond {
- cond := builder.NewCond()
- if opts.RepoID != 0 {
- cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
- }
- if opts.IsClosed.Has() {
- cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.Value()})
- }
- if opts.RepoCond != nil && opts.RepoCond.IsValid() {
- cond = cond.And(builder.In("repo_id", builder.Select("id").From("repository").Where(opts.RepoCond)))
- }
- if len(opts.RepoIDs) > 0 {
- cond = cond.And(builder.In("repo_id", opts.RepoIDs))
- }
- if len(opts.Name) != 0 {
- cond = cond.And(db.BuildCaseInsensitiveLike("name", opts.Name))
- }
-
- return cond
- }
-
- func (opts FindMilestoneOptions) ToOrders() string {
- switch opts.SortType {
- case "furthestduedate":
- return "deadline_unix DESC"
- case "leastcomplete":
- return "completeness ASC"
- case "mostcomplete":
- return "completeness DESC"
- case "leastissues":
- return "num_issues ASC"
- case "mostissues":
- return "num_issues DESC"
- case "id":
- return "id ASC"
- default:
- return "deadline_unix ASC, id ASC"
- }
- }
-
- // GetMilestoneIDsByNames returns a list of milestone ids by given names.
- // It doesn't filter them by repo, so it could return milestones belonging to different repos.
- // It's used for filtering issues via indexer, otherwise it would be useless.
- // Since it could return milestones with the same name, so the length of returned ids could be more than the length of names.
- func GetMilestoneIDsByNames(ctx context.Context, names []string) ([]int64, error) {
- var ids []int64
- return ids, db.GetEngine(ctx).Table("milestone").
- Where(db.BuildCaseInsensitiveIn("name", names)).
- Cols("id").
- Find(&ids)
- }
-
- // LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request
- func (milestones MilestoneList) LoadTotalTrackedTimes(ctx context.Context) error {
- type totalTimesByMilestone struct {
- MilestoneID int64
- Time int64
- }
- if len(milestones) == 0 {
- return nil
- }
- trackedTimes := make(map[int64]int64, len(milestones))
-
- // Get total tracked time by milestone_id
- rows, err := db.GetEngine(ctx).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
- }
-
- // CountMilestonesByRepoCondAndKw map from repo conditions and the keyword of milestones' name to number of milestones matching the options`
- func CountMilestonesMap(ctx context.Context, opts FindMilestoneOptions) (map[int64]int64, error) {
- sess := db.GetEngine(ctx).Where(opts.ToConds())
-
- 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
- }
-
- // MilestonesStats represents milestone statistic information.
- type MilestonesStats struct {
- OpenCount, ClosedCount int64
- }
-
- // Total returns the total counts of milestones
- func (m MilestonesStats) Total() int64 {
- return m.OpenCount + m.ClosedCount
- }
-
- // GetMilestonesStatsByRepoCondAndKw returns milestone statistic information for dashboard by given repo conditions and name keyword.
- func GetMilestonesStatsByRepoCondAndKw(ctx context.Context, repoCond builder.Cond, keyword string) (*MilestonesStats, error) {
- var err error
- stats := &MilestonesStats{}
-
- sess := db.GetEngine(ctx).Where("is_closed = ?", false)
- if len(keyword) > 0 {
- sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
- }
- if repoCond.IsValid() {
- sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
- }
- stats.OpenCount, err = sess.Count(new(Milestone))
- if err != nil {
- return nil, err
- }
-
- sess = db.GetEngine(ctx).Where("is_closed = ?", true)
- if len(keyword) > 0 {
- sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
- }
- if repoCond.IsValid() {
- sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
- }
- stats.ClosedCount, err = sess.Count(new(Milestone))
- if err != nil {
- return nil, err
- }
-
- return stats, nil
- }
|