Skip to content

Conversation

olucasfreitas
Copy link
Contributor

Details

This PR improves the UX when listing resources by introducing a new --hide-empty-columns flag that automatically hides columns containing no data across all rows, making the output cleaner and more readable.

The flag has been added to the following rosa list commands:

  • accessrequests
  • accountroles
  • addon
  • breakglasscredential
  • cluster
  • dnsdomains
  • gates
  • iamserviceaccounts
  • idp
  • ingress
  • instancetypes
  • ocmroles
  • oidcconfig
  • operatorroles
  • region
  • service
  • upgrade

Note: The following commands do NOT have this flag:

  • list machinepools - Already has built-in auto-hide empty columns logic
  • list version, list userroles, list user, list rhRegion, list tuningconfigs, list oidcprovider, list kubeletconfig, list externalauthprovider - These maintain their original behavior

Default Behavior (All Columns Shown)

  1. Run any supported list command without the flag:

    ./rosa list clusters
  2. You should see all columns displayed, including empty ones:

    ID          NAME        STATE  TOPOLOGY  REGION      PROVIDER  VERSION
    1a2b3c4d    my-cluster  ready            us-east-1   aws       4.14.0
    5e6f7g8h    test-rosa   ready            us-west-2   aws       4.13.5
    

Using --hide-empty-columns Flag

  1. Run the same command with the flag:

    ./rosa list clusters --hide-empty-columns
  2. Empty columns (like TOPOLOGY in this example) should be automatically hidden:

    ID          NAME        STATE  REGION      PROVIDER  VERSION
    1a2b3c4d    my-cluster  ready  us-east-1   aws       4.14.0
    5e6f7g8h    test-rosa   ready  us-west-2   aws       4.13.5
    

New behavior of the '--all'

  1. The --all flag shows the 3 additional columns (AZ TYPE, WIN-LI ENABLED, DEDICATED HOST) but still hides empty columns:

    ./rosa list machinepools --cluster <cluster-name> --all

    Example output:

    ID       AUTOSCALING  REPLICAS  INSTANCE TYPE  SPOT INSTANCES  DISK SIZE  AZ TYPE   WIN-LI ENABLED  DEDICATED HOST
    workers  No           3         m5.xlarge      No              default    Standard  No              No
    

Ticket

Closes [OCM-1520]

@openshift-ci openshift-ci bot requested review from ciaranRoche and gdbranco August 21, 2025 01:57
Copy link
Contributor

openshift-ci bot commented Aug 21, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: olucasfreitas
Once this PR has been reviewed and has the lgtm label, please assign gdbranco for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Copy link

codecov bot commented Aug 21, 2025

Codecov Report

❌ Patch coverage is 21.49321% with 347 lines in your changes missing coverage. Please review.
✅ Project coverage is 27.18%. Comparing base (8f44abd) to head (bdf62a6).
⚠️ Report is 27 commits behind head on master.

Files with missing lines Patch % Lines
pkg/output/table_filter.go 0.00% 44 Missing ⚠️
cmd/list/addon/cmd.go 3.03% 32 Missing ⚠️
cmd/list/operatorroles/cmd.go 3.57% 27 Missing ⚠️
cmd/list/gates/cmd.go 3.70% 26 Missing ⚠️
pkg/machinepool/machinepool.go 60.93% 25 Missing ⚠️
cmd/list/idp/cmd.go 4.16% 23 Missing ⚠️
cmd/list/ocmroles/cmd.go 5.00% 19 Missing ⚠️
cmd/list/ingress/cmd.go 5.26% 18 Missing ⚠️
cmd/list/service/cmd.go 5.55% 17 Missing ⚠️
cmd/list/cluster/cmd.go 6.25% 15 Missing ⚠️
... and 10 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2983      +/-   ##
==========================================
- Coverage   27.37%   27.18%   -0.19%     
==========================================
  Files         306      308       +2     
  Lines       34350    34710     +360     
==========================================
+ Hits         9403     9437      +34     
- Misses      24301    24615     +314     
- Partials      646      658      +12     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@hunterkepley hunterkepley left a comment

Choose a reason for hiding this comment

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

Overall looks much better than the original write, left some comments- will wait for you to make changes before doing review on the pkg/output files introduced since they may change

@olucasfreitas
Copy link
Contributor Author

@hunterkepley addressed all the comments in the new commit

@olucasfreitas olucasfreitas force-pushed the OCM-1520 branch 2 times, most recently from ef825a0 to eb6702d Compare August 27, 2025 21:45
Copy link
Contributor

@hunterkepley hunterkepley left a comment

Choose a reason for hiding this comment

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

Some more comments, looking good 🙂 Noticed a couple of improvements which could be made to lessen the LoC committed for this effort, as well as a couple Go things that would be useful

if err := writer.Flush(); err != nil {
return err
}

_, hasPending, pendingId := getAccessRequestsOutput(clusterId, accessRequests)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why wouldn't we use the output like the original file did here?

Copy link
Contributor Author

@olucasfreitas olucasfreitas Aug 28, 2025

Choose a reason for hiding this comment

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

We're no longer using the output string from getAccessRequestsOutput because we've refactored the code to use the dynamic headers approach with output.BuildTable()

@olucasfreitas
Copy link
Contributor Author

@hunterkepley new comments addressed on the new commit

Copy link
Contributor

openshift-ci bot commented Aug 28, 2025

