diff options
Diffstat (limited to 'models/issue.go')
-rw-r--r-- | models/issue.go | 159 |
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) |