Skip to content

Commit 3610402

Browse files
authored
Use new table formatter everywhere (#3544)
1 parent c432979 commit 3610402

File tree

13 files changed

+198
-197
lines changed

13 files changed

+198
-197
lines changed

cli/cliui/table.go

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
9090
sort = strings.ToLower(strings.ReplaceAll(sort, "_", " "))
9191
h, ok := headersMap[sort]
9292
if !ok {
93-
return "", xerrors.Errorf("specified sort column %q not found in table headers, available columns are %q", sort, strings.Join(headersRaw, `", "`))
93+
return "", xerrors.Errorf(`specified sort column %q not found in table headers, available columns are "%v"`, sort, strings.Join(headersRaw, `", "`))
9494
}
9595

9696
// Autocorrect
@@ -101,7 +101,7 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
101101
column := strings.ToLower(strings.ReplaceAll(column, "_", " "))
102102
h, ok := headersMap[column]
103103
if !ok {
104-
return "", xerrors.Errorf("specified filter column %q not found in table headers, available columns are %q", sort, strings.Join(headersRaw, `", "`))
104+
return "", xerrors.Errorf(`specified filter column %q not found in table headers, available columns are "%v"`, sort, strings.Join(headersRaw, `", "`))
105105
}
106106

107107
// Autocorrect
@@ -158,6 +158,10 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
158158
if val != nil {
159159
v = val.Format(time.Stamp)
160160
}
161+
case fmt.Stringer:
162+
if val != nil {
163+
v = val.String()
164+
}
161165
}
162166

163167
rowSlice[i] = v
@@ -301,19 +305,3 @@ func valueToTableMap(val reflect.Value) (map[string]any, error) {
301305

302306
return row, nil
303307
}
304-
305-
func ValidateColumns(all, given []string) error {
306-
for _, col := range given {
307-
found := false
308-
for _, c := range all {
309-
if strings.EqualFold(strings.ReplaceAll(col, "_", " "), c) {
310-
found = true
311-
break
312-
}
313-
}
314-
if !found {
315-
return fmt.Errorf("unknown column: %s", col)
316-
}
317-
}
318-
return nil
319-
}

cli/cliui/table_test.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cliui_test
22