@olucasfreitas: all tests passed!

Full PR test history. Your PR dashboard.

Instructions 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. I understand the commands that are listed here.

Copy link
Contributor

@hunterkepley hunterkepley left a comment

Choose a reason for hiding this comment

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

Some suggestions on the implementation of BuildTable as well as the edge case

@@ -126,7 +125,6 @@ var _ = Describe("rosa attach policy", func() {
Expect(err).NotTo(HaveOccurred())

stdOut, _ := t.StdOutReader.Read()
fmt.Println(stdOut)
Copy link
Contributor

Choose a reason for hiding this comment

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

unnecessary change

Comment on lines +109 to +126
func checkForPendingRequests(clusterId string, accessRequests []*v1.AccessRequest) (bool, string) {
hasPending := false
id := "<ID>"
for _, accessRequest := range accessRequests {
if accessRequest.Status().State() == v1.AccessRequestStatePending {
if accessRequest.Status().State() == v1.AccessRequestStatePending ||
accessRequest.Status().State() == v1.AccessRequestStateApproved {
hasPending = true
if clusterId != "" {
id = accessRequest.ID()
}
// Once we find the first pending/approved request for the cluster, we can break
// since we only need one ID for the suggestion message
if clusterId != "" && hasPending {
break
}
}
output += fmt.Sprintf("%s\t%s\t%s\t%s\n",
accessRequest.Status().State(),
accessRequest.ID(),
accessRequest.ClusterId(),
accessRequest.UpdatedAt().Format(time.UnixDate))
}

return output, hasPending, id
return hasPending, id
Copy link
Contributor

Choose a reason for hiding this comment

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

This command has been altered pretty heavily in terms of how it gathers data and im worried it will have different a different table for certain clusters after these changes. Could you provide some output from it to show that it works the same?

Comment on lines -120 to +126
writer,
"%s\t%s\t%s\t%s\t%s\n",
row := []string{
accountRole.RoleName,
accountRole.RoleType,
accountRole.RoleARN,
accountRole.Version,
awsManaged,
)
}
tableData = append(tableData, row)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can be simplified:

tableData = append(tableData, []string{
    accountRole.RoleName,
    ...
    ...
})

if err := writer.Flush(); err != nil {
_ = r.Reporter.Errorf("Failed to flush output: %v", err)
os.Exit(1)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This file looks great

Comment on lines +1353 to +1364
// removeEmptyColumnsExceptSpecial removes empty columns except those marked as special
// (i.e., columns that are controlled by explicit flags and should be shown even if empty)
func removeEmptyColumnsExceptSpecial(headers []string, tableData [][]string, specialColumns map[int]bool) ([]string, [][]string) {
var newHeaders []string
var columnsToKeep []int

for i, header := range headers {
// Keep special columns regardless of whether they're empty
if specialColumns[i] {
newHeaders = append(newHeaders, header)
columnsToKeep = append(columnsToKeep, i)
continue
Copy link
Contributor

Choose a reason for hiding this comment

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

We should be able to come up with a solution which can take in special cases, rather than this.

Also the name of this function is not clear

Comment on lines +53 to +81
func BuildTable(writer *tabwriter.Writer, separator string, tableData [][]string) {
for _, row := range tableData {
if len(row) == 0 {
continue
}

// Build format string dynamically based on number of columns
formatString := buildFormatString(len(row), separator)

// Convert []string to []interface{} for fmt.Fprintf
args := make([]any, len(row))
for i, v := range row {
args[i] = v
}

fmt.Fprintf(writer, formatString, args...)
}
}

// buildFormatString creates a format string with the appropriate number of %s placeholders
func buildFormatString(columnCount int, separator string) string {
if columnCount == 0 {
return "\n"
}

format := ""
for i := range columnCount {
format += "%s"
if i < columnCount-1 {
Copy link
Contributor

@hunterkepley hunterkepley Sep 5, 2025

Choose a reason for hiding this comment

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

I think we could simplify this somewhat.

finalString := ""
for i, row := range c {
		finalString += strings.Join(row, "\t")
         if i < len(c) {
             finalString+="\n"
    }
}

This accomplishes joining rows using a separator \t

Now, if we want to use a custom separator (such as col1\t\tcol2\t\tcol3\tcol4\tcol5\n which has \t\t and \t) to avoid making an edge case function like you have, we can modify this slightly

separators := ["\t\t", "\t\t", "\t", "\t"] // Pass in rather than `separator` string
// To make it simple, you can edit this slice in `RemoveEmptyColumns` like you do with the tableData

// Combine rows with separator simply, using only a single nested for loop
for i, row := range c {
		for j, col := range row {
			finalStr += col + separators[j]
		}
		if i < len(c) {
			finalStr += "\n"
		}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

We need to make sure the separators list is the same length as each row. Probably could also add an if statement in the loop to break if it is >= the length of the separator list

Comment on lines +35 to +40
for _, row := range tableData {
var newRow []string
for _, colIdx := range columnsToKeep {
if colIdx < len(row) {
newRow = append(newRow, row[colIdx])
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

colIdx -> col (lets keep naming consistent..)

if colIdx < len(row) {
newRow = append(newRow, row[colIdx])
} else {
newRow = append(newRow, "")
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't do anything

@hunterkepley
Copy link
Contributor

Here's a Go Playground showing the solution I came up with for a list of separators: https://go.dev/play/p/2qUevkuSUUz

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants