From 7f22041fc1791705fe162462bb85cf236522084b Mon Sep 17 00:00:00 2001
From: Robert Raposa <rraposa@edx.org>
Date: Thu, 2 Jul 2020 12:00:23 -0400
Subject: [PATCH] ARCHBOM-1316: always enable some course_experience flags
 (#24322)

In order to remove the deprecated flag_undefined_default=True
argument, this commit updates the following flags to always be
enabled using a new temporary class:

- course_experience.course_outline_page
- course_experience.unified_course_tab

Adds a temporary setting `USE_DEFAULT_TRUE_NAMESPACE`,
to enable a monitored rollout of this change.

TNL-7061 is the ticket where these flags will actually be
removed. This requires more careful work including removing
all dead code, and potentially refactoring tests that were
testing shared functionality, but only when the flag was
False.

ARCHBOM-1316
---
 .../core/djangoapps/waffle_utils/__init__.py  | 14 +--
 .../core/djangoapps/waffle_utils/testutils.py |  4 +-
 .../features/course_experience/__init__.py    | 94 ++++++++++++++++++-
 3 files changed, 99 insertions(+), 13 deletions(-)

diff --git a/openedx/core/djangoapps/waffle_utils/__init__.py b/openedx/core/djangoapps/waffle_utils/__init__.py
index 0bb3872f8f1..2b97a3de926 100644
--- a/openedx/core/djangoapps/waffle_utils/__init__.py
+++ b/openedx/core/djangoapps/waffle_utils/__init__.py
@@ -8,25 +8,25 @@ Usage:
 For Waffle Flags, first set up the namespace, and then create flags using the
 namespace.  For example::
 
-   WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='course_experience')
+   WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='my_namespace')
 
    # Use CourseWaffleFlag when you are in the context of a course.
-   UNIFIED_COURSE_TAB_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'unified_course_tab')
+   SOME_COURSE_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'some_course_feature')
    # Use WaffleFlag when outside the context of a course.
-   HIDE_SEARCH_FLAG = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'hide_search')
+   SOME_FLAG = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'some_feature')
 
 You can check these flags in code using the following::
 
-    HIDE_SEARCH_FLAG.is_enabled()
-    UNIFIED_COURSE_TAB_FLAG.is_enabled(course_key)
+    SOME_FLAG.is_enabled()
+    SOME_COURSE_FLAG.is_enabled(course_key)
 
 To test these WaffleFlags, see testutils.py.
 
 In the above examples, you will use Django Admin "waffle" section to configure
-for a flag named: course_experience.unified_course_tab
+for a flag named: my_namespace.some_course_feature
 
 You could also use the Django Admin "waffle_utils" section to configure a course
-override for this same flag (e.g. course_experience.unified_course_tab).
+override for this same flag (e.g. my_namespace.some_course_feature).
 
 For Waffle Switches, first set up the namespace, and then create the flag name.
 For example::
diff --git a/openedx/core/djangoapps/waffle_utils/testutils.py b/openedx/core/djangoapps/waffle_utils/testutils.py
index 0f52fd6992f..af7b6a614ba 100644
--- a/openedx/core/djangoapps/waffle_utils/testutils.py
+++ b/openedx/core/djangoapps/waffle_utils/testutils.py
@@ -19,7 +19,7 @@ class override_waffle_flag(override_flag):
     It accepts two parameters, the flag itself and its intended state. Example
     usage::
 
-        with override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True):
+        with override_waffle_flag(SOME_COURSE_FLAG, active=True):
             ...
 
     If the flag already exists, its value will be changed inside the context
@@ -29,7 +29,7 @@ class override_waffle_flag(override_flag):
 
     It can also act as a decorator::
 
