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

Mark unlicensed users as disabled. #38

Merged
merged 2 commits into from
Oct 17, 2024
Merged

Conversation

ggreer
Copy link
Contributor

@ggreer ggreer commented Oct 16, 2024

Previously we didn't sync unlicensed users. Now we mark them as disabled.

Summary by CodeRabbit

  • New Features

    • Introduced a new method to retrieve users associated with a specific group.
  • Enhancements

    • Improved user handling with updated filtering mechanisms for better user retrieval.
  • Functionality Updates

    • Updated method signatures for user resource creation and processing across multiple components.
  • Pagination Improvements

    • Modified pagination logic for more efficient query parameter construction.

Copy link

coderabbitai bot commented Oct 16, 2024

Walkthrough

The pull request introduces several modifications across multiple files. A new method, GetGroupUsers, is added to the DataCenterClient struct for retrieving users from a specified group with pagination support. The function signature for userResource is updated in several connector files to include an additional parameter. Additionally, the pagination logic in pageTokenToQueryParams is modified, and the userBuilder struct is enhanced to improve user handling, including a new filtering mechanism.

Changes

Files Change Summary
pkg/client/client.go Added new method GetGroupUsers(ctx context.Context, group string) ([]User, error) for retrieving group users with pagination.
pkg/client/pagination.go Modified pageTokenToQueryParams by removing a conditional block related to ResourceTypeID for constructing query parameters.
pkg/connector/group.go, pkg/connector/org.go, pkg/connector/project.go, pkg/connector/repo.go, pkg/connector/user.go Updated userResource function signatures to include an additional nil parameter.
pkg/connector/user.go Modified userBuilder struct: replaced userGroups with userGroupFilter and added groupUsers. Updated List method logic for user retrieval based on new filtering.

Possibly related PRs

Poem

🐇 In the code, new paths we weave,
With users grouped, we now believe.
Pagination flows, a smoother ride,
In the rabbit hole, we take great pride!
With GetGroupUsers, we hop along,
Enhancing queries, where we belong! 🌟


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 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 to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

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

@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: 8

🧹 Outside diff range and nitpick comments (1)
pkg/connector/user.go (1)

109-109: Externalize status message for maintainability

The status message "No Bitbucket license" is hardcoded, which might make future updates or internationalization efforts more challenging. Consider defining this message as a constant or retrieving it from a configuration.

Apply this diff to define a constant:

 const (
     // ... existing constants
+    noLicenseMessage = "No Bitbucket license"
 )

 // ...

     opts = append(opts, rs.WithDetailedStatus(v2.UserTrait_Status_STATUS_DISABLED, noLicenseMessage))
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 3e24f83 and 8c5993f.

📒 Files selected for processing (7)
  • pkg/client/client.go (2 hunks)
  • pkg/client/pagination.go (0 hunks)
  • pkg/connector/group.go (1 hunks)
  • pkg/connector/org.go (1 hunks)
  • pkg/connector/project.go (1 hunks)
  • pkg/connector/repo.go (1 hunks)
  • pkg/connector/user.go (8 hunks)
💤 Files with no reviewable changes (1)
  • pkg/client/pagination.go
🧰 Additional context used
🔇 Additional comments (5)
pkg/connector/org.go (1)

138-138: LGTM. Please verify the new parameter's purpose and add a comment.

The addition of the nil parameter to the userResource function call aligns with the PR objective of marking unlicensed users as disabled. However, to ensure clarity and maintainability:

  1. Please verify that the nil value is correctly handled in the userResource function to represent a non-disabled user.
  2. Consider adding a comment explaining the purpose of this new parameter, e.g.:
    // nil represents that the user is not disabled

To ensure consistency across the codebase, please run the following command to check for any other occurrences of userResource calls that might need updating:

✅ Verification successful

Verified

The addition of the nil parameter in the userResource function call is consistent with the function's signature and Go best practices. Passing nil for the opts slice safely represents the absence of additional options.

No further action is required.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for userResource function calls
rg 'userResource\s*\(' --type go

Length of output: 571


Script:

#!/bin/bash
# Extract the userResource function signatures
ast-grep --lang go --pattern 'func userResource($_, $_, $_, $_) $_' .

Length of output: 3001

pkg/connector/repo.go (1)

163-163: Clarify the purpose of the new parameter in userResource

The addition of a nil argument to the userResource function call appears to be related to the PR objective of marking unlicensed users as disabled. However, the purpose and impact of this new parameter are not immediately clear from this change alone.

