Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve atmos list components view #828

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open

improve atmos list components view #828

wants to merge 21 commits into from

Conversation

pkbhowmick
Copy link
Collaborator

@pkbhowmick pkbhowmick commented Dec 8, 2024

what

why

references

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new list subsection in the configuration, allowing structured representation of components, stacks, tenants, and regions.
    • Enhanced component filtering and listing functionality with an additional abstract flag for improved filtering options.
  • Bug Fixes

    • Improved error handling with color-coded error messages.
  • Documentation

    • Updated method signatures to reflect changes in functionality and parameters.
  • Tests

    • Modified test cases to accommodate new configuration options in the component listing functionality.

Signed-off-by: Pulak Kanti Bhowmick <pkbhowmick007@gmail.com>
Signed-off-by: Pulak Kanti Bhowmick <pkbhowmick007@gmail.com>
Signed-off-by: Pulak Kanti Bhowmick <pkbhowmick007@gmail.com>
@pkbhowmick pkbhowmick marked this pull request as ready for review December 8, 2024 14:05
@pkbhowmick pkbhowmick requested review from a team as code owners December 8, 2024 14:05
@pkbhowmick
Copy link
Collaborator Author

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Dec 8, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The pull request introduces modifications to the listComponentsCmd command and related components across several files. Key changes include the addition of a new list subsection in the atmos.yaml configuration file, updates to method signatures in the pkg/list/list_components.go file to accommodate new parameters, and the introduction of new types in the pkg/schema/schema.go file. These changes enhance the configurability of component listing and improve the structure of the output.

📝 Changes

Changes

File Change Summary
cmd/list_components.go Reordered import statements, removed duplicates, updated l.FilterAndListComponents method signature to include cliConfig.Components.List, and added abstractFlag.
examples/quick-start-advanced/atmos.yaml Added a new list subsection under components with columns: Component, Stack, Tenant, and Region, using Atmos context variables.
pkg/list/list_components.go Updated getStackComponents and collectComponents signatures to accept new parameters, added resolveKey helper function for key resolution, and modified output formatting.
pkg/list/list_components_test.go Updated TestListComponents to call FilterAndListComponents with the new schema.ListConfig{} parameter.
pkg/schema/schema.go Introduced ListConfig and ListColumnConfig types, added List field to Components struct.
📝 Assessment against linked issues

Assessment against linked issues

Objective Addressed Explanation
Add functionality to filter for real/abstract components (833)
Make output configurable in atmos.yaml (DEV-2805)
Include abstract components when --abstract is passed (DEV-2805) No implementation for abstract components.
Downgrade UI to plain-text if no TTY attached (DEV-2805) No information on TTY handling in changes.
📝 Possibly related PRs

Possibly related PRs

📝 Suggested reviewers

Suggested reviewers

  • Gowiem
  • aknysh

Tip

CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command @coderabbitai generate docstrings to have CodeRabbit automatically generate docstrings for your pull request. We would love to hear your feedback on Discord.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary or @auto-summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @auto-title anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (3)
pkg/list/list_components.go (1)

51-78: Consider adding function documentation

Adding a comment or docstring to resolveKey would improve readability and help others understand its purpose.

cmd/list_components.go (1)

42-44: Validate list configuration before use

Ensure cliConfig.Components.List is properly initialized to prevent unexpected errors when listing components.

pkg/schema/schema.go (1)

102-104: LGTM! Consider adding field documentation.

The ListColumnConfig type is well-structured and follows consistent patterns. Consider adding documentation comments to describe the expected format for the Value field, especially if it supports templating or special syntax.

