From 1fa9c6150802c1f755357705c2853038b474a95e Mon Sep 17 00:00:00 2001 From: Ventsislav Georgiev Date: Sat, 15 Jun 2024 16:53:47 +0300 Subject: [PATCH] feat(sort): allow sorting any column with top like navigation --- internal/ui/key.go | 12 +++-- internal/ui/table.go | 84 ++++++++++++++++++++++++++--- internal/ui/types.go | 10 ++++ internal/view/alias_test.go | 2 +- internal/view/cm_test.go | 2 +- internal/view/container_test.go | 2 +- internal/view/context_test.go | 2 +- internal/view/dir_test.go | 2 +- internal/view/dp_test.go | 2 +- internal/view/ds_test.go | 2 +- internal/view/help_test.go | 2 +- internal/view/ns_test.go | 2 +- internal/view/pf_test.go | 2 +- internal/view/pod_test.go | 2 +- internal/view/priorityclass_test.go | 2 +- internal/view/pvc_test.go | 2 +- internal/view/rbac_test.go | 2 +- internal/view/reference_test.go | 2 +- internal/view/screen_dump_test.go | 2 +- internal/view/secret_test.go | 2 +- internal/view/sts_test.go | 2 +- internal/view/svc_test.go | 2 +- internal/view/table.go | 3 ++ 23 files changed, 117 insertions(+), 30 deletions(-) diff --git a/internal/ui/key.go b/internal/ui/key.go index ff90b310ba..60834378cd 100644 --- a/internal/ui/key.go +++ b/internal/ui/key.go @@ -13,6 +13,8 @@ func initKeys() { tcell.KeyNames[KeyHelp] = "?" tcell.KeyNames[KeySlash] = "/" tcell.KeyNames[KeySpace] = "space" + tcell.KeyNames[KeyLess] = "<" + tcell.KeyNames[KeyGreater] = ">" initNumbKeys() initStdKeys() @@ -76,10 +78,12 @@ const ( KeyX KeyY KeyZ - KeyHelp = 63 - KeySlash = 47 - KeyColon = 58 - KeySpace = 32 + KeyHelp = 63 + KeySlash = 47 + KeyColon = 58 + KeySpace = 32 + KeyLess = 60 + KeyGreater = 62 ) // Define Shift Keys. diff --git a/internal/ui/table.go b/internal/ui/table.go index a1cd7365ef..dc9154f6e6 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -254,6 +254,26 @@ func (t *Table) SetSortCol(name string, asc bool) { t.setSortCol(model1.SortColumn{Name: name, ASC: asc}) } +func (t *Table) GetSortCol() (string, bool) { + return t.sortCol.Name, t.sortCol.ASC +} + +func (t *Table) isVisible(h model1.HeaderColumn) bool { + if h.Name == "NAMESPACE" && !t.GetModel().ClusterWide() { + return false + } + if h.MX && !t.hasMetrics { + return false + } + if h.Wide && !t.wide { + return false + } + if h.VS && vul.ImgScanner == nil { + return false + } + return true +} + // Update table content. func (t *Table) Update(data *model1.TableData, hasMetrics bool) *model1.TableData { if t.decorateFn != nil { @@ -321,6 +341,20 @@ func (t *Table) UpdateUI(cdata, data *model1.TableData) { return true }) + colIndex, ok := cdata.Header().IndexOf(t.sortCol.Name, false) + if ok { + events := cdata.GetRowEvents() + events.Sort( + cdata.GetNamespace(), + colIndex, + t.sortCol.Name == "AGE", + data.Header().IsMetricsCol(colIndex), + t.sortCol.Name == "CAPACITY", + t.sortCol.ASC, + ) + cdata.SetRowEvents(events) + } + t.updateSelection(true) t.UpdateTitle() } @@ -343,13 +377,7 @@ func (t *Table) buildRow(r int, re, ore model1.RowEvent, h model1.Header, pads M continue } - if h[c].Name == "NAMESPACE" && !t.GetModel().ClusterWide() { - continue - } - if h[c].MX && !t.hasMetrics { - continue - } - if h[c].VS && vul.ImgScanner == nil { + if !t.isVisible(h[c]) { continue } @@ -396,6 +424,48 @@ func (t *Table) SortColCmd(name string, asc bool) func(evt *tcell.EventKey) *tce } } +// SortColChange changes on which column to sort +func (t *Table) SortColChange(direction SortChange) func(evt *tcell.EventKey) *tcell.EventKey { + return func(evt *tcell.EventKey) *tcell.EventKey { + sortCol := t.sortCol.Name + sortColIdx := -1 + newSortColIdx := -1 + prevSortColIdx := -1 + header := t.GetModel().Peek().GetHeader() + + for i, h := range header { + if !t.isVisible(h) { + continue + } + + if h.Name == sortCol { + sortColIdx = i + break + } + + prevSortColIdx = i + } + + if direction == SortPrevCol { + newSortColIdx = prevSortColIdx + } else { + for i := sortColIdx + 1; i < len(header); i++ { + if t.isVisible(header[i]) { + newSortColIdx = i + break + } + } + } + + if newSortColIdx != -1 { + t.sortCol.Name = header[newSortColIdx].Name + t.Refresh() + } + + return nil + } +} + // SortInvertCmd reverses sorting order. func (t *Table) SortInvertCmd(evt *tcell.EventKey) *tcell.EventKey { t.toggleSortCol() diff --git a/internal/ui/types.go b/internal/ui/types.go index 534d084e29..263350ef21 100644 --- a/internal/ui/types.go +++ b/internal/ui/types.go @@ -14,6 +14,16 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +type ( + // SortChange changes the column on which to sort + SortChange bool +) + +const ( + SortNextCol SortChange = true + SortPrevCol SortChange = false +) + // Namespaceable represents a namespaceable model. type Namespaceable interface { // ClusterWide returns true if the model represents resource in all namespaces. diff --git a/internal/view/alias_test.go b/internal/view/alias_test.go index 6deba28983..561a592777 100644 --- a/internal/view/alias_test.go +++ b/internal/view/alias_test.go @@ -27,7 +27,7 @@ func TestAliasNew(t *testing.T) { assert.Nil(t, v.Init(makeContext())) assert.Equal(t, "Aliases", v.Name()) - assert.Equal(t, 6, len(v.Hints())) + assert.Equal(t, 9, len(v.Hints())) } func TestAliasSearch(t *testing.T) { diff --git a/internal/view/cm_test.go b/internal/view/cm_test.go index 6e787defc8..dc5aacbdcf 100644 --- a/internal/view/cm_test.go +++ b/internal/view/cm_test.go @@ -16,5 +16,5 @@ func TestConfigMapNew(t *testing.T) { assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "ConfigMaps", s.Name()) - assert.Equal(t, 7, len(s.Hints())) + assert.Equal(t, 10, len(s.Hints())) } diff --git a/internal/view/container_test.go b/internal/view/container_test.go index 84787f6d1b..6e966f8ad1 100644 --- a/internal/view/container_test.go +++ b/internal/view/container_test.go @@ -16,5 +16,5 @@ func TestContainerNew(t *testing.T) { assert.Nil(t, c.Init(makeCtx())) assert.Equal(t, "Containers", c.Name()) - assert.Equal(t, 19, len(c.Hints())) + assert.Equal(t, 22, len(c.Hints())) } diff --git a/internal/view/context_test.go b/internal/view/context_test.go index a265459ae7..d3f1cb1224 100644 --- a/internal/view/context_test.go +++ b/internal/view/context_test.go @@ -16,5 +16,5 @@ func TestContext(t *testing.T) { assert.Nil(t, ctx.Init(makeCtx())) assert.Equal(t, "Contexts", ctx.Name()) - assert.Equal(t, 5, len(ctx.Hints())) + assert.Equal(t, 8, len(ctx.Hints())) } diff --git a/internal/view/dir_test.go b/internal/view/dir_test.go index 7757eb8abf..8acc17b2d0 100644 --- a/internal/view/dir_test.go +++ b/internal/view/dir_test.go @@ -15,5 +15,5 @@ func TestDir(t *testing.T) { assert.Nil(t, v.Init(makeCtx())) assert.Equal(t, "Directory", v.Name()) - assert.Equal(t, 7, len(v.Hints())) + assert.Equal(t, 10, len(v.Hints())) } diff --git a/internal/view/dp_test.go b/internal/view/dp_test.go index a3e8a51fd4..3ada7e3516 100644 --- a/internal/view/dp_test.go +++ b/internal/view/dp_test.go @@ -16,5 +16,5 @@ func TestDeploy(t *testing.T) { assert.Nil(t, v.Init(makeCtx())) assert.Equal(t, "Deployments", v.Name()) - assert.Equal(t, 16, len(v.Hints())) + assert.Equal(t, 19, len(v.Hints())) } diff --git a/internal/view/ds_test.go b/internal/view/ds_test.go index 94545218e2..8a07d7ac42 100644 --- a/internal/view/ds_test.go +++ b/internal/view/ds_test.go @@ -16,5 +16,5 @@ func TestDaemonSet(t *testing.T) { assert.Nil(t, v.Init(makeCtx())) assert.Equal(t, "DaemonSets", v.Name()) - assert.Equal(t, 17, len(v.Hints())) + assert.Equal(t, 20, len(v.Hints())) } diff --git a/internal/view/help_test.go b/internal/view/help_test.go index b6f19c7831..fafb863310 100644 --- a/internal/view/help_test.go +++ b/internal/view/help_test.go @@ -24,7 +24,7 @@ func TestHelp(t *testing.T) { v := view.NewHelp(app) assert.Nil(t, v.Init(ctx)) - assert.Equal(t, 29, v.GetRowCount()) + assert.Equal(t, 32, v.GetRowCount()) assert.Equal(t, 8, v.GetColumnCount()) assert.Equal(t, "", strings.TrimSpace(v.GetCell(1, 0).Text)) assert.Equal(t, "Attach", strings.TrimSpace(v.GetCell(1, 1).Text)) diff --git a/internal/view/ns_test.go b/internal/view/ns_test.go index 3c06101feb..85bd308292 100644 --- a/internal/view/ns_test.go +++ b/internal/view/ns_test.go @@ -16,5 +16,5 @@ func TestNSCleanser(t *testing.T) { assert.Nil(t, ns.Init(makeCtx())) assert.Equal(t, "Namespaces", ns.Name()) - assert.Equal(t, 7, len(ns.Hints())) + assert.Equal(t, 10, len(ns.Hints())) } diff --git a/internal/view/pf_test.go b/internal/view/pf_test.go index 245e593933..bba7d6497e 100644 --- a/internal/view/pf_test.go +++ b/internal/view/pf_test.go @@ -16,5 +16,5 @@ func TestPortForwardNew(t *testing.T) { assert.Nil(t, pf.Init(makeCtx())) assert.Equal(t, "PortForwards", pf.Name()) - assert.Equal(t, 10, len(pf.Hints())) + assert.Equal(t, 13, len(pf.Hints())) } diff --git a/internal/view/pod_test.go b/internal/view/pod_test.go index 23bdebaf74..59d153ef10 100644 --- a/internal/view/pod_test.go +++ b/internal/view/pod_test.go @@ -19,7 +19,7 @@ func TestPodNew(t *testing.T) { assert.Nil(t, po.Init(makeCtx())) assert.Equal(t, "Pods", po.Name()) - assert.Equal(t, 28, len(po.Hints())) + assert.Equal(t, 31, len(po.Hints())) } // Helpers... diff --git a/internal/view/priorityclass_test.go b/internal/view/priorityclass_test.go index 60cef2abf3..df73d1c408 100644 --- a/internal/view/priorityclass_test.go +++ b/internal/view/priorityclass_test.go @@ -16,5 +16,5 @@ func TestPriorityClassNew(t *testing.T) { assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "PriorityClass", s.Name()) - assert.Equal(t, 6, len(s.Hints())) + assert.Equal(t, 9, len(s.Hints())) } diff --git a/internal/view/pvc_test.go b/internal/view/pvc_test.go index 7434729bc9..c70c251d85 100644 --- a/internal/view/pvc_test.go +++ b/internal/view/pvc_test.go @@ -16,5 +16,5 @@ func TestPVCNew(t *testing.T) { assert.Nil(t, v.Init(makeCtx())) assert.Equal(t, "PersistentVolumeClaims", v.Name()) - assert.Equal(t, 11, len(v.Hints())) + assert.Equal(t, 14, len(v.Hints())) } diff --git a/internal/view/rbac_test.go b/internal/view/rbac_test.go index 5d72d3588d..feac8dae20 100644 --- a/internal/view/rbac_test.go +++ b/internal/view/rbac_test.go @@ -16,5 +16,5 @@ func TestRbacNew(t *testing.T) { assert.Nil(t, v.Init(makeCtx())) assert.Equal(t, "Rbac", v.Name()) - assert.Equal(t, 5, len(v.Hints())) + assert.Equal(t, 8, len(v.Hints())) } diff --git a/internal/view/reference_test.go b/internal/view/reference_test.go index 0e3578f7f7..909f9c7d95 100644 --- a/internal/view/reference_test.go +++ b/internal/view/reference_test.go @@ -16,5 +16,5 @@ func TestReferenceNew(t *testing.T) { assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "References", s.Name()) - assert.Equal(t, 4, len(s.Hints())) + assert.Equal(t, 7, len(s.Hints())) } diff --git a/internal/view/screen_dump_test.go b/internal/view/screen_dump_test.go index b812435b43..1718430d8e 100644 --- a/internal/view/screen_dump_test.go +++ b/internal/view/screen_dump_test.go @@ -16,5 +16,5 @@ func TestScreenDumpNew(t *testing.T) { assert.Nil(t, po.Init(makeCtx())) assert.Equal(t, "ScreenDumps", po.Name()) - assert.Equal(t, 5, len(po.Hints())) + assert.Equal(t, 8, len(po.Hints())) } diff --git a/internal/view/secret_test.go b/internal/view/secret_test.go index a02972af3d..d3a7a5fea0 100644 --- a/internal/view/secret_test.go +++ b/internal/view/secret_test.go @@ -16,5 +16,5 @@ func TestSecretNew(t *testing.T) { assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "Secrets", s.Name()) - assert.Equal(t, 8, len(s.Hints())) + assert.Equal(t, 11, len(s.Hints())) } diff --git a/internal/view/sts_test.go b/internal/view/sts_test.go index 4455194528..18e25aea70 100644 --- a/internal/view/sts_test.go +++ b/internal/view/sts_test.go @@ -16,5 +16,5 @@ func TestStatefulSetNew(t *testing.T) { assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "StatefulSets", s.Name()) - assert.Equal(t, 14, len(s.Hints())) + assert.Equal(t, 17, len(s.Hints())) } diff --git a/internal/view/svc_test.go b/internal/view/svc_test.go index 19978d8f48..1c3c7a505d 100644 --- a/internal/view/svc_test.go +++ b/internal/view/svc_test.go @@ -173,5 +173,5 @@ func TestServiceNew(t *testing.T) { assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "Services", s.Name()) - assert.Equal(t, 12, len(s.Hints())) + assert.Equal(t, 15, len(s.Hints())) } diff --git a/internal/view/table.go b/internal/view/table.go index 47a8d6163b..51d741e559 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -198,6 +198,9 @@ func (t *Table) bindKeys() { tcell.KeyCtrlW: ui.NewKeyAction("Toggle Wide", t.toggleWideCmd, false), ui.KeyShiftN: ui.NewKeyAction("Sort Name", t.SortColCmd(nameCol, true), false), ui.KeyShiftA: ui.NewKeyAction("Sort Age", t.SortColCmd(ageCol, true), false), + ui.KeyQ: ui.NewKeyAction("Reverse Sort Order", t.SortInvertCmd, false), + ui.KeyLess: ui.NewKeyAction("Sort Previous Column", t.SortColChange(ui.SortPrevCol), false), + ui.KeyGreater: ui.NewKeyAction("Sort Next Column", t.SortColChange(ui.SortNextCol), false), }) }