Could you please provide more context on:

  1. What does this new parameter represent in the userResource function?
  2. How does passing nil here contribute to marking unlicensed users as disabled?

Additionally, consider if there's a more explicit way to handle this functionality. For example, if this parameter is meant to represent a license status, it might be clearer to use a boolean flag or a dedicated type instead of nil.

To better understand the impact of this change, let's examine the userResource function definition:

This will help us verify the new parameter's purpose and ensure it's being used correctly.

✅ Verification successful

**The addition of the nil argument in the userResource function call correctly matches the updated function signature and aligns with the PR objectives. **

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the userResource function definition
ast-grep --lang go --pattern 'func userResource($_, $_, $_, $_) {
  $$$
}'

Length of output: 76


Script:

#!/bin/bash
# Search for all userResource function definitions regardless of the number of parameters
ast-grep --lang go --pattern 'func userResource($${}) {
  $$$
}'

# Alternatively, use ripgrep to find all occurrences of userResource function definitions
rg 'func userResource' --context 2

Length of output: 413

pkg/client/client.go (3)

231-231: LGTM: Method signature and placement are appropriate.

The GetGroupUsers method signature follows the conventions used in other methods of the DataCenterClient struct. It correctly includes a context parameter and returns appropriate types ([]User, error).


234-260: Pagination logic is well-implemented.

The method effectively handles pagination by:

  1. Using a for loop to fetch all pages.
  2. Updating the start variable with NextPageStart from the API response.
  3. Breaking the loop when IsLastPage is true.
  4. Appending users from each page to the users slice.

The use of the ITEMSPERPAGE constant for the page size is good for maintainability.


231-263: Overall, the new GetGroupUsers method is a valuable addition.

The implementation is solid and follows the patterns established in other methods of the DataCenterClient struct. While there are opportunities for optimization and improved error handling, the current implementation should work well for most use cases.

Key strengths:

  1. Correct pagination handling
  2. Proper use of context in the method signature
  3. Consistent error checking

Areas for potential improvement:

  1. More robust error handling
  2. Optimizations for very large datasets
  3. Respecting context cancellation within the loop

These suggestions can be addressed in future iterations to make the method even more robust and efficient.

@@ -103,7 +103,7 @@ func (g *groupBuilder) Grants(ctx context.Context, resource *v2.Resource, pToken
ID: usrCppy.ID,
Slug: usrCppy.Slug,
Type: usrCppy.Type,
}, resource.Id)
}, resource.Id, nil)
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Inconsistent usage of userResource function parameters

The userResource function is being called with varying numbers of parameters across different files, which may lead to inconsistencies and potential bugs.

Current parameter counts:

  • 5 parameters:
    • pkg/connector/user.go
  • 4 parameters:
    • pkg/connector/group.go
    • pkg/connector/repo.go
  • 3 parameters:
    • pkg/connector/project.go
    • pkg/connector/org.go

Please ensure that all userResource function calls conform to the updated function signature to maintain consistency and prevent potential issues.

🔗 Analysis chain

Approve change, but clarification needed on new parameter

The addition of the nil parameter to the userResource function call appears to be part of a broader update to the function signature. This change is likely related to the PR objective of marking unlicensed users as disabled.

However, I have a few suggestions:

  1. Could you please clarify the purpose of this new parameter? Understanding its role would help in reviewing the change more effectively.

  2. Consider adding a comment to explain why nil is used here. This would improve code readability and maintainability.

  3. It's important to ensure that this change is consistent across all calls to userResource in the codebase.

To verify the consistency of this change, please run the following script:

This script will help identify any inconsistencies in the userResource function calls across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistency in userResource function calls

# Search for all userResource function calls
echo "Searching for userResource function calls:"
rg --type go "userResource\(" -A 3

# Count the number of parameters in each userResource call
echo -e "\nCounting parameters in userResource calls:"
rg --type go "userResource\(" | sed -E 's/.*userResource\((.*)\).*/\1/' | awk -F',' '{print NF}'

Length of output: 1915

@@ -158,7 +158,7 @@ func (p *projectBuilder) Grants(ctx context.Context, resource *v2.Resource, pTok
ID: usrCppy.ID,
Slug: usrCppy.Slug,
Type: usrCppy.Type,
}, resource.Id)
}, resource.Id, nil)
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Inconsistent userResource Function Calls Detected