Example documentation:

 type ListColumnConfig struct {
+    // Name is the display name of the column
     Name  string `yaml:"name" json:"name" mapstructure:"name"`
+    // Value is the template expression to extract the column value
     Value string `yaml:"value" json:"value" mapstructure:"value"`
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3f2f103 and a3fec3d.

📒 Files selected for processing (5)
  • cmd/list_components.go (2 hunks)
  • examples/quick-start-advanced/atmos.yaml (1 hunks)
  • pkg/list/list_components.go (4 hunks)
  • pkg/list/list_components_test.go (1 hunks)
  • pkg/schema/schema.go (1 hunks)
🔇 Additional comments (2)
examples/quick-start-advanced/atmos.yaml (1)

21-28: Configuration looks good

The new list section is well-structured and aligns with the intended enhancements.

pkg/schema/schema.go (1)

93-96: LGTM! Clean addition to Components struct.

The new List field is well-integrated into the existing structure, maintaining backward compatibility and following consistent field tag patterns.

pkg/list/list_components.go Outdated Show resolved Hide resolved
pkg/list/list_components_test.go Outdated Show resolved Hide resolved
pkg/schema/schema.go Show resolved Hide resolved
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
pkg/list/list_components.go (2)

48-48: Consider using '%v' for fmt.Sprintf to handle all value types

Using %v in fmt.Sprintf ensures that all types of value are formatted correctly, even if they aren't strings.

Apply this diff to update the code:

-rowData = append(rowData, fmt.Sprintf("%s", value))
+rowData = append(rowData, fmt.Sprintf("%v", value))

102-103: Improve error message for more context

Including the invalid value in the error message can help users identify formatting issues more easily.

Apply this diff to enhance the error message:

-return "", fmt.Errorf("invalid value format for column name %s", v.Name)
+return "", fmt.Errorf("invalid value format '%s' for column name '%s'", v.Value, v.Name)
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a3fec3d and d192dcf.

📒 Files selected for processing (1)
  • pkg/list/list_components.go (4 hunks)

Signed-off-by: Pulak Kanti Bhowmick <pkbhowmick007@gmail.com>
coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 8, 2024
@pkbhowmick
Copy link
Collaborator Author

demo:
image

@osterman
Copy link
Member

osterman commented Dec 8, 2024

Something is wrong with the folder. This is a folder relating to a stack configuration and not the component filesystem location.

Also, please left justify the columns.

Also, if a TTY is detected, please use lipgloss to display the table.

Other than that, it's on the right path.

Signed-off-by: Pulak Kanti Bhowmick <pkbhowmick007@gmail.com>
@pkbhowmick
Copy link
Collaborator Author

With lipgloss:
image

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (3)
pkg/list/list_components.go (3)

Line range hint 16-52: STRONG WORK on the error handling, warrior! Let's make it even more robust.

The function handles type assertions well, but we should validate the input parameters for additional strength.

Consider these battle-hardening improvements:

 func getStackComponents(stackData any, listFields []string) ([]string, error) {
+    if len(listFields) == 0 {
+        return nil, fmt.Errorf("listFields cannot be empty")
+    }
+
     stackMap, ok := stackData.(map[string]any)
     if !ok {
         return nil, fmt.Errorf("could not parse stacks")
     }

Also, pre-allocate the result slice with a capacity hint to optimize memory allocation:

-    result := make([]string, 0)
+    result := make([]string, 0, len(uniqueKeys))

54-81: Well-crafted battle plan for key resolution, soldier! Let's document it properly.

The function handles nested key resolution effectively, but could use more detailed documentation for future warriors.

Consider enhancing the documentation:

-// resolveKey resolves a key from a map, supporting nested keys with dot notation
+// resolveKey resolves a key from a map using dot notation for nested access.
+// Example:
+//   data := map[string]any{"vars": map[string]any{"tenant": "sparta"}}
+//   value, found := resolveKey(data, "vars.tenant") // Returns "sparta", true
+// Returns the resolved value and true if found, or nil and false if not found.
 func resolveKey(data map[string]any, key string) (any, bool) {

180-185: A simple but vital function for our formation! Let's make it faster.

The current implementation works but could be more efficient for longer padding requirements.

Consider using strings.Repeat for better performance:

 func padToWidth(str string, width int) string {
-    for len(str) < width {
-        str += " "
-    }
-    return str
+    if len(str) >= width {
+        return str
+    }
+    return str + strings.Repeat(" ", width-len(str))
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 6b4f28e and 277e5b7.

📒 Files selected for processing (1)
  • pkg/list/list_components.go (2 hunks)

pkg/list/list_components.go Outdated Show resolved Hide resolved
Signed-off-by: Pulak Kanti Bhowmick <pkbhowmick007@gmail.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (3)
pkg/list/list_components.go (3)

66-93: Strengthen our defenses with better error context!

While the function is well-structured, consider enhancing error reporting by including the full key path in the error context when traversal fails.

-            return nil, false
+            return nil, fmt.Errorf("failed to resolve key '%s' at part '%s'", key, part)

146-167: Optimize our battle formation!

The function performs well, but we can improve memory allocation efficiency by pre-allocating slices with capacity hints.

-    colWidths := make([]int, len(header))
+    colWidths := make([]int, len(header), len(header))

200-227: Document our battle strategy!

While the implementation is solid, consider adding godoc-style documentation to explain the return values and their significance.

+// FilterAndListComponents processes and displays components in a formatted table.
+// Returns an empty string on success or "No components found" when appropriate.
+// Error is returned if any processing step fails.
 func FilterAndListComponents(stackFlag string, stacksMap map[string]any, listConfig schema.ListConfig) (string, error) {
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 277e5b7 and 2555c35.

📒 Files selected for processing (1)
  • pkg/list/list_components.go (2 hunks)
🔇 Additional comments (3)
pkg/list/list_components.go (3)

15-24: By the gods, this is well-structured!

The extraction of color constants and the introduction of the tableData struct shows excellent organization. This follows our previous battle plan for better code structure.


Line range hint 27-63: A warrior's victory! Type safety has been conquered!

The function now safely handles type assertions with proper error messages, addressing our previous concerns. The addition of the listFields parameter provides flexibility in output formatting.


170-197: A display worthy of Olympus!

The table formatting implementation with lipgloss is clean and effective. The padding function is simple yet robust.

pkg/list/list_components.go Outdated Show resolved Hide resolved
pkg/list/list_components.go Outdated Show resolved Hide resolved
Signed-off-by: Pulak Kanti Bhowmick <pkbhowmick007@gmail.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (6)
pkg/list/list_components.go (6)

15-24: By the gods, these constants and types are well-structured!

The color constants and tableData struct provide a solid foundation for table formatting. However, a true warrior documents their arsenal.

Consider adding documentation comments for the tableData struct fields:

 type tableData struct {
+    // header contains the column names
     header    []string
+    // rows contains the table data
     rows      [][]string
+    // colWidths contains the width of each column
     colWidths []int
 }

65-93: A valiant attempt at key resolution, but let's sharpen our sword!

The function handles nested key traversal well, but could provide more context in error cases.

Consider enhancing error context:

 func resolveKey(data map[string]any, key string) (any, bool) {
     key = strings.TrimPrefix(key, ".")
     parts := strings.Split(key, ".")
     current := data
+    path := ""
     for i, part := range parts {
+        path += "." + part
         if i == len(parts)-1 {
             if value, exists := current[part]; exists {
                 return value, true
             }
-            return nil, false
+            return nil, fmt.Errorf("key not found at path: %s", strings.TrimPrefix(path, "."))
         }
         if nestedMap, ok := current[part].(map[string]any); ok {
             current = nestedMap
         } else {
-            return nil, false
+            return nil, fmt.Errorf("invalid path at: %s", strings.TrimPrefix(path, "."))
         }
     }
     return nil, false
 }

95-113: A well-forged function, but let's optimize for battle!

The column parsing logic is sound, but the regular expression compilation could be moved to package level for better performance.

Consider pre-compiling the regex:

+var columnValueRegex = regexp.MustCompile(`\{\{\s*(.*?)\s*\}\}`)
+
 func parseColumns(listConfig schema.ListConfig) ([]string, []string, error) {
     header := make([]string, 0)
     listFields := make([]string, 0)
-    re := regexp.MustCompile(`\{\{\s*(.*?)\s*\}\}`)
 
     for _, col := range listConfig.Columns {
         if col.Value == "" {
             return nil, nil, fmt.Errorf("empty value for column name %s", col.Name)
         }
         header = append(header, col.Name)
-        match := re.FindStringSubmatch(col.Value)
+        match := columnValueRegex.FindStringSubmatch(col.Value)

116-151: A strong warrior that now reports all battle casualties!

The error collection implementation is praiseworthy. For larger battles (datasets), we could employ parallel processing.

Consider adding concurrent processing for multiple stacks:

func collectComponentsConcurrently(stacksMap map[string]any, listFields []string) ([][]string, error) {
    var mu sync.Mutex
    var wg sync.WaitGroup
    components := [][]string{}
    errors := []string{}
    
    for stackName, stackData := range stacksMap {
        wg.Add(1)
        go func(name string, data any) {
            defer wg.Done()
            stackComponents, err := getStackComponents(data, listFields)
            mu.Lock()
            defer mu.Unlock()
            if err != nil {
                errors = append(errors, fmt.Sprintf("stack '%s': %v", name, err))
                return
            }
            for _, c := range stackComponents {
                components = append(components, strings.Fields(c))
            }
        }(stackName, stackData)
    }
    wg.Wait()
    
    if len(errors) > 0 {
        return components, fmt.Errorf("errors processing stacks: %s", strings.Join(errors, "; "))
    }
    return components, nil
}

177-205: A visually pleasing battle formation, but let's consider the battlefield size!

The table formatting looks great, but we should consider terminal width constraints.

Consider adding terminal width handling:

+import "golang.org/x/term"
+
 func formatTable(data tableData) {
+    width, _, err := term.GetSize(0)
+    if err != nil {
+        width = 80 // fallback width
+    }
+    
+    // Calculate total width including spacing
+    totalWidth := 0
+    for _, w := range data.colWidths {
+        totalWidth += w + 2 // +2 for spacing
+    }
+    
+    // Adjust column widths if necessary
+    if totalWidth > width {
+        ratio := float64(width) / float64(totalWidth)
+        for i := range data.colWidths {
+            data.colWidths[i] = int(float64(data.colWidths[i]) * ratio)
+        }
+    }

207-235: A well-orchestrated battle plan, but let's clarify the victory message!

The function orchestrates the process well, but returning an empty string on success might be confusing.

Consider returning a summary message on success:

 func FilterAndListComponents(stackFlag string, stacksMap map[string]any, listConfig schema.ListConfig) (string, error) {
     // ... existing code ...
     
     formatTable(data)
 
-    return "", nil
+    return fmt.Sprintf("Successfully displayed %d components", len(processedComponents)), nil
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 2555c35 and bb305d1.

📒 Files selected for processing (1)
  • pkg/list/list_components.go (2 hunks)
🔇 Additional comments (2)
pkg/list/list_components.go (2)

Line range hint 27-63: A mighty improvement, warrior!

The function now safely handles type assertions and constructs row data with precision. The implementation of the past review suggestions shows great wisdom.


153-175: A masterful display of component processing!

The function efficiently handles deduplication, sorting, and column width calculations. The use of the lo library for unique operations is particularly elegant.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 10, 2024
@pkbhowmick
Copy link
Collaborator Author

image

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (4)
pkg/list/list_components.go (4)

61-88: Shield this function from the arrows of empty keys, brave warrior!

The resolveKey function handles nested keys well, but could be more defensive against edge cases.

Consider adding these validations:

 func resolveKey(data map[string]any, key string) (any, bool) {
+    if key == "" {
+        return nil, false
+    }
+    if data == nil {
+        return nil, false
+    }
     // Remove leading dot from the key
     key = strings.TrimPrefix(key, ".")

129-144: A true warrior provides context in their battle reports!

The error collection is good, but we can make the error messages more informative.

Consider enhancing error context:

-errors = append(errors, err.Error())
+errors = append(errors, fmt.Sprintf("stack processing error: %v", err))

172-226: Let our warriors customize their battle formations!

The table formatting looks great, but consider making the styles configurable.

Consider extracting style configuration:

+type TableStyle struct {
+    HeaderColor    lipgloss.Color
+    BorderStyle    lipgloss.Border
+    HeaderAlign    lipgloss.Position
+}
+
+func generateTable(data tableData, style TableStyle) {
     // Use style configuration instead of hardcoded values

246-274: Document our battle strategy for future generations!

The function orchestration is clear, but could benefit from more detailed documentation.

Consider adding detailed documentation:

+// FilterAndListComponents orchestrates the component listing process by:
+// 1. Parsing and validating column configuration
+// 2. Collecting components from specified or all stacks
+// 3. Processing and deduplicating components
+// 4. Generating and displaying a formatted table
+// Returns an empty string on success or an error message
 func FilterAndListComponents(stackFlag string, stacksMap map[string]any, listConfig schema.ListConfig) (string, error) {
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between bb305d1 and dafdfc8.

📒 Files selected for processing (2)
  • examples/quick-start-advanced/atmos.yaml (1 hunks)
  • pkg/list/list_components.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/quick-start-advanced/atmos.yaml
🔇 Additional comments (1)
pkg/list/list_components.go (1)

15-19: By the gods, this structure is well-forged!

The tableData struct provides a clean organization for table-related data, making it easier to pass table information between functions.

pkg/list/list_components.go Outdated Show resolved Hide resolved
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 10, 2024
@osterman
Copy link
Member

osterman commented Dec 10, 2024

Can we use a purple color scheme rather than pink?
image

https://github.com/charmbracelet/lipgloss/blob/master/examples/table/languages/main.go

@pkbhowmick
Copy link
Collaborator Author

Updated table:
image

Signed-off-by: Pulak Kanti Bhowmick <pkbhowmick007@gmail.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (5)
pkg/list/list_components.go (5)

Line range hint 24-60: Strengthen our error messages, brave warrior!

While the error handling is solid, the error messages could be more descriptive to aid in debugging.

Consider enhancing the error messages with more context:

-		return nil, fmt.Errorf("could not parse stacks")
+		return nil, fmt.Errorf("failed to parse stack data: expected map[string]any, got %T", stackData)

-		return nil, fmt.Errorf("could not parse components")
+		return nil, fmt.Errorf("failed to parse components map from stack: components key not found or invalid type")

-		return nil, fmt.Errorf("could not parse Terraform components")
+		return nil, fmt.Errorf("failed to parse terraform components: terraform key not found or invalid type in components map")

62-89: A well-crafted function worthy of Sparta, but let's make it faster!

The resolveKey function is solid, but we can optimize it for better performance.

Consider these optimizations:

 func resolveKey(data map[string]any, key string) (any, bool) {
+	// Pre-allocate parts slice with expected capacity
+	parts := strings.SplitN(strings.TrimPrefix(key, "."), ".", 10)
-	parts := strings.Split(key, ".")
-	key = strings.TrimPrefix(key, ".")

	current := data
	for i, part := range parts {
+		// Early return if part is empty
+		if part == "" {
+			return nil, false
+		}
		// ... rest of the function

116-151: Let's unleash the power of concurrent processing, warrior!

For handling multiple stacks, we could leverage goroutines to process them concurrently.

Consider this concurrent approach:

 func collectComponents(stackFlag string, stacksMap map[string]any, listFields []string) ([][]string, error) {
+	type result struct {
+		components [][]string
+		err        error
+	}
+
 	if stackFlag == "" {
+		ch := make(chan result, len(stacksMap))
+		for _, stackData := range stacksMap {
+			go func(data any) {
+				stackComponents, err := getStackComponents(data, listFields)
+				var components [][]string
+				if err == nil {
+					for _, c := range stackComponents {
+						components = append(components, strings.Fields(c))
+					}
+				}
+				ch <- result{components, err}
+			}(stackData)
+		}
+
+		var allComponents [][]string
+		var errors []string
+		for range stacksMap {
+			r := <-ch
+			if r.err != nil {
+				errors = append(errors, r.err.Error())
+			} else {
+				allComponents = append(allComponents, r.components...)
+			}
+		}

153-175: A function worthy of Athens, but let's optimize memory usage!

The processComponents function could be more memory efficient.

Consider these optimizations:

 func processComponents(header []string, components [][]string) ([][]string, []int) {
+	// Pre-allocate slices with capacity
+	colWidths := make([]int, len(header))
+	uniqueMap := make(map[string][]string, len(components))
+
-	uniqueComponents := lo.UniqBy(components, func(item []string) string {
-		return strings.Join(item, "\t")
-	})
+	// Deduplicate while building map
+	for _, comp := range components {
+		key := strings.Join(comp, "\t")
+		uniqueMap[key] = comp
+	}

177-233: A beautifully styled table, but let's make it more configurable!

The table styling is well-implemented, but we could make it more flexible.

Consider making the styles configurable:

+type TableStyle struct {
+	HeaderColor    lipgloss.Color
+	OddRowColor    lipgloss.Color
+	EvenRowColor   lipgloss.Color
+	BorderColor    lipgloss.Color
+	DefaultWidth   int
+	HeaderAlign    lipgloss.Position
+}
+
+var DefaultTableStyle = TableStyle{
+	HeaderColor:    purple,
+	OddRowColor:    gray,
+	EvenRowColor:   lightGray,
+	BorderColor:    purple,
+	DefaultWidth:   20,
+	HeaderAlign:    lipgloss.Center,
+}
+
-func generateTable(data tableData) {
+func generateTable(data tableData, style TableStyle) {
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dafdfc8 and 82a8a73.

📒 Files selected for processing (1)
  • pkg/list/list_components.go (2 hunks)
🔇 Additional comments (1)
pkg/list/list_components.go (1)

5-21: By the gods, these imports and structures are well-organized!

The tableData struct provides a clean organization for table-related data, and the imports are properly structured for the new table formatting capabilities.

pkg/list/list_components.go Outdated Show resolved Hide resolved
pkg/list/list_components.go Show resolved Hide resolved
@osterman
Copy link
Member

We need to dynamically calculate the column widths to reduce the likelihood of wrapping. There is too much white space with equal width columns.

@osterman
Copy link
Member

Also, please post a screenshot with no tty attached. E.g. redirect </dev/null to the command. When this happens it should degrade and emit a simple tabular table. This is so grepping and parsing in pipelines works well.

@osterman
Copy link
Member

Is a component is disabled, it should be dark gray.

@osterman
Copy link
Member

I changed my mind on the purple border. Let's make it dark gray.

Signed-off-by: Pulak Kanti Bhowmick <pkbhowmick007@gmail.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
cmd/list_components.go (2)

Line range hint 43-48: Consider enhancing error handling with more context.

While the function call is correct, we could improve error handling to provide more context about which components failed to process.

-		output, err := l.FilterAndListComponents(stackFlag, abstractFlag, stacksMap, cliConfig.Components.List)
-		if err != nil {
-			u.PrintMessageInColor(fmt.Sprintf("Error: %v"+"\n", err), color.New(color.FgYellow))
-			return
-		}
+		output, err := l.FilterAndListComponents(stackFlag, abstractFlag, stacksMap, cliConfig.Components.List)
+		if err != nil {
+			if strings.Contains(err.Error(), "errors processing stacks") {
+				u.PrintMessageInColor(fmt.Sprintf("Warning: Some stacks failed to process: %v"+"\n", err), color.New(color.FgYellow))
+			} else {
+				u.PrintMessageInColor(fmt.Sprintf("Error: %v"+"\n", err), color.New(color.FgRed))
+				return
+			}
+		}

55-55: Enhance the flag description for better clarity.

The help message could be more descriptive to better explain the flag's purpose.

-	listComponentsCmd.PersistentFlags().Bool("abstract", false, "Filter abstract component if true")
+	listComponentsCmd.PersistentFlags().Bool("abstract", false, "Include abstract components in the output (default: only concrete components)")
pkg/list/list_components.go (4)

18-22: Add documentation for the tableData struct.

Consider adding a descriptive comment to explain the purpose of each field in the struct.

+// tableData holds the necessary information for rendering a formatted table
+// header: contains the column names
+// rows: contains the data rows
+// colWidths: contains the width of each column for proper alignment
 type tableData struct {
 	header    []string
 	rows      [][]string
 	colWidths []int
 }

69-97: Strengthen error handling in resolveKey function.

The function handles basic cases well, but consider adding validation for empty keys and nil maps.

 func resolveKey(data map[string]any, key string) (any, bool) {
+	if data == nil {
+		return nil, false
+	}
+	if key == "" {
+		return nil, false
+	}
 	// Remove leading dot from the key
 	key = strings.TrimPrefix(key, ".")

141-156: Enhance error reporting with stack context.

Consider structuring the error collection to provide more context about which stacks failed.

-		var errors []string
+		type stackError struct {
+			stack string
+			err   error
+		}
+		var errors []stackError
 		for stackName, stackData := range stacksMap {
 			stackComponents, err := getStackComponents(stackData, abstractFlag, listFields)
 			if err != nil {
-				errors = append(errors, err.Error())
+				errors = append(errors, stackError{stack: stackName, err: err})
 				continue // Skip invalid stacks
 			}
 		}
 		if len(errors) > 0 {
-			return components, fmt.Errorf("errors processing stacks: %s", strings.Join(errors, "; "))
+			var errMsgs []string
+			for _, e := range errors {
+				errMsgs = append(errMsgs, fmt.Sprintf("stack '%s': %v", e.stack, e.err))
+			}
+			return components, fmt.Errorf("errors processing stacks:\n%s", strings.Join(errMsgs, "\n"))
 		}

242-270: Consider returning meaningful success information.

The function returns an empty string on success, which could be more informative.

-func FilterAndListComponents(stackFlag string, abstractFlag bool, stacksMap map[string]any, listConfig schema.ListConfig) (string, error) {
+func FilterAndListComponents(stackFlag string, abstractFlag bool, stacksMap map[string]any, listConfig schema.ListConfig) (result struct {
+	ComponentCount int
+	Message       string
+}, error) {
 	// ... existing code ...
-	return "", nil
+	return struct {
+		ComponentCount int
+		Message       string
+	}{
+		ComponentCount: len(processedComponents),
+		Message:       fmt.Sprintf("Successfully listed %d components", len(processedComponents)),
+	}, nil
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 925d0e7 and 2c85549.

📒 Files selected for processing (3)
  • cmd/list_components.go (4 hunks)
  • pkg/list/list_components.go (2 hunks)
  • pkg/list/list_components_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/list/list_components_test.go
🔇 Additional comments (1)
cmd/list_components.go (1)

6-8: LGTM! Clean import organization and flag addition.

The import statements are well-organized, and the new abstractFlag is properly integrated.

Also applies to: 28-28

pkg/list/list_components.go Outdated Show resolved Hide resolved
Signed-off-by: Pulak Kanti Bhowmick <pkbhowmick007@gmail.com>
Signed-off-by: Pulak Kanti Bhowmick <pkbhowmick007@gmail.com>
@pkbhowmick
Copy link
Collaborator Author

Update table:
image

Without TTY:
image

@pkbhowmick
Copy link
Collaborator Author

@coderabbitai review

@osterman
Copy link
Member

That’s closer, but the border is not dark gray (e.g #666666) and there’s no columnar display when it degrades.

It should look like this when it degrades
image

@osterman
Copy link
Member

Oh, also looks like each row is encapsulated by brackets. Remove those too.

@osterman osterman added the minor New features that do not break anything label Dec 18, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
pkg/list/list_components.go (1)

100-122: 🛠️ Refactor suggestion

Optimize our battle strategy with pre-compiled regex!

The regex pattern should be compiled once at the package level for better performance.

Move the regex compilation to package level:

+var columnValueRegex = regexp.MustCompile(`\{\{\s*(.*?)\s*\}\}`)
+
 func parseColumns(listConfig schema.ListConfig) ([]string, []string, error) {
-    re := regexp.MustCompile(`\{\{\s*(.*?)\s*\}\}`)
     // ... rest of the function
-    match := re.FindStringSubmatch(col.Value)
+    match := columnValueRegex.FindStringSubmatch(col.Value)
🧹 Nitpick comments (5)
pkg/list/list_components.go (5)

Line range hint 26-67: By Ares' shield, let's make this function more readable!

The function has good error handling, but we can make it more maintainable with early returns.

Consider this battle-tested structure:

 func getStackComponents(stackData any, abstractFlag bool, listFields []string) ([]string, error) {
     stackMap, ok := stackData.(map[string]any)
     if !ok {
         return nil, fmt.Errorf("could not parse stacks")
     }
 
     componentsMap, ok := stackMap["components"].(map[string]any)
     if !ok {
         return nil, fmt.Errorf("could not parse components")
     }
 
     terraformComponents, ok := componentsMap["terraform"].(map[string]any)
     if !ok {
         return nil, fmt.Errorf("could not parse Terraform components")
     }
 
-    var uniqueKeys []string
-    if abstractFlag {
-        uniqueKeys = lo.Keys(terraformComponents)
-    } else {
-        uniqueKeys = exec.FilterAbstractComponents(terraformComponents)
-    }
+    uniqueKeys := lo.Keys(terraformComponents)
+    if !abstractFlag {
+        uniqueKeys = exec.FilterAbstractComponents(terraformComponents)
+    }

70-98: A well-forged function deserves proper documentation!

The resolveKey function is well-implemented, but adding examples would help future warriors understand its usage.

Consider adding this documentation:

+// resolveKey resolves a key from a map using dot notation.
+// Example:
+//   data := map[string]any{"vars": map[string]any{"tenant": "sparta"}}
+//   value, found := resolveKey(data, "vars.tenant") // Returns "sparta", true
 func resolveKey(data map[string]any, key string) (any, bool) {

124-159: A mighty implementation worthy of Sparta!

The error handling is robust, especially the collection of errors from multiple stacks.

Consider using a custom error type for better error handling:

type StackProcessingError struct {
    Stack string
    Err   error
}

func (e *StackProcessingError) Error() string {
    return fmt.Sprintf("error processing stack '%s': %v", e.Stack, e.Err)
}

161-183: A well-organized battle formation!

The component processing is efficient, but for large datasets, we could parallelize the operations.

Consider using goroutines for parallel processing:

func processComponentsParallel(header []string, components [][]string) ([][]string, []int) {
    // Deduplicate and sort (current implementation)
    uniqueComponents := lo.UniqBy(components, func(item []string) string {
        return strings.Join(item, "\t")
    })
    
    // Calculate column widths in parallel
    colWidths := make([]int, len(header))
    var wg sync.WaitGroup
    for i := range header {
        wg.Add(1)
        go func(colIndex int) {
            defer wg.Done()
            for _, row := range uniqueComponents {
                if len(row[colIndex]) > colWidths[colIndex] {
                    colWidths[colIndex] = len(row[colIndex])
                }
            }
        }(i)
    }
    wg.Wait()
    
    return uniqueComponents, colWidths
}

185-288: A visually pleasing battle report!

The table generation is well-implemented with good TTY detection and styling.

Consider making the colors configurable:

+type TableConfig struct {
+    HeaderColor  lipgloss.Color
+    OddRowColor  lipgloss.Color
+    EvenRowColor lipgloss.Color
+    BorderColor  lipgloss.Color
+}
+
+var DefaultTableConfig = TableConfig{
+    HeaderColor:  purple,
+    OddRowColor:  gray,
+    EvenRowColor: lightGray,
+    BorderColor:  gray,
+}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c85549 and f368057.

📒 Files selected for processing (1)
  • pkg/list/list_components.go (2 hunks)
🔇 Additional comments (2)
pkg/list/list_components.go (2)

5-23: LGTM! Well-structured imports and type definition.

The imports are logically grouped and the tableData struct is clean and focused.


290-318: Enhance our victory message!

The function successfully orchestrates the process but could provide more information about the operation.

Consider returning a more informative result:

-func FilterAndListComponents(stackFlag string, abstractFlag bool, stacksMap map[string]any, listConfig schema.ListConfig) (string, error) {
+func FilterAndListComponents(stackFlag string, abstractFlag bool, stacksMap map[string]any, listConfig schema.ListConfig) (result struct {
+    ComponentCount int
+    Message        string
+}, error) {
     // ... rest of the function
-    return "", nil
+    return struct {
+        ComponentCount int
+        Message        string
+    }{
+        ComponentCount: len(processedComponents),
+        Message:        fmt.Sprintf("Successfully listed %d components", len(processedComponents)),
+    }, nil

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
cmd/list_components.go (1)

28-28: Consider adding error handling for the flag.

While the flag addition is good, the error from GetBool is being ignored. Consider handling potential flag parsing errors.

-abstractFlag, _ := cmd.Flags().GetBool("abstract")
+abstractFlag, err := cmd.Flags().GetBool("abstract")
+if err != nil {
+    u.PrintMessageInColor(fmt.Sprintf("Error getting abstract flag: %v", err), color.New(color.FgRed))
+    return
+}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f368057 and d939c70.

📒 Files selected for processing (3)
  • cmd/list_components.go (4 hunks)
  • pkg/list/list_components_test.go (1 hunks)
  • pkg/schema/schema.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/list/list_components_test.go
  • pkg/schema/schema.go
🧰 Additional context used
🪛 GitHub Check: Build (macos-latest, macos)
cmd/list_components.go

[failure] 43-43:
undefined: cliConfig

🪛 GitHub Check: Build (ubuntu-latest, linux)
cmd/list_components.go

[failure] 43-43:
undefined: cliConfig

🔇 Additional comments (3)
cmd/list_components.go (3)

6-8: LGTM! Clean import organization.

The import statements are well-organized and properly separated.


55-55: LGTM! Well-documented flag registration.

The abstract flag is properly registered with a clear description that matches the PR objectives.


Line range hint 43-48: Verify TTY detection and formatting requirements.

Based on the PR discussion:

  1. The output should use lipgloss formatting when TTY is detected
  2. It should degrade to a simple tabular format when no TTY is attached
  3. Disabled components should be displayed in dark gray

Consider adding TTY detection and appropriate formatting logic.

✅ Verification successful

Let me check the specific implementation details to verify the TTY and formatting requirements.


Let me check one more detail about the TTY detection in the list components implementation.


Great work! The code already implements all requested features

The implementation shows excellent attention to detail:

  • TTY detection is properly implemented using term.IsTerminal
  • Lipgloss formatting is used for TTY output with proper styling
  • Graceful degradation to simple tabular format for non-TTY environments
  • Color scheme includes gray tones for different row types, meeting the disabled components requirement

The code demonstrates a robust and user-friendly approach to component listing, exactly as requested in the PR discussion.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if TTY-specific handling exists in the codebase
rg -l "isatty|IsTTY" .

# Check for lipgloss usage
rg -l "lipgloss" .

Length of output: 584


Script:

#!/bin/bash
# Check TTY detection implementation in vendor_component_utils.go
rg -A 5 "isatty|IsTTY" ./internal/exec/vendor_component_utils.go

# Check lipgloss formatting in list_components.go
rg -A 10 "lipgloss" ./pkg/list/list_components.go

# Check the FilterAndListComponents function implementation
ast-grep --pattern 'func (l *ListComponents) FilterAndListComponents($$$) ($$$) {
  $$$
}'

Length of output: 2045


Script:

#!/bin/bash
# Check for TTY detection in list_components.go
rg -B 5 -A 5 "term\." ./pkg/list/list_components.go

# Check the full implementation context
ast-grep --pattern 'func (l *ListComponents) FilterAndListComponents($_, $_, $_, $_) ($_, $_) {
  $$$
}'

Length of output: 354

🧰 Tools
🪛 GitHub Check: Build (macos-latest, macos)

[failure] 43-43:
undefined: cliConfig

🪛 GitHub Check: Build (ubuntu-latest, linux)

[failure] 43-43:
undefined: cliConfig

@@ -38,7 +40,7 @@
return
}

output, err := l.FilterAndListComponents(stackFlag, stacksMap)
output, err := l.FilterAndListComponents(stackFlag, abstractFlag, stacksMap, cliConfig.Components.List)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix undefined cliConfig variable.

The code is trying to use cliConfig which is undefined. You should use the atmosConfig variable that was initialized earlier.

-output, err := l.FilterAndListComponents(stackFlag, abstractFlag, stacksMap, cliConfig.Components.List)
+output, err := l.FilterAndListComponents(stackFlag, abstractFlag, stacksMap, atmosConfig.Components.List)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
output, err := l.FilterAndListComponents(stackFlag, abstractFlag, stacksMap, cliConfig.Components.List)
output, err := l.FilterAndListComponents(stackFlag, abstractFlag, stacksMap, atmosConfig.Components.List)
🧰 Tools
🪛 GitHub Check: Build (macos-latest, macos)

[failure] 43-43:
undefined: cliConfig

🪛 GitHub Check: Build (ubuntu-latest, linux)

[failure] 43-43:
undefined: cliConfig

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
minor New features that do not break anything
Projects
None yet
Development

Successfully merging this pull request may close these issues.

atmos list should be able to filter for real and abstract components
2 participants