diff --git a/cmd/workspace/alerts/overrides.go b/cmd/workspace/alerts/overrides.go index ed5fdaa8db..b7d3a3e3f4 100644 --- a/cmd/workspace/alerts/overrides.go +++ b/cmd/workspace/alerts/overrides.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" ) -func listOverride(listCmd *cobra.Command, listReq *sql.ListAlertsRequest) { +func listOverride(listCmd *cobra.Command, _ *sql.ListAlertsRequest) { listCmd.Annotations["template"] = cmdio.Heredoc(` {{range .}}{{green "%s" .Id}} {{.DisplayName}} {{.State}} {{end}}`) diff --git a/cmd/workspace/apps/overrides.go b/cmd/workspace/apps/overrides.go index 5ccc6024f4..1d1808be0f 100644 --- a/cmd/workspace/apps/overrides.go +++ b/cmd/workspace/apps/overrides.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" ) -func listOverride(listCmd *cobra.Command, listReq *apps.ListAppsRequest) { +func listOverride(listCmd *cobra.Command, _ *apps.ListAppsRequest) { listCmd.Annotations["headerTemplate"] = cmdio.Heredoc(` {{header "Name"}} {{header "Url"}} {{header "ComputeStatus"}} {{header "DeploymentStatus"}}`) listCmd.Annotations["template"] = cmdio.Heredoc(` @@ -41,7 +41,7 @@ func listOverride(listCmd *cobra.Command, listReq *apps.ListAppsRequest) { tableview.RegisterConfig(listCmd, tableview.TableConfig{Columns: columns}) } -func listDeploymentsOverride(listDeploymentsCmd *cobra.Command, listDeploymentsReq *apps.ListAppDeploymentsRequest) { +func listDeploymentsOverride(listDeploymentsCmd *cobra.Command, _ *apps.ListAppDeploymentsRequest) { listDeploymentsCmd.Annotations["headerTemplate"] = cmdio.Heredoc(` {{header "DeploymentId"}} {{header "State"}} {{header "CreatedAt"}}`) listDeploymentsCmd.Annotations["template"] = cmdio.Heredoc(` diff --git a/cmd/workspace/catalogs/overrides.go b/cmd/workspace/catalogs/overrides.go index 46d66a08b2..d86af1eeea 100644 --- a/cmd/workspace/catalogs/overrides.go +++ b/cmd/workspace/catalogs/overrides.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" ) -func listOverride(listCmd *cobra.Command, listReq *catalog.ListCatalogsRequest) { +func listOverride(listCmd *cobra.Command, _ *catalog.ListCatalogsRequest) { listCmd.Annotations["headerTemplate"] = cmdio.Heredoc(` {{header "Name"}} {{header "Type"}} {{header "Comment"}}`) listCmd.Annotations["template"] = cmdio.Heredoc(` diff --git a/cmd/workspace/cluster-policies/overrides.go b/cmd/workspace/cluster-policies/overrides.go index 9278b29c39..8bc320aa5f 100644 --- a/cmd/workspace/cluster-policies/overrides.go +++ b/cmd/workspace/cluster-policies/overrides.go @@ -2,6 +2,7 @@ package cluster_policies import ( "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/tableview" "github.com/databricks/databricks-sdk-go/service/compute" "github.com/spf13/cobra" ) @@ -10,6 +11,23 @@ func listOverride(listCmd *cobra.Command, _ *compute.ListClusterPoliciesRequest) listCmd.Annotations["template"] = cmdio.Heredoc(` {{range .}}{{.PolicyId | green}} {{.Name}} {{end}}`) + + columns := []tableview.ColumnDef{ + {Header: "Policy ID", Extract: func(v any) string { + return v.(compute.Policy).PolicyId + }}, + {Header: "Name", Extract: func(v any) string { + return v.(compute.Policy).Name + }}, + {Header: "Default", Extract: func(v any) string { + if v.(compute.Policy).IsDefault { + return "yes" + } + return "" + }}, + } + + tableview.RegisterConfig(listCmd, tableview.TableConfig{Columns: columns}) } func getOverride(getCmd *cobra.Command, _ *compute.GetClusterPolicyRequest) { diff --git a/cmd/workspace/external-locations/overrides.go b/cmd/workspace/external-locations/overrides.go index 9d9108f5be..607550da3a 100644 --- a/cmd/workspace/external-locations/overrides.go +++ b/cmd/workspace/external-locations/overrides.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" ) -func listOverride(listCmd *cobra.Command, listReq *catalog.ListExternalLocationsRequest) { +func listOverride(listCmd *cobra.Command, _ *catalog.ListExternalLocationsRequest) { listCmd.Annotations["headerTemplate"] = cmdio.Heredoc(` {{header "Name"}} {{header "Credential"}} {{header "URL"}}`) listCmd.Annotations["template"] = cmdio.Heredoc(` diff --git a/cmd/workspace/jobs/overrides.go b/cmd/workspace/jobs/overrides.go index 101226e27c..49ea37a1ce 100644 --- a/cmd/workspace/jobs/overrides.go +++ b/cmd/workspace/jobs/overrides.go @@ -42,7 +42,7 @@ func listOverride(listCmd *cobra.Command, listReq *jobs.ListJobsRequest) { }) } -func listRunsOverride(listRunsCmd *cobra.Command, listRunsReq *jobs.ListRunsRequest) { +func listRunsOverride(listRunsCmd *cobra.Command, _ *jobs.ListRunsRequest) { listRunsCmd.Annotations["headerTemplate"] = cmdio.Heredoc(` {{header "Job ID"}} {{header "Run ID"}} {{header "Result State"}} URL`) listRunsCmd.Annotations["template"] = cmdio.Heredoc(` diff --git a/cmd/workspace/lakeview/overrides.go b/cmd/workspace/lakeview/overrides.go index 6ffb641aa9..55357f703d 100644 --- a/cmd/workspace/lakeview/overrides.go +++ b/cmd/workspace/lakeview/overrides.go @@ -1,10 +1,34 @@ package lakeview import ( + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/tableview" "github.com/databricks/databricks-sdk-go/service/dashboards" "github.com/spf13/cobra" ) +func listOverride(listCmd *cobra.Command, _ *dashboards.ListDashboardsRequest) { + listCmd.Annotations["headerTemplate"] = cmdio.Heredoc(` + {{header "Dashboard ID"}} {{header "Name"}} {{header "State"}}`) + listCmd.Annotations["template"] = cmdio.Heredoc(` + {{range .}}{{green "%s" .DashboardId}} {{.DisplayName}} {{blue "%s" .LifecycleState}} + {{end}}`) + + columns := []tableview.ColumnDef{ + {Header: "Dashboard ID", Extract: func(v any) string { + return v.(dashboards.Dashboard).DashboardId + }}, + {Header: "Name", Extract: func(v any) string { + return v.(dashboards.Dashboard).DisplayName + }}, + {Header: "State", Extract: func(v any) string { + return string(v.(dashboards.Dashboard).LifecycleState) + }}, + } + + tableview.RegisterConfig(listCmd, tableview.TableConfig{Columns: columns}) +} + func publishOverride(cmd *cobra.Command, req *dashboards.PublishRequest) { originalRunE := cmd.RunE cmd.RunE = func(cmd *cobra.Command, args []string) error { @@ -15,5 +39,6 @@ func publishOverride(cmd *cobra.Command, req *dashboards.PublishRequest) { } func init() { + listOverrides = append(listOverrides, listOverride) publishOverrides = append(publishOverrides, publishOverride) } diff --git a/cmd/workspace/pipelines/overrides.go b/cmd/workspace/pipelines/overrides.go index 89f00dd64c..5187e64fbd 100644 --- a/cmd/workspace/pipelines/overrides.go +++ b/cmd/workspace/pipelines/overrides.go @@ -54,8 +54,34 @@ func listPipelinesOverride(listCmd *cobra.Command, listReq *pipelines.ListPipeli }) } +func listPipelineEventsOverride(listCmd *cobra.Command, _ *pipelines.ListPipelineEventsRequest) { + listCmd.Annotations["headerTemplate"] = cmdio.Heredoc(` + {{header "Timestamp"}} {{header "Level"}} {{header "Event Type"}} {{header "Message"}}`) + listCmd.Annotations["template"] = cmdio.Heredoc(` + {{range .}}{{.Timestamp}} {{.Level}} {{.EventType}} {{.Message}} + {{end}}`) + + columns := []tableview.ColumnDef{ + {Header: "Timestamp", Extract: func(v any) string { + return v.(pipelines.PipelineEvent).Timestamp + }}, + {Header: "Level", Extract: func(v any) string { + return string(v.(pipelines.PipelineEvent).Level) + }}, + {Header: "Event Type", Extract: func(v any) string { + return v.(pipelines.PipelineEvent).EventType + }}, + {Header: "Message", MaxWidth: 60, Extract: func(v any) string { + return v.(pipelines.PipelineEvent).Message + }}, + } + + tableview.RegisterConfig(listCmd, tableview.TableConfig{Columns: columns}) +} + func init() { listPipelinesOverrides = append(listPipelinesOverrides, listPipelinesOverride) + listPipelineEventsOverrides = append(listPipelineEventsOverrides, listPipelineEventsOverride) cmdOverrides = append(cmdOverrides, func(cli *cobra.Command) { // all auto-generated commands apart from nonManagementCommands go into 'management' group diff --git a/cmd/workspace/repos/overrides.go b/cmd/workspace/repos/overrides.go index 0b3852d0ff..b5bf428284 100644 --- a/cmd/workspace/repos/overrides.go +++ b/cmd/workspace/repos/overrides.go @@ -17,7 +17,7 @@ import ( "github.com/spf13/cobra" ) -func listOverride(listCmd *cobra.Command, listReq *workspace.ListReposRequest) { +func listOverride(listCmd *cobra.Command, _ *workspace.ListReposRequest) { listCmd.Annotations["template"] = cmdio.Heredoc(` {{range .}}{{green "%d" .Id}} {{.Path}} {{.Branch|blue}} {{.Url|cyan}} {{end}}`) diff --git a/cmd/workspace/schemas/overrides.go b/cmd/workspace/schemas/overrides.go index 625c92f3d7..0e9b1b03b9 100644 --- a/cmd/workspace/schemas/overrides.go +++ b/cmd/workspace/schemas/overrides.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" ) -func listOverride(listCmd *cobra.Command, listReq *catalog.ListSchemasRequest) { +func listOverride(listCmd *cobra.Command, _ *catalog.ListSchemasRequest) { listCmd.Annotations["headerTemplate"] = cmdio.Heredoc(` {{header "Full Name"}} {{header "Owner"}} {{header "Comment"}}`) listCmd.Annotations["template"] = cmdio.Heredoc(` diff --git a/cmd/workspace/secrets/overrides.go b/cmd/workspace/secrets/overrides.go index b215f17a7f..5de7268905 100644 --- a/cmd/workspace/secrets/overrides.go +++ b/cmd/workspace/secrets/overrides.go @@ -1,7 +1,10 @@ package secrets import ( + "time" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/tableview" "github.com/databricks/databricks-sdk-go/service/workspace" "github.com/spf13/cobra" ) @@ -16,6 +19,17 @@ func listScopesOverride(listScopesCmd *cobra.Command) { listScopesCmd.Annotations["template"] = cmdio.Heredoc(` {{range .}}{{.Name|green}} {{.BackendType}} {{end}}`) + + columns := []tableview.ColumnDef{ + {Header: "Scope", Extract: func(v any) string { + return v.(workspace.SecretScope).Name + }}, + {Header: "Backend Type", Extract: func(v any) string { + return string(v.(workspace.SecretScope).BackendType) + }}, + } + + tableview.RegisterConfig(listScopesCmd, tableview.TableConfig{Columns: columns}) } func listSecretsOverride(listSecretsCommand *cobra.Command, _ *workspace.ListSecretsRequest) { @@ -24,6 +38,21 @@ func listSecretsOverride(listSecretsCommand *cobra.Command, _ *workspace.ListSec listSecretsCommand.Annotations["template"] = cmdio.Heredoc(` {{range .}}{{.Key|green}} {{.LastUpdatedTimestamp}} {{end}}`) + + columns := []tableview.ColumnDef{ + {Header: "Key", Extract: func(v any) string { + return v.(workspace.SecretMetadata).Key + }}, + {Header: "Last Updated", Extract: func(v any) string { + ts := v.(workspace.SecretMetadata).LastUpdatedTimestamp + if ts == 0 { + return "" + } + return time.UnixMilli(ts).UTC().Format("2006-01-02 15:04:05") + }}, + } + + tableview.RegisterConfig(listSecretsCommand, tableview.TableConfig{Columns: columns}) } func init() { diff --git a/cmd/workspace/tables/overrides.go b/cmd/workspace/tables/overrides.go index 157d62daf9..8e0987d469 100644 --- a/cmd/workspace/tables/overrides.go +++ b/cmd/workspace/tables/overrides.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" ) -func listOverride(listCmd *cobra.Command, listReq *catalog.ListTablesRequest) { +func listOverride(listCmd *cobra.Command, _ *catalog.ListTablesRequest) { listCmd.Annotations["headerTemplate"] = cmdio.Heredoc(` {{header "Full Name"}} {{header "Table Type"}}`) listCmd.Annotations["template"] = cmdio.Heredoc(` diff --git a/cmd/workspace/volumes/overrides.go b/cmd/workspace/volumes/overrides.go index 0a4f645de3..66b946f2ea 100644 --- a/cmd/workspace/volumes/overrides.go +++ b/cmd/workspace/volumes/overrides.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" ) -func listOverride(listCmd *cobra.Command, listReq *catalog.ListVolumesRequest) { +func listOverride(listCmd *cobra.Command, _ *catalog.ListVolumesRequest) { listCmd.Annotations["template"] = cmdio.Heredoc(` {{range .}}{{green "%s" .Name}} {{.VolumeType}} {{.FullName}} {{end}}`) diff --git a/cmd/workspace/warehouses/overrides.go b/cmd/workspace/warehouses/overrides.go index edc58ad681..14b2635a04 100644 --- a/cmd/workspace/warehouses/overrides.go +++ b/cmd/workspace/warehouses/overrides.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" ) -func listOverride(listCmd *cobra.Command, listReq *sql.ListWarehousesRequest) { +func listOverride(listCmd *cobra.Command, _ *sql.ListWarehousesRequest) { listCmd.Annotations["headerTemplate"] = cmdio.Heredoc(` {{header "ID"}} {{header "Name"}} {{header "Size"}} {{header "State"}}`) listCmd.Annotations["template"] = cmdio.Heredoc(` diff --git a/libs/tableview/config.go b/libs/tableview/config.go index c933f08e87..f6ba32a41a 100644 --- a/libs/tableview/config.go +++ b/libs/tableview/config.go @@ -4,8 +4,11 @@ import "context" // ColumnDef defines a column in the TUI table. type ColumnDef struct { - Header string // Display name in header row. - MaxWidth int // Max cell width; 0 = default (50). + Header string // Display name in header row. + // MaxWidth caps cell display width; 0 = default (50). Values exceeding + // this limit are destructively truncated with "..." in the rendered + // output. Horizontal scrolling does not recover the hidden portion. + MaxWidth int Extract func(v any) string // Extracts cell value from typed SDK struct. } diff --git a/libs/tableview/paginated.go b/libs/tableview/paginated.go index 21872ad059..788ca07437 100644 --- a/libs/tableview/paginated.go +++ b/libs/tableview/paginated.go @@ -40,6 +40,10 @@ type searchDebounceMsg struct { seq int } +// PaginatedModel is the exported alias used by callers (e.g. RenderIterator) +// to inspect the final model returned by tea.Program.Run(). +type PaginatedModel = paginatedModel + type paginatedModel struct { cfg *TableConfig headers []string @@ -77,6 +81,11 @@ type paginatedModel struct { limitReached bool } +// Err returns the error recorded during data fetching, if any. +func (m paginatedModel) Err() error { + return m.err +} + // newFetchCmdFunc returns a closure that creates fetch commands, capturing ctx. func newFetchCmdFunc(ctx context.Context) func(paginatedModel) tea.Cmd { return func(m paginatedModel) tea.Cmd { @@ -151,13 +160,16 @@ func NewPaginatedProgram(ctx context.Context, w io.Writer, cfg *TableConfig, ite // RunPaginated launches the paginated TUI table. func RunPaginated(ctx context.Context, w io.Writer, cfg *TableConfig, iter RowIterator, maxItems int) error { p := NewPaginatedProgram(ctx, w, cfg, iter, maxItems) - _, err := p.Run() - return err -} - -// Err returns any error that occurred during data fetching. -func (m paginatedModel) Err() error { - return m.err + finalModel, err := p.Run() + if err != nil { + return err + } + if m, ok := finalModel.(PaginatedModel); ok { + if fetchErr := m.Err(); fetchErr != nil { + return fetchErr + } + } + return nil } func (m paginatedModel) Init() tea.Cmd { @@ -263,7 +275,12 @@ func (m paginatedModel) renderContent() string { } fmt.Fprintln(tw, strings.Join(seps, "\t")) - // Data rows + // Data rows. + // NOTE: MaxWidth truncation here is destructive, not display wrapping. + // Values exceeding MaxWidth are cut and suffixed with "..." in the + // rendered output. Horizontal scrolling cannot recover the hidden tail. + // A future improvement could store full values and only truncate the + // visible slice, but that requires per-cell width tracking. for _, row := range m.rows { vals := make([]string, len(m.headers)) for i := range m.headers { diff --git a/libs/tableview/paginated_test.go b/libs/tableview/paginated_test.go index c7add1aacd..d839e5f9a1 100644 --- a/libs/tableview/paginated_test.go +++ b/libs/tableview/paginated_test.go @@ -116,6 +116,14 @@ func TestPaginatedFetchError(t *testing.T) { assert.Equal(t, "network error", pm.err.Error()) } +func TestPaginatedErrAccessor(t *testing.T) { + m := newTestModel(t, nil, 0) + assert.NoError(t, m.Err()) + + m.err = errors.New("api timeout") + assert.EqualError(t, m.Err(), "api timeout") +} + func TestPaginatedCursorMovement(t *testing.T) { m := newTestModel(t, nil, 0) m.ready = true