-        @override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True)
+        @override_waffle_flag(SOME_COURSE_FLAG, active=True)
         def test_happy_mode_enabled():
             ...
     """
diff --git a/openedx/features/course_experience/__init__.py b/openedx/features/course_experience/__init__.py
index a038208310b..5bbe6004e86 100644
--- a/openedx/features/course_experience/__init__.py
+++ b/openedx/features/course_experience/__init__.py
@@ -1,9 +1,11 @@
 """
 Unified course experience settings and helper methods.
 """
-
-
+import crum
+from django.conf import settings
 from django.utils.translation import ugettext as _
+from edx_django_utils.monitoring import set_custom_metric
+from waffle import flag_is_active
 
 from lms.djangoapps.experiments.flags import ExperimentWaffleFlag
 from openedx.core.djangoapps.util.user_messages import UserMessageCollection
@@ -12,11 +14,95 @@ from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlag, W
 # Namespace for course experience waffle flags.
 WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='course_experience')
 
+# .. toggle_name: USE_DEFAULT_TRUE_NAMESPACE
+# .. toggle_implementation: DjangoSetting
+# .. toggle_default: False
+# .. toggle_description: When True, uses the new default_true namespace to help deprecate flag_undefined_default.
+# .. toggle_category: course_experience
+# .. toggle_use_cases: monitored_rollout
+# .. toggle_creation_date: 2020-06-30
+# .. toggle_expiration_date: 2020-07
+# .. toggle_warnings: n/a
+# .. toggle_tickets: n/a
+# .. toggle_status: supported
+_USE_DEFAULT_TRUE_NAMESPACE = 'USE_DEFAULT_TRUE_NAMESPACE'
+
+
+class DefaultTrueWaffleFlagNamespace(WaffleFlagNamespace):
+    """
+    This is a temporary class to help deprecate/remove ``flag_undefined_default``.
+
+    TODO: TNL-7061: Perform the actual clean-up required to remove these flags
+        and refactor/fix any tests that shouldn't be removed.
+
+    """
+    def _is_flag_active(self, flag_name):
+        """
+        Returns and caches whether the provided flag is active.
+
+        If the flag value is already cached in the request, it is returned.
+        If the flag doesn't exist, always returns default of True.
+
+        Note: This is a similified version of the method it overrides, that
+        hard codes the default to True, and skips the call back used for
+        course overrides:
+        https://github.com/edx/edx-platform/blob/df9be8c678f8266e2e5710513c74deca14c4527c/openedx/core/djangoapps/waffle_utils/__init__.py#L229-L305
+
+        """
+        # Import is placed here to avoid model import at project startup.
+        from waffle.models import Flag
+
+        # validate arguments
+        namespaced_flag_name = self._namespaced_name(flag_name)
+        value = self._cached_flags.get(namespaced_flag_name)
+        if value is None:
+
+            # determine if the flag is undefined in waffle
+            try:
+                Flag.objects.get(name=namespaced_flag_name)
+            except Flag.DoesNotExist:
+                # default to True if not defined
+                value = True
+
+            if value is None:
+                request = crum.get_current_request()
+                if request:
+                    value = flag_is_active(request, namespaced_flag_name)
+                else:
+                    set_custom_metric('warn_flag_no_request', True)
+                    # Return the default value if not in a request context.
+                    # Same as the original implementation
+                    self._set_waffle_flag_metric(namespaced_flag_name, value)
+                    return True
+
+            self._cached_flags[namespaced_flag_name] = value
+
+        self._set_waffle_flag_metric(namespaced_flag_name, value)
+        return value
+
+    def is_flag_active(self, flag_name, check_before_waffle_callback=None, flag_undefined_default=None):
+        """
+        Overrides is_flag_active if setting USE_DEFAULT_TRUE_NAMESPACE is True.
+        """
+        use_default_true_namespace = getattr(settings, _USE_DEFAULT_TRUE_NAMESPACE, False)
+        set_custom_metric('temp_use_default_true_namespace', use_default_true_namespace)
+        if use_default_true_namespace:
+            return self._is_flag_active(flag_name)
+        else:
+            return super().is_flag_active(flag_name, check_before_waffle_callback, flag_undefined_default=True)
+
+
+DEFAULT_TRUE_WAFFLE_FLAG_NAMESPACE = DefaultTrueWaffleFlagNamespace(name='course_experience')
+
 # Waffle flag to enable the separate course outline page and full width content.
-COURSE_OUTLINE_PAGE_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'course_outline_page', flag_undefined_default=True)
+# NOTE: The special namespace makes the default True and skips checking course overrides.
+# TODO: TNL-7061: Perform the actual clean-up required to remove this flag.
+COURSE_OUTLINE_PAGE_FLAG = CourseWaffleFlag(DEFAULT_TRUE_WAFFLE_FLAG_NAMESPACE, 'course_outline_page')
 
 # Waffle flag to enable a single unified "Course" tab.
-UNIFIED_COURSE_TAB_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'unified_course_tab', flag_undefined_default=True)
+# NOTE: The special namespace makes the default True and skips checking course overrides.
+# TODO: TNL-7061: Perform the actual clean-up required to remove this flag.
+UNIFIED_COURSE_TAB_FLAG = CourseWaffleFlag(DEFAULT_TRUE_WAFFLE_FLAG_NAMESPACE, 'unified_course_tab')
 
 # Waffle flag to enable the sock on the footer of the home and courseware pages.
 DISPLAY_COURSE_SOCK_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'display_course_sock')
-- 
GitLab