def get_bucket(self, course_key=None, track=True): """ Return which bucket number the specified user is in. The user may be force-bucketed if matching subordinate flags of the form "main_flag.BUCKET_NUM" exist. Otherwise, they will be hashed into a default bucket based on their username, the experiment name, and the course-run key. If `self.use_course_aware_bucketing` is False, the course-run key will be omitted from the hashing formula, thus making it so a given user has the same default bucket across all course runs; however, subordinate flags that match the course-run key will still apply. If `course_key` argument is omitted altogether, then subordinate flags will be evaluated outside of the course-run context, and the default bucket will be calculated as if `self.use_course_aware_bucketing` is False. Finally, Bucket 0 is assumed to be the control bucket and will be returned if the experiment is not enabled for this user and course. Arguments: course_key (Optional[CourseKey]) track (bool): Whether an analytics event should be generated if the user is bucketed for the first time. Returns: int """ # Keep some imports in here, because this class is commonly used at a module level, and we want to avoid # circular imports for any models. from lms.djangoapps.experiments.models import ExperimentKeyValue from lms.djangoapps.courseware.masquerade import get_specific_masquerading_user request = get_current_request() if not request: return 0 if not hasattr(request, 'user') or not request.user.id: # We need username for stable bucketing and id for tracking, so just skip anonymous (not-logged-in) users return 0 user = get_specific_masquerading_user(request.user, course_key) if user is None: user = request.user masquerading_as_specific_student = False else: masquerading_as_specific_student = True # If a course key is passed in, include it in the experiment name # in order to separate caches and analytics calls per course-run. # If we are using course-aware bucketing, then also append that course key # to `bucketing_group_name`, such that users can be hashed into different # buckets for different course-runs. experiment_name = bucketing_group_name = self.namespaced_flag_name if course_key: experiment_name += ".{}".format(course_key) if course_key and self.use_course_aware_bucketing: bucketing_group_name += ".{}".format(course_key) # Check if we have a cache for this request already request_cache = RequestCache('experiments') cache_response = request_cache.get_cached_response(experiment_name) if cache_response.is_found: return cache_response.value # Check if the main flag is even enabled for this user and course. if not self.is_experiment_on( course_key): # grabs user from the current request, if any return self._cache_bucket(experiment_name, 0) # Check if the enrollment should even be considered (if it started before the experiment wants, we ignore) if course_key and self.experiment_id is not None: values = ExperimentKeyValue.objects.filter( experiment_id=self.experiment_id).values('key', 'value') values = {pair['key']: pair['value'] for pair in values} if not self._is_enrollment_inside_date_bounds( values, user, course_key): return self._cache_bucket(experiment_name, 0) # Determine the user's bucket. # First check if forced into a particular bucket, using our subordinate bucket flags. # If not, calculate their default bucket using a consistent hash function. for i, bucket_flag in enumerate(self.bucket_flags): if bucket_flag.is_enabled(course_key): bucket = i break else: bucket = stable_bucketing_hash_group(bucketing_group_name, self.num_buckets, user.username) session_key = 'tracked.{}'.format(experiment_name) if (track and hasattr(request, 'session') and session_key not in request.session and not masquerading_as_specific_student): segment.track(user_id=user.id, event_name='edx.bi.experiment.user.bucketed', properties={ 'site': request.site.domain, 'app_label': self.waffle_namespace.name, 'experiment': self.flag_name, 'course_id': str(course_key) if course_key else None, 'bucket': bucket, 'is_staff': user.is_staff, 'nonInteraction': 1, }) # Mark that we've recorded this bucketing, so that we don't do it again this session request.session[session_key] = True return self._cache_bucket(experiment_name, bucket)
def get_bucket(self, course_key=None, track=True): """ Return which bucket number the specified user is in. Bucket 0 is assumed to be the control bucket and will be returned if the experiment is not enabled for this user and course. """ # Keep some imports in here, because this class is commonly used at a module level, and we want to avoid # circular imports for any models. from experiments.models import ExperimentKeyValue from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.masquerade import ( setup_masquerade, is_masquerading, get_specific_masquerading_user, ) request = get_current_request() if not request: return 0 if not hasattr(request, 'user') or not request.user.id: # We need username for stable bucketing and id for tracking, so just skip anonymous (not-logged-in) users return 0 user = get_specific_masquerading_user(request.user, course_key) if user is None: user = request.user masquerading_as_specific_student = False else: masquerading_as_specific_student = True # Use course key in experiment name to separate caches and segment calls per-course-run experiment_name = self.namespaced_flag_name + ('.{}'.format(course_key) if course_key else '') # Check if we have a cache for this request already request_cache = RequestCache('experiments') cache_response = request_cache.get_cached_response(experiment_name) if cache_response.is_found: return cache_response.value # Check if the main flag is even enabled for this user and course. if not self.is_experiment_on( course_key): # grabs user from the current request, if any return self._cache_bucket(experiment_name, 0) # Check if the enrollment should even be considered (if it started before the experiment wants, we ignore) if course_key and self.experiment_id is not None: values = ExperimentKeyValue.objects.filter( experiment_id=self.experiment_id).values('key', 'value') values = {pair['key']: pair['value'] for pair in values} if not self._is_enrollment_inside_date_bounds( values, user, course_key): return self._cache_bucket(experiment_name, 0) bucket = stable_bucketing_hash_group(experiment_name, self.num_buckets, user.username) # Now check if the user is forced into a particular bucket, using our subordinate bucket flags for i, bucket_flag in enumerate(self.bucket_flags): if bucket_flag.is_enabled(course_key): bucket = i break session_key = 'tracked.{}'.format(experiment_name) if track and hasattr( request, 'session' ) and session_key not in request.session and not masquerading_as_specific_student: segment.track(user_id=user.id, event_name='edx.bi.experiment.user.bucketed', properties={ 'site': request.site.domain, 'app_label': self.waffle_namespace.name, 'experiment': self.flag_name, 'course_id': str(course_key) if course_key else None, 'bucket': bucket, 'is_staff': user.is_staff, 'nonInteraction': 1, }) # Mark that we've recorded this bucketing, so that we don't do it again this session request.session[session_key] = True return self._cache_bucket(experiment_name, bucket)