Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ func TestHandleGraphCommand_APIError(t *testing.T) {
handleGraphCommand(testArgs, mockClient, mockGQLClient)
})

expectedError := "Error fetching pull requests for graph:"
expectedError := "simulated API error"
if !strings.Contains(stderr, expectedError) {
t.Errorf("Expected stderr to contain '%s', got: %s", expectedError, stderr)
}
Expand Down
217 changes: 110 additions & 107 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"path/filepath"
"sort"
"strings"
"sync"
"time"

"os/user"
Expand Down Expand Up @@ -538,65 +539,19 @@ func handleAllCommand(args []string, client GitHubClient, gqlClient GraphQLClien
return
}

org, err := orgConfigFunc()
if err != nil {
org = defaultOrg
}
if orgFlag != "" {
org = orgFlag
}

prQuery := buildQuery("is:pr", login)
prSearchURL := fmt.Sprintf("search/issues?q=%s", prQuery)
if debug {
fmt.Printf("Calling GitHub API for PRs with URL: %s\n", prSearchURL)
}

prItems, err := fetchAllResults(client, prSearchURL)
if err != nil {
fmt.Fprintf(os.Stderr, "Error fetching pull requests: %v\n", err)
return
}

reviewQuery := buildReviewQuery(login)
reviewSearchURL := fmt.Sprintf("search/issues?q=%s", reviewQuery)
if debug {
fmt.Printf("Calling GitHub API for reviews with URL: %s\n", reviewSearchURL)
}

reviewItems, err := fetchAllResults(client, reviewSearchURL)
if err != nil {
fmt.Fprintf(os.Stderr, "Error fetching reviews: %v\n", err)
return
}

// Deduplicate: remove reviews that the user also authored (already in prItems)
reviewItems = deduplicateItems(prItems, reviewItems)

issueQuery := buildQuery("is:issue", login)
issueSearchURL := fmt.Sprintf("search/issues?q=%s", issueQuery)
if debug {
fmt.Printf("Calling GitHub API for issues with URL: %s\n", issueSearchURL)
}

issueItems, err := fetchAllResults(client, issueSearchURL)
if err != nil {
fmt.Fprintf(os.Stderr, "Error fetching issues: %v\n", err)
return
}
org := getEffectiveOrg()

discussionItems, err := fetchDiscussions(gqlClient, login, org, since)
results, err := fetchAllContributions(client, gqlClient, login, org, since)
if err != nil {
fmt.Fprintf(os.Stderr, "Error fetching discussions: %v\n", err)
fmt.Fprintf(os.Stderr, "%v\n", err)
return
}

if bodyOnly {

printBodies(prItems, startOfPR, endOfPR)
printBodies(reviewItems, startOfReview, endOfReview)
printBodies(issueItems, startOfIssue, endOfIssue)
printBodies(discussionItems, startOfDiscussion, endOfDiscussion)
printBodies(results.prItems, startOfPR, endOfPR)
printBodies(results.reviewItems, startOfReview, endOfReview)
printBodies(results.issueItems, startOfIssue, endOfIssue)
printBodies(results.discussionItems, startOfDiscussion, endOfDiscussion)
return
}

