2014-03-21 00: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 21:50:50 +04:00
import (
2014-05-08 00:51:14 +04:00
"bytes"
2014-03-23 00:00:46 +04:00
"errors"
2014-03-22 21:50:50 +04:00
"strings"
"time"
2014-05-08 00:51:14 +04:00
"github.com/gogits/gogs/modules/base"
2014-03-22 21:50:50 +04:00
)
2014-03-23 00:00:46 +04:00
var (
ErrIssueNotExist = errors . New ( "Issue does not exist" )
)
2014-03-22 21:50:50 +04:00
// Issue represents an issue or pull request of repository.
2014-03-21 00:04:56 +04:00
type Issue struct {
2014-03-29 18:24:42 +04:00
Id int64
Index int64 // Index in one repository.
Name string
2014-05-07 20:09:30 +04:00
RepoId int64 ` xorm:"INDEX" `
2014-03-29 18:24:42 +04:00
Repo * Repository ` xorm:"-" `
PosterId int64
Poster * User ` xorm:"-" `
MilestoneId int64
AssigneeId int64
2014-05-08 20:24:11 +04:00
Assignee * User ` xorm:"-" `
IsRead bool ` xorm:"-" `
IsPull bool // Indicates whether is a pull request or not.
2014-03-29 18:24:42 +04:00
IsClosed bool
Labels string ` xorm:"TEXT" `
Content string ` xorm:"TEXT" `
RenderedContent string ` xorm:"-" `
2014-05-07 00:28:52 +04:00
Priority int
2014-03-29 18:24:42 +04:00
NumComments int
2014-05-07 00:28:52 +04:00
Deadline time . Time
2014-05-07 20: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 )
2014-05-09 04:00:07 +04:00
if err == ErrUserNotExist {
i . Poster = & User { Name : "FakeUser" }
return nil
}
2014-05-07 20:09:30 +04:00
return err
}
2014-05-08 20:24:11 +04:00
func ( i * Issue ) GetAssignee ( ) ( err error ) {
if i . AssigneeId == 0 {
return nil
}
i . Assignee , err = GetUserById ( i . AssigneeId )
return err
}
2014-03-22 21:50:50 +04:00
// CreateIssue creates new issue for repository.
2014-05-07 20:09:30 +04:00
func NewIssue ( issue * Issue ) ( err error ) {
2014-03-27 20:48:29 +04:00
sess := orm . NewSession ( )
defer sess . Close ( )
2014-05-07 20:09:30 +04:00
if err = sess . Begin ( ) ; err != nil {
return err
2014-03-23 00:00:46 +04:00
}
2014-05-07 20:09:30 +04:00
2014-03-27 20:48:29 +04:00
if _ , err = sess . Insert ( issue ) ; err != nil {
sess . Rollback ( )
2014-05-07 20:09:30 +04:00
return err
2014-03-27 20:48:29 +04:00
}
rawSql := "UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?"
2014-05-07 20:09:30 +04:00
if _ , err = sess . Exec ( rawSql , issue . RepoId ) ; err != nil {
2014-03-27 20:48:29 +04:00
sess . Rollback ( )
2014-05-07 20:09:30 +04:00
return err
2014-03-27 20:48:29 +04:00
}
2014-05-07 20:09:30 +04:00
return sess . Commit ( )
2014-03-21 00:04:56 +04:00
}
2014-05-07 20: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-24 03:09:11 +04:00
has , err := orm . Get ( issue )
2014-03-23 00:00:46 +04:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrIssueNotExist
}
return issue , nil
}
2014-05-08 00:51:14 +04:00
// GetIssueById returns an issue by ID.
func GetIssueById ( id int64 ) ( * Issue , error ) {
issue := & Issue { Id : id }
has , err := orm . Get ( issue )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrIssueNotExist
}
return issue , nil
}
2014-03-22 21:50:50 +04:00
// GetIssues returns a list of issues by given conditions.
2014-05-07 20:09:30 +04:00
func GetIssues ( uid , rid , pid , mid int64 , page int , isClosed bool , labels , sortType string ) ( [ ] Issue , error ) {
2014-03-23 00:00:46 +04:00
sess := orm . Limit ( 20 , ( page - 1 ) * 20 )
2014-05-07 20:09:30 +04:00
if rid > 0 {
sess . Where ( "repo_id=?" , rid ) . And ( "is_closed=?" , isClosed )
2014-03-23 00:00:46 +04:00
} else {
2014-03-23 14:27:01 +04:00
sess . Where ( "is_closed=?" , isClosed )
2014-03-23 00:00:46 +04:00
}
2014-05-07 20:09:30 +04:00
if uid > 0 {
sess . And ( "assignee_id=?" , uid )
} else if pid > 0 {
sess . And ( "poster_id=?" , pid )
2014-03-22 21:50:50 +04:00
}
2014-05-07 20:09:30 +04:00
if mid > 0 {
sess . And ( "milestone_id=?" , mid )
2014-03-22 21:50:50 +04:00
}
if len ( labels ) > 0 {
for _ , label := range strings . Split ( labels , "," ) {
2014-05-07 20:09:30 +04:00
sess . And ( "labels like '%$" + label + "|%'" )
2014-03-22 21:50:50 +04:00
}
}
switch sortType {
case "oldest" :
2014-03-23 14:27:01 +04:00
sess . Asc ( "created" )
2014-03-22 21:50:50 +04:00
case "recentupdate" :
2014-03-23 14:27:01 +04:00
sess . Desc ( "updated" )
2014-03-22 21:50:50 +04:00
case "leastupdate" :
2014-03-23 14:27:01 +04:00
sess . Asc ( "updated" )
2014-03-22 21:50:50 +04:00
case "mostcomment" :
2014-03-23 14:27:01 +04:00
sess . Desc ( "num_comments" )
2014-03-22 21:50:50 +04:00
case "leastcomment" :
2014-03-23 14:27:01 +04:00
sess . Asc ( "num_comments" )
2014-05-08 04:36:00 +04:00
case "priority" :
sess . Desc ( "priority" )
2014-03-22 21:50:50 +04:00
default :
2014-03-23 14:27:01 +04:00
sess . Desc ( "created" )
2014-03-22 21:50:50 +04:00
}
var issues [ ] Issue
err := sess . Find ( & issues )
return issues , err
}
2014-05-08 00:51:14 +04:00
// GetIssueCountByPoster returns number of issues of repository by poster.
func GetIssueCountByPoster ( uid , rid int64 , isClosed bool ) int64 {
count , _ := orm . Where ( "repo_id=?" , rid ) . And ( "poster_id=?" , uid ) . And ( "is_closed=?" , isClosed ) . Count ( new ( Issue ) )
return count
}
// IssueUser represents an issue-user relation.
type IssueUser struct {
Id int64
Uid int64 // User ID.
IssueId int64
RepoId int64
IsRead bool
IsAssigned bool
IsMentioned bool
IsPoster bool
IsClosed bool
}
// NewIssueUserPairs adds new issue-user pairs for new issue of repository.
2014-05-08 20:24:11 +04:00
func NewIssueUserPairs ( rid , iid , oid , pid , aid int64 , repoName string ) ( err error ) {
2014-05-08 00:51:14 +04:00
iu := & IssueUser { IssueId : iid , RepoId : rid }
2014-05-08 20:24:11 +04:00
us , err := GetCollaborators ( repoName )
2014-05-08 00:51:14 +04:00
if err != nil {
return err
}
2014-05-08 20:24:11 +04:00
isNeedAddPoster := true
for _ , u := range us {
iu . Uid = u . Id
iu . IsPoster = iu . Uid == pid
if isNeedAddPoster && iu . IsPoster {
isNeedAddPoster = false
2014-05-08 00:51:14 +04:00
}
2014-05-08 20:24:11 +04:00
iu . IsAssigned = iu . Uid == aid
if _ , err = orm . Insert ( iu ) ; err != nil {
return err
2014-05-08 00:51:14 +04:00
}
2014-05-08 20:24:11 +04:00
}
if isNeedAddPoster {
iu . Uid = pid
iu . IsPoster = true
2014-05-08 00:51:14 +04:00
iu . IsAssigned = iu . Uid == aid
if _ , err = orm . Insert ( iu ) ; err != nil {
return err
}
}
2014-05-08 20:24:11 +04:00
2014-05-08 00:51:14 +04:00
return nil
}
2014-05-07 20:09:30 +04:00
// PairsContains returns true when pairs list contains given issue.
2014-05-08 00:51:14 +04:00
func PairsContains ( ius [ ] * IssueUser , issueId int64 ) int {
2014-05-07 20:09:30 +04:00
for i := range ius {
2014-05-08 00:51:14 +04:00
if ius [ i ] . IssueId == issueId {
return i
2014-05-07 20:09:30 +04:00
}
}
2014-05-08 00:51:14 +04:00
return - 1
2014-05-07 20:09:30 +04:00
}
2014-05-08 00:51:14 +04:00
// GetIssueUserPairs returns issue-user pairs by given repository and user.
func GetIssueUserPairs ( rid , uid int64 , isClosed bool ) ( [ ] * IssueUser , error ) {
ius := make ( [ ] * IssueUser , 0 , 10 )
err := orm . Where ( "is_closed=?" , isClosed ) . Find ( & ius , & IssueUser { RepoId : rid , Uid : uid } )
2014-05-07 20:09:30 +04:00
return ius , err
}
2014-05-08 00:51:14 +04:00
// GetIssueUserPairsByRepoIds returns issue-user pairs by given repository IDs.
func GetIssueUserPairsByRepoIds ( rids [ ] int64 , isClosed bool , page int ) ( [ ] * IssueUser , error ) {
buf := bytes . NewBufferString ( "" )
for _ , rid := range rids {
buf . WriteString ( "repo_id=" )
buf . WriteString ( base . ToStr ( rid ) )
buf . WriteString ( " OR " )
}
cond := strings . TrimSuffix ( buf . String ( ) , " OR " )
ius := make ( [ ] * IssueUser , 0 , 10 )
sess := orm . Limit ( 20 , ( page - 1 ) * 20 ) . Where ( "is_closed=?" , isClosed )
if len ( cond ) > 0 {
sess . And ( cond )
}
err := sess . Find ( & ius )
return ius , err
}
// GetIssueUserPairsByMode returns issue-user pairs by given repository and user.
func GetIssueUserPairsByMode ( uid , rid int64 , isClosed bool , page , filterMode int ) ( [ ] * IssueUser , error ) {
ius := make ( [ ] * IssueUser , 0 , 10 )
sess := orm . Limit ( 20 , ( page - 1 ) * 20 ) . Where ( "uid=?" , uid ) . And ( "is_closed=?" , isClosed )
if rid > 0 {
sess . And ( "repo_id=?" , rid )
}
switch filterMode {
case FM_ASSIGN :
sess . And ( "is_assigned=?" , true )
case FM_CREATE :
sess . And ( "is_poster=?" , true )
default :
return ius , nil
}
err := sess . Find ( & ius )
return ius , err
2014-03-28 00:31:32 +04:00
}
2014-05-07 20: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
)
2014-05-08 00:51:14 +04:00
// GetIssueStats returns issue statistic information by given conditions.
2014-05-07 20:09:30 +04:00
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 {
2014-05-08 00:51:14 +04:00
sess := orm . Where ( "repo_id=?" , rid ) . And ( "uid=?" , uid ) . And ( "is_mentioned=?" , true )
* tmpSess = * sess
stats . OpenCount , _ = tmpSess . And ( "is_closed=?" , false ) . Count ( new ( IssueUser ) )
2014-05-07 20:09:30 +04:00
* tmpSess = * sess
2014-05-08 00:51:14 +04:00
stats . ClosedCount , _ = tmpSess . And ( "is_closed=?" , true ) . Count ( new ( IssueUser ) )
2014-05-07 20:09:30 +04:00
}
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 )
2014-05-08 00:51:14 +04:00
stats . MentionCount , _ = orm . Where ( "repo_id=?" , rid ) . And ( "uid=?" , uid ) . And ( "is_closed=?" , isShowClosed ) . And ( "is_mentioned=?" , true ) . Count ( new ( IssueUser ) )
2014-05-07 20:09:30 +04:00
return stats
}
2014-05-08 00:51:14 +04:00
// GetUserIssueStats returns issue statistic information for dashboard by given conditions.
2014-05-07 20:09:30 +04:00
func GetUserIssueStats ( uid int64 , filterMode int ) * IssueStats {
stats := & IssueStats { }
issue := new ( 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-24 03:09:11 +04:00
// UpdateIssue updates information of issue.
func UpdateIssue ( issue * Issue ) error {
2014-05-08 00:51:14 +04:00
_ , err := orm . Id ( issue . Id ) . AllCols ( ) . Update ( issue )
return err
}
// UpdateIssueUserByStatus updates issue-user pairs by issue status.
func UpdateIssueUserPairsByStatus ( iid int64 , isClosed bool ) error {
rawSql := "UPDATE `issue_user` SET is_closed = ? WHERE issue_id = ?"
_ , err := orm . Exec ( rawSql , isClosed , iid )
2014-03-24 03:09:11 +04:00
return err
}
2014-05-09 01:17:45 +04:00
// UpdateIssueUserPairByAssignee updates issue-user pair for assigning.
func UpdateIssueUserPairByAssignee ( aid , iid int64 ) error {
rawSql := "UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?"
if _ , err := orm . Exec ( rawSql , false , iid ) ; err != nil {
return err
}
2014-05-11 21:46:36 +04:00
// Assignee ID equals to 0 means clear assignee.
if aid == 0 {
return nil
}
2014-05-09 01:17:45 +04:00
rawSql = "UPDATE `issue_user` SET is_assigned = true WHERE uid = ? AND issue_id = ?"
2014-05-11 21:46:36 +04:00
_ , err := orm . Exec ( rawSql , aid , iid )
2014-05-09 01:17:45 +04:00
return err
}
2014-05-08 00:51:14 +04:00
// UpdateIssueUserPairByRead updates issue-user pair for reading.
func UpdateIssueUserPairByRead ( uid , iid int64 ) error {
rawSql := "UPDATE `issue_user` SET is_read = ? WHERE uid = ? AND issue_id = ?"
_ , err := orm . Exec ( rawSql , true , uid , iid )
return err
}
// UpdateIssueUserPairsByMentions updates issue-user pairs by mentioning.
func UpdateIssueUserPairsByMentions ( uids [ ] int64 , iid int64 ) error {
for _ , uid := range uids {
iu := & IssueUser { Uid : uid , IssueId : iid }
has , err := orm . Get ( iu )
if err != nil {
return err
}
iu . IsMentioned = true
if has {
_ , err = orm . Id ( iu . Id ) . AllCols ( ) . Update ( iu )
} else {
_ , err = orm . Insert ( iu )
}
if err != nil {
return err
}
}
return nil
}
// Label represents a label of repository for issues.
2014-03-22 21:50:50 +04:00
type Label struct {
2014-05-08 00:51:14 +04:00
Id int64
Rid int64 ` xorm:"INDEX" `
Name string
Color string
NumIssues int
NumClosedIssues int
NumOpenIssues int ` xorm:"-" `
2014-03-22 21:50:50 +04:00
}
// Milestone represents a milestone of repository.
type Milestone struct {
2014-05-08 00:51:14 +04:00
Id int64
Rid int64 ` xorm:"INDEX" `
Name string
Content string
IsClosed bool
NumIssues int
NumClosedIssues int
Completeness int // Percentage(1-100).
Deadline time . Time
ClosedDate time . Time
2014-03-22 21:50:50 +04:00
}
2014-03-30 01: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 21:50:50 +04:00
// Comment represents a comment in commit and issue page.
2014-03-21 00:04:56 +04:00
type Comment struct {
2014-03-22 21:50:50 +04:00
Id int64
2014-03-30 01:50:51 +04:00
Type int
2014-03-22 21:50:50 +04:00
PosterId int64
2014-03-26 20:31:01 +04:00
Poster * User ` xorm:"-" `
2014-03-22 21:50:50 +04:00
IssueId int64
CommitId int64
2014-03-26 20:31:01 +04:00
Line int64
2014-03-22 21:50:50 +04:00
Content string
2014-05-07 20:09:30 +04:00
Created time . Time ` xorm:"CREATED" `
2014-03-21 00:04:56 +04:00
}
2014-03-26 20:31:01 +04:00
// CreateComment creates comment of issue or commit.
2014-03-30 01:50:51 +04:00
func CreateComment ( userId , repoId , issueId , commitId , line int64 , cmtType int , content string ) error {
2014-03-27 00:41:16 +04:00
sess := orm . NewSession ( )
defer sess . Close ( )
2014-05-08 00:51:14 +04:00
if err := sess . Begin ( ) ; err != nil {
return err
}
2014-03-27 00:41:16 +04:00
2014-04-30 07:12:03 +04:00
if _ , err := sess . Insert ( & Comment { PosterId : userId , Type : cmtType , IssueId : issueId ,
2014-03-27 23:24:11 +04:00
CommitId : commitId , Line : line , Content : content } ) ; err != nil {
2014-03-27 00:41:16 +04:00
sess . Rollback ( )
return err
}
2014-03-30 01: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-27 00:41:16 +04:00
}
return sess . Commit ( )
2014-03-26 20: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
}