Skip to content

Commit

Permalink
feat: update license assign view to set source for assigned licenses
Browse files Browse the repository at this point in the history
  • Loading branch information
muhammad-ammar committed Sep 22, 2023
1 parent f9367ed commit 1ac30c0
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 4 deletions.
31 changes: 30 additions & 1 deletion license_manager/apps/api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from rest_framework import serializers
from rest_framework.fields import SerializerMethodField

from license_manager.apps.subscriptions.constants import ACTIVATED, ASSIGNED
from django.core.validators import MinLengthValidator

from license_manager.apps.subscriptions.constants import ACTIVATED, ASSIGNED, SALESFORCE_ID_LENGTH
from license_manager.apps.subscriptions.models import (
CustomerAgreement,
License,
Expand Down Expand Up @@ -361,8 +363,35 @@ class LicenseAdminAssignActionSerializer(CustomTextWithMultipleEmailsSerializer)
"""

notify_users = serializers.BooleanField(required=False)
user_sfids = serializers.ListField(
child=serializers.CharField(
allow_blank=True,
allow_null=True,
write_only=True,
validators=[MinLengthValidator(SALESFORCE_ID_LENGTH)]
),
allow_empty=False,
required=False,
error_messages = {"empty": "Wrong Salesforce Ids."}
)

class Meta:
fields = CustomTextWithMultipleEmailsSerializer.Meta.fields + [
'notify_users',
]

def validate(self, data):
user_emails = data.get('user_emails')
user_sfids = data.get('user_sfids')

if user_sfids:
# if saleforce ids list is present then its length must be equal to number of user emails
if len(user_emails) != len(user_sfids):
raise serializers.ValidationError('Wrong Salesforce Ids.')

Check warning on line 390 in license_manager/apps/api/serializers.py

View check run for this annotation

Codecov / codecov/patch

license_manager/apps/api/serializers.py#L390

Added line #L390 was not covered by tests

# if saleforce ids list is present then not every item in the list evaluates to False
allFalse = not any(user_sfids)

Check warning on line 393 in license_manager/apps/api/serializers.py

View check run for this annotation

Codecov / codecov/patch

license_manager/apps/api/serializers.py#L393

Added line #L393 was not covered by tests
if allFalse:
raise serializers.ValidationError('Wrong Salesforce Ids.')

Check warning on line 395 in license_manager/apps/api/serializers.py

View check run for this annotation

Codecov / codecov/patch

license_manager/apps/api/serializers.py#L395

Added line #L395 was not covered by tests

return super().validate(data)
46 changes: 44 additions & 2 deletions license_manager/apps/api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
License,
SubscriptionPlan,
SubscriptionsRoleAssignment,
SubscriptionLicenseSource,
SubscriptionLicenseSourceType,
)
from license_manager.apps.subscriptions.utils import (
chunks,
Expand Down Expand Up @@ -726,11 +728,36 @@ def _assign_new_licenses(self, subscription_plan, user_emails):
)
return licenses

def _set_source_for_assigned_licenses(self, assigned_licenses, emails_and_sfids):
"""
Set source for each assigned license.
"""
license_source = SubscriptionLicenseSourceType.get_source_type(SubscriptionLicenseSourceType.AMT)
source_objects = []

Check warning on line 736 in license_manager/apps/api/v1/views.py

View check run for this annotation

Codecov / codecov/patch

license_manager/apps/api/v1/views.py#L735-L736

Added lines #L735 - L736 were not covered by tests
for assigned_license in assigned_licenses:
sf_opportunity_id = emails_and_sfids.get(assigned_license.user_email)

Check warning on line 738 in license_manager/apps/api/v1/views.py

View check run for this annotation

Codecov / codecov/patch

license_manager/apps/api/v1/views.py#L738

Added line #L738 was not covered by tests
if sf_opportunity_id:
source = SubscriptionLicenseSource(

Check warning on line 740 in license_manager/apps/api/v1/views.py

View check run for this annotation

Codecov / codecov/patch

license_manager/apps/api/v1/views.py#L740

Added line #L740 was not covered by tests
license=assigned_license,
source_id=sf_opportunity_id,
source_type=license_source
)
source_objects.append(source)

Check warning on line 745 in license_manager/apps/api/v1/views.py

View check run for this annotation

Codecov / codecov/patch

license_manager/apps/api/v1/views.py#L745

Added line #L745 was not covered by tests

SubscriptionLicenseSource.objects.bulk_create(

Check warning on line 747 in license_manager/apps/api/v1/views.py

View check run for this annotation

Codecov / codecov/patch

license_manager/apps/api/v1/views.py#L747

Added line #L747 was not covered by tests
source_objects,
batch_size=constants.LICENSE_SOURCE_BULK_OPERATION_BATCH_SIZE
)

@action(detail=False, methods=['post'])
def assign(self, request, subscription_uuid=None): # pylint: disable=unused-argument
"""
Given a list of emails, assigns a license to those user emails and sends an activation email.
Endpoint will also receive an optional list of salesforce ids. If present then for each
assigned license a source object will be created to later identify the source of a license
assignment.
Assignment is intended to be a fully atomic and linearizable operation. We utilize
a cache-based lock to ensure that only one assignment operation can be executed at a
time for the given subscription plan.
Expand All @@ -743,6 +770,10 @@ def assign(self, request, subscription_uuid=None): # pylint: disable=unused-arg
'user_emails': [
'[email protected]',
'[email protected]'
],
'user_sfids': [
'001OE000001f26OXZP',
'001OE000001a25WXYZ'
]
}
"""
Expand All @@ -769,8 +800,17 @@ def _assign(self, request, subscription_plan):
# Validate the user_emails and text sent in the data
self._validate_data(request.data)

# Dedupe all lowercase emails before turning back into a list for indexing
user_emails = list({email.lower() for email in request.data.get('user_emails', [])})
emails_and_sfids = None
# remove duplicate emails and convert to lowercase
if 'user_sfids' in request.data:
user_emails = map(str.lower, request.data.get('user_emails', []))
user_sfids = request.data.get('user_sfids', [])

Check warning on line 807 in license_manager/apps/api/v1/views.py

View check run for this annotation

Codecov / codecov/patch

license_manager/apps/api/v1/views.py#L806-L807

Added lines #L806 - L807 were not covered by tests

emails_and_sfids = dict(zip(user_emails, user_sfids))
user_emails = list(emails_and_sfids.keys())

Check warning on line 810 in license_manager/apps/api/v1/views.py

View check run for this annotation

Codecov / codecov/patch

license_manager/apps/api/v1/views.py#L809-L810

Added lines #L809 - L810 were not covered by tests
else:
# Dedupe all lowercase emails before turning back into a list for indexing
user_emails = list({email.lower() for email in request.data.get('user_emails', [])})

user_emails, already_associated_emails = self._trim_already_associated_emails(
subscription_plan,
Expand All @@ -795,6 +835,8 @@ def _assign(self, request, subscription_plan):
assigned_licenses = self._assign_new_licenses(
subscription_plan, user_emails,
)
if emails_and_sfids:
self._set_source_for_assigned_licenses(assigned_licenses, emails_and_sfids)

Check warning on line 839 in license_manager/apps/api/v1/views.py

View check run for this annotation

Codecov / codecov/patch

license_manager/apps/api/v1/views.py#L839

Added line #L839 was not covered by tests
except DatabaseError:
error_message = 'Database error occurred while assigning licenses, no assignments were completed'
logger.exception(error_message)
Expand Down
19 changes: 18 additions & 1 deletion license_manager/apps/subscriptions/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ class LicenseAdmin(DjangoQLSearchMixin, admin.ModelAdmin):
'activation_key',
'get_renewed_to',
'get_renewed_from',
'auto_applied'
'auto_applied',
'source_id',
'source_type',
]
exclude = ['history', 'renewed_to']
list_display = (
Expand Down Expand Up @@ -79,8 +81,23 @@ class LicenseAdmin(DjangoQLSearchMixin, admin.ModelAdmin):
def get_queryset(self, request):
return super().get_queryset(request).select_related(
'subscription_plan',
'source'
)

@admin.display(description='Source ID')
def source_id(self, instance):
try:
return instance.source.source_id
except License.source.RelatedObjectDoesNotExist:
return ''

@admin.display(description='Source Type')
def source_type(self, instance):
try:
return instance.source.source_type.slug
except License.source.RelatedObjectDoesNotExist:
return ''

@admin.display(
description='Subscription Plan'
)
Expand Down
1 change: 1 addition & 0 deletions license_manager/apps/subscriptions/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class SegmentEvents:
# Bulk operation constants
LICENSE_BULK_OPERATION_BATCH_SIZE = 100
PENDING_ACCOUNT_CREATION_BATCH_SIZE = 100
LICENSE_SOURCE_BULK_OPERATION_BATCH_SIZE = 100

# Num distinct catalog query validation batch size
VALIDATE_NUM_CATALOG_QUERIES_BATCH_SIZE = 100
Expand Down

0 comments on commit 1ac30c0

Please sign in to comment.