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
61 changes: 35 additions & 26 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,30 @@ func (app *App) turnData(ctx context.Context, url string, updatedAt time.Time) (
hash := sha256.Sum256([]byte(key))
cacheFile := filepath.Join(app.cacheDir, hex.EncodeToString(hash[:])[:16]+".json")

// Try to read from cache (gracefully handle all cache errors)
if data, readErr := os.ReadFile(cacheFile); readErr == nil {
var entry cacheEntry
if unmarshalErr := json.Unmarshal(data, &entry); unmarshalErr != nil {
log.Printf("Failed to unmarshal cache data for %s: %v", url, unmarshalErr)
// Remove corrupted cache file
if removeErr := os.Remove(cacheFile); removeErr != nil {
log.Printf("Failed to remove corrupted cache file: %v", removeErr)
// Skip cache if --no-cache flag is set
if !app.noCache {
// Try to read from cache (gracefully handle all cache errors)
if data, readErr := os.ReadFile(cacheFile); readErr == nil {
var entry cacheEntry
if unmarshalErr := json.Unmarshal(data, &entry); unmarshalErr != nil {
log.Printf("Failed to unmarshal cache data for %s: %v", url, unmarshalErr)
// Remove corrupted cache file
if removeErr := os.Remove(cacheFile); removeErr != nil {
log.Printf("Failed to remove corrupted cache file: %v", removeErr)
}
} else if time.Since(entry.CachedAt) < cacheTTL && entry.UpdatedAt.Equal(updatedAt) {
// Check if cache is still valid (2 hour TTL)
return entry.Data, nil
}
} else if time.Since(entry.CachedAt) < cacheTTL && entry.UpdatedAt.Equal(updatedAt) {
// Check if cache is still valid (2 hour TTL)
return entry.Data, nil
}
}

// Cache miss, fetch from API
log.Printf("Cache miss for %s, fetching from Turn API", url)
if app.noCache {
log.Printf("Cache bypassed for %s (--no-cache), fetching from Turn API", url)
} else {
log.Printf("Cache miss for %s, fetching from Turn API", url)
}

// Just try once with timeout - if Turn API fails, it's not critical
turnCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
Expand All @@ -57,20 +64,22 @@ func (app *App) turnData(ctx context.Context, url string, updatedAt time.Time) (
return nil, err
}

// Save to cache (don't fail if caching fails)
entry := cacheEntry{
Data: data,
CachedAt: time.Now(),
UpdatedAt: updatedAt,
}
if cacheData, marshalErr := json.Marshal(entry); marshalErr != nil {
log.Printf("Failed to marshal cache data for %s: %v", url, marshalErr)
} else {
// Ensure cache directory exists
if dirErr := os.MkdirAll(filepath.Dir(cacheFile), 0o700); dirErr != nil {
log.Printf("Failed to create cache directory: %v", dirErr)
} else if writeErr := os.WriteFile(cacheFile, cacheData, 0o600); writeErr != nil {
log.Printf("Failed to write cache file for %s: %v", url, writeErr)
// Save to cache (don't fail if caching fails) - skip if --no-cache is set
if !app.noCache {
entry := cacheEntry{
Data: data,
CachedAt: time.Now(),
UpdatedAt: updatedAt,
}
if cacheData, marshalErr := json.Marshal(entry); marshalErr != nil {
log.Printf("Failed to marshal cache data for %s: %v", url, marshalErr)
} else {
// Ensure cache directory exists
if dirErr := os.MkdirAll(filepath.Dir(cacheFile), 0o700); dirErr != nil {
log.Printf("Failed to create cache directory: %v", dirErr)
} else if writeErr := os.WriteFile(cacheFile, cacheData, 0o600); writeErr != nil {
log.Printf("Failed to write cache file for %s: %v", url, writeErr)
}
}
}

Expand Down
30 changes: 25 additions & 5 deletions github.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func (app *App) fetchPRs(ctx context.Context) (incoming []PR, outgoing []PR, err
}

// updatePRData updates PR data with Turn API results.
func (app *App) updatePRData(url string, needsReview bool, isOwner bool) *PR {
func (app *App) updatePRData(url string, needsReview bool, isOwner bool, actionReason string) *PR {
app.mu.Lock()
defer app.mu.Unlock()

Expand All @@ -236,6 +236,7 @@ func (app *App) updatePRData(url string, needsReview bool, isOwner bool) *PR {
if app.outgoing[i].URL == url {
app.outgoing[i].NeedsReview = needsReview
app.outgoing[i].IsBlocked = needsReview
app.outgoing[i].ActionReason = actionReason
return &app.outgoing[i]
}
}
Expand All @@ -244,6 +245,7 @@ func (app *App) updatePRData(url string, needsReview bool, isOwner bool) *PR {
for i := range app.incoming {
if app.incoming[i].URL == url {
app.incoming[i].NeedsReview = needsReview
app.incoming[i].ActionReason = actionReason
return &app.incoming[i]
}
}
Expand Down Expand Up @@ -338,22 +340,40 @@ func (app *App) fetchTurnDataAsync(ctx context.Context, issues []*github.Issue,
const minUpdateInterval = 500 * time.Millisecond

for result := range results {
// Debug logging for PR #1203 - check all responses
if strings.Contains(result.url, "1203") {
log.Printf("[TURN] DEBUG PR #1203: result.err=%v, turnData=%v", result.err, result.turnData != nil)
if result.turnData != nil {
log.Printf("[TURN] DEBUG PR #1203: PRState.UnblockAction=%v", result.turnData.PRState.UnblockAction != nil)
}
}

if result.err == nil && result.turnData != nil && result.turnData.PRState.UnblockAction != nil {
turnSuccesses++

// Check if user needs to review
// Debug logging for PR #1203
if strings.Contains(result.url, "1203") {
log.Printf("[TURN] DEBUG PR #1203: UnblockAction keys: %+v", result.turnData.PRState.UnblockAction)
}

// Check if user needs to review and get action reason
needsReview := false
if _, exists := result.turnData.PRState.UnblockAction[user]; exists {
actionReason := ""
if action, exists := result.turnData.PRState.UnblockAction[user]; exists {
needsReview = true
actionReason = action.Reason
log.Printf("[TURN] UnblockAction for %s: Reason=%q, Kind=%q", result.url, action.Reason, action.Kind)
} else {
log.Printf("[TURN] No UnblockAction found for user %s on %s", user, result.url)
}

// Update the PR in our lists
pr := app.updatePRData(result.url, needsReview, result.isOwner)
pr := app.updatePRData(result.url, needsReview, result.isOwner, actionReason)

if pr != nil {
updatesApplied++
updateBatch++
log.Printf("[TURN] Turn data received for %s (needsReview=%v)", result.url, needsReview)
log.Printf("[TURN] Turn data received for %s (needsReview=%v, actionReason=%q)", result.url, needsReview, actionReason)
// Update the specific menu item immediately
app.updatePRMenuItem(*pr)

Expand Down
19 changes: 12 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,14 @@ const (

// PR represents a pull request with metadata.
type PR struct {
UpdatedAt time.Time
Title string
URL string
Repository string
Number int
IsBlocked bool
NeedsReview bool
UpdatedAt time.Time
Title string
URL string
Repository string
ActionReason string // Action reason from Turn API when blocked
Number int
IsBlocked bool
NeedsReview bool
}

// App holds the application state.
Expand All @@ -74,12 +75,15 @@ type App struct {
turnDataLoading bool
turnDataLoaded bool
menuInitialized bool
noCache bool
}

func main() {
// Parse command line flags
var targetUser string
var noCache bool
flag.StringVar(&targetUser, "user", "", "GitHub user to query PRs for (defaults to authenticated user)")
flag.BoolVar(&noCache, "no-cache", false, "Bypass cache for debugging")
flag.Parse()

log.SetFlags(log.LstdFlags | log.Lshortfile)
Expand All @@ -104,6 +108,7 @@ func main() {
targetUser: targetUser,
prMenuItems: make(map[string]*systray.MenuItem),
sectionHeaders: make(map[string]*systray.MenuItem),
noCache: noCache,
}

log.Println("Initializing GitHub clients...")
Expand Down
14 changes: 14 additions & 0 deletions ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,17 @@ func (app *App) updatePRMenuItem(pr PR) {
if pr.NeedsReview {
title = fmt.Sprintf("• %s", title)
}

// Update tooltip with action reason
tooltip := fmt.Sprintf("%s (%s)", pr.Title, formatAge(pr.UpdatedAt))
if (pr.NeedsReview || pr.IsBlocked) && pr.ActionReason != "" {
tooltip = fmt.Sprintf("%s - %s", tooltip, pr.ActionReason)
log.Printf("[MENU] DEBUG: Updating tooltip for %s with actionReason: %q -> %q", pr.URL, pr.ActionReason, tooltip)
}

log.Printf("[MENU] Updating PR menu item for %s: '%s' -> '%s'", pr.URL, oldTitle, title)
item.SetTitle(title)
item.SetTooltip(tooltip)
} else {
log.Printf("[MENU] WARNING: Tried to update non-existent PR menu item for %s", pr.URL)
}
Expand All @@ -213,6 +222,11 @@ func (app *App) addPRMenuItem(ctx context.Context, pr PR, _ bool) {
title = fmt.Sprintf("• %s", title)
}
tooltip := fmt.Sprintf("%s (%s)", pr.Title, formatAge(pr.UpdatedAt))
// Add action reason for blocked PRs
if (pr.NeedsReview || pr.IsBlocked) && pr.ActionReason != "" {
tooltip = fmt.Sprintf("%s - %s", tooltip, pr.ActionReason)
log.Printf("[MENU] DEBUG: Setting tooltip for %s with actionReason: %q -> %q", pr.URL, pr.ActionReason, tooltip)
}

// Check if menu item already exists
if existingItem, exists := app.prMenuItems[pr.URL]; exists {
Expand Down