Upgrade waffle classes to the new edx-toggles API

Waffle classes no longer have namespaces. All features are moved to the
WaffleFlag/WaffleSwitch classes.

Here we use the edx_toggles.toggles.__future__ API, which is available
in 1.2.0. This means that we don't have to upgrade edx-toggles. We
should remove the __future__ imports as soon as we upgrade to 2.0.0.
This commit is contained in:
Régis Behmo
2020-11-05 09:53:47 +01:00
parent d13a1fb5f5
commit 216b99264a
8 changed files with 58 additions and 20 deletions

View File

@@ -4,7 +4,6 @@ Feature flag support for experiments
import datetime
import logging
from contextlib import contextmanager
import dateutil
import pytz
@@ -62,6 +61,7 @@ class ExperimentWaffleFlag(CourseWaffleFlag):
def test_my_experiment(self):
...
"""
def __init__(
self,
waffle_namespace,
@@ -81,6 +81,33 @@ class ExperimentWaffleFlag(CourseWaffleFlag):
]
self.use_course_aware_bucketing = use_course_aware_bucketing
@property
def _app_label(self):
"""
By convention, the app label associated to an experiment waffle flag is the dotted prefix of the flag name. For
example: if the flag name is "grades.my.experiment.waffle.flag", then the `_app_label` will be "grades".
This app label replaces what was formerly known as the waffle flag namespace.
"""
return self._split_name[0]
@property
def _experiment_name(self):
"""
By convention, the app label associated to an experiment waffle flag is the first dotted suffix of the flag
name. For example: if the flag name name is "grades.my.experiment.waffle.flag", then the `_experiment_name`
will be "my.experiment.waffle.flag".
"""
return self._split_name[1]
@property
def _split_name(self):
"""
Return the flag name prefix (before the first dot) and suffix. This raises a ValueError if the flag does not
contain a dot ".".
"""
prefix, suffix = self.name.split(".", maxsplit=1)
return prefix, suffix
def _cache_bucket(self, key, value):
request_cache = RequestCache('experiments')
request_cache.set(key, value)
@@ -178,7 +205,7 @@ class ExperimentWaffleFlag(CourseWaffleFlag):
# 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
experiment_name = bucketing_group_name = self.name
if course_key:
experiment_name += ".{}".format(course_key)
if course_key and self.use_course_aware_bucketing:
@@ -225,8 +252,8 @@ class ExperimentWaffleFlag(CourseWaffleFlag):
event_name='edx.bi.experiment.user.bucketed',
properties={
'site': request.site.domain,
'app_label': self.waffle_namespace.name,
'experiment': self.flag_name,
'app_label': self._app_label,
'experiment': self._experiment_name,
'course_id': str(course_key) if course_key else None,
'bucket': bucket,
'is_staff': user.is_staff,

View File

@@ -156,6 +156,15 @@ class ExperimentWaffleFlagTests(SharedModuleStoreTestCase):
self.assertEqual(self.flag.get_bucket(), expected_bucket)
self.assertEqual(self.flag.is_experiment_on(), active)
def test_app_label_experiment_name(self):
# pylint: disable=protected-access
self.assertEqual("experiments", self.flag._app_label)
self.assertEqual("test", self.flag._experiment_name)
flag = ExperimentWaffleFlag("namespace", "flag.name", __name__)
self.assertEqual("namespace", flag._app_label)
self.assertEqual("flag.name", flag._experiment_name)
class ExperimentWaffleFlagCourseAwarenessTest(SharedModuleStoreTestCase):
"""

View File

