From c40df54e28fe1a1cfd0fc5f834451996d96028f3 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pta2002@users.noreply.github.com>
Date: Sun, 25 Oct 2020 21:49:48 +0000
Subject: [PATCH] Group Label Changed Comments in timeline (#13304)

* Create function to group label comments

* Combine multiple label additions into one

* Group removed and added labels in the same comment

* Fix indentation on comments.tmpl

Co-authored-by: zeripath <art27@cantab.net>

Co-authored-by: zeripath <art27@cantab.net>
---
 models/issue_comment.go                       |  4 +-
 modules/templates/helper.go                   | 10 ++++
 options/locale/locale_en-US.ini               |  7 ++-
 routers/repo/issue.go                         | 46 +++++++++++++++++++
 .../repo/issue/view_content/comments.tmpl     | 10 +++-
 5 files changed, 72 insertions(+), 5 deletions(-)

diff --git a/models/issue_comment.go b/models/issue_comment.go
index 5c053ec02a..63256ddc99 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -124,7 +124,9 @@ type Comment struct {
 	IssueID          int64  `xorm:"INDEX"`
 	Issue            *Issue `xorm:"-"`
 	LabelID          int64
-	Label            *Label `xorm:"-"`
+	Label            *Label   `xorm:"-"`
+	AddedLabels      []*Label `xorm:"-"`
+	RemovedLabels    []*Label `xorm:"-"`
 	OldProjectID     int64
 	ProjectID        int64
 	OldProject       *Project `xorm:"-"`
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 63be27d987..03ec80f99c 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -343,6 +343,16 @@ func NewFuncMap() []template.FuncMap {
 			// the table is NOT sorted with this header
 			return ""
 		},
+		"RenderLabels": func(labels []*models.Label) template.HTML {
+			html := ""
+
+			for _, label := range labels {
+				html = fmt.Sprintf("%s<div class='ui label' style='color: %s; background-color: %s'>%s</div>",
+					html, label.ForegroundColor(), label.Color, RenderEmoji(label.Name))
+			}
+
+			return template.HTML(html)
+		},
 	}}
 }
 
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 9864346bac..60557aea76 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -979,8 +979,11 @@ issues.label_templates.info = No labels exist yet. Create a label with 'New Labe
 issues.label_templates.helper = Select a label set
 issues.label_templates.use = Use Label Set
 issues.label_templates.fail_to_load_file = Failed to load label template file '%s': %v
-issues.add_label_at = added the <div class="ui label" style="color: %s\; background-color: %s">%s</div> label %s
-issues.remove_label_at = removed the <div class="ui label" style="color: %s\; background-color: %s">%s</div> label %s
+issues.add_label = added the %s label %s
+issues.add_labels = added the %s labels %s
+issues.remove_label = removed the %s label %s
+issues.remove_labels = removed the %s labels %s
+issues.add_remove_labels = added %s and removed %s labels %s
 issues.add_milestone_at = `added this to the <b>%s</b> milestone %s`
 issues.add_project_at = `added this to the <b>%s</b> project %s`
 issues.change_milestone_at = `modified the milestone from <b>%s</b> to <b>%s</b> %s`
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index 835a952e5e..1763b1c1a2 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -1354,6 +1354,9 @@ func ViewIssue(ctx *context.Context) {
 		}
 	}
 
+	// Combine multiple label assignments into a single comment
+	combineLabelComments(issue)
+
 	getBranchData(ctx, issue)
 	if issue.IsPull {
 		pull := issue.PullRequest
@@ -2385,3 +2388,46 @@ func attachmentsHTML(ctx *context.Context, attachments []*models.Attachment) str
 	}
 	return attachHTML
 }
+
+func combineLabelComments(issue *models.Issue) {
+	for i := 0; i < len(issue.Comments); {
+		c := issue.Comments[i]
+		var shouldMerge bool
+		var removingCur bool
+		var prev *models.Comment
+
+		if i == 0 {
+			shouldMerge = false
+		} else {
+			prev = issue.Comments[i-1]
+			removingCur = c.Content != "1"
+
+			shouldMerge = prev.PosterID == c.PosterID && c.CreatedUnix-prev.CreatedUnix < 60 &&
+				c.Type == prev.Type
+		}
+
+		if c.Type == models.CommentTypeLabel {
+			if !shouldMerge {
+				if removingCur {
+					c.RemovedLabels = make([]*models.Label, 1)
+					c.AddedLabels = make([]*models.Label, 0)
+					c.RemovedLabels[0] = c.Label
+				} else {
+					c.RemovedLabels = make([]*models.Label, 0)
+					c.AddedLabels = make([]*models.Label, 1)
+					c.AddedLabels[0] = c.Label
+				}
+			} else {
+				if removingCur {
+					prev.RemovedLabels = append(prev.RemovedLabels, c.Label)
+				} else {
+					prev.AddedLabels = append(prev.AddedLabels, c.Label)
+				}
+				prev.CreatedUnix = c.CreatedUnix
+				issue.Comments = append(issue.Comments[:i], issue.Comments[i+1:]...)
+				continue
+			}
+		}
+		i++
+	}
+}
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index 68bb2d49f7..e37c7e7fdd 100644
--- a/templates/repo/issue/view_content/comments.tmpl
+++ b/templates/repo/issue/view_content/comments.tmpl
@@ -161,7 +161,7 @@
 			</div>
 		</div>
 	{{else if eq .Type 7}}
-		{{if .Label}}
+		{{if or .AddedLabels .RemovedLabels}}
 			<div class="timeline-item event" id="{{.HashTag}}">
 				<span class="badge">{{svg "octicon-tag"}}</span>
 				<a class="ui avatar image" href="{{.Poster.HomeLink}}">
@@ -169,7 +169,13 @@
 				</a>
 				<span class="text grey">
 					<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
-					{{if .Content}}{{$.i18n.Tr "repo.issues.add_label_at" .Label.ForegroundColor .Label.Color (.Label.Name|RenderEmoji) $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_label_at" .Label.ForegroundColor .Label.Color (.Label.Name|RenderEmoji) $createdStr | Safe}}{{end}}
+					{{if and .AddedLabels (not .RemovedLabels)}}
+						{{$.i18n.Tr (TrN $.i18n.Lang (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels") (RenderLabels .AddedLabels) $createdStr | Safe}}
+					{{else if and (not .AddedLabels) .RemovedLabels}}
+						{{$.i18n.Tr (TrN $.i18n.Lang (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels") (RenderLabels .RemovedLabels) $createdStr | Safe}}
+					{{else}}
+						{{$.i18n.Tr "repo.issues.add_remove_labels" (RenderLabels .AddedLabels) (RenderLabels .RemovedLabels) $createdStr | Safe}}
+					{{end}}
 				</span>
 			</div>
 		{{end}}