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
157 changes: 157 additions & 0 deletions cmd/goose/filtering_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package main

import (
"testing"
"time"
)

// TestCountPRsWithHiddenOrgs tests that PRs from hidden orgs are not counted
func TestCountPRsWithHiddenOrgs(t *testing.T) {
app := &App{
incoming: []PR{
{Repository: "org1/repo1", NeedsReview: true, UpdatedAt: time.Now()},
{Repository: "org2/repo2", NeedsReview: true, UpdatedAt: time.Now()},
{Repository: "org3/repo3", NeedsReview: true, UpdatedAt: time.Now()},
},
outgoing: []PR{
{Repository: "org1/repo4", IsBlocked: true, UpdatedAt: time.Now()},
{Repository: "org2/repo5", IsBlocked: true, UpdatedAt: time.Now()},
},
hiddenOrgs: map[string]bool{
"org2": true, // Hide org2
},
hideStaleIncoming: false,
}

counts := app.countPRs()

// Should only count PRs from org1 and org3, not org2
if counts.IncomingTotal != 2 {
t.Errorf("IncomingTotal = %d, want 2 (org2 should be hidden)", counts.IncomingTotal)
}
if counts.IncomingBlocked != 2 {
t.Errorf("IncomingBlocked = %d, want 2 (org2 should be hidden)", counts.IncomingBlocked)
}
if counts.OutgoingTotal != 1 {
t.Errorf("OutgoingTotal = %d, want 1 (org2 should be hidden)", counts.OutgoingTotal)
}
if counts.OutgoingBlocked != 1 {
t.Errorf("OutgoingBlocked = %d, want 1 (org2 should be hidden)", counts.OutgoingBlocked)
}
}

// TestCountPRsWithStalePRs tests that stale PRs are not counted when hideStaleIncoming is true
func TestCountPRsWithStalePRs(t *testing.T) {
now := time.Now()
staleTime := now.Add(-100 * 24 * time.Hour) // 100 days ago
recentTime := now.Add(-1 * time.Hour) // 1 hour ago

app := &App{
incoming: []PR{
{Repository: "org1/repo1", NeedsReview: true, UpdatedAt: staleTime},
{Repository: "org1/repo2", NeedsReview: true, UpdatedAt: recentTime},
{Repository: "org2/repo3", NeedsReview: false, UpdatedAt: staleTime},
},
outgoing: []PR{
{Repository: "org1/repo4", IsBlocked: true, UpdatedAt: staleTime},
{Repository: "org1/repo5", IsBlocked: true, UpdatedAt: recentTime},
},
hiddenOrgs: map[string]bool{},
hideStaleIncoming: true, // Hide stale PRs
}

counts := app.countPRs()

// Should only count recent PRs
if counts.IncomingTotal != 1 {
t.Errorf("IncomingTotal = %d, want 1 (stale PRs should be hidden)", counts.IncomingTotal)
}
if counts.IncomingBlocked != 1 {
t.Errorf("IncomingBlocked = %d, want 1 (stale PRs should be hidden)", counts.IncomingBlocked)
}
if counts.OutgoingTotal != 1 {
t.Errorf("OutgoingTotal = %d, want 1 (stale PRs should be hidden)", counts.OutgoingTotal)
}
if counts.OutgoingBlocked != 1 {
t.Errorf("OutgoingBlocked = %d, want 1 (stale PRs should be hidden)", counts.OutgoingBlocked)
}
}

