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

Commite8e31dc

Browse files
authored
fix(cli): use correct task status in list/status output (#20453)
1 parent40e1784 commite8e31dc

File tree

7 files changed

+127
-44
lines changed

7 files changed

+127
-44
lines changed

‎cli/cliui/table.go‎

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -296,22 +296,23 @@ func renderTable(out any, sort string, headers table.Row, filterColumns []string
296296
// returned. If the table tag is malformed, an error is returned.
297297
//
298298
// The returned name is transformed from "snake_case" to "normal text".
299-
funcparseTableStructTag(field reflect.StructField) (namestring,defaultSort,noSortOpt,recursive,skipParentNamebool,errerror) {
299+
funcparseTableStructTag(field reflect.StructField) (namestring,defaultSort,noSortOpt,recursive,skipParentName,emptyNilbool,errerror) {
300300
tags,err:=structtag.Parse(string(field.Tag))
301301
iferr!=nil {
302-
return"",false,false,false,false,xerrors.Errorf("parse struct field tag %q: %w",string(field.Tag),err)
302+
return"",false,false,false,false,false,xerrors.Errorf("parse struct field tag %q: %w",string(field.Tag),err)
303303
}
304304

305305
tag,err:=tags.Get("table")
306306
iferr!=nil||tag.Name=="-" {
307307
// tags.Get only returns an error if the tag is not found.
308-
return"",false,false,false,false,nil
308+
return"",false,false,false,false,false,nil
309309
}
310310

311311
defaultSortOpt:=false
312312
noSortOpt=false
313313
recursiveOpt:=false
314314
skipParentNameOpt:=false
315+
emptyNilOpt:=false
315316
for_,opt:=rangetag.Options {
316317
switchopt {
317318
case"default_sort":
@@ -326,12 +327,14 @@ func parseTableStructTag(field reflect.StructField) (name string, defaultSort, n
326327
// make sure the child name is unique across all nested structs in the parent.
327328
recursiveOpt=true
328329
skipParentNameOpt=true
330+
case"empty_nil":
331+
emptyNilOpt=true
329332
default:
330-
return"",false,false,false,false,xerrors.Errorf("unknown option %q in struct field tag",opt)
333+
return"",false,false,false,false,false,xerrors.Errorf("unknown option %q in struct field tag",opt)
331334
}
332335
}
333336

334-
returnstrings.ReplaceAll(tag.Name,"_"," "),defaultSortOpt,noSortOpt,recursiveOpt,skipParentNameOpt,nil
337+
returnstrings.ReplaceAll(tag.Name,"_"," "),defaultSortOpt,noSortOpt,recursiveOpt,skipParentNameOpt,emptyNilOpt,nil
335338
}
336339

337340
funcisStructOrStructPointer(t reflect.Type)bool {
@@ -358,7 +361,7 @@ func typeToTableHeaders(t reflect.Type, requireDefault bool) ([]string, string,
358361
noSortOpt:=false
359362
fori:=0;i<t.NumField();i++ {
360363
field:=t.Field(i)
361-
name,defaultSort,noSort,recursive,skip,err:=parseTableStructTag(field)
364+
name,defaultSort,noSort,recursive,skip,_,err:=parseTableStructTag(field)
362365
iferr!=nil {
363366
returnnil,"",xerrors.Errorf("parse struct tags for field %q in type %q: %w",field.Name,t.String(),err)
364367
}
@@ -435,16 +438,22 @@ func valueToTableMap(val reflect.Value) (map[string]any, error) {
435438
fori:=0;i<val.NumField();i++ {
436439
field:=val.Type().Field(i)
437440
fieldVal:=val.Field(i)
438-
name,_,_,recursive,skip,err:=parseTableStructTag(field)
441+
name,_,_,recursive,skip,emptyNil,err:=parseTableStructTag(field)
439442
iferr!=nil {
440443
returnnil,xerrors.Errorf("parse struct tags for field %q in type %T: %w",field.Name,val,err)
441444
}
442445
ifname=="" {
443446
continue
444447
}
445448

446-
// Recurse if it's a struct.
447449
fieldType:=field.Type
450+
451+
// If empty_nil is set and this is a nil pointer, use a zero value.
452+
ifemptyNil&&fieldVal.Kind()==reflect.Pointer&&fieldVal.IsNil() {
453+
fieldVal=reflect.New(fieldType.Elem())
454+
}
455+
456+
// Recurse if it's a struct.
448457
ifrecursive {
449458
if!isStructOrStructPointer(fieldType) {
450459
returnnil,xerrors.Errorf("field %q in type %q is marked as recursive but does not contain a struct or a pointer to a struct",field.Name,fieldType.String())
@@ -467,7 +476,7 @@ func valueToTableMap(val reflect.Value) (map[string]any, error) {
467476
}
468477

469478
// Otherwise, we just use the field value.
470-
row[name]=val.Field(i).Interface()
479+
row[name]=fieldVal.Interface()
471480
}
472481

473482
returnrow,nil

‎cli/cliui/table_test.go‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,78 @@ foo <nil> 10 [a, b, c] foo1 11 foo2 12 fo
400400
})
401401
})
402402
})
403+
404+
t.Run("EmptyNil",func(t*testing.T) {
405+
t.Parallel()
406+
407+
typeemptyNilTeststruct {
408+
Namestring`table:"name,default_sort"`
409+
EmptyOnNil*string`table:"empty_on_nil,empty_nil"`
410+
NormalBehavior*string`table:"normal_behavior"`
411+
}
412+
413+
value:="value"
414+
in:= []emptyNilTest{
415+
{
416+
Name:"has_value",
417+
EmptyOnNil:&value,
418+
NormalBehavior:&value,
419+
},
420+
{
421+
Name:"has_nil",
422+
EmptyOnNil:nil,
423+
NormalBehavior:nil,
424+
},
425+
}
426+
427+
expected:=`
428+
NAME EMPTY ON NIL NORMAL BEHAVIOR
429+
has_nil <nil>
430+
has_value value value
431+
`
432+
433+
out,err:=cliui.DisplayTable(in,"",nil)
434+
log.Println("rendered table:\n"+out)
435+
require.NoError(t,err)
436+
compareTables(t,expected,out)
437+
})
438+
439+
t.Run("EmptyNilWithRecursiveInline",func(t*testing.T) {
440+
t.Parallel()
441+
442+
typenestedDatastruct {
443+
Namestring`table:"name"`
444+
}
445+
446+
typeinlineTeststruct {
447+
Nested*nestedData`table:"ignored,recursive_inline,empty_nil"`
448+
Countint`table:"count,default_sort"`
449+
}
450+
451+
in:= []inlineTest{
452+
{
453+
Nested:&nestedData{
454+
Name:"alice",
455+
},
456+
Count:1,
457+
},
458+
{
459+
Nested:nil,
460+
Count:2,
461+
},
462+
}
463+
464+
expected:=`
465+
NAME COUNT
466+
alice 1
467+
2
468+
`
469+
470+
out,err:=cliui.DisplayTable(in,"",nil)
471+
log.Println("rendered table:\n"+out)
472+
require.NoError(t,err)
473+
compareTables(t,expected,out)
474+
})
403475
}
404476

405477
// compareTables normalizes the incoming table lines

‎cli/exp_task_list_test.go‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func TestExpTaskList(t *testing.T) {
162162

163163
// Validate the table includes the task and status.
164164
pty.ExpectMatch(task.Name)
165-
pty.ExpectMatch("running")
165+
pty.ExpectMatch("initializing")
166166
pty.ExpectMatch(wantPrompt)
167167
})
168168

@@ -175,9 +175,9 @@ func TestExpTaskList(t *testing.T) {
175175
owner:=coderdtest.CreateFirstUser(t,client)
176176
memberClient,memberUser:=coderdtest.CreateAnotherUser(t,client,owner.OrganizationID)
177177

178-
// Create two AI tasks: onerunning, onestopped.
179-
runningTask:=makeAITask(t,db,owner.OrganizationID,owner.UserID,memberUser.ID,database.WorkspaceTransitionStart,"keep merunning")
180-
stoppedTask:=makeAITask(t,db,owner.OrganizationID,owner.UserID,memberUser.ID,database.WorkspaceTransitionStop,"stop me please")
178+
// Create two AI tasks: oneinitializing, onepaused.
179+
initializingTask:=makeAITask(t,db,owner.OrganizationID,owner.UserID,memberUser.ID,database.WorkspaceTransitionStart,"keep meinitializing")
180+
pausedTask:=makeAITask(t,db,owner.OrganizationID,owner.UserID,memberUser.ID,database.WorkspaceTransitionStop,"stop me please")
181181

182182
// Use JSON output to reliably validate filtering.
183183
inv,root:=clitest.New(t,"exp","task","list","--status=paused","--output=json")
@@ -194,10 +194,10 @@ func TestExpTaskList(t *testing.T) {
194194
vartasks []codersdk.Task
195195
require.NoError(t,json.Unmarshal(stdout.Bytes(),&tasks))
196196

197-
// Only thestopped task is returned.
197+
// Only thepaused task is returned.
198198
require.Len(t,tasks,1,"expected one task after filtering")
199-
require.Equal(t,stoppedTask.ID,tasks[0].ID)
200-
require.NotEqual(t,runningTask.ID,tasks[0].ID)
199+
require.Equal(t,pausedTask.ID,tasks[0].ID)
200+
require.NotEqual(t,initializingTask.ID,tasks[0].ID)
201201
})
202202

203203
t.Run("UserFlag_Me_Table",func(t*testing.T) {
@@ -234,7 +234,7 @@ func TestExpTaskList(t *testing.T) {
234234
memberClient,memberUser:=coderdtest.CreateAnotherUser(t,client,owner.OrganizationID)
235235

236236
// Given: We have two tasks
237-
task1:=makeAITask(t,db,owner.OrganizationID,owner.UserID,memberUser.ID,database.WorkspaceTransitionStart,"keep merunning")
237+
task1:=makeAITask(t,db,owner.OrganizationID,owner.UserID,memberUser.ID,database.WorkspaceTransitionStart,"keep meactive")
238238
task2:=makeAITask(t,db,owner.OrganizationID,owner.UserID,memberUser.ID,database.WorkspaceTransitionStop,"stop me please")
239239

240240
// Given: We add the `--quiet` flag

‎cli/exp_task_status.go‎

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -156,28 +156,21 @@ func taskWatchIsEnded(task codersdk.Task) bool {
156156
}
157157

158158
typetaskStatusRowstruct {
159-
codersdk.Task`table:"-"`
160-
ChangedAgostring`json:"-" table:"state changed,default_sort"`
161-
Timestamp time.Time`json:"-" table:"-"`
162-
TaskStatusstring`json:"-" table:"status"`
163-
Healthybool`json:"-" table:"healthy"`
164-
TaskStatestring`json:"-" table:"state"`
165-
Messagestring`json:"-" table:"message"`
159+
codersdk.Task`table:"r,recursive_inline"`
160+
ChangedAgostring`json:"-" table:"state changed"`
161+
Healthybool`json:"-" table:"healthy"`
166162
}
167163

168164
functaskStatusRowEqual(r1,r2taskStatusRow)bool {
169-
returnr1.TaskStatus==r2.TaskStatus&&
165+
returnr1.Status==r2.Status&&
170166
r1.Healthy==r2.Healthy&&
171-
r1.TaskState==r2.TaskState&&
172-
r1.Message==r2.Message
167+
taskStateEqual(r1.CurrentState,r2.CurrentState)
173168
}
174169

175170
functoStatusRow(task codersdk.Task)taskStatusRow {
176171
tsr:=taskStatusRow{
177172
Task:task,
178173
ChangedAgo:time.Since(task.UpdatedAt).Truncate(time.Second).String()+" ago",
179-
Timestamp:task.UpdatedAt,
180-
TaskStatus:string(task.WorkspaceStatus),
181174
}
182175
tsr.Healthy=task.WorkspaceAgentHealth!=nil&&
183176
task.WorkspaceAgentHealth.Healthy&&
@@ -187,9 +180,19 @@ func toStatusRow(task codersdk.Task) taskStatusRow {
187180

188181
iftask.CurrentState!=nil {
189182
tsr.ChangedAgo=time.Since(task.CurrentState.Timestamp).Truncate(time.Second).String()+" ago"
190-
tsr.Timestamp=task.CurrentState.Timestamp
191-
tsr.TaskState=string(task.CurrentState.State)
192-
tsr.Message=task.CurrentState.Message
193183
}
194184
returntsr
195185
}
186+
187+
functaskStateEqual(se1,se2*codersdk.TaskStateEntry)bool {
188+
vars1,m1,s2,m2string
189+
ifse1!=nil {
190+
s1=string(se1.State)
191+
m1=se1.Message
192+
}
193+
ifse2!=nil {
194+
s2=string(se2.State)
195+
m2=se2.Message
196+
}
197+
returns1==s2&&m1==m2
198+
}

‎cli/exp_task_status_test.go‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ func Test_TaskStatus(t *testing.T) {
5555
},
5656
{
5757
args: []string{"exists"},
58-
expectOutput:`STATE CHANGED STATUSHEALTHY STATE MESSAGE
59-
0s agorunning true working Thinking furiously...`,
58+
expectOutput:`STATE CHANGED STATUS HEALTHY STATE MESSAGE
59+
0s agoactive true working Thinking furiously...`,
6060
hf:func(ctx context.Context,now time.Time)func(w http.ResponseWriter,r*http.Request) {
6161
returnfunc(w http.ResponseWriter,r*http.Request) {
6262
switchr.URL.Path {
@@ -114,12 +114,12 @@ func Test_TaskStatus(t *testing.T) {
114114
},
115115
{
116116
args: []string{"exists","--watch"},
117-
expectOutput:`
118-
STATE CHANGED STATUS HEALTHY STATE MESSAGE
117+
expectOutput:`STATE CHANGED STATUS HEALTHY STATE MESSAGE
119118
5s ago pending true
120-
4s ago running true
121-
3s ago running true working Reticulating splines...
122-
2s ago running true complete Splines reticulated successfully!`,
119+
4s ago initializing true
120+
4s ago active true
121+
3s ago active true working Reticulating splines...
122+
2s ago active true complete Splines reticulated successfully!`,
123123
hf:func(ctx context.Context,now time.Time)func(http.ResponseWriter,*http.Request) {
124124
varcalls atomic.Int64
125125
returnfunc(w http.ResponseWriter,r*http.Request) {

‎cli/exp_task_test.go‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,7 @@ func Test_Tasks(t *testing.T) {
9494
vartask codersdk.Task
9595
require.NoError(t,json.NewDecoder(strings.NewReader(stdout)).Decode(&task),"should unmarshal task status")
9696
require.Equal(t,task.Name,taskName,"task name should match")
97-
// NOTE: task status changes type, this is so this test works with both old and new model
98-
require.Contains(t, []string{"active","running"},string(task.Status),"task should be active")
97+
require.Equal(t,codersdk.TaskStatusActive,task.Status,"task should be active")
9998
},
10099
},
101100
{

‎codersdk/aitasks.go‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,15 @@ type Task struct {
163163
TemplateDisplayNamestring`json:"template_display_name" table:"template display name"`
164164
TemplateIconstring`json:"template_icon" table:"template icon"`
165165
WorkspaceID uuid.NullUUID`json:"workspace_id" format:"uuid" table:"workspace id"`
166-
WorkspaceStatusWorkspaceStatus`json:"workspace_status,omitempty" enums:"pending,starting,running,stopping,stopped,failed,canceling,canceled,deleting,deleted" table:"status"`
166+
WorkspaceStatusWorkspaceStatus`json:"workspace_status,omitempty" enums:"pending,starting,running,stopping,stopped,failed,canceling,canceled,deleting,deleted" table:"workspacestatus"`
167167
WorkspaceBuildNumberint32`json:"workspace_build_number,omitempty" table:"workspace build number"`
168168
WorkspaceAgentID uuid.NullUUID`json:"workspace_agent_id" format:"uuid" table:"workspace agent id"`
169169
WorkspaceAgentLifecycle*WorkspaceAgentLifecycle`json:"workspace_agent_lifecycle" table:"workspace agent lifecycle"`
170170
WorkspaceAgentHealth*WorkspaceAgentHealth`json:"workspace_agent_health" table:"workspace agent health"`
171171
WorkspaceAppID uuid.NullUUID`json:"workspace_app_id" format:"uuid" table:"workspace app id"`
172172
InitialPromptstring`json:"initial_prompt" table:"initial prompt"`
173-
StatusTaskStatus`json:"status" enums:"pending,initializing,active,paused,unknown,error" table:"taskstatus"`
174-
CurrentState*TaskStateEntry`json:"current_state" table:"cs,recursive_inline"`
173+
StatusTaskStatus`json:"status" enums:"pending,initializing,active,paused,unknown,error" table:"status"`
174+
CurrentState*TaskStateEntry`json:"current_state" table:"cs,recursive_inline,empty_nil"`
175175
CreatedAt time.Time`json:"created_at" format:"date-time" table:"created at"`
176176
UpdatedAt time.Time`json:"updated_at" format:"date-time" table:"updated at"`
177177
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp