diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-03-22 05:26:42 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-03-22 05:26:42 +0000 |
| commit | 2e0f3aaf2566db9979ca827b9d29884be8fbeee0 (patch) | |
| tree | 2ed59ff21f0f38fb70e90ab7ad6e9167bb22cfe9 | |
| parent | 5081b0c014d8e82e7be1907441c246fbd01ca21e (diff) | |
feat: surface error_msg on failed task cards in UI
| -rw-r--r-- | internal/api/task_view.go | 26 | ||||
| -rw-r--r-- | web/app.js | 10 | ||||
| -rw-r--r-- | web/style.css | 9 |
3 files changed, 35 insertions, 10 deletions
diff --git a/internal/api/task_view.go b/internal/api/task_view.go index e6e7097..6a4b58e 100644 --- a/internal/api/task_view.go +++ b/internal/api/task_view.go @@ -14,28 +14,34 @@ type taskView struct { *task.Task Changestats *task.Changestats `json:"changestats,omitempty"` DeploymentStatus *deployment.Status `json:"deployment_status,omitempty"` + ErrorMsg string `json:"error_msg,omitempty"` +} + +var failedStates = map[task.State]bool{ + task.StateFailed: true, + task.StateBudgetExceeded: true, + task.StateTimedOut: true, } // enrichTask fetches the latest execution for the given task and attaches -// changestats and deployment_status fields for READY tasks. -// Non-READY tasks are returned without these extra fields. +// changestats, deployment_status, and error_msg fields. func (s *Server) enrichTask(tk *task.Task) *taskView { view := &taskView{Task: tk} - if tk.State != task.StateReady { - return view - } - exec, err := s.store.GetLatestExecution(tk.ID) if err != nil { - if err == sql.ErrNoRows { - // No execution yet — still include deployment status (empty commits). + if err == sql.ErrNoRows && tk.State == task.StateReady { view.DeploymentStatus = deployment.Check(nil, tk.RepositoryURL) } return view } - view.Changestats = exec.Changestats - view.DeploymentStatus = deployment.Check(exec.Commits, tk.RepositoryURL) + if failedStates[tk.State] && exec.ErrorMsg != "" { + view.ErrorMsg = exec.ErrorMsg + } + if tk.State == task.StateReady { + view.Changestats = exec.Changestats + view.DeploymentStatus = deployment.Check(exec.Commits, tk.RepositoryURL) + } return view } @@ -171,6 +171,16 @@ function createTaskCard(task) { card.appendChild(desc); } + // Error message for failed tasks + const FAILED_STATES = new Set(['FAILED', 'BUDGET_EXCEEDED', 'TIMED_OUT']); + if (FAILED_STATES.has(task.state) && task.error_msg) { + const errEl = document.createElement('div'); + errEl.className = 'task-error-msg'; + errEl.textContent = task.error_msg; + errEl.title = task.error_msg; + card.appendChild(errEl); + } + // Changestats badge for COMPLETED/READY tasks const CHANGESTATS_STATES = new Set(['COMPLETED', 'READY']); if (CHANGESTATS_STATES.has(task.state) && task.changestats != null) { diff --git a/web/style.css b/web/style.css index 90ceb90..96a6602 100644 --- a/web/style.css +++ b/web/style.css @@ -283,6 +283,15 @@ main { text-overflow: ellipsis; } +.task-error-msg { + font-size: 0.78rem; + color: var(--state-failed); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-top: 2px; +} + /* Run button */ .task-card-footer { display: flex; |
