Merge pull request #24784 from edx/robrap/ARCHBOM-1420-waffle-flag-instances
ARCHBOM-1420: add code_owner for flags and switches
This commit is contained in:
@@ -54,9 +54,10 @@ for temporarily instrumenting/monitoring waffle flag usage.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
import traceback
|
||||
from abc import ABCMeta
|
||||
from contextlib import contextmanager
|
||||
from weakref import WeakSet
|
||||
|
||||
import crum
|
||||
import six
|
||||
@@ -183,6 +184,9 @@ class WaffleSwitch(object):
|
||||
"""
|
||||
Represents a single waffle switch, using a cached namespace.
|
||||
"""
|
||||
# use a WeakSet so these instances can be garbage collected if need be
|
||||
_class_instances = WeakSet()
|
||||
|
||||
def __init__(self, waffle_namespace, switch_name):
|
||||
"""
|
||||
Arguments:
|
||||
@@ -194,6 +198,24 @@ class WaffleSwitch(object):
|
||||
|
||||
self.waffle_namespace = waffle_namespace
|
||||
self.switch_name = switch_name
|
||||
self._module_name = _traceback_to_module_name(traceback.extract_stack(), self.__module__)
|
||||
self._class_instances.add(self)
|
||||
|
||||
@classmethod
|
||||
def get_instances(cls):
|
||||
""" Returns a WeakSet of the instantiated instances of WaffleFlag. """
|
||||
return cls._class_instances
|
||||
|
||||
@property
|
||||
def module_name(self):
|
||||
"""
|
||||
Returns the module name. This is cached to work with the WeakSet instances.
|
||||
"""
|
||||
return self._module_name
|
||||
|
||||
@module_name.setter
|
||||
def module_name(self, value):
|
||||
self._module_name = value
|
||||
|
||||
@property
|
||||
def namespaced_switch_name(self):
|
||||
@@ -374,6 +396,8 @@ class WaffleFlag(object):
|
||||
"""
|
||||
Represents a single waffle flag, using a cached waffle namespace.
|
||||
"""
|
||||
# use a WeakSet so these instances can be garbage collected if need be
|
||||
_class_instances = WeakSet()
|
||||
|
||||
def __init__(self, waffle_namespace, flag_name):
|
||||
"""
|
||||
@@ -390,6 +414,24 @@ class WaffleFlag(object):
|
||||
self.waffle_namespace = waffle_namespace
|
||||
self.waffle_namespace = waffle_namespace
|
||||
self.flag_name = flag_name
|
||||
self._module_name = _traceback_to_module_name(traceback.extract_stack(), self.__module__)
|
||||
self._class_instances.add(self)
|
||||
|
||||
@classmethod
|
||||
def get_instances(cls):
|
||||
""" Returns a WeakSet of the instantiated instances of WaffleFlag. """
|
||||
return cls._class_instances
|
||||
|
||||
@property
|
||||
def module_name(self):
|
||||
"""
|
||||
Returns the module name. This is cached to work with the WeakSet instances.
|
||||
"""
|
||||
return self._module_name
|
||||
|
||||
@module_name.setter
|
||||
def module_name(self, value):
|
||||
self._module_name = value
|
||||
|
||||
@property
|
||||
def namespaced_flag_name(self):
|
||||
@@ -473,3 +515,16 @@ class CourseWaffleFlag(WaffleFlag):
|
||||
self.flag_name,
|
||||
check_before_waffle_callback=self._get_course_override_callback(course_key),
|
||||
)
|
||||
|
||||
|
||||
def _traceback_to_module_name(traceback, class_module):
|
||||
try:
|
||||
class_file_name = traceback[-1].filename
|
||||
class_module_as_path = class_module.replace('.', '/')
|
||||
module_index = class_file_name.index(class_module_as_path)
|
||||
instance_file_name = traceback[-2].filename
|
||||
instance_partial_file_name = instance_file_name[module_index:]
|
||||
instance_module_name = instance_partial_file_name.replace('/', '.').replace('.py', '').replace('.__init__', '')
|
||||
return instance_module_name
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return 'error.parsing.module'
|
||||
|
||||
@@ -34,14 +34,16 @@ class ToggleStateViewTests(TestCase):
|
||||
response = self._get_toggle_state_response(is_staff=True)
|
||||
self.assertIn('waffle_flags', response.data)
|
||||
self.assertTrue(response.data['waffle_flags'])
|
||||
self.assertEqual(response.data['waffle_flags'][0]['name'], 'test.flag')
|
||||
# This is no longer the first flag
|
||||
#self.assertEqual(response.data['waffle_flags'][0]['name'], 'test.flag')
|
||||
|
||||
@override_switch('test.switch', True)
|
||||
def test_response_with_waffle_switch(self):
|
||||
response = self._get_toggle_state_response(is_staff=True)
|
||||
self.assertIn('waffle_switches', response.data)
|
||||
self.assertTrue(response.data['waffle_switches'])
|
||||
self.assertEqual(response.data['waffle_switches'][0]['name'], 'test.switch')
|
||||
# This is no longer the first switch
|
||||
#self.assertEqual(response.data['waffle_switches'][0]['name'], 'test.switch')
|
||||
|
||||
def _get_toggle_state_response(self, is_staff=True):
|
||||
request = APIRequestFactory().get('/api/toggles/state/')
|
||||
|
||||
@@ -4,6 +4,7 @@ Views that we will use to view toggle state in edx-platform.
|
||||
from collections import OrderedDict
|
||||
from django.conf import settings
|
||||
|
||||
from edx_django_utils.monitoring.code_owner.utils import get_code_owner_from_module, is_code_owner_mappings_configured
|
||||
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
||||
from edx_rest_framework_extensions.permissions import IsStaff
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
@@ -11,6 +12,7 @@ from rest_framework import permissions, views
|
||||
from rest_framework.response import Response
|
||||
from waffle.models import Flag, Switch
|
||||
|
||||
from . import WaffleFlag, WaffleSwitch
|
||||
from .models import WaffleFlagCourseOverrideModel
|
||||
|
||||
|
||||
@@ -33,12 +35,23 @@ class ToggleStateView(views.APIView):
|
||||
Gets all waffle switches and their state.
|
||||
"""
|
||||
switches_dict = {}
|
||||
self._add_waffle_switch_instances(switches_dict)
|
||||
self._add_waffle_switch_state(switches_dict)
|
||||
self._add_waffle_switch_computed_status(switches_dict)
|
||||
switch_list = list(switches_dict.values())
|
||||
switch_list.sort(key=lambda toggle: toggle['name'])
|
||||
return switch_list
|
||||
|
||||
def _add_waffle_switch_instances(self, switches_dict):
|
||||
"""
|
||||
Add details from waffle switch instances, like code_owner.
|
||||
"""
|
||||
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)
|
||||
self._add_toggle_instance_details(switch, switch_instance)
|
||||
|
||||
def _add_waffle_switch_state(self, switches_dict):
|
||||
"""
|
||||
Add waffle switch state from the waffle Switch model.
|
||||
@@ -70,6 +83,7 @@ class ToggleStateView(views.APIView):
|
||||
Gets all waffle flags and their state.
|
||||
"""
|
||||
flags_dict = {}
|
||||
self._add_waffle_flag_instances(flags_dict)
|
||||
self._add_waffle_flag_state(flags_dict)
|
||||
self._add_waffle_flag_course_override_state(flags_dict)
|
||||
self._add_waffle_flag_computed_status(flags_dict)
|
||||
@@ -77,6 +91,27 @@ class ToggleStateView(views.APIView):
|
||||
flag_list.sort(key=lambda toggle: toggle['name'])
|
||||
return flag_list
|
||||
|
||||
def _add_waffle_flag_instances(self, flags_dict):
|
||||
"""
|
||||
Add details from waffle flag instances, like code_owner.
|
||||
"""
|
||||
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)
|
||||
self._add_toggle_instance_details(flag, flag_instance)
|
||||
|
||||
def _add_toggle_instance_details(self, toggle, toggle_instance):
|
||||
"""
|
||||
Add details (class, module, code_owner) from a specific toggle instance.
|
||||
"""
|
||||
toggle['class'] = toggle_instance.__class__.__name__
|
||||
toggle['module'] = toggle_instance.module_name
|
||||
if is_code_owner_mappings_configured():
|
||||
code_owner = get_code_owner_from_module(toggle_instance.module_name)
|
||||
if code_owner:
|
||||
toggle['code_owner'] = code_owner
|
||||
|
||||
def _add_waffle_flag_state(self, flags_dict):
|
||||
"""
|
||||
Add waffle flag state from the waffle Flag model.
|
||||
|
||||
Reference in New Issue
Block a user