Skip to content

Conversation

@aldevv
Copy link
Contributor

@aldevv aldevv commented Sep 22, 2025

Summary by CodeRabbit

  • New Features

    • Admins can grant or revoke user licenses and site roles directly from the app, with changes applied immediately to Tableau.
    • Updated role mappings ensure accurate assignment of Creator, Explorer, and related roles.
  • Bug Fixes

    • Improved entitlement ID validation and clearer error handling.
    • More reliable transitions to “Unlicensed” when revoking access, reducing inconsistent states.

@aldevv aldevv requested review from a team and btipling September 22, 2025 17:25
cursor[bot]

This comment was marked as outdated.

@sergiocorral-conductorone sergiocorral-conductorone requested a review from a team September 22, 2025 22:52
@luisina-santos luisina-santos requested a review from a team September 23, 2025 11:58
Copy link

@agustin-conductor agustin-conductor left a comment

Choose a reason for hiding this comment

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

please address luisina's comment https://github.com/ConductorOne/baton-tableau/pull/16/files#r2372060918 using slugs can lead to missing grants if the slug is edited in C1

@agustin-conductor agustin-conductor requested a review from a team September 23, 2025 13:42
@coderabbitai
Copy link

coderabbitai bot commented Sep 23, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds programmatic grant/revoke for Tableau site roles and licenses by implementing Grant/Revoke on license and site connector types, a helper to parse entitlement IDs, and a client method (plus URL helper) to update a user’s siteRole via the Tableau API.

Changes

Cohort / File(s) Summary of Changes
Connector — License entitlements
pkg/connector/license.go
Added licensesMap and adjusted RolesPerLicense ordering; implemented Grant and Revoke methods on licenseResourceType to resolve license from entitlement and set/unset user siteRole via client; return annotations/errors.
Connector — Site entitlements
pkg/connector/site.go
Added Grant and Revoke methods on siteResourceType to parse entitlement IDs, map internal role names to API role names, and call client to set/unset user siteRole; added parseRoleFromEntitlementID(entitlementID string) (string, error) and imported strings.
Tableau Client — User role update & URL helper
pkg/tableau/client.go
Added UpdateUserSiteRole(ctx, userId, siteRole) error to PUT /sites/{siteId}/users/{userId} with JSON { "user": { "siteRole": siteRole } }; added helper buildResourceURL(baseURL, endpoint string, elems ...string) (string, error) to construct resource URLs; handles marshal and request errors.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant Connector as Connector (license/site)
  participant Client as Tableau Client
  participant API as Tableau REST API

  Caller->>Connector: Grant(resource, entitlement)
  Note over Connector: Parse entitlement → resolve role/license
  Connector->>Client: UpdateUserSiteRole(userId, siteRole)
  Client->>API: PUT /sites/{siteId}/users/{userId} { "user": { "siteRole": siteRole } }
  API-->>Client: 200 OK / error
  Client-->>Connector: nil / error
  Connector-->>Caller: annotations / error
Loading
sequenceDiagram
  autonumber
  actor Caller
  participant Connector as Connector (license/site)
  participant Client as Tableau Client
  participant API as Tableau REST API

  Caller->>Connector: Revoke(grant)
  Note over Connector: Set siteRole to "Unlicensed"
  Connector->>Client: UpdateUserSiteRole(userId, "Unlicensed")
  Client->>API: PUT /sites/{siteId}/users/{userId} { "user": { "siteRole": "Unlicensed" } }
  API-->>Client: 200 OK / error
  Client-->>Connector: nil / error
  Connector-->>Caller: annotations / error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I hop through code with nimble paws,
Mapping roles without a pause,
Grant and Revoke — a tidy dance,
URLs built with careful stance,
Licenses set — carrots for the cause. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "BB-1542 Add license entitlement provisioning" is concise and accurately describes the primary change in the diff—adding programmatic license/entitlement grant and revoke handling (and supporting client methods) and includes the tracking ticket ID; it focuses on the main intent without extraneous detail. It is specific and clear enough for a teammate scanning history to understand the primary change.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add_license_entitlement_provisioning

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 663955d and 65ce57b.

📒 Files selected for processing (1)
  • pkg/tableau/client.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/tableau/client.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot

Comment @coderabbitai help to get the list of available commands and usage tips.

cursor[bot]

This comment was marked as outdated.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/tableau/client.go (1)

388-391: Critical: fix JSON Decode target — pass res (not &res)

json.Decoder must write into the concrete pointer held in res; decoding into &res decodes into a local interface and discards the result, breaking all non-DELETE responses.

Locations:

  • pkg/tableau/client.go:102
  • pkg/tableau/client.go:389
-    if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
+    if err := json.NewDecoder(resp.Body).Decode(res); err != nil {
      return err
    }
