Merge remote-tracking branch 'origin/graphql' into ghr-context
This commit is contained in:
commit
2389b142f7
3 changed files with 341 additions and 84 deletions
155
api/client.go
Normal file
155
api/client.go
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/user"
|
||||
"regexp"
|
||||
|
||||
"github.com/github/gh-cli/version"
|
||||
)
|
||||
|
||||
type graphQLResponse struct {
|
||||
Data interface{}
|
||||
Errors []struct {
|
||||
Message string
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
graphQL usage
|
||||
|
||||
type repoResponse struct {
|
||||
Repository struct {
|
||||
CreatedAt string
|
||||
}
|
||||
}
|
||||
|
||||
query := `query {
|
||||
repository(owner: "golang", name: "go") {
|
||||
createdAt
|
||||
}
|
||||
}`
|
||||
|
||||
variables := map[string]string{}
|
||||
|
||||
var resp repoResponse
|
||||
err := graphql(query, map[string]string{}, &resp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", resp)
|
||||
*/
|
||||
func graphQL(query string, variables map[string]string, v interface{}) error {
|
||||
url := "https://api.github.com/graphql"
|
||||
reqBody, err := json.Marshal(map[string]interface{}{"query": query, "variables": variables})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
token, err := getToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "token "+token)
|
||||
req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
req.Header.Set("User-Agent", "GitHub CLI "+version.Version)
|
||||
|
||||
debugRequest(req, string(reqBody))
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
debugResponse(resp, string(body))
|
||||
return handleResponse(resp, body, v)
|
||||
}
|
||||
|
||||
func handleResponse(resp *http.Response, body []byte, v interface{}) error {
|
||||
success := resp.StatusCode >= 200 && resp.StatusCode < 300
|
||||
|
||||
if success {
|
||||
gr := &graphQLResponse{Data: v}
|
||||
err := json.Unmarshal(body, &gr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(gr.Errors) > 0 {
|
||||
errorMessages := gr.Errors[0].Message
|
||||
for _, e := range gr.Errors[1:] {
|
||||
errorMessages += ", " + e.Message
|
||||
}
|
||||
return fmt.Errorf("graphql error: '%s'", errorMessages)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return handleHTTPError(resp, body)
|
||||
}
|
||||
|
||||
func handleHTTPError(resp *http.Response, body []byte) error {
|
||||
var message string
|
||||
var parsedBody struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
err := json.Unmarshal(body, &parsedBody)
|
||||
if err != nil {
|
||||
message = string(body)
|
||||
} else {
|
||||
message = parsedBody.Message
|
||||
}
|
||||
|
||||
return fmt.Errorf("http error, '%s' failed (%d): '%s'", resp.Request.URL, resp.StatusCode, message)
|
||||
}
|
||||
|
||||
func debugRequest(req *http.Request, body string) {
|
||||
if _, ok := os.LookupEnv("DEBUG"); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("DEBUG: GraphQL request to %s:\n %s\n\n", req.URL, body)
|
||||
}
|
||||
|
||||
func debugResponse(resp *http.Response, body string) {
|
||||
if _, ok := os.LookupEnv("DEBUG"); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("DEBUG: GraphQL response:\n%+v\n\n%s\n\n", resp, body)
|
||||
}
|
||||
|
||||
// TODO: Everything below this line will be removed when Nate's context work is complete
|
||||
func getToken() (string, error) {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(usr.HomeDir + "/.config/hub")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
r := regexp.MustCompile(`oauth_token: (\w+)`)
|
||||
token := r.FindStringSubmatch(string(content))
|
||||
return token[1], nil
|
||||
}
|
||||
161
api/queries.go
Normal file
161
api/queries.go
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/github/gh-cli/git"
|
||||
"github.com/github/gh-cli/github"
|
||||
)
|
||||
|
||||
type PullRequestsPayload struct {
|
||||
ViewerCreated []PullRequest
|
||||
ReviewRequested []PullRequest
|
||||
CurrentPR *PullRequest
|
||||
}
|
||||
|
||||
type PullRequest struct {
|
||||
Number int
|
||||
Title string
|
||||
URL string
|
||||
HeadRefName string
|
||||
}
|
||||
|
||||
func PullRequests() (PullRequestsPayload, error) {
|
||||
type edges struct {
|
||||
Edges []struct {
|
||||
Node PullRequest
|
||||
}
|
||||
PageInfo struct {
|
||||
HasNextPage bool
|
||||
EndCursor string
|
||||
}
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Repository struct {
|
||||
PullRequests edges
|
||||
}
|
||||
ViewerCreated edges
|
||||
ReviewRequested edges
|
||||
}
|
||||
|
||||
query := `
|
||||
fragment pr on PullRequest {
|
||||
number
|
||||
title
|
||||
url
|
||||
headRefName
|
||||
}
|
||||
|
||||
query($owner: String!, $repo: String!, $headRefName: String!, $viewerQuery: String!, $reviewerQuery: String!, $per_page: Int = 10) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequests(headRefName: $headRefName, first: 1) {
|
||||
edges {
|
||||
node {
|
||||
...pr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
viewerCreated: search(query: $viewerQuery, type: ISSUE, first: $per_page) {
|
||||
edges {
|
||||
node {
|
||||
...pr
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
}
|
||||
reviewRequested: search(query: $reviewerQuery, type: ISSUE, first: $per_page) {
|
||||
edges {
|
||||
node {
|
||||
...pr
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
project := project()
|
||||
owner := project.Owner
|
||||
repo := project.Name
|
||||
currentBranch := currentBranch()
|
||||
|
||||
viewerQuery := fmt.Sprintf("repo:%s/%s state:open is:pr author:%s", owner, repo, currentUsername())
|
||||
reviewerQuery := fmt.Sprintf("repo:%s/%s state:open review-requested:%s", owner, repo, currentUsername())
|
||||
|
||||
variables := map[string]string{
|
||||
"viewerQuery": viewerQuery,
|
||||
"reviewerQuery": reviewerQuery,
|
||||
"owner": owner,
|
||||
"repo": repo,
|
||||
"headRefName": currentBranch,
|
||||
}
|
||||
|
||||
var resp response
|
||||
err := graphQL(query, variables, &resp)
|
||||
if err != nil {
|
||||
return PullRequestsPayload{}, err
|
||||
}
|
||||
|
||||
var viewerCreated []PullRequest
|
||||
for _, edge := range resp.ViewerCreated.Edges {
|
||||
viewerCreated = append(viewerCreated, edge.Node)
|
||||
}
|
||||
|
||||
var reviewRequested []PullRequest
|
||||
for _, edge := range resp.ReviewRequested.Edges {
|
||||
reviewRequested = append(reviewRequested, edge.Node)
|
||||
}
|
||||
|
||||
var currentPR *PullRequest
|
||||
for _, edge := range resp.Repository.PullRequests.Edges {
|
||||
currentPR = &edge.Node
|
||||
}
|
||||
|
||||
payload := PullRequestsPayload{
|
||||
viewerCreated,
|
||||
reviewRequested,
|
||||
currentPR,
|
||||
}
|
||||
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
// TODO: Everything below this line will be removed when Nate's context work is complete
|
||||
func project() github.Project {
|
||||
remotes, error := github.Remotes()
|
||||
if error != nil {
|
||||
panic(error)
|
||||
}
|
||||
|
||||
for _, remote := range remotes {
|
||||
if project, error := remote.Project(); error == nil {
|
||||
return *project
|
||||
}
|
||||
}
|
||||
|
||||
panic("Could not get the project. What is a project? I don't know, it's kind of like a git repository I think?")
|
||||
}
|
||||
|
||||
func currentBranch() string {
|
||||
currentBranch, err := git.Head()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return strings.Replace(currentBranch, "refs/heads/", "", 1)
|
||||
}
|
||||
|
||||
func currentUsername() string {
|
||||
host, err := github.CurrentConfig().DefaultHost()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return host.User
|
||||
}
|
||||
109
command/pr.go
109
command/pr.go
|
|
@ -3,8 +3,7 @@ package command
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/github/gh-cli/git"
|
||||
"github.com/github/gh-cli/github"
|
||||
"github.com/github/gh-cli/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -28,93 +27,35 @@ var prListCmd = &cobra.Command{
|
|||
Use: "list",
|
||||
Short: "List pull requests",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ExecutePr()
|
||||
err := ExecutePr()
|
||||
if err != nil {
|
||||
panic(err) // In the future this should handle the error better, but for now panic seems like a valid reaction
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
type prFilter int
|
||||
func ExecutePr() error {
|
||||
prPayload, err := api.PullRequests()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const (
|
||||
createdByViewer prFilter = iota
|
||||
reviewRequested
|
||||
)
|
||||
fmt.Printf("Current Pr\n")
|
||||
if prPayload.CurrentPR != nil {
|
||||
printPr(*prPayload.CurrentPR)
|
||||
}
|
||||
fmt.Printf("Your Prs\n")
|
||||
for _, pr := range prPayload.ViewerCreated {
|
||||
printPr(pr)
|
||||
}
|
||||
fmt.Printf("Prs you need to review\n")
|
||||
for _, pr := range prPayload.ReviewRequested {
|
||||
printPr(pr)
|
||||
}
|
||||
|
||||
func ExecutePr() {
|
||||
// prsForCurrentBranch := pullRequestsForCurrentBranch()
|
||||
prsCreatedByViewer := pullRequests(createdByViewer)
|
||||
// prsRequestingReview := pullRequests(reviewRequested)
|
||||
|
||||
fmt.Printf("🌭 count! %d\n", len(prsCreatedByViewer))
|
||||
return nil
|
||||
}
|
||||
|
||||
type searchBody struct {
|
||||
Items []github.PullRequest `json:"items"`
|
||||
}
|
||||
|
||||
func pullRequestsForCurrentBranch() []github.PullRequest {
|
||||
project := project()
|
||||
client := github.NewClient(project.Host)
|
||||
currentBranch, error := git.Head()
|
||||
if error != nil {
|
||||
panic(error)
|
||||
}
|
||||
|
||||
headWithOwner := fmt.Sprintf("%s:%s", project.Owner, currentBranch)
|
||||
filterParams := map[string]interface{}{"headWithOwner": headWithOwner}
|
||||
prs, error := client.FetchPullRequests(&project, filterParams, 10, nil)
|
||||
if error != nil {
|
||||
panic(error)
|
||||
}
|
||||
|
||||
return prs
|
||||
}
|
||||
|
||||
func pullRequests(filter prFilter) []github.PullRequest {
|
||||
project := project()
|
||||
client := github.NewClient(project.Host)
|
||||
owner := project.Owner
|
||||
name := project.Name
|
||||
user, error := client.CurrentUser()
|
||||
if error != nil {
|
||||
panic(error)
|
||||
}
|
||||
|
||||
var headers map[string]string
|
||||
var q string
|
||||
if filter == createdByViewer {
|
||||
q = fmt.Sprintf("user:%s repo:%s state:open is:pr author:%s", owner, name, user.Login)
|
||||
} else if filter == reviewRequested {
|
||||
q = fmt.Sprintf("user:%s repo:%s state:open review-requested:%s", owner, name, user.Login)
|
||||
} else {
|
||||
panic("This is not a fitler")
|
||||
}
|
||||
|
||||
data := map[string]interface{}{"q": q}
|
||||
|
||||
response, error := client.GenericAPIRequest("GET", "search/issues", data, headers, 60)
|
||||
if error != nil {
|
||||
panic(fmt.Sprintf("GenericAPIRequest failed %+v", error))
|
||||
}
|
||||
searchBody := searchBody{}
|
||||
error = response.Unmarshal(&searchBody)
|
||||
if error != nil {
|
||||
panic(fmt.Sprintf("Unmarshal failed %+v", error))
|
||||
}
|
||||
|
||||
return searchBody.Items
|
||||
}
|
||||
|
||||
func project() github.Project {
|
||||
remotes, error := github.Remotes()
|
||||
if error != nil {
|
||||
panic(error)
|
||||
}
|
||||
|
||||
for _, remote := range remotes {
|
||||
if project, error := remote.Project(); error == nil {
|
||||
return *project
|
||||
}
|
||||
}
|
||||
|
||||
panic("Could not get the project. What is a project? I don't know, it's kind of like a git repository I think?")
|
||||
func printPr(pr api.PullRequest) {
|
||||
fmt.Printf(" #%d %s [%s]\n", pr.Number, pr.Title, pr.HeadRefName)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue