diff --git a/devtools/gh-nudge/internal/github/github.go b/devtools/gh-nudge/internal/github/github.go index 9c52c73..7bbcd42 100644 --- a/devtools/gh-nudge/internal/github/github.go +++ b/devtools/gh-nudge/internal/github/github.go @@ -62,7 +62,7 @@ func NewClientWithExecutor(executor CommandExecutor) *Client { const pullRequestListLimit = 100 // GetPendingPullRequests fetches pending pull requests using the gh CLI. -// It fetches open PRs created by the current user. +// It fetches open PRs created by the current user, excluding drafts. func (c *Client) GetPendingPullRequests() ([]models.PullRequest, error) { // Construct the gh command to get PR information. // This fetches open PRs authored by the current user with their title, URL, @@ -71,7 +71,7 @@ func (c *Client) GetPendingPullRequests() ([]models.PullRequest, error) { output, err := c.executor.Execute("gh", "pr", "list", "--author", "@me", "--limit", fmt.Sprintf("%d", pullRequestListLimit), - "--json", "url,title,reviewRequests,files,mergeable,headRefName,statusCheckRollup") + "--json", "url,title,reviewRequests,files,mergeable,headRefName,statusCheckRollup,isDraft") if err != nil { return nil, fmt.Errorf("failed to execute gh command: %w", err) } @@ -82,7 +82,17 @@ func (c *Client) GetPendingPullRequests() ([]models.PullRequest, error) { return nil, fmt.Errorf("failed to parse gh command output: %w", err) } - return prs, nil + // Skip draft PRs: reviewers are not expected to act on them yet, so + // nudging about them would be noise. + pending := make([]models.PullRequest, 0, len(prs)) + for _, pr := range prs { + if pr.IsDraft { + continue + } + pending = append(pending, pr) + } + + return pending, nil } // GetMergeablePullRequests fetches pull requests with no review requests. diff --git a/devtools/gh-nudge/internal/github/github_test.go b/devtools/gh-nudge/internal/github/github_test.go index efed64d..f26b554 100644 --- a/devtools/gh-nudge/internal/github/github_test.go +++ b/devtools/gh-nudge/internal/github/github_test.go @@ -276,6 +276,62 @@ func TestGetPendingPullRequests(t *testing.T) { } }) + t.Run("skips draft PRs", func(t *testing.T) { + sampleJSON := `[ + { + "title": "Draft PR", + "url": "https://github.com/org/repo/pull/1", + "files": [], + "reviewRequests": [{"__typename": "User", "login": "reviewer"}], + "isDraft": true + }, + { + "title": "Ready PR", + "url": "https://github.com/org/repo/pull/2", + "files": [], + "reviewRequests": [{"__typename": "User", "login": "reviewer"}], + "isDraft": false + } + ]` + + client := NewClientWithExecutor(&mockExecutor{output: sampleJSON}) + prs, err := client.GetPendingPullRequests() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if len(prs) != 1 { + t.Fatalf("expected 1 non-draft PR, got %d", len(prs)) + } + if prs[0].Title != "Ready PR" { + t.Errorf("expected non-draft PR to be returned, got %q", prs[0].Title) + } + }) + + t.Run("treats PRs without isDraft field as non-draft", func(t *testing.T) { + sampleJSON := `[ + { + "title": "PR without isDraft", + "url": "https://github.com/org/repo/pull/1", + "files": [], + "reviewRequests": [] + } + ]` + + client := NewClientWithExecutor(&mockExecutor{output: sampleJSON}) + prs, err := client.GetPendingPullRequests() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if len(prs) != 1 { + t.Fatalf("expected 1 PR, got %d", len(prs)) + } + if prs[0].IsDraft { + t.Error("expected PR without isDraft field to default to non-draft") + } + }) + t.Run("parses PR with multiple files", func(t *testing.T) { sampleJSON := `[ { diff --git a/devtools/gh-nudge/internal/models/pr.go b/devtools/gh-nudge/internal/models/pr.go index 4a23c90..30b9241 100644 --- a/devtools/gh-nudge/internal/models/pr.go +++ b/devtools/gh-nudge/internal/models/pr.go @@ -18,6 +18,7 @@ type PullRequest struct { Mergeable string `json:"mergeable,omitempty"` HeadRefName string `json:"headRefName,omitempty"` StatusCheckRollup []StatusCheck `json:"statusCheckRollup,omitempty"` + IsDraft bool `json:"isDraft,omitempty"` } // StatusCheck represents a single CI check or status from GitHub's statusCheckRollup.