// TestCountPRsWithBothFilters tests that both filters work together
func TestCountPRsWithBothFilters(t *testing.T) {
now := time.Now()
staleTime := now.Add(-100 * 24 * time.Hour)
recentTime := now.Add(-1 * time.Hour)

app := &App{
incoming: []PR{
{Repository: "org1/repo1", NeedsReview: true, UpdatedAt: recentTime}, // Should be counted
{Repository: "org2/repo2", NeedsReview: true, UpdatedAt: recentTime}, // Hidden org
{Repository: "org3/repo3", NeedsReview: true, UpdatedAt: staleTime}, // Stale
{Repository: "org1/repo4", NeedsReview: false, UpdatedAt: recentTime}, // Not blocked
},
outgoing: []PR{
{Repository: "org1/repo5", IsBlocked: true, UpdatedAt: recentTime}, // Should be counted
{Repository: "org2/repo6", IsBlocked: true, UpdatedAt: recentTime}, // Hidden org
{Repository: "org3/repo7", IsBlocked: true, UpdatedAt: staleTime}, // Stale
},
hiddenOrgs: map[string]bool{
"org2": true,
},
hideStaleIncoming: true,
}

counts := app.countPRs()

// Should only count org1/repo1 (incoming) and org1/repo5 (outgoing)
if counts.IncomingTotal != 2 {
t.Errorf("IncomingTotal = %d, want 2", counts.IncomingTotal)
}
if counts.IncomingBlocked != 1 {
t.Errorf("IncomingBlocked = %d, want 1", counts.IncomingBlocked)
}
if counts.OutgoingTotal != 1 {
t.Errorf("OutgoingTotal = %d, want 1", counts.OutgoingTotal)
}
if counts.OutgoingBlocked != 1 {
t.Errorf("OutgoingBlocked = %d, want 1", counts.OutgoingBlocked)
}
}

// TestExtractOrgFromRepo tests the org extraction function
func TestExtractOrgFromRepo(t *testing.T) {
tests := []struct {
repo string
name string
want string
}{
{
name: "standard repo path",
repo: "microsoft/vscode",
want: "microsoft",
},
{
name: "single segment",
repo: "justarepo",
want: "justarepo",
},
{
name: "empty string",
repo: "",
want: "",
},
{
name: "nested path",
repo: "org/repo/subpath",
want: "org",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := extractOrgFromRepo(tt.repo); got != tt.want {
t.Errorf("extractOrgFromRepo(%q) = %q, want %q", tt.repo, got, tt.want)
}
})
}
}
20 changes: 20 additions & 0 deletions cmd/goose/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ import (
"golang.org/x/oauth2"
)

// extractOrgFromRepo extracts the organization name from a repository path like "org/repo".
func extractOrgFromRepo(repo string) string {
parts := strings.Split(repo, "/")
if len(parts) >= 1 {
return parts[0]
}
return ""
}

// initClients initializes GitHub and Turn API clients.
func (app *App) initClients(ctx context.Context) error {
token, err := app.token(ctx)
Expand Down Expand Up @@ -321,6 +330,17 @@ func (app *App) fetchPRsInternal(ctx context.Context, waitForTurn bool) (incomin
}
repo := strings.TrimPrefix(issue.GetRepositoryURL(), "https://api.github.com/repos/")

// Extract org and track it (but don't filter here)
org := extractOrgFromRepo(repo)
if org != "" {
app.mu.Lock()
if !app.seenOrgs[org] {
log.Printf("[ORG] Discovered new organization: %s", org)
}
app.seenOrgs[org] = true
app.mu.Unlock()
}

pr := PR{
Title: issue.GetTitle(),
URL: issue.GetHTMLURL(),
Expand Down
22 changes: 22 additions & 0 deletions cmd/goose/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
}

// App holds the application state.
type App struct {

Check failure on line 74 in cmd/goose/main.go

View workflow job for this annotation

GitHub Actions / golangci-lint

fieldalignment: struct with 304 pointer bytes could be 240 (govet)
lastSuccessfulFetch time.Time
startTime time.Time
client *github.Client
Expand All @@ -97,6 +97,8 @@
enableAutoBrowser bool
hideStaleIncoming bool
noCache bool
hiddenOrgs map[string]bool
seenOrgs map[string]bool
}

func loadCurrentUser(ctx context.Context, app *App) {
Expand Down Expand Up @@ -219,6 +221,8 @@
enableAutoBrowser: false, // Default to false for safety
browserRateLimiter: NewBrowserRateLimiter(browserOpenDelay, maxBrowserOpensMinute, maxBrowserOpensDay),
startTime: time.Now(),
seenOrgs: make(map[string]bool),
hiddenOrgs: make(map[string]bool),
}

// Load saved settings
Expand Down Expand Up @@ -626,6 +630,12 @@
}
}

