@@ -3,6 +3,8 @@ package tui
33import (
44 "fmt"
55 "strings"
6+ "sync"
7+ "time"
68
79 "github.com/charmbracelet/bubbles/help"
810 "github.com/charmbracelet/bubbles/key"
@@ -25,6 +27,68 @@ const (
2527 WorkflowRunLogsView
2628)
2729
30+ // JobsCacheEntry represents a cached job entry with timestamp
31+ type JobsCacheEntry struct {
32+ Jobs []models.Job
33+ Timestamp time.Time
34+ }
35+
36+ // JobsCache represents the jobs cache with TTL
37+ type JobsCache struct {
38+ mu sync.RWMutex
39+ entries map [int64 ]JobsCacheEntry
40+ ttl time.Duration
41+ }
42+
43+ // NewJobsCache creates a new jobs cache
44+ func NewJobsCache (ttl time.Duration ) * JobsCache {
45+ return & JobsCache {
46+ entries : make (map [int64 ]JobsCacheEntry ),
47+ ttl : ttl ,
48+ }
49+ }
50+
51+ // Get retrieves jobs from cache if not expired
52+ func (c * JobsCache ) Get (runID int64 ) ([]models.Job , bool ) {
53+ c .mu .RLock ()
54+ defer c .mu .RUnlock ()
55+
56+ entry , exists := c .entries [runID ]
57+ if ! exists {
58+ return nil , false
59+ }
60+
61+ if time .Since (entry .Timestamp ) > c .ttl {
62+ return nil , false
63+ }
64+
65+ return entry .Jobs , true
66+ }
67+
68+ // Set stores jobs in cache with current timestamp
69+ func (c * JobsCache ) Set (runID int64 , jobs []models.Job ) {
70+ c .mu .Lock ()
71+ defer c .mu .Unlock ()
72+
73+ c .entries [runID ] = JobsCacheEntry {
74+ Jobs : jobs ,
75+ Timestamp : time .Now (),
76+ }
77+ }
78+
79+ // Cleanup removes expired entries
80+ func (c * JobsCache ) Cleanup () {
81+ c .mu .Lock ()
82+ defer c .mu .Unlock ()
83+
84+ now := time .Now ()
85+ for runID , entry := range c .entries {
86+ if now .Sub (entry .Timestamp ) > c .ttl {
87+ delete (c .entries , runID )
88+ }
89+ }
90+ }
91+
2892// App represents the main application state
2993type App struct {
3094 client * github.Client
@@ -75,6 +139,12 @@ type App struct {
75139 allRunsPage int
76140 allRunsPerPage int
77141 allRunsTotal int
142+
143+ // Cache and debounce
144+ jobsCache * JobsCache
145+ debounceTimer * time.Timer
146+ pendingRunID int64
147+ debounceMutex sync.Mutex
78148}
79149
80150// NewApp creates a new TUI application
@@ -127,11 +197,21 @@ func NewApp(client *github.Client, owner, repo string) *App {
127197 workflowsPerPage : 100 ,
128198 allRunsPage : 1 ,
129199 allRunsPerPage : 100 ,
200+ jobsCache : NewJobsCache (10 * time .Minute ),
130201 }
131202}
132203
133204// Init initializes the application
134205func (a * App ) Init () tea.Cmd {
206+ // Start periodic cache cleanup
207+ go func () {
208+ ticker := time .NewTicker (5 * time .Minute )
209+ defer ticker .Stop ()
210+ for range ticker .C {
211+ a .jobsCache .Cleanup ()
212+ }
213+ }()
214+
135215 return tea .Batch (
136216 a .loadAllRunsPaginated (),
137217 tea .EnterAltScreen ,
@@ -457,11 +537,11 @@ func (a *App) updateLists(msg tea.Msg) (tea.Model, tea.Cmd) {
457537 a .allRunsList , cmd = a .allRunsList .Update (msg )
458538 cmds = append (cmds , cmd )
459539
460- // If selection changed, load jobs for the new selection
540+ // If selection changed, load jobs for the new selection with debounce
461541 if a .allRunsList .Index () != oldIndex && len (a .allRuns ) > 0 {
462542 if a .allRunsList .Index () < len (a .allRuns ) {
463543 selectedRun := a .allRuns [a .allRunsList .Index ()]
464- cmds = append ( cmds , a . loadWorkflowRunJobs (selectedRun .ID ) )
544+ a . scheduleJobsLoad (selectedRun .ID )
465545 }
466546 }
467547 case WorkflowListView :
@@ -472,11 +552,11 @@ func (a *App) updateLists(msg tea.Msg) (tea.Model, tea.Cmd) {
472552 a .runsList , cmd = a .runsList .Update (msg )
473553 cmds = append (cmds , cmd )
474554
475- // If selection changed, load jobs for the new selection
555+ // If selection changed, load jobs for the new selection with debounce
476556 if a .runsList .Index () != oldIndex && len (a .workflowRuns ) > 0 {
477557 if a .runsList .Index () < len (a .workflowRuns ) {
478558 selectedRun := a .workflowRuns [a .runsList .Index ()]
479- cmds = append ( cmds , a . loadWorkflowRunJobs (selectedRun .ID ) )
559+ a . scheduleJobsLoad (selectedRun .ID )
480560 }
481561 }
482562 }
@@ -914,12 +994,74 @@ func (a *App) loadWorkflowRunLogs(runID int64) tea.Cmd {
914994 })
915995}
916996
997+ // scheduleJobsLoad schedules a debounced jobs load
998+ func (a * App ) scheduleJobsLoad (runID int64 ) {
999+ a .debounceMutex .Lock ()
1000+ defer a .debounceMutex .Unlock ()
1001+
1002+ // キャッシュから取得を試行
1003+ if jobs , found := a .jobsCache .Get (runID ); found {
1004+ a .currentJobs = jobs
1005+ return
1006+ }
1007+
1008+ // Cancel existing timer
1009+ if a .debounceTimer != nil {
1010+ a .debounceTimer .Stop ()
1011+ }
1012+
1013+ a .pendingRunID = runID
1014+
1015+ // Set new timer
1016+ a .debounceTimer = time .AfterFunc (400 * time .Millisecond , func () {
1017+ // Execute the API call after debounce period
1018+ a .executeJobsLoad (runID )
1019+ })
1020+ }
1021+
1022+ // executeJobsLoad executes the actual jobs load
1023+ func (a * App ) executeJobsLoad (runID int64 ) {
1024+ a .debounceMutex .Lock ()
1025+ defer a .debounceMutex .Unlock ()
1026+
1027+ // Check if this is still the pending request
1028+ if a .pendingRunID != runID {
1029+ return
1030+ }
1031+
1032+ // キャッシュから再度確認(並行処理対策)
1033+ if jobs , found := a .jobsCache .Get (runID ); found {
1034+ a .currentJobs = jobs
1035+ return
1036+ }
1037+
1038+ // API呼び出し実行
1039+ go func () {
1040+ jobs , err := a .client .GetWorkflowRunJobs (a .owner , a .repo , runID )
1041+ if err == nil {
1042+ // キャッシュに保存
1043+ a .jobsCache .Set (runID , jobs )
1044+ a .currentJobs = jobs
1045+ }
1046+ }()
1047+ }
1048+
9171049func (a * App ) loadWorkflowRunJobs (runID int64 ) tea.Cmd {
9181050 return tea .Cmd (func () tea.Msg {
1051+ // キャッシュから取得を試行
1052+ if jobs , found := a .jobsCache .Get (runID ); found {
1053+ return jobsLoadedMsg {jobs : jobs }
1054+ }
1055+
1056+ // キャッシュにない場合のみAPI呼び出し
9191057 jobs , err := a .client .GetWorkflowRunJobs (a .owner , a .repo , runID )
9201058 if err != nil {
9211059 return errorMsg {err : err }
9221060 }
1061+
1062+ // キャッシュに保存
1063+ a .jobsCache .Set (runID , jobs )
1064+
9231065 return jobsLoadedMsg {jobs : jobs }
9241066 })
9251067}
0 commit comments