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

githubapi: Update for githubql package rename.

Follows https://github.com/shurcooL/githubv4/commit/19298c78142b5fe80c2cdaa2091bc51cbc3a94fc.
dmitshur committed 6 years ago commit c380a3d196cbdd6ee199615434e6eb4ec25c0430
Collapse all
githubapi/githubapi.go
@@ -11,11 +11,11 @@ import (
	"strings"

	"dmitri.shuralyov.com/route/github"
	"dmitri.shuralyov.com/service/change"
	githubv3 "github.com/google/go-github/github"
	"github.com/shurcooL/githubql"
	"github.com/shurcooL/githubv4"
	"github.com/shurcooL/issues"
	"github.com/shurcooL/notifications"
	"github.com/shurcooL/reactions"
	"github.com/shurcooL/users"
)
@@ -24,11 +24,11 @@ import (
// It uses notifications service, if not nil. At this time it infers the current user
// from GitHub clients (their authentication info), and cannot be used to serve multiple users.
// Both GitHub clients must use same authentication info.
//
// If router is nil, github.DotCom router is used, which links to subjects on github.com.
func NewService(clientV3 *githubv3.Client, clientV4 *githubql.Client, notifications notifications.ExternalService, router github.Router) change.Service {
func NewService(clientV3 *githubv3.Client, clientV4 *githubv4.Client, notifications notifications.ExternalService, router github.Router) change.Service {
	if router == nil {
		router = github.DotCom{}
	}
	return service{
		clV3:          clientV3,
@@ -38,11 +38,11 @@ func NewService(clientV3 *githubv3.Client, clientV4 *githubql.Client, notificati
	}
}

type service struct {
	clV3 *githubv3.Client // GitHub REST API v3 client.
	clV4 *githubql.Client // GitHub GraphQL API v4 client.
	clV4 *githubv4.Client // GitHub GraphQL API v4 client.
	rtr  github.Router

	// notifications may be nil if there's no notifications service.
	notifications notifications.ExternalService
}
@@ -54,16 +54,16 @@ func (s service) List(ctx context.Context, rs string, opt change.ListOptions) ([
	repo, err := ghRepoSpec(rs)
	if err != nil {
		// TODO: Map to 400 Bad Request HTTP error.
		return nil, err
	}
	var states []githubql.PullRequestState
	var states []githubv4.PullRequestState
	switch opt.Filter {
	case change.FilterOpen:
		states = []githubql.PullRequestState{githubql.PullRequestStateOpen}
		states = []githubv4.PullRequestState{githubv4.PullRequestStateOpen}
	case change.FilterClosedMerged:
		states = []githubql.PullRequestState{githubql.PullRequestStateClosed, githubql.PullRequestStateMerged}
		states = []githubv4.PullRequestState{githubv4.PullRequestStateClosed, githubv4.PullRequestStateMerged}
	case change.FilterAll:
		states = nil // No states to filter the PRs by.
	default:
		// TODO: Map to 400 Bad Request HTTP error.
		return nil, fmt.Errorf("invalid change.ListOptions.Filter value: %q", opt.Filter)
@@ -71,30 +71,30 @@ func (s service) List(ctx context.Context, rs string, opt change.ListOptions) ([
	var q struct {
		Repository struct {
			PullRequests struct {
				Nodes []struct {
					Number uint64
					State  githubql.PullRequestState
					State  githubv4.PullRequestState
					Title  string
					Labels struct {
						Nodes []struct {
							Name  string
							Color string
						}
					} `graphql:"labels(first:100)"`
					Author    *githubqlActor
					CreatedAt githubql.DateTime
					Author    *githubV4Actor
					CreatedAt githubv4.DateTime
					Comments  struct {
						TotalCount int
					}
				}
			} `graphql:"pullRequests(first:30,orderBy:{field:CREATED_AT,direction:DESC},states:$prStates)"`
		} `graphql:"repository(owner:$repositoryOwner,name:$repositoryName)"`
	}
	variables := map[string]interface{}{
		"repositoryOwner": githubql.String(repo.Owner),
		"repositoryName":  githubql.String(repo.Repo),
		"repositoryOwner": githubv4.String(repo.Owner),
		"repositoryName":  githubv4.String(repo.Repo),
		"prStates":        states,
	}
	err = s.clV4.Query(ctx, &q, variables)
	if err != nil {
		return nil, err
@@ -125,16 +125,16 @@ func (s service) Count(ctx context.Context, rs string, opt change.ListOptions) (
	repo, err := ghRepoSpec(rs)
	if err != nil {
		// TODO: Map to 400 Bad Request HTTP error.
		return 0, err
	}
	var states []githubql.PullRequestState
	var states []githubv4.PullRequestState
	switch opt.Filter {
	case change.FilterOpen:
		states = []githubql.PullRequestState{githubql.PullRequestStateOpen}
		states = []githubv4.PullRequestState{githubv4.PullRequestStateOpen}
	case change.FilterClosedMerged:
		states = []githubql.PullRequestState{githubql.PullRequestStateClosed, githubql.PullRequestStateMerged}
		states = []githubv4.PullRequestState{githubv4.PullRequestStateClosed, githubv4.PullRequestStateMerged}
	case change.FilterAll:
		states = nil // No states to filter the PRs by.
	default:
		// TODO: Map to 400 Bad Request HTTP error.
		return 0, fmt.Errorf("invalid change.ListOptions.Filter value: %q", opt.Filter)
@@ -145,12 +145,12 @@ func (s service) Count(ctx context.Context, rs string, opt change.ListOptions) (
				TotalCount uint64
			} `graphql:"pullRequests(states:$prStates)"`
		} `graphql:"repository(owner:$repositoryOwner,name:$repositoryName)"`
	}
	variables := map[string]interface{}{
		"repositoryOwner": githubql.String(repo.Owner),
		"repositoryName":  githubql.String(repo.Repo),
		"repositoryOwner": githubv4.String(repo.Owner),
		"repositoryName":  githubv4.String(repo.Repo),
		"prStates":        states,
	}
	err = s.clV4.Query(ctx, &q, variables)
	return q.Repository.PullRequests.TotalCount, err
}
@@ -163,14 +163,14 @@ func (s service) Get(ctx context.Context, rs string, id uint64) (change.Change,
	}
	var q struct {
		Repository struct {
			PullRequest struct {
				Number    uint64
				State     githubql.PullRequestState
				State     githubv4.PullRequestState
				Title     string
				Author    *githubqlActor
				CreatedAt githubql.DateTime
				Author    *githubV4Actor
				CreatedAt githubv4.DateTime
				Comments  struct {
					TotalCount int
				}
				Commits struct {
					TotalCount int
@@ -178,13 +178,13 @@ func (s service) Get(ctx context.Context, rs string, id uint64) (change.Change,
				ChangedFiles int
			} `graphql:"pullRequest(number:$prNumber)"`
		} `graphql:"repository(owner:$repositoryOwner,name:$repositoryName)"`
	}
	variables := map[string]interface{}{
		"repositoryOwner": githubql.String(repo.Owner),
		"repositoryName":  githubql.String(repo.Repo),
		"prNumber":        githubql.Int(id),
		"repositoryOwner": githubv4.String(repo.Owner),
		"repositoryName":  githubv4.String(repo.Repo),
		"prNumber":        githubv4.Int(id),
	}
	err = s.clV4.Query(ctx, &q, variables)
	if err != nil {
		return change.Change{}, err
	}
@@ -259,43 +259,43 @@ func (s service) ListTimeline(ctx context.Context, rs string, id uint64, opt *ch
		// TODO: Map to 400 Bad Request HTTP error.
		return nil, err
	}

	type event struct { // Common fields for all events.
		Actor     *githubqlActor
		CreatedAt githubql.DateTime
		Actor     *githubV4Actor
		CreatedAt githubv4.DateTime
	}
	var q struct {
		Repository struct {
			PullRequest struct {
				Author          *githubqlActor
				PublishedAt     githubql.DateTime
				LastEditedAt    *githubql.DateTime
				Editor          *githubqlActor
				Body            githubql.String
				Author          *githubV4Actor
				PublishedAt     githubv4.DateTime
				LastEditedAt    *githubv4.DateTime
				Editor          *githubV4Actor
				Body            githubv4.String
				ReactionGroups  reactionGroups
				ViewerCanUpdate bool

				Timeline struct {
					Nodes []struct {
						Typename     string `graphql:"__typename"`
						IssueComment struct {
							DatabaseID      uint64
							Author          *githubqlActor
							PublishedAt     githubql.DateTime
							LastEditedAt    *githubql.DateTime
							Editor          *githubqlActor
							Author          *githubV4Actor
							PublishedAt     githubv4.DateTime
							LastEditedAt    *githubv4.DateTime
							Editor          *githubV4Actor
							Body            string
							ReactionGroups  reactionGroups
							ViewerCanUpdate bool
						} `graphql:"...on IssueComment"`
						ClosedEvent struct {
							event
							Closer struct {
								Typename    string `graphql:"__typename"`
								PullRequest struct {
									State      githubql.PullRequestState
									State      githubv4.PullRequestState
									Title      string
									Repository struct {
										Owner struct{ Login string }
										Name  string
									}
@@ -334,17 +334,17 @@ func (s service) ListTimeline(ctx context.Context, rs string, id uint64, opt *ch
							}
						} `graphql:"...on UnlabeledEvent"`
						ReviewRequestedEvent struct {
							event
							RequestedReviewer struct {
								User *githubqlUser `graphql:"...on User"`
								User *githubV4User `graphql:"...on User"`
							}
						} `graphql:"...on ReviewRequestedEvent"`
						ReviewRequestRemovedEvent struct {
							event
							RequestedReviewer struct {
								User *githubqlUser `graphql:"...on User"`
								User *githubV4User `graphql:"...on User"`
							}
						} `graphql:"...on ReviewRequestRemovedEvent"`
						MergedEvent struct {
							event
							Commit struct {
@@ -367,15 +367,15 @@ func (s service) ListTimeline(ctx context.Context, rs string, id uint64, opt *ch
				// 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
						Author          *githubV4Actor
						PublishedAt     githubv4.DateTime
						LastEditedAt    *githubv4.DateTime
						Editor          *githubV4Actor
						State           githubv4.PullRequestReviewState
						Body            string
						ViewerCanUpdate bool
						Comments        struct {
							Nodes []struct {
								DatabaseID       uint64
@@ -387,16 +387,16 @@ func (s service) ListTimeline(ctx context.Context, rs string, id uint64, opt *ch
						} `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)"`
		Viewer githubqlUser
		Viewer githubV4User
	}
	variables := map[string]interface{}{
		"repositoryOwner": githubql.String(repo.Owner),
		"repositoryName":  githubql.String(repo.Repo),
		"prNumber":        githubql.Int(id),
		"repositoryOwner": githubv4.String(repo.Owner),
		"repositoryName":  githubv4.String(repo.Repo),
		"prNumber":        githubv4.Int(id),
	}
	err = s.clV4.Query(ctx, &q, variables)
	if err != nil {
		return nil, err
	}
@@ -609,31 +609,31 @@ func (s service) EditComment(ctx context.Context, rs string, id uint64, cr chang
			return change.Comment{}, err
		}
		// See if user has already reacted with that reaction.
		// If not, add it. Otherwise, remove it.
		var (
			subjectID        githubql.ID
			subjectID        githubv4.ID
			viewerHasReacted bool
			viewer           users.User
		)
		switch {
		case cr.ID == prDescriptionCommentID:
			var q struct {
				Repository struct {
					PullRequest struct {
						ID        githubql.ID
						ID        githubv4.ID
						Reactions struct {
							ViewerHasReacted bool
						} `graphql:"reactions(content:$reactionContent)"`
					} `graphql:"pullRequest(number:$prNumber)"`
				} `graphql:"repository(owner:$repositoryOwner,name:$repositoryName)"`
				Viewer githubqlUser
				Viewer githubV4User
			}
			variables := map[string]interface{}{
				"repositoryOwner": githubql.String(repo.Owner),
				"repositoryName":  githubql.String(repo.Repo),
				"prNumber":        githubql.Int(id),
				"repositoryOwner": githubv4.String(repo.Owner),
				"repositoryName":  githubv4.String(repo.Repo),
				"prNumber":        githubv4.Int(id),
				"reactionContent": reactionContent,
			}
			err = s.clV4.Query(ctx, &q, variables)
			if err != nil {
				return change.Comment{}, err
@@ -644,20 +644,20 @@ func (s service) EditComment(ctx context.Context, rs string, id uint64, cr chang
		case strings.HasPrefix(cr.ID, "c"):
			commentID := "012:IssueComment" + cr.ID[len("c"):]
			var q struct {
				Node struct {
					IssueComment struct {
						ID        githubql.ID
						ID        githubv4.ID
						Reactions struct {
							ViewerHasReacted bool
						} `graphql:"reactions(content:$reactionContent)"`
					} `graphql:"...on IssueComment"`
				} `graphql:"node(id:$commentID)"`
				Viewer githubqlUser
				Viewer githubV4User
			}
			variables := map[string]interface{}{
				"commentID":       githubql.ID(base64.StdEncoding.EncodeToString([]byte(commentID))), // HACK, TODO: Confirm StdEncoding vs URLEncoding.
				"commentID":       githubv4.ID(base64.StdEncoding.EncodeToString([]byte(commentID))), // HACK, TODO: Confirm StdEncoding vs URLEncoding.
				"reactionContent": reactionContent,
			}
			err = s.clV4.Query(ctx, &q, variables)
			if err != nil {
				return change.Comment{}, err
@@ -668,20 +668,20 @@ func (s service) EditComment(ctx context.Context, rs string, id uint64, cr chang
		case strings.HasPrefix(cr.ID, "rc"):
			commentID := "024:PullRequestReviewComment" + cr.ID[len("rc"):]
			var q struct {
				Node struct {
					PullRequestReviewComment struct {
						ID        githubql.ID
						ID        githubv4.ID
						Reactions struct {
							ViewerHasReacted bool
						} `graphql:"reactions(content:$reactionContent)"`
					} `graphql:"...on PullRequestReviewComment"`
				} `graphql:"node(id:$commentID)"`
				Viewer githubqlUser
				Viewer githubV4User
			}
			variables := map[string]interface{}{
				"commentID":       githubql.ID(base64.StdEncoding.EncodeToString([]byte(commentID))), // HACK, TODO: Confirm StdEncoding vs URLEncoding.
				"commentID":       githubv4.ID(base64.StdEncoding.EncodeToString([]byte(commentID))), // HACK, TODO: Confirm StdEncoding vs URLEncoding.
				"reactionContent": reactionContent,
			}
			err = s.clV4.Query(ctx, &q, variables)
			if err != nil {
				return change.Comment{}, err
@@ -701,11 +701,11 @@ func (s service) EditComment(ctx context.Context, rs string, id uint64, cr chang
					Subject struct {
						ReactionGroups reactionGroups
					}
				} `graphql:"addReaction(input:$input)"`
			}
			input := githubql.AddReactionInput{
			input := githubv4.AddReactionInput{
				SubjectID: subjectID,
				Content:   reactionContent,
			}
			err := s.clV4.Mutate(ctx, &m, input, nil)
			if err != nil {
@@ -719,11 +719,11 @@ func (s service) EditComment(ctx context.Context, rs string, id uint64, cr chang
					Subject struct {
						ReactionGroups reactionGroups
					}
				} `graphql:"removeReaction(input:$input)"`
			}
			input := githubql.RemoveReactionInput{
			input := githubv4.RemoveReactionInput{
				SubjectID: subjectID,
				Content:   reactionContent,
			}
			err := s.clV4.Mutate(ctx, &m, input, nil)
			if err != nil {
@@ -754,11 +754,11 @@ func ghRepoSpec(rs string) (repoSpec, error) {
		Owner: ghOwnerRepo[1],
		Repo:  ghOwnerRepo[2],
	}, nil
}

type githubqlActor struct {
type githubV4Actor struct {
	User struct {
		DatabaseID uint64
	} `graphql:"...on User"`
	Bot struct {
		DatabaseID uint64
@@ -766,11 +766,11 @@ type githubqlActor struct {
	Login     string
	AvatarURL string `graphql:"avatarUrl(size:96)"`
	URL       string
}

func ghActor(actor *githubqlActor) users.User {
func ghActor(actor *githubV4Actor) users.User {
	if actor == nil {
		return ghost // Deleted user, replace with https://github.com/ghost.
	}
	return users.User{
		UserSpec: users.UserSpec{
@@ -781,18 +781,18 @@ func ghActor(actor *githubqlActor) users.User {
		AvatarURL: actor.AvatarURL,
		HTMLURL:   actor.URL,
	}
}

type githubqlUser struct {
type githubV4User struct {
	DatabaseID uint64
	Login      string
	AvatarURL  string `graphql:"avatarUrl(size:96)"`
	URL        string
}

func ghUser(user *githubqlUser) users.User {
func ghUser(user *githubV4User) users.User {
	if user == nil {
		return ghost // Deleted user, replace with https://github.com/ghost.
	}
	return users.User{
		UserSpec: users.UserSpec{
@@ -837,38 +837,38 @@ var ghost = users.User{
	AvatarURL: "https://avatars3.githubusercontent.com/u/10137?v=4",
	HTMLURL:   "https://github.com/ghost",
}

// ghPRState converts a GitHub PullRequestState to change.State.
func ghPRState(state githubql.PullRequestState) change.State {
func ghPRState(state githubv4.PullRequestState) change.State {
	switch state {
	case githubql.PullRequestStateOpen:
	case githubv4.PullRequestStateOpen:
		return change.OpenState
	case githubql.PullRequestStateClosed:
	case githubv4.PullRequestStateClosed:
		return change.ClosedState
	case githubql.PullRequestStateMerged:
	case githubv4.PullRequestStateMerged:
		return change.MergedState
	default:
		panic("unreachable")
	}
}

// ghPRReviewState converts a GitHub PullRequestReviewState to change.ReviewState, if it's supported.
func ghPRReviewState(state githubql.PullRequestReviewState) (_ change.ReviewState, ok bool) {
func ghPRReviewState(state githubv4.PullRequestReviewState) (_ change.ReviewState, ok bool) {
	switch state {
	case githubql.PullRequestReviewStateApproved:
	case githubv4.PullRequestReviewStateApproved:
		return change.Approved, true
	case githubql.PullRequestReviewStateCommented:
	case githubv4.PullRequestReviewStateCommented:
		return change.Commented, true
	case githubql.PullRequestReviewStateChangesRequested:
	case githubv4.PullRequestReviewStateChangesRequested:
		return change.ChangesRequested, true
	case githubql.PullRequestReviewStateDismissed:
	case githubv4.PullRequestReviewStateDismissed:
		// PullRequestReviewStateDismissed are reviews that have been retroactively dismissed.
		// Display them as a regular comment review for now (we can't know the original state).
		// THINK: Consider displaying these more distinctly.
		return change.Commented, true
	case githubql.PullRequestReviewStatePending:
	case githubv4.PullRequestReviewStatePending:
		// PullRequestReviewStatePending are reviews that are pending (haven't been posted yet).
		// TODO: Consider displaying pending review comments. Figure this out
		//       when adding ability to leave reviews.
		return 0, false
	default:
@@ -883,19 +883,19 @@ func ghColor(hex string) issues.RGB {
	fmt.Sscanf(hex, "%02x%02x%02x", &c.R, &c.G, &c.B)
	return c
}

type reactionGroups []struct {
	Content githubql.ReactionContent
	Content githubv4.ReactionContent
	Users   struct {
		Nodes      []*githubqlUser
		Nodes      []*githubV4User
		TotalCount int
	} `graphql:"users(first:10)"`
	ViewerHasReacted bool
}

// ghReactions converts []githubql.ReactionGroup to []reactions.Reaction.
// ghReactions converts []githubv4.ReactionGroup to []reactions.Reaction.
func ghReactions(rgs reactionGroups, viewer users.User) []reactions.Reaction {
	var rs []reactions.Reaction
	for _, rg := range rgs {
		if rg.Users.TotalCount == 0 {
			continue
@@ -929,45 +929,45 @@ func ghReactions(rgs reactionGroups, viewer users.User) []reactions.Reaction {
		})
	}
	return rs
}

// internalizeReaction converts githubql.ReactionContent to reactions.EmojiID.
func internalizeReaction(reaction githubql.ReactionContent) reactions.EmojiID {
// internalizeReaction converts githubv4.ReactionContent to reactions.EmojiID.
func internalizeReaction(reaction githubv4.ReactionContent) reactions.EmojiID {
	switch reaction {
	case githubql.ReactionContentThumbsUp:
	case githubv4.ReactionContentThumbsUp:
		return "+1"
	case githubql.ReactionContentThumbsDown:
	case githubv4.ReactionContentThumbsDown:
		return "-1"
	case githubql.ReactionContentLaugh:
	case githubv4.ReactionContentLaugh:
		return "smile"
	case githubql.ReactionContentHooray:
	case githubv4.ReactionContentHooray:
		return "tada"
	case githubql.ReactionContentConfused:
	case githubv4.ReactionContentConfused:
		return "confused"
	case githubql.ReactionContentHeart:
	case githubv4.ReactionContentHeart:
		return "heart"
	default:
		panic("unreachable")
	}
}

// externalizeReaction converts reactions.EmojiID to githubql.ReactionContent.
func externalizeReaction(reaction reactions.EmojiID) (githubql.ReactionContent, error) {
// externalizeReaction converts reactions.EmojiID to githubv4.ReactionContent.
func externalizeReaction(reaction reactions.EmojiID) (githubv4.ReactionContent, error) {
	switch reaction {
	case "+1":
		return githubql.ReactionContentThumbsUp, nil
		return githubv4.ReactionContentThumbsUp, nil
	case "-1":
		return githubql.ReactionContentThumbsDown, nil
		return githubv4.ReactionContentThumbsDown, nil
	case "smile":
		return githubql.ReactionContentLaugh, nil
		return githubv4.ReactionContentLaugh, nil
	case "tada":
		return githubql.ReactionContentHooray, nil
		return githubv4.ReactionContentHooray, nil
	case "confused":
		return githubql.ReactionContentConfused, nil
		return githubv4.ReactionContentConfused, nil
	case "heart":
		return githubql.ReactionContentHeart, nil
		return githubv4.ReactionContentHeart, nil
	default:
		return "", fmt.Errorf("%q is an unsupported reaction", reaction)
	}
}