From c8fc9c211fb279e433da1789af3af2b4cf865487 Mon Sep 17 00:00:00 2001 From: Ahtisham Shahid Date: Wed, 27 Mar 2024 21:41:56 +0500 Subject: [PATCH] feat: added openedx-events package s: added notification for staff s: added notification for staff s: updated version s: updated version fix: resolved failing tests fix: resolved failing tests feat: added unit test for notification util feat: added unit test for notification util --- openassessment/__init__.py | 2 +- .../apis/assessments/self_assessment_api.py | 2 +- .../apis/submissions/submissions_actions.py | 42 +++++++++---- .../xblock/test/test_grade_explanation.py | 5 +- .../xblock/test/test_notifications.py | 60 +++++++++++++++++++ openassessment/xblock/utils/notifications.py | 33 ++++++++++ package.json | 2 +- requirements/base.in | 1 + requirements/base.txt | 10 ++-- requirements/common_constraints.txt | 1 + requirements/pip-tools.txt | 11 +--- requirements/pip.txt | 2 +- 12 files changed, 137 insertions(+), 34 deletions(-) create mode 100644 openassessment/xblock/test/test_notifications.py create mode 100644 openassessment/xblock/utils/notifications.py diff --git a/openassessment/__init__.py b/openassessment/__init__.py index 5413861a57..72bdf58d69 100644 --- a/openassessment/__init__.py +++ b/openassessment/__init__.py @@ -2,4 +2,4 @@ Initialization Information for Open Assessment Module """ -__version__ = '6.6.2' +__version__ = '6.7.0' diff --git a/openassessment/xblock/apis/assessments/self_assessment_api.py b/openassessment/xblock/apis/assessments/self_assessment_api.py index 334731586a..c3dda3f2e1 100644 --- a/openassessment/xblock/apis/assessments/self_assessment_api.py +++ b/openassessment/xblock/apis/assessments/self_assessment_api.py @@ -15,7 +15,6 @@ create_submission_dict, ) - logger = logging.getLogger(__name__) @@ -129,6 +128,7 @@ def self_assess( config_data.publish_assessment_event("openassessmentblock.self_assess", assessment) # After we've created the self-assessment, we need to update the workflow. workflow_data.update_workflow_status() + except ( SelfAssessmentRequestError, AssessmentWorkflowRequestError, diff --git a/openassessment/xblock/apis/submissions/submissions_actions.py b/openassessment/xblock/apis/submissions/submissions_actions.py index 245fd459fe..f6ee18fa7b 100644 --- a/openassessment/xblock/apis/submissions/submissions_actions.py +++ b/openassessment/xblock/apis/submissions/submissions_actions.py @@ -5,6 +5,8 @@ import json import logging import os + +from opaque_keys.edx.keys import CourseKey from submissions.api import Submission, SubmissionError, SubmissionRequestError from openassessment.fileupload.exceptions import FileUploadError @@ -22,12 +24,14 @@ SubmitInternalError, UnsupportedFileTypeException ) +from openassessment.xblock.utils.notifications import send_staff_notification from openassessment.xblock.utils.validation import validate_submission from openassessment.xblock.utils.data_conversion import ( format_files_for_submission, prepare_submission_for_serialization, ) + logger = logging.getLogger(__name__) # pylint: disable=invalid-name @@ -115,9 +119,9 @@ def submit(text_responses, block_config_data, block_submission_data, block_workf logger.exception(msg) raise except ( - SubmissionError, - AssessmentWorkflowError, - NoTeamToCreateSubmissionForError, + SubmissionError, + AssessmentWorkflowError, + NoTeamToCreateSubmissionForError, ) as e: msg = ( "An unknown error occurred while submitting " @@ -130,11 +134,11 @@ def submit(text_responses, block_config_data, block_submission_data, block_workf def create_submission( - student_item_dict, - submission_data, - block_config_data, - block_submission_data, - block_workflow_data + student_item_dict, + submission_data, + block_config_data, + block_submission_data, + block_workflow_data ): """Creates submission for the submitted assessment response or a list for a team assessment.""" # Import is placed here to avoid model import at project startup. @@ -157,6 +161,18 @@ def create_submission( # Set student submission_uuid block_config_data._block.submission_uuid = submission["uuid"] # pylint: disable=protected-access + has_staff_step = block_workflow_data.workflow_requirements.get('staff', {}).get('required', False) + + if has_staff_step: + if block_config_data.course: + course_id = block_config_data.course.id + else: + course_id = CourseKey.from_string(student_item_dict.get("course_id")) + send_staff_notification( + course_id, + student_item_dict.get("item_id"), + block_config_data._block.display_name # pylint: disable=protected-access + ) # Emit analytics event... block_config_data.publish_event( @@ -174,11 +190,11 @@ def create_submission( def create_team_submission( - student_item_dict, - submission_data, - block_config_data, - block_submission_data, - block_workflow_data + student_item_dict, + submission_data, + block_config_data, + block_submission_data, + block_workflow_data ): """A student submitting for a team should generate matching submissions for every member of the team.""" diff --git a/openassessment/xblock/test/test_grade_explanation.py b/openassessment/xblock/test/test_grade_explanation.py index 3a9e4aac97..c7382e1a7f 100644 --- a/openassessment/xblock/test/test_grade_explanation.py +++ b/openassessment/xblock/test/test_grade_explanation.py @@ -52,8 +52,9 @@ def test_render_grade_explanation_self_only(self, xblock, assessment_score_prior self.assertIn(self.second_sentences_options["self"], resp.decode('utf-8')) @scenario('data/grade_scenario_staff_only.xml', user_id='Bernard') + @patch('openassessment.xblock.apis.submissions.submissions_actions.send_staff_notification') @data(*assessment_score_priority) - def test_render_explanation_grade_staff_only(self, xblock, assessment_score_priority): + def test_render_explanation_grade_staff_only(self, xblock, assessment_score_priority, mock_send_staff_notification): with patch( 'openassessment.workflow.models.AssessmentWorkflow.ASSESSMENT_SCORE_PRIORITY', assessment_score_priority @@ -64,6 +65,8 @@ def test_render_explanation_grade_staff_only(self, xblock, assessment_score_prio self.assertIn(self.second_sentences_options["staff"], resp.decode('utf-8')) + mock_send_staff_notification.assert_called_once() + @scenario('data/grade_scenario_peer_only.xml', user_id='Bernard') @data(*assessment_score_priority) def test_render_grade_explanation_peer_only(self, xblock, assessment_score_priority): diff --git a/openassessment/xblock/test/test_notifications.py b/openassessment/xblock/test/test_notifications.py new file mode 100644 index 0000000000..001d46d250 --- /dev/null +++ b/openassessment/xblock/test/test_notifications.py @@ -0,0 +1,60 @@ +""" +Unit test for notification util +""" +import unittest +from unittest.mock import patch + +from openassessment.xblock.utils.notifications import send_staff_notification + + +class TestSendStaffNotification(unittest.TestCase): + """ + Test for send_staff_notification function + """ + @patch('openassessment.xblock.utils.notifications.COURSE_NOTIFICATION_REQUESTED.send_event') + def test_send_staff_notification(self, mock_send_event): + """ + Test send_staff_notification function + """ + # Mocked data + course_id = 'course_id' + problem_id = 'problem_id' + ora_name = 'ora_name' + + # Call the function + send_staff_notification(course_id, problem_id, ora_name) + + # Assertions + mock_send_event.assert_called_once() + args, kwargs = mock_send_event.call_args + notification_data = kwargs['course_notification_data'] + + # Check if CourseNotificationData is properly initialized + self.assertEqual(notification_data.course_key, course_id) + self.assertEqual(notification_data.content_context['ora_name'], ora_name) + self.assertEqual(notification_data.notification_type, 'ora_staff_notification') + self.assertEqual(notification_data.content_url, f"/{problem_id}") + self.assertEqual(notification_data.app_name, "ora") + self.assertEqual(notification_data.audience_filters['course_roles'], ['staff', 'instructor']) + + @patch('openassessment.xblock.utils.notifications.logger.error') + @patch('openassessment.xblock.utils.notifications.COURSE_NOTIFICATION_REQUESTED.send_event') + def test_send_staff_notification_error_logging(self, mock_send_event, mock_logger_error): + """ + Test send_staff_notification function when an exception is raised + """ + # Mocked data + course_id = 'course_id' + problem_id = 'problem_id' + ora_name = 'ora_name' + + # Mock exception + mock_exception = Exception('Test exception') + + mock_send_event.side_effect = mock_exception + + # Call the function + send_staff_notification(course_id, problem_id, ora_name) + + # Assertions + mock_logger_error.assert_called_once_with(f"Error while sending ora staff notification: {mock_exception}") diff --git a/openassessment/xblock/utils/notifications.py b/openassessment/xblock/utils/notifications.py new file mode 100644 index 0000000000..e13275e38b --- /dev/null +++ b/openassessment/xblock/utils/notifications.py @@ -0,0 +1,33 @@ +""" +This module contains utility functions for sending notifications. +""" +import logging + +from django.conf import settings +from openedx_events.learning.signals import COURSE_NOTIFICATION_REQUESTED +from openedx_events.learning.data import CourseNotificationData + +logger = logging.getLogger(__name__) + + +def send_staff_notification(course_id, problem_id, ora_name): + """ + Send a staff notification for a course + """ + try: + audience_filters = { + 'course_roles': ['staff', 'instructor'] + } + notification_data = CourseNotificationData( + course_key=course_id, + content_context={ + 'ora_name': ora_name + }, + notification_type='ora_staff_notification', + content_url=f"{getattr(settings, 'ORA_GRADING_MICROFRONTEND_URL', '')}/{problem_id}", + app_name="ora", + audience_filters=audience_filters, + ) + COURSE_NOTIFICATION_REQUESTED.send_event(course_notification_data=notification_data) + except Exception as e: + logger.error(f"Error while sending ora staff notification: {e}") diff --git a/package.json b/package.json index bc837b861d..45b9ef8aea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "edx-ora2", - "version": "6.6.2", + "version": "6.7.0", "repository": "https://github.com/openedx/edx-ora2.git", "dependencies": { "@edx/frontend-build": "8.0.6", diff --git a/requirements/base.in b/requirements/base.in index 52da27a3a4..be3c9319c1 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -26,6 +26,7 @@ jsonfield lazy loremipsum lxml +openedx-events path.py python-dateutil pytz diff --git a/requirements/base.txt b/requirements/base.txt index e8c910b83a..1ed9ea5d03 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,8 +1,8 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# make upgrade +# pip-compile requirements/base.in # appdirs==1.4.4 # via fs @@ -197,10 +197,8 @@ stevedore==5.2.0 # edx-opaque-keys text-unidecode==1.3 # via python-slugify -typing-extensions==4.11.0 - # via - # asgiref - # edx-opaque-keys +typing-extensions==4.10.0 + # via edx-opaque-keys urllib3==1.26.18 # via # botocore diff --git a/requirements/common_constraints.txt b/requirements/common_constraints.txt index 8b0c901b10..cf7a810367 100644 --- a/requirements/common_constraints.txt +++ b/requirements/common_constraints.txt @@ -1,4 +1,5 @@ + # A central location for most common version constraints # (across edx repos) for pip-installation. # diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt index 1471eddf1c..c139a398fa 100644 --- a/requirements/pip-tools.txt +++ b/requirements/pip-tools.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # make upgrade @@ -8,8 +8,6 @@ build==1.2.1 # via pip-tools click==8.1.7 # via pip-tools -importlib-metadata==7.1.0 - # via build packaging==24.0 # via build pip-tools==7.4.1 @@ -18,15 +16,8 @@ pyproject-hooks==1.0.0 # via # build # pip-tools -tomli==2.0.1 - # via - # build - # pip-tools - # pyproject-hooks wheel==0.43.0 # via pip-tools -zipp==3.18.1 - # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/requirements/pip.txt b/requirements/pip.txt index e551e29df0..20ac3b91fe 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # make upgrade