feat: center section divider to match table width (#2698)

The '========== Updated Releases ==========' header was a fixed 38
chars regardless of the table width below it. Now the divider line
is extended to match the table's visual width, with the title text
centered within the '=' borders.

- Add TableVisualWidth() to measure table width via runewidth
- Add HeaderDividerCentered() and HeaderDividerCenteredStyled()
  for centered dividers with optional bold+blue ANSI styling
- Refactor DisplayAffectedReleases to build the table first, then
  compute its width before logging the header
- Update all test snapshots and integration test output files

Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
yxxhero
2026-07-10 21:17:35 +08:00
committed by GitHub
parent 30f529f529
commit a9e4a8518a
59 changed files with 191 additions and 78 deletions
+1 -1
View File
@@ -92,7 +92,7 @@ require (
github.com/klauspost/compress v1.18.4 // indirect
github.com/lib/pq v1.12.3 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.19
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
@@ -23,12 +23,12 @@ GROUP RELEASES
processing releases in group 1/1: default//foo
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
foo stable/mychart1 3.1.0 0s
========== Deleted Releases ==========
==== Deleted Releases =====
NAME NAMESPACE DURATION
bar 0s
@@ -26,12 +26,12 @@ GROUP RELEASES
processing releases in group 1/1: default//foo
WARNING: release foo needs bar, but bar is not installed due to installed: false. Either mark bar as installed or remove bar from foo's needs
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
foo stable/mychart1 3.1.0 0s
========== Deleted Releases ==========
==== Deleted Releases =====
NAME NAMESPACE DURATION
bar 0s
@@ -20,7 +20,7 @@ GROUP RELEASES
processing releases in group 1/2: default//bar
processing releases in group 2/2: default//foo
========== Deleted Releases ==========
==== Deleted Releases =====
NAME NAMESPACE DURATION
bar 0s
foo 0s
@@ -20,7 +20,7 @@ GROUP RELEASES
processing releases in group 1/2: default//foo
processing releases in group 2/2: default//bar
========== Deleted Releases ==========
==== Deleted Releases =====
NAME NAMESPACE DURATION
foo 0s
bar 0s
@@ -26,12 +26,12 @@ GROUP RELEASES
processing releases in group 1/1: default//bar
WARNING: release bar needs foo, but foo is not installed due to installed: false. Either mark foo as installed or remove foo from bar's needs
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
bar stable/mychart2 3.1.0 0s
========== Deleted Releases ==========
==== Deleted Releases =====
NAME NAMESPACE DURATION
foo 0s
@@ -23,12 +23,12 @@ GROUP RELEASES
processing releases in group 1/1: default//bar
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
bar stable/mychart2 3.1.0 0s
========== Deleted Releases ==========
==== Deleted Releases =====
NAME NAMESPACE DURATION
foo 0s
@@ -24,7 +24,7 @@ update strategy - sync success
processing releases in group 2/2: default//foo
getting deployed release version failed: Failed to get the version for: mychart1
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
baz stable/mychart3 3.1.0 0s
bar stable/mychart2 3.1.0 0s
@@ -24,7 +24,7 @@ update strategy - sync success
processing releases in group 2/2: default//foo
getting deployed release version failed: Failed to get the version for: mychart1
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
baz stable/mychart3 3.1.0 0s
bar stable/mychart2 3.1.0 0s
@@ -22,7 +22,7 @@ processing releases in group 1/2: default//baz, default//bar
processing releases in group 2/2: default//foo
getting deployed release version failed: Failed to get the version for: mychart1
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
baz stable/mychart3 3.1.0 0s
bar stable/mychart2 3.1.0 0s
@@ -22,7 +22,7 @@ processing releases in group 1/2: default//baz, default//bar
processing releases in group 2/2: default//foo
getting deployed release version failed: Failed to get the version for: mychart1
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
baz stable/mychart3 3.1.0 0s
bar stable/mychart2 3.1.0 0s
+1 -1
View File
@@ -24,7 +24,7 @@ getting deployed release version failed: unexpected list key: listkey(filter=^ba
processing releases in group 2/2: default//foo
getting deployed release version failed: unexpected list key: listkey(filter=^foo$,flags=--kube-context default --uninstalling --deployed --failed --pending) not found in
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
baz stable/mychart3 0s
bar stable/mychart2 0s
+2 -2
View File
@@ -46,7 +46,7 @@ processing releases in group 3/5: default//anotherbackend
processing releases in group 4/5: default//backend-v2
processing releases in group 5/5: default//frontend-v3
========== Updated Releases ==========
========================== Updated Releases ===========================
NAME NAMESPACE CHART VERSION DURATION
logging charts/fluent-bit 3.1.0 0s
front-proxy stable/envoy 3.1.0 0s
@@ -57,7 +57,7 @@ backend-v2 charts/backend 3.1.0 0s
frontend-v3 charts/frontend 3.1.0 0s
========== Deleted Releases ==========
======== Deleted Releases ========
NAME NAMESPACE DURATION
frontend-v1 0s
backend-v1 0s
@@ -22,7 +22,7 @@ getting deployed release version failed: Failed to get the version for: mychart1
processing releases in group 2/2: default/testNamespace/bar
getting deployed release version failed: Failed to get the version for: mychart2
========== Updated Releases ==========
==================== Updated Releases =====================
NAME NAMESPACE CHART VERSION DURATION
foo testNamespace stable/mychart1 0s
bar testNamespace stable/mychart2 0s
+1 -1
View File
@@ -22,7 +22,7 @@ getting deployed release version failed: Failed to get the version for: mychart1
processing releases in group 2/2: default//bar
getting deployed release version failed: Failed to get the version for: mychart2
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
foo stable/mychart1 0s
bar stable/mychart2 0s
@@ -22,7 +22,7 @@ getting deployed release version failed: Failed to get the version for: mychart2
processing releases in group 2/2: default/testNamespace/foo
getting deployed release version failed: Failed to get the version for: mychart1
========== Updated Releases ==========
==================== Updated Releases =====================
NAME NAMESPACE CHART VERSION DURATION
bar testNamespace stable/mychart2 0s
foo testNamespace stable/mychart1 0s
+1 -1
View File
@@ -22,7 +22,7 @@ getting deployed release version failed: Failed to get the version for: mychart2
processing releases in group 2/2: default//foo
getting deployed release version failed: Failed to get the version for: mychart1
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
bar stable/mychart2 0s
foo stable/mychart1 0s
@@ -28,13 +28,13 @@ getting deployed release version failed: Failed to get the version for: mychart2
processing releases in group 2/2: default/ns1/foo
getting deployed release version failed: Failed to get the version for: mychart1
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
bar ns1 stable/mychart2 0s
foo ns1 stable/mychart1 0s
========== Deleted Releases ==========
==== Deleted Releases =====
NAME NAMESPACE DURATION
bar ns2 0s
@@ -22,7 +22,7 @@ getting deployed release version failed: Failed to get the version for: mychart2
processing releases in group 2/2: default/ns1/foo
getting deployed release version failed: Failed to get the version for: mychart1
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
bar ns2 stable/mychart2 0s
foo ns1 stable/mychart1 0s
@@ -22,7 +22,7 @@ getting deployed release version failed: Failed to get the version for: mychart1
processing releases in group 2/2: default/ns2/bar
getting deployed release version failed: Failed to get the version for: mychart2
========== Updated Releases ==========
================== Updated Releases ===================
NAME NAMESPACE CHART VERSION DURATION
foo ns1 stable/mychart1 0s
bar ns2 stable/mychart2 0s
@@ -47,7 +47,7 @@ GROUP RELEASES
processing releases in group 1/2: default/default/external-secrets
processing releases in group 2/2: default/default/my-release
========== Updated Releases ==========
======================= Updated Releases ========================
NAME NAMESPACE CHART VERSION DURATION
external-secrets default incubator/raw 3.1.0 0s
my-release default incubator/raw 3.1.0 0s
+1 -1
View File
@@ -15,7 +15,7 @@ GROUP RELEASES
processing releases in group 1/1: default/default/foo
========== Updated Releases ==========
================= Updated Releases ==================
NAME NAMESPACE CHART VERSION DURATION
foo default incubator/raw 3.1.0 0s
@@ -48,7 +48,7 @@ processing releases in group 1/3: default//serviceC
processing releases in group 2/3: default//serviceB
processing releases in group 3/3: default//serviceA
========== Updated Releases ==========
================= Updated Releases =================
NAME NAMESPACE CHART VERSION DURATION
serviceC my/chart 3.1.0 0s
serviceB my/chart 3.1.0 0s
@@ -15,7 +15,7 @@ GROUP RELEASES
processing releases in group 1/1: default/default/foo
========== Updated Releases ==========
================= Updated Releases ==================
NAME NAMESPACE CHART VERSION DURATION
foo default incubator/raw 3.1.0 0s
@@ -52,7 +52,7 @@ processing releases in group 1/3: default/kube-system/kubernetes-external-secret
processing releases in group 2/3: default/default/external-secrets
processing releases in group 3/3: default/default/my-release
========== Updated Releases ==========
============================== Updated Releases ==============================
NAME NAMESPACE CHART VERSION DURATION
kubernetes-external-secrets kube-system incubator/raw 3.1.0 0s
external-secrets default incubator/raw 3.1.0 0s
@@ -49,7 +49,7 @@ GROUP RELEASES
processing releases in group 1/2: default/default/external-secrets
processing releases in group 2/2: default/default/my-release
========== Updated Releases ==========
======================= Updated Releases ========================
NAME NAMESPACE CHART VERSION DURATION
external-secrets default incubator/raw 3.1.0 0s
my-release default incubator/raw 3.1.0 0s
@@ -59,13 +59,13 @@ processing releases in group 1/2: default/default/external-secrets
WARNING: release external-secrets needs kubernetes-external-secrets, but kubernetes-external-secrets is not installed due to installed: false. Either mark kubernetes-external-secrets as installed or remove kubernetes-external-secrets from external-secrets's needs
processing releases in group 2/2: default/default/my-release
========== Updated Releases ==========
======================= Updated Releases ========================
NAME NAMESPACE CHART VERSION DURATION
external-secrets default incubator/raw 3.1.0 0s
my-release default incubator/raw 3.1.0 0s
========== Deleted Releases ==========
================= Deleted Releases =================
NAME NAMESPACE DURATION
kubernetes-external-secrets kube-system 0s
@@ -53,7 +53,7 @@ processing releases in group 1/2: default/default/external-secrets
WARNING: release external-secrets needs kubernetes-external-secrets, but kubernetes-external-secrets is not installed due to installed: false. Either mark kubernetes-external-secrets as installed or remove kubernetes-external-secrets from external-secrets's needs
processing releases in group 2/2: default/default/my-release
========== Updated Releases ==========
======================= Updated Releases ========================
NAME NAMESPACE CHART VERSION DURATION
external-secrets default incubator/raw 3.1.0 0s
my-release default incubator/raw 3.1.0 0s
+1 -1
View File
@@ -47,7 +47,7 @@ GROUP RELEASES
processing releases in group 1/2: default/default/external-secrets
processing releases in group 2/2: default/default/my-release
========== Updated Releases ==========
======================= Updated Releases ========================
NAME NAMESPACE CHART VERSION DURATION
external-secrets default incubator/raw 3.1.0 0s
my-release default incubator/raw 3.1.0 0s
@@ -44,7 +44,7 @@ GROUP RELEASES
processing releases in group 1/1: default/default/external-secrets
========== Updated Releases ==========
======================= Updated Releases ========================
NAME NAMESPACE CHART VERSION DURATION
external-secrets default incubator/raw 3.1.0 0s
@@ -15,7 +15,7 @@ GROUP RELEASES
processing releases in group 1/1: default/default/my-release
========== Updated Releases ==========
==================== Updated Releases =====================
NAME NAMESPACE CHART VERSION DURATION
my-release default incubator/raw 3.1.0 0s
@@ -52,7 +52,7 @@ processing releases in group 1/3: kube-system/kubernetes-external-secrets
processing releases in group 2/3: default/external-secrets
processing releases in group 3/3: default/my-release
========== Updated Releases ==========
============================== Updated Releases ==============================
NAME NAMESPACE CHART VERSION DURATION
kubernetes-external-secrets kube-system incubator/raw 3.1.0 0s
external-secrets default incubator/raw 3.1.0 0s
@@ -49,7 +49,7 @@ GROUP RELEASES
processing releases in group 1/2: default/external-secrets
processing releases in group 2/2: default/my-release
========== Updated Releases ==========
======================= Updated Releases ========================
NAME NAMESPACE CHART VERSION DURATION
external-secrets default incubator/raw 3.1.0 0s
my-release default incubator/raw 3.1.0 0s
@@ -59,13 +59,13 @@ processing releases in group 1/2: default/external-secrets
WARNING: release external-secrets needs kubernetes-external-secrets, but kubernetes-external-secrets is not installed due to installed: false. Either mark kubernetes-external-secrets as installed or remove kubernetes-external-secrets from external-secrets's needs
processing releases in group 2/2: default/my-release
========== Updated Releases ==========
======================= Updated Releases ========================
NAME NAMESPACE CHART VERSION DURATION
external-secrets default incubator/raw 3.1.0 0s
my-release default incubator/raw 3.1.0 0s
========== Deleted Releases ==========
================= Deleted Releases =================
NAME NAMESPACE DURATION
kubernetes-external-secrets kube-system 0s
@@ -53,7 +53,7 @@ processing releases in group 1/2: default/external-secrets
WARNING: release external-secrets needs kubernetes-external-secrets, but kubernetes-external-secrets is not installed due to installed: false. Either mark kubernetes-external-secrets as installed or remove kubernetes-external-secrets from external-secrets's needs
processing releases in group 2/2: default/my-release
========== Updated Releases ==========
======================= Updated Releases ========================
NAME NAMESPACE CHART VERSION DURATION
external-secrets default incubator/raw 3.1.0 0s
my-release default incubator/raw 3.1.0 0s
+1 -1
View File
@@ -47,7 +47,7 @@ GROUP RELEASES
processing releases in group 1/2: default/external-secrets
processing releases in group 2/2: default/my-release
========== Updated Releases ==========
======================= Updated Releases ========================
NAME NAMESPACE CHART VERSION DURATION
external-secrets default incubator/raw 3.1.0 0s
my-release default incubator/raw 3.1.0 0s
@@ -44,7 +44,7 @@ GROUP RELEASES
processing releases in group 1/1: default/external-secrets
========== Updated Releases ==========
======================= Updated Releases ========================
NAME NAMESPACE CHART VERSION DURATION
external-secrets default incubator/raw 3.1.0 0s
@@ -8,7 +8,7 @@ hook[preapply] logs |
hook[presync] logs | foo
hook[presync] logs |
========== Updated Releases ==========
================= Updated Releases ==================
NAME NAMESPACE CHART VERSION DURATION
foo default incubator/raw 0s
@@ -2,7 +2,7 @@
hook[presync] logs | foo
hook[presync] logs |
========== Updated Releases ==========
================= Updated Releases ==================
NAME NAMESPACE CHART VERSION DURATION
foo default incubator/raw 0s
@@ -2,7 +2,7 @@
hook[preapply] logs | foo
hook[preapply] logs |
========== Updated Releases ==========
================= Updated Releases ==================
NAME NAMESPACE CHART VERSION DURATION
foo default incubator/raw 0s
@@ -11,7 +11,7 @@ hook[preapply] logs |
hook[presync] logs | foo
hook[presync] logs |
========== Updated Releases ==========
================= Updated Releases ==================
NAME NAMESPACE CHART VERSION DURATION
foo default incubator/raw 3.1.0 0s
@@ -8,7 +8,7 @@ hook[preapply] logs |
hook[presync] logs | foo
hook[presync] logs |
========== Updated Releases ==========
================= Updated Releases ==================
NAME NAMESPACE CHART VERSION DURATION
foo default incubator/raw 0s
@@ -17,7 +17,7 @@ hook[presync] logs |
hook[presync] logs | bar
hook[presync] logs |
========== Updated Releases ==========
================= Updated Releases ==================
NAME NAMESPACE CHART VERSION DURATION
foo default incubator/raw 0s
bar default incubator/raw 0s
@@ -14,12 +14,12 @@ hook[presync] logs |
hook[presync] logs | foo
hook[presync] logs |
========== Updated Releases ==========
================= Updated Releases ==================
NAME NAMESPACE CHART VERSION DURATION
foo default incubator/raw 3.1.0 0s
========== Deleted Releases ==========
==== Deleted Releases =====
NAME NAMESPACE DURATION
bar default 0s
@@ -13,7 +13,7 @@ release "frontend-v1" processed
processing releases in group 2/2: default//backend-v1
release "backend-v1" processed
========== Deleted Releases ==========
======== Deleted Releases ========
NAME NAMESPACE DURATION
frontend-v1 0s
backend-v1 0s
@@ -8,7 +8,7 @@ GROUP RELEASES
processing releases in group 1/1: default//logging
release "logging" processed
========== Deleted Releases ==========
====== Deleted Releases ======
NAME NAMESPACE DURATION
logging 0s
+1 -1
View File
@@ -13,7 +13,7 @@ release "frontend-v1" processed
processing releases in group 2/2: default//backend-v1
release "backend-v1" processed
========== Deleted Releases ==========
======== Deleted Releases ========
NAME NAMESPACE DURATION
frontend-v1 0s
backend-v1 0s
+1 -1
View File
@@ -25,7 +25,7 @@ processing releases in group 5/5: default//front-proxy, default//logging
release "front-proxy" processed
release "logging" processed
========== Deleted Releases ==========
========= Deleted Releases ==========
NAME NAMESPACE DURATION
frontend-v3 0s
frontend-v2 0s
@@ -13,7 +13,7 @@ release "frontend-v1" processed
processing releases in group 2/2: backend-v1
release "backend-v1" processed
========== Deleted Releases ==========
======== Deleted Releases ========
NAME NAMESPACE DURATION
frontend-v1 0s
backend-v1 0s
@@ -8,7 +8,7 @@ GROUP RELEASES
processing releases in group 1/1: logging
release "logging" processed
========== Deleted Releases ==========
====== Deleted Releases ======
NAME NAMESPACE DURATION
logging 0s
+1 -1
View File
@@ -13,7 +13,7 @@ release "frontend-v1" processed
processing releases in group 2/2: backend-v1
release "backend-v1" processed
========== Deleted Releases ==========
======== Deleted Releases ========
NAME NAMESPACE DURATION
frontend-v1 0s
backend-v1 0s
+1 -1
View File
@@ -25,7 +25,7 @@ processing releases in group 5/5: front-proxy, logging
release "front-proxy" processed
release "logging" processed
========== Deleted Releases ==========
========= Deleted Releases ==========
NAME NAMESPACE DURATION
frontend-v3 0s
frontend-v2 0s
+43
View File
@@ -9,6 +9,7 @@ import (
"time"
"unicode/utf8"
"github.com/mattn/go-runewidth"
"github.com/werf/kubedog/pkg/trackers/dyntracker/logstore"
"github.com/werf/kubedog/pkg/trackers/dyntracker/statestore"
kdutil "github.com/werf/kubedog/pkg/trackers/dyntracker/util"
@@ -53,6 +54,48 @@ func HeaderDividerStyled(title string, useColor bool) string {
return ansiBold + ansiBlue + h + ansiReset
}
// TableVisualWidth returns the maximum visual width of any line in the
// given table string, using runewidth for correct wide-character handling.
// Trailing spaces are trimmed before measuring because prettytable omits
// trailing padding on left-aligned last columns.
func TableVisualWidth(tableStr string) int {
max := 0
for _, line := range strings.Split(tableStr, "\n") {
line = strings.TrimRight(line, " ")
w := runewidth.StringWidth(line)
if w > max {
max = w
}
}
return max
}
// HeaderDividerCentered returns a section header where the title is centered
// within a line of '=' characters whose total visual width matches width.
// Falls back to HeaderDivider when the table is too narrow for the title.
func HeaderDividerCentered(title string, width int) string {
content := " " + title + " "
contentWidth := runewidth.StringWidth(content)
if width <= contentWidth {
return HeaderDivider(title)
}
padding := width - contentWidth
left := padding / 2
right := padding - left
return strings.Repeat("=", left) + content + strings.Repeat("=", right)
}
// HeaderDividerCenteredStyled is HeaderDividerCentered with the bold+blue
// style used for section titles. When useColor is false it returns the plain
// centered divider so non-TTY/CI-without-color callers see identical bytes.
func HeaderDividerCenteredStyled(title string, width int, useColor bool) string {
h := HeaderDividerCentered(title, width)
if !useColor {
return h
}
return ansiBold + ansiBlue + h + ansiReset
}
// StyleWarning wraps text in bold+yellow so it stands out among the regular
// info lines in CI logs without screaming "everything is on fire" — that's
// what red is for. Use this for messages an operator should notice and act
+65
View File
@@ -546,6 +546,71 @@ func TestHeaderDividerStyled(t *testing.T) {
"styled header must contain the plain divider unchanged")
}
func TestHeaderDividerCentered_TitleCenteredWithinWidth(t *testing.T) {
// "Updated Releases" = 16 chars; content with spaces = 18.
// width 71 → padding 53, left 26, right 27.
got := HeaderDividerCentered("Updated Releases", 71)
assert.Equal(t, 71, len(got))
assert.Contains(t, got, " Updated Releases ")
assert.True(t, strings.HasPrefix(got, "="))
assert.True(t, strings.HasSuffix(got, "="))
}
func TestHeaderDividerCentered_OddPaddingPutsExtraOnRight(t *testing.T) {
// content " hi " = 4 chars; width 10 → padding 6, left 3, right 3.
got := HeaderDividerCentered("hi", 10)
assert.Equal(t, "=== hi ===", got)
// width 11 → padding 7, left 3, right 4.
got = HeaderDividerCentered("hi", 11)
assert.Equal(t, "=== hi ====", got)
}
func TestHeaderDividerCentered_FallsBackWhenTooNarrow(t *testing.T) {
// "Updated Releases" with spaces = 18 chars.
// width 18 → exactly fits content, no room for '=' → fallback.
got := HeaderDividerCentered("Updated Releases", 18)
assert.Equal(t, HeaderDivider("Updated Releases"), got)
// width 10 → narrower than content → fallback.
got = HeaderDividerCentered("Updated Releases", 10)
assert.Equal(t, HeaderDivider("Updated Releases"), got)
}
func TestHeaderDividerCenteredStyled_NoColorIdenticalToPlain(t *testing.T) {
assert.Equal(t,
HeaderDividerCentered("title:", 30),
HeaderDividerCenteredStyled("title:", 30, false),
)
}
func TestHeaderDividerCenteredStyled_WithColorWrapsAnsi(t *testing.T) {
styled := HeaderDividerCenteredStyled("title:", 30, true)
assert.True(t, strings.HasPrefix(styled, ansiBold+ansiBlue),
"styled header must start with bold+blue, got %q", styled)
assert.True(t, strings.HasSuffix(styled, ansiReset),
"styled header must end with reset, got %q", styled)
assert.Contains(t, styled, HeaderDividerCentered("title:", 30),
"styled header must contain the plain centered divider unchanged")
}
func TestTableVisualWidth_MeasuresMaxLine(t *testing.T) {
// "NAME CHART" = 12, "foo charts/bar" = 17 → max is 17.
table := "NAME CHART\nfoo charts/bar\n"
assert.Equal(t, 17, TableVisualWidth(table))
}
func TestTableVisualWidth_TrimsTrailingSpaces(t *testing.T) {
// Last column left-aligned → trailing spaces on data rows.
// Header line "NAME CHART" = 12, data "foo baz " = 12 (with trailing).
// After trimming, data = "foo baz" = 10, so max is 12.
table := "NAME CHART\nfoo baz \n"
assert.Equal(t, 12, TableVisualWidth(table))
}
func TestTableVisualWidth_EmptyString(t *testing.T) {
assert.Equal(t, 0, TableVisualWidth(""))
}
func TestFlushHeartbeat_EmitsWhenIdleWithInFlight(t *testing.T) {
taskStore := kdutil.NewConcurrent(statestore.NewTaskStore())
logStore := kdutil.NewConcurrent(logstore.NewLogStore())
+18 -13
View File
@@ -5167,12 +5167,11 @@ func hideChartCredentials(chartCredentials string) (string, error) {
}
// DisplayAffectedReleases logs the upgraded, deleted and in error releases.
// useColor controls whether the section headers carry the bold+blue style we
// use elsewhere for visual section breaks; pass false (e.g. when --no-color is
// set) for clean output in non-TTY consumers.
// Each section header is centered within a line of '=' characters whose width
// matches the table below it. useColor controls whether the headers carry
// the bold+blue style used for visual section breaks.
func (ar *AffectedReleases) DisplayAffectedReleases(logger *zap.SugaredLogger, useColor bool) {
if len(ar.Upgraded) > 0 {
logger.Infof("\n%s", kubedog.HeaderDividerStyled("Updated Releases", useColor))
tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"},
prettytable.Column{Header: "NAMESPACE", MinWidth: 6},
prettytable.Column{Header: "CHART", MinWidth: 6},
@@ -5191,10 +5190,11 @@ func (ar *AffectedReleases) DisplayAffectedReleases(logger *zap.SugaredLogger, u
logger.Warn("Could not add row, %v", err)
}
}
logger.Info(tbl.String())
tableStr := tbl.String()
logger.Infof("\n%s", kubedog.HeaderDividerCenteredStyled("Updated Releases", kubedog.TableVisualWidth(tableStr), useColor))
logger.Info(tableStr)
}
if len(ar.Reinstalled) > 0 {
logger.Infof("\n%s", kubedog.HeaderDividerStyled("Reinstalled Releases", useColor))
tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"},
prettytable.Column{Header: "NAMESPACE", MinWidth: 6},
prettytable.Column{Header: "CHART", MinWidth: 6},
@@ -5213,10 +5213,11 @@ func (ar *AffectedReleases) DisplayAffectedReleases(logger *zap.SugaredLogger, u
logger.Warn("Could not add row, %v", err)
}
}
logger.Info(tbl.String())
tableStr := tbl.String()
logger.Infof("\n%s", kubedog.HeaderDividerCenteredStyled("Reinstalled Releases", kubedog.TableVisualWidth(tableStr), useColor))
logger.Info(tableStr)
}
if len(ar.Deleted) > 0 {
logger.Infof("\n%s", kubedog.HeaderDividerStyled("Deleted Releases", useColor))
tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"},
prettytable.Column{Header: "NAMESPACE", MinWidth: 6},
prettytable.Column{Header: "DURATION", AlignRight: true},
@@ -5228,10 +5229,11 @@ func (ar *AffectedReleases) DisplayAffectedReleases(logger *zap.SugaredLogger, u
logger.Warn("Could not add row, %v", err)
}
}
logger.Info(tbl.String())
tableStr := tbl.String()
logger.Infof("\n%s", kubedog.HeaderDividerCenteredStyled("Deleted Releases", kubedog.TableVisualWidth(tableStr), useColor))
logger.Info(tableStr)
}
if len(ar.Failed) > 0 {
logger.Infof("\n%s", kubedog.HeaderDividerStyled("Failed Releases", useColor))
tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"},
prettytable.Column{Header: "NAMESPACE", MinWidth: 6},
prettytable.Column{Header: "CHART", MinWidth: 6},
@@ -5245,10 +5247,11 @@ func (ar *AffectedReleases) DisplayAffectedReleases(logger *zap.SugaredLogger, u
logger.Warn("Could not add row, %v", err)
}
}
logger.Info(tbl.String())
tableStr := tbl.String()
logger.Infof("\n%s", kubedog.HeaderDividerCenteredStyled("Failed Releases", kubedog.TableVisualWidth(tableStr), useColor))
logger.Info(tableStr)
}
if len(ar.DeleteFailed) > 0 {
logger.Infof("\n%s", kubedog.HeaderDividerStyled("Failed to Delete Releases", useColor))
tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"},
prettytable.Column{Header: "NAMESPACE", MinWidth: 6},
prettytable.Column{Header: "DURATION", AlignRight: true},
@@ -5260,7 +5263,9 @@ func (ar *AffectedReleases) DisplayAffectedReleases(logger *zap.SugaredLogger, u
logger.Warn("Could not add row, %v", err)
}
}
logger.Info(tbl.String())
tableStr := tbl.String()
logger.Infof("\n%s", kubedog.HeaderDividerCenteredStyled("Failed to Delete Releases", kubedog.TableVisualWidth(tableStr), useColor))
logger.Info(tableStr)
}
}
@@ -4,6 +4,6 @@ Listing releases matching ^uninstalled$
Upgrading release=installed, chart=../../../charts/httpbin, namespace=helmfile-tests
Listing releases matching ^installed$
========== Updated Releases ==========
=========================== Updated Releases ============================
NAME NAMESPACE CHART VERSION DURATION
@@ -4,6 +4,6 @@ Listing releases matching ^uninstalled$
Upgrading release=installed, chart=../../../charts/httpbin, namespace=helmfile-tests
Listing releases matching ^installed$
========== Updated Releases ==========
=========================== Updated Releases ============================
NAME NAMESPACE CHART VERSION DURATION
@@ -9,6 +9,6 @@ REVISION: 1
TEST SUITE: None
Listing releases matching ^installed$
========== Updated Releases ==========
=========================== Updated Releases ============================
NAME NAMESPACE CHART VERSION DURATION
@@ -10,6 +10,6 @@ DESCRIPTION: Install complete
TEST SUITE: None
Listing releases matching ^installed$
========== Updated Releases ==========
=========================== Updated Releases ============================
NAME NAMESPACE CHART VERSION DURATION