Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose JSON endpoint for Waffle flag/switch/sample state #452

Merged
merged 6 commits into from
Jul 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/usage/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ JavaScript.
mixins
templates
javascript
json
cli
62 changes: 62 additions & 0 deletions docs/usage/json.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.. _usage-json:

=====================
Waffle Status as JSON
=====================

Although :doc:`WaffleJS<javascript>` returns the status of all
:ref:`flags <types-flag>`, :ref:`switches <types-switch>`, and
:ref:`samples <types-sample>`, it does so by exposing a Javascript
object, rather than returning the data in a directly consumable format.

In cases where a directly consumable format is preferrable,
Waffle also exposes this data as JSON via the ``waffle_status`` view.


Using the view
--------------

Using the ``waffle_status`` view requires adding Waffle to your URL
configuration. For example, in your ``ROOT_URLCONF``::

urlpatterns = patterns('',
(r'^', include('waffle.urls')),
)

This adds a route called ``waffle_status``, which will return the current
status of each flag, switch, and sample as JSON, with the following structure:

.. code-block:: json

{
"flags": {
"flag_active": {
"is_active": true,
"last_modified": "2020-01-01T12:00:00.000"
},
"flag_inactive": {
"is_active": false,
"last_modified": "2020-01-01T12:00:00.000"
}
},
"switches": {
"switch_active": {
"is_active": true,
"last_modified": "2020-01-01T12:00:00.000"
},
"switch_inactive": {
"is_active": false,
"last_modified": "2020-01-01T12:00:00.000"
}
},
"samples": {
"sample_active": {
"is_active": true,
"last_modified": "2020-01-01T12:00:00.000"
},
"sample_inactive": {
"is_active": false,
"last_modified": "2020-01-01T12:00:00.000"
}
}
}
14 changes: 11 additions & 3 deletions waffle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,32 @@


def flag_is_active(request, flag_name, read_only=False):
flag = get_waffle_model('FLAG_MODEL').get(flag_name)
flag = get_waffle_flag_model().get(flag_name)
return flag.is_active(request, read_only=read_only)


def switch_is_active(switch_name):
switch = get_waffle_model('SWITCH_MODEL').get(switch_name)
switch = get_waffle_switch_model().get(switch_name)
return switch.is_active()


def sample_is_active(sample_name):
sample = get_waffle_model('SAMPLE_MODEL').get(sample_name)
sample = get_waffle_sample_model().get(sample_name)
return sample.is_active()


def get_waffle_flag_model():
return get_waffle_model('FLAG_MODEL')


def get_waffle_switch_model():
return get_waffle_model('SWITCH_MODEL')


def get_waffle_sample_model():
return get_waffle_model('SAMPLE_MODEL')


def get_waffle_model(setting_name):
"""
Returns the waffle Flag model that is active in this project.
Expand Down
37 changes: 36 additions & 1 deletion waffle/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.urls import reverse

from waffle import get_waffle_flag_model
from waffle import get_waffle_flag_model, get_waffle_sample_model, get_waffle_switch_model
from waffle.models import Sample, Switch
from waffle.tests.base import TestCase

Expand All @@ -14,6 +14,41 @@ def test_wafflejs(self):
for control in response['cache-control'].split(',')]
self.assertIn('max-age=0', cache_control)

def test_waffle_status(self):
response = self.client.get(reverse('waffle_status'))
self.assertEqual(200, response.status_code)
self.assertEqual('application/json', response['content-type'])
cache_control = [control.strip()
for control in response['cache-control'].split(',')]
self.assertIn('max-age=0', cache_control)

def test_waffle_status_response(self):
get_waffle_flag_model().objects.create(name='test_flag_active', everyone=True)
get_waffle_flag_model().objects.create(name='test_flag_inactive', everyone=False)
get_waffle_switch_model().objects.create(name='test_switch_active', active=True)
get_waffle_switch_model().objects.create(name='test_switch_inactive', active=False)
get_waffle_sample_model().objects.create(name='test_sample_active', percent=100)
get_waffle_sample_model().objects.create(name='test_sample_inactive', percent=0)

response = self.client.get(reverse('waffle_status'))
self.assertEqual(200, response.status_code)
content = response.json()

assert 'test_flag_active' in content['flags'].keys()
assert content['flags']['test_flag_active']['is_active']
assert 'test_flag_inactive' in content['flags'].keys()
assert not content['flags']['test_flag_inactive']['is_active']

assert 'test_switch_active' in content['switches'].keys()
assert content['switches']['test_switch_active']['is_active']
assert 'test_switch_inactive' in content['switches'].keys()
assert not content['switches']['test_switch_inactive']['is_active']

assert 'test_sample_active' in content['samples'].keys()
assert content['samples']['test_sample_active']['is_active']
assert 'test_sample_inactive' in content['samples'].keys()
assert not content['samples']['test_sample_inactive']['is_active']

def test_flush_all_flags(self):
"""Test the 'FLAGS_ALL' list gets invalidated correctly."""
get_waffle_flag_model().objects.create(name='myflag1', everyone=True)
Expand Down
3 changes: 2 additions & 1 deletion waffle/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from django.urls import path

from waffle.views import wafflejs
from waffle.views import wafflejs, waffle_json

urlpatterns = [
path('wafflejs', wafflejs, name='wafflejs'),
path('waffle_status', waffle_json, name='waffle_status'),
]
49 changes: 44 additions & 5 deletions waffle/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from django.http import HttpResponse
from django.http import HttpResponse, JsonResponse
from django.template import loader
from django.views.decorators.cache import never_cache

from waffle import get_waffle_flag_model
from waffle.models import Sample, Switch
from waffle import get_waffle_flag_model, get_waffle_switch_model, get_waffle_sample_model
from waffle.utils import get_setting


Expand All @@ -17,10 +16,10 @@ def _generate_waffle_js(request):
flags = get_waffle_flag_model().get_all()
flag_values = [(f.name, f.is_active(request)) for f in flags]

switches = Switch.get_all()
switches = get_waffle_switch_model().get_all()
switch_values = [(s.name, s.is_active()) for s in switches]

samples = Sample.get_all()
samples = get_waffle_sample_model().get_all()
sample_values = [(s.name, s.is_active()) for s in samples]

return loader.render_to_string('waffle/waffle.js', {
Expand All @@ -31,3 +30,43 @@ def _generate_waffle_js(request):
'switch_default': get_setting('SWITCH_DEFAULT'),
'sample_default': get_setting('SAMPLE_DEFAULT'),
})


@never_cache
def waffle_json(request):
return JsonResponse(_generate_waffle_json(request))


def _generate_waffle_json(request):
flags = get_waffle_flag_model().get_all()
flag_values = {
f.name: {
'is_active': f.is_active(request),
'last_modified': f.modified,
}
for f in flags
}

switches = get_waffle_switch_model().get_all()
switch_values = {
s.name: {
'is_active': s.is_active(),
'last_modified': s.modified,
}
for s in switches
}

samples = get_waffle_sample_model().get_all()
sample_values = {
s.name: {
'is_active': s.is_active(),
'last_modified': s.modified,
}
for s in samples
}

return {
'flags': flag_values,
'switches': switch_values,
'samples': sample_values,
}