code.gitea.io/gitea@v1.22.3/models/project/issue.go (about) 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package project 5 6 import ( 7 "context" 8 "fmt" 9 10 "code.gitea.io/gitea/models/db" 11 "code.gitea.io/gitea/modules/log" 12 "code.gitea.io/gitea/modules/util" 13 ) 14 15 // ProjectIssue saves relation from issue to a project 16 type ProjectIssue struct { //revive:disable-line:exported 17 ID int64 `xorm:"pk autoincr"` 18 IssueID int64 `xorm:"INDEX"` 19 ProjectID int64 `xorm:"INDEX"` 20 21 // ProjectBoardID should not be zero since 1.22. If it's zero, the issue will not be displayed on UI and it might result in errors. 22 ProjectBoardID int64 `xorm:"INDEX"` 23 24 // the sorting order on the board 25 Sorting int64 `xorm:"NOT NULL DEFAULT 0"` 26 } 27 28 func init() { 29 db.RegisterModel(new(ProjectIssue)) 30 } 31 32 func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error { 33 _, err := db.GetEngine(ctx).Where("project_id=?", projectID).Delete(&ProjectIssue{}) 34 return err 35 } 36 37 // NumIssues return counter of all issues assigned to a project 38 func (p *Project) NumIssues(ctx context.Context) int { 39 c, err := db.GetEngine(ctx).Table("project_issue"). 40 Where("project_id=?", p.ID). 41 GroupBy("issue_id"). 42 Cols("issue_id"). 43 Count() 44 if err != nil { 45 log.Error("NumIssues: %v", err) 46 return 0 47 } 48 return int(c) 49 } 50 51 // NumClosedIssues return counter of closed issues assigned to a project 52 func (p *Project) NumClosedIssues(ctx context.Context) int { 53 c, err := db.GetEngine(ctx).Table("project_issue"). 54 Join("INNER", "issue", "project_issue.issue_id=issue.id"). 55 Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true). 56 Cols("issue_id"). 57 Count() 58 if err != nil { 59 log.Error("NumClosedIssues: %v", err) 60 return 0 61 } 62 return int(c) 63 } 64 65 // NumOpenIssues return counter of open issues assigned to a project 66 func (p *Project) NumOpenIssues(ctx context.Context) int { 67 c, err := db.GetEngine(ctx).Table("project_issue"). 68 Join("INNER", "issue", "project_issue.issue_id=issue.id"). 69 Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false). 70 Cols("issue_id"). 71 Count() 72 if err != nil { 73 log.Error("NumOpenIssues: %v", err) 74 return 0 75 } 76 return int(c) 77 } 78 79 // MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column 80 func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64) error { 81 return db.WithTx(ctx, func(ctx context.Context) error { 82 sess := db.GetEngine(ctx) 83 issueIDs := util.ValuesOfMap(sortedIssueIDs) 84 85 count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count() 86 if err != nil { 87 return err 88 } 89 if int(count) != len(sortedIssueIDs) { 90 return fmt.Errorf("all issues have to be added to a project first") 91 } 92 93 for sorting, issueID := range sortedIssueIDs { 94 _, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID) 95 if err != nil { 96 return err 97 } 98 } 99 return nil 100 }) 101 } 102 103 func (b *Board) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Board) error { 104 if b.ProjectID != newColumn.ProjectID { 105 return fmt.Errorf("columns have to be in the same project") 106 } 107 108 if b.ID == newColumn.ID { 109 return nil 110 } 111 112 res := struct { 113 MaxSorting int64 114 IssueCount int64 115 }{} 116 if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as issue_count"). 117 Table("project_issue"). 118 Where("project_id=?", newColumn.ProjectID). 119 And("project_board_id=?", newColumn.ID). 120 Get(&res); err != nil { 121 return err 122 } 123 124 issues, err := b.GetIssues(ctx) 125 if err != nil { 126 return err 127 } 128 if len(issues) == 0 { 129 return nil 130 } 131 132 nextSorting := util.Iif(res.IssueCount > 0, res.MaxSorting+1, 0) 133 return db.WithTx(ctx, func(ctx context.Context) error { 134 for i, issue := range issues { 135 issue.ProjectBoardID = newColumn.ID 136 issue.Sorting = nextSorting + int64(i) 137 if _, err := db.GetEngine(ctx).ID(issue.ID).Cols("project_board_id", "sorting").Update(issue); err != nil { 138 return err 139 } 140 } 141 return nil 142 }) 143 } 144 145 // DeleteAllProjectIssueByIssueIDsAndProjectIDs delete all project's issues by issue's and project's ids 146 func DeleteAllProjectIssueByIssueIDsAndProjectIDs(ctx context.Context, issueIDs, projectIDs []int64) error { 147 _, err := db.GetEngine(ctx).In("project_id", projectIDs).In("issue_id", issueIDs).Delete(&ProjectIssue{}) 148 return err 149 }