feat: combine review requests comments
- Combine review requests comments similairy how labels comments are combined. If review requests comments were made within 60 seconds of each other they will be grouped. - Integration and unit test added. - Resolves #2774
This commit is contained in:
parent
aa86e94853
commit
8fdc0a7a6c
9 changed files with 685 additions and 65 deletions
|
@ -222,43 +222,51 @@ func (r RoleInRepo) LocaleHelper(lang translation.Locale) string {
|
||||||
return lang.TrString("repo.issues.role." + string(r) + "_helper")
|
return lang.TrString("repo.issues.role." + string(r) + "_helper")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RequestReviewTarget interface {
|
||||||
|
ID() int64
|
||||||
|
Name() string
|
||||||
|
Type() string
|
||||||
|
}
|
||||||
|
|
||||||
// Comment represents a comment in commit and issue page.
|
// Comment represents a comment in commit and issue page.
|
||||||
type Comment struct {
|
type Comment struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
Type CommentType `xorm:"INDEX"`
|
Type CommentType `xorm:"INDEX"`
|
||||||
PosterID int64 `xorm:"INDEX"`
|
PosterID int64 `xorm:"INDEX"`
|
||||||
Poster *user_model.User `xorm:"-"`
|
Poster *user_model.User `xorm:"-"`
|
||||||
OriginalAuthor string
|
OriginalAuthor string
|
||||||
OriginalAuthorID int64
|
OriginalAuthorID int64
|
||||||
IssueID int64 `xorm:"INDEX"`
|
IssueID int64 `xorm:"INDEX"`
|
||||||
Issue *Issue `xorm:"-"`
|
Issue *Issue `xorm:"-"`
|
||||||
LabelID int64
|
LabelID int64
|
||||||
Label *Label `xorm:"-"`
|
Label *Label `xorm:"-"`
|
||||||
AddedLabels []*Label `xorm:"-"`
|
AddedLabels []*Label `xorm:"-"`
|
||||||
RemovedLabels []*Label `xorm:"-"`
|
RemovedLabels []*Label `xorm:"-"`
|
||||||
OldProjectID int64
|
AddedRequestReview []RequestReviewTarget `xorm:"-"`
|
||||||
ProjectID int64
|
RemovedRequestReview []RequestReviewTarget `xorm:"-"`
|
||||||
OldProject *project_model.Project `xorm:"-"`
|
OldProjectID int64
|
||||||
Project *project_model.Project `xorm:"-"`
|
ProjectID int64
|
||||||
OldMilestoneID int64
|
OldProject *project_model.Project `xorm:"-"`
|
||||||
MilestoneID int64
|
Project *project_model.Project `xorm:"-"`
|
||||||
OldMilestone *Milestone `xorm:"-"`
|
OldMilestoneID int64
|
||||||
Milestone *Milestone `xorm:"-"`
|
MilestoneID int64
|
||||||
TimeID int64
|
OldMilestone *Milestone `xorm:"-"`
|
||||||
Time *TrackedTime `xorm:"-"`
|
Milestone *Milestone `xorm:"-"`
|
||||||
AssigneeID int64
|
TimeID int64
|
||||||
RemovedAssignee bool
|
Time *TrackedTime `xorm:"-"`
|
||||||
Assignee *user_model.User `xorm:"-"`
|
AssigneeID int64
|
||||||
AssigneeTeamID int64 `xorm:"NOT NULL DEFAULT 0"`
|
RemovedAssignee bool
|
||||||
AssigneeTeam *organization.Team `xorm:"-"`
|
Assignee *user_model.User `xorm:"-"`
|
||||||
ResolveDoerID int64
|
AssigneeTeamID int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||||
ResolveDoer *user_model.User `xorm:"-"`
|
AssigneeTeam *organization.Team `xorm:"-"`
|
||||||
OldTitle string
|
ResolveDoerID int64
|
||||||
NewTitle string
|
ResolveDoer *user_model.User `xorm:"-"`
|
||||||
OldRef string
|
OldTitle string
|
||||||
NewRef string
|
NewTitle string
|
||||||
DependentIssueID int64 `xorm:"index"` // This is used by issue_service.deleteIssue
|
OldRef string
|
||||||
DependentIssue *Issue `xorm:"-"`
|
NewRef string
|
||||||
|
DependentIssueID int64 `xorm:"index"` // This is used by issue_service.deleteIssue
|
||||||
|
DependentIssue *Issue `xorm:"-"`
|
||||||
|
|
||||||
CommitID int64
|
CommitID int64
|
||||||
Line int64 // - previous line / + proposed line
|
Line int64 // - previous line / + proposed line
|
||||||
|
|
|
@ -182,6 +182,7 @@ func NewFuncMap() template.FuncMap {
|
||||||
"RenderMarkdownToHtml": RenderMarkdownToHtml,
|
"RenderMarkdownToHtml": RenderMarkdownToHtml,
|
||||||
"RenderLabel": RenderLabel,
|
"RenderLabel": RenderLabel,
|
||||||
"RenderLabels": RenderLabels,
|
"RenderLabels": RenderLabels,
|
||||||
|
"RenderReviewRequest": RenderReviewRequest,
|
||||||
|
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
// misc
|
// misc
|
||||||
|
|
|
@ -262,3 +262,15 @@ func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issu
|
||||||
htmlCode += "</span>"
|
htmlCode += "</span>"
|
||||||
return template.HTML(htmlCode)
|
return template.HTML(htmlCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RenderReviewRequest(users []issues_model.RequestReviewTarget) template.HTML {
|
||||||
|
usernames := make([]string, 0, len(users))
|
||||||
|
for _, user := range users {
|
||||||
|
usernames = append(usernames, template.HTMLEscapeString(user.Name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlCode := `<span class="review-request-list">`
|
||||||
|
htmlCode += strings.Join(usernames, ", ")
|
||||||
|
htmlCode += "</span>"
|
||||||
|
return template.HTML(htmlCode)
|
||||||
|
}
|
||||||
|
|
|
@ -1791,9 +1791,12 @@ issues.review.left_comment = left a comment
|
||||||
issues.review.content.empty = You need to leave a comment indicating the requested change(s).
|
issues.review.content.empty = You need to leave a comment indicating the requested change(s).
|
||||||
issues.review.reject = requested changes %s
|
issues.review.reject = requested changes %s
|
||||||
issues.review.wait = was requested for review %s
|
issues.review.wait = was requested for review %s
|
||||||
issues.review.add_review_request = requested review from %s %s
|
issues.review.add_review_request = requested review from %[1]s %[2]s
|
||||||
issues.review.remove_review_request = removed review request for %s %s
|
issues.review.add_review_requests = requested reviews from %[1]s %[2]s
|
||||||
issues.review.remove_review_request_self = refused to review %s
|
issues.review.remove_review_request = removed review request for %[1]s %[2]s
|
||||||
|
issues.review.remove_review_requests = removed review requests for %[1]s %[2]s
|
||||||
|
issues.review.remove_review_request_self = refused to review %[1]s %[2]s
|
||||||
|
issues.review.add_remove_review_requests = requested reviews from %[1]s and removed review requests for %[2]s %[3]s
|
||||||
issues.review.pending = Pending
|
issues.review.pending = Pending
|
||||||
issues.review.pending.tooltip = This comment is not currently visible to other users. To submit your pending comments, select "%s" -> "%s/%s/%s" at the top of the page.
|
issues.review.pending.tooltip = This comment is not currently visible to other users. To submit your pending comments, select "%s" -> "%s/%s/%s" at the top of the page.
|
||||||
issues.review.review = Review
|
issues.review.review = Review
|
||||||
|
|
|
@ -1825,6 +1825,7 @@ func ViewIssue(ctx *context.Context) {
|
||||||
|
|
||||||
// Combine multiple label assignments into a single comment
|
// Combine multiple label assignments into a single comment
|
||||||
combineLabelComments(issue)
|
combineLabelComments(issue)
|
||||||
|
combineRequestReviewComments(issue)
|
||||||
|
|
||||||
getBranchData(ctx, issue)
|
getBranchData(ctx, issue)
|
||||||
if issue.IsPull {
|
if issue.IsPull {
|
||||||
|
@ -3665,6 +3666,127 @@ func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment,
|
||||||
return attachHTML
|
return attachHTML
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RequestReviewTarget struct {
|
||||||
|
user *user_model.User
|
||||||
|
team *organization.Team
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *RequestReviewTarget) ID() int64 {
|
||||||
|
if t.user != nil {
|
||||||
|
return t.user.ID
|
||||||
|
}
|
||||||
|
return t.team.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *RequestReviewTarget) Name() string {
|
||||||
|
if t.user != nil {
|
||||||
|
return t.user.GetDisplayName()
|
||||||
|
}
|
||||||
|
return t.team.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *RequestReviewTarget) Type() string {
|
||||||
|
if t.user != nil {
|
||||||
|
return "user"
|
||||||
|
}
|
||||||
|
return "team"
|
||||||
|
}
|
||||||
|
|
||||||
|
// combineRequestReviewComments combine the nearby request review comments as one.
|
||||||
|
func combineRequestReviewComments(issue *issues_model.Issue) {
|
||||||
|
var prev, cur *issues_model.Comment
|
||||||
|
for i := 0; i < len(issue.Comments); i++ {
|
||||||
|
cur = issue.Comments[i]
|
||||||
|
if i > 0 {
|
||||||
|
prev = issue.Comments[i-1]
|
||||||
|
}
|
||||||
|
if i == 0 || cur.Type != issues_model.CommentTypeReviewRequest ||
|
||||||
|
(prev != nil && prev.PosterID != cur.PosterID) ||
|
||||||
|
(prev != nil && cur.CreatedUnix-prev.CreatedUnix >= 60) {
|
||||||
|
if cur.Type == issues_model.CommentTypeReviewRequest && (cur.Assignee != nil || cur.AssigneeTeam != nil) {
|
||||||
|
if cur.RemovedAssignee {
|
||||||
|
if cur.AssigneeTeam != nil {
|
||||||
|
cur.RemovedRequestReview = append(cur.RemovedRequestReview, &RequestReviewTarget{team: cur.AssigneeTeam})
|
||||||
|
} else {
|
||||||
|
cur.RemovedRequestReview = append(cur.RemovedRequestReview, &RequestReviewTarget{user: cur.Assignee})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if cur.AssigneeTeam != nil {
|
||||||
|
cur.AddedRequestReview = append(cur.AddedRequestReview, &RequestReviewTarget{team: cur.AssigneeTeam})
|
||||||
|
} else {
|
||||||
|
cur.AddedRequestReview = append(cur.AddedRequestReview, &RequestReviewTarget{user: cur.Assignee})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Previous comment is not a review request, so cannot group. Start a new group.
|
||||||
|
if prev.Type != issues_model.CommentTypeReviewRequest {
|
||||||
|
if cur.RemovedAssignee {
|
||||||
|
if cur.AssigneeTeam != nil {
|
||||||
|
cur.RemovedRequestReview = append(cur.RemovedRequestReview, &RequestReviewTarget{team: cur.AssigneeTeam})
|
||||||
|
} else {
|
||||||
|
cur.RemovedRequestReview = append(cur.RemovedRequestReview, &RequestReviewTarget{user: cur.Assignee})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if cur.AssigneeTeam != nil {
|
||||||
|
cur.AddedRequestReview = append(cur.AddedRequestReview, &RequestReviewTarget{team: cur.AssigneeTeam})
|
||||||
|
} else {
|
||||||
|
cur.AddedRequestReview = append(cur.AddedRequestReview, &RequestReviewTarget{user: cur.Assignee})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start grouping.
|
||||||
|
if cur.RemovedAssignee {
|
||||||
|
addedIndex := slices.IndexFunc(prev.AddedRequestReview, func(t issues_model.RequestReviewTarget) bool {
|
||||||
|
if cur.AssigneeTeam != nil {
|
||||||
|
return cur.AssigneeTeam.ID == t.ID() && t.Type() == "team"
|
||||||
|
}
|
||||||
|
return cur.Assignee.ID == t.ID() && t.Type() == "user"
|
||||||
|
})
|
||||||
|
|
||||||
|
// If for this target a AddedRequestReview, then we remove that entry. If it's not found, then add it to the RemovedRequestReview.
|
||||||
|
if addedIndex == -1 {
|
||||||
|
if cur.AssigneeTeam != nil {
|
||||||
|
prev.RemovedRequestReview = append(prev.RemovedRequestReview, &RequestReviewTarget{team: cur.AssigneeTeam})
|
||||||
|
} else {
|
||||||
|
prev.RemovedRequestReview = append(prev.RemovedRequestReview, &RequestReviewTarget{user: cur.Assignee})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prev.AddedRequestReview = slices.Delete(prev.AddedRequestReview, addedIndex, addedIndex+1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
removedIndex := slices.IndexFunc(prev.RemovedRequestReview, func(t issues_model.RequestReviewTarget) bool {
|
||||||
|
if cur.AssigneeTeam != nil {
|
||||||
|
return cur.AssigneeTeam.ID == t.ID() && t.Type() == "team"
|
||||||
|
}
|
||||||
|
return cur.Assignee.ID == t.ID() && t.Type() == "user"
|
||||||
|
})
|
||||||
|
|
||||||
|
// If for this target a RemovedRequestReview, then we remove that entry. If it's not found, then add it to the AddedRequestReview.
|
||||||
|
if removedIndex == -1 {
|
||||||
|
if cur.AssigneeTeam != nil {
|
||||||
|
prev.AddedRequestReview = append(prev.AddedRequestReview, &RequestReviewTarget{team: cur.AssigneeTeam})
|
||||||
|
} else {
|
||||||
|
prev.AddedRequestReview = append(prev.AddedRequestReview, &RequestReviewTarget{user: cur.Assignee})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prev.RemovedRequestReview = slices.Delete(prev.RemovedRequestReview, removedIndex, removedIndex+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Propoagate creation time.
|
||||||
|
prev.CreatedUnix = cur.CreatedUnix
|
||||||
|
|
||||||
|
// Remove the current comment since it has been combined to prev comment
|
||||||
|
issue.Comments = append(issue.Comments[:i], issue.Comments[i+1:]...)
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// combineLabelComments combine the nearby label comments as one.
|
// combineLabelComments combine the nearby label comments as one.
|
||||||
func combineLabelComments(issue *issues_model.Issue) {
|
func combineLabelComments(issue *issues_model.Issue) {
|
||||||
var prev, cur *issues_model.Comment
|
var prev, cur *issues_model.Comment
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
org_model "code.gitea.io/gitea/models/organization"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -373,3 +375,432 @@ func TestCombineLabelComments(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCombineReviewRequests(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
beforeCombined []*issues_model.Comment
|
||||||
|
afterCombined []*issues_model.Comment
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "case 1",
|
||||||
|
beforeCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
RemovedAssignee: true,
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeComment,
|
||||||
|
PosterID: 1,
|
||||||
|
Content: "test",
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
afterCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
CreatedUnix: 0,
|
||||||
|
AddedRequestReview: []issues_model.RequestReviewTarget{},
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeComment,
|
||||||
|
PosterID: 1,
|
||||||
|
Content: "test",
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "case 2",
|
||||||
|
beforeCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 2,
|
||||||
|
Name: "Ghost 2",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
afterCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
CreatedUnix: 0,
|
||||||
|
AddedRequestReview: []issues_model.RequestReviewTarget{
|
||||||
|
&RequestReviewTarget{
|
||||||
|
user: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&RequestReviewTarget{
|
||||||
|
user: &user_model.User{
|
||||||
|
ID: 2,
|
||||||
|
Name: "Ghost 2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "case 3",
|
||||||
|
beforeCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
RemovedAssignee: true,
|
||||||
|
AssigneeTeam: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
afterCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
CreatedUnix: 0,
|
||||||
|
AddedRequestReview: []issues_model.RequestReviewTarget{
|
||||||
|
&RequestReviewTarget{
|
||||||
|
user: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RemovedRequestReview: []issues_model.RequestReviewTarget{
|
||||||
|
&RequestReviewTarget{
|
||||||
|
team: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "case 4",
|
||||||
|
beforeCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
RemovedAssignee: true,
|
||||||
|
AssigneeTeam: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
AssigneeTeam: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
afterCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
CreatedUnix: 0,
|
||||||
|
AddedRequestReview: []issues_model.RequestReviewTarget{
|
||||||
|
&RequestReviewTarget{
|
||||||
|
user: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RemovedRequestReview: []issues_model.RequestReviewTarget{},
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "case 5",
|
||||||
|
beforeCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
RemovedAssignee: true,
|
||||||
|
AssigneeTeam: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
AssigneeTeam: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
RemovedAssignee: true,
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
afterCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
CreatedUnix: 0,
|
||||||
|
AddedRequestReview: []issues_model.RequestReviewTarget{},
|
||||||
|
RemovedRequestReview: []issues_model.RequestReviewTarget{},
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "case 6",
|
||||||
|
beforeCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
RemovedAssignee: true,
|
||||||
|
AssigneeTeam: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeComment,
|
||||||
|
PosterID: 1,
|
||||||
|
Content: "test",
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
AssigneeTeam: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
RemovedAssignee: true,
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
afterCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
CreatedUnix: 0,
|
||||||
|
RemovedRequestReview: []issues_model.RequestReviewTarget{&RequestReviewTarget{
|
||||||
|
team: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
AddedRequestReview: []issues_model.RequestReviewTarget{&RequestReviewTarget{
|
||||||
|
user: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeComment,
|
||||||
|
PosterID: 1,
|
||||||
|
Content: "test",
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
CreatedUnix: 0,
|
||||||
|
AddedRequestReview: []issues_model.RequestReviewTarget{&RequestReviewTarget{
|
||||||
|
team: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
RemovedRequestReview: []issues_model.RequestReviewTarget{&RequestReviewTarget{
|
||||||
|
user: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
AssigneeTeam: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "case 7",
|
||||||
|
beforeCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
CreatedUnix: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
AssigneeTeam: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
CreatedUnix: 61,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
afterCombined: []*issues_model.Comment{
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
CreatedUnix: 0,
|
||||||
|
AddedRequestReview: []issues_model.RequestReviewTarget{&RequestReviewTarget{
|
||||||
|
user: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
Assignee: &user_model.User{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Ghost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: issues_model.CommentTypeReviewRequest,
|
||||||
|
PosterID: 1,
|
||||||
|
CreatedUnix: 0,
|
||||||
|
RemovedRequestReview: []issues_model.RequestReviewTarget{&RequestReviewTarget{
|
||||||
|
team: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
AssigneeTeam: &org_model.Team{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Team 1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
issue := issues_model.Issue{
|
||||||
|
Comments: testCase.beforeCombined,
|
||||||
|
}
|
||||||
|
combineRequestReviewComments(&issue)
|
||||||
|
assert.EqualValues(t, testCase.afterCombined[0], issue.Comments[0])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -522,35 +522,23 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{else if eq .Type 27}}
|
{{else if eq .Type 27}}
|
||||||
<div class="timeline-item event" id="{{.HashTag}}">
|
{{if or .AddedRequestReview .RemovedRequestReview}}
|
||||||
<span class="badge">{{svg "octicon-eye"}}</span>
|
<div class="timeline-item event" id="{{.HashTag}}">
|
||||||
{{template "shared/user/avatarlink" dict "user" .Poster}}
|
<span class="badge">{{svg "octicon-tag"}}</span>
|
||||||
<span class="text grey muted-links">
|
{{template "shared/user/avatarlink" dict "user" .Poster}}
|
||||||
{{template "shared/user/authorlink" .Poster}}
|
<span class="text grey muted-links">
|
||||||
{{if (gt .AssigneeID 0)}}
|
{{if and (eq (len .RemovedRequestReview) 1) (eq (len .AddedRequestReview) 0) (eq ((index .RemovedRequestReview 0).ID) .PosterID) (eq ((index .RemovedRequestReview 0).Type) "user")}}
|
||||||
{{if .RemovedAssignee}}
|
<span class="review-request-list">{{ctx.Locale.Tr "repo.issues.review.remove_review_request_self" $createdStr}}</span>
|
||||||
{{if eq .PosterID .AssigneeID}}
|
{{else if and .AddedRequestReview (not .RemovedRequestReview)}}
|
||||||
{{ctx.Locale.Tr "repo.issues.review.remove_review_request_self" $createdStr}}
|
{{ctx.Locale.TrN (len .AddedRequestReview) "repo.issues.review.add_review_request" "repo.issues.review.add_review_requests" (RenderReviewRequest .AddedRequestReview) $createdStr}}
|
||||||
{{else}}
|
{{else if and (not .AddedRequestReview) .RemovedRequestReview}}
|
||||||
{{ctx.Locale.Tr "repo.issues.review.remove_review_request" .Assignee.GetDisplayName $createdStr}}
|
{{ctx.Locale.TrN (len .RemovedRequestReview) "repo.issues.review.remove_review_request" "repo.issues.review.remove_review_requests" (RenderReviewRequest .RemovedRequestReview) $createdStr}}
|
||||||
{{end}}
|
|
||||||
{{else}}
|
{{else}}
|
||||||
{{ctx.Locale.Tr "repo.issues.review.add_review_request" .Assignee.GetDisplayName $createdStr}}
|
{{ctx.Locale.Tr "repo.issues.review.add_remove_review_requests" (RenderReviewRequest .AddedRequestReview) (RenderReviewRequest .RemovedRequestReview) $createdStr}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
</span>
|
||||||
<!-- If the assigned team is deleted, just displaying "Ghost Team" in the comment -->
|
</div>
|
||||||
{{$teamName := "Ghost Team"}}
|
{{end}}
|
||||||
{{if .AssigneeTeam}}
|
|
||||||
{{$teamName = .AssigneeTeam.Name}}
|
|
||||||
{{end}}
|
|
||||||
{{if .RemovedAssignee}}
|
|
||||||
{{ctx.Locale.Tr "repo.issues.review.remove_review_request" $teamName $createdStr}}
|
|
||||||
{{else}}
|
|
||||||
{{ctx.Locale.Tr "repo.issues.review.add_review_request" $teamName $createdStr}}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{{else if and (eq .Type 29) (or (gt .CommitsNum 0) .IsForcePush)}}
|
{{else if and (eq .Type 29) (or (gt .CommitsNum 0) .IsForcePush)}}
|
||||||
<!-- If PR is closed, the comments whose type is CommentTypePullRequestPush(29) after latestCloseCommentID won't be rendered. //-->
|
<!-- If PR is closed, the comments whose type is CommentTypePullRequestPush(29) after latestCloseCommentID won't be rendered. //-->
|
||||||
{{if and .Issue.IsClosed (gt .ID $.LatestCloseCommentID)}}
|
{{if and .Issue.IsClosed (gt .ID $.LatestCloseCommentID)}}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
-
|
||||||
|
id: 1001
|
||||||
|
type: 4
|
||||||
|
reviewer_id: 2
|
||||||
|
issue_id: 3
|
||||||
|
official: true
|
||||||
|
stale: false
|
||||||
|
original_author_id: 0
|
||||||
|
updated_unix: 1000000000
|
||||||
|
created_unix: 1000000000
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1002
|
||||||
|
type: 4
|
||||||
|
reviewer_id: 11
|
||||||
|
issue_id: 3
|
||||||
|
official: true
|
||||||
|
stale: false
|
||||||
|
original_author_id: 0
|
||||||
|
updated_unix: 1000000000
|
||||||
|
created_unix: 1000000000
|
|
@ -65,3 +65,37 @@ func TestPullManuallyMergeWarning(t *testing.T) {
|
||||||
assert.NotContains(t, mergeInstructions, warningMessage)
|
assert.NotContains(t, mergeInstructions, warningMessage)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPullCombinedReviewRequest(t *testing.T) {
|
||||||
|
defer tests.AddFixtures("tests/integration/fixtures/TestPullCombinedReviewRequest/")()
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
|
helper := func(t *testing.T, action, userID, expectedText string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
req := NewRequestWithValues(t, "POST", "/user2/repo1/pulls/request_review", map[string]string{
|
||||||
|
"_csrf": GetCSRF(t, session, "/user2/repo1/pulls/3"),
|
||||||
|
"issue_ids": "3",
|
||||||
|
"action": action,
|
||||||
|
"id": userID,
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", "/user2/repo1/pulls/3")
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
|
|
||||||
|
assert.Contains(t, htmlDoc.Find(".timeline-item:has(.review-request-list)").Last().Text(), expectedText)
|
||||||
|
}
|
||||||
|
|
||||||
|
helper(t, "detach", "2", "refused to review")
|
||||||
|
helper(t, "attach", "4", "requested reviews from user4 and removed review requests for user2")
|
||||||
|
helper(t, "attach", "9", "requested reviews from user4, user9 and removed review requests for user2")
|
||||||
|
helper(t, "attach", "2", "requested reviews from user4, user9")
|
||||||
|
helper(t, "detach", "4", "requested review from user9")
|
||||||
|
helper(t, "detach", "11", "requested reviews from user9 and removed review requests for user11")
|
||||||
|
helper(t, "detach", "9", "removed review request for user11")
|
||||||
|
helper(t, "detach", "2", "removed review requests for user11, user2")
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue