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

feat: creating new enterprise group members endpoint #2270

Merged
merged 3 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ Unreleased
----------
* nothing unreleased

[4.33.1]
--------
* feat: Creating enterprise customer members endpoint for admin portal
kiram15 marked this conversation as resolved.
Show resolved Hide resolved

[4.33.0]
--------
* feat: Updated pagination for reporting configurations.
Expand Down
2 changes: 1 addition & 1 deletion enterprise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Your project description goes here.
"""

__version__ = "4.33.0"
__version__ = "4.33.1"
39 changes: 39 additions & 0 deletions enterprise/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1909,6 +1909,45 @@ def get_role_assignments(self, obj):
return None


class EnterpriseMembersSerializer(serializers.Serializer):
"""
Serializer for EnterpriseCustomerUser model with additions.
"""
class Meta:
model = models.EnterpriseCustomerUser
fields = (
'enterprise_customer_user',
'enrollments',
)

enterprise_customer_user = serializers.SerializerMethodField()
enrollments = serializers.SerializerMethodField()

def get_enrollments(self, obj):
"""
Fetch all of user's enterprise enrollments
"""
if user := obj:
user_id = user[0]
enrollments = models.EnterpriseCourseEnrollment.objects.filter(
enterprise_customer_user=user_id,
)
return len(enrollments)
return 0

def get_enterprise_customer_user(self, obj):
"""
Return either the member's name and email if it's the case that the member is realized, otherwise just email
"""
if user := obj:
return {
"email": user[1],
"joined_org": user[2].strftime("%b %d, %Y"),
"name": user[3],
}
return None


class DefaultEnterpriseEnrollmentIntentionSerializer(serializers.ModelSerializer):
"""
Serializer for the DefaultEnterpriseEnrollmentIntention model.
Expand Down
6 changes: 6 additions & 0 deletions enterprise/api/v1/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
enterprise_customer_branding_configuration,
enterprise_customer_catalog,
enterprise_customer_invite_key,
enterprise_customer_members,
enterprise_customer_reporting,
enterprise_customer_sso_configuration,
enterprise_customer_support,
Expand Down Expand Up @@ -211,6 +212,11 @@
),
name='enterprise-customer-support'
),
re_path(
r'^enterprise-customer-members/(?P<enterprise_uuid>[A-Za-z0-9-]+)$',
enterprise_customer_members.EnterpriseCustomerMembersViewSet.as_view({'get': 'get_members'}),
name='enterprise-customer-members'
),
]

urlpatterns += router.urls
116 changes: 116 additions & 0 deletions enterprise/api/v1/views/enterprise_customer_members.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""
Views for the ``enterprise-customer-members`` API endpoint.
"""

from collections import OrderedDict

from rest_framework import permissions, response, status
from rest_framework.pagination import PageNumberPagination

from django.core.exceptions import ValidationError
from django.db import connection

from enterprise import models
from enterprise.api.v1 import serializers
from enterprise.api.v1.views.base_views import EnterpriseReadOnlyModelViewSet
from enterprise.logging import getEnterpriseLogger

LOGGER = getEnterpriseLogger(__name__)


class EnterpriseCustomerMembersPaginator(PageNumberPagination):
"""Custom paginator for the enterprise customer members."""

page_size = 10

def get_paginated_response(self, data):
"""Return a paginated style `Response` object for the given output data."""
return response.Response(

Check warning on line 28 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L28

Added line #L28 was not covered by tests
OrderedDict(
[
("count", self.page.paginator.count),
("num_pages", self.page.paginator.num_pages),
("next", self.get_next_link()),
("previous", self.get_previous_link()),
("results", data),
]
)
)

def paginate_queryset(self, queryset, request, view=None):
"""
Paginate a queryset if required, either returning a page object,
or `None` if pagination is not configured for this view.

"""
if isinstance(queryset, filter):
queryset = list(queryset)

Check warning on line 47 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L47

Added line #L47 was not covered by tests

return super().paginate_queryset(queryset, request, view)

Check warning on line 49 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L49

Added line #L49 was not covered by tests


class EnterpriseCustomerMembersViewSet(EnterpriseReadOnlyModelViewSet):
"""
API views for the ``enterprise-customer-members`` API endpoint.
"""
queryset = models.EnterpriseCustomerUser.objects.all()
serializer_class = serializers.EnterpriseMembersSerializer

permission_classes = (permissions.IsAuthenticated,)
paginator = EnterpriseCustomerMembersPaginator()

def get_members(self, request, *args, **kwargs):
"""
Get all members associated with that enterprise customer
"""
enterprise_uuid = kwargs.get("enterprise_uuid", None)

Check warning on line 66 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L66

Added line #L66 was not covered by tests
# Raw sql is picky about uuid format
uuid_no_dashes = str(enterprise_uuid).replace("-", "")
users = []
user_query = self.request.query_params.get("user_query", None)

Check warning on line 70 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L68-L70

Added lines #L68 - L70 were not covered by tests

# On logistration, the name field of auth_userprofile is populated, but if it's not
# filled in, we check the auth_user model for it's first/last name fields
# https://2u-internal.atlassian.net/wiki/spaces/ENGAGE/pages/747143186/Use+of+full+name+in+edX#Data-on-Name-Field
query = """

Check warning on line 75 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L75

