dmitri.shuralyov.com/service/change/...

gerritapi: display reviewer requests and removals

This change makes it possible to see the people who were requested
as reviewers, which helps understand the context for notifications
about CLs better.
dmitshur committed 2 weeks ago commit 1161aef33b4aafec8f04c40d9863324a08f5c912
gerritapi/gerritapi.go
@@ -8,10 +8,11 @@ import (
 	"net/http"
 	"os"
 	"sort"
 	"strconv"
 	"strings"
+	"time"
 	"unicode"
 
 	"dmitri.shuralyov.com/service/change"
 	"dmitri.shuralyov.com/state"
 	"github.com/andygrunwald/go-gerrit"
@@ -299,11 +300,12 @@ func (s service) ListTimeline(ctx context.Context, repo string, id uint64, opt *
 		User:      s.gerritUser(chg.Owner),
 		CreatedAt: chg.Created.Time,
 		Body:      commitMessageBody(commit.Message),
 		Editable:  false,
 	})
-	var mergedRevisionSHA string // Set to merged revision SHA when a change.MergedEvent event is encountered.
+	var mergedRevisionSHA string               // Set to merged revision SHA when a change.MergedEvent event is encountered.
+	var labelChanges = make(map[time.Time]int) // Map of times when a label was changed by someone. Time -> AccountID.
 	for idx, message := range chg.Messages {
 		if strings.HasPrefix(message.Tag, "autogenerated:") {
 		Outer:
 			switch message.Tag[len("autogenerated:"):] {
 			case "gerrit:merged":
@@ -408,10 +410,13 @@ func (s service) ListTimeline(ctx context.Context, repo string, id uint64, opt *
 		}
 		labels, body, ok := parseMessage(message.Message)
 		if !ok {
 			continue
 		}
+		if labels != "" {
+			labelChanges[message.Date.Time] = message.Author.AccountID
+		}
 		var cs []change.InlineComment
 		for file, comments := range *comments {
 			for _, c := range comments {
 				if c.Updated.Equal(message.Date.Time) {
 					cs = append(cs, change.InlineComment{
@@ -456,10 +461,42 @@ func (s service) ListTimeline(ctx context.Context, repo string, id uint64, opt *
 				SHA:     sha,
 				Subject: fmt.Sprintf("Patch Set %d", r.Number),
 			},
 		})
 	}
+	var reviewers = make(map[int]struct{}) // Set of reviewers during ReviewerUpdates iteration. Key is AccountID.
+	for _, ru := range chg.ReviewerUpdates {
+		switch ru.State {
+		case "REVIEWER":
+			reviewers[ru.Reviewer.AccountID] = struct{}{}
+			if ru.UpdatedBy.AccountID == ru.Reviewer.AccountID &&
+				labelChanges[ru.Updated.Time] == ru.UpdatedBy.AccountID {
+				// Skip because it was an implicit add-self-reviewer due to label change.
+				continue
+			}
+			timeline = append(timeline, change.TimelineItem{
+				Actor:     s.gerritUser(ru.UpdatedBy),
+				CreatedAt: ru.Updated.Time,
+				Payload: change.ReviewRequestedEvent{
+					RequestedReviewer: s.gerritUser(ru.Reviewer),
+				},
+			})
+		case "CC", "REMOVED":
+			if _, ok := reviewers[ru.Reviewer.AccountID]; !ok {
+				// Skip because they weren't a reviewer.
+				continue
+			}
+			delete(reviewers, ru.Reviewer.AccountID)
+			timeline = append(timeline, change.TimelineItem{
+				Actor:     s.gerritUser(ru.UpdatedBy),
+				CreatedAt: ru.Updated.Time,
+				Payload: change.ReviewRequestRemovedEvent{
+					RequestedReviewer: s.gerritUser(ru.Reviewer),
+				},
+			})
+		}
+	}
 	return timeline, nil
 }
 
 func parseMessage(m string) (labels string, body string, ok bool) {
 	// "Patch Set ".