summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-03-22 05:26:42 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-03-22 05:26:42 +0000
commit2e0f3aaf2566db9979ca827b9d29884be8fbeee0 (patch)
tree2ed59ff21f0f38fb70e90ab7ad6e9167bb22cfe9
parent5081b0c014d8e82e7be1907441c246fbd01ca21e (diff)
feat: surface error_msg on failed task cards in UI
-rw-r--r--internal/api/task_view.go26
-rw-r--r--web/app.js10
-rw-r--r--web/style.css9
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
}
diff --git a/web/app.js b/web/app.js
index 90fcd6e..d26d051 100644
--- a/web/app.js
+++ b/web/app.js
@@ -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;