mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: set codersdk.Task current_state during task initialization (#20692)
## Problem With the new tasks data model, a task starts with an `initializing` status. However, the API returns `current_state: null` to represent the agent state, causing the frontend to display "No message available". This PR updates `codersdk.Task` to return a `current_state` when the task is initializing with meaningful messages about what's happening during task initialization. **Previous message** <img width="2764" height="288" alt="Screenshot 2025-11-07 at 09 06 13" src="https://github.com/user-attachments/assets/feec9f15-91ca-4378-8565-5f9de062d11a" /> **New message** <img width="2726" height="226" alt="Screenshot 2025-11-12 at 11 00 15" src="https://github.com/user-attachments/assets/2f9bee3e-7ac4-4382-b1c3-1d06bbc2906e" /> ## Changes - Populate `current_state` with descriptive initialization messages when task status is `initializing` and no valid app status exists for the current build - **dbfake**: Fix `WorkspaceBuild` builder to properly handle pending/running jobs by linking tasks without requiring agent/app resources **Note:** UI Storybook changes to reflect these new messages will be addressed in a follow-up PR. Closes: https://github.com/coder/internal/issues/1063
This commit is contained in:
+79
-18
@@ -13,6 +13,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"cdr.dev/slog"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/audit"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
"github.com/coder/coder/v2/coderd/searchquery"
|
||||
"github.com/coder/coder/v2/coderd/taskname"
|
||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||
"github.com/coder/coder/v2/coderd/util/slice"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
|
||||
@@ -270,15 +272,21 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
|
||||
func taskFromDBTaskAndWorkspace(dbTask database.Task, ws codersdk.Workspace) codersdk.Task {
|
||||
var taskAgentLifecycle *codersdk.WorkspaceAgentLifecycle
|
||||
var taskAgentHealth *codersdk.WorkspaceAgentHealth
|
||||
var taskAppHealth *codersdk.WorkspaceAppHealth
|
||||
|
||||
// If we have an agent ID from the task, find the agent details in the
|
||||
// workspace.
|
||||
if dbTask.WorkspaceAgentLifecycleState.Valid {
|
||||
taskAgentLifecycle = ptr.Ref(codersdk.WorkspaceAgentLifecycle(dbTask.WorkspaceAgentLifecycleState.WorkspaceAgentLifecycleState))
|
||||
}
|
||||
if dbTask.WorkspaceAppHealth.Valid {
|
||||
taskAppHealth = ptr.Ref(codersdk.WorkspaceAppHealth(dbTask.WorkspaceAppHealth.WorkspaceAppHealth))
|
||||
}
|
||||
|
||||
// If we have an agent ID from the task, find the agent health info
|
||||
if dbTask.WorkspaceAgentID.Valid {
|
||||
findTaskAgentLoop:
|
||||
for _, resource := range ws.LatestBuild.Resources {
|
||||
for _, agent := range resource.Agents {
|
||||
if agent.ID == dbTask.WorkspaceAgentID.UUID {
|
||||
taskAgentLifecycle = &agent.LifecycleState
|
||||
taskAgentHealth = &agent.Health
|
||||
break findTaskAgentLoop
|
||||
}
|
||||
@@ -286,21 +294,7 @@ func taskFromDBTaskAndWorkspace(dbTask database.Task, ws codersdk.Workspace) cod
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore 'latest app status' if it is older than the latest build and the
|
||||
// latest build is a 'start' transition. This ensures that you don't show a
|
||||
// stale app status from a previous build. For stop transitions, there is
|
||||
// still value in showing the latest app status.
|
||||
var currentState *codersdk.TaskStateEntry
|
||||
if ws.LatestAppStatus != nil {
|
||||
if ws.LatestBuild.Transition != codersdk.WorkspaceTransitionStart || ws.LatestAppStatus.CreatedAt.After(ws.LatestBuild.CreatedAt) {
|
||||
currentState = &codersdk.TaskStateEntry{
|
||||
Timestamp: ws.LatestAppStatus.CreatedAt,
|
||||
State: codersdk.TaskState(ws.LatestAppStatus.State),
|
||||
Message: ws.LatestAppStatus.Message,
|
||||
URI: ws.LatestAppStatus.URI,
|
||||
}
|
||||
}
|
||||
}
|
||||
currentState := deriveTaskCurrentState(dbTask, ws, taskAgentLifecycle, taskAppHealth)
|
||||
|
||||
return codersdk.Task{
|
||||
ID: dbTask.ID,
|
||||
@@ -330,6 +324,73 @@ func taskFromDBTaskAndWorkspace(dbTask database.Task, ws codersdk.Workspace) cod
|
||||
}
|
||||
}
|
||||
|
||||
// deriveTaskCurrentState determines the current state of a task based on the
|
||||
// workspace's latest app status and initialization phase.
|
||||
// Returns nil if no valid state can be determined.
|
||||
func deriveTaskCurrentState(
|
||||
dbTask database.Task,
|
||||
ws codersdk.Workspace,
|
||||
taskAgentLifecycle *codersdk.WorkspaceAgentLifecycle,
|
||||
taskAppHealth *codersdk.WorkspaceAppHealth,
|
||||
) *codersdk.TaskStateEntry {
|
||||
var currentState *codersdk.TaskStateEntry
|
||||
|
||||
// Ignore 'latest app status' if it is older than the latest build and the
|
||||
// latest build is a 'start' transition. This ensures that you don't show a
|
||||
// stale app status from a previous build. For stop transitions, there is
|
||||
// still value in showing the latest app status.
|
||||
if ws.LatestAppStatus != nil {
|
||||
if ws.LatestBuild.Transition != codersdk.WorkspaceTransitionStart || ws.LatestAppStatus.CreatedAt.After(ws.LatestBuild.CreatedAt) {
|
||||
currentState = &codersdk.TaskStateEntry{
|
||||
Timestamp: ws.LatestAppStatus.CreatedAt,
|
||||
State: codersdk.TaskState(ws.LatestAppStatus.State),
|
||||
Message: ws.LatestAppStatus.Message,
|
||||
URI: ws.LatestAppStatus.URI,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid agent state was found for the current build and the task is initializing,
|
||||
// provide a descriptive initialization message.
|
||||
if currentState == nil && dbTask.Status == database.TaskStatusInitializing {
|
||||
message := "Initializing workspace"
|
||||
|
||||
switch {
|
||||
case ws.LatestBuild.Status == codersdk.WorkspaceStatusPending ||
|
||||
ws.LatestBuild.Status == codersdk.WorkspaceStatusStarting:
|
||||
message = fmt.Sprintf("Workspace is %s", ws.LatestBuild.Status)
|
||||
case taskAgentLifecycle != nil:
|
||||
switch {
|
||||
case *taskAgentLifecycle == codersdk.WorkspaceAgentLifecycleCreated:
|
||||
message = "Agent is connecting"
|
||||
case *taskAgentLifecycle == codersdk.WorkspaceAgentLifecycleStarting:
|
||||
message = "Agent is starting"
|
||||
case *taskAgentLifecycle == codersdk.WorkspaceAgentLifecycleReady:
|
||||
if taskAppHealth != nil && *taskAppHealth == codersdk.WorkspaceAppHealthInitializing {
|
||||
message = "App is initializing"
|
||||
} else {
|
||||
// In case the workspace app is not initializing,
|
||||
// the overall task status should be updated accordingly
|
||||
message = "Initializing workspace applications"
|
||||
}
|
||||
default:
|
||||
// In case the workspace agent is not initializing,
|
||||
// the overall task status should be updated accordingly
|
||||
message = "Initializing workspace agent"
|
||||
}
|
||||
}
|
||||
|
||||
currentState = &codersdk.TaskStateEntry{
|
||||
Timestamp: ws.LatestBuild.CreatedAt,
|
||||
State: codersdk.TaskStateWorking,
|
||||
Message: message,
|
||||
URI: "",
|
||||
}
|
||||
}
|
||||
|
||||
return currentState
|
||||
}
|
||||
|
||||
// @Summary List AI tasks
|
||||
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
|
||||
// @ID list-tasks
|
||||
|
||||
Reference in New Issue
Block a user