🧹 Nitpick comments (1)
pkg/connector/site.go (1)

172-178: Make entitlement parsing more robust (use last segment).

IDs with additional colons would fail len(parts) == 3. Use the last segment.

Apply this diff:

 func parseRoleFromEntitlementID(entitlementID string) (string, error) {
-    parts := strings.Split(entitlementID, ":")
-    if len(parts) != 3 {
+    parts := strings.Split(entitlementID, ":")
+    if len(parts) < 3 {
         return "", fmt.Errorf("invalid entitlement ID: %s", entitlementID)
     }
-    return parts[2], nil
+    return parts[len(parts)-1], nil
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 700ebc2 and 663955d.

📒 Files selected for processing (3)
  • pkg/connector/license.go (2 hunks)
  • pkg/connector/site.go (2 hunks)
  • pkg/tableau/client.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
pkg/connector/site.go (1)
pkg/tableau/client.go (1)
  • Client (22-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (8)
pkg/connector/license.go (4)

20-25: Fix for license-case mapping is good.

Mapping lowercased resource IDs to Tableau’s cased role constants addresses the earlier case-sensitivity bug.


27-27: Roles-per-license set looks reasonable.

Creator includes SiteAdministratorCreator; Explorer includes SiteAdministratorExplorer and ReadOnly. If this reflects Tableau semantics, LGTM.

Please confirm Explorer legitimately includes SiteAdministrator (without suffix) per Tableau docs.


114-128: Return rate limit annotations (carry forward prior feedback).

You’re creating outputAnnotations but not populating rate limit info. Please add outputAnnotations.WithRateLimiting(...) once the client exposes rate-limit data from doRequest.

Would you like a small patch to plumb rate-limit headers from client.doRequest and attach them here?


130-140: Same: add rate limit annotations on revoke.

Mirror Grant: attach rate-limit details to outputAnnotations.

pkg/connector/site.go (3)

6-6: strings import is appropriate for entitlement parsing.


127-152: Attach annotations (incl. rate limit) and return them.

Currently returns nil annotations. Prefer returning an annotations object and, when available, include rate-limit data.

Apply this diff:

 func (o *siteResourceType) Grant(ctx context.Context, principal *v2.Resource, entitlement *v2.Entitlement) (annotations.Annotations, error) {
+    outputAnnotations := annotations.New()
     roleName, err := parseRoleFromEntitlementID(entitlement.Id)
     if err != nil {
-        return nil, fmt.Errorf("failed to parse role from entitlement ID: %w", err)
+        return outputAnnotations, fmt.Errorf("failed to parse role from entitlement ID: %w", err)
     }
     principalID := principal.Id.Resource

     var apiRoleName string
     for key, value := range roles {
         if value == roleName {
             apiRoleName = key
             break
         }
     }

     if apiRoleName == "" {
-        return nil, fmt.Errorf("unknown role: %s", roleName)
+        return outputAnnotations, fmt.Errorf("unknown role: %s", roleName)
     }

     err = o.client.UpdateUserSiteRole(ctx, principalID, apiRoleName)
     if err != nil {
-        return nil, fmt.Errorf("failed to grant %s role to user %s: %w", roleName, principalID, err)
+        return outputAnnotations, fmt.Errorf("failed to grant %s role to user %s: %w", roleName, principalID, err)
     }

-    return nil, nil
+    // TODO: outputAnnotations.WithRateLimiting(rateLimitData) once available.
+    return outputAnnotations, nil
 }

154-163: Return annotations on revoke (and include rate limit when available).

Apply this diff:

 func (o *siteResourceType) Revoke(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) {
+    outputAnnotations := annotations.New()
     principalID := grant.Principal.Id.Resource

-    err := o.client.UpdateUserSiteRole(ctx, principalID, unlicensed)
+    err := o.client.UpdateUserSiteRole(ctx, principalID, unlicensed)
     if err != nil {
-        return nil, fmt.Errorf("failed to revoke site role from user %s: %w", principalID, err)
+        return outputAnnotations, fmt.Errorf("failed to revoke site role from user %s: %w", principalID, err)
     }

-    return nil, nil
+    // TODO: outputAnnotations.WithRateLimiting(rateLimitData) once available.
+    return outputAnnotations, nil
 }
pkg/tableau/client.go (1)

430-451: Approved — PUT to /sites/{siteId}/users/{userId} with {"user":{"siteRole":""}} is correct

Tableau REST API expects PUT /api/{api-version}/sites/{site-id}/users/{user-id} with JSON body {"user":{"siteRole":""}}; returns 200 and the updated user.

@aldevv aldevv merged commit 8f22efa into main Sep 24, 2025
4 checks passed
@aldevv aldevv deleted the add_license_entitlement_provisioning branch September 24, 2025 14: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.

7 participants