diff --git a/openassessment/xblock/test/test_submission.py b/openassessment/xblock/test/test_submission.py index 453cbe8363..ed93d89dac 100644 --- a/openassessment/xblock/test/test_submission.py +++ b/openassessment/xblock/test/test_submission.py @@ -32,6 +32,7 @@ from openassessment.xblock.utils.data_conversion import create_submission_dict, prepare_submission_for_serialization from openassessment.xblock.openassessmentblock import OpenAssessmentBlock from openassessment.xblock.ui_mixins.legacy.views.submission import get_team_submission_context, render_submission +from openassessment.xblock.ui_mixins.legacy.handlers_mixin import LegacyHandlersMixin from openassessment.xblock.workflow_mixin import WorkflowMixin from openassessment.xblock.test.test_team import MockTeamsService, MOCK_TEAM_ID @@ -361,28 +362,30 @@ def test_delete_and_submit(self, xblock): with patch('submissions.api.create_submission') as mocked_submit: with patch.object(WorkflowMixin, 'create_workflow'): - mocked_submit.return_value = { - "uuid": '1111', - "attempt_number": 1, - "created_at": dt.datetime.now(), - "submitted_at": dt.datetime.now(), - "answer": {}, - } - resp = self.request(xblock, 'submit', self.SUBMISSION, response_format='json') - mocked_submit.assert_called_once() - student_sub_dict = mocked_submit.call_args[0][1] - self.assertEqual( - student_sub_dict['files_descriptions'], - [meta['description'] for meta in expected_file_metadata] - ) - self.assertEqual( - student_sub_dict['files_names'], - [meta['fileName'] for meta in expected_file_metadata] - ) - self.assertEqual( - student_sub_dict['files_sizes'], - [meta['fileSize'] for meta in expected_file_metadata] - ) + with patch.object(LegacyHandlersMixin, 'send_ora_submission_created_event') as mock_send_event: + mocked_submit.return_value = { + "uuid": '1111', + "attempt_number": 1, + "created_at": dt.datetime.now(), + "submitted_at": dt.datetime.now(), + "answer": {}, + } + resp = self.request(xblock, 'submit', self.SUBMISSION, response_format='json') + mock_send_event.assert_called_once() + mocked_submit.assert_called_once() + student_sub_dict = mocked_submit.call_args[0][1] + self.assertEqual( + student_sub_dict['files_descriptions'], + [meta['description'] for meta in expected_file_metadata] + ) + self.assertEqual( + student_sub_dict['files_names'], + [meta['fileName'] for meta in expected_file_metadata] + ) + self.assertEqual( + student_sub_dict['files_sizes'], + [meta['fileSize'] for meta in expected_file_metadata] + ) @mock_s3 @override_settings( diff --git a/openassessment/xblock/ui_mixins/legacy/handlers_mixin.py b/openassessment/xblock/ui_mixins/legacy/handlers_mixin.py index 707fac942d..6cbb19448c 100644 --- a/openassessment/xblock/ui_mixins/legacy/handlers_mixin.py +++ b/openassessment/xblock/ui_mixins/legacy/handlers_mixin.py @@ -2,6 +2,8 @@ import logging +from openedx_events.learning.data import ORASubmissionAnswer, ORASubmissionData +from openedx_events.learning.signals import ORA_SUBMISSION_CREATED from xblock.core import XBlock from submissions import api as submissions_api from openassessment.assessment.errors.peer import ( @@ -42,6 +44,7 @@ from openassessment.xblock.staff_area_mixin import require_course_staff from openassessment.xblock.ui_mixins.legacy.serializers import SaveFilesDescriptionRequestSerializer from openassessment.xblock.utils.data_conversion import verify_assessment_parameters +from openassessment.xblock.utils.user_data import get_anonymous_user_id logger = logging.getLogger(__name__) @@ -61,6 +64,39 @@ class LegacyHandlersMixin: Exposes actions (@XBlock.json_handlers) used in our legacy ORA UI """ + def send_ora_submission_created_event(self, submission: dict) -> None: + """ + Send an event when a submission is created. + + Args: + submission (dict): The submission data + """ + # This import is here to avoid a circular import + from openassessment.xblock.openassessmentblock import OpenAssessmentBlock + + file_downloads = OpenAssessmentBlock.get_download_urls_from_submission(submission) + file_urls = [file_download.get("download_url") for file_download in file_downloads] + answer = submission.get("answer", {}) + # .. event_implemented_name: ORA_SUBMISSION_CREATED + ORA_SUBMISSION_CREATED.send_event( + submission=ORASubmissionData( + uuid=submission.get("uuid"), + anonymous_user_id=get_anonymous_user_id(self.runtime.service(self, "user")), + location=str(self.location), + attempt_number=submission.get("attempt_number"), + created_at=submission.get("created_at"), + submitted_at=submission.get("submitted_at"), + answer=ORASubmissionAnswer( + parts=answer.get("parts"), + file_keys=answer.get("file_keys"), + file_descriptions=answer.get("files_descriptions"), + file_names=answer.get("files_names"), + file_sizes=answer.get("files_sizes"), + file_urls=file_urls, + ), + ) + ) + # Submissions @XBlock.json_handler @@ -79,6 +115,7 @@ def submit(self, data, suffix=""): # pylint: disable=unused-argument self.submission_data, self.workflow_data ) + self.send_ora_submission_created_event(submission) return ( True, submission.get("student_item"), diff --git a/openassessment/xblock/utils/user_data.py b/openassessment/xblock/utils/user_data.py index 922ae4286b..959edd5160 100644 --- a/openassessment/xblock/utils/user_data.py +++ b/openassessment/xblock/utils/user_data.py @@ -20,3 +20,12 @@ def get_user_preferences(user_service): user_preferences['user_language'] = retrieved_preferences.get('pref-lang') return user_preferences + + +def get_anonymous_user_id(user_service): + """ + Returns the anonymous user id for the current user. + + :param user_service: XblockUserService + """ + return user_service.get_current_user().opt_attrs.get('edx-platform.anonymous_user_id') diff --git a/requirements/base.in b/requirements/base.in index ca87fa873b..52da27a3a4 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -10,6 +10,7 @@ djangorestframework Xblock edx-opaque-keys openedx-filters +openedx-events django django-simple-history diff --git a/requirements/base.txt b/requirements/base.txt index 166890f8b4..e8c910b83a 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -8,6 +8,8 @@ appdirs==1.4.4 # via fs asgiref==3.8.1 # via django +attrs==23.2.0 + # via openedx-events backports-zoneinfo==0.2.1 ; python_version < "3.9" # via # -c requirements/constraints.txt @@ -15,9 +17,9 @@ backports-zoneinfo==0.2.1 ; python_version < "3.9" # djangorestframework bleach==6.1.0 # via -r requirements/base.in -boto3==1.34.83 +boto3==1.34.87 # via -r requirements/base.in -botocore==1.34.83 +botocore==1.34.87 # via # boto3 # s3transfer @@ -48,6 +50,7 @@ django==4.2.11 # edx-submissions # edx-toggles # jsonfield + # openedx-events # openedx-filters django-crum==0.7.9 # via @@ -73,14 +76,19 @@ edx-django-utils==5.12.0 # via # -r requirements/base.in # edx-toggles + # openedx-events edx-i18n-tools==1.5.0 # via -r requirements/base.in -edx-opaque-keys==2.5.1 - # via -r requirements/base.in +edx-opaque-keys[django]==2.5.1 + # via + # -r requirements/base.in + # openedx-events edx-submissions==3.7.0 # via -r requirements/base.in edx-toggles==5.2.0 # via -r requirements/base.in +fastavro==1.9.4 + # via openedx-events fs==2.0.18 # via # -c requirements/constraints.txt @@ -122,7 +130,9 @@ markupsafe==2.1.5 # xblock newrelic==9.8.0 # via edx-django-utils -openedx-filters==1.8.0 +openedx-events==9.9.2 + # via -r requirements/base.in +openedx-filters==1.8.1 # via -r requirements/base.in path==13.1.0 # via @@ -178,7 +188,7 @@ six==1.16.0 # html5lib # python-dateutil # python-swiftclient -sqlparse==0.4.4 +sqlparse==0.5.0 # via django stevedore==5.2.0 # via diff --git a/requirements/ci.txt b/requirements/ci.txt index 186554ff6c..12bb9d6980 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -68,7 +68,7 @@ tox==4.14.2 # via -r requirements/tox.txt urllib3==2.2.1 # via requests -virtualenv==20.25.1 +virtualenv==20.25.3 # via # -r requirements/tox.txt # tox diff --git a/requirements/quality.txt b/requirements/quality.txt index c1283c39ab..83e2f428b2 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -24,6 +24,10 @@ astroid==3.1.0 # via # pylint # pylint-celery +attrs==23.2.0 + # via + # -r requirements/test.txt + # openedx-events backports-zoneinfo[tzdata]==0.2.1 ; python_version < "3.9" # via # -c requirements/constraints.txt @@ -42,12 +46,12 @@ binaryornot==0.4.4 # cookiecutter bleach==6.1.0 # via -r requirements/test.txt -boto3==1.34.83 +boto3==1.34.87 # via # -r requirements/test.txt # fs-s3fs # moto -botocore==1.34.83 +botocore==1.34.87 # via # -r requirements/test.txt # boto3 @@ -57,7 +61,7 @@ cachetools==5.3.3 # via # -r requirements/test.txt # tox -celery==5.3.6 +celery==5.4.0 # via -r requirements/test.txt certifi==2024.2.2 # via @@ -149,6 +153,7 @@ django==4.2.11 # edx-submissions # edx-toggles # jsonfield + # openedx-events # openedx-filters # xblock-sdk django-crum==0.7.9 @@ -177,12 +182,15 @@ edx-django-utils==5.12.0 # via # -r requirements/test.txt # edx-toggles + # openedx-events edx-i18n-tools==1.5.0 # via -r requirements/test.txt edx-lint==5.3.6 # via -r requirements/quality.in -edx-opaque-keys==2.5.1 - # via -r requirements/test.txt +edx-opaque-keys[django]==2.5.1 + # via + # -r requirements/test.txt + # openedx-events edx-submissions==3.7.0 # via -r requirements/test.txt edx-toggles==5.2.0 @@ -193,10 +201,14 @@ exceptiongroup==1.2.0 # pytest factory-boy==3.3.0 # via -r requirements/test.txt -faker==24.8.0 +faker==24.11.0 # via # -r requirements/test.txt # factory-boy +fastavro==1.9.4 + # via + # -r requirements/test.txt + # openedx-events filelock==3.13.4 # via # -r requirements/test.txt @@ -295,7 +307,9 @@ newrelic==9.8.0 # via # -r requirements/test.txt # edx-django-utils -openedx-filters==1.8.0 +openedx-events==9.9.2 + # via -r requirements/test.txt +openedx-filters==1.8.1 # via -r requirements/test.txt packaging==24.0 # via @@ -456,7 +470,7 @@ six==1.16.0 # html5lib # python-dateutil # python-swiftclient -sqlparse==0.4.4 +sqlparse==0.5.0 # via # -r requirements/test.txt # django @@ -515,7 +529,7 @@ vine==5.1.0 # amqp # celery # kombu -virtualenv==20.25.1 +virtualenv==20.25.3 # via # -r requirements/test.txt # tox diff --git a/requirements/test-acceptance.txt b/requirements/test-acceptance.txt index 3aecc7b9cb..4eab85bb4f 100644 --- a/requirements/test-acceptance.txt +++ b/requirements/test-acceptance.txt @@ -20,6 +20,10 @@ asgiref==3.8.1 # via # -r requirements/test.txt # django +attrs==23.2.0 + # via + # -r requirements/test.txt + # openedx-events backports-zoneinfo[tzdata]==0.2.1 ; python_version < "3.9" # via # -c requirements/constraints.txt @@ -38,12 +42,12 @@ binaryornot==0.4.4 # cookiecutter bleach==6.1.0 # via -r requirements/test.txt -boto3==1.34.83 +boto3==1.34.87 # via # -r requirements/test.txt # fs-s3fs # moto -botocore==1.34.83 +botocore==1.34.87 # via # -r requirements/test.txt # boto3 @@ -53,7 +57,7 @@ cachetools==5.3.3 # via # -r requirements/test.txt # tox -celery==5.3.6 +celery==5.4.0 # via -r requirements/test.txt certifi==2024.2.2 # via @@ -139,6 +143,7 @@ django==4.2.11 # edx-submissions # edx-toggles # jsonfield + # openedx-events # openedx-filters # xblock-sdk django-crum==0.7.9 @@ -167,10 +172,13 @@ edx-django-utils==5.12.0 # via # -r requirements/test.txt # edx-toggles + # openedx-events edx-i18n-tools==1.5.0 # via -r requirements/test.txt -edx-opaque-keys==2.5.1 - # via -r requirements/test.txt +edx-opaque-keys[django]==2.5.1 + # via + # -r requirements/test.txt + # openedx-events edx-submissions==3.7.0 # via -r requirements/test.txt edx-toggles==5.2.0 @@ -181,10 +189,14 @@ exceptiongroup==1.2.0 # pytest factory-boy==3.3.0 # via -r requirements/test.txt -faker==24.8.0 +faker==24.11.0 # via # -r requirements/test.txt # factory-boy +fastavro==1.9.4 + # via + # -r requirements/test.txt + # openedx-events filelock==3.13.4 # via # -r requirements/test.txt @@ -279,7 +291,9 @@ newrelic==9.8.0 # via # -r requirements/test.txt # edx-django-utils -openedx-filters==1.8.0 +openedx-events==9.9.2 + # via -r requirements/test.txt +openedx-filters==1.8.1 # via -r requirements/test.txt packaging==24.0 # via @@ -425,7 +439,7 @@ six==1.16.0 # html5lib # python-dateutil # python-swiftclient -sqlparse==0.4.4 +sqlparse==0.5.0 # via # -r requirements/test.txt # django @@ -479,7 +493,7 @@ vine==5.1.0 # amqp # celery # kombu -virtualenv==20.25.1 +virtualenv==20.25.3 # via # -r requirements/test.txt # tox diff --git a/requirements/test.txt b/requirements/test.txt index 6799e25413..a531aaedac 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -16,6 +16,10 @@ asgiref==3.8.1 # via # -r requirements/base.txt # django +attrs==23.2.0 + # via + # -r requirements/base.txt + # openedx-events backports-zoneinfo[tzdata]==0.2.1 ; python_version < "3.9" # via # -c requirements/constraints.txt @@ -30,12 +34,12 @@ binaryornot==0.4.4 # via cookiecutter bleach==6.1.0 # via -r requirements/base.txt -boto3==1.34.83 +boto3==1.34.87 # via # -r requirements/base.txt # fs-s3fs # moto -botocore==1.34.83 +botocore==1.34.87 # via # -r requirements/base.txt # boto3 @@ -43,7 +47,7 @@ botocore==1.34.83 # s3transfer cachetools==5.3.3 # via tox -celery==5.3.6 +celery==5.4.0 # via -r requirements/test.in certifi==2024.2.2 # via @@ -112,6 +116,7 @@ distlib==0.3.8 # edx-submissions # edx-toggles # jsonfield + # openedx-events # openedx-filters # xblock-sdk django-crum==0.7.9 @@ -139,10 +144,13 @@ edx-django-utils==5.12.0 # via # -r requirements/base.txt # edx-toggles + # openedx-events edx-i18n-tools==1.5.0 # via -r requirements/base.txt -edx-opaque-keys==2.5.1 - # via -r requirements/base.txt +edx-opaque-keys[django]==2.5.1 + # via + # -r requirements/base.txt + # openedx-events edx-submissions==3.7.0 # via -r requirements/base.txt edx-toggles==5.2.0 @@ -151,8 +159,12 @@ exceptiongroup==1.2.0 # via pytest factory-boy==3.3.0 # via -r requirements/test.in -faker==24.8.0 +faker==24.11.0 # via factory-boy +fastavro==1.9.4 + # via + # -r requirements/base.txt + # openedx-events filelock==3.13.4 # via # tox @@ -237,7 +249,9 @@ newrelic==9.8.0 # via # -r requirements/base.txt # edx-django-utils -openedx-filters==1.8.0 +openedx-events==9.9.2 + # via -r requirements/base.txt +openedx-filters==1.8.1 # via -r requirements/base.txt packaging==24.0 # via @@ -365,7 +379,7 @@ six==1.16.0 # html5lib # python-dateutil # python-swiftclient -sqlparse==0.4.4 +sqlparse==0.5.0 # via # -r requirements/base.txt # django @@ -414,7 +428,7 @@ vine==5.1.0 # amqp # celery # kombu -virtualenv==20.25.1 +virtualenv==20.25.3 # via tox voluptuous==0.14.2 # via diff --git a/requirements/tox.txt b/requirements/tox.txt index 246985fb98..9d7ebcf2c2 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -34,5 +34,5 @@ tomli==2.0.1 # tox tox==4.14.2 # via -r requirements/tox.in -virtualenv==20.25.1 +virtualenv==20.25.3 # via tox