diff --git a/cli/cliui/table.go b/cli/cliui/table.go index d113b97c2d..82272ce9c9 100644 --- a/cli/cliui/table.go +++ b/cli/cliui/table.go @@ -9,6 +9,8 @@ import ( "github.com/fatih/structtag" "github.com/jedib0t/go-pretty/v6/table" "golang.org/x/xerrors" + + "github.com/coder/coder/v2/codersdk" ) // Table creates a new table with standardized styles. @@ -195,6 +197,12 @@ func renderTable(out any, sort string, headers table.Row, filterColumns []string if val != nil { v = val.Format(time.RFC3339) } + case codersdk.NullTime: + if val.Valid { + v = val.Time.Format(time.RFC3339) + } else { + v = nil + } case *int64: if val != nil { v = *val @@ -204,8 +212,13 @@ func renderTable(out any, sort string, headers table.Row, filterColumns []string v = val.String() } case fmt.Stringer: - if val != nil { + // Protect against typed nils since fmt.Stringer is an interface. + vv := reflect.ValueOf(v) + nilPtr := vv.Kind() == reflect.Ptr && vv.IsNil() + if val != nil && !nilPtr { v = val.String() + } else if nilPtr { + v = nil } } diff --git a/cli/cliui/table_test.go b/cli/cliui/table_test.go index bb46219c3c..56e23fc448 100644 --- a/cli/cliui/table_test.go +++ b/cli/cliui/table_test.go @@ -1,6 +1,7 @@ package cliui_test import ( + "database/sql" "fmt" "log" "strings" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/coder/coder/v2/cli/cliui" + "github.com/coder/coder/v2/codersdk" ) type stringWrapper struct { @@ -24,18 +26,20 @@ func (s stringWrapper) String() string { } type tableTest1 struct { - Name string `table:"name,default_sort"` - NotIncluded string // no table tag - Age int `table:"age"` - Roles []string `table:"roles"` - Sub1 tableTest2 `table:"sub_1,recursive"` - Sub2 *tableTest2 `table:"sub_2,recursive"` - Sub3 tableTest3 `table:"sub 3,recursive"` - Sub4 tableTest2 `table:"sub 4"` // not recursive + Name string `table:"name,default_sort"` + AltName *stringWrapper `table:"alt_name"` + NotIncluded string // no table tag + Age int `table:"age"` + Roles []string `table:"roles"` + Sub1 tableTest2 `table:"sub_1,recursive"` + Sub2 *tableTest2 `table:"sub_2,recursive"` + Sub3 tableTest3 `table:"sub 3,recursive"` + Sub4 tableTest2 `table:"sub 4"` // not recursive // Types with special formatting. - Time time.Time `table:"time"` - TimePtr *time.Time `table:"time_ptr"` + Time time.Time `table:"time"` + TimePtr *time.Time `table:"time_ptr"` + NullTime codersdk.NullTime `table:"null_time"` } type tableTest2 struct { @@ -62,9 +66,10 @@ func Test_DisplayTable(t *testing.T) { // Not sorted by name or age to test sorting. in := []tableTest1{ { - Name: "bar", - Age: 20, - Roles: []string{"a"}, + Name: "bar", + AltName: &stringWrapper{str: "bar alt"}, + Age: 20, + Roles: []string{"a"}, Sub1: tableTest2{ Name: stringWrapper{str: "bar1"}, Age: 21, @@ -82,6 +87,12 @@ func Test_DisplayTable(t *testing.T) { }, Time: someTime, TimePtr: nil, + NullTime: codersdk.NullTime{ + NullTime: sql.NullTime{ + Time: someTime, + Valid: true, + }, + }, }, { Name: "foo", @@ -138,10 +149,10 @@ func Test_DisplayTable(t *testing.T) { t.Parallel() expected := ` -NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR -bar 20 [a] bar1 21 bar3 23 {bar4 24 } 2022-08-02T15:49:10Z -baz 30 [] baz1 31 baz3 33 {baz4 34 } 2022-08-02T15:49:10Z -foo 10 [a, b, c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z +NAME ALT NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR NULL TIME +bar bar alt 20 [a] bar1 21 bar3 23 {bar4 24 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z +baz 30 [] baz1 31 baz3 33 {baz4 34 } 2022-08-02T15:49:10Z +foo 10 [a, b, c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z ` // Test with non-pointer values. @@ -165,10 +176,10 @@ foo 10 [a, b, c] foo1 11 foo2 12 foo3 t.Parallel() expected := ` -NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR -foo 10 [a, b, c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z -bar 20 [a] bar1 21 bar3 23 {bar4 24 } 2022-08-02T15:49:10Z -baz 30 [] baz1 31 baz3 33 {baz4 34 } 2022-08-02T15:49:10Z +NAME ALT NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR NULL TIME +foo 10 [a, b, c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z +bar bar alt 20 [a] bar1 21 bar3 23 {bar4 24 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z +baz 30 [] baz1 31 baz3 33 {baz4 34 } 2022-08-02T15:49:10Z ` out, err := cliui.DisplayTable(in, "age", nil) @@ -235,12 +246,12 @@ Alice 25 t.Run("WithSeparator", func(t *testing.T) { t.Parallel() expected := ` -NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR -bar 20 [a] bar1 21 bar3 23 {bar4 24 } 2022-08-02T15:49:10Z ---------------------------------------------------------------------------------------------------------------------------------------------------------------- -baz 30 [] baz1 31 baz3 33 {baz4 34 } 2022-08-02T15:49:10Z ---------------------------------------------------------------------------------------------------------------------------------------------------------------- -foo 10 [a, b, c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z +NAME ALT NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR NULL TIME +bar bar alt 20 [a] bar1 21 bar3 23 {bar4 24 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +baz 30 [] baz1 31 baz3 33 {baz4 34 } 2022-08-02T15:49:10Z +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +foo 10 [a, b, c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z ` var inlineIn []any