* Add Approval Counts to pulls list Add simple approvals counts to pulls lists * Remove non-official counts * Add PR features to milestone_issues.tmpltags/v1.12.0-dev
@@ -515,3 +515,37 @@ func (issues IssueList) LoadComments() error { | |||||
func (issues IssueList) LoadDiscussComments() error { | func (issues IssueList) LoadDiscussComments() error { | ||||
return issues.loadComments(x, builder.Eq{"comment.type": CommentTypeComment}) | return issues.loadComments(x, builder.Eq{"comment.type": CommentTypeComment}) | ||||
} | } | ||||
// GetApprovalCounts returns a map of issue ID to slice of approval counts | |||||
// FIXME: only returns official counts due to double counting of non-official approvals | |||||
func (issues IssueList) GetApprovalCounts() (map[int64][]*ReviewCount, error) { | |||||
return issues.getApprovalCounts(x) | |||||
} | |||||
func (issues IssueList) getApprovalCounts(e Engine) (map[int64][]*ReviewCount, error) { | |||||
rCounts := make([]*ReviewCount, 0, 6*len(issues)) | |||||
ids := make([]int64, len(issues)) | |||||
for i, issue := range issues { | |||||
ids[i] = issue.ID | |||||
} | |||||
sess := e.In("issue_id", ids) | |||||
err := sess.Select("issue_id, type, count(id) as `count`").Where("official = ?", true).GroupBy("issue_id, type").OrderBy("issue_id").Table("review").Find(&rCounts) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
approvalCountMap := make(map[int64][]*ReviewCount, len(issues)) | |||||
if len(rCounts) > 0 { | |||||
start := 0 | |||||
lastID := rCounts[0].IssueID | |||||
for i, current := range rCounts[1:] { | |||||
if lastID != current.IssueID { | |||||
approvalCountMap[lastID] = rCounts[start:i] | |||||
start = i | |||||
lastID = current.IssueID | |||||
} | |||||
} | |||||
approvalCountMap[lastID] = rCounts[start:] | |||||
} | |||||
return approvalCountMap, nil | |||||
} |
@@ -352,6 +352,25 @@ func (pr *PullRequest) GetCommitMessages() string { | |||||
return stringBuilder.String() | return stringBuilder.String() | ||||
} | } | ||||
// ReviewCount represents a count of Reviews | |||||
type ReviewCount struct { | |||||
IssueID int64 | |||||
Type ReviewType | |||||
Count int64 | |||||
} | |||||
// GetApprovalCounts returns the approval counts by type | |||||
// FIXME: Only returns official counts due to double counting of non-official counts | |||||
func (pr *PullRequest) GetApprovalCounts() ([]*ReviewCount, error) { | |||||
return pr.getApprovalCounts(x) | |||||
} | |||||
func (pr *PullRequest) getApprovalCounts(e Engine) ([]*ReviewCount, error) { | |||||
rCounts := make([]*ReviewCount, 0, 6) | |||||
sess := e.Where("issue_id = ?", pr.IssueID) | |||||
return rCounts, sess.Select("issue_id, type, count(id) as `count`").Where("official = ?", true).GroupBy("issue_id, type").Table("review").Find(&rCounts) | |||||
} | |||||
// GetApprovers returns the approvers of the pull request | // GetApprovers returns the approvers of the pull request | ||||
func (pr *PullRequest) GetApprovers() string { | func (pr *PullRequest) GetApprovers() string { | ||||
@@ -1082,6 +1082,11 @@ pulls.cannot_auto_merge_desc = This pull request cannot be merged automatically | |||||
pulls.cannot_auto_merge_helper = Merge manually to resolve the conflicts. | pulls.cannot_auto_merge_helper = Merge manually to resolve the conflicts. | ||||
pulls.num_conflicting_files_1 = "%d conflicting file" | pulls.num_conflicting_files_1 = "%d conflicting file" | ||||
pulls.num_conflicting_files_n = "%d conflicting files" | pulls.num_conflicting_files_n = "%d conflicting files" | ||||
pulls.approve_count_1 = "%d approval" | |||||
pulls.approve_count_n = "%d approvals" | |||||
pulls.reject_count_1 = "%d change request" | |||||
pulls.reject_count_n = "%d change requests" | |||||
pulls.no_merge_desc = This pull request cannot be merged because all repository merge options are disabled. | pulls.no_merge_desc = This pull request cannot be merged because all repository merge options are disabled. | ||||
pulls.no_merge_helper = Enable merge options in the repository settings or merge the pull request manually. | pulls.no_merge_helper = Enable merge options in the repository settings or merge the pull request manually. | ||||
pulls.no_merge_wip = This pull request can not be merged because it is marked as being a work in progress. | pulls.no_merge_wip = This pull request can not be merged because it is marked as being a work in progress. | ||||
@@ -216,6 +216,12 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB | |||||
} | } | ||||
} | } | ||||
approvalCounts, err := models.IssueList(issues).GetApprovalCounts() | |||||
if err != nil { | |||||
ctx.ServerError("ApprovalCounts", err) | |||||
return | |||||
} | |||||
var commitStatus = make(map[int64]*models.CommitStatus, len(issues)) | var commitStatus = make(map[int64]*models.CommitStatus, len(issues)) | ||||
// Get posters. | // Get posters. | ||||
@@ -263,6 +269,22 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB | |||||
assigneeID = 0 // Reset ID to prevent unexpected selection of assignee. | assigneeID = 0 // Reset ID to prevent unexpected selection of assignee. | ||||
} | } | ||||
ctx.Data["ApprovalCounts"] = func(issueID int64, typ string) int64 { | |||||
counts, ok := approvalCounts[issueID] | |||||
if !ok || len(counts) == 0 { | |||||
return 0 | |||||
} | |||||
reviewTyp := models.ReviewTypeApprove | |||||
if typ == "reject" { | |||||
reviewTyp = models.ReviewTypeReject | |||||
} | |||||
for _, count := range counts { | |||||
if count.Type == reviewTyp { | |||||
return count.Count | |||||
} | |||||
} | |||||
return 0 | |||||
} | |||||
ctx.Data["IssueStats"] = issueStats | ctx.Data["IssueStats"] = issueStats | ||||
ctx.Data["SelLabelIDs"] = labelIDs | ctx.Data["SelLabelIDs"] = labelIDs | ||||
ctx.Data["SelectLabels"] = selectLabels | ctx.Data["SelectLabels"] = selectLabels | ||||
@@ -528,6 +528,12 @@ func Issues(ctx *context.Context) { | |||||
issues = []*models.Issue{} | issues = []*models.Issue{} | ||||
} | } | ||||
approvalCounts, err := models.IssueList(issues).GetApprovalCounts() | |||||
if err != nil { | |||||
ctx.ServerError("ApprovalCounts", err) | |||||
return | |||||
} | |||||
showReposMap := make(map[int64]*models.Repository, len(counts)) | showReposMap := make(map[int64]*models.Repository, len(counts)) | ||||
for repoID := range counts { | for repoID := range counts { | ||||
if repoID > 0 { | if repoID > 0 { | ||||
@@ -639,6 +645,22 @@ func Issues(ctx *context.Context) { | |||||
} | } | ||||
ctx.Data["Issues"] = issues | ctx.Data["Issues"] = issues | ||||
ctx.Data["ApprovalCounts"] = func(issueID int64, typ string) int64 { | |||||
counts, ok := approvalCounts[issueID] | |||||
if !ok || len(counts) == 0 { | |||||
return 0 | |||||
} | |||||
reviewTyp := models.ReviewTypeApprove | |||||
if typ == "reject" { | |||||
reviewTyp = models.ReviewTypeReject | |||||
} | |||||
for _, count := range counts { | |||||
if count.Type == reviewTyp { | |||||
return count.Count | |||||
} | |||||
} | |||||
return 0 | |||||
} | |||||
ctx.Data["CommitStatus"] = commitStatus | ctx.Data["CommitStatus"] = commitStatus | ||||
ctx.Data["Repos"] = showRepos | ctx.Data["Repos"] = showRepos | ||||
ctx.Data["Counts"] = counts | ctx.Data["Counts"] = counts | ||||
@@ -202,6 +202,7 @@ | |||||
</div> | </div> | ||||
<div class="issue list"> | <div class="issue list"> | ||||
{{ $approvalCounts := .ApprovalCounts}} | |||||
{{range .Issues}} | {{range .Issues}} | ||||
<li class="item"> | <li class="item"> | ||||
{{if $.CanWriteIssuesOrPulls}} | {{if $.CanWriteIssuesOrPulls}} | ||||
@@ -268,6 +269,16 @@ | |||||
</a> | </a> | ||||
{{end}} | {{end}} | ||||
{{if .IsPull}} | {{if .IsPull}} | ||||
{{$approveOfficial := call $approvalCounts .ID "approve"}} | |||||
{{$rejectOfficial := call $approvalCounts .ID "reject"}} | |||||
{{if or (gt $approveOfficial 0) (gt $rejectOfficial 0)}} | |||||
<span class="approvals">{{svg "octicon-check" 16}} | |||||
{{$.i18n.Tr (TrN $.i18n.Lang $approveOfficial "repo.pulls.approve_count_1" "repo.pulls.approve_count_n") $approveOfficial}} | |||||
{{if or (gt $rejectOfficial 0)}} | |||||
<span class="rejects">{{svg "octicon-x" 16}} | |||||
{{$.i18n.Tr (TrN $.i18n.Lang $rejectOfficial "repo.pulls.reject_count_1" "repo.pulls.reject_count_n") $rejectOfficial}} | |||||
{{end}} | |||||
{{end}} | |||||
{{if and (not .PullRequest.HasMerged) (gt (len .PullRequest.ConflictedFiles) 0)}} | {{if and (not .PullRequest.HasMerged) (gt (len .PullRequest.ConflictedFiles) 0)}} | ||||
<span class="conflicting">{{svg "octicon-mirror" 16}} {{$.i18n.Tr (TrN $.i18n.Lang (len .PullRequest.ConflictedFiles) "repo.pulls.num_conflicting_files_1" "repo.pulls.num_conflicting_files_n") (len .PullRequest.ConflictedFiles)}}</span> | <span class="conflicting">{{svg "octicon-mirror" 16}} {{$.i18n.Tr (TrN $.i18n.Lang (len .PullRequest.ConflictedFiles) "repo.pulls.num_conflicting_files_1" "repo.pulls.num_conflicting_files_n") (len .PullRequest.ConflictedFiles)}}</span> | ||||
{{end}} | {{end}} | ||||
@@ -177,6 +177,7 @@ | |||||
</div> | </div> | ||||
<div class="issue list"> | <div class="issue list"> | ||||
{{ $approvalCounts := .ApprovalCounts}} | |||||
{{range .Issues}} | {{range .Issues}} | ||||
{{ $timeStr:= TimeSinceUnix .CreatedUnix $.Lang }} | {{ $timeStr:= TimeSinceUnix .CreatedUnix $.Lang }} | ||||
<li class="item"> | <li class="item"> | ||||
@@ -185,9 +186,15 @@ | |||||
<input type="checkbox" data-issue-id={{.ID}}></input> | <input type="checkbox" data-issue-id={{.ID}}></input> | ||||
</div> | </div> | ||||
{{end}} | {{end}} | ||||
<div class="ui {{if .IsRead}}gray{{else}}green{{end}} label">#{{.Index}}</div> | |||||
<div class="ui {{if .IsClosed}}{{if .IsPull}}{{if .PullRequest.HasMerged}}purple{{else}}red{{end}}{{else}}red{{end}}{{else}}{{if .IsRead}}white{{else}}green{{end}}{{end}} label">#{{.Index}}</div> | |||||
<a class="title has-emoji" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title}}</a> | <a class="title has-emoji" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title}}</a> | ||||
{{if .IsPull }} | |||||
{{if (index $.CommitStatus .PullRequest.ID)}} | |||||
{{template "repo/commit_status" (index $.CommitStatus .PullRequest.ID)}} | |||||
{{end}} | |||||
{{end}} | |||||
{{range .Labels}} | {{range .Labels}} | ||||
<a class="ui label has-emoji" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}&assignee={{$.AssigneeID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description}}">{{.Name}}</a> | <a class="ui label has-emoji" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}&assignee={{$.AssigneeID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description}}">{{.Name}}</a> | ||||
{{end}} | {{end}} | ||||
@@ -201,11 +208,15 @@ | |||||
{{end}} | {{end}} | ||||
<p class="desc"> | <p class="desc"> | ||||
{{if gt .Poster.ID 0}} | |||||
{{$.i18n.Tr .GetLastEventLabel $timeStr .Poster.HomeLink (.Poster.GetDisplayName|Escape) | Safe}} | |||||
{{ $timeStr := TimeSinceUnix .GetLastEventTimestamp $.Lang }} | |||||
{{if .OriginalAuthor }} | |||||
{{$.i18n.Tr .GetLastEventLabelFake $timeStr .OriginalAuthor | Safe}} | |||||
{{else if gt .Poster.ID 0}} | |||||
{{$.i18n.Tr .GetLastEventLabel $timeStr .Poster.HomeLink (.Poster.GetDisplayName | Escape) | Safe}} | |||||
{{else}} | {{else}} | ||||
{{$.i18n.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName|Escape) | Safe}} | |||||
{{$.i18n.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName | Escape) | Safe}} | |||||
{{end}} | {{end}} | ||||
{{if .Ref}} | {{if .Ref}} | ||||
<a class="ref" href="{{$.RepoLink}}/src/branch/{{.Ref}}"> | <a class="ref" href="{{$.RepoLink}}/src/branch/{{.Ref}}"> | ||||
{{svg "octicon-git-branch" 16}} {{.Ref}} | {{svg "octicon-git-branch" 16}} {{.Ref}} | ||||
@@ -227,6 +238,21 @@ | |||||
<img class="ui avatar image" src="{{.RelAvatarLink}}"> | <img class="ui avatar image" src="{{.RelAvatarLink}}"> | ||||
</a> | </a> | ||||
{{end}} | {{end}} | ||||
{{if .IsPull}} | |||||
{{$approveOfficial := call $approvalCounts .ID "approve"}} | |||||
{{$rejectOfficial := call $approvalCounts .ID "reject"}} | |||||
{{if or (gt $approveOfficial 0) (gt $rejectOfficial 0)}} | |||||
<span class="approvals">{{svg "octicon-check" 16}} | |||||
{{$.i18n.Tr (TrN $.i18n.Lang $approveOfficial "repo.pulls.approve_count_1" "repo.pulls.approve_count_n") $approveOfficial}} | |||||
{{if or (gt $rejectOfficial 0)}} | |||||
<span class="rejects">{{svg "octicon-x" 16}} | |||||
{{$.i18n.Tr (TrN $.i18n.Lang $rejectOfficial "repo.pulls.reject_count_1" "repo.pulls.reject_count_n") $rejectOfficial}} | |||||
{{end}} | |||||
{{end}} | |||||
{{if and (not .PullRequest.HasMerged) ((len .PullRequest.ConflictedFiles) gt 0)}} | |||||
<span class="conflicting">{{svg "octicon-mirror" 16}} {{$.i18n.Tr (TrN $.i18n.Lang (len .PullRequest.ConflictedFiles) "repo.pulls.num_conflicting_files_1" "repo.pulls.num_conflicting_files_n") (len .PullRequest.ConflictedFiles)}}</span> | |||||
{{end}} | |||||
{{end}} | |||||
</p> | </p> | ||||
</li> | </li> | ||||
{{end}} | {{end}} | ||||
@@ -101,6 +101,7 @@ | |||||
</div> | </div> | ||||
<div class="issue list"> | <div class="issue list"> | ||||
{{ $approvalCounts := .ApprovalCounts}} | |||||
{{range .Issues}} | {{range .Issues}} | ||||
{{ $timeStr:= TimeSinceUnix .CreatedUnix $.Lang }} | {{ $timeStr:= TimeSinceUnix .CreatedUnix $.Lang }} | ||||
@@ -170,6 +171,16 @@ | |||||
</span> | </span> | ||||
{{end}} | {{end}} | ||||
{{if .IsPull}} | {{if .IsPull}} | ||||
{{$approveOfficial := call $approvalCounts .ID "approve"}} | |||||
{{$rejectOfficial := call $approvalCounts .ID "reject"}} | |||||
{{if or (gt $approveOfficial 0) (gt $rejectOfficial 0) }} | |||||
<span class="approvals">{{svg "octicon-check" 16}} | |||||
{{$.i18n.Tr (TrN $.i18n.Lang $approveOfficial "repo.pulls.approve_count_1" "repo.pulls.approve_count_n") $approveOfficial}} | |||||
{{if or (gt $rejectOfficial 0)}} | |||||
<span class="rejects">{{svg "octicon-x" 16}} | |||||
{{$.i18n.Tr (TrN $.i18n.Lang $rejectOfficial "repo.pulls.reject_count_1" "repo.pulls.reject_count_n") $rejectOfficial}} | |||||
{{end}} | |||||
{{end}} | |||||
{{if and (not .PullRequest.HasMerged) (gt (len .PullRequest.ConflictedFiles) 0)}} | {{if and (not .PullRequest.HasMerged) (gt (len .PullRequest.ConflictedFiles) 0)}} | ||||
<span class="conflicting">{{svg "octicon-mirror" 16}} {{$.i18n.Tr (TrN $.i18n.Lang (len .PullRequest.ConflictedFiles) "repo.pulls.num_conflicting_files_1" "repo.pulls.num_conflicting_files_n") (len .PullRequest.ConflictedFiles)}}</span> | <span class="conflicting">{{svg "octicon-mirror" 16}} {{$.i18n.Tr (TrN $.i18n.Lang (len .PullRequest.ConflictedFiles) "repo.pulls.num_conflicting_files_1" "repo.pulls.num_conflicting_files_n") (len .PullRequest.ConflictedFiles)}}</span> | ||||
{{end}} | {{end}} | ||||