From 5c1ff0ed7bcd656955a4760a1658511a4c168218 Mon Sep 17 00:00:00 2001 From: derailed Date: Fri, 30 Oct 2020 12:52:15 -0600 Subject: [PATCH] Fix #906 #889 #564 --- Makefile | 2 +- change_logs/release_v0.23.2.md | 35 ++++++++++++++++ cmd/root.go | 48 +++++++-------------- internal/config/config.go | 11 ----- internal/config/flags.go | 2 + internal/config/k9s.go | 17 +++++++- internal/config/k9s_test.go | 51 +++++++++++++++++++++++ internal/config/testdata/k9s.yml | 1 + internal/config/testdata/k9s_readonly.yml | 31 ++++++++++++++ internal/dao/helpers.go | 14 +++++++ internal/dao/log_item.go | 30 ++++++++----- internal/model/log.go | 20 ++------- internal/model/log_test.go | 10 +++-- internal/model/pulse_health.go | 5 --- internal/render/header.go | 5 +++ internal/render/helpers.go | 13 +++--- internal/render/row.go | 29 +++++++++---- internal/render/row_event.go | 23 ++++++---- internal/render/row_event_test.go | 10 ++--- internal/render/row_test.go | 20 +++++---- internal/ui/table.go | 4 +- 21 files changed, 260 insertions(+), 121 deletions(-) create mode 100644 change_logs/release_v0.23.2.md create mode 100644 internal/config/testdata/k9s_readonly.yml diff --git a/Makefile b/Makefile index 009f87e6..af07dd59 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PACKAGE := github.com/derailed/$(NAME) GIT := $(shell git rev-parse --short HEAD) SOURCE_DATE_EPOCH ?= $(shell date +%s) DATE := $(shell date -u -d @${SOURCE_DATE_EPOCH} +%FT%T%Z) -VERSION ?= v0.23.1 +VERSION ?= v0.23.2 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.23.2.md b/change_logs/release_v0.23.2.md new file mode 100644 index 00000000..e14eb90e --- /dev/null +++ b/change_logs/release_v0.23.2.md @@ -0,0 +1,35 @@ + + +# Release v0.23.2 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better are as ever very much noted and appreciated! + +If you feel K9s is helping your Kubernetes journey, please consider joining our [sponsorhip program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +--- + +### Write Mode + +K9s is writable by default, meaning you can interact with your cluster and make changes using one shot commands ie edit, delete, scale, etc... There `readOnly` config option that can be specified in the configuration or via a cli arg to override this behavior. In this drop, we're introducing a symmetrical command line arg aka `--write` that overrides a K9s session and make it writable tho the readOnly config option is set to true. + +## Inverse Log Filtering + +In the last drop, we've introduces reverse filters to filter out resources from table views. Now you will be able to apply inverse filtering on your log views as well via `/!fred` + +--- + +## Resolved Issues/Features + +* [Issue #906](https://github.com/derailed/k9s/issues/906) Print resources in pod view. With Feelings. Thanks Claudio! +* [Issue #889](https://github.com/derailed/k9s/issues/889) Disable readOnly config +* [Issue #564](https://github.com/derailed/k9s/issues/564) Invert filter mode on logs + +## Resolved PRs + +--- + + © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/cmd/root.go b/cmd/root.go index 18ac5890..e9a9323c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -28,7 +28,6 @@ var ( version, commit, date = "dev", "dev", client.NA k9sFlags *config.Flags k8sFlags *genericclioptions.ConfigFlags - demoMode = new(bool) rootCmd = &cobra.Command{ Use: appName, @@ -40,7 +39,6 @@ var ( func init() { rootCmd.AddCommand(versionCmd(), infoCmd()) - initTransientFlags() initK9sFlags() initK8sFlags() @@ -105,28 +103,15 @@ func loadConfiguration() *config.Config { log.Warn().Msg("Unable to locate K9s config. Generating new configuration...") } - if demoMode != nil { - k9sCfg.SetDemoMode(*demoMode) - } if *k9sFlags.RefreshRate != config.DefaultRefreshRate { k9sCfg.K9s.OverrideRefreshRate(*k9sFlags.RefreshRate) } - if k9sFlags.Headless != nil { - k9sCfg.K9s.OverrideHeadless(*k9sFlags.Headless) - } - - if k9sFlags.Crumbsless != nil { - k9sCfg.K9s.OverrideCrumbsless(*k9sFlags.Crumbsless) - } - - if k9sFlags.ReadOnly != nil { - k9sCfg.K9s.OverrideReadOnly(*k9sFlags.ReadOnly) - } - - if k9sFlags.Command != nil { - k9sCfg.K9s.OverrideCommand(*k9sFlags.Command) - } + k9sCfg.K9s.OverrideHeadless(*k9sFlags.Headless) + k9sCfg.K9s.OverrideCrumbsless(*k9sFlags.Crumbsless) + k9sCfg.K9s.OverrideReadOnly(*k9sFlags.ReadOnly) + k9sCfg.K9s.OverrideWrite(*k9sFlags.Write) + k9sCfg.K9s.OverrideCommand(*k9sFlags.Command) if isBoolSet(k9sFlags.AllNamespaces) && k9sCfg.SetActiveNamespace(client.AllNamespaces) != nil { log.Error().Msg("Setting active namespace") @@ -175,15 +160,6 @@ func parseLevel(level string) zerolog.Level { } } -func initTransientFlags() { - rootCmd.Flags().BoolVar( - demoMode, - "demo", - false, - "Enable demo mode to show keyboard commands", - ) -} - func initK9sFlags() { k9sFlags = config.NewFlags() rootCmd.Flags().IntVarP( @@ -204,6 +180,12 @@ func initK9sFlags() { false, "Turn K9s header off", ) + rootCmd.Flags().BoolVar( + k9sFlags.Crumbsless, + "crumbsless", + false, + "Turn K9s crumbs off", + ) rootCmd.Flags().BoolVarP( k9sFlags.AllNamespaces, "all-namespaces", "A", @@ -220,13 +202,13 @@ func initK9sFlags() { k9sFlags.ReadOnly, "readonly", false, - "Toggles readOnly mode by overriding configuration setting", + "Sets readOnly mode by overriding readOnly configuration setting", ) rootCmd.Flags().BoolVar( - k9sFlags.Crumbsless, - "crumbsless", + k9sFlags.Write, + "write", false, - "Turn K9s crumbs off", + "Sets write mode by overriding the readOnly configuration setting", ) } diff --git a/internal/config/config.go b/internal/config/config.go index 59d48ffc..73e13370 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -52,7 +52,6 @@ type ( K9s *K9s `yaml:"k9s"` client client.Connection settings KubeSettings - demoMode bool } ) @@ -70,16 +69,6 @@ func NewConfig(ks KubeSettings) *Config { return &Config{K9s: NewK9s(), settings: ks} } -// DemoMode returns true if demo mode is active, false otherwise. -func (c *Config) DemoMode() bool { - return c.demoMode -} - -// SetDemoMode sets the demo mode. -func (c *Config) SetDemoMode(b bool) { - c.demoMode = b -} - // Refine the configuration based on cli args. func (c *Config) Refine(flags *genericclioptions.ConfigFlags) error { cfg, err := flags.ToRawKubeConfigLoader().RawConfig() diff --git a/internal/config/flags.go b/internal/config/flags.go index 82c762b2..df776907 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -19,6 +19,7 @@ type Flags struct { Command *string AllNamespaces *bool ReadOnly *bool + Write *bool Crumbsless *bool } @@ -31,6 +32,7 @@ func NewFlags() *Flags { Command: strPtr(DefaultCommand), AllNamespaces: boolPtr(false), ReadOnly: boolPtr(false), + Write: boolPtr(false), Crumbsless: boolPtr(false), } } diff --git a/internal/config/k9s.go b/internal/config/k9s.go index d928ca0a..15f54795 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -1,6 +1,8 @@ package config -import "github.com/derailed/k9s/internal/client" +import ( + "github.com/derailed/k9s/internal/client" +) const ( defaultRefreshRate = 2 @@ -56,7 +58,17 @@ func (k *K9s) OverrideCrumbsless(b bool) { // OverrideReadOnly set the readonly mode manually. func (k *K9s) OverrideReadOnly(b bool) { - k.manualReadOnly = &b + if b { + k.manualReadOnly = &b + } +} + +// OverrideWrite set the write mode manually. +func (k *K9s) OverrideWrite(b bool) { + if b { + flag := !b + k.manualReadOnly = &flag + } } // OverrideCommand set the command manually. @@ -100,6 +112,7 @@ func (k *K9s) IsReadOnly() bool { if k.manualReadOnly != nil { readOnly = *k.manualReadOnly } + return readOnly } diff --git a/internal/config/k9s_test.go b/internal/config/k9s_test.go index b33b4055..504a8989 100644 --- a/internal/config/k9s_test.go +++ b/internal/config/k9s_test.go @@ -8,6 +8,57 @@ import ( "github.com/stretchr/testify/assert" ) +func TestIsReadOnly(t *testing.T) { + uu := map[string]struct { + config string + read, write bool + readOnly bool + }{ + "writable": { + config: "k9s.yml", + }, + "writable_read_override": { + config: "k9s.yml", + read: true, + readOnly: true, + }, + "writable_write_override": { + config: "k9s.yml", + write: true, + }, + "readonly": { + config: "k9s_readonly.yml", + readOnly: true, + }, + "readonly_read_override": { + config: "k9s_readonly.yml", + read: true, + readOnly: true, + }, + "readonly_write_override": { + config: "k9s_readonly.yml", + write: true, + }, + "readonly_both_override": { + config: "k9s_readonly.yml", + read: true, + write: true, + }, + } + + mk := NewMockKubeSettings() + cfg := config.NewConfig(mk) + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Nil(t, cfg.Load("testdata/"+u.config)) + cfg.K9s.OverrideReadOnly(u.read) + cfg.K9s.OverrideWrite(u.write) + assert.Equal(t, u.readOnly, cfg.K9s.IsReadOnly()) + }) + } +} + func TestK9sValidate(t *testing.T) { mc := NewMockConnection() m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) diff --git a/internal/config/testdata/k9s.yml b/internal/config/testdata/k9s.yml index 7bf42504..3b6a6ac6 100644 --- a/internal/config/testdata/k9s.yml +++ b/internal/config/testdata/k9s.yml @@ -1,5 +1,6 @@ k9s: refreshRate: 2 + readOnly: false logger: tail: 200 buffer: 2000 diff --git a/internal/config/testdata/k9s_readonly.yml b/internal/config/testdata/k9s_readonly.yml new file mode 100644 index 00000000..e8c5c192 --- /dev/null +++ b/internal/config/testdata/k9s_readonly.yml @@ -0,0 +1,31 @@ +k9s: + refreshRate: 2 + readOnly: true + logger: + tail: 200 + buffer: 2000 + currentContext: minikube + currentCluster: minikube + clusters: + minikube: + namespace: + active: kube-system + favorites: + - default + - kube-public + - istio-system + - all + - kube-system + view: + active: ctx + fred: + namespace: + active: default + favorites: + - default + - kube-public + - istio-system + - all + - kube-system + view: + active: po diff --git a/internal/dao/helpers.go b/internal/dao/helpers.go index a60e9184..fec28e05 100644 --- a/internal/dao/helpers.go +++ b/internal/dao/helpers.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "math" + "regexp" "github.com/derailed/tview" runewidth "github.com/mattn/go-runewidth" @@ -13,6 +14,19 @@ import ( "k8s.io/cli-runtime/pkg/printers" ) +var ( + inverseRx = regexp.MustCompile(`\A\!`) + fuzzyRx = regexp.MustCompile(`\A\-f`) +) + +// IsInverseSelector checks if inverse char has been provided. +func IsInverseSelector(s string) bool { + if s == "" { + return false + } + return inverseRx.MatchString(s) +} + // IsFuzzySelector checks if filter is fuzzy or not. func IsFuzzySelector(s string) bool { if s == "" { diff --git a/internal/dao/log_item.go b/internal/dao/log_item.go index 0981f1ab..d966e9bb 100644 --- a/internal/dao/log_item.go +++ b/internal/dao/log_item.go @@ -180,8 +180,6 @@ func (l LogItems) Filter(q string, showTime bool) ([]int, [][]int, error) { return matches, indices, nil } -var fuzzyRx = regexp.MustCompile(`\A\-f`) - func (l LogItems) fuzzyFilter(q string, showTime bool) ([]int, [][]int) { q = strings.TrimSpace(q) matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10) @@ -195,22 +193,32 @@ func (l LogItems) fuzzyFilter(q string, showTime bool) ([]int, [][]int) { } func (l LogItems) filterLogs(q string, showTime bool) ([]int, [][]int, error) { + var invert bool + if IsInverseSelector(q) { + invert = true + q = q[1:] + } rx, err := regexp.Compile(`(?i)` + q) if err != nil { return nil, nil, err } matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10) for i, line := range l.Lines(showTime) { - if locs := rx.FindIndex(line); locs != nil { - matches = append(matches, i) - ii := make([]int, 0, 10) - for i := 0; i < len(locs); i += 2 { - for j := locs[i]; j < locs[i+1]; j++ { - ii = append(ii, j) - } - } - indices = append(indices, ii) + locs := rx.FindIndex(line) + if locs != nil && invert { + continue } + if locs == nil && !invert { + continue + } + matches = append(matches, i) + ii := make([]int, 0, 10) + for i := 0; i < len(locs); i += 2 { + for j := locs[i]; j < locs[i+1]; j++ { + ii = append(ii, j) + } + } + indices = append(indices, ii) } return matches, indices, nil diff --git a/internal/model/log.go b/internal/model/log.go index 638b56ab..c9224254 100644 --- a/internal/model/log.go +++ b/internal/model/log.go @@ -38,7 +38,6 @@ type Log struct { filter string lastSent int flushTimeout time.Duration - filtering bool } // NewLog returns a new model. @@ -176,28 +175,15 @@ func (l *Log) Filter(q string) { defer l.mx.Unlock() if len(q) == 0 { - l.filter, l.filtering = "", false + l.filter = "" l.fireLogCleared() l.fireLogBuffChanged(l.lines) return } l.filter = q - // BOZO!! No needed since cmdbuff is now throttled!! - if l.filtering { - return - } - l.filtering = true - go func(l *Log) { - <-time.After(500 * time.Millisecond) - l.fireLogCleared() - l.fireLogBuffChanged(l.lines) - l.mx.Lock() - { - l.filtering = false - } - l.mx.Unlock() - }(l) + l.fireLogCleared() + l.fireLogBuffChanged(l.lines) } func (l *Log) load() error { diff --git a/internal/model/log_test.go b/internal/model/log_test.go index 02f830d9..e963a203 100644 --- a/internal/model/log_test.go +++ b/internal/model/log_test.go @@ -50,6 +50,10 @@ func TestLogFilter(t *testing.T) { q: `pod-line-[1-3]{1}`, e: 4, }, + "invert": { + q: `!pod-line-1`, + e: 8, + }, "fuzzy": { q: `-f po-l1`, e: 2, @@ -75,13 +79,13 @@ func TestLogFilter(t *testing.T) { m.Notify() assert.Equal(t, 1, v.dataCalled) - assert.Equal(t, 1, v.clearCalled) + assert.Equal(t, 2, v.clearCalled) assert.Equal(t, 0, v.errCalled) assert.Equal(t, u.e, len(v.data)) m.ClearFilter() assert.Equal(t, 2, v.dataCalled) - assert.Equal(t, 2, v.clearCalled) + assert.Equal(t, 3, v.clearCalled) assert.Equal(t, 0, v.errCalled) assert.Equal(t, size, len(v.data)) }) @@ -195,7 +199,7 @@ func TestLogTimedout(t *testing.T) { } m.Notify() assert.Equal(t, 1, v.dataCalled) - assert.Equal(t, 1, v.clearCalled) + assert.Equal(t, 2, v.clearCalled) assert.Equal(t, 0, v.errCalled) const e = "\x1b[38;5;209ml\x1b[0m\x1b[38;5;209mi\x1b[0m\x1b[38;5;209mn\x1b[0m\x1b[38;5;209me\x1b[0m\x1b[38;5;209m1\x1b[0m" assert.Equal(t, e, string(v.data[0])) diff --git a/internal/model/pulse_health.go b/internal/model/pulse_health.go index ec7dcf8a..759fe2e0 100644 --- a/internal/model/pulse_health.go +++ b/internal/model/pulse_health.go @@ -3,7 +3,6 @@ package model import ( "context" "fmt" - "time" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" @@ -27,10 +26,6 @@ func NewPulseHealth(f dao.Factory) *PulseHealth { // List returns a canned collection of resources health. func (h *PulseHealth) List(ctx context.Context, ns string) ([]runtime.Object, error) { - defer func(t time.Time) { - log.Debug().Msgf("PulseHealthCheck %v", time.Since(t)) - }(time.Now()) - gvrs := []string{ "v1/pods", "v1/events", diff --git a/internal/render/header.go b/internal/render/header.go index bb993f1e..cbb949f6 100644 --- a/internal/render/header.go +++ b/internal/render/header.go @@ -145,6 +145,11 @@ func (h Header) HasAge() bool { return h.IndexOf(ageCol, true) != -1 } +// IsMetricsCol checks if given column index represents metrics. +func (h Header) IsMetricsCol(col int) bool { + return h[col].MX +} + // IsAgeCol checks if given column index is the age column. func (h Header) IsAgeCol(col int) bool { if !h.HasAge() || col >= len(h) { diff --git a/internal/render/helpers.go b/internal/render/helpers.go index 5b830995..d6cd6b37 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -1,6 +1,7 @@ package render import ( + "fmt" "regexp" "sort" "strconv" @@ -259,30 +260,26 @@ func mapToIfc(m interface{}) (s string) { func toMcPerc(v1, v2 *resource.Quantity) string { m := v1.MilliValue() - return toMc(m) + " (" + - strconv.Itoa(client.ToPercentage(m, v2.MilliValue())) + "%)" + return fmt.Sprintf("%s (%d%%)", toMc(m), client.ToPercentage(m, v2.MilliValue())) } func toMiPerc(v1, v2 *resource.Quantity) string { m := v1.Value() - return toMi(m) + " (" + - strconv.Itoa(client.ToPercentage(m, v2.Value())) + "%)" + return fmt.Sprintf("%s (%d%%)", toMi(m), client.ToPercentage(m, v2.Value())) } func toMc(v int64) string { if v == 0 { return ZeroValue } - p := message.NewPrinter(language.English) - return p.Sprintf("%d", v) + return AsThousands(v) } func toMi(v int64) string { if v == 0 { return ZeroValue } - p := message.NewPrinter(language.English) - return p.Sprintf("%d", client.ToMB(v)) + return AsThousands(client.ToMB(v)) } func boolPtrToStr(b *bool) string { diff --git a/internal/render/row.go b/internal/render/row.go index d974837d..91fcc477 100644 --- a/internal/render/row.go +++ b/internal/render/row.go @@ -4,6 +4,7 @@ import ( "reflect" "sort" "strconv" + "strings" "time" "github.com/fvbommel/sortorder" @@ -146,8 +147,14 @@ func (rr Rows) Find(id string) (int, bool) { } // Sort rows based on column index and order. -func (rr Rows) Sort(col int, asc bool) { - t := RowSorter{Rows: rr, Index: col, Asc: asc} +func (rr Rows) Sort(col int, asc, isNum, isDur bool) { + t := RowSorter{ + Rows: rr, + Index: col, + IsNumber: isNum, + IsDuration: isDur, + Asc: asc, + } sort.Sort(t) } @@ -155,9 +162,10 @@ func (rr Rows) Sort(col int, asc bool) { // RowSorter sorts rows. type RowSorter struct { - Rows Rows - Index int - Asc bool + Rows Rows + Index int + IsNumber, IsDuration bool + Asc bool } func (s RowSorter) Len() int { @@ -169,7 +177,7 @@ func (s RowSorter) Swap(i, j int) { } func (s RowSorter) Less(i, j int) bool { - return Less(s.Asc, s.Rows[i].Fields[s.Index], s.Rows[j].Fields[s.Index]) + return Less(s.Asc, s.IsNumber, s.IsDuration, s.Rows[i].Fields[s.Index], s.Rows[j].Fields[s.Index]) } // ---------------------------------------------------------------------------- @@ -185,8 +193,13 @@ func toAgeDuration(dur string) string { } // Less return true if c1 < c2. -func Less(asc bool, c1, c2 string) bool { - c1, c2 = toAgeDuration(c1), toAgeDuration(c2) +func Less(asc, isNumber, isDuration bool, c1, c2 string) bool { + if isNumber { + c1, c2 = strings.Replace(c1, ",", "", -1), strings.Replace(c2, ",", "", -1) + } + if isDuration { + c1, c2 = toAgeDuration(c1), toAgeDuration(c2) + } b := sortorder.NaturalLess(c1, c2) if asc { return b diff --git a/internal/render/row_event.go b/internal/render/row_event.go index 2202eb83..8a6d6e5e 100644 --- a/internal/render/row_event.go +++ b/internal/render/row_event.go @@ -196,12 +196,19 @@ func (r RowEvents) FindIndex(id string) (int, bool) { } // Sort rows based on column index and order. -func (r RowEvents) Sort(ns string, sortCol int, ageCol bool, asc bool) { +func (r RowEvents) Sort(ns string, sortCol int, ageCol, numCol, asc bool) { if sortCol == -1 { return } - t := RowEventSorter{NS: ns, Events: r, Index: sortCol, Asc: asc} + t := RowEventSorter{ + NS: ns, + Events: r, + Index: sortCol, + Asc: asc, + IsNumber: numCol, + IsDuration: ageCol, + } sort.Sort(t) iids, fields := map[string][]string{}, make(StringSet, 0, len(r)) @@ -227,10 +234,12 @@ func (r RowEvents) Sort(ns string, sortCol int, ageCol bool, asc bool) { // RowEventSorter sorts row events by a given colon. type RowEventSorter struct { - Events RowEvents - Index int - NS string - Asc bool + Events RowEvents + Index int + NS string + IsNumber bool + IsDuration bool + Asc bool } func (r RowEventSorter) Len() int { @@ -243,7 +252,7 @@ func (r RowEventSorter) Swap(i, j int) { func (r RowEventSorter) Less(i, j int) bool { f1, f2 := r.Events[i].Row.Fields, r.Events[j].Row.Fields - return Less(r.Asc, f1[r.Index], f2[r.Index]) + return Less(r.Asc, r.IsNumber, r.IsDuration, f1[r.Index], f2[r.Index]) } // ---------------------------------------------------------------------------- diff --git a/internal/render/row_event_test.go b/internal/render/row_event_test.go index e2c21434..80cf75a9 100644 --- a/internal/render/row_event_test.go +++ b/internal/render/row_event_test.go @@ -409,10 +409,10 @@ func TestRowEventsDelete(t *testing.T) { func TestRowEventsSort(t *testing.T) { uu := map[string]struct { - re render.RowEvents - col int - age, asc bool - e render.RowEvents + re render.RowEvents + col int + age, num, asc bool + e render.RowEvents }{ "age_time": { re: render.RowEvents{ @@ -483,7 +483,7 @@ func TestRowEventsSort(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - u.re.Sort("", u.col, u.age, u.asc) + u.re.Sort("", u.col, u.age, u.num, u.asc) assert.Equal(t, u.e, u.re) }) } diff --git a/internal/render/row_test.go b/internal/render/row_test.go index c0caf2ef..79d31fb2 100644 --- a/internal/render/row_test.go +++ b/internal/render/row_test.go @@ -231,10 +231,10 @@ func TestRowsUpsert(t *testing.T) { func TestRowsSortText(t *testing.T) { uu := map[string]struct { - rows render.Rows - col int - asc bool - e render.Rows + rows render.Rows + col int + asc, num bool + e render.Rows }{ "plainAsc": { rows: render.Rows{ @@ -266,6 +266,7 @@ func TestRowsSortText(t *testing.T) { {Fields: []string{"1", "blee"}}, }, col: 0, + num: true, asc: true, e: render.Rows{ {Fields: []string{"1", "blee"}}, @@ -278,6 +279,7 @@ func TestRowsSortText(t *testing.T) { {Fields: []string{"1", "blee"}}, }, col: 0, + num: true, asc: false, e: render.Rows{ {Fields: []string{"10", "duh"}}, @@ -301,7 +303,7 @@ func TestRowsSortText(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - u.rows.Sort(u.col, u.asc) + u.rows.Sort(u.col, u.asc, u.num, false) assert.Equal(t, u.e, u.rows) }) } @@ -342,7 +344,7 @@ func TestRowsSortDuration(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - u.rows.Sort(u.col, u.asc) + u.rows.Sort(u.col, u.asc, false, true) assert.Equal(t, u.e, u.rows) }) } @@ -369,13 +371,13 @@ func TestRowsSortMetrics(t *testing.T) { }, "metricDesc": { rows: render.Rows{ - {Fields: []string{"10m", "100Mi"}}, + {Fields: []string{"10000m", "1000Mi"}}, {Fields: []string{"1m", "50Mi"}}, }, col: 1, asc: false, e: render.Rows{ - {Fields: []string{"10m", "100Mi"}}, + {Fields: []string{"10000m", "1000Mi"}}, {Fields: []string{"1m", "50Mi"}}, }, }, @@ -384,7 +386,7 @@ func TestRowsSortMetrics(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - u.rows.Sort(u.col, u.asc) + u.rows.Sort(u.col, u.asc, true, false) assert.Equal(t, u.e, u.rows) }) } diff --git a/internal/ui/table.go b/internal/ui/table.go index b3a5018a..82e3a3bf 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -234,10 +234,12 @@ func (t *Table) doUpdate(data render.TableData) { c.SetTextColor(fg) col++ } + colIndex := custData.Header.IndexOf(t.sortCol.name, false) custData.RowEvents.Sort( custData.Namespace, - custData.Header.IndexOf(t.sortCol.name, false), + colIndex, t.sortCol.name == "AGE", + data.Header.IsMetricsCol(colIndex), t.sortCol.asc, )