Several userResource function calls across the codebase are missing the newly added nil parameter. This inconsistency may lead to unexpected behavior or runtime errors.

Affected Files:

  • pkg/connector/project.go
  • pkg/connector/org.go
  • pkg/connector/group.go
🔗 Analysis chain

Approve change, but verify consistency across codebase.

The addition of the nil parameter to the userResource function call aligns with the PR objective of handling unlicensed users. This change appears to be correct in this context.

However, to ensure consistency and prevent potential issues, it's important to verify that all other calls to userResource across the codebase have been updated similarly.

To verify the consistency of this change, we can run the following script:

Would you like me to run this verification script?

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for inconsistent calls to userResource function

# Search for userResource function calls
echo "Searching for userResource function calls:"
rg --type go 'userResource\s*\(' -A 3

# Note: Manually review the output to ensure all calls have been updated consistently

Length of output: 1708

Comment on lines 231 to 263
func (d *DataCenterClient) GetGroupUsers(ctx context.Context, group string) ([]User, error) {
var users []User
var err error
start := 0

for {
queryParams := map[string]string{
"start": strconv.Itoa(start),
"limit": strconv.Itoa(ITEMSPERPAGE),
"group": group,
}

uri, err := d.MakeURL(ctx, allUsersEndpoint, queryParams)
if err != nil {
return nil, err
}

var userData UsersAPIData
resp, err := d.Do(ctx, http.MethodGet, uri, nil, &userData)
if err != nil {
return nil, err
}
defer resp.Body.Close()
users = append(users, userData.Users...)

start = userData.NextPageStart
if userData.IsLastPage {
break
}
}

return users, err
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider optimizations for large datasets and context cancellation.

While the current implementation works well for most cases, consider the following improvements:

  1. For very large groups, fetching all users at once might be memory-intensive. Consider implementing a way to stream results or fetch users in batches.

  2. Add a check for context cancellation within the loop to allow for early termination of the operation.

  3. Implement a rate limiting or backoff mechanism to avoid overwhelming the API with requests.

Here's a sketch of how you might implement these improvements:

func (d *DataCenterClient) GetGroupUsers(ctx context.Context, group string) ([]User, error) {
    var users []User
    start := 0

    for {
        select {
        case <-ctx.Done():
            return users, ctx.Err()
        default:
            // Existing code here...

            // Add a small delay between requests
            time.Sleep(100 * time.Millisecond)
        }
    }
}

Consider also adding a parameter to allow callers to specify a maximum number of users to fetch, which can help manage memory usage for very large groups.

Comment on lines 232 to 263
var users []User
var err error
start := 0

for {
queryParams := map[string]string{
"start": strconv.Itoa(start),
"limit": strconv.Itoa(ITEMSPERPAGE),
"group": group,
}

uri, err := d.MakeURL(ctx, allUsersEndpoint, queryParams)
if err != nil {
return nil, err
}

var userData UsersAPIData
resp, err := d.Do(ctx, http.MethodGet, uri, nil, &userData)
if err != nil {
return nil, err
}
defer resp.Body.Close()
users = append(users, userData.Users...)

start = userData.NextPageStart
if userData.IsLastPage {
break
}
}

return users, err
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider improving error handling.

While the current error handling is generally good, there's room for improvement:

  1. The method returns the last error encountered in the loop, which might not be the most relevant if an earlier iteration failed.
  2. Consider using a separate error variable for the loop and returning it immediately if an error occurs.

Here's a suggested improvement:

 func (d *DataCenterClient) GetGroupUsers(ctx context.Context, group string) ([]User, error) {
 	var users []User
-	var err error
 	start := 0

 	for {
 		queryParams := map[string]string{
 			"start": strconv.Itoa(start),
 			"limit": strconv.Itoa(ITEMSPERPAGE),
 			"group": group,
 		}

 		uri, err := d.MakeURL(ctx, allUsersEndpoint, queryParams)
 		if err != nil {
 			return nil, err
 		}

 		var userData UsersAPIData
 		resp, err := d.Do(ctx, http.MethodGet, uri, nil, &userData)
 		if err != nil {
 			return nil, err
 		}
 		defer resp.Body.Close()
 		users = append(users, userData.Users...)

 		start = userData.NextPageStart
 		if userData.IsLastPage {
 			break
 		}
 	}

-	return users, err
+	return users, nil
 }

This change ensures that any error encountered during the loop is immediately returned, providing more accurate error reporting.

📝 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
var users []User
var err error
start := 0
for {
queryParams := map[string]string{
"start": strconv.Itoa(start),
"limit": strconv.Itoa(ITEMSPERPAGE),
"group": group,
}
uri, err := d.MakeURL(ctx, allUsersEndpoint, queryParams)
if err != nil {
return nil, err
}
var userData UsersAPIData
resp, err := d.Do(ctx, http.MethodGet, uri, nil, &userData)
if err != nil {
return nil, err
}
defer resp.Body.Close()
users = append(users, userData.Users...)
start = userData.NextPageStart
if userData.IsLastPage {
break
}
}
return users, err
}
func (d *DataCenterClient) GetGroupUsers(ctx context.Context, group string) ([]User, error) {
var users []User
start := 0
for {
queryParams := map[string]string{
"start": strconv.Itoa(start),
"limit": strconv.Itoa(ITEMSPERPAGE),
"group": group,
}
uri, err := d.MakeURL(ctx, allUsersEndpoint, queryParams)
if err != nil {
return nil, err
}
var userData UsersAPIData
resp, err := d.Do(ctx, http.MethodGet, uri, nil, &userData)
if err != nil {
return nil, err
}
defer resp.Body.Close()
users = append(users, userData.Users...)
start = userData.NextPageStart
if userData.IsLastPage {
break
}
}
return users, nil
}

