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

gerritapi: Update implementation for new changes.Service.
dmitshur committed 2 years ago commit 3183db689a0242496e5b81d66ea4c848f7ed2773
gerritapi/gerritapi.go
@@ -6,14 +6,14 @@ import (
 	"fmt"
 	"os"
 	"sort"
 	"strings"
 	"time"
+	"unicode"
 
 	"dmitri.shuralyov.com/changes"
 	"github.com/andygrunwald/go-gerrit"
-	"github.com/shurcooL/issues"
 	"github.com/shurcooL/users"
 )
 
 // NewService creates a Gerrit-backed issues.Service using given Gerrit client.
 // client must be non-nil.
@@ -243,48 +243,126 @@ func (s service) GetDiff(ctx context.Context, _ string, id uint64, opt *changes.
 		}
 		return []byte(diff), nil
 	}
 }
 
-func (s service) ListComments(ctx context.Context, _ string, id uint64, opt *changes.ListCommentsOptions) ([]issues.Comment, error) {
+func (s service) ListTimeline(ctx context.Context, _ string, id uint64, opt *changes.ListTimelineOptions) ([]interface{}, error) {
 	// TODO: Pagination. Respect opt.Start and opt.Length, if given.
 
 	change, _, err := s.cl.Changes.GetChangeDetail(fmt.Sprint(id), nil)
 	if err != nil {
 		return nil, err
 	}
-	var comments []issues.Comment
+	var timeline []interface{}
+	{
+		timeline = append(timeline, changes.Comment{
+			ID:        0,
+			User:      s.gerritUser(change.Owner),
+			CreatedAt: time.Time(change.Created),
+			Body:      "", // THINK: Include commit message or no?
+			Editable:  false,
+		})
+	}
 	for idx, message := range change.Messages {
 		if strings.HasPrefix(message.Tag, "autogenerated:") {
 			continue
 		}
-		comments = append(comments, issues.Comment{
+		label, body, ok := parseMessage(message.Message)
+		if !ok {
+			continue
+		}
+		switch label {
+		case "Code-Review+2":
+			timeline = append(timeline, changes.TimelineItem{
+				Actor:     s.gerritUser(message.Author),
+				CreatedAt: time.Time(message.Date),
+				Payload:   changes.ApprovedEvent{},
+			})
+		case "Code-Review-2":
+			timeline = append(timeline, changes.TimelineItem{
+				Actor:     s.gerritUser(message.Author),
+				CreatedAt: time.Time(message.Date),
+				Payload:   changes.ChangesRequestedEvent{},
+			})
+		}
+		if body == "" {
+			continue
+		}
+		timeline = append(timeline, changes.Comment{
 			ID:        uint64(idx), // TODO: message.ID is not uint64; e.g., "bfba753d015916303152305cee7152ea7a112fe0".
 			User:      s.gerritUser(message.Author),
 			CreatedAt: time.Time(message.Date),
-			Body:      message.Message,
+			Body:      body,
 			Editable:  false,
 		})
 	}
-	return comments, nil
+	return timeline, nil
 }
 
-func (s service) ListEvents(ctx context.Context, _ string, id uint64, opt *changes.ListCommentsOptions) ([]issues.Event, error) {
-	// TODO.
-	return nil, nil
+func parseMessage(m string) (label string, body string, ok bool) {
+	// "Patch Set ".
+	if !strings.HasPrefix(m, "Patch Set ") {
+		return "", "", false
+	}
+	m = m[len("Patch Set "):]
+
+	// "123".
+	i := strings.IndexFunc(m, func(c rune) bool { return !unicode.IsNumber(c) })
+	if i == -1 {
+		return "", "", false
+	}
+	m = m[i:]
+
+	// ":".
+	if len(m) < 1 || m[0] != ':' {
+		return "", "", false
+	}
+	m = m[1:]
+
+	switch i = strings.IndexByte(m, '\n'); i {
+	case -1:
+		label = m
+	default:
+		label = m[:i]
+		body = m[i+1:]
+	}
+
+	if label != "" {
+		// " ".
+		if len(label) < 1 || label[0] != ' ' {
+			return "", "", false
+		}
+		label = label[1:]
+	}
+
+	if body != "" {
+		// "\n".
+		if len(body) < 1 || body[0] != '\n' {
+			return "", "", false
+		}
+		body = body[1:]
+	}
+
+	return label, body, true
 }
 
 func (s service) gerritUser(user gerrit.AccountInfo) users.User {
+	var avatarURL string
+	for _, avatar := range user.Avatars {
+		if avatar.Height == 100 {
+			avatarURL = avatar.URL
+		}
+	}
 	return users.User{
 		UserSpec: users.UserSpec{
 			ID:     uint64(user.AccountID),
 			Domain: s.domain,
 		},
 		Login: user.Name, //user.Username, // TODO.
 		Name:  user.Name,
 		//Email:     user.Email,
-		AvatarURL: fmt.Sprintf("https://%s/accounts/%d/avatar?s=96", s.domain, user.AccountID),
+		AvatarURL: avatarURL,
 	}
 }
 
 func project(repo string) string {
 	if i := strings.IndexByte(repo, '/'); i != -1 {
gerritapi/gerritapi_test.go
@@ -0,0 +1,41 @@
+package gerritapi
+
+import (
+	"testing"
+)
+
+func TestParseMessage(t *testing.T) {
+	tests := []struct {
+		in        string
+		wantLabel string
+		wantBody  string
+	}{
+		{
+			in:        "Patch Set 2: Code-Review+2",
+			wantLabel: "Code-Review+2",
+			wantBody:  "",
+		},
+		{
+			in:        "Patch Set 2: Code-Review+2\n\nThanks.",
+			wantLabel: "Code-Review+2",
+			wantBody:  "Thanks.",
+		},
+		{
+			in:        "Patch Set 1:\n\nFirst contribution — trying to get my feet wet. Please review.",
+			wantLabel: "",
+			wantBody:  "First contribution — trying to get my feet wet. Please review.",
+		},
+	}
+	for i, tc := range tests {
+		gotLabel, gotBody, ok := parseMessage(tc.in)
+		if !ok {
+			t.Fatalf("%d: not ok", i)
+		}
+		if gotLabel != tc.wantLabel {
+			t.Errorf("%d: got label: %q, want: %q", i, gotLabel, tc.wantLabel)
+		}
+		if gotBody != tc.wantBody {
+			t.Errorf("%d: got body: %q, want: %q", i, gotBody, tc.wantBody)
+		}
+	}
+}