Skip to content

UN-2773 [FEAT] Add sharing functionality for API deployments#1520

Merged
ritwik-g merged 10 commits into
mainfrom
UN-2773-add-sharing-for-api-deployments
Oct 1, 2025
Merged

UN-2773 [FEAT] Add sharing functionality for API deployments#1520
ritwik-g merged 10 commits into
mainfrom
UN-2773-add-sharing-for-api-deployments

Conversation

@johnyrahul

@johnyrahul johnyrahul commented Sep 3, 2025

Copy link
Copy Markdown
Contributor

Summary

  • Implements sharing functionality for API deployments similar to workflow sharing
  • Allows users to share API deployments with specific organization members
  • Adds email notifications when API deployments are shared

Changes

Backend

  • Added shared_users and shared_to_org fields to APIDeployment model
  • Updated APIDeploymentViewSet to use IsOwnerOrSharedUser permission
  • Added list_of_shared_users endpoint for retrieving sharing details
  • Implemented sharing notifications via email
  • Created database migration for new sharing fields

Frontend

  • Added Share button to API deployment actions menu
  • Integrated SharePermission modal for managing shared users
  • Added owner column to API deployments table
  • Fetches and displays current sharing status

Test Plan

  • Migration applies successfully
  • Django system check passes
  • Frontend builds without errors
  • Share button opens modal with loading state
  • Users can be added/removed from sharing list
  • Email notifications are sent when sharing (if configured)
  • Shared users can view API deployments
  • Only owners can modify sharing settings

Notes

  • Organization-wide sharing (shared_to_org) field is included but not actively used (similar to workflow sharing)
  • Email notifications require SendGrid configuration
  • Follows the same pattern as workflow sharing implementation

Screenshots

image image image

🤖 Generated with Claude Code

- Add shared_users and shared_to_org fields to APIDeployment model
- Update APIDeploymentViewSet with sharing permissions and endpoints
- Add list_of_shared_users endpoint for retrieving sharing details
- Implement sharing notifications via email when users are added
- Update frontend with Share button and SharePermission modal integration
- Include owner column in API deployments table
- Support individual user sharing with organization member filtering

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 3, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Share API deployments with specific users or your entire organization.
    • View and manage shared users for a deployment from a new “Share” action and modal.
    • See who owns each deployment via a new Owner column (“You” or creator’s email/name).
    • Shared users can access deployments they’re granted.
    • Optional notifications are sent to newly added shared users when sharing changes.
    • API now supports listing shared users and updating sharing settings from the UI.

Walkthrough

Adds sharing to APIDeployments: model fields and migration, manager method for_user, serializers and endpoint to list shared users, view permission adjustments and PATCH handling to update sharing with optional notifications, plus frontend UI, actions, and service methods to view/update sharing.

Changes

