Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
e672906
Add endless-scrolling TUI table for interactive list commands
simonfaltum Mar 12, 2026
d7a7104
Fix stale fetch race condition and empty-table search restore
simonfaltum Mar 13, 2026
8b589d1
Merge branch 'main' into simonfaltum/list-tui-paginated
simonfaltum Mar 13, 2026
b7ca3d8
Add debounced live search to paginated TUI table
simonfaltum Mar 13, 2026
aa4cdb0
Fix search/fetch race conditions, esc restore, and error propagation
simonfaltum Mar 13, 2026
dbd2241
Fix lint: use assert.NoError per testifylint rule
simonfaltum Mar 13, 2026
831b105
Merge branch 'main' into simonfaltum/list-tui-paginated
simonfaltum Mar 14, 2026
229baa9
Fix UTF-8 backspace corruption and fragile typing check in search
simonfaltum Mar 16, 2026
78e6720
Fix RunPaginated silently dropping model-level fetch errors
simonfaltum Mar 16, 2026
78b56b7
Recompute column widths on every batch, not just the first
simonfaltum Mar 16, 2026
96e614a
Extract restorePreSearchState to fix DRY violation and stuck loading
simonfaltum Mar 16, 2026
3143a72
Show fetch errors in footer instead of replacing loaded data
simonfaltum Mar 16, 2026
a548b79
Fix sticky errors, missing space input, and search/fetch race in TUI …
simonfaltum Mar 16, 2026
43c526d
Fix search cancel silently dropping in-flight fetch rows
simonfaltum Mar 16, 2026
4281953
Fix search/fetch race: preserve loading state when no search was active
simonfaltum Mar 16, 2026
b3a6a53
Fix loading state stuck after canceling search without executing
simonfaltum Mar 16, 2026
7b4c0ee
Separate search and loading concerns in paginated model
simonfaltum Mar 16, 2026
aaf150e
Fix lint: remove extra blank line, convert if/else to switch
simonfaltum Mar 16, 2026
5709b10
Fix exhaustive lint: replace switch on tea.KeyType with if-else
simonfaltum Mar 17, 2026
ed23efc
Merge branch 'main' into simonfaltum/list-tui-paginated
simonfaltum Mar 18, 2026
c7fb162
Add curated TUI table overrides for list commands (#4731)
simonfaltum Mar 19, 2026
ffe9d6a
Merge branch 'main' into simonfaltum/list-tui-paginated
simonfaltum Mar 20, 2026
ed87880
Merge branch 'main' into simonfaltum/list-tui-paginated
simonfaltum Mar 22, 2026
8d63e1a
Add TUI table overrides for high-traffic list commands (#4732)
simonfaltum Mar 23, 2026
649f435
Merge branch 'main' into simonfaltum/list-tui-paginated
simonfaltum Mar 25, 2026
ff16f2b
Fix UTF-8 truncation, context cancellation masking, and dead code
simonfaltum Mar 25, 2026
f755a0e
Fix search highlighting misalignment, stale limitReached, and missing…
simonfaltum Mar 25, 2026
be88c2e
Merge branch 'main' into simonfaltum/list-tui-paginated
simonfaltum Mar 25, 2026
673d24e
Merge remote-tracking branch 'origin/main' into simonfaltum/list-tui-…
simonfaltum Apr 6, 2026
a9cc00e
Merge branch 'main' into simonfaltum/list-tui-paginated
simonfaltum Apr 9, 2026
d4fe4fb
Fix iterator data race: set loading=true before Init() fires first fetch
simonfaltum Apr 14, 2026
c185c98
Merge branch 'main' into simonfaltum/list-tui-paginated
simonfaltum Apr 14, 2026
8e7674f
Merge branch 'main' into simonfaltum/list-tui-paginated
simonfaltum Apr 15, 2026
bcf339d
Address review feedback: fix linter errors, unify command plumbing, i…
simonfaltum Apr 15, 2026
d967b28
Address review nits: add missing override comments, document RenderIt…
simonfaltum Apr 15, 2026
046f656
Replace global tableview registry with context-based config
simonfaltum Apr 15, 2026
da84d97
Fix context propagation: inject table config at execution time via Pr…
simonfaltum Apr 15, 2026
baaf1a8
Extract shared computeColumnWidths to deduplicate width computation
simonfaltum Apr 15, 2026
4404611
Extract shared renderTableToLines to deduplicate table rendering
simonfaltum Apr 15, 2026
5767ea3
Consolidate maybeFetch tests, replace iterator panics with errors
simonfaltum Apr 15, 2026
c0f314e
Address review findings: search UX, unused param, sanitize dedup
simonfaltum Apr 15, 2026
a8b2626
Merge branch 'main' into simonfaltum/list-tui-paginated
simonfaltum Apr 15, 2026
56e3829
Merge branch 'main' into simonfaltum/list-tui-paginated
simonfaltum Apr 17, 2026
4981680
Add generic Col helper to reduce TableConfig boilerplate
simonfaltum Apr 17, 2026
e269ea3
Consolidate paginated search state into searchState struct
simonfaltum Apr 17, 2026
83662c5
Merge branch 'main' into simonfaltum/list-tui-paginated
simonfaltum Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions cmd/workspace/alerts/overrides.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package alerts

import (
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/tableview"
"github.com/databricks/databricks-sdk-go/service/sql"
"github.com/spf13/cobra"
)

func listOverride(listCmd *cobra.Command, _ *sql.ListAlertsRequest) {
// Template is the text-mode fallback for non-interactive/piped output.
// TableConfig drives the interactive TUI when the terminal supports it.
listCmd.Annotations["template"] = cmdio.Heredoc(`
{{range .}}{{green "%s" .Id}} {{.DisplayName}} {{.State}}
{{end}}`)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need the template if we also use the tableview?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the template is the fallback for non-interactive output (piped, --output text, non-TTY). The TUI only runs when SupportsTUI() is true. Added a comment to all overrides clarifying this.


columns := []tableview.ColumnDef{
tableview.Col("ID", func(a sql.ListAlertsResponseAlert) string { return a.Id }),
tableview.Col("Name", func(a sql.ListAlertsResponseAlert) string { return a.DisplayName }),
tableview.Col("State", func(a sql.ListAlertsResponseAlert) string { return string(a.State) }),
}

tableview.SetTableConfigOnCmd(listCmd, &tableview.TableConfig{Columns: columns})
}

func init() {
listOverrides = append(listOverrides, listOverride)
}
26 changes: 24 additions & 2 deletions cmd/workspace/apps/overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,41 @@ import (

appsCli "github.com/databricks/cli/cmd/apps"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/tableview"
"github.com/databricks/databricks-sdk-go/service/apps"
"github.com/spf13/cobra"
)

func listOverride(listCmd *cobra.Command, listReq *apps.ListAppsRequest) {
func listOverride(listCmd *cobra.Command, _ *apps.ListAppsRequest) {
// Template is the text-mode fallback for non-interactive/piped output.
// TableConfig drives the interactive TUI when the terminal supports it.
listCmd.Annotations["headerTemplate"] = cmdio.Heredoc(`
{{header "Name"}} {{header "Url"}} {{header "ComputeStatus"}} {{header "DeploymentStatus"}}`)
listCmd.Annotations["template"] = cmdio.Heredoc(`
{{range .}}{{.Name | green}} {{.Url}} {{if .ComputeStatus}}{{if eq .ComputeStatus.State "ACTIVE"}}{{green "%s" .ComputeStatus.State }}{{else}}{{blue "%s" .ComputeStatus.State}}{{end}}{{end}} {{if .ActiveDeployment}}{{if eq .ActiveDeployment.Status.State "SUCCEEDED"}}{{green "%s" .ActiveDeployment.Status.State }}{{else}}{{blue "%s" .ActiveDeployment.Status.State}}{{end}}{{end}}
{{end}}`)

columns := []tableview.ColumnDef{
tableview.Col("Name", func(a apps.App) string { return a.Name }),
tableview.Col("URL", func(a apps.App) string { return a.Url }),
tableview.Col("Compute Status", func(a apps.App) string {
if a.ComputeStatus != nil {
return string(a.ComputeStatus.State)
}
return ""
}),
tableview.Col("Deploy Status", func(a apps.App) string {
if a.ActiveDeployment != nil && a.ActiveDeployment.Status != nil {
return string(a.ActiveDeployment.Status.State)
}
return ""
}),
}

tableview.SetTableConfigOnCmd(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(`
Expand Down
68 changes: 68 additions & 0 deletions cmd/workspace/apps/overrides_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package apps

import (
"testing"

"github.com/databricks/cli/libs/tableview"
sdkapps "github.com/databricks/databricks-sdk-go/service/apps"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestListTableConfig(t *testing.T) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why test here and not other commands?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apps has nested pointer dereferences (ComputeStatus.State, ActiveDeployment.Status.State) that can nil-panic if the Extract functions aren't careful. The other overrides are single-field type assertions so there's less to go wrong.

cmd := newList()

cfg := tableview.GetTableConfigForCmd(cmd)
require.NotNil(t, cfg)
require.Len(t, cfg.Columns, 4)

tests := []struct {
name string
app sdkapps.App
wantName string
wantURL string
wantCompute string
wantDeploy string
}{
{
name: "with nested fields",
app: sdkapps.App{
Name: "test-app",
Url: "https://example.com",
ComputeStatus: &sdkapps.ComputeStatus{
State: sdkapps.ComputeStateActive,
},
ActiveDeployment: &sdkapps.AppDeployment{
Status: &sdkapps.AppDeploymentStatus{
State: sdkapps.AppDeploymentStateSucceeded,
},
},
},
wantName: "test-app",
wantURL: "https://example.com",
wantCompute: "ACTIVE",
wantDeploy: "SUCCEEDED",
},
{
name: "nil nested fields",
app: sdkapps.App{
Name: "test-app",
Url: "https://example.com",
ActiveDeployment: &sdkapps.AppDeployment{},
},
wantName: "test-app",
wantURL: "https://example.com",
wantCompute: "",
wantDeploy: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.wantName, cfg.Columns[0].Extract(tt.app))
assert.Equal(t, tt.wantURL, cfg.Columns[1].Extract(tt.app))
assert.Equal(t, tt.wantCompute, cfg.Columns[2].Extract(tt.app))
assert.Equal(t, tt.wantDeploy, cfg.Columns[3].Extract(tt.app))
})
}
}
13 changes: 12 additions & 1 deletion cmd/workspace/catalogs/overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@ package catalogs

import (
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/tableview"
"github.com/databricks/databricks-sdk-go/service/catalog"
"github.com/spf13/cobra"
)

func listOverride(listCmd *cobra.Command, listReq *catalog.ListCatalogsRequest) {
func listOverride(listCmd *cobra.Command, _ *catalog.ListCatalogsRequest) {
// Template is the text-mode fallback for non-interactive/piped output.
// TableConfig drives the interactive TUI when the terminal supports it.
listCmd.Annotations["headerTemplate"] = cmdio.Heredoc(`
{{header "Name"}} {{header "Type"}} {{header "Comment"}}`)
listCmd.Annotations["template"] = cmdio.Heredoc(`
{{range .}}{{.Name|green}} {{blue "%s" .CatalogType}} {{.Comment}}
{{end}}`)

columns := []tableview.ColumnDef{
tableview.Col("Name", func(c catalog.CatalogInfo) string { return c.Name }),
tableview.Col("Type", func(c catalog.CatalogInfo) string { return string(c.CatalogType) }),
tableview.ColMax("Comment", 40, func(c catalog.CatalogInfo) string { return c.Comment }),
}

tableview.SetTableConfigOnCmd(listCmd, &tableview.TableConfig{Columns: columns})
}

func init() {
Expand Down
16 changes: 16 additions & 0 deletions cmd/workspace/cluster-policies/overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,30 @@ 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"
)

func listOverride(listCmd *cobra.Command, _ *compute.ListClusterPoliciesRequest) {
// Template is the text-mode fallback for non-interactive/piped output.
// TableConfig drives the interactive TUI when the terminal supports it.
listCmd.Annotations["template"] = cmdio.Heredoc(`
{{range .}}{{.PolicyId | green}} {{.Name}}
{{end}}`)

columns := []tableview.ColumnDef{
tableview.Col("Policy ID", func(p compute.Policy) string { return p.PolicyId }),
tableview.Col("Name", func(p compute.Policy) string { return p.Name }),
tableview.Col("Default", func(p compute.Policy) string {
if p.IsDefault {
return "yes"
}
return ""
}),
}

tableview.SetTableConfigOnCmd(listCmd, &tableview.TableConfig{Columns: columns})
}

func getOverride(getCmd *cobra.Command, _ *compute.GetClusterPolicyRequest) {
Expand Down
11 changes: 11 additions & 0 deletions cmd/workspace/clusters/overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,30 @@ import (
"strings"

"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/tableview"
"github.com/databricks/databricks-sdk-go/service/compute"
"github.com/spf13/cobra"
)

// Below we add overrides for filter flags for cluster list command to allow for custom filtering
// Auto generating such flags is not yet supported by the CLI generator
func listOverride(listCmd *cobra.Command, listReq *compute.ListClustersRequest) {
// Template is the text-mode fallback for non-interactive/piped output.
// TableConfig drives the interactive TUI when the terminal supports it.
listCmd.Annotations["headerTemplate"] = cmdio.Heredoc(`
{{header "ID"}} {{header "Name"}} {{header "State"}}`)
listCmd.Annotations["template"] = cmdio.Heredoc(`
{{range .}}{{.ClusterId | green}} {{.ClusterName | cyan}} {{if eq .State "RUNNING"}}{{green "%s" .State}}{{else if eq .State "TERMINATED"}}{{red "%s" .State}}{{else}}{{blue "%s" .State}}{{end}}
{{end}}`)

columns := []tableview.ColumnDef{
tableview.Col("Cluster ID", func(c compute.ClusterDetails) string { return c.ClusterId }),
tableview.Col("Name", func(c compute.ClusterDetails) string { return c.ClusterName }),
tableview.Col("State", func(c compute.ClusterDetails) string { return string(c.State) }),
}

tableview.SetTableConfigOnCmd(listCmd, &tableview.TableConfig{Columns: columns})

listReq.FilterBy = &compute.ListClustersFilterBy{}
listCmd.Flags().BoolVar(&listReq.FilterBy.IsPinned, "is-pinned", false, "Filter clusters by pinned status")
listCmd.Flags().StringVar(&listReq.FilterBy.PolicyId, "policy-id", "", "Filter clusters by policy id")
Expand Down
13 changes: 12 additions & 1 deletion cmd/workspace/external-locations/overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@ package external_locations

import (
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/tableview"
"github.com/databricks/databricks-sdk-go/service/catalog"
"github.com/spf13/cobra"
)

func listOverride(listCmd *cobra.Command, listReq *catalog.ListExternalLocationsRequest) {
func listOverride(listCmd *cobra.Command, _ *catalog.ListExternalLocationsRequest) {
// Template is the text-mode fallback for non-interactive/piped output.
// TableConfig drives the interactive TUI when the terminal supports it.
listCmd.Annotations["headerTemplate"] = cmdio.Heredoc(`
{{header "Name"}} {{header "Credential"}} {{header "URL"}}`)
listCmd.Annotations["template"] = cmdio.Heredoc(`
{{range .}}{{.Name|green}} {{.CredentialName|cyan}} {{.Url}}
{{end}}`)

columns := []tableview.ColumnDef{
tableview.Col("Name", func(l catalog.ExternalLocationInfo) string { return l.Name }),
tableview.Col("Credential", func(l catalog.ExternalLocationInfo) string { return l.CredentialName }),
tableview.Col("URL", func(l catalog.ExternalLocationInfo) string { return l.Url }),
}

tableview.SetTableConfigOnCmd(listCmd, &tableview.TableConfig{Columns: columns})
}

func init() {
Expand Down
13 changes: 13 additions & 0 deletions cmd/workspace/instance-pools/overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,26 @@ package instance_pools

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"
)

func listOverride(listCmd *cobra.Command) {
// Template is the text-mode fallback for non-interactive/piped output.
// TableConfig drives the interactive TUI when the terminal supports it.
listCmd.Annotations["template"] = cmdio.Heredoc(`
{{range .}}{{.InstancePoolId|green}} {{.InstancePoolName}} {{.NodeTypeId}} {{.State}}
{{end}}`)

columns := []tableview.ColumnDef{
tableview.Col("Pool ID", func(p compute.InstancePoolAndStats) string { return p.InstancePoolId }),
tableview.Col("Name", func(p compute.InstancePoolAndStats) string { return p.InstancePoolName }),
tableview.Col("Node Type", func(p compute.InstancePoolAndStats) string { return p.NodeTypeId }),
tableview.Col("State", func(p compute.InstancePoolAndStats) string { return string(p.State) }),
}

tableview.SetTableConfigOnCmd(listCmd, &tableview.TableConfig{Columns: columns})
}

func init() {
Expand Down
34 changes: 33 additions & 1 deletion cmd/workspace/jobs/overrides.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,50 @@
package jobs

import (
"context"
"strconv"

"github.com/databricks/cli/libs/cmdctx"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/tableview"
"github.com/databricks/databricks-sdk-go/service/jobs"
"github.com/spf13/cobra"
)

func listOverride(listCmd *cobra.Command, listReq *jobs.ListJobsRequest) {
// Template is the text-mode fallback for non-interactive/piped output.
// TableConfig drives the interactive TUI when the terminal supports it.
listCmd.Annotations["template"] = cmdio.Heredoc(`
{{range .}}{{green "%d" .JobId}} {{.Settings.Name}}
{{end}}`)

columns := []tableview.ColumnDef{
tableview.Col("Job ID", func(j jobs.BaseJob) string { return strconv.FormatInt(j.JobId, 10) }),
tableview.Col("Name", func(j jobs.BaseJob) string {
if j.Settings != nil {
return j.Settings.Name
}
return ""
}),
}

tableview.SetTableConfigOnCmd(listCmd, &tableview.TableConfig{
Columns: columns,
Search: &tableview.SearchConfig{
Placeholder: "Search by exact name...",
NewIterator: func(ctx context.Context, query string) tableview.RowIterator {
req := *listReq
req.Name = query
req.PageToken = ""
req.Offset = 0
w := cmdctx.WorkspaceClient(ctx)
return tableview.WrapIterator(w.Jobs.List(ctx, req), columns)
},
},
})
}

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(`
Expand Down
50 changes: 50 additions & 0 deletions cmd/workspace/jobs/overrides_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package jobs

import (
"testing"

"github.com/databricks/cli/libs/tableview"
sdkjobs "github.com/databricks/databricks-sdk-go/service/jobs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestListTableConfig(t *testing.T) {
cmd := newList()

cfg := tableview.GetTableConfigForCmd(cmd)
require.NotNil(t, cfg)
require.Len(t, cfg.Columns, 2)

tests := []struct {
name string
job sdkjobs.BaseJob
wantID string
wantName string
}{
{
name: "with settings",
job: sdkjobs.BaseJob{
JobId: 123,
Settings: &sdkjobs.JobSettings{Name: "test-job"},
},
wantID: "123",
wantName: "test-job",
},
{
name: "nil settings",
job: sdkjobs.BaseJob{
JobId: 456,
},
wantID: "456",
wantName: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.wantID, cfg.Columns[0].Extract(tt.job))
assert.Equal(t, tt.wantName, cfg.Columns[1].Extract(tt.job))
})
}
}
Loading
Loading