Skip to content

Commit

Permalink
Merge pull request #364 from openedx/pwnage101/ENT-8105
Browse files Browse the repository at this point in the history
feat: add a "redeemed" assignment action
  • Loading branch information
pwnage101 authored Dec 13, 2023
2 parents 1e58947 + b91340a commit fc3fa46
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 4 deletions.
4 changes: 4 additions & 0 deletions enterprise_access/apps/content_assignments/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ class AssignmentActions:
LEARNER_LINKED = 'learner_linked'
NOTIFIED = 'notified'
REMINDED = 'reminded'
REDEEMED = 'redeemed'
CANCELLED_NOTIFICATION = 'cancelled'
AUTOMATIC_CANCELLATION_NOTIFICATION = 'automatic_cancellation'

CHOICES = (
(LEARNER_LINKED, 'Learner linked to customer'),
(NOTIFIED, 'Learner notified of assignment'),
(REMINDED, 'Learner reminded about assignment'),
(REDEEMED, 'Learner redeemed the assigned content'),
(CANCELLED_NOTIFICATION, 'Learner assignment cancelled'),
(AUTOMATIC_CANCELLATION_NOTIFICATION, 'Learner assignment cancelled automatically'),
)
Expand All @@ -55,10 +57,12 @@ class AssignmentActionErrors:
"""
EMAIL_ERROR = 'email_error'
INTERNAL_API_ERROR = 'internal_api_error'
ENROLLMENT_ERROR = 'enrollment_error'

CHOICES = (
(EMAIL_ERROR, 'Email error'),
(INTERNAL_API_ERROR, 'Internal API error'),
(ENROLLMENT_ERROR, 'Enrollment error'),
)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 4.2.6 on 2023-12-12 23:01

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('content_assignments', '0012_assignmentaction_add_automatic_cancellation_option'),
]

operations = [
migrations.AlterField(
model_name='historicallearnercontentassignmentaction',
name='action_type',
field=models.CharField(choices=[('learner_linked', 'Learner linked to customer'), ('notified', 'Learner notified of assignment'), ('reminded', 'Learner reminded about assignment'), ('redeemed', 'Learner redeemed the assigned content'), ('cancelled', 'Learner assignment cancelled'), ('automatic_cancellation', 'Learner assignment cancelled automatically')], db_index=True, help_text='The type of action take on the related assignment record.', max_length=255),
),
migrations.AlterField(
model_name='historicallearnercontentassignmentaction',
name='error_reason',
field=models.CharField(blank=True, choices=[('email_error', 'Email error'), ('internal_api_error', 'Internal API error'), ('enrollment_error', 'Enrollment error')], db_index=True, help_text='The type of error that occurred during the action, if any.', max_length=255, null=True),
),
migrations.AlterField(
model_name='learnercontentassignmentaction',
name='action_type',
field=models.CharField(choices=[('learner_linked', 'Learner linked to customer'), ('notified', 'Learner notified of assignment'), ('reminded', 'Learner reminded about assignment'), ('redeemed', 'Learner redeemed the assigned content'), ('cancelled', 'Learner assignment cancelled'), ('automatic_cancellation', 'Learner assignment cancelled automatically')], db_index=True, help_text='The type of action take on the related assignment record.', max_length=255),
),
migrations.AlterField(
model_name='learnercontentassignmentaction',
name='error_reason',
field=models.CharField(blank=True, choices=[('email_error', 'Email error'), ('internal_api_error', 'Internal API error'), ('enrollment_error', 'Enrollment error')], db_index=True, help_text='The type of error that occurred during the action, if any.', max_length=255, null=True),
),
]
19 changes: 19 additions & 0 deletions enterprise_access/apps/content_assignments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,25 @@ def add_errored_expiration_action(self, exc):
traceback=format_traceback(exc),
)

def add_successful_redeemed_action(self):
"""
Adds a successful redeemed LearnerContentAssignmentAction for this assignment record.
"""
return self.actions.create(
action_type=AssignmentActions.REDEEMED,
completed_at=timezone.now(),
)

def add_errored_redeemed_action(self, exc):
"""
Adds an errored redeemed LearnerContentAssignmentAction for this assignment record.
"""
return self.actions.create(
action_type=AssignmentActions.REDEEMED,
error_reason=AssignmentActionErrors.ENROLLMENT_ERROR,
traceback=format_traceback(exc),
)

@classmethod
def annotate_dynamic_fields_onto_queryset(cls, queryset):
"""
Expand Down
4 changes: 3 additions & 1 deletion enterprise_access/apps/subsidy_access_policy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1202,15 +1202,17 @@ def redeem(self, lms_user_id, content_key, all_transactions, metadata=None, **kw
metadata=metadata,
requested_price_cents=requested_price_cents,
)
except SubsidyAPIHTTPError:
except SubsidyAPIHTTPError as exc:
# Migrate assignment to errored if the subsidy API call errored.
found_assignment.state = LearnerContentAssignmentStateChoices.ERRORED
found_assignment.save()
found_assignment.add_errored_redeemed_action(exc)
raise
# Migrate assignment to accepted.
found_assignment.state = LearnerContentAssignmentStateChoices.ACCEPTED
found_assignment.transaction_uuid = ledger_transaction.get('uuid') # uuid should always be in the API response.
found_assignment.save()
found_assignment.add_successful_redeemed_action()
return ledger_transaction

def validate_requested_allocation_price(self, content_key, requested_price_cents):
Expand Down
21 changes: 18 additions & 3 deletions enterprise_access/apps/subsidy_access_policy/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings

from enterprise_access.apps.content_assignments.constants import LearnerContentAssignmentStateChoices
from enterprise_access.apps.content_assignments.constants import (
AssignmentActionErrors,
AssignmentActions,
LearnerContentAssignmentStateChoices
)
from enterprise_access.apps.content_assignments.models import AssignmentConfiguration
from enterprise_access.apps.content_assignments.tests.factories import LearnerContentAssignmentFactory
from enterprise_access.apps.subsidy_access_policy.constants import (
Expand Down Expand Up @@ -1022,14 +1026,25 @@ def test_redeem(
**expected_redeem_payload,
)

# Finally, assert that the assignment object was correctly updated to reflect the success/failure.
# assert that the assignment object was correctly updated to reflect the success/failure.
if assignment:
assignment.refresh_from_db()
assert assignment.state == assignment_ending_state
# happy path should result in an updated transaction_uuid.
if not redeem_raises:
# happy path should result in an updated transaction_uuid.
assert assignment.transaction_uuid == test_transaction_uuid

# happy path should also result in a null error_reason on the redeemed action.
redeemed_action = assignment.actions.last()
assert redeemed_action.action_type == AssignmentActions.REDEEMED
assert not redeemed_action.error_reason
if fail_subsidy_create_transaction:
# sad path should generate a failed redeememd action with populated error_reason and traceback.
redeemed_action = assignment.actions.last()
assert redeemed_action.action_type == AssignmentActions.REDEEMED
assert redeemed_action.error_reason == AssignmentActionErrors.ENROLLMENT_ERROR
assert redeemed_action.traceback

def test_can_allocate_inactive_policy(self):
"""
Tests that inactive policies can't be allocated against.
Expand Down

0 comments on commit fc3fa46

Please sign in to comment.