def _create_override(self, request_user, subsection_grade_model, **override_data): """ Helper method to create a `PersistentSubsectionGradeOverride` object and send a `SUBSECTION_OVERRIDE_CHANGED` signal. """ override = PersistentSubsectionGradeOverride.update_or_create_override( requesting_user=request_user, subsection_grade_model=subsection_grade_model, feature=grades_constants.GradeOverrideFeatureEnum.gradebook, **override_data ) set_event_transaction_type(grades_events.SUBSECTION_GRADE_CALCULATED) create_new_event_transaction_id() recalculate_subsection_grade_v3.apply( kwargs=dict( user_id=subsection_grade_model.user_id, anonymous_user_id=None, course_id=text_type(subsection_grade_model.course_id), usage_id=text_type(subsection_grade_model.usage_key), only_if_higher=False, expected_modified_time=to_timestamp(override.modified), score_deleted=False, event_transaction_id=six.text_type(get_event_transaction_id()), event_transaction_type=six.text_type(get_event_transaction_type()), score_db_table=grades_constants.ScoreDatabaseTableEnum.overrides, force_update_subsections=True, ) ) # Emit events to let our tracking system to know we updated subsection grade grades_events.subsection_grade_calculated(subsection_grade_model) return override
def grade_updated(**kwargs): """ Emits the appropriate grade-related event after checking for which event-transaction is active. Emits a problem.submitted event only if there is no current event transaction type, i.e. we have not reached this point in the code via an outer event type (such as problem.rescored or score_overridden). """ root_type = get_event_transaction_type() if not root_type: root_id = get_event_transaction_id() if not root_id: root_id = create_new_event_transaction_id() set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE) tracker.emit( six.text_type(PROBLEM_SUBMITTED_EVENT_TYPE), { 'user_id': six.text_type(kwargs['user_id']), 'course_id': six.text_type(kwargs['course_id']), 'problem_id': six.text_type(kwargs['usage_id']), 'event_transaction_id': six.text_type(root_id), 'event_transaction_type': six.text_type(PROBLEM_SUBMITTED_EVENT_TYPE), 'weighted_earned': kwargs.get('weighted_earned'), 'weighted_possible': kwargs.get('weighted_possible'), } ) elif root_type in [GRADES_RESCORE_EVENT_TYPE, GRADES_OVERRIDE_EVENT_TYPE]: current_user = get_current_user() instructor_id = getattr(current_user, 'id', None) tracker.emit( six.text_type(root_type), { 'course_id': six.text_type(kwargs['course_id']), 'user_id': six.text_type(kwargs['user_id']), 'problem_id': six.text_type(kwargs['usage_id']), 'new_weighted_earned': kwargs.get('weighted_earned'), 'new_weighted_possible': kwargs.get('weighted_possible'), 'only_if_higher': kwargs.get('only_if_higher'), 'instructor_id': six.text_type(instructor_id), 'event_transaction_id': six.text_type(get_event_transaction_id()), 'event_transaction_type': six.text_type(root_type), } ) elif root_type in [SUBSECTION_OVERRIDE_EVENT_TYPE]: tracker.emit( six.text_type(root_type), { 'course_id': six.text_type(kwargs['course_id']), 'user_id': six.text_type(kwargs['user_id']), 'problem_id': six.text_type(kwargs['usage_id']), 'only_if_higher': kwargs.get('only_if_higher'), 'override_deleted': kwargs.get('score_deleted', False), 'event_transaction_id': six.text_type(get_event_transaction_id()), 'event_transaction_type': six.text_type(root_type), } )
def subsection_grade_calculated(subsection_grade): """ Emits an edx.grades.subsection.grade_calculated event with data from the passed subsection_grade. """ event_name = SUBSECTION_GRADE_CALCULATED context = contexts.course_context_from_course_id(subsection_grade.course_id) # TODO (AN-6134): remove this context manager with tracker.get_tracker().context(event_name, context): tracker.emit( event_name, { 'user_id': unicode(subsection_grade.user_id), 'course_id': unicode(subsection_grade.course_id), 'block_id': unicode(subsection_grade.usage_key), 'course_version': unicode(subsection_grade.course_version), 'weighted_total_earned': subsection_grade.earned_all, 'weighted_total_possible': subsection_grade.possible_all, 'weighted_graded_earned': subsection_grade.earned_graded, 'weighted_graded_possible': subsection_grade.possible_graded, 'first_attempted': unicode(subsection_grade.first_attempted), 'subtree_edited_timestamp': unicode(subsection_grade.subtree_edited_timestamp), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(get_event_transaction_type()), 'visible_blocks_hash': unicode(subsection_grade.visible_blocks_id), } )
def subsection_grade_calculated(subsection_grade): """ Emits an edx.grades.subsection.grade_calculated event with data from the passed subsection_grade. """ event_name = SUBSECTION_GRADE_CALCULATED context = contexts.course_context_from_course_id(subsection_grade.course_id) # TODO (AN-6134): remove this context manager with tracker.get_tracker().context(event_name, context): tracker.emit( event_name, { 'user_id': six.text_type(subsection_grade.user_id), 'course_id': six.text_type(subsection_grade.course_id), 'block_id': six.text_type(subsection_grade.usage_key), 'course_version': six.text_type(subsection_grade.course_version), 'weighted_total_earned': subsection_grade.earned_all, 'weighted_total_possible': subsection_grade.possible_all, 'weighted_graded_earned': subsection_grade.earned_graded, 'weighted_graded_possible': subsection_grade.possible_graded, 'first_attempted': six.text_type(subsection_grade.first_attempted), 'subtree_edited_timestamp': six.text_type(subsection_grade.subtree_edited_timestamp), 'event_transaction_id': six.text_type(get_event_transaction_id()), 'event_transaction_type': six.text_type(get_event_transaction_type()), 'visible_blocks_hash': six.text_type(subsection_grade.visible_blocks_id), } )
def _emit_problem_submitted_event(kwargs): """ Emits a problem submitted event only if there is no current event transaction type, i.e. we have not reached this point in the code via a rescore or student state deletion. """ root_type = get_event_transaction_type() if not root_type: root_id = get_event_transaction_id() if not root_id: root_id = create_new_event_transaction_id() set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE) tracker.emit( unicode(PROBLEM_SUBMITTED_EVENT_TYPE), { 'user_id': unicode(kwargs['user_id']), 'course_id': unicode(kwargs['course_id']), 'problem_id': unicode(kwargs['usage_id']), 'event_transaction_id': unicode(root_id), 'event_transaction_type': unicode(PROBLEM_SUBMITTED_EVENT_TYPE), 'weighted_earned': kwargs.get('weighted_earned'), 'weighted_possible': kwargs.get('weighted_possible'), } )
def grade_updated(**kwargs): """ Emits the appropriate grade-related event after checking for which event-transaction is active. Emits a problem.submitted event only if there is no current event transaction type, i.e. we have not reached this point in the code via an outer event type (such as problem.rescored or score_overridden). """ root_type = get_event_transaction_type() if not root_type: root_id = get_event_transaction_id() if not root_id: root_id = create_new_event_transaction_id() set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE) tracker.emit( unicode(PROBLEM_SUBMITTED_EVENT_TYPE), { 'user_id': unicode(kwargs['user_id']), 'course_id': unicode(kwargs['course_id']), 'problem_id': unicode(kwargs['usage_id']), 'event_transaction_id': unicode(root_id), 'event_transaction_type': unicode(PROBLEM_SUBMITTED_EVENT_TYPE), 'weighted_earned': kwargs.get('weighted_earned'), 'weighted_possible': kwargs.get('weighted_possible'), } ) elif root_type in [GRADES_RESCORE_EVENT_TYPE, GRADES_OVERRIDE_EVENT_TYPE]: current_user = get_current_user() instructor_id = getattr(current_user, 'id', None) tracker.emit( unicode(root_type), { 'course_id': unicode(kwargs['course_id']), 'user_id': unicode(kwargs['user_id']), 'problem_id': unicode(kwargs['usage_id']), 'new_weighted_earned': kwargs.get('weighted_earned'), 'new_weighted_possible': kwargs.get('weighted_possible'), 'only_if_higher': kwargs.get('only_if_higher'), 'instructor_id': unicode(instructor_id), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(root_type), } ) elif root_type in [SUBSECTION_OVERRIDE_EVENT_TYPE]: tracker.emit( unicode(root_type), { 'course_id': unicode(kwargs['course_id']), 'user_id': unicode(kwargs['user_id']), 'problem_id': unicode(kwargs['usage_id']), 'only_if_higher': kwargs.get('only_if_higher'), 'override_deleted': kwargs.get('score_deleted', False), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(root_type), } )
def course_grade_calculated(course_grade): """ Emits an edx.grades.course.grade_calculated event with data from the passed course_grade. """ event_name = COURSE_GRADE_CALCULATED context = contexts.course_context_from_course_id(course_grade.course_id) # TODO (AN-6134): remove this context manager with tracker.get_tracker().context(event_name, context): tracker.emit( event_name, { 'user_id': unicode(course_grade.user_id), 'course_id': unicode(course_grade.course_id), 'course_version': unicode(course_grade.course_version), 'percent_grade': course_grade.percent_grade, 'letter_grade': unicode(course_grade.letter_grade), 'course_edited_timestamp': unicode(course_grade.course_edited_timestamp), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(get_event_transaction_type()), 'grading_policy_hash': unicode(course_grade.grading_policy_hash), })
def _create_override(self, request_user, subsection_grade_model, **override_data): """ Helper method to create a `PersistentSubsectionGradeOverride` object and send a `SUBSECTION_OVERRIDE_CHANGED` signal. """ override = PersistentSubsectionGradeOverride.update_or_create_override( requesting_user=request_user, subsection_grade_model=subsection_grade_model, feature=PersistentSubsectionGradeOverrideHistory.GRADEBOOK, **override_data) set_event_transaction_type(SUBSECTION_GRADE_CALCULATED) create_new_event_transaction_id() recalculate_subsection_grade_v3.apply(kwargs=dict( user_id=subsection_grade_model.user_id, anonymous_user_id=None, course_id=text_type(subsection_grade_model.course_id), usage_id=text_type(subsection_grade_model.usage_key), only_if_higher=False, expected_modified_time=to_timestamp(override.modified), score_deleted=False, event_transaction_id=unicode(get_event_transaction_id()), event_transaction_type=unicode(get_event_transaction_type()), score_db_table=ScoreDatabaseTableEnum.overrides, force_update_subsections=True, )) # Emit events to let our tracking system to know we updated subsection grade subsection_grade_calculated(subsection_grade_model) return override
def _emit_event(kwargs): """ Emits a problem submitted event only if there is no current event transaction type, i.e. we have not reached this point in the code via a rescore or student state deletion. If the event transaction type has already been set and the transacation is a rescore, emits a problem rescored event. """ root_type = get_event_transaction_type() if not root_type: root_id = get_event_transaction_id() if not root_id: root_id = create_new_event_transaction_id() set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE) tracker.emit( unicode(PROBLEM_SUBMITTED_EVENT_TYPE), { 'user_id': unicode(kwargs['user_id']), 'course_id': unicode(kwargs['course_id']), 'problem_id': unicode(kwargs['usage_id']), 'event_transaction_id': unicode(root_id), 'event_transaction_type': unicode(PROBLEM_SUBMITTED_EVENT_TYPE), 'weighted_earned': kwargs.get('weighted_earned'), 'weighted_possible': kwargs.get('weighted_possible'), } ) if root_type in [GRADES_RESCORE_EVENT_TYPE, GRADES_OVERRIDE_EVENT_TYPE]: current_user = get_current_user() instructor_id = getattr(current_user, 'id', None) tracker.emit( unicode(GRADES_RESCORE_EVENT_TYPE), { 'course_id': unicode(kwargs['course_id']), 'user_id': unicode(kwargs['user_id']), 'problem_id': unicode(kwargs['usage_id']), 'new_weighted_earned': kwargs.get('weighted_earned'), 'new_weighted_possible': kwargs.get('weighted_possible'), 'only_if_higher': kwargs.get('only_if_higher'), 'instructor_id': unicode(instructor_id), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(root_type), } ) if root_type in [SUBSECTION_OVERRIDE_EVENT_TYPE]: tracker.emit( unicode(SUBSECTION_OVERRIDE_EVENT_TYPE), { 'course_id': unicode(kwargs['course_id']), 'user_id': unicode(kwargs['user_id']), 'problem_id': unicode(kwargs['usage_id']), 'only_if_higher': kwargs.get('only_if_higher'), 'override_deleted': kwargs.get('score_deleted', False), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(root_type), } )
def _emit_grade_calculated_event(grade): """ Emits an edx.grades.course.grade_calculated event with data from the passed grade. """ # TODO: remove this context manager after completion of AN-6134 event_name = u'edx.grades.course.grade_calculated' context = contexts.course_context_from_course_id(grade.course_id) with tracker.get_tracker().context(event_name, context): tracker.emit( event_name, { 'user_id': unicode(grade.user_id), 'course_id': unicode(grade.course_id), 'course_version': unicode(grade.course_version), 'percent_grade': grade.percent_grade, 'letter_grade': unicode(grade.letter_grade), 'course_edited_timestamp': unicode(grade.course_edited_timestamp), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(get_event_transaction_type()), 'grading_policy_hash': unicode(grade.grading_policy_hash), })
def _emit_grade_calculated_event(grade): """ Emits an edx.grades.subsection.grade_calculated event with data from the passed grade. """ # TODO: remove this context manager after completion of AN-6134 event_name = u'edx.grades.subsection.grade_calculated' context = contexts.course_context_from_course_id(grade.course_id) with tracker.get_tracker().context(event_name, context): tracker.emit( event_name, { 'user_id': unicode(grade.user_id), 'course_id': unicode(grade.course_id), 'block_id': unicode(grade.usage_key), 'course_version': unicode(grade.course_version), 'weighted_total_earned': grade.earned_all, 'weighted_total_possible': grade.possible_all, 'weighted_graded_earned': grade.earned_graded, 'weighted_graded_possible': grade.possible_graded, 'first_attempted': unicode(grade.first_attempted), 'subtree_edited_timestamp': unicode(grade.subtree_edited_timestamp), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(get_event_transaction_type()), 'visible_blocks_hash': unicode(grade.visible_blocks_id), } )
def _retry_recalculate_subsection_grade( user_id, course_id, usage_id, only_if_higher, expected_modified_time, score_deleted, exc=None, ): """ Calls retry for the recalculate_subsection_grade task with the given inputs. """ recalculate_subsection_grade_v2.retry( kwargs=dict( user_id=user_id, course_id=course_id, usage_id=usage_id, only_if_higher=only_if_higher, expected_modified_time=expected_modified_time, score_deleted=score_deleted, event_transaction_id=unicode(get_event_transaction_id()), event_transaction_type=unicode(get_event_transaction_type()), ), exc=exc, )
def _assert_tracker_emitted_event(self, tracker_mock, grade): """ Helper function to ensure that the mocked event tracker was called with the expected info based on the passed grade. """ tracker_mock.emit.assert_called_with( u'edx.grades.subsection.grade_calculated', { 'user_id': six.text_type(grade.user_id), 'course_id': six.text_type(grade.course_id), 'block_id': six.text_type(grade.usage_key), 'course_version': six.text_type(grade.course_version), 'weighted_total_earned': grade.earned_all, 'weighted_total_possible': grade.possible_all, 'weighted_graded_earned': grade.earned_graded, 'weighted_graded_possible': grade.possible_graded, 'first_attempted': six.text_type(grade.first_attempted), 'subtree_edited_timestamp': six.text_type(grade.subtree_edited_timestamp), 'event_transaction_id': six.text_type(get_event_transaction_id()), 'event_transaction_type': six.text_type(get_event_transaction_type()), 'visible_blocks_hash': six.text_type(grade.visible_blocks_id), })
def _assert_tracker_emitted_event(self, tracker_mock, grade): """ Helper function to ensure that the mocked event tracker was called with the expected info based on the passed grade. """ tracker_mock.emit.assert_called_with( u'edx.grades.course.grade_calculated', { 'user_id': six.text_type(grade.user_id), 'course_id': six.text_type(grade.course_id), 'course_version': six.text_type(grade.course_version), 'percent_grade': grade.percent_grade, 'letter_grade': six.text_type(grade.letter_grade), 'course_edited_timestamp': six.text_type(grade.course_edited_timestamp), 'event_transaction_id': six.text_type(get_event_transaction_id()), 'event_transaction_type': six.text_type(get_event_transaction_type()), 'grading_policy_hash': six.text_type(grade.grading_policy_hash), })
def _retry_recalculate_subsection_grade( user_id, course_id, usage_id, only_if_higher, expected_modified_time, score_deleted, exc=None, ): """ Calls retry for the recalculate_subsection_grade task with the given inputs. """ recalculate_subsection_grade_v2.retry( kwargs=dict( user_id=user_id, course_id=course_id, usage_id=usage_id, only_if_higher=only_if_higher, expected_modified_time=expected_modified_time, score_deleted=score_deleted, event_transaction_id=unicode(get_event_transaction_id()), event_transaction_type=unicode(get_event_transaction_type()), ), exc=exc, )
def enqueue_subsection_update(sender, **kwargs): # pylint: disable=unused-argument """ Handles the PROBLEM_WEIGHTED_SCORE_CHANGED or SUBSECTION_OVERRIDE_CHANGED signals by enqueueing a subsection update operation to occur asynchronously. """ events.grade_updated(**kwargs) context_key = LearningContextKey.from_string(kwargs['course_id']) if not context_key.is_course: return # If it's not a course, it has no subsections, so skip the subsection grading update recalculate_subsection_grade_v3.apply_async( kwargs=dict( user_id=kwargs['user_id'], anonymous_user_id=kwargs.get('anonymous_user_id'), course_id=kwargs['course_id'], usage_id=kwargs['usage_id'], only_if_higher=kwargs.get('only_if_higher'), expected_modified_time=to_timestamp(kwargs['modified']), score_deleted=kwargs.get('score_deleted', False), event_transaction_id=six.text_type(get_event_transaction_id()), event_transaction_type=six.text_type(get_event_transaction_type()), score_db_table=kwargs['score_db_table'], force_update_subsections=kwargs.get('force_update_subsections', False), ), countdown=RECALCULATE_GRADE_DELAY_SECONDS, )
def _emit_problem_submitted_event(kwargs): """ Emits a problem submitted event only if there is no current event transaction type, i.e. we have not reached this point in the code via a rescore or student state deletion. """ root_type = get_event_transaction_type() if not root_type: root_id = get_event_transaction_id() if not root_id: root_id = create_new_event_transaction_id() set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE) tracker.emit( unicode(PROBLEM_SUBMITTED_EVENT_TYPE), { 'user_id': unicode(kwargs['user_id']), 'course_id': unicode(kwargs['course_id']), 'problem_id': unicode(kwargs['usage_id']), 'event_transaction_id': unicode(root_id), 'event_transaction_type': unicode(PROBLEM_SUBMITTED_EVENT_TYPE), 'weighted_earned': kwargs.get('weighted_earned'), 'weighted_possible': kwargs.get('weighted_possible'), } )
def enqueue_subsection_update(sender, **kwargs): # pylint: disable=unused-argument """ Handles the PROBLEM_WEIGHTED_SCORE_CHANGED signal by enqueueing a subsection update operation to occur asynchronously. """ _emit_problem_submitted_event(kwargs) result = recalculate_subsection_grade_v3.apply_async( kwargs=dict( user_id=kwargs['user_id'], anonymous_user_id=kwargs.get('anonymous_user_id'), course_id=kwargs['course_id'], usage_id=kwargs['usage_id'], only_if_higher=kwargs.get('only_if_higher'), expected_modified_time=to_timestamp(kwargs['modified']), score_deleted=kwargs.get('score_deleted', False), event_transaction_id=unicode(get_event_transaction_id()), event_transaction_type=unicode(get_event_transaction_type()), score_db_table=kwargs['score_db_table'], ), countdown=RECALCULATE_GRADE_DELAY, ) log.info( u'Grades: Request async calculation of subsection grades with args: {}. Task [{}]'.format( ', '.join('{}:{}'.format(arg, kwargs[arg]) for arg in sorted(kwargs)), getattr(result, 'id', 'N/A'), ) )
def enqueue_subsection_update(sender, **kwargs): # pylint: disable=unused-argument """ Handles the PROBLEM_WEIGHTED_SCORE_CHANGED signal by enqueueing a subsection update operation to occur asynchronously. """ _emit_problem_submitted_event(kwargs) result = recalculate_subsection_grade_v3.apply_async( kwargs=dict( user_id=kwargs['user_id'], anonymous_user_id=kwargs.get('anonymous_user_id'), course_id=kwargs['course_id'], usage_id=kwargs['usage_id'], only_if_higher=kwargs.get('only_if_higher'), expected_modified_time=to_timestamp(kwargs['modified']), score_deleted=kwargs.get('score_deleted', False), event_transaction_id=unicode(get_event_transaction_id()), event_transaction_type=unicode(get_event_transaction_type()), score_db_table=kwargs['score_db_table'], ), countdown=RECALCULATE_GRADE_DELAY, ) log.info( u'Grades: Request async calculation of subsection grades with args: {}. Task [{}]'.format( ', '.join('{}:{}'.format(arg, kwargs[arg]) for arg in sorted(kwargs)), getattr(result, 'id', 'N/A'), ) )
def _emit_grade_calculated_event(grade): """ Emits an edx.grades.subsection.grade_calculated event with data from the passed grade. """ # TODO: remove this context manager after completion of AN-6134 event_name = u'edx.grades.subsection.grade_calculated' context = contexts.course_context_from_course_id(grade.course_id) with tracker.get_tracker().context(event_name, context): tracker.emit( event_name, { 'user_id': unicode(grade.user_id), 'course_id': unicode(grade.course_id), 'block_id': unicode(grade.usage_key), 'course_version': unicode(grade.course_version), 'weighted_total_earned': grade.earned_all, 'weighted_total_possible': grade.possible_all, 'weighted_graded_earned': grade.earned_graded, 'weighted_graded_possible': grade.possible_graded, 'first_attempted': unicode(grade.first_attempted), 'subtree_edited_timestamp': unicode(grade.subtree_edited_timestamp), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(get_event_transaction_type()), 'visible_blocks_hash': unicode(grade.visible_blocks_id), } )
def handle_grading_policy_changed(sender, **kwargs): # pylint: disable=unused-argument """ Receives signal and kicks off celery task to recalculate grades """ course_key = kwargs.get('course_key') result = compute_all_grades_for_course.apply_async( course_key=course_key, event_transaction_id=get_event_transaction_id(), event_transaction_type=get_event_transaction_type(), ) log.info("Grades: Created {task_name}[{task_id}] with arguments {kwargs}".format( task_name=compute_all_grades_for_course.name, task_id=result.task_id, kwargs=kwargs, ))
def handle_grading_policy_changed(sender, **kwargs): # pylint: disable=unused-argument """ Receives signal and kicks off celery task to recalculate grades """ kwargs = { 'course_key': unicode(kwargs.get('course_key')), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(get_event_transaction_type()), } result = compute_all_grades_for_course.apply_async(kwargs=kwargs, countdown=GRADING_POLICY_COUNTDOWN_SECONDS) log.info("Grades: Created {task_name}[{task_id}] with arguments {kwargs}".format( task_name=compute_all_grades_for_course.name, task_id=result.task_id, kwargs=kwargs, ))
def handle_grading_policy_changed(sender, **kwargs): # pylint: disable=unused-argument """ Receives signal and kicks off celery task to recalculate grades """ kwargs = { 'course_key': unicode(kwargs.get('course_key')), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(get_event_transaction_type()), } result = compute_all_grades_for_course.apply_async(kwargs=kwargs) log.info("Grades: Created {task_name}[{task_id}] with arguments {kwargs}".format( task_name=compute_all_grades_for_course.name, task_id=result.task_id, kwargs=kwargs, ))
def _emit_event(kwargs): """ Emits a problem submitted event only if there is no current event transaction type, i.e. we have not reached this point in the code via a rescore or student state deletion. If the event transaction type has already been set and the transacation is a rescore, emits a problem rescored event. """ root_type = get_event_transaction_type() if not root_type: root_id = get_event_transaction_id() if not root_id: root_id = create_new_event_transaction_id() set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE) tracker.emit( unicode(PROBLEM_SUBMITTED_EVENT_TYPE), { 'user_id': unicode(kwargs['user_id']), 'course_id': unicode(kwargs['course_id']), 'problem_id': unicode(kwargs['usage_id']), 'event_transaction_id': unicode(root_id), 'event_transaction_type': unicode(PROBLEM_SUBMITTED_EVENT_TYPE), 'weighted_earned': kwargs.get('weighted_earned'), 'weighted_possible': kwargs.get('weighted_possible'), } ) if root_type in [GRADES_RESCORE_EVENT_TYPE, GRADES_OVERRIDE_EVENT_TYPE]: current_user = get_current_user() instructor_id = getattr(current_user, 'id', None) tracker.emit( unicode(GRADES_RESCORE_EVENT_TYPE), { 'course_id': unicode(kwargs['course_id']), 'user_id': unicode(kwargs['user_id']), 'problem_id': unicode(kwargs['usage_id']), 'new_weighted_earned': kwargs.get('weighted_earned'), 'new_weighted_possible': kwargs.get('weighted_possible'), 'only_if_higher': kwargs.get('only_if_higher'), 'instructor_id': unicode(instructor_id), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(root_type), } )
def _emit_event(kwargs): """ Emits a problem submitted event only if there is no current event transaction type, i.e. we have not reached this point in the code via a rescore or student state deletion. If the event transaction type has already been set and the transacation is a rescore, emits a problem rescored event. """ root_type = get_event_transaction_type() if not root_type: root_id = get_event_transaction_id() if not root_id: root_id = create_new_event_transaction_id() set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE) tracker.emit( unicode(PROBLEM_SUBMITTED_EVENT_TYPE), { 'user_id': unicode(kwargs['user_id']), 'course_id': unicode(kwargs['course_id']), 'problem_id': unicode(kwargs['usage_id']), 'event_transaction_id': unicode(root_id), 'event_transaction_type': unicode(PROBLEM_SUBMITTED_EVENT_TYPE), 'weighted_earned': kwargs.get('weighted_earned'), 'weighted_possible': kwargs.get('weighted_possible'), }) if root_type == 'edx.grades.problem.rescored': current_user = get_current_user() if current_user is not None and hasattr(current_user, 'id'): instructor_id = unicode(current_user.id) else: instructor_id = None tracker.emit( unicode(GRADES_RESCORE_EVENT_TYPE), { 'course_id': unicode(kwargs['course_id']), 'user_id': unicode(kwargs['user_id']), 'problem_id': unicode(kwargs['usage_id']), 'new_weighted_earned': kwargs.get('weighted_earned'), 'new_weighted_possible': kwargs.get('weighted_possible'), 'only_if_higher': kwargs.get('only_if_higher'), 'instructor_id': instructor_id, 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(GRADES_RESCORE_EVENT_TYPE), })
def _emit_grade_calculated_event(grade): """ Emits an edx.grades.course.grade_calculated event with data from the passed grade. """ tracker.emit( u'edx.grades.course.grade_calculated', { 'user_id': unicode(grade.user_id), 'course_id': unicode(grade.course_id), 'course_version': unicode(grade.course_version), 'percent_grade': grade.percent_grade, 'letter_grade': unicode(grade.letter_grade), 'course_edited_timestamp': unicode(grade.course_edited_timestamp), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(get_event_transaction_type()), 'grading_policy_hash': unicode(grade.grading_policy_hash), } )
def _assert_tracker_emitted_event(self, tracker_mock, grade): """ Helper function to ensure that the mocked event tracker was called with the expected info based on the passed grade. """ tracker_mock.emit.assert_called_with( u'edx.grades.course.grade_calculated', { 'user_id': unicode(grade.user_id), 'course_id': unicode(grade.course_id), 'course_version': unicode(grade.course_version), 'percent_grade': grade.percent_grade, 'letter_grade': unicode(grade.letter_grade), 'course_edited_timestamp': unicode(grade.course_edited_timestamp), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(get_event_transaction_type()), 'grading_policy_hash': unicode(grade.grading_policy_hash), } )
def enqueue_subsection_update(sender, **kwargs): # pylint: disable=unused-argument """ Handles the PROBLEM_WEIGHTED_SCORE_CHANGED or SUBSECTION_OVERRIDE_CHANGED signals by enqueueing a subsection update operation to occur asynchronously. """ events.grade_updated(**kwargs) recalculate_subsection_grade_v3.apply_async( kwargs=dict( user_id=kwargs['user_id'], anonymous_user_id=kwargs.get('anonymous_user_id'), course_id=kwargs['course_id'], usage_id=kwargs['usage_id'], only_if_higher=kwargs.get('only_if_higher'), expected_modified_time=to_timestamp(kwargs['modified']), score_deleted=kwargs.get('score_deleted', False), event_transaction_id=unicode(get_event_transaction_id()), event_transaction_type=unicode(get_event_transaction_type()), score_db_table=kwargs['score_db_table'], ), countdown=RECALCULATE_GRADE_DELAY_SECONDS, )
def enqueue_subsection_update(sender, **kwargs): # pylint: disable=unused-argument """ Handles the PROBLEM_WEIGHTED_SCORE_CHANGED or SUBSECTION_OVERRIDE_CHANGED signals by enqueueing a subsection update operation to occur asynchronously. """ _emit_event(kwargs) result = recalculate_subsection_grade_v3.apply_async( kwargs=dict( user_id=kwargs['user_id'], anonymous_user_id=kwargs.get('anonymous_user_id'), course_id=kwargs['course_id'], usage_id=kwargs['usage_id'], only_if_higher=kwargs.get('only_if_higher'), expected_modified_time=to_timestamp(kwargs['modified']), score_deleted=kwargs.get('score_deleted', False), event_transaction_id=unicode(get_event_transaction_id()), event_transaction_type=unicode(get_event_transaction_type()), score_db_table=kwargs['score_db_table'], ), countdown=RECALCULATE_GRADE_DELAY, )
def _emit_grade_calculated_event(grade): """ Emits an edx.grades.subsection.grade_calculated event with data from the passed grade. """ tracker.emit( u'edx.grades.subsection.grade_calculated', { 'user_id': unicode(grade.user_id), 'course_id': unicode(grade.course_id), 'block_id': unicode(grade.usage_key), 'course_version': unicode(grade.course_version), 'weighted_total_earned': grade.earned_all, 'weighted_total_possible': grade.possible_all, 'weighted_graded_earned': grade.earned_graded, 'weighted_graded_possible': grade.possible_graded, 'first_attempted': unicode(grade.first_attempted), 'subtree_edited_timestamp': unicode(grade.subtree_edited_timestamp), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(get_event_transaction_type()), 'visible_blocks_hash': unicode(grade.visible_blocks_id), } )
def _emit_grade_calculated_event(grade): """ Emits an edx.grades.course.grade_calculated event with data from the passed grade. """ # TODO: remove this context manager after completion of AN-6134 event_name = u'edx.grades.course.grade_calculated' context = contexts.course_context_from_course_id(grade.course_id) with tracker.get_tracker().context(event_name, context): tracker.emit( event_name, { 'user_id': unicode(grade.user_id), 'course_id': unicode(grade.course_id), 'course_version': unicode(grade.course_version), 'percent_grade': grade.percent_grade, 'letter_grade': unicode(grade.letter_grade), 'course_edited_timestamp': unicode(grade.course_edited_timestamp), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(get_event_transaction_type()), 'grading_policy_hash': unicode(grade.grading_policy_hash), } )
def _assert_tracker_emitted_event(self, tracker_mock, grade): """ Helper function to ensure that the mocked event tracker was called with the expected info based on the passed grade. """ tracker_mock.emit.assert_called_with( u'edx.grades.subsection.grade_calculated', { 'user_id': unicode(grade.user_id), 'course_id': unicode(grade.course_id), 'block_id': unicode(grade.usage_key), 'course_version': unicode(grade.course_version), 'weighted_total_earned': grade.earned_all, 'weighted_total_possible': grade.possible_all, 'weighted_graded_earned': grade.earned_graded, 'weighted_graded_possible': grade.possible_graded, 'first_attempted': unicode(grade.first_attempted), 'subtree_edited_timestamp': unicode(grade.subtree_edited_timestamp), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(get_event_transaction_type()), 'visible_blocks_hash': unicode(grade.visible_blocks_id), } )
def course_grade_calculated(course_grade): """ Emits an edx.grades.course.grade_calculated event with data from the passed course_grade. """ event_name = COURSE_GRADE_CALCULATED context = contexts.course_context_from_course_id(course_grade.course_id) # TODO (AN-6134): remove this context manager with tracker.get_tracker().context(event_name, context): tracker.emit( event_name, { 'user_id': unicode(course_grade.user_id), 'course_id': unicode(course_grade.course_id), 'course_version': unicode(course_grade.course_version), 'percent_grade': course_grade.percent_grade, 'letter_grade': unicode(course_grade.letter_grade), 'course_edited_timestamp': unicode(course_grade.course_edited_timestamp), 'event_transaction_id': unicode(get_event_transaction_id()), 'event_transaction_type': unicode(get_event_transaction_type()), 'grading_policy_hash': unicode(course_grade.grading_policy_hash), } )
def _create_override(self, request_user, subsection_grade_model, **override_data): """ Helper method to create a `PersistentSubsectionGradeOverride` object and send a `SUBSECTION_OVERRIDE_CHANGED` signal. """ override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=subsection_grade_model, defaults=self._clean_override_data(override_data), ) _ = PersistentSubsectionGradeOverrideHistory.objects.create( override_id=override.id, user=request_user, feature=PersistentSubsectionGradeOverrideHistory.GRADEBOOK, action=PersistentSubsectionGradeOverrideHistory.CREATE_OR_UPDATE, ) set_event_transaction_type(SUBSECTION_GRADE_CALCULATED) create_new_event_transaction_id() recalculate_subsection_grade_v3.apply( kwargs=dict( user_id=subsection_grade_model.user_id, anonymous_user_id=None, course_id=text_type(subsection_grade_model.course_id), usage_id=text_type(subsection_grade_model.usage_key), only_if_higher=False, expected_modified_time=to_timestamp(override.modified), score_deleted=False, event_transaction_id=unicode(get_event_transaction_id()), event_transaction_type=unicode(get_event_transaction_type()), score_db_table=ScoreDatabaseTableEnum.overrides, force_update_subsections=True, ) ) # Emit events to let our tracking system to know we updated subsection grade subsection_grade_calculated(subsection_grade_model)