mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-05-31 20:02:09 +00:00
feat(ui): create a comment aggregator to reduce noise in issues (#6523)
Some checks are pending
/ release (push) Waiting to run
testing / backend-checks (push) Has been skipped
testing / frontend-checks (push) Has been skipped
testing / test-unit (push) Has been skipped
testing / test-e2e (push) Has been skipped
testing / test-mysql (push) Has been skipped
testing / test-pgsql (push) Has been skipped
testing / test-sqlite (push) Has been skipped
testing / test-remote-cacher (redis) (push) Has been skipped
testing / test-remote-cacher (valkey) (push) Has been skipped
testing / test-remote-cacher (garnet) (push) Has been skipped
testing / test-remote-cacher (redict) (push) Has been skipped
testing / security-check (push) Has been skipped
Some checks are pending
/ release (push) Waiting to run
testing / backend-checks (push) Has been skipped
testing / frontend-checks (push) Has been skipped
testing / test-unit (push) Has been skipped
testing / test-e2e (push) Has been skipped
testing / test-mysql (push) Has been skipped
testing / test-pgsql (push) Has been skipped
testing / test-sqlite (push) Has been skipped
testing / test-remote-cacher (redis) (push) Has been skipped
testing / test-remote-cacher (valkey) (push) Has been skipped
testing / test-remote-cacher (garnet) (push) Has been skipped
testing / test-remote-cacher (redict) (push) Has been skipped
testing / security-check (push) Has been skipped
Closes: https://codeberg.org/forgejo/forgejo/issues/6042 Continuation of: https://codeberg.org/forgejo/forgejo/pulls/6284 Replaces: https://codeberg.org/forgejo/forgejo/pulls/6285 Context: https://codeberg.org/forgejo/forgejo/pulls/6284#issuecomment-2518599 Create a new type of comment: `CommentTypeAggregator` Replaces the grouping of labels and review request in a single place: the comment aggregator The whole list of comments is "scanned", if they can get aggregated (diff of time < 60secs, same poster, open / close issue, add / del labels, add /del review req), they are added to the aggregator. Once needed, the list of all the aggregated comments are replaced with a single aggregated comment containing all the data required. In templates, have a specific HTML rendering part for the comment aggregator, reuse the same rendering as with the other types of comments. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6523 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Reviewed-by: Otto <otto@codeberg.org> Co-authored-by: Litchi Pi <litchi.pi@proton.me> Co-committed-by: Litchi Pi <litchi.pi@proton.me>
This commit is contained in:
parent
2c27a0f727
commit
dc7f5d6b84
8 changed files with 1264 additions and 1006 deletions
|
@ -1834,8 +1834,7 @@ func ViewIssue(ctx *context.Context) {
|
|||
ctx.Data["LatestCloseCommentID"] = latestCloseCommentID
|
||||
|
||||
// Combine multiple label assignments into a single comment
|
||||
combineLabelComments(issue)
|
||||
combineRequestReviewComments(issue)
|
||||
issues_model.CombineCommentsHistory(issue, time.Now().Unix())
|
||||
|
||||
getBranchData(ctx, issue)
|
||||
if issue.IsPull {
|
||||
|
@ -3710,194 +3709,6 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate 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
|
||||
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.CommentTypeLabel ||
|
||||
(prev != nil && prev.PosterID != cur.PosterID) ||
|
||||
(prev != nil && cur.CreatedUnix-prev.CreatedUnix >= 60) {
|
||||
if cur.Type == issues_model.CommentTypeLabel && cur.Label != nil {
|
||||
if cur.Content != "1" {
|
||||
cur.RemovedLabels = append(cur.RemovedLabels, cur.Label)
|
||||
} else {
|
||||
cur.AddedLabels = append(cur.AddedLabels, cur.Label)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if cur.Label != nil { // now cur MUST be label comment
|
||||
if prev.Type == issues_model.CommentTypeLabel { // we can combine them only prev is a label comment
|
||||
if cur.Content != "1" {
|
||||
// remove labels from the AddedLabels list if the label that was removed is already
|
||||
// in this list, and if it's not in this list, add the label to RemovedLabels
|
||||
addedAndRemoved := false
|
||||
for i, label := range prev.AddedLabels {
|
||||
if cur.Label.ID == label.ID {
|
||||
prev.AddedLabels = append(prev.AddedLabels[:i], prev.AddedLabels[i+1:]...)
|
||||
addedAndRemoved = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !addedAndRemoved {
|
||||
prev.RemovedLabels = append(prev.RemovedLabels, cur.Label)
|
||||
}
|
||||
} else {
|
||||
// remove labels from the RemovedLabels list if the label that was added is already
|
||||
// in this list, and if it's not in this list, add the label to AddedLabels
|
||||
removedAndAdded := false
|
||||
for i, label := range prev.RemovedLabels {
|
||||
if cur.Label.ID == label.ID {
|
||||
prev.RemovedLabels = append(prev.RemovedLabels[:i], prev.RemovedLabels[i+1:]...)
|
||||
removedAndAdded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !removedAndAdded {
|
||||
prev.AddedLabels = append(prev.AddedLabels, cur.Label)
|
||||
}
|
||||
}
|
||||
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--
|
||||
} else { // if prev is not a label comment, start a new group
|
||||
if cur.Content != "1" {
|
||||
cur.RemovedLabels = append(cur.RemovedLabels, cur.Label)
|
||||
} else {
|
||||
cur.AddedLabels = append(cur.AddedLabels, cur.Label)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get all teams that current user can mention
|
||||
func handleTeamMentions(ctx *context.Context) {
|
||||
if ctx.Doer == nil || !ctx.Repo.Owner.IsOrganization() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue