summaryrefslogtreecommitdiffstats
path: root/models/issue.go
diff options
context:
space:
mode:
Diffstat (limited to 'models/issue.go')
-rw-r--r--models/issue.go159
1 files changed, 57 insertions, 102 deletions
diff --git a/models/issue.go b/models/issue.go
index 7f83d59842..ad354cc34e 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -37,7 +37,7 @@ type Issue struct {
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
Priority int
- AssigneeID int64 `xorm:"INDEX"`
+ AssigneeID int64 `xorm:"-"`
Assignee *User `xorm:"-"`
IsClosed bool `xorm:"INDEX"`
IsRead bool `xorm:"-"`
@@ -56,6 +56,7 @@ type Issue struct {
Comments []*Comment `xorm:"-"`
Reactions ReactionList `xorm:"-"`
TotalTrackedTime int64 `xorm:"-"`
+ Assignees []*User `xorm:"-"`
}
var (
@@ -140,22 +141,6 @@ func (issue *Issue) loadPoster(e Engine) (err error) {
return
}
-func (issue *Issue) loadAssignee(e Engine) (err error) {
- if issue.Assignee == nil && issue.AssigneeID > 0 {
- issue.Assignee, err = getUserByID(e, issue.AssigneeID)
- if err != nil {
- issue.AssigneeID = -1
- issue.Assignee = NewGhostUser()
- if !IsErrUserNotExist(err) {
- return fmt.Errorf("getUserByID.(assignee) [%d]: %v", issue.AssigneeID, err)
- }
- err = nil
- return
- }
- }
- return
-}
-
func (issue *Issue) loadPullRequest(e Engine) (err error) {
if issue.IsPull && issue.PullRequest == nil {
issue.PullRequest, err = getPullRequestByIssueID(e, issue.ID)
@@ -231,7 +216,7 @@ func (issue *Issue) loadAttributes(e Engine) (err error) {
}
}
- if err = issue.loadAssignee(e); err != nil {
+ if err = issue.loadAssignees(e); err != nil {
return
}
@@ -343,8 +328,11 @@ func (issue *Issue) APIFormat() *api.Issue {
if issue.Milestone != nil {
apiIssue.Milestone = issue.Milestone.APIFormat()
}
- if issue.Assignee != nil {
- apiIssue.Assignee = issue.Assignee.APIFormat()
+ if len(issue.Assignees) > 0 {
+ for _, assignee := range issue.Assignees {
+ apiIssue.Assignees = append(apiIssue.Assignees, assignee.APIFormat())
+ }
+ apiIssue.Assignee = issue.Assignees[0].APIFormat() // For compatibility, we're keeping the first assignee as `apiIssue.Assignee`
}
if issue.IsPull {
apiIssue.PullRequest = &api.PullRequestMeta{
@@ -605,19 +593,6 @@ func (issue *Issue) ReplaceLabels(labels []*Label, doer *User) (err error) {
return sess.Commit()
}
-// GetAssignee sets the Assignee attribute of this issue.
-func (issue *Issue) GetAssignee() (err error) {
- if issue.AssigneeID == 0 || issue.Assignee != nil {
- return nil
- }
-
- issue.Assignee, err = GetUserByID(issue.AssigneeID)
- if IsErrUserNotExist(err) {
- return nil
- }
- return err
-}
-
// ReadBy sets issue to be read by given user.
func (issue *Issue) ReadBy(userID int64) error {
if err := UpdateIssueUserByRead(userID, issue.ID); err != nil {
@@ -823,55 +798,6 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
return nil
}
-// ChangeAssignee changes the Assignee field of this issue.
-func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
- var oldAssigneeID = issue.AssigneeID
- issue.AssigneeID = assigneeID
- if err = UpdateIssueUserByAssignee(issue); err != nil {
- return fmt.Errorf("UpdateIssueUserByAssignee: %v", err)
- }
-
- sess := x.NewSession()
- defer sess.Close()
-
- if err = issue.loadRepo(sess); err != nil {
- return fmt.Errorf("loadRepo: %v", err)
- }
-
- if _, err = createAssigneeComment(sess, doer, issue.Repo, issue, oldAssigneeID, assigneeID); err != nil {
- return fmt.Errorf("createAssigneeComment: %v", err)
- }
-
- issue.Assignee, err = GetUserByID(issue.AssigneeID)
- if err != nil && !IsErrUserNotExist(err) {
- log.Error(4, "GetUserByID [assignee_id: %v]: %v", issue.AssigneeID, err)
- return nil
- }
-
- // Error not nil here means user does not exist, which is remove assignee.
- isRemoveAssignee := err != nil
- if issue.IsPull {
- issue.PullRequest.Issue = issue
- apiPullRequest := &api.PullRequestPayload{
- Index: issue.Index,
- PullRequest: issue.PullRequest.APIFormat(),
- Repository: issue.Repo.APIFormat(AccessModeNone),
- Sender: doer.APIFormat(),
- }
- if isRemoveAssignee {
- apiPullRequest.Action = api.HookIssueUnassigned
- } else {
- apiPullRequest.Action = api.HookIssueAssigned
- }
- if err := PrepareWebhooks(issue.Repo, HookEventPullRequest, apiPullRequest); err != nil {
- log.Error(4, "PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, isRemoveAssignee, err)
- return nil
- }
- }
- go HookQueue.Add(issue.RepoID)
- return nil
-}
-
// GetTasks returns the amount of tasks in the issues content
func (issue *Issue) GetTasks() int {
return len(issueTasksPat.FindAllStringIndex(issue.Content, -1))
@@ -887,6 +813,7 @@ type NewIssueOptions struct {
Repo *Repository
Issue *Issue
LabelIDs []int64
+ AssigneeIDs []int64
Attachments []string // In UUID format.
IsPull bool
}
@@ -909,14 +836,32 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
}
}
- if assigneeID := opts.Issue.AssigneeID; assigneeID > 0 {
- valid, err := hasAccess(e, assigneeID, opts.Repo, AccessModeWrite)
- if err != nil {
- return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", assigneeID, opts.Repo.ID, err)
+ // Keep the old assignee id thingy for compatibility reasons
+ if opts.Issue.AssigneeID > 0 {
+ isAdded := false
+ // Check if the user has already been passed to issue.AssigneeIDs, if not, add it
+ for _, aID := range opts.AssigneeIDs {
+ if aID == opts.Issue.AssigneeID {
+ isAdded = true
+ break
+ }
}
- if !valid {
- opts.Issue.AssigneeID = 0
- opts.Issue.Assignee = nil
+
+ if !isAdded {
+ opts.AssigneeIDs = append(opts.AssigneeIDs, opts.Issue.AssigneeID)
+ }
+ }
+
+ // Check for and validate assignees
+ if len(opts.AssigneeIDs) > 0 {
+ for _, assigneeID := range opts.AssigneeIDs {
+ valid, err := hasAccess(e, assigneeID, opts.Repo, AccessModeWrite)
+ if err != nil {
+ return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", assigneeID, opts.Repo.ID, err)
+ }
+ if !valid {
+ return ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: opts.Repo.Name}
+ }
}
}
@@ -931,11 +876,10 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
}
}
- if opts.Issue.AssigneeID > 0 {
- if err = opts.Issue.loadRepo(e); err != nil {
- return err
- }
- if _, err = createAssigneeComment(e, doer, opts.Issue.Repo, opts.Issue, -1, opts.Issue.AssigneeID); err != nil {
+ // Insert the assignees
+ for _, assigneeID := range opts.AssigneeIDs {
+ err = opts.Issue.changeAssignee(e, doer, assigneeID)
+ if err != nil {
return err
}
}
@@ -995,7 +939,7 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
}
// NewIssue creates new issue with labels for repository.
-func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
+func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []int64, uuids []string) (err error) {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
@@ -1007,7 +951,11 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
Issue: issue,
LabelIDs: labelIDs,
Attachments: uuids,
+ AssigneeIDs: assigneeIDs,
}); err != nil {
+ if IsErrUserDoesNotHaveAccessToRepo(err) {
+ return err
+ }
return fmt.Errorf("newIssue: %v", err)
}
@@ -1150,7 +1098,8 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) error {
}
if opts.AssigneeID > 0 {
- sess.And("issue.assignee_id=?", opts.AssigneeID)
+ sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
+ And("issue_assignees.assignee_id = ?", opts.AssigneeID)
}
if opts.PosterID > 0 {
@@ -1372,7 +1321,8 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) {
}
if opts.AssigneeID > 0 {
- sess.And("issue.assignee_id = ?", opts.AssigneeID)
+ sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
+ And("issue_assignees.assignee_id = ?", opts.AssigneeID)
}
if opts.PosterID > 0 {
@@ -1438,13 +1388,15 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
}
case FilterModeAssign:
stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false).
- And("assignee_id = ?", opts.UserID).
+ Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
+ And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.ClosedCount, err = x.Where(cond).And("is_closed = ?", true).
- And("assignee_id = ?", opts.UserID).
+ Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
+ And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
@@ -1466,7 +1418,8 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed})
stats.AssignCount, err = x.Where(cond).
- And("assignee_id = ?", opts.UserID).
+ Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
+ And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
@@ -1505,8 +1458,10 @@ func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen
switch filterMode {
case FilterModeAssign:
- openCountSession.And("assignee_id = ?", uid)
- closedCountSession.And("assignee_id = ?", uid)
+ openCountSession.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
+ And("issue_assignees.assignee_id = ?", uid)
+ closedCountSession.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
+ And("issue_assignees.assignee_id = ?", uid)
case FilterModeCreate:
openCountSession.And("poster_id = ?", uid)
closedCountSession.And("poster_id = ?", uid)