Improve addons list table design #21288
Improve addons list table design #21288pavansaikrishna78 wants to merge 7 commits intokubernetes:masterfrom
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: pavansaikrishna78 The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
Hi @pavansaikrishna78. Thanks for your PR. I'm waiting for a kubernetes member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
Can one of the admins verify this patch? |
| // Mirror CN | ||
| AliyunMirror = "registry.cn-hangzhou.aliyuncs.com/google_containers" | ||
|
|
||
| //TABLE WRITER COLORS |
There was a problem hiding this comment.
is there a way to show how it looks like maybe a link or copy to the comment ?
is a library to use?
There was a problem hiding this comment.
| enabled := addonBundle.IsEnabled(cc) | ||
| temp = []string{addonName, cc.Name, fmt.Sprintf("%s %s", stringFromStatus(enabled), iconFromStatus(enabled)), maintainer} | ||
| if enabled{ | ||
| status := fmt.Sprintf("%s%s%s", constants.Enabled, iconFromStatus(enabled), constants.Default) |
There was a problem hiding this comment.
add a helper func to Format the text
we have a style package
style.Enable(.....)
| cpIP, strconv.Itoa(cpPort), k8sVersion, p.Status, strconv.Itoa(len(p.Config.Nodes)), c, k}) | ||
| if p.Status == "OK" { | ||
| data = append(data, []string{ | ||
| fmt.Sprintf("%s%s%s", constants.Enabled, p.Name, constants.Default), |
There was a problem hiding this comment.
we should have a style func that does
style.Normal( white color)
style.Enabled(
Style.Trouble(
| if isDetailed { | ||
| data = append(data, []string{p.Name, p.Config.Driver, p.Config.KubernetesConfig.ContainerRuntime, | ||
| cpIP, strconv.Itoa(cpPort), k8sVersion, p.Status, strconv.Itoa(len(p.Config.Nodes)), c, k}) | ||
| if p.Status == "OK" { |
There was a problem hiding this comment.
this should be an iteration loop that calls the and based on the status it will call the Style Function in the package.
nirs
left a comment
There was a problem hiding this comment.
Please split the logical changes to separate commits instead of posing a new commit on every change.
If tests fail fix the failing commit. Don't post another commit fixing the first commit. This makes reviewing the changes much harder. Adding fix commit locally is fine, but before you post a new version you should squash the fix commit into the commit that introduced the problem.
Please remove all the unrelated and unnecessary changes. Focus on only on the design change to keep the change small, easy to review and be able to complete it quickly.
| AliyunMirror = "registry.cn-hangzhou.aliyuncs.com/google_containers" | ||
|
|
||
| //TABLE WRITER COLORS | ||
| Enabled = "\033[32m" |
There was a problem hiding this comment.
This is green forground color. I will help to add a comment like:
Enabled = "\033[32m" // green forground|
|
||
| //TABLE WRITER COLORS | ||
| Enabled = "\033[32m" | ||
| Default = "\033[0m" |
There was a problem hiding this comment.
This is not a color but a reset command, removing all attributes (foreground color, background color, effects). We don't need a constant for default since it does not change the text.
| //TABLE WRITER COLORS | ||
| Enabled = "\033[32m" | ||
| Default = "\033[0m" | ||
| Disabled = "\033[0m" |
There was a problem hiding this comment.
Same, since this is using the same value as default.
| Enabled = "\033[32m" | ||
| Default = "\033[0m" | ||
| Disabled = "\033[0m" | ||
| Error = "\033[31m" |
There was a problem hiding this comment.
This is red foreground, a comment will help.
But I don't think this is the way to go. What we need is a new package for color that can be reused by code writing output for tables or other output.
This package can use constants to the define the color of enabled, disabled, or errors, and provide functions using this signature:
Enabled(s string) stringCode rendering UI should use the functions to decorate fragments of text.
To implement this we can use a library like this:
https://github.com/fatih/color?tab=readme-ov-file#insert-into-noncolor-strings-sprintfunc
The advantage of using a library is using a well tested code that works on all platforms.
| @@ -1,5 +1,5 @@ | |||
| /* | |||
| Copyright 2019 The Kubernetes Authors All rights reserved. | |||
| Copyright 2025 The Kubernetes Authors All rights reserved. | |||
There was a problem hiding this comment.
We don't need to update the dates, that's pointless effort. The best way is to remove the date form the copyright headers, but this is not in the scope of this PR, so just remove this change.
| applyColor = true | ||
| default: | ||
| applyColor = false | ||
| } |
There was a problem hiding this comment.
This is too complicated for no reason.
| row[i] = fmt.Sprintf("%s%d%s", colorCode, v, constants.Default) | ||
| default: | ||
| row[i] = fmt.Sprintf("%s%v%s", colorCode, v, constants.Default) | ||
| } |
There was a problem hiding this comment.
Converting the values to string like we did before was better. If we colorize all value in a raw, we can keep the old code as is and apply the right color function to the entire slice.
| updateProfilesStatus(validProfiles) | ||
|
|
||
| var body = map[string]interface{}{} | ||
| body := map[string]interface{}{} |
| tHeader = []string{"Addon Name", "Maintainer"} | ||
| } else { | ||
| tHeader = []string{"Addon Name", "Profile", "Status", "Maintainer"} | ||
| tHeader = []string{"Addon Name", "Enabled", "Maintainer"} |
There was a problem hiding this comment.
We have 3 logic changes:
- Removing the profile column
- Replacing Status with Enabled
- Coloring the output
Each change should be done in a separate commit. This will make it easier to write the code without errors, test it, and review.
| temp[i] = fmt.Sprintf("%s%s%s", valueColorCode, valueStr, constants.Default) | ||
| } else { | ||
| temp[i] = valueStr | ||
| } |
There was a problem hiding this comment.
Too complicated.
We need 2 steps:
- Create list of strings for the table writer
- Color the strings
For creating list of strings we can modify the previous code a little bit:
enabled := addonBundle.IsEnabled(cc)
row = []string{addonName, cc.Name, iconFromStatus(enabled), maintainer}iconForStatus() should return empty string if enabled is false. If it does not do this create a function that does this.
Since the list is so simple, we don't really need a loop to color it:
if enabled {
row[0] = color.Enabled(row[0])
row[2] = color.Enabled(row[2])
}But since wan to color longer lists (like profile list), we can add a function to color rows:
type colorFunc func(string) string
func ColorRow(row []string, colored colorFunc) {
for i := range row {
text := row[i]
if text == "" || isEmoji(text) {
continue
}
row[i] = colored(text)
}
}With this we can do:
switch status {
case Broken:
color.ColorRow(row, color.Broken)
case Error:
color.ColorRow(row, color.Error)
case Enabled:
color.ColorRow(row, color.Enabled)
}3f61c2c to
6bc5fee
Compare
565498b to
cd36cf8
Compare
addons list and profile list and making pass the test
cd36cf8 to
789116d
Compare
Wilson-Duncan
left a comment
There was a problem hiding this comment.
Reviewed and left a comment. In addition, for the run instructions it would be cleaner to update the screenshots to just be the table output. Or update listing the full command in plain text to make it easier to copy and paste.
i.e.
command: ./out/minikube addons list
...
command: ./out/minikube profile list -d
| } | ||
|
|
||
| var printAddonsList = func(cc *config.ClusterConfig, printDocs bool) { | ||
| func printAddonsList(cc *config.ClusterConfig, printDocs bool) { |
There was a problem hiding this comment.
I don't think this change is needed or relevant to the PR. Other files follow the same convention. So this should be taken care of in a different PR for refactoring once its determined why its being done this way. I think this is done to make the function easier to test but I'm not sure.
There was a problem hiding this comment.
hi @Wilson-Duncan here is the output of the and correcting your above comment



There was a problem hiding this comment.
I'm saying the commands should not be in the screenshots. ./out/minikube profile list, make, ./out/minikube addons list, and ./out/minikube profile list -d should be plain text and not a part of the screenshot.
addons list function correction
b3a9d84 to
64cb1a4
Compare
64cb1a4 to
b3a9d84
Compare
nirs
left a comment
There was a problem hiding this comment.
Lets remove all unrelated changes and keep only the code needed to add colors.
We can add additional changes later, for now we need to focus on the color changes.
|
|
||
| table.Options( | ||
| tablewriter.WithHeaderAutoFormat(tw.On), | ||
| ) |
There was a problem hiding this comment.
table.Options(tablewriter.WithHeaderAutoFormat(tw.On)) replaces this code?
Is this related to the color change? If not please don't include this in this PR. I'm trying to review the color changes and I don't have time to review other changes now.
| header = append(header, "Docs") | ||
| } | ||
| table.Header(tHeader) | ||
| table.Header(header) |
There was a problem hiding this comment.
Why did you change these names? Is this related to the color changes?
| if maintainer == "" { | ||
| maintainer = "3rd party (unknown)" | ||
| } | ||
|
|
There was a problem hiding this comment.
Why to you keep adding unrelated whitespace changes? I already commented about it in previous reviews several times.
| row[i] = colored("%s", row[i]) | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Lets move the helpers to another file since they are used both in adding_list.go and profile_list.go. We can add a color.go file for these helpers. If we use colors only in the config package this is good enough for now, but I think the best place for it is a new color package in pkg/minikube/color/color.go.
| type colorFunc func(string, ...interface{}) string | ||
|
|
||
| func isEmoji(s string) bool { | ||
| return strings.Contains(s, "✅") |
There was a problem hiding this comment.
This will not color test with emoji like "enabled ✅". Currently we don't have this but it will be more correct to use:
s == "✅"
Ideally we can use the unicode package to detect if the string contains only emoji characters.
|
The Kubernetes project currently lacks enough contributors to adequately respond to all PRs. This bot triages PRs according to the following rules:
You can:
Please send feedback to sig-contributor-experience at kubernetes/community. /lifecycle stale |
There was a problem hiding this comment.
Pull request overview
This pull request improves the visual design of the addons and profile list tables by adding color-coding based on status. Enabled addons and profiles with "OK" status are displayed in green, stopped/paused profiles are shown in yellow, and others in white. The PR also simplifies the addons list table structure.
Key changes:
- Introduced a new
pkg/minikube/colorpackage with aColorRowfunction for applying colors to table rows - Added color-coding to profile list output based on status (OK=green, Stopped/Paused=yellow, others=white)
- Modified addons list table to use colored rows for enabled/disabled addons and simplified the table headers
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| pkg/minikube/color/color.go | New package providing color helper functions for table row coloring with emoji detection |
| cmd/minikube/cmd/config/profile_list.go | Added color-coding logic to profile list table rows based on profile status |
| cmd/minikube/cmd/config/addons_list.go | Refactored table building, added color-coding for enabled/disabled addons, and removed Profile column from output |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if s == "✅" { | ||
| return true |
There was a problem hiding this comment.
The isEmoji function only checks for the checkmark emoji ("✅") but doesn't handle any other emojis. This is too specific and makes the function name misleading. Consider either renaming the function to isCheckmarkEmoji or extending it to handle all emojis that should be skipped during coloring.
| if s == "✅" { | |
| return true | |
| for _, r := range s { | |
| // Basic coverage for common emoji ranges: | |
| // - Miscellaneous Symbols & Dingbats, etc. (includes ✅ U+2705) | |
| // - Miscellaneous Symbols and Pictographs, Supplemental Symbols and Pictographs, etc. | |
| if (r >= 0x2600 && r <= 0x27BF) || (r >= 0x1F300 && r <= 0x1FAFF) { | |
| return true | |
| } |
| package color | ||
|
|
||
| // colorFunc allows generic coloring | ||
| type colorFunc func(string, ...interface{}) string | ||
|
|
||
| func isEmoji(s string) bool { | ||
| if s == "✅" { | ||
| return true | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| func ColorRow(row []string, colored colorFunc) { | ||
| for i := range row { | ||
| if row[i] == "" || isEmoji(row[i]) { | ||
| continue | ||
| } | ||
| row[i] = colored("%s", row[i]) | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
The new ColorRow function in pkg/minikube/color lacks test coverage. Given that other packages in pkg/minikube/ have test files (e.g., out_test.go, style_test.go), this new color package should also include tests to verify the coloring behavior, especially for edge cases like empty strings and emoji handling.
| row = []string{p.Name, p.Config.Driver, p.Config.KubernetesConfig.ContainerRuntime, | ||
| cpIP, k8sVersion, p.Status, strconv.Itoa(len(p.Config.Nodes)), c, k} | ||
| } | ||
|
|
There was a problem hiding this comment.
Trailing whitespace on this empty line should be removed to maintain code cleanliness and consistency with coding standards.
| default: | ||
| pkgcolor.ColorRow(row, color.WhiteString) | ||
| } | ||
|
|
There was a problem hiding this comment.
Trailing whitespace on this empty line should be removed to maintain code cleanliness and consistency with coding standards.
| func ColorRow(row []string, colored colorFunc) { | ||
| for i := range row { | ||
| if row[i] == "" || isEmoji(row[i]) { | ||
| continue | ||
| } | ||
| row[i] = colored("%s", row[i]) | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
The ColorRow function modifies the slice in place, which can be confusing as the function name doesn't indicate mutation. Consider either: 1) returning a new colored slice instead of modifying in place, or 2) renaming the function to something like ColorRowInPlace to make the mutation behavior explicit, or 3) adding documentation explaining the in-place modification.
| if cc == nil { | ||
| temp = []string{addonName, maintainer} | ||
| row = []string{addonName, maintainer} | ||
| } else { | ||
| enabled := addonBundle.IsEnabled(cc) | ||
| temp = []string{addonName, cc.Name, fmt.Sprintf("%s %s", stringFromStatus(enabled), iconFromStatus(enabled)), maintainer} | ||
| status := iconFromStatus(enabled) | ||
| row = []string{addonName, status, maintainer} | ||
| if printDocs { | ||
| row = append(row, docs) | ||
| } | ||
|
|
||
| // Step 2: apply coloring | ||
| switch { | ||
| case enabled: | ||
| pkgcolor.ColorRow(row, color.GreenString) | ||
| default: | ||
| pkgcolor.ColorRow(row, color.WhiteString) | ||
| } | ||
| } | ||
| if printDocs { | ||
| temp = append(temp, docs) | ||
|
|
||
| if cc == nil && printDocs { | ||
| row = append(row, docs) | ||
| } |
There was a problem hiding this comment.
The logic for building rows when cc == nil doesn't apply coloring but still appends the docs when printDocs is true. This creates an inconsistency where colored rows are built inside the "cc != nil" block at lines 125-144, but non-colored rows have their docs appended separately at lines 146-148. This duplicate logic for handling printDocs is harder to maintain and could lead to bugs.
| enabled := addonBundle.IsEnabled(cc) | ||
| temp = []string{addonName, cc.Name, fmt.Sprintf("%s %s", stringFromStatus(enabled), iconFromStatus(enabled)), maintainer} | ||
| status := iconFromStatus(enabled) | ||
| row = []string{addonName, status, maintainer} |
There was a problem hiding this comment.
The profile name (cc.Name) was removed from the table output entirely. This is a breaking change for users who may be parsing or relying on the table output to know which profile the addons belong to. Consider keeping the profile information in the table, especially when there are multiple profiles.
| row = []string{addonName, status, maintainer} | |
| // Include profile name in the table output to indicate which profile the addon belongs to. | |
| row = []string{cc.Name, addonName, status, maintainer} |
|
PR needs rebase. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
@pavansaikrishna78 this PR replaces this #22418 but you can still make the PR to change the addons list table to be worded differently and have less columns let me know if you intereted to do it |
If status is enabled then the whole row green
command: addons list

command: addons list -d

command: profile list

command: profile list -d

ADDON.LIST.MINIKUBE.docx