2020-08-17 04:07:38 +01:00
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
2021-11-19 21:39:57 +08:00
"context"
2020-08-17 04:07:38 +01:00
"fmt"
2021-09-19 19:49:59 +08:00
"code.gitea.io/gitea/models/db"
2021-11-24 17:49:20 +08:00
user_model "code.gitea.io/gitea/models/user"
2020-08-17 04:07:38 +01:00
)
// ProjectIssue saves relation from issue to a project
type ProjectIssue struct {
ID int64 ` xorm:"pk autoincr" `
IssueID int64 ` xorm:"INDEX" `
ProjectID int64 ` xorm:"INDEX" `
// If 0, then it has not been added to a specific board in the project
ProjectBoardID int64 ` xorm:"INDEX" `
2021-12-08 07:57:18 +01:00
Sorting int64 ` xorm:"NOT NULL DEFAULT 0" `
2020-08-17 04:07:38 +01:00
}
2021-09-19 19:49:59 +08:00
func init ( ) {
db . RegisterModel ( new ( ProjectIssue ) )
}
func deleteProjectIssuesByProjectID ( e db . Engine , projectID int64 ) error {
2020-08-17 04:07:38 +01:00
_ , err := e . Where ( "project_id=?" , projectID ) . Delete ( & ProjectIssue { } )
return err
}
// ___
// |_ _|___ ___ _ _ ___
// | |/ __/ __| | | |/ _ \
// | |\__ \__ \ |_| | __/
// |___|___/___/\__,_|\___|
// LoadProject load the project the issue was assigned to
func ( i * Issue ) LoadProject ( ) ( err error ) {
2021-09-23 16:45:36 +01:00
return i . loadProject ( db . GetEngine ( db . DefaultContext ) )
2020-08-17 04:07:38 +01:00
}
2021-09-19 19:49:59 +08:00
func ( i * Issue ) loadProject ( e db . Engine ) ( err error ) {
2020-08-17 04:07:38 +01:00
if i . Project == nil {
var p Project
if _ , err = e . Table ( "project" ) .
Join ( "INNER" , "project_issue" , "project.id=project_issue.project_id" ) .
Where ( "project_issue.issue_id = ?" , i . ID ) .
Get ( & p ) ; err != nil {
return err
}
i . Project = & p
}
return
}
// ProjectID return project id if issue was assigned to one
func ( i * Issue ) ProjectID ( ) int64 {
2021-09-23 16:45:36 +01:00
return i . projectID ( db . GetEngine ( db . DefaultContext ) )
2020-08-17 04:07:38 +01:00
}
2021-09-19 19:49:59 +08:00
func ( i * Issue ) projectID ( e db . Engine ) int64 {
2020-08-17 04:07:38 +01:00
var ip ProjectIssue
has , err := e . Where ( "issue_id=?" , i . ID ) . Get ( & ip )
if err != nil || ! has {
return 0
}
return ip . ProjectID
}
// ProjectBoardID return project board id if issue was assigned to one
func ( i * Issue ) ProjectBoardID ( ) int64 {
2021-09-23 16:45:36 +01:00
return i . projectBoardID ( db . GetEngine ( db . DefaultContext ) )
2020-08-17 04:07:38 +01:00
}
2021-09-19 19:49:59 +08:00
func ( i * Issue ) projectBoardID ( e db . Engine ) int64 {
2020-08-17 04:07:38 +01:00
var ip ProjectIssue
has , err := e . Where ( "issue_id=?" , i . ID ) . Get ( & ip )
if err != nil || ! has {
return 0
}
return ip . ProjectBoardID
}
// ____ _ _
// | _ \ _ __ ___ (_) ___ ___| |_
// | |_) | '__/ _ \| |/ _ \/ __| __|
// | __/| | | (_) | | __/ (__| |_
// |_| |_| \___// |\___|\___|\__|
// |__/
// NumIssues return counter of all issues assigned to a project
func ( p * Project ) NumIssues ( ) int {
2021-09-23 16:45:36 +01:00
c , err := db . GetEngine ( db . DefaultContext ) . Table ( "project_issue" ) .
2020-08-17 04:07:38 +01:00
Where ( "project_id=?" , p . ID ) .
GroupBy ( "issue_id" ) .
Cols ( "issue_id" ) .
Count ( )
if err != nil {
return 0
}
return int ( c )
}
// NumClosedIssues return counter of closed issues assigned to a project
func ( p * Project ) NumClosedIssues ( ) int {
2021-09-23 16:45:36 +01:00
c , err := db . GetEngine ( db . DefaultContext ) . Table ( "project_issue" ) .
2020-08-17 04:07:38 +01:00
Join ( "INNER" , "issue" , "project_issue.issue_id=issue.id" ) .
Where ( "project_issue.project_id=? AND issue.is_closed=?" , p . ID , true ) .
Cols ( "issue_id" ) .
Count ( )
if err != nil {
return 0
}
return int ( c )
}
// NumOpenIssues return counter of open issues assigned to a project
func ( p * Project ) NumOpenIssues ( ) int {
2021-09-23 16:45:36 +01:00
c , err := db . GetEngine ( db . DefaultContext ) . Table ( "project_issue" ) .
2020-08-17 04:07:38 +01:00
Join ( "INNER" , "issue" , "project_issue.issue_id=issue.id" ) .
2021-11-19 12:00:18 +08:00
Where ( "project_issue.project_id=? AND issue.is_closed=?" , p . ID , false ) .
Cols ( "issue_id" ) .
Count ( )
2020-08-17 04:07:38 +01:00
if err != nil {
return 0
}
return int ( c )
}
// ChangeProjectAssign changes the project associated with an issue
2021-11-24 17:49:20 +08:00
func ChangeProjectAssign ( issue * Issue , doer * user_model . User , newProjectID int64 ) error {
2021-11-19 21:39:57 +08:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2020-08-17 04:07:38 +01:00
return err
}
2021-11-19 21:39:57 +08:00
defer committer . Close ( )
2020-08-17 04:07:38 +01:00
2021-11-19 21:39:57 +08:00
if err := addUpdateIssueProject ( ctx , issue , doer , newProjectID ) ; err != nil {
2020-08-17 04:07:38 +01:00
return err
}
2021-11-19 21:39:57 +08:00
return committer . Commit ( )
2020-08-17 04:07:38 +01:00
}
2021-11-24 17:49:20 +08:00
func addUpdateIssueProject ( ctx context . Context , issue * Issue , doer * user_model . User , newProjectID int64 ) error {
2021-11-19 21:39:57 +08:00
e := db . GetEngine ( ctx )
2020-08-17 04:07:38 +01:00
oldProjectID := issue . projectID ( e )
if _ , err := e . Where ( "project_issue.issue_id=?" , issue . ID ) . Delete ( & ProjectIssue { } ) ; err != nil {
return err
}
2021-12-10 09:27:50 +08:00
if err := issue . loadRepo ( ctx ) ; err != nil {
2020-08-17 04:07:38 +01:00
return err
}
if oldProjectID > 0 || newProjectID > 0 {
2021-11-19 21:39:57 +08:00
if _ , err := createComment ( ctx , & CreateCommentOptions {
2020-08-17 04:07:38 +01:00
Type : CommentTypeProject ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
OldProjectID : oldProjectID ,
ProjectID : newProjectID ,
} ) ; err != nil {
return err
}
}
_ , err := e . Insert ( & ProjectIssue {
IssueID : issue . ID ,
ProjectID : newProjectID ,
} )
return err
}
// ____ _ _ ____ _
// | _ \ _ __ ___ (_) ___ ___| |_| __ ) ___ __ _ _ __ __| |
// | |_) | '__/ _ \| |/ _ \/ __| __| _ \ / _ \ / _` | '__/ _` |
// | __/| | | (_) | | __/ (__| |_| |_) | (_) | (_| | | | (_| |
// |_| |_| \___// |\___|\___|\__|____/ \___/ \__,_|_| \__,_|
// |__/
2021-12-08 07:57:18 +01:00
// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
func MoveIssuesOnProjectBoard ( board * ProjectBoard , sortedIssueIDs map [ int64 ] int64 ) error {
return db . WithTx ( func ( ctx context . Context ) error {
sess := db . GetEngine ( ctx )
2020-08-17 04:07:38 +01:00
2021-12-08 07:57:18 +01:00
issueIDs := make ( [ ] int64 , 0 , len ( sortedIssueIDs ) )
for _ , issueID := range sortedIssueIDs {
issueIDs = append ( issueIDs , issueID )
}
count , err := sess . Table ( new ( ProjectIssue ) ) . Where ( "project_id=?" , board . ProjectID ) . In ( "issue_id" , issueIDs ) . Count ( )
if err != nil {
return err
}
if int ( count ) != len ( sortedIssueIDs ) {
return fmt . Errorf ( "all issues have to be added to a project first" )
}
2020-08-17 04:07:38 +01:00
2021-12-08 07:57:18 +01:00
for sorting , issueID := range sortedIssueIDs {
_ , err = sess . Exec ( "UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?" , board . ID , sorting , issueID )
if err != nil {
return err
}
}
return nil
} )
2020-08-17 04:07:38 +01:00
}
2021-09-19 19:49:59 +08:00
func ( pb * ProjectBoard ) removeIssues ( e db . Engine ) error {
2021-12-08 07:57:18 +01:00
_ , err := e . Exec ( "UPDATE `project_issue` SET project_board_id = 0, sorting = 0 WHERE project_board_id = ? " , pb . ID )
2020-08-17 04:07:38 +01:00
return err
}