Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pagination examples for RESTClient and GQLClient #102

Merged
merged 2 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
111 changes: 97 additions & 14 deletions example_gh_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package gh_test

import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"regexp"
"time"

gh "github.com/cli/go-gh"
"github.com/cli/go-gh/pkg/api"
"github.com/cli/go-gh/pkg/tableprinter"
"github.com/cli/go-gh/pkg/term"
graphql "github.com/cli/shurcooL-graphql"
"github.com/shurcooL/githubv4"
)

// Execute 'gh issue list -R cli/cli', and print the output.
Expand Down Expand Up @@ -70,29 +72,66 @@ func ExampleRESTClient_request() {
if err != nil {
log.Fatal(err)
}

// URL to cli/cli release v2.14.2 checksums.txt
assetURL := "repos/cli/cli/releases/assets/71589494"
resp, err := client.Request("GET", assetURL, nil)
response, err := client.Request(http.MethodGet, assetURL, nil)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()

defer response.Body.Close()
f, err := os.CreateTemp("", "*_checksums.txt")
if err != nil {
log.Fatal(err)
}
defer f.Close()

_, err = io.Copy(f, resp.Body)
_, err = io.Copy(f, response.Body)
if err != nil {
log.Fatal(err)
}

fmt.Printf("Asset downloaded to %s\n", f.Name())
}

// Get releases from cli/cli repository using REST API with paginated results.
func ExampleRESTClient_pagination() {
var linkRE = regexp.MustCompile(`<([^>]+)>;\s*rel="([^"]+)"`)
findNextPage := func(response *http.Response) (string, bool) {
for _, m := range linkRE.FindAllStringSubmatch(response.Header.Get("Link"), -1) {
Comment on lines +96 to +98
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought for the future: having to do this as an extension author feels a bit low-level and requires understanding of certain details in the GitHub API. I wonder if we could provide facilities to encapsulate pagination from go-gh in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed #23

if len(m) > 2 && m[2] == "next" {
return m[1], true
}
}
return "", false
}
client, err := gh.RESTClient(nil)
if err != nil {
log.Fatal(err)
}
requestPath := "repos/cli/cli/releases"
page := 1
for {
response, err := client.Request(http.MethodGet, requestPath, nil)
if err != nil {
log.Fatal(err)
}
data := []struct{ Name string }{}
decoder := json.NewDecoder(response.Body)
err = decoder.Decode(&data)
if err != nil {
log.Fatal(err)
}
if err := response.Body.Close(); err != nil {
log.Fatal(err)
}
fmt.Printf("Page: %d\n", page)
fmt.Println(data)
var hasNextPage bool
if requestPath, hasNextPage = findNextPage(response); !hasNextPage {
break
}
page++
}
}

// Query tags from cli/cli repository using GQL API.
func ExampleGQLClient_simple() {
client, err := gh.GQLClient(nil)
Expand Down Expand Up @@ -155,12 +194,12 @@ func ExampleGQLClient_advanced() {
}

// Add a star to the cli/go-gh repository using the GQL API.
func ExampleGQLClient_Mutate_simple() {
func ExampleGQLClient_mutate_simple() {
client, err := gh.GQLClient(nil)
if err != nil {
log.Fatal(err)
}
var m struct {
var mutation struct {
AddStar struct {
Starrable struct {
Repository struct {
Expand All @@ -172,16 +211,60 @@ func ExampleGQLClient_Mutate_simple() {
}
} `graphql:"addStar(input: $input)"`
}
// Note that the shurcooL/githubv4 package has defined input structs generated from the
// GraphQL schema that can be used instead of writing your own.
type AddStarInput struct {
StarrableID string `json:"starrableId"`
}
variables := map[string]interface{}{
"input": githubv4.AddStarInput{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit torn: on one hand I do appreciate that the new example has fewer dependencies, but I also did find value in this example demonstrating the use of the shurcooL library that ensures typed inputs for each GraphQL mutation. I would actually recommend the use of that library to API clients such as extensions. @mntlty: thoughts? #99

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's two use cases of what the examples here are for.

  • To show how go-gh can be used
  • To show how go-gh should be used

If it's the first, having another package when a struct will do the job isn't necessary. If it's the second, having githubv4 here introduces it to users who will likely benefit from the structure and safety it provides. They don't have to use it, but it's a big help to have the compile time checks.

Personally I learned about githubv4 when contributing to the CLI, which not everyone is fortunate enough to be able to do ❤️

Copy link
Contributor Author

@samcoe samcoe Feb 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thoughts on this are that as go-gh is meant to be used in other projects and all of our dependencies come with it so we should try hard to minimize them. If go had a way to mark a dependency for test or examples only then this wouldn't matter. This was a case I felt that having the dependency was not worth it. What I think would be a good compromise would be to add a comment to this example where the struct is defined saying that the user could alternatively use the githubv4 package where these are already defined. What do people think about that idea?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samcoe I like that compromise!

StarrableID: githubv4.NewID("R_kgDOF_MgQQ"),
"input": AddStarInput{
StarrableID: "R_kgDOF_MgQQ",
},
}
err = client.Mutate("AddStar", &m, variables)
err = client.Mutate("AddStar", &mutation, variables)
if err != nil {
log.Fatal(err)
}
fmt.Println(m.AddStar.Starrable.Repository.StargazerCount)
fmt.Println(mutation.AddStar.Starrable.Repository.StargazerCount)
}

// Query releases from cli/cli repository using GQL API with paginated results.
func ExampleGQLClient_pagination() {
client, err := gh.GQLClient(nil)
if err != nil {
log.Fatal(err)
}
var query struct {
Repository struct {
Releases struct {
Nodes []struct {
Name string
}
PageInfo struct {
HasNextPage bool
EndCursor string
}
} `graphql:"releases(first: 30, after: $endCursor)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}
variables := map[string]interface{}{
"owner": graphql.String("cli"),
"name": graphql.String("cli"),
"endCursor": (*graphql.String)(nil),
}
page := 1
for {
if err := client.Query("RepositoryReleases", &query, variables); err != nil {
log.Fatal(err)
}
fmt.Printf("Page: %d\n", page)
fmt.Println(query.Repository.Releases.Nodes)
if !query.Repository.Releases.PageInfo.HasNextPage {
break
}
variables["endCursor"] = graphql.String(query.Repository.Releases.PageInfo.EndCursor)
page++
}
}

// Get repository for the current directory.
Expand Down
3 changes: 0 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ require (
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
github.com/muesli/reflow v0.3.0
github.com/muesli/termenv v0.12.0
github.com/shurcooL/githubv4 v0.0.0-20221229060216-a8d4a561cc93
github.com/stretchr/testify v1.7.0
github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e
golang.org/x/sys v0.4.0
Expand All @@ -40,10 +39,8 @@ require (
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 // indirect
github.com/yuin/goldmark v1.4.4 // indirect
github.com/yuin/goldmark-emoji v1.0.1 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/oauth2 v0.4.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)
9 changes: 0 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
Expand Down Expand Up @@ -66,10 +65,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/shurcooL/githubv4 v0.0.0-20221229060216-a8d4a561cc93 h1:JNy04upyaTaAGVlUFAL+60/1nphmJtuTu36tLhbaqXk=
github.com/shurcooL/githubv4 v0.0.0-20221229060216-a8d4a561cc93/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 h1:B1PEwpArrNp4dkQrfxh/abbBAOZBVp0ds+fBEOUOqOc=
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
Expand All @@ -84,8 +79,6 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -106,8 +99,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down