Repository: google/go-github
Pull request #2135 (2021-10-21 12:40:14)

Handle 'since' in addition to 'page' for pagination response
pagination may return either 'page' or 'since' in the header. 'since' was not being handled

Fixes: #2112.

google-cla[bot]: 
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please visit <https://cla.developers.google.com/> to sign.**

Once you've signed (or fixed any issues), please reply here with `@googlebot I signed it!` and we'll verify it.

----

#### What to do if you already signed the CLA

##### Individual signers

*   It's possible we don't have your GitHub username or you're using a different email address on your commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).

##### Corporate signers

*   Your company has a Point of Contact who decides which employees are authorized to participate. Ask your POC to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the Google project maintainer to [go/cla#troubleshoot](http://go/cla#troubleshoot) ([Public version](https://opensource.google/docs/cla/#troubleshoot)).
*   The email used to register you as an authorized contributor must be the email used for the Git commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
*   The email used to register you as an authorized contributor must also be [attached to your GitHub account](https://github.com/settings/emails).
		

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogle%2Fgo-github%2Fpull%2F2135) for more info**.

<!-- need_sender_cla -->
brianlangdon: I've cleared the conflict introduced by PR #2154 would it be possible for somebody to approve and merge? @gmlewis , @wesleimp ?
gmlewis: > I've cleared the conflict introduced by PR #2154 would it be possible for somebody to approve and merge? @gmlewis , @wesleimp ?

Thank you, @brianlangdon - can you please run `go fmt` on your file and push the changes?
brianlangdon: @gmlewis, changes pushed I hope!, Thanks
gmlewis: Thank you, @brianlangdon !  LGTM.

> I've cleared the conflict introduced by PR #2154 would it be possible for somebody to approve and merge? @gmlewis , @wesleimp ?

Please note that _ALL_ other contributors to this repo are welcome to provide the second PR review/comment/approval that we need for merging.

Perhaps @Parker77 has time today to review.

Patch:
diff --git a/github/actions_workflow_runs.go b/github/actions_workflow_runs.go
index 37bc819a..2c90956c 100644
--- a/github/actions_workflow_runs.go
+++ b/github/actions_workflow_runs.go
@@ -211,6 +211,20 @@ func (s *ActionsService) GetWorkflowRunLogs(ctx context.Context, owner, repo str
 	return parsedURL, newResponse(resp), err
 }
 
+// DeleteWorkflowRun deletes a workflow run by ID.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/actions#delete-a-workflow-run
+func (s *ActionsService) DeleteWorkflowRun(ctx context.Context, owner, repo string, runID int64) (*Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/actions/runs/%v", owner, repo, runID)
+
+	req, err := s.client.NewRequest("DELETE", u, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(ctx, req, nil)
+}
+
 // DeleteWorkflowRunLogs deletes all logs for a workflow run.
 //
 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#delete-workflow-run-logs
diff --git a/github/github.go b/github/github.go
index 56f1be89..a7afd834 100644
--- a/github/github.go
+++ b/github/github.go
@@ -536,12 +536,18 @@ func (r *Response) populatePageValues() {
 			}
 
 			page := q.Get("page")
+			since := q.Get("since")
 			before := q.Get("before")
 			after := q.Get("after")
-			if page == "" && before == "" && after == "" {
+
+			if page == "" && before == "" && after == "" && since == "" {
 				continue
 			}
 
+			if since != "" {
+				page = since
+			}
+
 			for _, segment := range segments[1:] {
 				switch strings.TrimSpace(segment) {
 				case `rel="next"`:


Test patch:
diff --git a/github/actions_workflow_runs_test.go b/github/actions_workflow_runs_test.go
index fcfbd360..d3623621 100644
--- a/github/actions_workflow_runs_test.go
+++ b/github/actions_workflow_runs_test.go
@@ -334,6 +334,32 @@ func TestActionService_ListRepositoryWorkflowRuns(t *testing.T) {
 	})
 }
 
+func TestActionService_DeleteWorkflowRun(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/actions/runs/399444496", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	ctx := context.Background()
+	if _, err := client.Actions.DeleteWorkflowRun(ctx, "o", "r", 399444496); err != nil {
+		t.Errorf("DeleteWorkflowRun returned error: %v", err)
+	}
+
+	const methodName = "DeleteWorkflowRun"
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.Actions.DeleteWorkflowRun(ctx, "\n", "\n", 399444496)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Actions.DeleteWorkflowRun(ctx, "o", "r", 399444496)
+	})
+}
+
 func TestActionService_DeleteWorkflowRunLogs(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/github_test.go b/github/github_test.go
index dfa0d338..3a0c2931 100644
--- a/github/github_test.go
+++ b/github/github_test.go
@@ -648,6 +648,35 @@ func TestResponse_populatePageValues(t *testing.T) {
 	}
 }
 
+func TestResponse_populateSinceValues(t *testing.T) {
+	r := http.Response{
+		Header: http.Header{
+			"Link": {`<https://api.github.com/?since=1>; rel="first",` +
+				` <https://api.github.com/?since=2>; rel="prev",` +
+				` <https://api.github.com/?since=4>; rel="next",` +
+				` <https://api.github.com/?since=5>; rel="last"`,
+			},
+		},
+	}
+
+	response := newResponse(&r)
+	if got, want := response.FirstPage, 1; got != want {
+		t.Errorf("response.FirstPage: %v, want %v", got, want)
+	}
+	if got, want := response.PrevPage, 2; want != got {
+		t.Errorf("response.PrevPage: %v, want %v", got, want)
+	}
+	if got, want := response.NextPage, 4; want != got {
+		t.Errorf("response.NextPage: %v, want %v", got, want)
+	}
+	if got, want := response.LastPage, 5; want != got {
+		t.Errorf("response.LastPage: %v, want %v", got, want)
+	}
+	if got, want := response.NextPageToken, ""; want != got {
+		t.Errorf("response.NextPageToken: %v, want %v", got, want)
+	}
+}
+
 func TestResponse_cursorPagination(t *testing.T) {
 	r := http.Response{
 		Header: http.Header{
@@ -761,6 +790,45 @@ func TestResponse_populatePageValues_invalid(t *testing.T) {
 	}
 }
 
+func TestResponse_populateSinceValues_invalid(t *testing.T) {
+	r := http.Response{
+		Header: http.Header{
+			"Link": {`<https://api.github.com/?since=1>,` +
+				`<https://api.github.com/?since=abc>; rel="first",` +
+				`https://api.github.com/?since=2; rel="prev",` +
+				`<https://api.github.com/>; rel="next",` +
+				`<https://api.github.com/?since=>; rel="last"`,
+			},
+		},
+	}
+
+	response := newResponse(&r)
+	if got, want := response.FirstPage, 0; got != want {
+		t.Errorf("response.FirstPage: %v, want %v", got, want)
+	}
+	if got, want := response.PrevPage, 0; got != want {
+		t.Errorf("response.PrevPage: %v, want %v", got, want)
+	}
+	if got, want := response.NextPage, 0; got != want {
+		t.Errorf("response.NextPage: %v, want %v", got, want)
+	}
+	if got, want := response.LastPage, 0; got != want {
+		t.Errorf("response.LastPage: %v, want %v", got, want)
+	}
+
+	// more invalid URLs
+	r = http.Response{
+		Header: http.Header{
+			"Link": {`<https://api.github.com/%?since=2>; rel="first"`},
+		},
+	}
+
+	response = newResponse(&r)
+	if got, want := response.FirstPage, 0; got != want {
+		t.Errorf("response.FirstPage: %v, want %v", got, want)
+	}
+}
+
 func TestDo(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()