33
import (
4+
"fmt"
45
"log"
56
"strings"
67
"testing"
@@ -12,6 +13,16 @@ import (
1213
"github.com/coder/coder/cli/cliui"
1314
)
1415

16+
type stringWrapper struct {
17+
str string
18+
}
19+
20+
var _ fmt.Stringer = stringWrapper{}
21+
22+
func (s stringWrapper) String() string {
23+
return s.str
24+
}
25+
1526
type tableTest1 struct {
1627
Name string `table:"name"`
1728
NotIncluded string // no table tag
@@ -28,9 +39,9 @@ type tableTest1 struct {
2839
}
2940

3041
type tableTest2 struct {
31-
Name string `table:"name"`
32-
Age int `table:"age"`
33-
NotIncluded string `table:"-"`
42+
Name stringWrapper `table:"name"`
43+
Age int `table:"age"`
44+
NotIncluded string `table:"-"`
3445
}
3546

3647
type tableTest3 struct {
@@ -48,21 +59,21 @@ func Test_DisplayTable(t *testing.T) {
4859
Age: 10,
4960
Roles: []string{"a", "b", "c"},
5061
Sub1: tableTest2{
51-
Name: "foo1",
62+
Name: stringWrapper{str: "foo1"},
5263
Age: 11,
5364
},
5465
Sub2: &tableTest2{
55-
Name: "foo2",
66+
Name: stringWrapper{str: "foo2"},
5667
Age: 12,
5768
},
5869
Sub3: tableTest3{
5970
Sub: tableTest2{
60-
Name: "foo3",
71+
Name: stringWrapper{str: "foo3"},
6172
Age: 13,
6273
},
6374
},
6475
Sub4: tableTest2{
65-
Name: "foo4",
76+
Name: stringWrapper{str: "foo4"},
6677
Age: 14,
6778
},
6879
Time: someTime,
@@ -73,18 +84,18 @@ func Test_DisplayTable(t *testing.T) {
7384
Age: 20,
7485
Roles: []string{"a"},
7586
Sub1: tableTest2{
76-
Name: "bar1",
87+
Name: stringWrapper{str: "bar1"},
7788
Age: 21,
7889
},
7990
Sub2: nil,
8091
Sub3: tableTest3{
8192
Sub: tableTest2{
82-
Name: "bar3",
93+
Name: stringWrapper{str: "bar3"},
8394
Age: 23,
8495
},
8596
},
8697
Sub4: tableTest2{
87-
Name: "bar4",
98+
Name: stringWrapper{str: "bar4"},
8899
Age: 24,
89100
},
90101
Time: someTime,
@@ -95,18 +106,18 @@ func Test_DisplayTable(t *testing.T) {
95106
Age: 30,
96107
Roles: nil,
97108
Sub1: tableTest2{
98-
Name: "baz1",
109+
Name: stringWrapper{str: "baz1"},
99110
Age: 31,
100111
},
101112
Sub2: nil,
102113
Sub3: tableTest3{
103114
Sub: tableTest2{
104-
Name: "baz3",
115+
Name: stringWrapper{str: "baz3"},
105116
Age: 33,
106117
},
107118
},
108119
Sub4: tableTest2{
109-
Name: "baz4",
120+
Name: stringWrapper{str: "baz4"},
110121
Age: 34,
111122
},
112123
Time: someTime,

cli/features.go

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ import (
55
"fmt"
66
"strings"
77

8-
"github.com/coder/coder/cli/cliui"
9-
"github.com/jedib0t/go-pretty/v6/table"
10-
118
"github.com/spf13/cobra"
129
"golang.org/x/xerrors"
1310

11+
"github.com/coder/coder/cli/cliui"
1412
"github.com/coder/coder/codersdk"
1513
)
1614

@@ -38,10 +36,6 @@ func featuresList() *cobra.Command {
3836
Use: "list",
3937
Aliases: []string{"ls"},
4038
RunE: func(cmd *cobra.Command, args []string) error {
41-
err := cliui.ValidateColumns(featureColumns, columns)
42-
if err != nil {
43-
return err
44-
}
4539
client, err := createClient(cmd)
4640
if err != nil {
4741
return err
@@ -54,11 +48,14 @@ func featuresList() *cobra.Command {
5448
out := ""
5549
switch outputFormat {
5650
case "table", "":
57-
out = displayFeatures(columns, entitlements.Features)
51+
out, err = displayFeatures(columns, entitlements.Features)
52+
if err != nil {
53+
return xerrors.Errorf("render table: %w", err)
54+
}
5855
case "json":
5956
outBytes, err := json.Marshal(entitlements)
6057
if err != nil {
61-
return xerrors.Errorf("marshal users to JSON: %w", err)
58+
return xerrors.Errorf("marshal features to JSON: %w", err)
6259
}
6360

6461
out = string(outBytes)
@@ -78,35 +75,28 @@ func featuresList() *cobra.Command {
7875
return cmd
7976
}
8077

78+
type featureRow struct {
79+
Name string `table:"name"`
80+
Entitlement string `table:"entitlement"`
81+
Enabled bool `table:"enabled"`
82+
Limit *int64 `table:"limit"`
83+
Actual *int64 `table:"actual"`
84+
}
85+
8186
// displayFeatures will return a table displaying all features passed in.
8287
// filterColumns must be a subset of the feature fields and will determine which
8388
// columns to display
84-
func displayFeatures(filterColumns []string, features map[string]codersdk.Feature) string {
85-
tableWriter := cliui.Table()
86-
header := table.Row{}
87-
for _, h := range featureColumns {
88-
header = append(header, h)
89-
}
90-
tableWriter.AppendHeader(header)
91-
tableWriter.SetColumnConfigs(cliui.FilterTableColumns(header, filterColumns))
92-
tableWriter.SortBy([]table.SortBy{{
93-
Name: "username",
94-
}})
89+
func displayFeatures(filterColumns []string, features map[string]codersdk.Feature) (string, error) {
90+
rows := make([]featureRow, 0, len(features))
9591
for name, feat := range features {
96-
tableWriter.AppendRow(table.Row{
97-
name,
98-
feat.Entitlement,
99-
feat.Enabled,
100-
intOrNil(feat.Limit),
101-
intOrNil(feat.Actual),
92+
rows = append(rows, featureRow{
93+
Name: name,
94+
Entitlement: string(feat.Entitlement),
95+
Enabled: feat.Enabled,
96+
Limit: feat.Limit,
97+
Actual: feat.Actual,
10298
})
10399
}
104-
return tableWriter.Render()
105-
}
106100

107-
func intOrNil(i *int64) string {
108-
if i == nil {
109-
return ""
110-
}
111-
return fmt.Sprintf("%d", *i)
101+
return cliui.DisplayTable(rows, "name", filterColumns)
112102
}

cli/list.go

Lines changed: 56 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"time"
66

77
"github.com/google/uuid"
8-
"github.com/jedib0t/go-pretty/v6/table"
98
"github.com/spf13/cobra"
109

1110
"github.com/coder/coder/cli/cliui"
@@ -14,6 +13,49 @@ import (
1413
"github.com/coder/coder/codersdk"
1514
)
1615

16+
type workspaceListRow struct {
17+
Workspace string `table:"workspace"`
18+
Template string `table:"template"`
19+
Status string `table:"status"`
20+
LastBuilt string `table:"last built"`
21+
Outdated bool `table:"outdated"`
22+
StartsAt string `table:"starts at"`
23+
StopsAfter string `table:"stops after"`
24+
}
25+
26+
func workspaceListRowFromWorkspace(now time.Time, usersByID map[uuid.UUID]codersdk.User, workspace codersdk.Workspace) workspaceListRow {
27+
status := codersdk.WorkspaceDisplayStatus(workspace.LatestBuild.Job.Status, workspace.LatestBuild.Transition)
28+
29+
lastBuilt := now.UTC().Sub(workspace.LatestBuild.Job.CreatedAt).Truncate(time.Second)
30+
autostartDisplay := "-"
31+
if !ptr.NilOrEmpty(workspace.AutostartSchedule) {
32+
if sched, err := schedule.Weekly(*workspace.AutostartSchedule); err == nil {
33+
autostartDisplay = fmt.Sprintf("%s %s (%s)", sched.Time(), sched.DaysOfWeek(), sched.Location())
34+
}
35+
}
36+
37+
autostopDisplay := "-"
38+
if !ptr.NilOrZero(workspace.TTLMillis) {
39+
dur := time.Duration(*workspace.TTLMillis) * time.Millisecond
40+
autostopDisplay = durationDisplay(dur)
41+
if !workspace.LatestBuild.Deadline.IsZero() && workspace.LatestBuild.Deadline.After(now) && status == "Running" {
42+
remaining := time.Until(workspace.LatestBuild.Deadline)
43+
autostopDisplay = fmt.Sprintf("%s (%s)", autostopDisplay, relative(remaining))
44+
}
45+
}
46+
47+
user := usersByID[workspace.OwnerID]
48+
return workspaceListRow{
49+
Workspace: user.Username + "/" + workspace.Name,
50+
Template: workspace.TemplateName,
51+
Status: status,
52+
LastBuilt: durationDisplay(lastBuilt),
53+
Outdated: workspace.Outdated,
54+
StartsAt: autostartDisplay,
55+
StopsAfter: autostopDisplay,
56+
}
57+
}
58+
1759
func list() *cobra.Command {
1860
var columns []string
1961
cmd := &cobra.Command{
@@ -32,10 +74,10 @@ func list() *cobra.Command {
3274
return err
3375
}
3476
if len(workspaces) == 0 {
35-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"No workspaces found! Create one:")
36-
_, _ = fmt.Fprintln(cmd.OutOrStdout())
37-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+cliui.Styles.Code.Render("coder create <name>"))
38-
_, _ = fmt.Fprintln(cmd.OutOrStdout())
77+
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), cliui.Styles.Prompt.String()+"No workspaces found! Create one:")
78+
_, _ = fmt.Fprintln(cmd.ErrOrStderr())
79+
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), " "+cliui.Styles.Code.Render("coder create <name>"))
80+
_, _ = fmt.Fprintln(cmd.ErrOrStderr())
3981
return nil
4082
}
4183
users, err := client.Users(cmd.Context(), codersdk.UsersRequest{})
@@ -47,48 +89,18 @@ func list() *cobra.Command {
4789
usersByID[user.ID] = user
4890
}
4991

50-
tableWriter := cliui.Table()
51-
header := table.Row{"workspace", "template", "status", "last built", "outdated", "starts at", "stops after"}
52-
tableWriter.AppendHeader(header)
53-
tableWriter.SortBy([]table.SortBy{{
54-
Name: "workspace",
55-
}})
56-
tableWriter.SetColumnConfigs(cliui.FilterTableColumns(header, columns))
57-
5892
now := time.Now()
59-
for _, workspace := range workspaces {
60-
status := codersdk.WorkspaceDisplayStatus(workspace.LatestBuild.Job.Status, workspace.LatestBuild.Transition)
61-
62-
lastBuilt := time.Now().UTC().Sub(workspace.LatestBuild.Job.CreatedAt).Truncate(time.Second)
63-
autostartDisplay := "-"
64-
if !ptr.NilOrEmpty(workspace.AutostartSchedule) {
65-
if sched, err := schedule.Weekly(*workspace.AutostartSchedule); err == nil {
66-
autostartDisplay = fmt.Sprintf("%s %s (%s)", sched.Time(), sched.DaysOfWeek(), sched.Location())
67-
}
68-
}
69-
70-
autostopDisplay := "-"
71-
if !ptr.NilOrZero(workspace.TTLMillis) {
72-
dur := time.Duration(*workspace.TTLMillis) * time.Millisecond
73-
autostopDisplay = durationDisplay(dur)
74-
if !workspace.LatestBuild.Deadline.IsZero() && workspace.LatestBuild.Deadline.After(now) && status == "Running" {
75-
remaining := time.Until(workspace.LatestBuild.Deadline)
76-
autostopDisplay = fmt.Sprintf("%s (%s)", autostopDisplay, relative(remaining))
77-
}
78-
}
93+
displayWorkspaces := make([]workspaceListRow, len(workspaces))
94+
for i, workspace := range workspaces {
95+
displayWorkspaces[i] = workspaceListRowFromWorkspace(now, usersByID, workspace)
96+
}
7997

80-
user := usersByID[workspace.OwnerID]
81-
tableWriter.AppendRow(table.Row{
82-
user.Username + "/" + workspace.Name,
83-
workspace.TemplateName,
84-
status,
85-
durationDisplay(lastBuilt),
86-
workspace.Outdated,
87-
autostartDisplay,
88-
autostopDisplay,
89-
})
98+
out, err := cliui.DisplayTable(displayWorkspaces, "workspace", columns)
99+
if err != nil {
100+
return err
90101
}
91-
_, err = fmt.Fprintln(cmd.OutOrStdout(), tableWriter.Render())
102+
103+
_, err = fmt.Fprintln(cmd.OutOrStdout(), out)
92104
return err
93105
},
94106
}

0 commit comments

Comments
 (0)