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

Closed
wants to merge 23 commits into from
Closed

improve atmos list components view #828

wants to merge 23 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 <[email protected]>
Signed-off-by: Pulak Kanti Bhowmick <[email protected]>
Signed-off-by: Pulak Kanti Bhowmick <[email protected]>
@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

📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between fa0c9f2 and b4bc19f.

📒 Files selected for processing (1)
  • pkg/schema/schema.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/schema/schema.go
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Summary

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 <[email protected]>
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.

@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 <[email protected]>
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 <[email protected]>
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
@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

osterman commented Dec 14, 2024

  • If a component is one of the following, it should be dark gray
    image
    (dark is "English" compared to light grey ("Spanish"), which is enabled)

    - `metadata.enabled=false`
    - `metadata.type=abstract`
    
  • If a component is one of the following, it should be red.
    - metadata.locked=true

@osterman
Copy link
Member

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

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 <[email protected]>
Signed-off-by: Pulak Kanti Bhowmick <[email protected]>
@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

cmd/list_components.go Outdated Show resolved Hide resolved
@@ -50,5 +52,6 @@ var listComponentsCmd = &cobra.Command{

func init() {
listComponentsCmd.PersistentFlags().StringP("stack", "s", "", "Filter components by stack (e.g., atmos list components -s stack1)")
listComponentsCmd.PersistentFlags().Bool("abstract", false, "Filter abstract component if true")
Copy link
Contributor

Choose a reason for hiding this comment

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

The inverted logic here is IMO a bit confusing. Can't we use something like --type which accepts the three values all (default), real, and abstract (as suggested in #833)?

Copy link
Member

@osterman osterman Jan 7, 2025

Choose a reason for hiding this comment

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

For certain, we want to hide abstract by default. We can use a type parameter as you suggest. That makes the default —-type real.

Please also add a —-kind parameter to filter for the kind of component (terraform, helmfile)

and —-kind any shows any kind of component (default).

Copy link
Contributor

Choose a reason for hiding this comment

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

--type real as the default would totally work for us, I think it is actually the reason why @astephanh implemented our custom command in the first place.

lightGray = lipgloss.Color("241") // Light gray for even rows
)

// Fallback for non-TTY environments
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we get a machine readable output, too? The current style without any column headings and the like would be fine but maybe just a --json?

Copy link
Member

@osterman osterman Jan 7, 2025

Choose a reason for hiding this comment

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

Yes, that’s a good suggestion. I think we support a —-format parameter elsewhere. Let’s support json, yaml, csv, tsv, plain, and pretty. Default is pretty.

@osterman
Copy link
Member

osterman commented Jan 7, 2025

@pkbhowmick this is not dark grey. This is light grey.

image

Something like this is dark grey:

image

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Comment on lines +185 to +189
const (
purple = lipgloss.Color("99") // Purple color for headers and borders
gray = lipgloss.Color("245") // Gray for odd rows
lightGray = lipgloss.Color("241") // Light gray for even rows
)
Copy link
Member

@osterman osterman Jan 7, 2025

Choose a reason for hiding this comment

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

We are refactoring how colors are handled in:

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

📜 Review details

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

📥 Commits

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

📒 Files selected for processing (1)
  • cmd/list_components.go (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build (windows-latest, windows)
  • GitHub Check: Summary
🔇 Additional comments (2)
cmd/list_components.go (2)

6-8: LGTM! Clean import organization.

The imports are now properly organized and grouped.


43-43: LGTM! Proper error handling with the updated method signature.

The method call has been updated to include all required parameters with appropriate error handling.

@@ -50,5 +52,6 @@ var listComponentsCmd = &cobra.Command{

func init() {
listComponentsCmd.PersistentFlags().StringP("stack", "s", "", "Filter components by stack (e.g., atmos list components -s stack1)")
listComponentsCmd.PersistentFlags().Bool("abstract", false, "Filter abstract component if true")
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Revise flag definition to align with user requirements.

Based on the discussion in past reviews:

  1. Abstract components should be hidden by default
  2. A --type flag would be more intuitive
  3. A --kind flag is needed for filtering component types
-listComponentsCmd.PersistentFlags().Bool("abstract", false, "Filter abstract component if true")
+listComponentsCmd.PersistentFlags().String("type", "real", "Filter components by type: real|abstract|all")
+listComponentsCmd.PersistentFlags().String("kind", "any", "Filter components by kind: terraform|helmfile|any")
📝 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
listComponentsCmd.PersistentFlags().Bool("abstract", false, "Filter abstract component if true")
listComponentsCmd.PersistentFlags().String("type", "real", "Filter components by type: real|abstract|all")
listComponentsCmd.PersistentFlags().String("kind", "any", "Filter components by kind: terraform|helmfile|any")

@@ -24,6 +25,7 @@
checkAtmosConfig()

stackFlag, _ := cmd.Flags().GetString("stack")
abstractFlag, _ := cmd.Flags().GetBool("abstract")
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using --type flag instead of --abstract.

Based on the discussion in issue #833, using a --type flag with values all|real|abstract would provide a more intuitive interface than a boolean flag.

-abstractFlag, _ := cmd.Flags().GetBool("abstract")
+componentType, _ := cmd.Flags().GetString("type")

Committable suggestion skipped: line range outside the PR's diff.

@@ -18,6 +18,16 @@
base_path: "."

components:
list:
Copy link
Member

Choose a reason for hiding this comment

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

Support a configurable default

Suggested change
list:
list:
format: pretty

Copy link
Member

@osterman osterman Jan 7, 2025

Choose a reason for hiding this comment

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

The default should be pretty and can be overridden by passing the --format flag

Copy link

mergify bot commented Jan 9, 2025

💥 This pull request now has conflicts. Could you fix it @pkbhowmick? 🙏

@mergify mergify bot added the conflict This PR has conflicts label Jan 9, 2025
@mergify mergify bot closed this Jan 16, 2025
Copy link

mergify bot commented Jan 16, 2025

This PR was closed due to inactivity and merge conflicts. 😭
Please resolve the conflicts and reopen if necessary.

@mergify mergify bot removed the conflict This PR has conflicts label Jan 16, 2025
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
Status: Done
Development

Successfully merging this pull request may close these issues.

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