This is an automated email from the ASF dual-hosted git repository.

yasithdev pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git


The following commit(s) were added to refs/heads/main by this push:
     new e1bfa3e52 feat(portal): de-Thrift entity sharing to the gRPC sharing 
facade (Track D, D5) (#184)
e1bfa3e52 is described below

commit e1bfa3e52ebfe9cbe7cf0071b9b40b0db7b6fb20
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Mon Jun 8 22:19:28 2026 -0400

    feat(portal): de-Thrift entity sharing to the gRPC sharing facade (Track D, 
D5) (#184)
    
    `SharedEntityViewSet` and `SharedEntitySerializer.get_hasSharingPermission` 
ran
    on the Thrift sharing-registry / profile-service clients, so opening a 
resource's
    sharing dialog blocked on the dead Thrift server's read-timeout.
    
    - `SharedEntityViewSet` accessible-users/groups loads ->
      `request.airavata.sharing.get_all[_directly]_accessible_{users,groups}`;
      share/revoke -> `share_resource_with_{users,groups}` /
      `revoke_sharing_of_resource_from_{users,groups}`; `_load_user_profile` ->
      `iam.get_user_profile_by_id` (+ new `grpc_adapters.user_profile`); 
`_load_group`
      -> `sharing.gm_get_group` (+ `grpc_adapters.group`).
    - `get_hasSharingPermission` -> the existing gRPC `user_has_access` helper.
    - The sharing facade takes the permission as the enum NAME (e.g. "READ"); 
the
      serializer still renders the Thrift int via `permissionType`. So the 
viewset
      bridges with `ResourcePermissionType(value).name` at the facade boundary 
and
      keeps the int in the returned dict (frontend contract unchanged).
    - New `grpc_adapters.user_profile` maps the protobuf `UserProfile` to the 
Thrift
      attribute names the serializer reads (incl. the 
`State`/`orginationAffiliation`/
      `labeledURI` quirks); nested `nsfDemographics`/`customDashboard` render 
null.
    
    Verified: `manage.py check` green; a real `UserProfile` renders cleanly 
through
    `UserProfileSerializer` via the adapter (all 26 fields, no missing-attr 
errors);
    the sharing facade calls accept the NAME permission and return live results.
---
 .../django_airavata/apps/api/grpc_adapters.py      | 39 +++++++++++++++++
 .../django_airavata/apps/api/serializers.py        |  5 +--
 .../django_airavata/apps/api/views.py              | 51 +++++++++++-----------
 3 files changed, 66 insertions(+), 29 deletions(-)

diff --git a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py 
b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
index 057c5a081..b2e24d634 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
@@ -38,6 +38,7 @@ from airavata.model.status.ttypes import (
     TaskState as _ThriftTaskState,
 )
 from airavata.model.task.ttypes import TaskTypes as _ThriftTaskTypes
+from airavata.model.user.ttypes import Status as _ThriftStatus
 from airavata.model.workspace.ttypes import (
     NotificationPriority as _ThriftNotificationPriority,
 )
@@ -844,6 +845,44 @@ def group(pb):
     )
 
 
+def user_profile(pb):
+    """gRPC ``UserProfile`` -> ``UserProfileSerializer`` shape.
+
+    Note the Thrift attribute quirks the serializer expects: ``State`` 
(capital),
+    ``orginationAffiliation`` (Thrift's spelling), ``labeledURI``. The nested
+    ``nsfDemographics``/``customDashboard`` structs are not surfaced in the
+    sharing UI, so they render null.
+    """
+    return SimpleNamespace(
+        userModelVersion=pb.user_model_version or None,
+        airavataInternalUserId=pb.airavata_internal_user_id,
+        userId=pb.user_id,
+        gatewayId=pb.gateway_id,
+        emails=list(pb.emails),
+        firstName=pb.first_name or None,
+        lastName=pb.last_name or None,
+        middleName=pb.middle_name or None,
+        namePrefix=pb.name_prefix or None,
+        nameSuffix=pb.name_suffix or None,
+        orcidId=pb.orcid_id or None,
+        phones=list(pb.phones),
+        country=pb.country or None,
+        nationality=list(pb.nationality),
+        homeOrganization=pb.home_organization or None,
+        orginationAffiliation=pb.origination_affiliation or None,
+        creationTime=pb.creation_time or None,
+        lastAccessTime=pb.last_access_time or None,
+        validUntil=pb.valid_until or None,
+        State=_thrift_enum_prefixed(pb, 'state', _ThriftStatus, 'STATUS_'),
+        comments=pb.comments or None,
+        labeledURI=pb.labeled_uri or None,
+        gpgKey=pb.gpg_key or None,
+        timeZone=pb.time_zone or None,
+        nsfDemographics=None,
+        customDashboard=None,
+    )
+
+
 def notification(pb):
     """gRPC ``Notification`` -> ``NotificationSerializer`` shape."""
     return SimpleNamespace(
diff --git a/airavata-django-portal/django_airavata/apps/api/serializers.py 
b/airavata-django-portal/django_airavata/apps/api/serializers.py
index 38e0630fb..c6a2e33c3 100644
--- a/airavata-django-portal/django_airavata/apps/api/serializers.py
+++ b/airavata-django-portal/django_airavata/apps/api/serializers.py
@@ -1794,9 +1794,8 @@ class SharedEntitySerializer(serializers.Serializer):
 
     def get_hasSharingPermission(self, shared_entity):
         request = self.context['request']
-        return request.airavata_client.userHasAccess(
-            request.authz_token, shared_entity['entityId'],
-            ResourcePermissionType.MANAGE_SHARING)
+        return user_has_access(
+            request, shared_entity['entityId'], "MANAGE_SHARING")
 
 
 class CredentialSummarySerializer(
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py 
b/airavata-django-portal/django_airavata/apps/api/views.py
index ca4ddea64..e7edb4246 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -1196,35 +1196,34 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin,
                 'owner': self._load_user_profile(owner_id)}
 
     def _load_accessible_users(self, entity_id, permission_type):
-        users = self.request.airavata_client.getAllAccessibleUsers(
-            self.authz_token, entity_id, permission_type)
+        users = self.request.airavata.sharing.get_all_accessible_users(
+            entity_id, ResourcePermissionType(permission_type).name)
         return {user_id: permission_type for user_id in users}
 
     def _load_directly_accessible_users(self, entity_id, permission_type):
-        users = self.request.airavata_client.getAllDirectlyAccessibleUsers(
-            self.authz_token, entity_id, permission_type)
+        users = 
self.request.airavata.sharing.get_all_directly_accessible_users(
+            entity_id, ResourcePermissionType(permission_type).name)
         return {user_id: permission_type for user_id in users}
 
     def _load_user_profile(self, user_id):
-        user_profile_client = self.request.profile_service['user_profile']
         username = user_id[0:user_id.rindex('@')]
-        return user_profile_client.getUserProfileById(self.authz_token,
-                                                      username,
-                                                      settings.GATEWAY_ID)
+        return grpc_adapters.user_profile(
+            self.request.airavata.iam.get_user_profile_by_id(
+                username, settings.GATEWAY_ID))
 
     def _load_accessible_groups(self, entity_id, permission_type):
-        groups = self.request.airavata_client.getAllAccessibleGroups(
-            self.authz_token, entity_id, permission_type)
+        groups = self.request.airavata.sharing.get_all_accessible_groups(
+            entity_id, ResourcePermissionType(permission_type).name)
         return {group_id: permission_type for group_id in groups}
 
     def _load_directly_accessible_groups(self, entity_id, permission_type):
-        groups = self.request.airavata_client.getAllDirectlyAccessibleGroups(
-            self.authz_token, entity_id, permission_type)
+        groups = 
self.request.airavata.sharing.get_all_directly_accessible_groups(
+            entity_id, ResourcePermissionType(permission_type).name)
         return {group_id: permission_type for group_id in groups}
 
     def _load_group(self, group_id):
-        group_manager_client = self.request.profile_service['group_manager']
-        return group_manager_client.getGroup(self.authz_token, group_id)
+        return grpc_adapters.group(
+            self.request.airavata.sharing.gm_get_group(group_id))
 
     def perform_update(self, serializer):
         shared_entity = serializer.save()
@@ -1279,24 +1278,24 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin,
                 shared_entity['_group_revoke_manage_sharing_permission'])
 
     def _share_with_users(self, entity_id, permission_type, user_ids):
-        self.request.airavata_client.shareResourceWithUsers(
-            self.authz_token, entity_id,
-            {user_id: permission_type for user_id in user_ids})
+        name = ResourcePermissionType(permission_type).name
+        self.request.airavata.sharing.share_resource_with_users(
+            entity_id, {user_id: name for user_id in user_ids})
 
     def _revoke_from_users(self, entity_id, permission_type, user_ids):
-        self.request.airavata_client.revokeSharingOfResourceFromUsers(
-            self.authz_token, entity_id,
-            {user_id: permission_type for user_id in user_ids})
+        name = ResourcePermissionType(permission_type).name
+        self.request.airavata.sharing.revoke_sharing_of_resource_from_users(
+            entity_id, {user_id: name for user_id in user_ids})
 
     def _share_with_groups(self, entity_id, permission_type, group_ids):
-        self.request.airavata_client.shareResourceWithGroups(
-            self.authz_token, entity_id,
-            {group_id: permission_type for group_id in group_ids})
+        name = ResourcePermissionType(permission_type).name
+        self.request.airavata.sharing.share_resource_with_groups(
+            entity_id, {group_id: name for group_id in group_ids})
 
     def _revoke_from_groups(self, entity_id, permission_type, group_ids):
-        self.request.airavata_client.revokeSharingOfResourceFromGroups(
-            self.authz_token, entity_id,
-            {group_id: permission_type for group_id in group_ids})
+        name = ResourcePermissionType(permission_type).name
+        self.request.airavata.sharing.revoke_sharing_of_resource_from_groups(
+            entity_id, {group_id: name for group_id in group_ids})
 
     @action(methods=['put'], detail=True)
     def merge(self, request, entity_id=None):

Reply via email to