Comment on lines +89 to +95
for _, group := range u.userGroupFilter {
users, err := u.client.GetGroupUsers(ctx, group)
if err != nil {
return nil, "", nil, err
}
u.groupUsers = append(u.groupUsers, users...)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle potential duplicates when aggregating group users

When appending users from each group to u.groupUsers, there is a possibility of duplicate entries if a user belongs to multiple groups in userGroupFilter. This could lead to unnecessary processing and memory usage.

If you continue using a slice for groupUsers, consider deduplicating the users after aggregation.

@@ -10,8 +11,7 @@
rs "github.com/conductorone/baton-sdk/pkg/types/resource"
)

func userResource(_ context.Context, user *client.User, parentResourceID *v2.ResourceId) (*v2.Resource, error) {
var userStatus v2.UserTrait_Status_Status = v2.UserTrait_Status_STATUS_ENABLED
func userResource(_ context.Context, user *client.User, parentResourceID *v2.ResourceId, opts []rs.UserTraitOption) (*v2.Resource, error) {
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Update All userResource Calls to Include the New opts Parameter

Several instances of userResource are missing the required opts parameter or are passing nil. Please update these calls to include the new parameter to ensure consistency and prevent potential compile-time errors.

  • pkg/connector/repo.go
  • pkg/connector/project.go
  • pkg/connector/group.go
  • pkg/connector/org.go
🔗 Analysis chain

Ensure all calls to userResource are updated to match the new signature

The userResource function signature has been modified to include an additional parameter opts []rs.UserTraitOption. Please verify that all invocations of userResource within the codebase have been updated to pass this new argument to prevent any potential compile-time errors.

You can run the following script to identify all usages of userResource and check if they have been updated:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all calls to userResource and display the lines for verification.

rg --type go 'userResource\(' -A 2

Length of output: 1332

Comment on lines +88 to +95
if pToken == nil || pToken.Token == "" {
for _, group := range u.userGroupFilter {
users, err := u.client.GetGroupUsers(ctx, group)
if err != nil {
return nil, "", nil, err
}
u.groupUsers = append(u.groupUsers, users...)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Populate groupUsers consistently across pagination

Currently, u.groupUsers is populated only when pToken is nil or empty. In scenarios where pagination occurs (i.e., pToken is not nil), u.groupUsers may not be populated, which could lead to incorrect behavior when checking if a user belongs to a group.

Consider refactoring to ensure that u.groupUsers is populated regardless of the pagination state to maintain consistent behavior across all pages.

Apply this diff to adjust the logic:

 if pToken == nil || pToken.Token == "" {
     for _, group := range u.userGroupFilter {
         users, err := u.client.GetGroupUsers(ctx, group)
         if err != nil {
             return nil, "", nil, err
         }
         u.groupUsers = append(u.groupUsers, users...)
     }
+} else {
+    // Continue appending group users for subsequent pages
+    for _, group := range u.userGroupFilter {
+        users, err := u.client.GetGroupUsers(ctx, group)
+        if err != nil {
+            return nil, "", nil, err
+        }
+        u.groupUsers = append(u.groupUsers, users...)
+    }
 }
📝 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
if pToken == nil || pToken.Token == "" {
for _, group := range u.userGroupFilter {
users, err := u.client.GetGroupUsers(ctx, group)
if err != nil {
return nil, "", nil, err
}
u.groupUsers = append(u.groupUsers, users...)
}
if pToken == nil || pToken.Token == "" {
for _, group := range u.userGroupFilter {
users, err := u.client.GetGroupUsers(ctx, group)
if err != nil {
return nil, "", nil, err
}
u.groupUsers = append(u.groupUsers, users...)
}
} else {
// Continue appending group users for subsequent pages
for _, group := range u.userGroupFilter {
users, err := u.client.GetGroupUsers(ctx, group)
if err != nil {
return nil, "", nil, err
}
u.groupUsers = append(u.groupUsers, users...)
}
}

Comment on lines 105 to 112
opts := []rs.UserTraitOption{}
if len(u.userGroupFilter) > 0 && !slices.ContainsFunc(u.groupUsers, func(u client.User) bool {
return u.ID == usr.ID
}) {
opts = append(opts, rs.WithDetailedStatus(v2.UserTrait_Status_STATUS_DISABLED, "No Bitbucket license"))
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize user lookup by using a map for faster performance

Using slices.ContainsFunc to check if a user exists in u.groupUsers can lead to performance issues with large datasets due to linear search time. To enhance performance, consider using a map to store groupUsers, enabling constant-time lookups.

Apply this diff to implement the optimization:

 type userBuilder struct {
     resourceType    *v2.ResourceType
     client          *client.DataCenterClient
     userGroupFilter []string
-    groupUsers      []client.User
+    groupUserIDs    map[string]struct{}
 }

 func (u *userBuilder) List(ctx context.Context, parentResourceID *v2.ResourceId, pToken *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
     var rv []*v2.Resource

     if pToken == nil || pToken.Token == "" {
+        u.groupUserIDs = make(map[string]struct{})
         for _, group := range u.userGroupFilter {
             users, err := u.client.GetGroupUsers(ctx, group)
             if err != nil {
                 return nil, "", nil, err
             }
-            u.groupUsers = append(u.groupUsers, users...)
+            for _, user := range users {
+                u.groupUserIDs[user.ID] = struct{}{}
+            }
         }
     }

     users, nextPageToken, err := u.client.GetUsers(ctx, pToken)
     if err != nil {
         return nil, "", nil, err
     }

     for _, usr := range users {
         usrCopy := usr
         opts := []rs.UserTraitOption{}
-        if len(u.userGroupFilter) > 0 && !slices.ContainsFunc(u.groupUsers, func(u client.User) bool {
-            return u.ID == usr.ID
-        }) {
+        if len(u.userGroupFilter) > 0 {
+            if _, exists := u.groupUserIDs[usr.ID]; !exists {
                 opts = append(opts, rs.WithDetailedStatus(v2.UserTrait_Status_STATUS_DISABLED, "No Bitbucket license"))
+            }
         }

         ur, err := userResource(ctx, &usrCopy, parentResourceID, opts)
         if err != nil {
             return nil, "", nil, err
         }
         rv = append(rv, ur)
     }

This change replaces the slice with a map, significantly improving the performance of user existence checks.

Committable suggestion was skipped due to low confidence.

if err != nil {
return nil, err
}
defer resp.Body.Close()
Copy link

Choose a reason for hiding this comment

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

this defer isn't safe inside the for loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Whoops. I close the response body every time now.

I'm guessing I didn't run into any bugs testing because baton-SDK's http wrapper replaces the response body with a noop reader.

Previously we didn't sync unlicensed users. Now we mark them as disabled.
@ggreer ggreer force-pushed the ggreer/disable-unlicensed-users branch from 8c5993f to 84e2d23 Compare October 16, 2024 23:03
Copy link

@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/connector/user.go (2)

73-76: LGTM: Updates to userBuilder struct

The renaming of userGroups to userGroupFilter and the addition of groupUsers field improve the clarity and functionality of the struct.

Consider using a map instead of a slice for groupUsers to improve lookup performance, especially if the number of users can be large. For example:

groupUsers map[string]client.User

This would allow for constant-time lookups instead of linear-time searches.


Line range hint 88-112: Improve consistency and performance in List method

While the changes add the desired functionality, there are a few areas for improvement:

  1. Consistency across pagination:
    The groupUsers is only populated when pToken is nil or empty. This may lead to inconsistent behavior when paginating through results.

  2. Performance of user lookup:
    The current implementation uses a linear search with slices.ContainsFunc, which can be inefficient for large datasets.

To address these issues:

  1. Consider populating groupUsers regardless of the pToken value to ensure consistency across all pages.

  2. As suggested in the TODO comment, use a map instead of a slice for groupUsers to improve lookup performance. This change would also address the concern raised in the userBuilder struct review.

Here's a proposed refactor:

type userBuilder struct {
    // ... other fields ...
    groupUsers map[string]struct{}
}

func (u *userBuilder) List(ctx context.Context, parentResourceID *v2.ResourceId, pToken *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
    var rv []*v2.Resource

    if u.groupUsers == nil {
        u.groupUsers = make(map[string]struct{})
        for _, group := range u.userGroupFilter {
            users, err := u.client.GetGroupUsers(ctx, group)
            if err != nil {
                return nil, "", nil, err
            }
            for _, user := range users {
                u.groupUsers[user.ID] = struct{}{}
            }
        }
    }

    users, nextPageToken, err := u.client.GetUsers(ctx, pToken)
    if err != nil {
        return nil, "", nil, err
    }

    for _, usr := range users {
        usrCopy := usr
        opts := []rs.UserTraitOption{}

        if len(u.userGroupFilter) > 0 {
            if _, exists := u.groupUsers[usr.ID]; !exists {
                opts = append(opts, rs.WithDetailedStatus(v2.UserTrait_Status_STATUS_DISABLED, "No Bitbucket license"))
            }
        }

        ur, err := userResource(ctx, &usrCopy, parentResourceID, opts)
        if err != nil {
            return nil, "", nil, err
        }
        rv = append(rv, ur)
    }

    return rv, nextPageToken, nil, nil
}

This refactoring addresses both the consistency and performance issues.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 8c5993f and 84e2d23.

📒 Files selected for processing (7)
  • pkg/client/client.go (2 hunks)
  • pkg/client/pagination.go (0 hunks)
  • pkg/connector/group.go (1 hunks)
  • pkg/connector/org.go (1 hunks)
  • pkg/connector/project.go (1 hunks)
  • pkg/connector/repo.go (1 hunks)
  • pkg/connector/user.go (8 hunks)
💤 Files with no reviewable changes (1)
  • pkg/client/pagination.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • pkg/client/client.go
  • pkg/connector/group.go
  • pkg/connector/org.go
  • pkg/connector/project.go
  • pkg/connector/repo.go
🧰 Additional context used
🔇 Additional comments (4)
pkg/connector/user.go (4)

5-5: LGTM: Import of "slices" package

The addition of the "slices" package import is appropriate for the new functionality in the List method.


14-14: LGTM: Updated userResource function signature

The addition of the opts []rs.UserTraitOption parameter enhances the flexibility of user trait configuration.

As mentioned in a previous review, please ensure that all calls to userResource across the codebase have been updated to include this new parameter.


50-51: LGTM: Incorporation of opts into userTraits

The addition of opts to userTraits correctly implements the new functionality provided by the updated function signature.


136-138: LGTM: Update to newUserBuilder function

The renaming of the userGroups parameter to userGroupFilter is consistent with the changes made to the userBuilder struct and accurately reflects its new purpose.

Copy link

@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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 84e2d23 and 6a47002.

📒 Files selected for processing (1)
  • pkg/client/client.go (2 hunks)
🧰 Additional context used
🔇 Additional comments (1)
pkg/client/client.go (1)

231-266: LGTM: The new GetGroupUsers method is correctly implemented

The implementation of the GetGroupUsers method appears correct and follows the established patterns for handling paginated API requests. The method appropriately handles pagination, error checking, and data aggregation.

for {
queryParams := map[string]string{
"start": strconv.Itoa(start),
"limit": strconv.Itoa(ITEMSPERPAGE),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Undefined Constant ITEMSPERPAGE

The constant ITEMSPERPAGE used in the query parameters for pagination is undefined in this file. Please ensure that ITEMSPERPAGE is defined or imported appropriately before using it.

@ggreer ggreer merged commit 170d808 into main Oct 17, 2024
3 checks passed
@ggreer ggreer deleted the ggreer/disable-unlicensed-users branch October 17, 2024 01:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants