def send_xapi_statements(self, lrs_configuration, days): """ Send xAPI analytics data of the enterprise learners to the given LRS. Arguments: lrs_configuration (XAPILRSConfiguration): Configuration object containing LRS configurations of the LRS where to send xAPI learner analytics. days (int): Include course enrollment of this number of days. """ persistent_course_grades = self.get_course_completions( lrs_configuration.enterprise_customer, days) users = self.prefetch_users(persistent_course_grades) course_overviews = self.prefetch_courses(persistent_course_grades) for persistent_course_grade in persistent_course_grades: try: user = users.get(persistent_course_grade.user_id) course_overview = course_overviews.get( persistent_course_grade.course_id) course_grade = CourseGradeFactory().read( user, course_key=persistent_course_grade.course_id) send_course_completion_statement(lrs_configuration, user, course_overview, course_grade) except ClientError: LOGGER.exception( 'Client error while sending course completion to xAPI for' ' enterprise customer {enterprise_customer}.'.format( enterprise_customer=lrs_configuration. enterprise_customer.name))
def send_xapi_statements(self, lrs_configuration, days): """ Send xAPI analytics data of the enterprise learners to the given LRS. Arguments: lrs_configuration (XAPILRSConfiguration): Configuration object containing LRS configurations of the LRS where to send xAPI learner analytics. days (int): Include course enrollment of this number of days. """ persistent_course_grades = self.get_course_completions( lrs_configuration.enterprise_customer, days) users = self.prefetch_users(persistent_course_grades) course_overviews = self.prefetch_courses(persistent_course_grades) for persistent_course_grade in persistent_course_grades: error_message = None user = users.get(persistent_course_grade.user_id) course_overview = course_overviews.get( persistent_course_grade.course_id) course_grade = CourseGradeFactory().read( user, course_key=persistent_course_grade.course_id) xapi_transmission_queryset = XAPILearnerDataTransmissionAudit.objects.filter( user=user, course_id=persistent_course_grade.course_id) if not xapi_transmission_queryset.exists(): LOGGER.warning( 'XAPILearnerDataTransmissionAudit object does not exist for user: {username}, course: ' '{course_id} so skipping the course completion statement to xapi.' ) continue try: send_course_completion_statement(lrs_configuration, user, course_overview, course_grade) except ClientError: error_message = 'Client error while sending course completion to xAPI for ' \ 'enterprise customer: {enterprise_customer}, user: {username} ' \ 'and course: {course_id}'.format( enterprise_customer=lrs_configuration.enterprise_customer.name, username=user.username if user else '', course_id=persistent_course_grade.course_id ) LOGGER.exception(error_message) status = 500 else: LOGGER.info( 'Successfully sent course completion to xAPI for user: {username} for course: {course_id}' .format(username=user.username if user else '', course_id=persistent_course_grade.course_id)) status = 200 fields = {'status': status, 'error_message': error_message} if status == 200: fields.update({ 'grade': course_grade.percent, 'timestamp': course_grade.passed_timestamp }) xapi_transmission_queryset.update(**fields)
def test_send_course_completion_statement(self): """ Verify that send_course_completion_statement sends xAPI statement to LRS. """ send_course_completion_statement( self.x_api_lrs_config, self.user, self.course_overview, self.course_grade, ) self.x_api_client.lrs.save_statement.assert_called() # pylint: disable=no-member
def test_send_course_completion_statement(self, mock_get_user_social_auth, *args): # pylint: disable=unused-argument """ Verify that send_course_completion_statement sends xAPI statement to LRS. """ mock_get_user_social_auth.return_value = self.mock_social_auth send_course_completion_statement(self.x_api_lrs_config, self.user, self.course_overview, self.course_grade, ContentType.COURSE, { 'status': 500, 'error_message': None }) self.x_api_client.lrs.save_statement.assert_called() # pylint: disable=no-member
def test_send_course_completion_statement(self, mock_catalog_integration, *args): # pylint: disable=unused-argument """ Verify that send_course_completion_statement sends xAPI statement to LRS. """ mock_integration_config = mock.Mock(enabled=True) mock_catalog_integration.current.return_value = mock_integration_config send_course_completion_statement( self.x_api_lrs_config, self.user, self.course_overview, self.course_grade, ) self.x_api_client.lrs.save_statement.assert_called() # pylint: disable=no-member
def send_xapi_statements(self, lrs_configuration, days): """ Send xAPI analytics data of the enterprise learners to the given LRS. Arguments: lrs_configuration (XAPILRSConfiguration): Configuration object containing LRS configurations of the LRS where to send xAPI learner analytics. days (Numeric): Deprecated. Original implementation utilized a "days" parameter to limit the number of enrollments transmitted, but this proved to be more problematic than helpful. """ enterprise_course_enrollments = self.get_enterprise_course_enrollments(lrs_configuration.enterprise_customer) enterprise_enrollment_ids = self.get_enterprise_enrollment_ids(enterprise_course_enrollments) xapi_transmission_queryset = self.get_xapi_transmission_queryset(enterprise_enrollment_ids) pertinent_enrollment_ids = self.get_pertinent_enrollment_ids(xapi_transmission_queryset) pertinent_enrollments = self.get_pertinent_enrollments(enterprise_course_enrollments, pertinent_enrollment_ids) enrollment_grades = self.get_course_completions(pertinent_enrollments) users = self.prefetch_users(enrollment_grades) course_overviews = self.prefetch_courses(enrollment_grades) course_catalog_client = get_course_catalog_api_service_client(site=lrs_configuration.enterprise_customer.site) for xapi_transmission in xapi_transmission_queryset: object_type = self.get_object_type(xapi_transmission) try: course_grade = enrollment_grades[xapi_transmission.enterprise_course_enrollment_id] except KeyError: continue user = users.get(course_grade.user_id) courserun_id = six.text_type(course_grade.course_id) course_overview = course_overviews.get(course_grade.course_id) course_run_identifiers = course_catalog_client.get_course_run_identifiers(courserun_id) course_overview.course_key = course_run_identifiers['course_key'] course_overview.course_uuid = course_run_identifiers['course_uuid'] default_error_message = 'Days argument has been deprecated. Value: {days}'.format(days=days) response_fields = {'status': 500, 'error_message': default_error_message} response_fields = send_course_completion_statement( lrs_configuration, user, course_overview, course_grade, object_type, response_fields ) if is_success_response(response_fields): self.save_xapi_learner_data_transmission_audit( xapi_transmission, course_grade.percent_grade, 1, course_grade.passed_timestamp, response_fields.get('status'), response_fields.get('error_message') )
def send_xapi_statements(self, lrs_configuration, days): """ Send xAPI analytics data of the enterprise learners to the given LRS. Arguments: lrs_configuration (XAPILRSConfiguration): Configuration object containing LRS configurations of the LRS where to send xAPI learner analytics. days (int): Include course enrollment of this number of days. """ persistent_course_grades = self.get_course_completions( lrs_configuration.enterprise_customer, days) users = self.prefetch_users(persistent_course_grades) course_overviews = self.prefetch_courses(persistent_course_grades) LOGGER.info( '[Integrated Channel][xAPI] Found %s course completion for enterprise customer: [%s] during last %s days', len(persistent_course_grades), lrs_configuration.enterprise_customer, days, ) for persistent_course_grade in persistent_course_grades: error_message = None user = users.get(persistent_course_grade.user_id) course_overview = course_overviews.get( persistent_course_grade.course_id) course_grade = CourseGradeFactory().read( user, course_key=persistent_course_grade.course_id) xapi_transmission_queryset = XAPILearnerDataTransmissionAudit.objects.filter( user=user, course_id=persistent_course_grade.course_id, course_completed=0) if not xapi_transmission_queryset.exists(): LOGGER.warning( 'XAPILearnerDataTransmissionAudit object does not exist for enterprise customer: ' '{enterprise_customer}, user: {username}, course: {course_id}. Skipping transmission ' 'of course completion statement to the configured LRS endpoint. This is likely because ' 'a corresponding course enrollment statement has not yet been transmitted.' .format( enterprise_customer=lrs_configuration. enterprise_customer.name, username=user.username if user else 'User Unavailable', course_id=persistent_course_grade.course_id)) continue fields = {'status': 500, 'error_message': None} try: response = send_course_completion_statement( lrs_configuration, user, course_overview, course_grade) except ClientError: error_message = 'Client error while sending course completion to xAPI for ' \ 'enterprise customer: {enterprise_customer}, user: {username} ' \ 'and course: {course_id}'.format( enterprise_customer=lrs_configuration.enterprise_customer.name, username=user.username if user else 'User Unavailable', course_id=persistent_course_grade.course_id ) LOGGER.exception(error_message) fields.update({'error_message': error_message}) else: status = response.response.status fields.update({'status': status}) if response.success: LOGGER.info( 'Successfully sent xAPI course completion for user: {username} for course: {course_id}' .format(username=user.username if user else 'User Unavailable', course_id=persistent_course_grade.course_id)) fields.update({ 'grade': course_grade.percent, 'course_completed': 1, 'completed_timestamp': persistent_course_grade.modified }) else: LOGGER.warning( 'Unexpected xAPI response received for user: {username} for course: {course_id}. Please ' 'reveiew the xAPI learner data transmission audit log for details' .format(username=user.username if user else 'User Unavailable', course_id=persistent_course_grade.course_id)) fields.update({'error_message': response.data}) xapi_transmission_queryset.update(**fields)