Added line #L75 was not covered by tests
WITH users AS (
SELECT
au.id,
au.email,
au.date_joined,
coalesce(NULLIF(aup.name, ''), concat(au.first_name, ' ', au.last_name)) as full_name
FROM enterprise_enterprisecustomeruser ecu
INNER JOIN auth_user as au on ecu.user_id = au.id
LEFT JOIN auth_userprofile as aup on au.id = aup.user_id
WHERE ecu.enterprise_customer_id = %s
) SELECT * FROM users {user_query_filter} ORDER BY full_name;
"""
try:
with connection.cursor() as cursor:

Check warning on line 89 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L88-L89

Added lines #L88 - L89 were not covered by tests
if user_query:
like_user_query = f"%{user_query}%"
sql_to_execute = query.format(

Check warning on line 92 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L91-L92

Added lines #L91 - L92 were not covered by tests
user_query_filter="WHERE full_name LIKE %s OR email LIKE %s"
)
cursor.execute(

Check warning on line 95 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L95

Added line #L95 was not covered by tests
sql_to_execute,
[uuid_no_dashes, like_user_query, like_user_query],
)
else:
sql_to_execute = query.format(user_query_filter="")
cursor.execute(sql_to_execute, [uuid_no_dashes])
users.extend(cursor.fetchall())

Check warning on line 102 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L100-L102

Added lines #L100 - L102 were not covered by tests

except ValidationError:

Check warning on line 104 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L104

Added line #L104 was not covered by tests
# did not find UUID match in either EnterpriseCustomerUser
return response.Response(

Check warning on line 106 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L106

Added line #L106 was not covered by tests
{"detail": "Could not find enterprise uuid {}".format(enterprise_uuid)},
status=status.HTTP_404_NOT_FOUND,
)

# paginate the queryset
users_page = self.paginator.paginate_queryset(users, request, view=self)

Check warning on line 112 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L112

Added line #L112 was not covered by tests

# serialize the paged dataset
serializer = serializers.EnterpriseMembersSerializer(users_page, many=True)
return self.paginator.get_paginated_response(serializer.data)

Check warning on line 116 in enterprise/api/v1/views/enterprise_customer_members.py

View check run for this annotation

Codecov / codecov/patch

enterprise/api/v1/views/enterprise_customer_members.py#L115-L116

Added lines #L115 - L116 were not covered by tests
70 changes: 69 additions & 1 deletion tests/test_enterprise/api/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
EnterpriseCustomerReportingConfigurationSerializer,
EnterpriseCustomerSerializer,
EnterpriseCustomerUserReadOnlySerializer,
EnterpriseMembersSerializer,
EnterpriseUserSerializer,
ImmutableStateSerializer,
)
Expand Down Expand Up @@ -455,7 +456,7 @@ def setUp(self):

super().setUp()

# setup Enteprise Customer
# setup Enterprise Customer
self.user_1 = factories.UserFactory()
self.user_2 = factories.UserFactory()
self.enterprise_customer_user_1 = factories.EnterpriseCustomerUserFactory(user_id=self.user_1.id)
Expand Down Expand Up @@ -558,3 +559,70 @@ def test_serialize_pending_users(self):
serialized_pending_admin_user = serializer.data

self.assertEqual(expected_pending_admin_user, serialized_pending_admin_user)


class TestEnterpriseMembersSerializer(TestCase):
"""
Tests for EnterpriseMembersSerializer.
"""
def setUp(self):
super().setUp()

# setup Enterprise Customer
self.user_1 = factories.UserFactory()
self.user_2 = factories.UserFactory()
self.enterprise_customer_user_1 = factories.EnterpriseCustomerUserFactory(user_id=self.user_1.id)
self.enterprise_customer_user_2 = factories.EnterpriseCustomerUserFactory(user_id=self.user_2.id)
self.enterprise_customer_1 = self.enterprise_customer_user_1.enterprise_customer
self.enterprise_customer_2 = self.enterprise_customer_user_2.enterprise_customer

self.enrollment_1 = factories.EnterpriseCourseEnrollmentFactory(
enterprise_customer_user=self.enterprise_customer_user_1,
)
self.enrollment_2 = factories.EnterpriseCourseEnrollmentFactory(
enterprise_customer_user=self.enterprise_customer_user_1,
)
self.enrollment_3 = factories.EnterpriseCourseEnrollmentFactory(
enterprise_customer_user=self.enterprise_customer_user_2,
)

def test_serialize_users(self):
expected_user = {
'enterprise_customer_user': {
'email': self.user_1.email,
'joined_org': self.user_1.date_joined.strftime("%b %d, %Y"),
'name': (self.user_1.first_name + ' ' + self.user_1.last_name),
},
'enrollments': 2,
}

serializer_input_1 = [
self.user_1.id,
self.user_1.email,
self.user_1.date_joined,
self.user_1.first_name + ' ' + self.user_1.last_name,
]
serializer = EnterpriseMembersSerializer(serializer_input_1)
serialized_user = serializer.data

self.assertEqual(serialized_user, expected_user)

expected_user_2 = {
'enterprise_customer_user': {
'email': self.user_2.email,
'joined_org': self.user_2.date_joined.strftime("%b %d, %Y"),
'name': self.user_2.first_name + ' ' + self.user_2.last_name,
},
'enrollments': 1,
}

serializer_input_2 = [
self.user_2.id,
self.user_2.email,
self.user_2.date_joined,
self.user_2.first_name + ' ' + self.user_2.last_name,
]

serializer = EnterpriseMembersSerializer(serializer_input_2)
serialized_user = serializer.data
self.assertEqual(serialized_user, expected_user_2)
Loading