Unverified Commit 7e1aa6fa by Gabe Mulley Committed by GitHub

Merge pull request #16441 from edx/mulby/hold-back-some-users

hold back some users from dynamic pacing features
parents e503ed86 e5a0bcfc
from django.contrib import admin
from django import forms
from django.utils.translation import ugettext_lazy as _
from . import models
......@@ -32,6 +33,15 @@ class ScheduleAdmin(admin.ModelAdmin):
return qs
class ScheduleConfigAdminForm(forms.ModelForm):
def clean_hold_back_ratio(self):
hold_back_ratio = self.cleaned_data["hold_back_ratio"]
if hold_back_ratio < 0 or hold_back_ratio > 1:
raise forms.ValidationError("Invalid hold back ratio, the value must be between 0 and 1.")
return hold_back_ratio
@admin.register(models.ScheduleConfig)
class ScheduleConfigAdmin(admin.ModelAdmin):
search_fields = ('site',)
......@@ -40,4 +50,6 @@ class ScheduleConfigAdmin(admin.ModelAdmin):
'enqueue_recurring_nudge', 'deliver_recurring_nudge',
'enqueue_upgrade_reminder', 'deliver_upgrade_reminder',
'enqueue_course_update', 'deliver_course_update',
'hold_back_ratio',
)
form = ScheduleConfigAdminForm
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('schedules', '0006_scheduleexperience'),
]
operations = [
migrations.AddField(
model_name='scheduleconfig',
name='hold_back_ratio',
field=models.FloatField(default=0),
),
]
......@@ -46,6 +46,7 @@ class ScheduleConfig(ConfigurationModel):
deliver_upgrade_reminder = models.BooleanField(default=False)
enqueue_course_update = models.BooleanField(default=False)
deliver_course_update = models.BooleanField(default=False)
hold_back_ratio = models.FloatField(default=0)
class ScheduleExperience(models.Model):
......
import datetime
import logging
import random
import analytics
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
......@@ -55,17 +57,26 @@ def create_schedule(sender, **kwargs):
upgrade_deadline = _calculate_upgrade_deadline(enrollment.course_id, content_availability_date)
if course_has_highlights(enrollment.course_id):
experience_type = ScheduleExperience.EXPERIENCES.course_updates
else:
experience_type = ScheduleExperience.EXPERIENCES.default
if _should_randomly_suppress_schedule_creation(
schedule_config,
enrollment,
upgrade_deadline,
experience_type,
content_availability_date,
):
return
schedule = Schedule.objects.create(
enrollment=enrollment,
start=content_availability_date,
upgrade_deadline=upgrade_deadline
)
if course_has_highlights(enrollment.course_id):
experience_type = ScheduleExperience.EXPERIENCES.course_updates
else:
experience_type = ScheduleExperience.EXPERIENCES.default
ScheduleExperience(schedule=schedule, experience_type=experience_type).save()
log.debug('Schedules: created a new schedule starting at %s with an upgrade deadline of %s and experience type: %s',
......@@ -138,3 +149,36 @@ def _get_upgrade_deadline_delta_setting(course_id):
delta = None
return delta
def _should_randomly_suppress_schedule_creation(
schedule_config,
enrollment,
upgrade_deadline,
experience_type,
content_availability_date,
):
# The hold back ratio is always between 0 and 1. A value of 0 indicates that schedules should be created for all
# schedules. A value of 1 indicates that no schedules should be created for any enrollments. A value of 0.2 would
# mean that 20% of enrollments should *not* be given schedules.
# This allows us to measure the impact of the dynamic schedule experience by comparing this "control" group that
# does not receive any of benefits of the feature against the group that does.
if random.random() < schedule_config.hold_back_ratio:
log.debug('Schedules: Enrollment held back from dynamic schedule experiences.')
upgrade_deadline_str = None
if upgrade_deadline:
upgrade_deadline_str = upgrade_deadline.isoformat()
analytics.track(
'edx.bi.schedule.suppressed',
{
'user_id': enrollment.user.id,
'course_id': unicode(enrollment.course_id),
'experience_type': experience_type,
'upgrade_deadline': upgrade_deadline_str,
'content_availability_date': content_availability_date.isoformat(),
}
)
return True
return False
......@@ -35,3 +35,4 @@ class ScheduleConfigFactory(factory.DjangoModelFactory):
deliver_upgrade_reminder = True
enqueue_course_update = True
deliver_course_update = True
hold_back_ratio = 0
......@@ -20,6 +20,7 @@ from ..models import Schedule
from ..tests.factories import ScheduleConfigFactory
@ddt.ddt
@patch('openedx.core.djangoapps.schedules.signals.get_current_site')
@skip_unless_lms
class CreateScheduleTests(SharedModuleStoreTestCase):
......@@ -94,6 +95,33 @@ class CreateScheduleTests(SharedModuleStoreTestCase):
mock_get_current_site.return_value = site
self.assert_schedule_created(experience_type=ScheduleExperience.EXPERIENCES.course_updates)
@override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True)
@patch('analytics.track')
@patch('random.random')
@ddt.data(
(0, True),
(0.1, True),
(0.3, False),
)
@ddt.unpack
def test_create_schedule_hold_backs(
self,
hold_back_ratio,
expect_schedule_created,
mock_random,
mock_track,
mock_get_current_site
):
mock_random.return_value = 0.2
schedule_config = ScheduleConfigFactory.create(enabled=True, hold_back_ratio=hold_back_ratio)
mock_get_current_site.return_value = schedule_config.site
if expect_schedule_created:
self.assert_schedule_created()
self.assertFalse(mock_track.called)
else:
self.assert_schedule_not_created()
mock_track.assert_called_once()
@ddt.ddt
@skip_unless_lms
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment