2014-03-20 16:04:56 -04:00
// Copyright 2014 The Gogs 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
2014-03-22 13:50:50 -04:00
import (
2014-03-22 16:00:46 -04:00
"errors"
2014-03-22 13:50:50 -04:00
"strings"
"time"
)
2014-03-22 16:00:46 -04:00
var (
ErrIssueNotExist = errors . New ( "Issue does not exist" )
)
2014-03-22 13:50:50 -04:00
// Issue represents an issue or pull request of repository.
2014-03-20 16:04:56 -04:00
type Issue struct {
2014-03-29 10:24:42 -04:00
Id int64
Index int64 // Index in one repository.
Name string
2014-05-07 12:09:30 -04:00
RepoId int64 ` xorm:"INDEX" `
2014-03-29 10:24:42 -04:00
Repo * Repository ` xorm:"-" `
PosterId int64
Poster * User ` xorm:"-" `
MilestoneId int64
AssigneeId int64
IsPull bool // Indicates whether is a pull request or not.
IsClosed bool
Labels string ` xorm:"TEXT" `
Content string ` xorm:"TEXT" `
RenderedContent string ` xorm:"-" `
2014-05-06 16:28:52 -04:00
Priority int
2014-03-29 10:24:42 -04:00
NumComments int
2014-05-06 16:28:52 -04:00
Deadline time . Time
2014-05-07 12:09:30 -04:00
Created time . Time ` xorm:"CREATED" `
Updated time . Time ` xorm:"UPDATED" `
}
func ( i * Issue ) GetPoster ( ) ( err error ) {
i . Poster , err = GetUserById ( i . PosterId )
return err
}
// IssseUser represents an issue-user relation.
type IssseUser struct {
Id int64
Iid int64 // Issue ID.
Rid int64 // Repository ID.
Uid int64 // User ID.
IsRead bool
IsAssigned bool
IsMentioned bool
IsClosed bool
2014-03-22 13:50:50 -04:00
}
// CreateIssue creates new issue for repository.
2014-05-07 12:09:30 -04:00
func NewIssue ( issue * Issue ) ( err error ) {
2014-03-27 12:48:29 -04:00
sess := orm . NewSession ( )
defer sess . Close ( )
2014-05-07 12:09:30 -04:00
if err = sess . Begin ( ) ; err != nil {
return err
2014-03-22 16:00:46 -04:00
}
2014-05-07 12:09:30 -04:00
2014-03-27 12:48:29 -04:00
if _ , err = sess . Insert ( issue ) ; err != nil {
sess . Rollback ( )
2014-05-07 12:09:30 -04:00
return err
2014-03-27 12:48:29 -04:00
}
rawSql := "UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?"
2014-05-07 12:09:30 -04:00
if _ , err = sess . Exec ( rawSql , issue . RepoId ) ; err != nil {
2014-03-27 12:48:29 -04:00
sess . Rollback ( )
2014-05-07 12:09:30 -04:00
return err
2014-03-27 12:48:29 -04:00
}
2014-05-07 12:09:30 -04:00
return sess . Commit ( )
2014-03-20 16:04:56 -04:00
}
2014-05-07 12:09:30 -04:00
// GetIssueByIndex returns issue by given index in repository.
func GetIssueByIndex ( rid , index int64 ) ( * Issue , error ) {
issue := & Issue { RepoId : rid , Index : index }
2014-03-23 19:09:11 -04:00
has , err := orm . Get ( issue )
2014-03-22 16:00:46 -04:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrIssueNotExist
}
return issue , nil
}
2014-03-22 13:50:50 -04:00
// GetIssues returns a list of issues by given conditions.
2014-05-07 12:09:30 -04:00
func GetIssues ( uid , rid , pid , mid int64 , page int , isClosed bool , labels , sortType string ) ( [ ] Issue , error ) {
2014-03-22 16:00:46 -04:00
sess := orm . Limit ( 20 , ( page - 1 ) * 20 )
2014-05-07 12:09:30 -04:00
if rid > 0 {
sess . Where ( "repo_id=?" , rid ) . And ( "is_closed=?" , isClosed )
2014-03-22 16:00:46 -04:00
} else {
2014-03-23 06:27:01 -04:00
sess . Where ( "is_closed=?" , isClosed )
2014-03-22 16:00:46 -04:00
}
2014-05-07 12:09:30 -04:00
if uid > 0 {
sess . And ( "assignee_id=?" , uid )
} else if pid > 0 {
sess . And ( "poster_id=?" , pid )
2014-03-22 13:50:50 -04:00
}
2014-05-07 12:09:30 -04:00
if mid > 0 {
sess . And ( "milestone_id=?" , mid )
2014-03-22 13:50:50 -04:00
}
if len ( labels ) > 0 {
for _ , label := range strings . Split ( labels , "," ) {
2014-05-07 12:09:30 -04:00
sess . And ( "labels like '%$" + label + "|%'" )
2014-03-22 13:50:50 -04:00
}
}
switch sortType {
case "oldest" :
2014-03-23 06:27:01 -04:00
sess . Asc ( "created" )
2014-03-22 13:50:50 -04:00
case "recentupdate" :
2014-03-23 06:27:01 -04:00
sess . Desc ( "updated" )
2014-03-22 13:50:50 -04:00
case "leastupdate" :
2014-03-23 06:27:01 -04:00
sess . Asc ( "updated" )
2014-03-22 13:50:50 -04:00
case "mostcomment" :
2014-03-23 06:27:01 -04:00
sess . Desc ( "num_comments" )
2014-03-22 13:50:50 -04:00
case "leastcomment" :
2014-03-23 06:27:01 -04:00
sess . Asc ( "num_comments" )
2014-03-22 13:50:50 -04:00
default :
2014-03-23 06:27:01 -04:00
sess . Desc ( "created" )
2014-03-22 13:50:50 -04:00
}
var issues [ ] Issue
err := sess . Find ( & issues )
return issues , err
}
2014-05-07 12:09:30 -04:00
// PairsContains returns true when pairs list contains given issue.
func PairsContains ( ius [ ] * IssseUser , issueId int64 ) bool {
for i := range ius {
if ius [ i ] . Iid == issueId {
return true
}
}
return false
}
// GetIssueUserPairs returns all issue-user pairs by given repository and user.
func GetIssueUserPairs ( rid , uid int64 , isClosed bool ) ( [ ] * IssseUser , error ) {
ius := make ( [ ] * IssseUser , 0 , 10 )
err := orm . Find ( & ius , & IssseUser { Rid : rid , Uid : uid , IsClosed : isClosed } )
return ius , err
}
2014-03-27 16:31:32 -04:00
// GetUserIssueCount returns the number of issues that were created by given user in repository.
2014-05-07 12:09:30 -04:00
func GetUserIssueCount ( uid , rid int64 ) int64 {
count , _ := orm . Where ( "poster_id=?" , uid ) . And ( "repo_id=?" , rid ) . Count ( new ( Issue ) )
2014-03-27 16:31:32 -04:00
return count
}
2014-05-07 12:09:30 -04:00
// IssueStats represents issue statistic information.
type IssueStats struct {
OpenCount , ClosedCount int64
AllCount int64
AssignCount int64
CreateCount int64
MentionCount int64
}
// Filter modes.
const (
FM_ASSIGN = iota + 1
FM_CREATE
FM_MENTION
)
// GetIssueStats returns issue statistic information by given condition.
func GetIssueStats ( rid , uid int64 , isShowClosed bool , filterMode int ) * IssueStats {
stats := & IssueStats { }
issue := new ( Issue )
sess := orm . Where ( "repo_id=?" , rid )
tmpSess := sess
stats . OpenCount , _ = tmpSess . And ( "is_closed=?" , false ) . Count ( issue )
* tmpSess = * sess
stats . ClosedCount , _ = tmpSess . And ( "is_closed=?" , true ) . Count ( issue )
if isShowClosed {
stats . AllCount = stats . ClosedCount
} else {
stats . AllCount = stats . OpenCount
}
if filterMode != FM_MENTION {
sess = orm . Where ( "repo_id=?" , rid )
switch filterMode {
case FM_ASSIGN :
sess . And ( "assignee_id=?" , uid )
case FM_CREATE :
sess . And ( "poster_id=?" , uid )
default :
goto nofilter
}
* tmpSess = * sess
stats . OpenCount , _ = tmpSess . And ( "is_closed=?" , false ) . Count ( issue )
* tmpSess = * sess
stats . ClosedCount , _ = tmpSess . And ( "is_closed=?" , true ) . Count ( issue )
} else {
sess := orm . Where ( "rid=?" , rid ) . And ( "uid=?" , uid ) . And ( "is_mentioned=?" , true )
tmpSess := sess
stats . OpenCount , _ = tmpSess . And ( "is_closed=?" , false ) . Count ( new ( IssseUser ) )
* tmpSess = * sess
stats . ClosedCount , _ = tmpSess . And ( "is_closed=?" , true ) . Count ( new ( IssseUser ) )
}
nofilter :
stats . AssignCount , _ = orm . Where ( "repo_id=?" , rid ) . And ( "is_closed=?" , isShowClosed ) . And ( "assignee_id=?" , uid ) . Count ( issue )
stats . CreateCount , _ = orm . Where ( "repo_id=?" , rid ) . And ( "is_closed=?" , isShowClosed ) . And ( "poster_id=?" , uid ) . Count ( issue )
stats . MentionCount , _ = orm . Where ( "rid=?" , rid ) . And ( "uid=?" , uid ) . And ( "is_closed=?" , isShowClosed ) . And ( "is_mentioned=?" , true ) . Count ( new ( IssseUser ) )
return stats
}
// GetUserIssueStats returns issue statistic information for dashboard by given condition.
func GetUserIssueStats ( uid int64 , filterMode int ) * IssueStats {
stats := & IssueStats { }
issue := new ( Issue )
iu := new ( IssseUser )
sess := orm . Where ( "uid=?" , uid )
tmpSess := sess
if filterMode == 0 {
stats . OpenCount , _ = tmpSess . And ( "is_closed=?" , false ) . Count ( iu )
* tmpSess = * sess
stats . ClosedCount , _ = tmpSess . And ( "is_closed=?" , true ) . Count ( iu )
}
switch filterMode {
case FM_ASSIGN :
sess . And ( "is_assigned=?" , true )
* tmpSess = * sess
stats . OpenCount , _ = tmpSess . And ( "is_closed=?" , false ) . Count ( iu )
* tmpSess = * sess
stats . ClosedCount , _ = tmpSess . And ( "is_closed=?" , true ) . Count ( iu )
case FM_CREATE :
sess . Where ( "poster_id=?" , uid )
* tmpSess = * sess
stats . OpenCount , _ = tmpSess . And ( "is_closed=?" , false ) . Count ( issue )
* tmpSess = * sess
stats . ClosedCount , _ = tmpSess . And ( "is_closed=?" , true ) . Count ( issue )
}
stats . AssignCount , _ = orm . Where ( "assignee_id=?" , uid ) . And ( "is_closed=?" , false ) . Count ( issue )
stats . CreateCount , _ = orm . Where ( "poster_id=?" , uid ) . And ( "is_closed=?" , false ) . Count ( issue )
return stats
}
2014-03-23 19:09:11 -04:00
// UpdateIssue updates information of issue.
func UpdateIssue ( issue * Issue ) error {
2014-05-07 12:09:30 -04:00
_ , err := orm . AllCols ( ) . Update ( issue )
2014-03-23 19:09:11 -04:00
return err
}
2014-03-22 13:50:50 -04:00
// Label represents a list of labels of repository for issues.
type Label struct {
Id int64
2014-05-07 12:09:30 -04:00
RepoId int64 ` xorm:"INDEX" `
2014-03-22 13:50:50 -04:00
Names string
Colors string
}
// Milestone represents a milestone of repository.
type Milestone struct {
Id int64
Name string
2014-05-07 12:09:30 -04:00
RepoId int64 ` xorm:"INDEX" `
2014-03-22 13:50:50 -04:00
IsClosed bool
Content string
NumIssues int
DueDate time . Time
2014-05-07 12:09:30 -04:00
Created time . Time ` xorm:"CREATED" `
2014-03-22 13:50:50 -04:00
}
2014-03-29 17:50:51 -04:00
// Issue types.
const (
IT_PLAIN = iota // Pure comment.
IT_REOPEN // Issue reopen status change prompt.
IT_CLOSE // Issue close status change prompt.
)
2014-03-22 13:50:50 -04:00
// Comment represents a comment in commit and issue page.
2014-03-20 16:04:56 -04:00
type Comment struct {
2014-03-22 13:50:50 -04:00
Id int64
2014-03-29 17:50:51 -04:00
Type int
2014-03-22 13:50:50 -04:00
PosterId int64
2014-03-26 12:31:01 -04:00
Poster * User ` xorm:"-" `
2014-03-22 13:50:50 -04:00
IssueId int64
CommitId int64
2014-03-26 12:31:01 -04:00
Line int64
2014-03-22 13:50:50 -04:00
Content string
2014-05-07 12:09:30 -04:00
Created time . Time ` xorm:"CREATED" `
2014-03-20 16:04:56 -04:00
}
2014-03-26 12:31:01 -04:00
// CreateComment creates comment of issue or commit.
2014-03-29 17:50:51 -04:00
func CreateComment ( userId , repoId , issueId , commitId , line int64 , cmtType int , content string ) error {
2014-03-26 16:41:16 -04:00
sess := orm . NewSession ( )
defer sess . Close ( )
sess . Begin ( )
2014-04-30 11:12:03 +08:00
if _ , err := sess . Insert ( & Comment { PosterId : userId , Type : cmtType , IssueId : issueId ,
2014-03-27 15:24:11 -04:00
CommitId : commitId , Line : line , Content : content } ) ; err != nil {
2014-03-26 16:41:16 -04:00
sess . Rollback ( )
return err
}
2014-03-29 17:50:51 -04:00
// Check comment type.
switch cmtType {
case IT_PLAIN :
rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
if _ , err := sess . Exec ( rawSql , issueId ) ; err != nil {
sess . Rollback ( )
return err
}
case IT_REOPEN :
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?"
if _ , err := sess . Exec ( rawSql , repoId ) ; err != nil {
sess . Rollback ( )
return err
}
case IT_CLOSE :
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?"
if _ , err := sess . Exec ( rawSql , repoId ) ; err != nil {
sess . Rollback ( )
return err
}
2014-03-26 16:41:16 -04:00
}
return sess . Commit ( )
2014-03-26 12:31:01 -04:00
}
// GetIssueComments returns list of comment by given issue id.
func GetIssueComments ( issueId int64 ) ( [ ] Comment , error ) {
comments := make ( [ ] Comment , 0 , 10 )
err := orm . Asc ( "created" ) . Find ( & comments , & Comment { IssueId : issueId } )
return comments , err
}