Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit16b8e60

Browse files
authored
fix: set codersdk.Task current_state during task initialization (#20692)
## ProblemWith the new tasks data model, a task starts with an `initializing`status. However, the API returns `current_state: null` to represent theagent state, causing the frontend to display "No message available".This PR updates `codersdk.Task` to return a `current_state` when thetask is initializing with meaningful messages about what's happeningduring 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 whentask status is `initializing` and no valid app status exists for thecurrent build- **dbfake**: Fix `WorkspaceBuild` builder to properly handlepending/running jobs by linking tasks without requiring agent/appresources**Note:** UI Storybook changes to reflect these new messages will beaddressed in a follow-up PR.Closes:coder/internal#1063
1 parent3551500 commit16b8e60

File tree

4 files changed

+319
-23
lines changed

4 files changed

+319
-23
lines changed

‎coderd/aitasks.go‎

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"golang.org/x/xerrors"
1414

1515
"cdr.dev/slog"
16+
1617
"github.com/coder/coder/v2/coderd/audit"
1718
"github.com/coder/coder/v2/coderd/database"
1819
"github.com/coder/coder/v2/coderd/database/dbtime"
@@ -23,6 +24,7 @@ import (
2324
"github.com/coder/coder/v2/coderd/rbac/policy"
2425
"github.com/coder/coder/v2/coderd/searchquery"
2526
"github.com/coder/coder/v2/coderd/taskname"
27+
"github.com/coder/coder/v2/coderd/util/ptr"
2628
"github.com/coder/coder/v2/coderd/util/slice"
2729
"github.com/coder/coder/v2/codersdk"
2830

@@ -270,37 +272,29 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
270272
functaskFromDBTaskAndWorkspace(dbTask database.Task,ws codersdk.Workspace) codersdk.Task {
271273
vartaskAgentLifecycle*codersdk.WorkspaceAgentLifecycle
272274
vartaskAgentHealth*codersdk.WorkspaceAgentHealth
275+
vartaskAppHealth*codersdk.WorkspaceAppHealth
276+
277+
ifdbTask.WorkspaceAgentLifecycleState.Valid {
278+
taskAgentLifecycle=ptr.Ref(codersdk.WorkspaceAgentLifecycle(dbTask.WorkspaceAgentLifecycleState.WorkspaceAgentLifecycleState))
279+
}
280+
ifdbTask.WorkspaceAppHealth.Valid {
281+
taskAppHealth=ptr.Ref(codersdk.WorkspaceAppHealth(dbTask.WorkspaceAppHealth.WorkspaceAppHealth))
282+
}
273283

274-
// If we have an agent ID from the task, find the agent details in the
275-
// workspace.
284+
// If we have an agent ID from the task, find the agent health info
276285
ifdbTask.WorkspaceAgentID.Valid {
277286
findTaskAgentLoop:
278287
for_,resource:=rangews.LatestBuild.Resources {
279288
for_,agent:=rangeresource.Agents {
280289
ifagent.ID==dbTask.WorkspaceAgentID.UUID {
281-
taskAgentLifecycle=&agent.LifecycleState
282290
taskAgentHealth=&agent.Health
283291
break findTaskAgentLoop
284292
}
285293
}
286294
}
287295
}
288296

289-
// Ignore 'latest app status' if it is older than the latest build and the
290-
// latest build is a 'start' transition. This ensures that you don't show a
291-
// stale app status from a previous build. For stop transitions, there is
292-
// still value in showing the latest app status.
293-
varcurrentState*codersdk.TaskStateEntry
294-
ifws.LatestAppStatus!=nil {
295-
ifws.LatestBuild.Transition!=codersdk.WorkspaceTransitionStart||ws.LatestAppStatus.CreatedAt.After(ws.LatestBuild.CreatedAt) {
296-
currentState=&codersdk.TaskStateEntry{
297-
Timestamp:ws.LatestAppStatus.CreatedAt,
298-
State:codersdk.TaskState(ws.LatestAppStatus.State),
299-
Message:ws.LatestAppStatus.Message,
300-
URI:ws.LatestAppStatus.URI,
301-
}
302-
}
303-
}
297+
currentState:=deriveTaskCurrentState(dbTask,ws,taskAgentLifecycle,taskAppHealth)
304298

305299
return codersdk.Task{
306300
ID:dbTask.ID,
@@ -330,6 +324,73 @@ func taskFromDBTaskAndWorkspace(dbTask database.Task, ws codersdk.Workspace) cod
330324
}
331325
}
332326

327+
// deriveTaskCurrentState determines the current state of a task based on the
328+
// workspace's latest app status and initialization phase.
329+
// Returns nil if no valid state can be determined.
330+
funcderiveTaskCurrentState(
331+
dbTask database.Task,
332+
ws codersdk.Workspace,
333+
taskAgentLifecycle*codersdk.WorkspaceAgentLifecycle,
334+
taskAppHealth*codersdk.WorkspaceAppHealth,
335+
)*codersdk.TaskStateEntry {
336+
varcurrentState*codersdk.TaskStateEntry
337+
338+
// Ignore 'latest app status' if it is older than the latest build and the
339+
// latest build is a 'start' transition. This ensures that you don't show a
340+
// stale app status from a previous build. For stop transitions, there is
341+
// still value in showing the latest app status.
342+
ifws.LatestAppStatus!=nil {
343+
ifws.LatestBuild.Transition!=codersdk.WorkspaceTransitionStart||ws.LatestAppStatus.CreatedAt.After(ws.LatestBuild.CreatedAt) {
344+
currentState=&codersdk.TaskStateEntry{
345+
Timestamp:ws.LatestAppStatus.CreatedAt,
346+
State:codersdk.TaskState(ws.LatestAppStatus.State),
347+
Message:ws.LatestAppStatus.Message,
348+
URI:ws.LatestAppStatus.URI,
349+
}
350+
}
351+
}
352+
353+
// If no valid agent state was found for the current build and the task is initializing,
354+
// provide a descriptive initialization message.
355+
ifcurrentState==nil&&dbTask.Status==database.TaskStatusInitializing {
356+
message:="Initializing workspace"
357+
358+
switch {
359+
casews.LatestBuild.Status==codersdk.WorkspaceStatusPending||
360+
ws.LatestBuild.Status==codersdk.WorkspaceStatusStarting:
361+
message=fmt.Sprintf("Workspace is %s",ws.LatestBuild.Status)
362+
casetaskAgentLifecycle!=nil:
363+
switch {
364+
case*taskAgentLifecycle==codersdk.WorkspaceAgentLifecycleCreated:
365+
message="Agent is connecting"
366+
case*taskAgentLifecycle==codersdk.WorkspaceAgentLifecycleStarting:
367+
message="Agent is starting"
368+
case*taskAgentLifecycle==codersdk.WorkspaceAgentLifecycleReady:
369+
iftaskAppHealth!=nil&&*taskAppHealth==codersdk.WorkspaceAppHealthInitializing {
370+
message="App is initializing"
371+
}else {
372+
// In case the workspace app is not initializing,
373+
// the overall task status should be updated accordingly
374+
message="Initializing workspace applications"
375+
}
376+
default:
377+
// In case the workspace agent is not initializing,
378+
// the overall task status should be updated accordingly
379+
message="Initializing workspace agent"
380+
}
381+
}
382+
383+
currentState=&codersdk.TaskStateEntry{
384+
Timestamp:ws.LatestBuild.CreatedAt,
385+
State:codersdk.TaskStateWorking,
386+
Message:message,
387+
URI:"",
388+
}
389+
}
390+
391+
returncurrentState
392+
}
393+
333394
// @Summary List AI tasks
334395
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
335396
// @ID list-tasks

‎coderd/aitasks_internal_test.go‎

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
package coderd
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/google/uuid"
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/coder/coder/v2/coderd/database"
12+
"github.com/coder/coder/v2/coderd/util/ptr"
13+
"github.com/coder/coder/v2/codersdk"
14+
)
15+
16+
funcTestDeriveTaskCurrentState_Unit(t*testing.T) {
17+
t.Parallel()
18+
19+
now:=time.Now()
20+
tests:= []struct {
21+
namestring
22+
task database.Task
23+
agentLifecycle*codersdk.WorkspaceAgentLifecycle
24+
appHealth*codersdk.WorkspaceAppHealth
25+
latestAppStatus*codersdk.WorkspaceAppStatus
26+
latestBuild codersdk.WorkspaceBuild
27+
expectCurrentStatebool
28+
expectedTimestamp time.Time
29+
expectedState codersdk.TaskState
30+
expectedMessagestring
31+
}{
32+
{
33+
name:"NoAppStatus",
34+
task: database.Task{
35+
ID:uuid.New(),
36+
Status:database.TaskStatusActive,
37+
},
38+
agentLifecycle:nil,
39+
appHealth:nil,
40+
latestAppStatus:nil,
41+
latestBuild: codersdk.WorkspaceBuild{
42+
Transition:codersdk.WorkspaceTransitionStart,
43+
CreatedAt:now,
44+
},
45+
expectCurrentState:false,
46+
},
47+
{
48+
name:"BuildStartTransition_AppStatus_NewerThanBuild",
49+
task: database.Task{
50+
ID:uuid.New(),
51+
Status:database.TaskStatusActive,
52+
},
53+
agentLifecycle:nil,
54+
appHealth:nil,
55+
latestAppStatus:&codersdk.WorkspaceAppStatus{
56+
State:codersdk.WorkspaceAppStatusStateWorking,
57+
Message:"Task is working",
58+
CreatedAt:now.Add(1*time.Minute),
59+
},
60+
latestBuild: codersdk.WorkspaceBuild{
61+
Transition:codersdk.WorkspaceTransitionStart,
62+
CreatedAt:now,
63+
},
64+
expectCurrentState:true,
65+
expectedTimestamp:now.Add(1*time.Minute),
66+
expectedState:codersdk.TaskState(codersdk.WorkspaceAppStatusStateWorking),
67+
expectedMessage:"Task is working",
68+
},
69+
{
70+
name:"BuildStartTransition_StaleAppStatus_OlderThanBuild",
71+
task: database.Task{
72+
ID:uuid.New(),
73+
Status:database.TaskStatusActive,
74+
},
75+
agentLifecycle:nil,
76+
appHealth:nil,
77+
latestAppStatus:&codersdk.WorkspaceAppStatus{
78+
State:codersdk.WorkspaceAppStatusStateComplete,
79+
Message:"Previous task completed",
80+
CreatedAt:now.Add(-1*time.Minute),
81+
},
82+
latestBuild: codersdk.WorkspaceBuild{
83+
Transition:codersdk.WorkspaceTransitionStart,
84+
CreatedAt:now,
85+
},
86+
expectCurrentState:false,
87+
},
88+
{
89+
name:"BuildStopTransition",
90+
task: database.Task{
91+
ID:uuid.New(),
92+
Status:database.TaskStatusActive,
93+
},
94+
agentLifecycle:nil,
95+
appHealth:nil,
96+
latestAppStatus:&codersdk.WorkspaceAppStatus{
97+
State:codersdk.WorkspaceAppStatusStateComplete,
98+
Message:"Task completed before stop",
99+
CreatedAt:now.Add(-1*time.Minute),
100+
},
101+
latestBuild: codersdk.WorkspaceBuild{
102+
Transition:codersdk.WorkspaceTransitionStop,
103+
CreatedAt:now,
104+
},
105+
expectCurrentState:true,
106+
expectedTimestamp:now.Add(-1*time.Minute),
107+
expectedState:codersdk.TaskState(codersdk.WorkspaceAppStatusStateComplete),
108+
expectedMessage:"Task completed before stop",
109+
},
110+
{
111+
name:"TaskInitializing_WorkspacePending",
112+
task: database.Task{
113+
ID:uuid.New(),
114+
Status:database.TaskStatusInitializing,
115+
},
116+
agentLifecycle:nil,
117+
appHealth:nil,
118+
latestAppStatus:nil,
119+
latestBuild: codersdk.WorkspaceBuild{
120+
Status:codersdk.WorkspaceStatusPending,
121+
CreatedAt:now,
122+
},
123+
expectCurrentState:true,
124+
expectedTimestamp:now,
125+
expectedState:codersdk.TaskStateWorking,
126+
expectedMessage:"Workspace is pending",
127+
},
128+
{
129+
name:"TaskInitializing_WorkspaceStarting",
130+
task: database.Task{
131+
ID:uuid.New(),
132+
Status:database.TaskStatusInitializing,
133+
},
134+
agentLifecycle:nil,
135+
appHealth:nil,
136+
latestAppStatus:nil,
137+
latestBuild: codersdk.WorkspaceBuild{
138+
Status:codersdk.WorkspaceStatusStarting,
139+
CreatedAt:now,
140+
},
141+
expectCurrentState:true,
142+
expectedTimestamp:now,
143+
expectedState:codersdk.TaskStateWorking,
144+
expectedMessage:"Workspace is starting",
145+
},
146+
{
147+
name:"TaskInitializing_AgentConnecting",
148+
task: database.Task{
149+
ID:uuid.New(),
150+
Status:database.TaskStatusInitializing,
151+
},
152+
agentLifecycle:ptr.Ref(codersdk.WorkspaceAgentLifecycleCreated),
153+
appHealth:nil,
154+
latestAppStatus:nil,
155+
latestBuild: codersdk.WorkspaceBuild{
156+
Status:codersdk.WorkspaceStatusRunning,
157+
CreatedAt:now,
158+
},
159+
expectCurrentState:true,
160+
expectedTimestamp:now,
161+
expectedState:codersdk.TaskStateWorking,
162+
expectedMessage:"Agent is connecting",
163+
},
164+
{
165+
name:"TaskInitializing_AgentStarting",
166+
task: database.Task{
167+
ID:uuid.New(),
168+
Status:database.TaskStatusInitializing,
169+
},
170+
agentLifecycle:ptr.Ref(codersdk.WorkspaceAgentLifecycleStarting),
171+
appHealth:nil,
172+
latestAppStatus:nil,
173+
latestBuild: codersdk.WorkspaceBuild{
174+
Status:codersdk.WorkspaceStatusRunning,
175+
CreatedAt:now,
176+
},
177+
expectCurrentState:true,
178+
expectedTimestamp:now,
179+
expectedState:codersdk.TaskStateWorking,
180+
expectedMessage:"Agent is starting",
181+
},
182+
{
183+
name:"TaskInitializing_AppInitializing",
184+
task: database.Task{
185+
ID:uuid.New(),
186+
Status:database.TaskStatusInitializing,
187+
},
188+
agentLifecycle:ptr.Ref(codersdk.WorkspaceAgentLifecycleReady),
189+
appHealth:ptr.Ref(codersdk.WorkspaceAppHealthInitializing),
190+
latestAppStatus:nil,
191+
latestBuild: codersdk.WorkspaceBuild{
192+
Status:codersdk.WorkspaceStatusRunning,
193+
CreatedAt:now,
194+
},
195+
expectCurrentState:true,
196+
expectedTimestamp:now,
197+
expectedState:codersdk.TaskStateWorking,
198+
expectedMessage:"App is initializing",
199+
},
200+
}
201+
202+
for_,tt:=rangetests {
203+
t.Run(tt.name,func(t*testing.T) {
204+
t.Parallel()
205+
206+
ws:= codersdk.Workspace{
207+
LatestBuild:tt.latestBuild,
208+
LatestAppStatus:tt.latestAppStatus,
209+
}
210+
211+
currentState:=deriveTaskCurrentState(tt.task,ws,tt.agentLifecycle,tt.appHealth)
212+
213+
iftt.expectCurrentState {
214+
require.NotNil(t,currentState)
215+
assert.Equal(t,tt.expectedTimestamp.UTC(),currentState.Timestamp.UTC())
216+
assert.Equal(t,tt.expectedState,currentState.State)
217+
assert.Equal(t,tt.expectedMessage,currentState.Message)
218+
}else {
219+
assert.Nil(t,currentState)
220+
}
221+
})
222+
}
223+
}

‎coderd/aitasks_test.go‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,14 +240,18 @@ func TestTasks(t *testing.T) {
240240
assert.NotNil(t,updated.CurrentState,"current state should not be nil")
241241
assert.Equal(t,"all done",updated.CurrentState.Message)
242242
assert.Equal(t,codersdk.TaskStateComplete,updated.CurrentState.State)
243+
previousCurrentState:=updated.CurrentState
243244

244245
// Start the workspace again
245246
coderdtest.MustTransitionWorkspace(t,client,task.WorkspaceID.UUID,codersdk.WorkspaceTransitionStop,codersdk.WorkspaceTransitionStart)
246247

247-
// Verify that the status from the previous build is no longer present
248+
// Verify that the status from the previous build has been cleared
249+
// and replaced by the agent initialization status.
248250
updated,err=exp.TaskByID(ctx,task.ID)
249251
require.NoError(t,err)
250-
assert.Nil(t,updated.CurrentState,"current state should be nil")
252+
assert.NotEqual(t,previousCurrentState,updated.CurrentState)
253+
assert.Equal(t,codersdk.TaskStateWorking,updated.CurrentState.State)
254+
assert.NotEqual(t,"all done",updated.CurrentState.Message)
251255
})
252256

253257
t.Run("Delete",func(t*testing.T) {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp