Skip to content

Commit

Permalink
feat: expose toggle state report API
Browse files Browse the repository at this point in the history
This report, previously in edx-platform, could not be exposed in other IDAs.
Here, we expose the report with a simple API. The modular ToggleStateReport
class can be overridden such that custom toggle values (such as those coming
from edx-platform's WaffleFlagCourseOverrideModel) can be added to the report.
  • Loading branch information
regisb committed Mar 19, 2021
1 parent 4a18384 commit 27398ae
Show file tree
Hide file tree
Showing 6 changed files with 408 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ Unreleased
~~~~~~~~~~
* Handle the case where certain toggle names come in as ``None`` when generating summary reports.

[4.1.0] - 2021-02-10
~~~~~~~~~~~~~~~~~~~~

* Expose toggle state report via a Python API.

[4.0.0] - 2021-01-24
~~~~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion edx_toggles/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
Library and utilities for feature toggles.
"""

__version__ = '4.0.0'
__version__ = '4.1.0'

default_app_config = 'edx_toggles.apps.TogglesConfig' # pylint: disable=invalid-name
133 changes: 133 additions & 0 deletions edx_toggles/tests/test_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""
Tests for waffle utils views.
"""
from django.conf import settings
from django.test import TestCase
from django.test.utils import override_settings
from waffle.testutils import override_switch

from edx_toggles.toggles import SettingDictToggle, SettingToggle, WaffleFlag
from edx_toggles.toggles.state import ToggleStateReport
from edx_toggles.toggles.testutils import override_waffle_flag

TEST_WAFFLE_FLAG = WaffleFlag("test.flag", __name__)


class ToggleStateTests(TestCase):
"""
Unit tests for the toggle state report.
"""
@override_waffle_flag(TEST_WAFFLE_FLAG, True)
def test_response_with_waffle_flag(self):
report = ToggleStateReport().as_dict()
self.assertIn("waffle_flags", report)
self.assertTrue(report["waffle_flags"])
waffle_names = [waffle["name"] for waffle in report["waffle_flags"]]
self.assertIn("test.flag", waffle_names)

@override_switch("test.switch", True)
def test_response_with_waffle_switch(self):
report = ToggleStateReport().as_dict()
self.assertIn("waffle_switches", report)
self.assertTrue(report["waffle_switches"])
waffle_names = [waffle["name"] for waffle in report["waffle_switches"]]
self.assertIn("test.switch", waffle_names)

def test_response_with_setting_toggle(self):
_toggle = SettingToggle("MYSETTING", default=False, module_name="module1")
with override_settings(MYSETTING=True):
report = ToggleStateReport().as_dict()

self.assertIn(
{
"name": "MYSETTING",
"is_active": True,
"module": "module1",
"class": "SettingToggle",
},
report["django_settings"],
)

def test_response_with_existing_setting_dict_toggle(self):
_toggle = SettingDictToggle("FEATURES", "MYFEATURE", default=True, module_name="module1")
report = ToggleStateReport().as_dict()
self.assertIn(
{
"name": "FEATURES['MYFEATURE']",
"is_active": True,
"module": "module1",
"class": "SettingDictToggle",
},
report["django_settings"],
)

def test_response_with_new_setting_dict_toggle(self):
_toggle = SettingDictToggle(
"CUSTOM_FEATURES", "MYSETTING", default=False, module_name="module1"
)
with override_settings(CUSTOM_FEATURES={"MYSETTING": True}):
report = ToggleStateReport().as_dict()

setting_dict = {toggle["name"]: toggle for toggle in report["django_settings"]}

self.assertEqual(
{
"name": "CUSTOM_FEATURES['MYSETTING']",
"is_active": True,
"module": "module1",
"class": "SettingDictToggle",
},
setting_dict["CUSTOM_FEATURES['MYSETTING']"],
)

def test_setting_overridden_by_setting_toggle(self):
_toggle2 = SettingToggle("MYSETTING2", module_name="module1")
_toggle3 = SettingDictToggle("MYDICT", "MYSETTING3", module_name="module1")
with override_settings(
MYSETTING1=True, MYSETTING2=False, MYDICT={"MYSETTING3": False}
):
# Need to pre-load settings, otherwise they are not picked up by the view
self.assertTrue(settings.MYSETTING1)
report = ToggleStateReport().as_dict()

setting_dict = {toggle["name"]: toggle for toggle in report["django_settings"]}

# Check that Django settings for which a SettingToggle exists have both the correct is_active and class values
self.assertTrue(setting_dict["MYSETTING1"]["is_active"])
self.assertNotIn("class", setting_dict["MYSETTING1"])
self.assertFalse(setting_dict["MYSETTING2"]["is_active"])
self.assertEqual("SettingToggle", setting_dict["MYSETTING2"]["class"])
self.assertFalse(setting_dict["MYDICT['MYSETTING3']"]["is_active"])
self.assertEqual(
"SettingDictToggle", setting_dict["MYDICT['MYSETTING3']"]["class"]
)

def test_no_duplicate_setting_toggle(self):
_toggle1 = SettingToggle("MYSETTING1", module_name="module1")
_toggle2 = SettingDictToggle("MYDICT", "MYSETTING2", module_name="module1")
with override_settings(MYSETTING1=True, MYDICT={"MYSETTING2": False}):
report = ToggleStateReport().as_dict()

# Check there are no duplicate setting/toggle
response_toggles_1 = [
toggle
for toggle in report["django_settings"]
if toggle["name"] == "MYSETTING1"
]
response_toggles_2 = [
toggle
for toggle in report["django_settings"]
if toggle["name"] == "MYDICT['MYSETTING2']"
]
self.assertEqual(1, len(response_toggles_1))
self.assertEqual(1, len(response_toggles_2))

def test_code_owners_without_module_information(self):
# Create a waffle flag without any associated module_name
waffle_flag = WaffleFlag("test.flag2", module_name="module1")
report = ToggleStateReport().as_dict()

result = [
flag for flag in report["waffle_flags"] if flag["name"] == waffle_flag.name
][0]
self.assertNotIn("code_owner", result)
4 changes: 4 additions & 0 deletions edx_toggles/toggles/state/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""
Expose public feature toggle state API.
"""
from .internal.report import ToggleStateReport, get_or_create_toggle_response
Empty file.
Loading

0 comments on commit 27398ae

Please sign in to comment.