forked from kevadesu/forgejo
Merge pull request 'feat: combine review requests comments' (#5695) from gusted/forgejo-combine-request-review into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5695 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
commit
0e0a153adb
9 changed files with 685 additions and 65 deletions
|
@ -1825,6 +1825,7 @@ func ViewIssue(ctx *context.Context) {
|
|||
|
||||
// Combine multiple label assignments into a single comment
|
||||
combineLabelComments(issue)
|
||||
combineRequestReviewComments(issue)
|
||||
|
||||
getBranchData(ctx, issue)
|
||||
if issue.IsPull {
|
||||
|
@ -3665,6 +3666,127 @@ func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment,
|
|||
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.
|
||||
func combineLabelComments(issue *issues_model.Issue) {
|
||||
var prev, cur *issues_model.Comment
|
||||
|
|
|
@ -7,6 +7,8 @@ import (
|
|||
"testing"
|
||||
|
||||
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"
|
||||
)
|
||||
|
@ -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])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue