Skip to content

Commit 3d9bcfa

Browse files
authored
Merge pull request #44 from codeGROOVE-dev/hide-orgs
Add menu item to hide GitHub orgs
2 parents 8505578 + ef127c1 commit 3d9bcfa

File tree

5 files changed

+310
-8
lines changed

5 files changed

+310
-8
lines changed

cmd/goose/filtering_test.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
// TestCountPRsWithHiddenOrgs tests that PRs from hidden orgs are not counted
9+
func TestCountPRsWithHiddenOrgs(t *testing.T) {
10+
app := &App{
11+
incoming: []PR{
12+
{Repository: "org1/repo1", NeedsReview: true, UpdatedAt: time.Now()},
13+
{Repository: "org2/repo2", NeedsReview: true, UpdatedAt: time.Now()},
14+
{Repository: "org3/repo3", NeedsReview: true, UpdatedAt: time.Now()},
15+
},
16+
outgoing: []PR{
17+
{Repository: "org1/repo4", IsBlocked: true, UpdatedAt: time.Now()},
18+
{Repository: "org2/repo5", IsBlocked: true, UpdatedAt: time.Now()},
19+
},
20+
hiddenOrgs: map[string]bool{
21+
"org2": true, // Hide org2
22+
},
23+
hideStaleIncoming: false,
24+
}
25+
26+
counts := app.countPRs()
27+
28+
// Should only count PRs from org1 and org3, not org2
29+
if counts.IncomingTotal != 2 {
30+
t.Errorf("IncomingTotal = %d, want 2 (org2 should be hidden)", counts.IncomingTotal)
31+
}
32+
if counts.IncomingBlocked != 2 {
33+
t.Errorf("IncomingBlocked = %d, want 2 (org2 should be hidden)", counts.IncomingBlocked)
34+
}
35+
if counts.OutgoingTotal != 1 {
36+
t.Errorf("OutgoingTotal = %d, want 1 (org2 should be hidden)", counts.OutgoingTotal)
37+
}
38+
if counts.OutgoingBlocked != 1 {
39+
t.Errorf("OutgoingBlocked = %d, want 1 (org2 should be hidden)", counts.OutgoingBlocked)
40+
}
41+
}
42+
43+
// TestCountPRsWithStalePRs tests that stale PRs are not counted when hideStaleIncoming is true
44+
func TestCountPRsWithStalePRs(t *testing.T) {
45+
now := time.Now()
46+
staleTime := now.Add(-100 * 24 * time.Hour) // 100 days ago
47+
recentTime := now.Add(-1 * time.Hour) // 1 hour ago
48+
49+
app := &App{
50+
incoming: []PR{
51+
{Repository: "org1/repo1", NeedsReview: true, UpdatedAt: staleTime},
52+
{Repository: "org1/repo2", NeedsReview: true, UpdatedAt: recentTime},
53+
{Repository: "org2/repo3", NeedsReview: false, UpdatedAt: staleTime},
54+
},
55+
outgoing: []PR{
56+
{Repository: "org1/repo4", IsBlocked: true, UpdatedAt: staleTime},
57+
{Repository: "org1/repo5", IsBlocked: true, UpdatedAt: recentTime},
58+
},
59+
hiddenOrgs: map[string]bool{},
60+
hideStaleIncoming: true, // Hide stale PRs
61+
}
62+
63+
counts := app.countPRs()
64+
65+
// Should only count recent PRs
66+
if counts.IncomingTotal != 1 {
67+
t.Errorf("IncomingTotal = %d, want 1 (stale PRs should be hidden)", counts.IncomingTotal)
68+
}
69+
if counts.IncomingBlocked != 1 {
70+
t.Errorf("IncomingBlocked = %d, want 1 (stale PRs should be hidden)", counts.IncomingBlocked)
71+
}
72+
if counts.OutgoingTotal != 1 {
73+
t.Errorf("OutgoingTotal = %d, want 1 (stale PRs should be hidden)", counts.OutgoingTotal)
74+
}
75+
if counts.OutgoingBlocked != 1 {
76+
t.Errorf("OutgoingBlocked = %d, want 1 (stale PRs should be hidden)", counts.OutgoingBlocked)
77+
}
78+
}
79+
80+
// TestCountPRsWithBothFilters tests that both filters work together
81+
func TestCountPRsWithBothFilters(t *testing.T) {
82+
now := time.Now()
83+
staleTime := now.Add(-100 * 24 * time.Hour)
84+
recentTime := now.Add(-1 * time.Hour)
85+
86+
app := &App{
87+
incoming: []PR{
88+
{Repository: "org1/repo1", NeedsReview: true, UpdatedAt: recentTime}, // Should be counted
89+
{Repository: "org2/repo2", NeedsReview: true, UpdatedAt: recentTime}, // Hidden org
90+
{Repository: "org3/repo3", NeedsReview: true, UpdatedAt: staleTime}, // Stale
91+
{Repository: "org1/repo4", NeedsReview: false, UpdatedAt: recentTime}, // Not blocked
92+
},
93+
outgoing: []PR{
94+
{Repository: "org1/repo5", IsBlocked: true, UpdatedAt: recentTime}, // Should be counted
95+
{Repository: "org2/repo6", IsBlocked: true, UpdatedAt: recentTime}, // Hidden org
96+
{Repository: "org3/repo7", IsBlocked: true, UpdatedAt: staleTime}, // Stale
97+
},
98+
hiddenOrgs: map[string]bool{
99+
"org2": true,
100+
},
101+
hideStaleIncoming: true,
102+
}
103+
104+
counts := app.countPRs()
105+
106+
// Should only count org1/repo1 (incoming) and org1/repo5 (outgoing)
107+
if counts.IncomingTotal != 2 {
108+
t.Errorf("IncomingTotal = %d, want 2", counts.IncomingTotal)
109+
}
110+
if counts.IncomingBlocked != 1 {
111+
t.Errorf("IncomingBlocked = %d, want 1", counts.IncomingBlocked)
112+
}
113+
if counts.OutgoingTotal != 1 {
114+
t.Errorf("OutgoingTotal = %d, want 1", counts.OutgoingTotal)
115+
}
116+
if counts.OutgoingBlocked != 1 {
117+
t.Errorf("OutgoingBlocked = %d, want 1", counts.OutgoingBlocked)
118+
}
119+
}
120+
121+
// TestExtractOrgFromRepo tests the org extraction function
122+
func TestExtractOrgFromRepo(t *testing.T) {
123+
tests := []struct {
124+
repo string
125+
name string
126+
want string
127+
}{
128+
{
129+
name: "standard repo path",
130+
repo: "microsoft/vscode",
131+
want: "microsoft",
132+
},
133+
{
134+
name: "single segment",
135+
repo: "justarepo",
136+
want: "justarepo",
137+
},
138+
{
139+
name: "empty string",
140+
repo: "",
141+
want: "",
142+
},
143+
{
144+
name: "nested path",
145+
repo: "org/repo/subpath",
146+
want: "org",
147+
},
148+
}
149+
150+
for _, tt := range tests {
151+
t.Run(tt.name, func(t *testing.T) {
152+
if got := extractOrgFromRepo(tt.repo); got != tt.want {
153+
t.Errorf("extractOrgFromRepo(%q) = %q, want %q", tt.repo, got, tt.want)
154+
}
155+
})
156+
}
157+
}

cmd/goose/github.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ import (
2020
"golang.org/x/oauth2"
2121
)
2222

23+
// extractOrgFromRepo extracts the organization name from a repository path like "org/repo".
24+
func extractOrgFromRepo(repo string) string {
25+
parts := strings.Split(repo, "/")
26+
if len(parts) >= 1 {
27+
return parts[0]
28+
}
29+
return ""
30+
}
31+
2332
// initClients initializes GitHub and Turn API clients.
2433
func (app *App) initClients(ctx context.Context) error {
2534
token, err := app.token(ctx)
@@ -321,6 +330,17 @@ func (app *App) fetchPRsInternal(ctx context.Context, waitForTurn bool) (incomin
321330
}
322331
repo := strings.TrimPrefix(issue.GetRepositoryURL(), "https://api.github.com/repos/")
323332

333+
// Extract org and track it (but don't filter here)
334+
org := extractOrgFromRepo(repo)
335+
if org != "" {
336+
app.mu.Lock()
337+
if !app.seenOrgs[org] {
338+
log.Printf("[ORG] Discovered new organization: %s", org)
339+
}
340+
app.seenOrgs[org] = true
341+
app.mu.Unlock()
342+
}
343+
324344
pr := PR{
325345
Title: issue.GetTitle(),
326346
URL: issue.GetHTMLURL(),

cmd/goose/main.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ type App struct {
9797
enableAutoBrowser bool
9898
hideStaleIncoming bool
9999
noCache bool
100+
hiddenOrgs map[string]bool
101+
seenOrgs map[string]bool
100102
}
101103

102104
func loadCurrentUser(ctx context.Context, app *App) {
@@ -219,6 +221,8 @@ func main() {
219221
enableAutoBrowser: false, // Default to false for safety
220222
browserRateLimiter: NewBrowserRateLimiter(browserOpenDelay, maxBrowserOpensMinute, maxBrowserOpensDay),
221223
startTime: time.Now(),
224+
seenOrgs: make(map[string]bool),
225+
hiddenOrgs: make(map[string]bool),
222226
}
223227

224228
// Load saved settings
@@ -626,6 +630,12 @@ func (app *App) checkForNewlyBlockedPRs(ctx context.Context) {
626630
}
627631
}
628632

633+
// Get hidden orgs for filtering
634+
hiddenOrgs := make(map[string]bool)
635+
for org, hidden := range app.hiddenOrgs {
636+
hiddenOrgs[org] = hidden
637+
}
638+
629639
// Log any removed entries
630640
removedCount := 0
631641
for url := range app.blockedPRTimes {
@@ -655,6 +665,12 @@ func (app *App) checkForNewlyBlockedPRs(ctx context.Context) {
655665

656666
// Check incoming PRs
657667
for i := range incoming {
668+
// Skip PRs from hidden orgs for notifications
669+
org := extractOrgFromRepo(incoming[i].Repository)
670+
if org != "" && hiddenOrgs[org] {
671+
continue
672+
}
673+
658674
if incoming[i].NeedsReview {
659675
currentBlocked[incoming[i].URL] = true
660676
// Track when first blocked
@@ -685,6 +701,12 @@ func (app *App) checkForNewlyBlockedPRs(ctx context.Context) {
685701

686702
// Check outgoing PRs
687703
for i := range outgoing {
704+
// Skip PRs from hidden orgs for notifications
705+
org := extractOrgFromRepo(outgoing[i].Repository)
706+
if org != "" && hiddenOrgs[org] {
707+
continue
708+
}
709+
688710
if outgoing[i].IsBlocked {
689711
currentBlocked[outgoing[i].URL] = true
690712
// Track when first blocked

cmd/goose/settings.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import (
1010

1111
// Settings represents persistent user settings.
1212
type Settings struct {
13-
EnableAudioCues bool `json:"enable_audio_cues"`
14-
HideStale bool `json:"hide_stale"`
15-
EnableAutoBrowser bool `json:"enable_auto_browser"`
13+
EnableAudioCues bool `json:"enable_audio_cues"`
14+
HideStale bool `json:"hide_stale"`
15+
EnableAutoBrowser bool `json:"enable_auto_browser"`
16+
HiddenOrgs map[string]bool `json:"hidden_orgs"`
1617
}
1718

1819
// settingsDir returns the configuration directory for settings.
@@ -33,6 +34,7 @@ func (app *App) loadSettings() {
3334
app.enableAudioCues = true
3435
app.hideStaleIncoming = true
3536
app.enableAutoBrowser = false
37+
app.hiddenOrgs = make(map[string]bool)
3638
return
3739
}
3840

@@ -47,6 +49,7 @@ func (app *App) loadSettings() {
4749
app.enableAudioCues = true
4850
app.hideStaleIncoming = true
4951
app.enableAutoBrowser = false
52+
app.hiddenOrgs = make(map[string]bool)
5053
return
5154
}
5255

@@ -57,14 +60,20 @@ func (app *App) loadSettings() {
5760
app.enableAudioCues = true
5861
app.hideStaleIncoming = true
5962
app.enableAutoBrowser = false
63+
app.hiddenOrgs = make(map[string]bool)
6064
return
6165
}
6266

6367
app.enableAudioCues = settings.EnableAudioCues
6468
app.hideStaleIncoming = settings.HideStale
6569
app.enableAutoBrowser = settings.EnableAutoBrowser
66-
log.Printf("Loaded settings: audio_cues=%v, hide_stale=%v, auto_browser=%v",
67-
app.enableAudioCues, app.hideStaleIncoming, app.enableAutoBrowser)
70+
if settings.HiddenOrgs != nil {
71+
app.hiddenOrgs = settings.HiddenOrgs
72+
} else {
73+
app.hiddenOrgs = make(map[string]bool)
74+
}
75+
log.Printf("Loaded settings: audio_cues=%v, hide_stale=%v, auto_browser=%v, hidden_orgs=%d",
76+
app.enableAudioCues, app.hideStaleIncoming, app.enableAutoBrowser, len(app.hiddenOrgs))
6877
}
6978

7079
// saveSettings saves current settings to disk.
@@ -80,6 +89,7 @@ func (app *App) saveSettings() {
8089
EnableAudioCues: app.enableAudioCues,
8190
HideStale: app.hideStaleIncoming,
8291
EnableAutoBrowser: app.enableAutoBrowser,
92+
HiddenOrgs: app.hiddenOrgs,
8393
}
8494
app.mu.RUnlock()
8595

@@ -102,6 +112,6 @@ func (app *App) saveSettings() {
102112
return
103113
}
104114

105-
log.Printf("Saved settings: audio_cues=%v, hide_stale=%v, auto_browser=%v",
106-
settings.EnableAudioCues, settings.HideStale, settings.EnableAutoBrowser)
115+
log.Printf("Saved settings: audio_cues=%v, hide_stale=%v, auto_browser=%v, hidden_orgs=%d",
116+
settings.EnableAudioCues, settings.HideStale, settings.EnableAutoBrowser, len(settings.HiddenOrgs))
107117
}

0 commit comments

Comments
 (0)