Expand All @@ -607,7 +562,7 @@ func handleAllCommand(args []string, client GitHubClient, gqlClient GraphQLClien
writer.Write([]string{"Type", "URL", "Title", "State"})

// Write pull requests
for _, pr := range prItems {
for _, pr := range results.prItems {
writer.Write([]string{
"Pull Request",
pr.HTMLURL + " ",
Expand All @@ -617,7 +572,7 @@ func handleAllCommand(args []string, client GitHubClient, gqlClient GraphQLClien
}

// Write reviews
for _, review := range reviewItems {
for _, review := range results.reviewItems {
writer.Write([]string{
"Review",
review.HTMLURL + " ",
Expand All @@ -627,7 +582,7 @@ func handleAllCommand(args []string, client GitHubClient, gqlClient GraphQLClien
}

// Write issues
for _, issue := range issueItems {
for _, issue := range results.issueItems {
writer.Write([]string{
"Issue",
issue.HTMLURL + " ",
Expand All @@ -637,7 +592,7 @@ func handleAllCommand(args []string, client GitHubClient, gqlClient GraphQLClien
}

// Write discussions
for _, disc := range discussionItems {
for _, disc := range results.discussionItems {
writer.Write([]string{
"Discussion",
disc.HTMLURL + " ",
Expand Down Expand Up @@ -697,60 +652,16 @@ func handleGraphCommand(args []string, client GitHubClient, gqlClient GraphQLCli
fmt.Printf("Debug: Creating graph for login '%s' in org '%s' since '%s'\n", login, org, since)
}

// Build the query for PRs within the time range
prQuery := buildQuery("is:pr", login)
prSearchURL := fmt.Sprintf("search/issues?q=%s", prQuery)

if debug {
fmt.Printf("Calling GitHub API for PRs with URL: %s\n", prSearchURL)
}

// Fetch all PRs
prItems, err := fetchAllResults(client, prSearchURL)
if err != nil {
fmt.Fprintf(os.Stderr, "Error fetching pull requests for graph: %v\n", err)
return
}

// Build the query for Reviews within the time range
reviewQuery := buildReviewQuery(login)
reviewSearchURL := fmt.Sprintf("search/issues?q=%s", reviewQuery)

if debug {
fmt.Printf("Calling GitHub API for Reviews with URL: %s\n", reviewSearchURL)
}

// Fetch all Reviews
reviewItems, err := fetchAllResults(client, reviewSearchURL)
results, err := fetchAllContributions(client, gqlClient, login, org, since)
if err != nil {
fmt.Fprintf(os.Stderr, "Error fetching reviews for graph: %v\n", err)
fmt.Fprintf(os.Stderr, "%v\n", err)
return
}

// Deduplicate: remove reviews that the user also authored
reviewItems = deduplicateItems(prItems, reviewItems)

// Build the query for Issues within the time range
issueQuery := buildQuery("is:issue", login)
issueSearchURL := fmt.Sprintf("search/issues?q=%s", issueQuery)

if debug {
fmt.Printf("Calling GitHub API for Issues with URL: %s\n", issueSearchURL)
}

// Fetch all Issues
issueItems, err := fetchAllResults(client, issueSearchURL)
if err != nil {
fmt.Fprintf(os.Stderr, "Error fetching issues for graph: %v\n", err)
return
}

// Fetch all Discussions
discussionItems, err := fetchDiscussions(gqlClient, login, org, since)
if err != nil {
fmt.Fprintf(os.Stderr, "Error fetching discussions for graph: %v\n", err)
return
}
prItems := results.prItems
reviewItems := results.reviewItems
issueItems := results.issueItems
discussionItems := results.discussionItems

// Check if there are any results to display
if len(prItems) == 0 && len(reviewItems) == 0 && len(issueItems) == 0 && len(discussionItems) == 0 {
Expand Down Expand Up @@ -1189,6 +1100,98 @@ query($query: String!, $first: Int!, $after: String) {
return allItems, nil
}

// contributionResults holds the results of fetching all contribution types concurrently.
type contributionResults struct {
prItems []GitHubItem
reviewItems []GitHubItem
issueItems []GitHubItem
discussionItems []GitHubItem
}

// fetchAllContributions fetches PRs, reviews, issues, and discussions concurrently.
func fetchAllContributions(client GitHubClient, gqlClient GraphQLClient, login, org, sinceDate string) (*contributionResults, error) {
var (
wg sync.WaitGroup
mu sync.Mutex
results contributionResults
errs []error
)

prQuery := buildQuery("is:pr", login)
prSearchURL := fmt.Sprintf("search/issues?q=%s", prQuery)

reviewQuery := buildReviewQuery(login)
reviewSearchURL := fmt.Sprintf("search/issues?q=%s", reviewQuery)

issueQuery := buildQuery("is:issue", login)
issueSearchURL := fmt.Sprintf("search/issues?q=%s", issueQuery)

if debug {
fmt.Printf("Fetching PRs, reviews, issues, and discussions concurrently for %s\n", login)
}

wg.Add(4)

go func() {
defer wg.Done()
items, err := fetchAllResults(client, prSearchURL)
mu.Lock()
defer mu.Unlock()
if err != nil {
errs = append(errs, fmt.Errorf("error fetching pull requests: %w", err))
return
}
results.prItems = items
}()

go func() {
defer wg.Done()
items, err := fetchAllResults(client, reviewSearchURL)
mu.Lock()
defer mu.Unlock()
if err != nil {
errs = append(errs, fmt.Errorf("error fetching reviews: %w", err))
return
}
results.reviewItems = items
}()

go func() {
defer wg.Done()
items, err := fetchAllResults(client, issueSearchURL)
mu.Lock()
defer mu.Unlock()
if err != nil {
errs = append(errs, fmt.Errorf("error fetching issues: %w", err))
return
}
results.issueItems = items
}()

go func() {
defer wg.Done()
items, err := fetchDiscussions(gqlClient, login, org, sinceDate)
mu.Lock()
defer mu.Unlock()
if err != nil {
errs = append(errs, fmt.Errorf("error fetching discussions: %w", err))
return
}
results.discussionItems = items
}()

wg.Wait()

if len(errs) > 0 {
return nil, errs[0]
}

// Deduplicate: remove reviews that the user also authored
results.reviewItems = deduplicateItems(results.prItems, results.reviewItems)

return &results, nil
}

func fetchAllResults(client GitHubClient, searchURL string) ([]GitHubItem, error) {
var allItems []GitHubItem
page := 1
Expand Down
11 changes: 8 additions & 3 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"os"
"strings"
"sync"
"testing"
"time"
)
Expand All @@ -19,26 +20,30 @@ type MockGitHubClient struct {
GetFunc func(path string, response interface{}) error
// GetCalls records the paths called with Get.
GetCalls []string
mu sync.Mutex
}

func (m *MockGitHubClient) Get(path string, response interface{}) error {
m.mu.Lock()
m.GetCalls = append(m.GetCalls, path)
m.mu.Unlock()
if m.GetFunc != nil {
return m.GetFunc(path, response)
}
// Default behavior: return empty response
// You might want to return specific errors or data based on path in GetFunc
return nil
}

// MockGraphQLClient simulates the GitHub GraphQL API client.
type MockGraphQLClient struct {
DoFunc func(query string, variables map[string]interface{}, response interface{}) error
DoFunc func(query string, variables map[string]interface{}, response interface{}) error
DoCalls []string
mu sync.Mutex
}

func (m *MockGraphQLClient) Do(query string, variables map[string]interface{}, response interface{}) error {
m.mu.Lock()
m.DoCalls = append(m.DoCalls, query)
m.mu.Unlock()
if m.DoFunc != nil {
return m.DoFunc(query, variables, response)
}
Expand Down