Cohort / File(s) Summary
Backend: Models & Migration
backend/api_v2/models.py, backend/api_v2/migrations/0002_apideployment_shared_to_org_and_more.py
Add shared_users (ManyToMany) and shared_to_org (Boolean) to APIDeployment; add APIDeploymentModelManager.for_user(user); migration to create these fields.
Backend: Deployment Views & Permissions
backend/api_v2/api_deployment_views.py
APIDeploymentViewSet: use objects.for_user(request.user) for queryset; add get_permissions to return IsOwner for destroy/partial_update/update and IsOwnerOrSharedUser for others; add list_of_shared_users endpoint; extend partial_update to detect shared_users changes and send optional sharing notifications (non-blocking).
Backend: Serializers
backend/api_v2/serializers.py
Add created_by_email to APIDeploymentListSerializer; introduce SharedUserListSerializer exposing shared_users and created_by info.
Backend: URLs
backend/api_v2/urls.py
Add route deployment/<uuid:pk>/users/ mapped to the new list_of_shared_users view.
Backend: API Key Permissions
backend/api_v2/api_key_views.py
Replace IsOwnerOrOrganizationMember with IsOwnerOrSharedUser for APIKeyViewSet permission classes; update imports.
Frontend: Deployment UI
frontend/src/components/deployments/api-deployment/ApiDeployment.jsx
Add Share action, Owner column, share modal wiring, handlers to fetch all users and a deployment’s shared users, apply sharing updates, and manage loading/success/error states.
Frontend: Service Methods
frontend/src/components/deployments/api-deployment/api-deployments-service.js
Add getSharedUsers(id), updateSharing(id, sharedUsers, shareWithEveryone), and getAllUsers(); endpoints: GET /users/, GET /api/deployment/{id}/users/, PATCH /api/deployment/{id}/ (sends shared_users and shared_to_org).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant FE as Frontend (ApiDeployment.jsx)
  participant SVC as api-deployments-service.js
  participant BE as APIDeploymentViewSet
  participant DB as Database

  U->>FE: Click "Share" on a deployment
  FE->>SVC: getAllUsers()
  SVC->>BE: GET /users/
  BE->>DB: Query users
  DB-->>BE: Users
  BE-->>SVC: 200 Users
  SVC-->>FE: Users
  FE->>SVC: getSharedUsers(deploymentId)
  SVC->>BE: GET /api/deployment/{id}/users/
  BE->>DB: Fetch deployment + shared_users
  DB-->>BE: Deployment + shared list
  BE-->>SVC: 200 Shared users
  SVC-->>FE: Shared users
  FE-->>U: Open Share modal with data
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant FE as Frontend
  participant SVC as api-deployments-service.js
  participant BE as APIDeploymentViewSet
  participant DB as Database
  participant NP as NotificationPlugin (optional)

  U->>FE: Apply sharing changes
  FE->>SVC: updateSharing(id, sharedUsers, shareWithEveryone)
  SVC->>BE: PATCH /api/deployment/{id} {shared_users, shared_to_org}
  BE->>DB: Load deployment (permission check via IsOwner/IsOwnerOrSharedUser)
  BE->>DB: Update `shared_users` M2M and `shared_to_org`
  DB-->>BE: Saved
  alt Notification plugin available & new users added
    BE->>NP: send_sharing_notification(resource, participants)
    NP-->>BE: ack or error (logged, non-fatal)
  end
  BE-->>SVC: 200 Updated deployment
  SVC-->>FE: Success
  FE-->>U: Success toast & refresh
Loading
sequenceDiagram
  autonumber
  actor User
  participant FE as Frontend
  participant BE as APIDeploymentViewSet
  participant DB as Database

  User->>FE: View deployments
  FE->>BE: GET /api/deployment/
  BE->>DB: APIDeployment.objects.for_user(request.user)
  DB-->>BE: Owned ∪ Shared deployments
  BE-->>FE: 200 List
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description does not follow the repository template, as it uses custom headings like Summary and Changes while omitting required sections such as What, Why, How, Can this PR break any existing features, Database Migrations, Env Config, Relevant Docs, Related Issues or PRs, Dependencies Versions, Notes on Testing, and the checklist. Please revise the description to match the repository’s template by adding sections for What, Why, How, Can this PR break any existing features, Database Migrations, Env Config, Relevant Docs, Related Issues or PRs, Dependencies Versions, Notes on Testing, and include the checklist from the template.
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the primary feature introduced by this pull request—adding sharing functionality for API deployments—while including the ticket identifier and feature tag for context.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch UN-2773-add-sharing-for-api-deployments

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

johnyrahul and others added 2 commits September 16, 2025 16:16
Handle cases where notification plugin may not be available by wrapping
imports in try-catch block and checking availability before use.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (9)
backend/api_v2/urls.py (1)

31-35: Endpoint wiring is fine; consider @action for cohesion

Defining list_of_shared_users as a DRF @action(detail=True, methods=["get"]) on the ViewSet keeps routing/permissions consistent and testable.

frontend/src/components/deployments/api-deployment/ApiDeployment.jsx (2)

149-162: Make owner check type‑stable

IDs may differ in type/format (e.g., UUID vs quoted string). Normalize before comparison to avoid false negatives.

Apply this diff:

-const currentUser = sessionDetails?.userId;
-const isOwner = record?.created_by === currentUser;
+const currentUser = sessionDetails?.userId;
+const isOwner = String(record?.created_by || "") === String(currentUser || "");