// Get hidden orgs for filtering
hiddenOrgs := make(map[string]bool)
for org, hidden := range app.hiddenOrgs {
hiddenOrgs[org] = hidden
}

// Log any removed entries
removedCount := 0
for url := range app.blockedPRTimes {
Expand Down Expand Up @@ -655,6 +665,12 @@

// Check incoming PRs
for i := range incoming {
// Skip PRs from hidden orgs for notifications
org := extractOrgFromRepo(incoming[i].Repository)
if org != "" && hiddenOrgs[org] {
continue
}

if incoming[i].NeedsReview {
currentBlocked[incoming[i].URL] = true
// Track when first blocked
Expand Down Expand Up @@ -685,6 +701,12 @@

// Check outgoing PRs
for i := range outgoing {
// Skip PRs from hidden orgs for notifications
org := extractOrgFromRepo(outgoing[i].Repository)
if org != "" && hiddenOrgs[org] {
continue
}

if outgoing[i].IsBlocked {
currentBlocked[outgoing[i].URL] = true
// Track when first blocked
Expand Down
24 changes: 17 additions & 7 deletions cmd/goose/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
)

// Settings represents persistent user settings.
type Settings struct {

Check failure on line 12 in cmd/goose/settings.go

View workflow job for this annotation

GitHub Actions / golangci-lint

fieldalignment: struct with 16 pointer bytes could be 8 (govet)
EnableAudioCues bool `json:"enable_audio_cues"`
HideStale bool `json:"hide_stale"`
EnableAutoBrowser bool `json:"enable_auto_browser"`
EnableAudioCues bool `json:"enable_audio_cues"`
HideStale bool `json:"hide_stale"`
EnableAutoBrowser bool `json:"enable_auto_browser"`
HiddenOrgs map[string]bool `json:"hidden_orgs"`
}

// settingsDir returns the configuration directory for settings.
Expand All @@ -33,6 +34,7 @@
app.enableAudioCues = true
app.hideStaleIncoming = true
app.enableAutoBrowser = false
app.hiddenOrgs = make(map[string]bool)
return
}

Expand All @@ -47,6 +49,7 @@
app.enableAudioCues = true
app.hideStaleIncoming = true
app.enableAutoBrowser = false
app.hiddenOrgs = make(map[string]bool)
return
}

Expand All @@ -57,14 +60,20 @@
app.enableAudioCues = true
app.hideStaleIncoming = true
app.enableAutoBrowser = false
app.hiddenOrgs = make(map[string]bool)
return
}

app.enableAudioCues = settings.EnableAudioCues
app.hideStaleIncoming = settings.HideStale
app.enableAutoBrowser = settings.EnableAutoBrowser
log.Printf("Loaded settings: audio_cues=%v, hide_stale=%v, auto_browser=%v",
app.enableAudioCues, app.hideStaleIncoming, app.enableAutoBrowser)
if settings.HiddenOrgs != nil {
app.hiddenOrgs = settings.HiddenOrgs
} else {
app.hiddenOrgs = make(map[string]bool)
}
log.Printf("Loaded settings: audio_cues=%v, hide_stale=%v, auto_browser=%v, hidden_orgs=%d",
app.enableAudioCues, app.hideStaleIncoming, app.enableAutoBrowser, len(app.hiddenOrgs))
}

// saveSettings saves current settings to disk.
Expand All @@ -80,6 +89,7 @@
EnableAudioCues: app.enableAudioCues,
HideStale: app.hideStaleIncoming,
EnableAutoBrowser: app.enableAutoBrowser,
HiddenOrgs: app.hiddenOrgs,
}
app.mu.RUnlock()

Expand All @@ -102,6 +112,6 @@
return
}

log.Printf("Saved settings: audio_cues=%v, hide_stale=%v, auto_browser=%v",
settings.EnableAudioCues, settings.HideStale, settings.EnableAutoBrowser)
log.Printf("Saved settings: audio_cues=%v, hide_stale=%v, auto_browser=%v, hidden_orgs=%d",
settings.EnableAudioCues, settings.HideStale, settings.EnableAutoBrowser, len(settings.HiddenOrgs))
}
Loading
Loading