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

githubapi: Add support for review inline comments.
dmitshur committed 2 years ago commit a8308642db459d9207a70575f35b455877cb007a
githubapi/githubapi.go
@@ -3,10 +3,11 @@ package githubapi
 
 import (
 	"context"
 	"fmt"
 	"log"
+	"sort"
 	"strings"
 
 	"dmitri.shuralyov.com/changes"
 	"github.com/google/go-github/github"
 	"github.com/shurcooL/githubql"
@@ -331,26 +332,32 @@ func (s service) ListTimeline(ctx context.Context, rs string, id uint64, opt *ch
 								OID string
 								URL string
 							}
 							MergeRefName string
 						} `graphql:"...on MergedEvent"`
-						PullRequestReview struct {
-							Author    githubqlActor
-							CreatedAt githubql.DateTime
-							State     githubql.PullRequestReviewState
-						} `graphql:"...on PullRequestReview"`
 					}
 				} `graphql:"timeline(first:100)"` // TODO: Pagination...
+
+				// Need to use PullRequest.Reviews rather than PullRequest.Timeline.PullRequestReview,
+				// because the latter is missing single-inline-reply reviews (as of 2018-02-08).
 				Reviews struct {
 					Nodes []struct {
 						DatabaseID      uint64
 						Author          githubqlActor
 						PublishedAt     githubql.DateTime
 						LastEditedAt    *githubql.DateTime
 						Editor          *githubqlActor
+						State           githubql.PullRequestReviewState
 						Body            string
 						ViewerCanUpdate bool
+						Comments        struct {
+							Nodes []struct {
+								Path             string
+								OriginalPosition int
+								Body             string
+							}
+						} `graphql:"comments(first:100)"` // TODO: Pagination... Figure out how to make pagination across 2 resource types work...
 					}
 				} `graphql:"reviews(first:100)"` // TODO: Pagination... Figure out how to make pagination across 2 resource types work...
 			} `graphql:"pullRequest(number:$prNumber)"`
 		} `graphql:"repository(owner:$repositoryOwner,name:$repositoryName)"`
 	}
@@ -412,27 +419,40 @@ func (s service) ListTimeline(ctx context.Context, rs string, id uint64, opt *ch
 			Reactions: reactions,
 			Editable:  comment.ViewerCanUpdate,
 		})
 	}
 	for _, review := range q.Repository.PullRequest.Reviews.Nodes {
-		if review.Body == "" {
-			continue
-		}
 		var edited *changes.Edited
 		if review.LastEditedAt != nil {
 			edited = &changes.Edited{
 				By: ghActor(*review.Editor),
 				At: review.LastEditedAt.Time,
 			}
 		}
-		timeline = append(timeline, changes.Comment{
+		var cs []changes.InlineComment
+		for _, comment := range review.Comments.Nodes {
+			cs = append(cs, changes.InlineComment{
+				File: comment.Path,
+				Line: comment.OriginalPosition,
+				Body: comment.Body,
+			})
+		}
+		sort.Slice(cs, func(i, j int) bool {
+			if cs[i].File == cs[j].File {
+				return cs[i].Line < cs[j].Line
+			}
+			return cs[i].File < cs[j].File
+		})
+		timeline = append(timeline, changes.Review{
 			ID:        review.DatabaseID,
 			User:      ghActor(review.Author),
 			CreatedAt: review.PublishedAt.Time,
 			Edited:    edited,
+			State:     ghPRReviewState(review.State),
 			Body:      review.Body,
 			Editable:  review.ViewerCanUpdate,
+			Comments:  cs,
 		})
 	}
 	for _, event := range q.Repository.PullRequest.Timeline.Nodes {
 		e := changes.TimelineItem{
 		//ID: 0, // TODO.
@@ -493,23 +513,10 @@ func (s service) ListTimeline(ctx context.Context, rs string, id uint64, opt *ch
 			e.Payload = changes.MergedEvent{
 				CommitID:      event.MergedEvent.Commit.OID,
 				CommitHTMLURL: event.MergedEvent.Commit.URL,
 				RefName:       event.MergedEvent.MergeRefName,
 			}
-		case "PullRequestReview":
-			switch event.PullRequestReview.State {
-			case githubql.PullRequestReviewStateApproved:
-				// TODO: Make this a thing that ListComments returns, etc. After all, it can have a non-empty body.
-				e.Payload = changes.ApprovedEvent{}
-			case githubql.PullRequestReviewStateChangesRequested:
-				// TODO: Make this a thing that ListComments returns, etc. After all, it can have a non-empty body.
-				e.Payload = changes.ChangesRequestedEvent{}
-			default:
-				continue
-			}
-			e.Actor = ghActor(event.PullRequestReview.Author)
-			e.CreatedAt = event.PullRequestReview.CreatedAt.Time
 		default:
 			continue
 		}
 		timeline = append(timeline, e)
 	}
@@ -634,10 +641,28 @@ func ghPRState(state githubql.PullRequestState) changes.State {
 	default:
 		panic("unreachable")
 	}
 }
 
+// ghPRReviewState converts a GitHub PullRequestReviewState to changes.ReviewState.
+func ghPRReviewState(state githubql.PullRequestReviewState) changes.ReviewState {
+	switch state {
+	case githubql.PullRequestReviewStateApproved:
+		return changes.Approved
+	case githubql.PullRequestReviewStateCommented:
+		return changes.Commented
+	case githubql.PullRequestReviewStateChangesRequested:
+		return changes.ChangesRequested
+	case githubql.PullRequestReviewStatePending:
+		panic("PullRequestReviewStatePending not implemented") // TODO.
+	case githubql.PullRequestReviewStateDismissed:
+		panic("PullRequestReviewStateDismissed not implemented") // TODO.
+	default:
+		panic("unreachable")
+	}
+}
+
 // ghColor converts a GitHub color hex string like "ff0000"
 // into an issues.RGB value.
 func ghColor(hex string) issues.RGB {
 	var c issues.RGB
 	fmt.Sscanf(hex, "%02x%02x%02x", &c.R, &c.G, &c.B)
timeline.go
@@ -104,9 +104,9 @@ type (
 		CommitID      string
 		CommitHTMLURL string // Optional.
 		RefName       string
 	}
 
-	// TODO: Merge into Comment or so.
+	// TODO: Remove, now that these have been merged into Review.
 	ApprovedEvent         struct{}
 	ChangesRequestedEvent struct{}
 )