369-384: Gate “Share” action to owners client‑side

To reduce noise and prevent inevitable 403s, disable the menu item when the current user isn’t the owner. Server‑side checks remain the source of truth.

Apply this diff:

 {
   key: "3",
   label: (
     <Space
       direction="horizontal"
       className="action-items"
       onClick={handleShare}
     >
@@
       <div>
         <Typography.Text>Share</Typography.Text>
       </div>
     </Space>
   ),
+  disabled: String(selectedRow?.created_by || "") !== String(sessionDetails?.userId || ""),
 },
backend/api_v2/api_deployment_views.py (3)

170-170: Avoid N+1 for created_by_email in list.

Select the relation once at the queryset level.

Apply this diff:

-        queryset = APIDeployment.objects.for_user(self.request.user)
+        queryset = (
+            APIDeployment.objects.for_user(self.request.user)
+            .select_related("created_by")
+        )

268-274: Silence false-positive unused args warning (Ruff ARG002).

DRF requires the standard signature even if not used.

Apply this diff:

-    def list_of_shared_users(self, request: Request, pk: str | None = None) -> Response:
+    def list_of_shared_users(  # noqa: ARG002
+        self, request: Request, pk: str | None = None
+    ) -> Response:

275-309: Notification path: compare by IDs, reuse singleton, log with stack.

  • Set-diff model instances can be fragile; compare by IDs.
  • Reuse the module-level service instead of re-instantiating.
  • Use logger.exception for tracebacks.

Apply this diff:

     def partial_update(self, request: Request, *args: Any, **kwargs: Any) -> Response:
         """Override partial_update to handle sharing notifications."""
-        # Get current instance and shared users
+        # Get current instance and shared users
         instance = self.get_object()
-        current_shared_users = set(instance.shared_users.all())
+        current_shared_user_ids = set(
+            instance.shared_users.values_list("id", flat=True)
+        )

         # Perform the update
         response = super().partial_update(request, *args, **kwargs)

         # If successful and shared_users changed, send notifications
         if (
             response.status_code == 200
             and "shared_users" in request.data
             and NOTIFICATION_PLUGIN_AVAILABLE
         ):
             try:
                 instance.refresh_from_db()
-                new_shared_users = set(instance.shared_users.all())
-                newly_shared_users = new_shared_users - current_shared_users
+                new_shared_user_ids = set(
+                    instance.shared_users.values_list("id", flat=True)
+                )
+                newly_added_ids = new_shared_user_ids - current_shared_user_ids

