-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(github): use graphql endpoint to get pull request status (#242)
This both makes multi-gitter support GitHub actions, and makes the status command much faster.
- Loading branch information
Showing
6 changed files
with
296 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package github | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
func (g *Github) makeGraphQLRequest(ctx context.Context, query string, data interface{}, res interface{}) error { | ||
type reqData struct { | ||
Query string `json:"query"` | ||
Data interface{} `json:"variables"` | ||
} | ||
rawReqData, err := json.Marshal(reqData{ | ||
Query: query, | ||
Data: data, | ||
}) | ||
if err != nil { | ||
return errors.WithMessage(err, "could not marshal graphql request") | ||
} | ||
|
||
graphQLURL := "https://api.github.com/graphql" | ||
if g.baseURL != "" { | ||
graphQLURL, err = graphQLEndpoint(g.baseURL) | ||
if err != nil { | ||
return errors.WithMessage(err, "could not get graphql endpoint") | ||
} | ||
} | ||
|
||
req, err := http.NewRequestWithContext(ctx, "POST", graphQLURL, bytes.NewBuffer(rawReqData)) | ||
if err != nil { | ||
return errors.WithMessage(err, "could not create graphql request") | ||
} | ||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", g.token)) | ||
|
||
resp, err := g.httpClient.Do(req) | ||
if err != nil { | ||
return err | ||
} | ||
defer resp.Body.Close() | ||
|
||
resultData := struct { | ||
Data json.RawMessage `json:"data"` | ||
Errors []struct { | ||
Message string `json:"message"` | ||
} `json:"errors"` | ||
}{} | ||
|
||
if err := json.NewDecoder(resp.Body).Decode(&resultData); err != nil { | ||
return errors.WithMessage(err, "could not read graphql response body") | ||
} | ||
|
||
if len(resultData.Errors) > 0 { | ||
errorsMsgs := make([]string, len(resultData.Errors)) | ||
for i := range resultData.Errors { | ||
errorsMsgs[i] = resultData.Errors[i].Message | ||
} | ||
return errors.WithMessage( | ||
errors.New(strings.Join(errorsMsgs, "\n")), | ||
"encountered error during GraphQL query", | ||
) | ||
} | ||
|
||
if err := json.Unmarshal(resultData.Data, res); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// graphQLEndpoint takes a url to a github enterprise instance (or the v3 api) and returns the url to the graphql endpoint | ||
func graphQLEndpoint(u string) (string, error) { | ||
baseEndpoint, err := url.Parse(u) | ||
if err != nil { | ||
return "", err | ||
} | ||
if !strings.HasSuffix(baseEndpoint.Path, "/") { | ||
baseEndpoint.Path += "/" | ||
} | ||
|
||
if strings.HasPrefix(baseEndpoint.Host, "api.") || | ||
strings.Contains(baseEndpoint.Host, ".api.") { | ||
baseEndpoint.Path += "graphql" | ||
} else { | ||
baseEndpoint.Path = stripSuffixIfExist(baseEndpoint.Path, "v3/") | ||
baseEndpoint.Path = stripSuffixIfExist(baseEndpoint.Path, "api/") | ||
baseEndpoint.Path += "api/graphql" | ||
} | ||
|
||
return baseEndpoint.String(), nil | ||
} | ||
|
||
type graphqlPullRequestState string | ||
|
||
const ( | ||
graphqlPullRequestStateError graphqlPullRequestState = "ERROR" | ||
graphqlPullRequestStateFailure graphqlPullRequestState = "FAILURE" | ||
graphqlPullRequestStatePending graphqlPullRequestState = "PENDING" | ||
graphqlPullRequestStateSuccess graphqlPullRequestState = "SUCCESS" | ||
) | ||
|
||
type graphqlRepo struct { | ||
PullRequests struct { | ||
Nodes []graphqlPR `json:"nodes"` | ||
} `json:"pullRequests"` | ||
} | ||
|
||
type graphqlPR struct { | ||
Number int `json:"number"` | ||
HeadRefName string `json:"headRefName"` | ||
Closed bool `json:"closed"` | ||
URL string `json:"url"` | ||
BaseRepository struct { | ||
Name string `json:"name"` | ||
Owner struct { | ||
Login string `json:"login"` | ||
} `json:"owner"` | ||
} `json:"baseRepository"` | ||
HeadRepository struct { | ||
Name string `json:"name"` | ||
Owner struct { | ||
Login string `json:"login"` | ||
} `json:"owner"` | ||
} `json:"headRepository"` | ||
Commits struct { | ||
Nodes []struct { | ||
Commit struct { | ||
StatusCheckRollup struct { | ||
State *graphqlPullRequestState `json:"state"` | ||
} `json:"statusCheckRollup"` | ||
} `json:"commit"` | ||
} `json:"nodes"` | ||
} `json:"commits"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package github | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func Test_graphQLEndpoint(t *testing.T) { | ||
tests := []struct { | ||
url string | ||
want string | ||
}{ | ||
{url: "https://github.detsbihcs.io/api/v3", want: "https://github.detsbihcs.io/api/graphql"}, | ||
{url: "https://github.detsbihcs.io/api/v3/", want: "https://github.detsbihcs.io/api/graphql"}, | ||
{url: "https://github.detsbihcs.io/api/", want: "https://github.detsbihcs.io/api/graphql"}, | ||
{url: "https://github.detsbihcs.io/", want: "https://github.detsbihcs.io/api/graphql"}, | ||
{url: "https://api.github.detsbihcs.io/", want: "https://api.github.detsbihcs.io/graphql"}, | ||
{url: "https://more.api.github.detsbihcs.io/", want: "https://more.api.github.detsbihcs.io/graphql"}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(fmt.Sprintf("url: %s", tt.url), func(t *testing.T) { | ||
if got, _ := graphQLEndpoint(tt.url); got != tt.want { | ||
t.Errorf("graphQLEndpoint() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.