From 1bd777b5837bfe9b6a9bc1ce2079e867c22a3ba6 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 3 Mar 2018 21:07:10 +0100 Subject: [PATCH 01/83] Issue assignees are now loaded of a seperate table when displayed in the UI --- models/issue.go | 84 +++++++++++++------ models/issue_list.go | 24 +----- models/migrations/migrations.go | 2 + models/migrations/v58.go | 43 ++++++++++ models/models.go | 1 + models/pull.go | 2 +- models/webhook_dingtalk.go | 6 +- models/webhook_discord.go | 6 +- models/webhook_slack.go | 6 +- options/locale/locale_en-US.ini | 2 +- templates/repo/issue/list.tmpl | 8 +- .../repo/issue/view_content/sidebar.tmpl | 14 ++-- 12 files changed, 139 insertions(+), 59 deletions(-) create mode 100644 models/migrations/v58.go diff --git a/models/issue.go b/models/issue.go index 9106db281a275..d20a2dd2b5d46 100644 --- a/models/issue.go +++ b/models/issue.go @@ -2,6 +2,10 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. +// Copyright 2018 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 ( @@ -54,6 +58,14 @@ type Issue struct { Attachments []*Attachment `xorm:"-"` Comments []*Comment `xorm:"-"` Reactions ReactionList `xorm:"-"` + Assignees []*User `xorm:"-"` +} + +// IssueAssignees saves all issue assignees +type IssueAssignees struct { + ID int64 `xorm:"pk autoincr"` + AssigneeID int64 `xorm:"INDEX"` + IssueID int64 `xorm:"INDEX"` } var ( @@ -115,17 +127,49 @@ 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) +func (issue *Issue) loadAssignees(e Engine) (err error) { + var assigneeIDs []IssueAssignees + + err = e.Where("issue_id = ?", issue.ID).Find(&assigneeIDs) + if err != nil { + return + } + + for _, assignee := range assigneeIDs { + user, err := getUserByID(e, assignee.AssigneeID) if err != nil { - issue.AssigneeID = -1 - issue.Assignee = NewGhostUser() - if !IsErrUserNotExist(err) { - return fmt.Errorf("getUserByID.(assignee) [%d]: %v", issue.AssigneeID, err) + user = NewGhostUser() + if IsErrUserNotExist(err) { + return nil } - err = nil - return + return err + } + issue.Assignees = append(issue.Assignees, user) + } + return +} + +func GetAssigneesByIssue(issue *Issue) (assignees []*User, err error) { + err = issue.loadAssignees(x) + if err != nil { + return assignees, err + } + + return issue.Assignees, nil +} + +// MakeAssigneeList concats a string with all names of the assignees. Useful for logs. +func MakeAssigneeList(issue Issue) (AssigneeList string, err error) { + err = issue.loadAssignees(x) + if err != nil { + return "", err + } + + for in, assignee := range issue.Assignees { + AssigneeList += assignee.Name + + if len(issue.Assignees) > (in + 1) { + AssigneeList += ", " } } return @@ -206,7 +250,7 @@ func (issue *Issue) loadAttributes(e Engine) (err error) { } } - if err = issue.loadAssignee(e); err != nil { + if err = issue.loadAssignees(e); err != nil { return } @@ -313,8 +357,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.Assignee.APIFormat() } if issue.IsPull { apiIssue.PullRequest = &api.PullRequestMeta{ @@ -572,19 +619,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 { diff --git a/models/issue_list.go b/models/issue_list.go index 4910915cd09a9..93202c90aeb86 100644 --- a/models/issue_list.go +++ b/models/issue_list.go @@ -165,26 +165,10 @@ func (issues IssueList) getAssigneeIDs() []int64 { } func (issues IssueList) loadAssignees(e Engine) error { - assigneeIDs := issues.getAssigneeIDs() - if len(assigneeIDs) == 0 { - return nil - } - - assigneeMaps := make(map[int64]*User, len(assigneeIDs)) - err := e. - In("id", assigneeIDs). - Find(&assigneeMaps) - if err != nil { - return err - } - - for _, issue := range issues { - if issue.AssigneeID <= 0 { - continue - } - var ok bool - if issue.Assignee, ok = assigneeMaps[issue.AssigneeID]; !ok { - issue.Assignee = NewGhostUser() + for in := range issues { + err := issues[in].loadAssignees(e) + if err != nil { + return err } } return nil diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index dfaef2c78db65..bf5f0f063d4bc 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -168,6 +168,8 @@ var migrations = []Migration{ NewMigration("remove is_owner, num_teams columns from org_user", removeIsOwnerColumnFromOrgUser), // v57 -> v58 NewMigration("add closed_unix column for issues", addIssueClosedTime), + // v58 -> v59 + NewMigration("add multiple assignees", addMultipleAssignees), } // Migrate database to current version diff --git a/models/migrations/v58.go b/models/migrations/v58.go new file mode 100644 index 0000000000000..fab0d6d7b2260 --- /dev/null +++ b/models/migrations/v58.go @@ -0,0 +1,43 @@ +// Copyright 2018 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 migrations + +import ( + "code.gitea.io/gitea/models" + + "github.com/go-xorm/xorm" +) + +func addMultipleAssignees(x *xorm.Engine) error { + + allIssues := []models.Issue{} + err := x.Find(&allIssues) + if err != nil { + return err + } + + // Create the table + type IssueAssignees struct { + ID int64 `xorm:"pk autoincr"` + AssigneeID int64 `xorm:"INDEX"` + IssueID int64 `xorm:"INDEX"` + } + err = x.Sync2(IssueAssignees{}) + if err != nil { + return err + } + + // Range over all issues and insert a new entry for each issue/assignee + for _, issue := range allIssues { + if issue.AssigneeID != 0 { + _, err := x.Insert(IssueAssignees{IssueID: issue.ID, AssigneeID: issue.AssigneeID}) + if err != nil { + return err + } + } + } + + return nil +} diff --git a/models/models.go b/models/models.go index 7738e1a3c2132..162a1ed46bb71 100644 --- a/models/models.go +++ b/models/models.go @@ -119,6 +119,7 @@ func init() { new(RepoIndexerStatus), new(LFSLock), new(Reaction), + new(IssueAssignees), ) gonicNames := []string{"SSL", "UID"} diff --git a/models/pull.go b/models/pull.go index 15b3a4fe18528..2d5319cc720c7 100644 --- a/models/pull.go +++ b/models/pull.go @@ -197,7 +197,7 @@ func (pr *PullRequest) APIFormat() *api.PullRequest { Body: apiIssue.Body, Labels: apiIssue.Labels, Milestone: apiIssue.Milestone, - Assignee: apiIssue.Assignee, + Assignees: apiIssue.Assignees, State: apiIssue.State, Comments: apiIssue.Comments, HTMLURL: pr.Issue.HTMLURL(), diff --git a/models/webhook_dingtalk.go b/models/webhook_dingtalk.go index e25e989084f21..5d78ae5b604d2 100644 --- a/models/webhook_dingtalk.go +++ b/models/webhook_dingtalk.go @@ -118,8 +118,12 @@ func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload, title = fmt.Sprintf("[%s] Pull request edited: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title) text = p.PullRequest.Body case api.HookIssueAssigned: + list, err := MakeAssigneeList(Issue{ID: p.PullRequest.ID}) + if err != nil { + return &DingtalkPayload{}, err + } title = fmt.Sprintf("[%s] Pull request assigned to %s: #%d %s", p.Repository.FullName, - p.PullRequest.Assignee.UserName, p.Index, p.PullRequest.Title) + list, p.Index, p.PullRequest.Title) text = p.PullRequest.Body case api.HookIssueUnassigned: title = fmt.Sprintf("[%s] Pull request unassigned: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title) diff --git a/models/webhook_discord.go b/models/webhook_discord.go index 6d39d8b9939d5..21b1b173f0b7a 100644 --- a/models/webhook_discord.go +++ b/models/webhook_discord.go @@ -191,8 +191,12 @@ func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta) text = p.PullRequest.Body color = warnColor case api.HookIssueAssigned: + list, err := MakeAssigneeList(Issue{ID: p.PullRequest.ID}) + if err != nil { + return &DiscordPayload{}, err + } title = fmt.Sprintf("[%s] Pull request assigned to %s: #%d %s", p.Repository.FullName, - p.PullRequest.Assignee.UserName, p.Index, p.PullRequest.Title) + list, p.Index, p.PullRequest.Title) text = p.PullRequest.Body color = successColor case api.HookIssueUnassigned: diff --git a/models/webhook_slack.go b/models/webhook_slack.go index dd25a8d7df7a8..07eb893ea70b1 100644 --- a/models/webhook_slack.go +++ b/models/webhook_slack.go @@ -172,8 +172,12 @@ func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*S text = fmt.Sprintf("[%s] Pull request edited: %s by %s", p.Repository.FullName, titleLink, senderLink) attachmentText = SlackTextFormatter(p.PullRequest.Body) case api.HookIssueAssigned: + list, err := MakeAssigneeList(Issue{ID: p.PullRequest.ID}) + if err != nil { + return &SlackPayload{}, err + } text = fmt.Sprintf("[%s] Pull request assigned to %s: %s by %s", p.Repository.FullName, - SlackLinkFormatter(setting.AppURL+p.PullRequest.Assignee.UserName, p.PullRequest.Assignee.UserName), + SlackLinkFormatter(setting.AppURL+list, list), titleLink, senderLink) case api.HookIssueUnassigned: text = fmt.Sprintf("[%s] Pull request unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 8f1f4f5f5b377..ef28f91283029 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -622,7 +622,7 @@ issues.new.open_milestone = Open Milestones issues.new.closed_milestone = Closed Milestones issues.new.assignee = Assignee issues.new.clear_assignee = Clear assignee -issues.new.no_assignee = No assignee +issues.new.no_assignees = Nobody assigned issues.no_ref = No Branch/Tag Specified issues.create = Create Issue issues.new_label = New Label diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index 794a4881341b1..90fec0b1cbf87 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -156,7 +156,7 @@ - +