@@ -3,7 +3,13 @@ Contains configuration for schedules app
"""
from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace, LegacyWaffleSwitch, LegacyWaffleSwitchNamespace
from edx_toggles.toggles import (
LegacyWaffleFlag,
LegacyWaffleFlagNamespace,
LegacyWaffleSwitch,
LegacyWaffleSwitchNamespace
)
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='schedules')

View File

@@ -131,7 +131,7 @@ class WaffleFlag(LegacyWaffleFlag):
yield
class CourseWaffleFlag(BaseWaffleFlag):
class CourseWaffleFlag(LegacyWaffleFlag):
"""
Represents a single waffle flag that can be forced on/off for a course. This class should be used instead of
WaffleFlag when in the context of a course.
@@ -165,13 +165,13 @@ class CourseWaffleFlag(BaseWaffleFlag):
from .models import WaffleFlagCourseOverrideModel
cache_key = "{}.{}".format(self.namespaced_flag_name, str(course_key))
course_override = self._cached_flags.get(cache_key)
course_override = self.cached_flags().get(cache_key)
if course_override is None:
course_override = WaffleFlagCourseOverrideModel.override_value(
self.namespaced_flag_name, course_key
)
self._cached_flags[cache_key] = course_override
self.cached_flags()[cache_key] = course_override
if course_override == WaffleFlagCourseOverrideModel.ALL_CHOICES.on:
return True
@@ -196,7 +196,6 @@ class CourseWaffleFlag(BaseWaffleFlag):
)
is_enabled_for_course = self._get_course_override_value(course_key)
if is_enabled_for_course is not None:
# pylint: disable=protected-access
self.waffle_namespace._monitor_value(self.flag_name, is_enabled_for_course)
self.set_monitor_value(is_enabled_for_course)
return is_enabled_for_course
return super().is_enabled()

View File

@@ -49,8 +49,7 @@ class ToggleStateView(views.APIView):
"""
waffle_switch_instances = WaffleSwitch.get_instances()
for switch_instance in waffle_switch_instances:
switch_name = switch_instance.namespaced_switch_name
switch = self._get_or_create_toggle_response(switches_dict, switch_name)
switch = self._get_or_create_toggle_response(switches_dict, switch_instance.name)
self._add_toggle_instance_details(switch, switch_instance)
def _add_waffle_switch_state(self, switches_dict):
@@ -98,8 +97,7 @@ class ToggleStateView(views.APIView):
"""
waffle_flag_instances = WaffleFlag.get_instances()
for flag_instance in waffle_flag_instances:
flag_name = flag_instance.namespaced_flag_name
flag = self._get_or_create_toggle_response(flags_dict, flag_name)
flag = self._get_or_create_toggle_response(flags_dict, flag_instance.name)
self._add_toggle_instance_details(flag, flag_instance)
def _add_toggle_instance_details(self, toggle, toggle_instance):

View File

@@ -951,9 +951,9 @@ class CourseHomeFragmentViewTests(ModuleStoreTestCase):
self.user = UserFactory()
self.client.login(username=self.user.username, password=TEST_PASSWORD)
name = SHOW_UPGRADE_MSG_ON_COURSE_HOME.waffle_namespace._namespaced_name(
SHOW_UPGRADE_MSG_ON_COURSE_HOME.flag_name)
self.flag, __ = Flag.objects.update_or_create(name=name, defaults={'everyone': True})
self.flag, __ = Flag.objects.update_or_create(
name=SHOW_UPGRADE_MSG_ON_COURSE_HOME.name, defaults={'everyone': True}
)
def assert_upgrade_message_not_displayed(self):
response = self.client.get(self.url)

View File

@@ -720,6 +720,7 @@ class TestCourseOutlineResumeCourse(SharedModuleStoreTestCase, CompletionWaffleT
switch_name = ENABLE_COMPLETION_TRACKING_SWITCH.name
switch, _ = Switch.objects.get_or_create(name=switch_name)
# pylint: disable=protected-access
self.assertEqual(switch.created, view._completion_data_collection_start())
switch.delete()

View File

@@ -166,10 +166,8 @@ class CourseOutlineFragmentView(EdxFragmentView):
"""
Returns the date that the ENABLE_COMPLETION_TRACKING waffle switch was enabled.
"""
# pylint: disable=protected-access
switch_name = completion_waffle.ENABLE_COMPLETION_TRACKING_SWITCH.name
try:
return Switch.objects.get(name=switch_name).created
return Switch.objects.get(name=ENABLE_COMPLETION_TRACKING_SWITCH.name).created
except Switch.DoesNotExist:
return DEFAULT_COMPLETION_TRACKING_START