-                if newly_shared_users:
-                    notification_service = SharingNotificationService()
-                    notification_service.send_sharing_notification(
+                if newly_added_ids and sharing_notification_service:
+                    newly_shared_users = list(
+                        instance.shared_users.filter(id__in=newly_added_ids)
+                    )
+                    sharing_notification_service.send_sharing_notification(
                         resource_type=ResourceType.API_DEPLOYMENT.value,
                         resource_name=instance.display_name,
                         resource_id=str(instance.id),
                         shared_by=request.user,
-                        shared_to=list(newly_shared_users),
+                        shared_to=newly_shared_users,
                         resource_instance=instance,
                     )
             except Exception as e:
-                logger.error(f"Failed to send sharing notification: {e}")
+                logger.exception("Failed to send sharing notification")
backend/api_v2/serializers.py (3)

382-382: Prefer source= for simple related fields; drop method.

Cuts boilerplate and pairs well with select_related above.

Apply this diff:

-    created_by_email = SerializerMethodField()
+    created_by_email = CharField(source="created_by.email", read_only=True)
@@
-    def get_created_by_email(self, obj):
-        """Get the email of the creator."""
-        return obj.created_by.email if obj.created_by else None
+    # get_created_by_email no longer needed

Also applies to: 399-402


446-449: Avoid loading full user objects; fetch only needed fields.

Use values to reduce overhead (even though it’s a single object endpoint).

Apply this diff:

     def get_shared_users(self, obj):
         """Return list of shared users with id and email."""
-        return [{"id": user.id, "email": user.email} for user in obj.shared_users.all()]
+        return list(obj.shared_users.values("id", "email"))

444-444: Ruff RUF012 on Meta.fields is a common false positive in Django.

Either ignore or silence inline.

Apply this diff:

-        fields = ["id", "display_name", "shared_users", "shared_to_org", "created_by"]
+        fields = ["id", "display_name", "shared_users", "shared_to_org", "created_by"]  # noqa: RUF012
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between ffac92b and f63e7a5.

📒 Files selected for processing (8)
  • backend/api_v2/api_deployment_views.py (4 hunks)
  • backend/api_v2/api_key_views.py (2 hunks)
  • backend/api_v2/migrations/0002_apideployment_shared_to_org_and_more.py (1 hunks)
  • backend/api_v2/models.py (2 hunks)
  • backend/api_v2/serializers.py (4 hunks)
  • backend/api_v2/urls.py (2 hunks)
  • frontend/src/components/deployments/api-deployment/ApiDeployment.jsx (11 hunks)
  • frontend/src/components/deployments/api-deployment/api-deployments-service.js (1 hunks)
🧰 Additional context used
🪛 Ruff (0.12.2)
backend/api_v2/migrations/0002_apideployment_shared_to_org_and_more.py

8-11: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


13-31: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

backend/api_v2/serializers.py

444-444: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

backend/api_v2/api_key_views.py

18-18: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

backend/api_v2/api_deployment_views.py

269-269: Unused method argument: request

(ARG002)


269-269: Unused method argument: pk

(ARG002)


305-305: Do not catch blind exception: Exception

(BLE001)


306-306: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

⏰ 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: build
🔇 Additional comments (11)
backend/api_v2/migrations/0002_apideployment_shared_to_org_and_more.py (2)

1-31: Migration looks correct

Adds fields with expected types and dependency ordering. No issues.


1-31: Ruff RUF012 on migrations can be ignored

RUF012 flags mutable class attrs, but Django migration dependencies/operations as lists are idiomatic and safe here.

frontend/src/components/deployments/api-deployment/ApiDeployment.jsx (3)

269-303: Good UX flow; minor nit

Opening the modal early with a loading spinner is fine. Consider handling the case where members is absent by defaulting to [] (already done). No changes required.


563-572: SharePermission wiring LGTM

Props look consistent with the service payload and the current choice to hide org‑wide sharing.


305-323: Don't drop the 2nd parameter — callers pass three args

Two callers invoke onApply with three arguments:

  • frontend/src/components/widgets/share-permission/SharePermission.jsx:155 — onApply(selectedUsers, adapter, shareWithEveryone)
  • frontend/src/components/custom-tools/export-tool/ExportTool.jsx:168 — onApply(selectedUsers, toolDetails, sharingOption === SHARE_ALL)

Either:

  • keep the second parameter in ApiDeployment.jsx (rename to _api or _unusedApi to indicate it's intentionally unused), or
  • remove the second parameter and update both callers above to stop passing the second arg (so they call onApply(selectedUsers, shareWithEveryone)).
backend/api_v2/api_key_views.py (1)

1-1: Import path is correct — no change required

IsOwnerOrSharedUser is defined in backend/permissions/permission.py and is imported by backend/api_v2/api_key_views.py (line 1).

backend/api_v2/models.py (1)

88-95: Enforce same-organization invariant for shared_users

Add server-side validation so users from other organizations cannot be added to shared_users — enforce in serializer.validate_shared_users or model.clean. Location: backend/api_v2/models.py (shared_users field, lines ~88–95). A search for validate_shared_users|clean in backend/api_v2 returned no matches; add/verify this check and accompanying tests.

Suggested serializer validator (adjust to your org model):

def validate_shared_users(self, users):
    org = self.instance.organization if self.instance else self.context["organization"]
    invalid = [u for u in users if getattr(u, "organization_id", None) != getattr(org, "id", None)]
    if invalid:
        raise serializers.ValidationError("All shared users must belong to the same organization.")
    return users
frontend/src/components/deployments/api-deployment/api-deployments-service.js (1)

99-124: Localize request options and verify endpoint/payload contracts

  • Use a function‑local const options to avoid mutating the module‑scoped options during concurrent requests (frontend/src/components/deployments/api-deployment/api-deployments-service.js).
  • Verified: backend defines the deployment users endpoint and sharing field (backend/api_v2/urls.py: deployment/<uuid:pk>/users/; backend/api_v2/models.py: APIDeployment.shared_users is a ManyToManyField; backend/api_v2/api_deployment_views.py checks for "shared_users" in request.data during partial_update). Confirm the APIDeployment serializer accepts shared_users as an array of user IDs and that ${path}/users/ is the intended org‑scoped users endpoint before merging.

Apply this diff:

 getSharedUsers: (id) => {
-  options = {
+  const options = {
     method: "GET",
     url: `${path}/api/deployment/${id}/users/`,
   };
   return axiosPrivate(options);
 },
 updateSharing: (id, sharedUsers, shareWithEveryone) => {
-  options = {
+  const options = {
     method: "PATCH",
     url: `${path}/api/deployment/${id}/`,
     headers: requestHeaders,
     data: {
       shared_users: sharedUsers,
       shared_to_org: shareWithEveryone,
     },
   };
   return axiosPrivate(options);
 },
 getAllUsers: () => {
-  options = {
+  const options = {
     method: "GET",
     url: `${path}/users/`,
   };
   return axiosPrivate(options);
 },
backend/api_v2/urls.py (1)

61-65: Object‑level permissions are enforced — no change required

list_of_shared_users already calls self.get_object() (backend/api_v2/api_deployment_views.py:269–273); DRF's GenericAPIView.get_object calls self.check_object_permissions(self.request, obj), so object‑level checks will run. (django-rest-framework.org)

backend/api_v2/api_deployment_views.py (2)

8-8: LGTM: permissions import.

Import of IsOwner and IsOwnerOrSharedUser aligns with usage below.


34-43: Good plugin gating + safe fallback.

Optional notification plugin is correctly feature-gated and won’t break OSS builds.

Comment thread backend/api_v2/api_deployment_views.py
Comment thread backend/api_v2/api_key_views.py
Comment thread backend/api_v2/models.py
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Rahul Johny <116638720+johnyrahul@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
backend/api_v2/api_deployment_views.py (1)

165-166: Nice: PUT now owner-only (addresses prior review).

🧹 Nitpick comments (3)
backend/api_v2/api_deployment_views.py (3)

169-177: Optional: prefetch relations to avoid N+1 in list views.

If serializers touch created_by and/or shared_users, prefetch for efficiency.

Apply this diff if applicable:

-        queryset = APIDeployment.objects.for_user(self.request.user)
+        queryset = (
+            APIDeployment.objects.for_user(self.request.user)
+            .select_related("created_by")
+            .prefetch_related("shared_users")
+        )

268-274: Silence Ruff ARG002 on unused args.

Rename parameters to underscores; logic unchanged.

Apply this diff:

-    def list_of_shared_users(self, request: Request, pk: str | None = None) -> Response:
+    def list_of_shared_users(self, _request: Request, _pk: str | None = None) -> Response:

275-309: Harden notification flow: 2xx check, reuse module service, drop brittle data gate, log stacktrace.

  • Check for any 2xx, not only 200.
  • Use the module-level sharing_notification_service instead of re-instantiating.
  • Don’t rely on "shared_users" in request.data; compute diff regardless.
  • Use logger.exception (also satisfies Ruff TRY400) and avoid blind catch message loss.

Apply this diff:

-        if (
-            response.status_code == 200
-            and "shared_users" in request.data
-            and NOTIFICATION_PLUGIN_AVAILABLE
-        ):
+        if (
+            200 <= response.status_code < 300
+            and sharing_notification_service
+        ):
             try:
                 instance.refresh_from_db()
                 new_shared_users = set(instance.shared_users.all())
                 newly_shared_users = new_shared_users - current_shared_users

-                if newly_shared_users:
-                    notification_service = SharingNotificationService()
-                    notification_service.send_sharing_notification(
+                if newly_shared_users:
+                    sharing_notification_service.send_sharing_notification(
                         resource_type=ResourceType.API_DEPLOYMENT.value,
                         resource_name=instance.display_name,
                         resource_id=str(instance.id),
                         shared_by=request.user,
                         shared_to=list(newly_shared_users),
                         resource_instance=instance,
                     )
-            except Exception as e:
-                logger.error(f"Failed to send sharing notification: {e}")
+            except Exception:
+                logger.exception("Failed to send sharing notification")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between f63e7a5 and 734a6b1.

📒 Files selected for processing (1)
  • backend/api_v2/api_deployment_views.py (4 hunks)
🧰 Additional context used
🪛 Ruff (0.12.2)
backend/api_v2/api_deployment_views.py

269-269: Unused method argument: request

(ARG002)


269-269: Unused method argument: pk

(ARG002)


305-305: Do not catch blind exception: Exception

(BLE001)


306-306: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

⏰ 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: build
🔇 Additional comments (2)
backend/api_v2/api_deployment_views.py (2)

8-8: LGTM: permission imports are correct.


31-31: LGTM: serializer import added for sharing list.

Comment thread backend/api_v2/api_deployment_views.py
Comment thread backend/api_v2/api_deployment_views.py
Comment thread backend/api_v2/api_deployment_views.py Outdated
@vishnuszipstack

Copy link
Copy Markdown
Contributor

@johnyrahul pls add screenshots.

Comment thread frontend/src/components/deployments/api-deployment/ApiDeployment.jsx Outdated
johnyrahul and others added 4 commits September 26, 2025 13:53
Co-authored-by: Chandrasekharan M <117059509+chandrasekharan-zipstack@users.noreply.github.com>
Signed-off-by: Rahul Johny <116638720+johnyrahul@users.noreply.github.com>
…Zipstack/unstract into UN-2773-add-sharing-for-api-deployments

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 734a6b1 and f57e6b5.

📒 Files selected for processing (2)
  • backend/api_v2/api_deployment_views.py (4 hunks)
  • frontend/src/components/deployments/api-deployment/ApiDeployment.jsx (11 hunks)
🧰 Additional context used
🪛 Ruff (0.13.1)
backend/api_v2/api_deployment_views.py

269-269: Unused method argument: request

(ARG002)


269-269: Unused method argument: pk

(ARG002)


306-306: Redundant exception object included in logging.exception call

(TRY401)

⏰ 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: build
🔇 Additional comments (2)
backend/api_v2/api_deployment_views.py (2)

164-174: Lock down list_of_shared_users to owners

Because get_permissions overrides per-action permission_classes, shared users can currently call list_of_shared_users and see the entire sharing roster. Include this action in the owner-only set.

-    def get_permissions(self) -> list[Any]:
-        if self.action in ["destroy", "partial_update", "update"]:
+    def get_permissions(self) -> list[Any]:
+        owner_only_actions = {
+            "destroy",
+            "partial_update",
+            "update",
+            "list_of_shared_users",
+        }
+        if self.action in owner_only_actions:
             return [IsOwner()]
         return [IsOwnerOrSharedUser()]

34-43: Don’t let notification plugin init crash imports

SharingNotificationService() is constructed at import time outside the ImportError guard, so any runtime misconfiguration raises and prevents Django from loading the module. Wrap the constructor in its own try/except and fall back to disabling notifications when initialization fails.

-try:
-    from plugins.notification.constants import ResourceType
-    from plugins.notification.sharing_notification import SharingNotificationService
-
-    NOTIFICATION_PLUGIN_AVAILABLE = True
-    sharing_notification_service = SharingNotificationService()
-except ImportError:
+try:
+    from plugins.notification.constants import ResourceType
+    from plugins.notification.sharing_notification import SharingNotificationService
+except ImportError:
     NOTIFICATION_PLUGIN_AVAILABLE = False
     sharing_notification_service = None
+else:
+    try:
+        sharing_notification_service = SharingNotificationService()
+        NOTIFICATION_PLUGIN_AVAILABLE = True
+    except Exception:
+        logging.exception(
+            "Failed to initialize SharingNotificationService; notifications disabled"
+        )
+        NOTIFICATION_PLUGIN_AVAILABLE = False
+        sharing_notification_service = None

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
backend/api_v2/api_deployment_views.py (1)

277-311: PUT path missing notifications; parity with PATCH needed.

Updating shared_users via PUT won’t send notifications. Mirror the partial_update logic in update().

Would you like me to open a follow-up PR? If you prefer inline, add:

def update(self, request: Request, *args: Any, **kwargs: Any) -> Response:
    instance = self.get_object()
    current_shared_users = set(instance.shared_users.all())
    response = super().update(request, *args, **kwargs)
    if (
        response.status_code == 200
        and "shared_users" in request.data
        and NOTIFICATION_PLUGIN_AVAILABLE
    ):
        try:
            instance.refresh_from_db()
            new_shared_users = set(instance.shared_users.all())
            newly_shared_users = new_shared_users - current_shared_users
            if newly_shared_users and sharing_notification_service:
                sharing_notification_service.send_sharing_notification(
                    resource_type=ResourceType.API_DEPLOYMENT.value,
                    resource_name=instance.display_name,
                    resource_id=str(instance.id),
                    shared_by=request.user,
                    shared_to=list(newly_shared_users),
                    resource_instance=instance,
                )
        except Exception:
            logger.exception("Failed to send sharing notification")
    return response
🧹 Nitpick comments (8)
backend/api_v2/api_deployment_views.py (6)

34-43: Avoid double-initializing the notification service.

You create SharingNotificationService() at import time and again per request in partial_update. Reuse the module-level instance to reduce overhead and keep behavior consistent with fail-fast init.

Apply this diff in the partial_update block (see lines 297-305 below) to use the module-level singleton instead of re-instantiating.


172-172: Prevent N+1 on created_by_email by selecting the relation.

APIDeploymentListSerializer uses created_by_email; select_related('created_by') avoids extra queries during list.

-        queryset = APIDeployment.objects.for_user(self.request.user)
+        queryset = (
+            APIDeployment.objects.for_user(self.request.user)
+            .select_related("created_by")
+        )

270-276: Make intent clear: remove redundant permission override and silence ARG002.

Since get_permissions returns IsOwnerOrSharedUser for this action (by design), the @action(permission_classes=[IsOwner]) is misleading and ignored. Also silence Ruff ARG002 for unused args.

-    @action(detail=True, methods=["get"], permission_classes=[IsOwner])
-    def list_of_shared_users(self, request: Request, pk: str | None = None) -> Response:
+    @action(detail=True, methods=["get"])
+    def list_of_shared_users(self, _request: Request, _pk: str | None = None) -> Response:
         """List users who have access to this API deployment."""
         instance = self.get_object()
         serializer = SharedUserListSerializer(instance)
         return Response(serializer.data)

Confirm that shared users should see this list. If owner-only is desired instead, add "list_of_shared_users" to the owner-only set in get_permissions.


298-305: Reuse the module-level service, don’t instantiate per request.

Prevents repeated construction and aligns with your fail-fast init choice.

-                    notification_service = SharingNotificationService()
-                    notification_service.send_sharing_notification(
+                    if sharing_notification_service:
+                        sharing_notification_service.send_sharing_notification(
                         resource_type=ResourceType.API_DEPLOYMENT.value,
                         resource_name=instance.display_name,
                         resource_id=str(instance.id),
                         shared_by=request.user,
                         shared_to=list(newly_shared_users),
                         resource_instance=instance,
-                    )
+                        )

308-308: Use logger.exception without interpolating the exception.

logging.exception already includes the traceback; embedding {e} is redundant (Ruff TRY401).

-                logger.exception(f"Failed to send sharing notification: {e}")
+                logger.exception("Failed to send sharing notification")

232-235: Respect sharing in by_prompt_studio_tool.

Currently filtered by created_by only; shared users won’t see deployments they can access. Filter via for_user to match list semantics.

# Outside changed lines; suggested replacement for the queryset in by_prompt_studio_tool:
deployments = (
    APIDeployment.objects.for_user(request.user)
    .filter(workflow_id__in=workflow_ids)
)

Confirm if this endpoint should reflect sharing. If owner-only is intended, ignore.

backend/api_v2/serializers.py (2)

395-410: Good addition; ensure queryset optimizes creator lookups.

created_by_email is useful. Pair with select_related('created_by') in the view (suggested) to avoid N+1 during list.


449-468: Minor: make Meta.fields immutable to quiet lint and reduce accidental mutation.

Using a tuple instead of list is a small improvement and may satisfy RUF012.

-        fields = ["id", "display_name", "shared_users", "shared_to_org", "created_by"]
+        fields = ("id", "display_name", "shared_users", "shared_to_org", "created_by")

Also confirm that exposing user emails to all shared users is acceptable per org privacy policy.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between f57e6b5 and 801dc1a.

📒 Files selected for processing (2)
  • backend/api_v2/api_deployment_views.py (4 hunks)
  • backend/api_v2/serializers.py (4 hunks)
🧰 Additional context used
🪛 Ruff (0.13.1)
backend/api_v2/api_deployment_views.py

271-271: Unused method argument: request

(ARG002)


271-271: Unused method argument: pk

(ARG002)


308-308: Redundant exception object included in logging.exception call

(TRY401)

backend/api_v2/serializers.py

457-457: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ 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: build
🔇 Additional comments (4)
backend/api_v2/api_deployment_views.py (2)

8-8: LGTM: Permission import and serializer wiring are correct.

Using IsOwnerOrSharedUser aligns with the new sharing model, and importing SharedUserListSerializer is consistent with the new endpoint.

Also applies to: 31-31


166-169: LGTM: PUT now owner-only.

Including "update" alongside "destroy" and "partial_update" is correct and closes the prior gap.

backend/api_v2/serializers.py (2)

19-20: LGTM: SerializerMethodField import added.

Import aligns with new computed fields.


412-415: LGTM.

Method is concise and safe if creator is missing.

@johnyrahul johnyrahul changed the title [FEAT] UN-2773: Add sharing functionality for API deployments UN-2773 [FEAT] Add sharing functionality for API deployments Sep 29, 2025
@github-actions

Copy link
Copy Markdown
Contributor
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_logs}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup\_skip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_client\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config\_without\_mount}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_run\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_for\_sidecar}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_sidecar\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{11}}$$ $$\textcolor{#23d18b}{\tt{11}}$$

@sonarqubecloud

Copy link
Copy Markdown

@ritwik-g ritwik-g merged commit 8480fad into main Oct 1, 2025
6 of 7 checks passed
@ritwik-g ritwik-g deleted the UN-2773-add-sharing-for-api-deployments branch October 1, 2025 07:15
Deepak-Kesavan pushed a commit that referenced this pull request Oct 7, 2025
* [FEAT] Add sharing functionality for API deployments

- Add shared_users and shared_to_org fields to APIDeployment model
- Update APIDeploymentViewSet with sharing permissions and endpoints
- Add list_of_shared_users endpoint for retrieving sharing details
- Implement sharing notifications via email when users are added
- Update frontend with Share button and SharePermission modal integration
- Include owner column in API deployments table
- Support individual user sharing with organization member filtering

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* [FIX] Import notification plugin conditionally in API deployment views

Handle cases where notification plugin may not be available by wrapping
imports in try-catch block and checking availability before use.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Update backend/api_v2/api_deployment_views.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Rahul Johny <116638720+johnyrahul@users.noreply.github.com>

* Update backend/api_v2/api_deployment_views.py

Co-authored-by: Chandrasekharan M <117059509+chandrasekharan-zipstack@users.noreply.github.com>
Signed-off-by: Rahul Johny <116638720+johnyrahul@users.noreply.github.com>

* Addressing review comments

---------

Signed-off-by: Rahul Johny <116638720+johnyrahul@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Chandrasekharan M <117059509+chandrasekharan-zipstack@users.noreply.github.com>
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.

4 participants