Skip to content

Commit

Permalink
fix: add helper example of using the graphql query API
Browse files Browse the repository at this point in the history
  • Loading branch information
jstrachan committed Sep 16, 2019
1 parent ccf6b89 commit 8c0c9b3
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 2 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/pkg/errors v0.8.1
github.com/shurcooL/githubv4 v0.0.0-20190718010115-4ba037080260
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect
github.com/sirupsen/logrus v1.4.2 // indirect
github.com/stretchr/testify v1.3.0
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
k8s.io/apimachinery v0.0.0-20190703205208-4cfb76a8bf76
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ github.com/h2non/gock v1.0.9/go.mod h1:CZMcB0Lg5IWnr9bF79pPMg9WeV6WumxQiUJ1UvdO1
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
Expand All @@ -40,8 +41,12 @@ github.com/shurcooL/githubv4 v0.0.0-20190718010115-4ba037080260 h1:xKXiRdBUtMVp6
github.com/shurcooL/githubv4 v0.0.0-20190718010115-4ba037080260/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk=
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand All @@ -56,6 +61,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
Expand Down
2 changes: 1 addition & 1 deletion scm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ type (
Reviews ReviewService
Users UserService
Webhooks WebhookService
Query GraphQLService
GraphQL GraphQLService

// DumpResponse optionally specifies a function to
// dump the the response body for debugging purposes.
Expand Down
2 changes: 1 addition & 1 deletion scm/driver/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func New(uri string) (*scm.Client, error) {
client.Webhooks = &webhookService{client}

graphqlEndpoint := scm.UrlJoin(uri, "/graphql")
client.Query = &dynamicGraphQLClient{client, graphqlEndpoint}
client.GraphQL = &dynamicGraphQLClient{client, graphqlEndpoint}

return client.Client, nil
}
Expand Down
208 changes: 208 additions & 0 deletions scm/factory/examples/graphql/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package main

import (
"context"
"fmt"
"os"
"strings"
"time"

githubql "github.com/shurcooL/githubv4"
"github.com/sirupsen/logrus"

"github.com/jenkins-x/go-scm/scm"
"github.com/jenkins-x/go-scm/scm/factory"
"github.com/jenkins-x/go-scm/scm/factory/examples/helpers"
)

var (
// searchTimeFormat is a time.Time format string for ISO8601 which is the
// format that GitHub requires for times specified as part of a search query.
searchTimeFormat = "2006-01-02T15:04:05Z"

// FoundingYear is the year GitHub was founded. This is just used so that
// we can lower bound dates related to PRs and issues.
foundingYear, _ = time.Parse(searchTimeFormat, "2007-01-01T00:00:00Z")
)

// PullRequest holds graphql data about a PR, including its commits and their contexts.
type PullRequest struct {
Number githubql.Int
Author struct {
Login githubql.String
}
BaseRef struct {
Name githubql.String
Prefix githubql.String
}
HeadRefName githubql.String `graphql:"headRefName"`
HeadRefOID githubql.String `graphql:"headRefOid"`
Mergeable githubql.MergeableState
Repository struct {
Name githubql.String
NameWithOwner githubql.String
Owner struct {
Login githubql.String
}
}
Commits struct {
Nodes []struct {
Commit Commit
}
// Request the 'last' 4 commits hoping that one of them is the logically 'last'
// commit with OID matching HeadRefOID. If we don't find it we have to use an
// additional API token. (see the 'headContexts' func for details)
// We can't raise this too much or we could hit the limit of 50,000 nodes
// per query: https://developer.github.com/v4/guides/resource-limitations/#node-limit
} `graphql:"commits(last: 4)"`
Labels struct {
Nodes []struct {
Name githubql.String
}
} `graphql:"labels(first: 100)"`
Milestone *struct {
Title githubql.String
}
Body githubql.String
Title githubql.String
UpdatedAt githubql.DateTime
}

// Commit holds graphql data about commits and which contexts they have
type Commit struct {
Status struct {
Contexts []Context
}
OID githubql.String `graphql:"oid"`
}

// Context holds graphql response data for github contexts.
type Context struct {
Context githubql.String
Description githubql.String
State githubql.StatusState
}

type PRNode struct {
PullRequest PullRequest `graphql:"... on PullRequest"`
}

type searchQuery struct {
RateLimit struct {
Cost githubql.Int
Remaining githubql.Int
}
Search struct {
PageInfo struct {
HasNextPage githubql.Boolean
EndCursor githubql.String
}
Nodes []PRNode
} `graphql:"search(type: ISSUE, first: 100, after: $searchCursor, query: $query)"`
}

func main() {
client, err := factory.NewClientFromEnvironment()
if err != nil {
helpers.Fail(err)
return
}
args := os.Args
if len(args) < 2 {
fmt.Printf("usage: queryString")
return
}
query := args[1]

fmt.Printf("searching issues and pull requests via GraphQL query %s\n", query)

graphql := client.GraphQL
if graphql == nil {
helpers.Fail(fmt.Errorf("No GraphQL support for driver %s", client.Driver.String()))
return
}
results, err := search(client, logrus.WithField("query", query), query, time.Time{}, time.Now())
if err != nil {
helpers.Fail(err)
return
}
fmt.Printf("Found %d results\n", len(results))

for _, r := range results {
commits := []string{}
for _, commit := range r.Commits.Nodes {
commits = append(commits, string(commit.Commit.OID))
}
fmt.Printf("PR %s #%d title: %s commits: %s\n", string(r.Repository.NameWithOwner), r.Number, string(r.Title), strings.Join(commits, ", "))
}
}

func datedQuery(q string, start, end time.Time) string {
return fmt.Sprintf("%s %s", q, dateToken(start, end))
}

func floor(t time.Time) time.Time {
if t.Before(foundingYear) {
return foundingYear
}
return t
}

func search(client *scm.Client, log *logrus.Entry, q string, start, end time.Time) ([]PullRequest, error) {
start = floor(start)
end = floor(end)
log = log.WithFields(logrus.Fields{
"query": q,
"start": start.String(),
"end": end.String(),
})
requestStart := time.Now()
var cursor *githubql.String
vars := map[string]interface{}{
"query": githubql.String(datedQuery(q, start, end)),
"searchCursor": cursor,
}

var totalCost, remaining int
var ret []PullRequest
var sq searchQuery
ctx := context.Background()
for {
log.Debug("Sending query")
if err := client.GraphQL.Query(ctx, &sq, vars); err != nil {
if cursor != nil {
err = fmt.Errorf("cursor: %q, err: %v", *cursor, err)
}
return ret, err
}
totalCost += int(sq.RateLimit.Cost)
remaining = int(sq.RateLimit.Remaining)
for _, n := range sq.Search.Nodes {
ret = append(ret, n.PullRequest)
}
if !sq.Search.PageInfo.HasNextPage {
break
}
cursor = &sq.Search.PageInfo.EndCursor
vars["searchCursor"] = cursor
log = log.WithField("searchCursor", *cursor)
}
log.WithField("duration", time.Since(requestStart).String()).Debugf("GraphQL returned %d PRs and cost %d point(s). %d remaining.", len(ret), totalCost, remaining)
return ret, nil
}

// dateToken generates a GitHub search query token for the specified date range.
// See: https://help.github.com/articles/understanding-the-search-syntax/#query-for-dates
func dateToken(start, end time.Time) string {
// GitHub's GraphQL API silently fails if you provide it with an invalid time
// string.
// Dates before 1970 (unix epoch) are considered invalid.
startString, endString := "*", "*"
if start.Year() >= 1970 {
startString = start.Format(searchTimeFormat)
}
if end.Year() >= 1970 {
endString = end.Format(searchTimeFormat)
}
return fmt.Sprintf("updated:%s..%s", startString, endString)
}

0 comments on commit 8c0c9b3

Please sign in to comment.