Skip to content

Commit

Permalink
Merge pull request #114 from jamesmallen/feature/bare-status
Browse files Browse the repository at this point in the history
Feature/bare status
  • Loading branch information
mwarkentin authored Jan 31, 2018
2 parents 4defc4a + ae48537 commit 806e2e9
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 99 deletions.
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ Contributors
* Daniel Widerin - https://github.com/saily
* Ryan Wilson-Perkin - https://github.com/ryanwilsonperkin
* David Hoffman - https://github.com/dhoffman34
* James M. Allen - https://github.com/jamesmallen
6 changes: 6 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
History
=======

0.15.0 (Unreleased)
-------------------

* [`#114 <https://github.com/mwarkentin/django-watchman/pull/114>`_] Add "bare" status view (@jamesmallen)


0.14.0 (2018-01-09)
-------------------

Expand Down
12 changes: 12 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,17 @@ requests, you can call ping:
It will return the text ``pong`` with a 200 status code. Calling this doesn't
run any of the checks.

Bare status view
****************

If you would like a "bare" status view (one that doesn't report any details,
just ``HTTP 200`` if checks pass, and ``HTTP 500`` if any checks fail), you
can use the ``bare_status`` view by putting the following into ``urls.py``::

import watchman.views
# ...
url(r'^status/?$', watchman.views.bare_status),

Django management command
*************************

Expand Down Expand Up @@ -326,3 +337,4 @@ Instructions
2. Visit watchman json endpoint in your browser: http://127.0.0.1:8000/watchman/
3. Visit watchman dashboard in your browser: http://127.0.0.1:8000/watchman/dashboard/
4. Visit watchman ping in your browser: http://127.0.0.1:8000/watchman/ping/
5. Visit watchman bare status in your browser: http://127.0.0.1:8000/watchman/bare/
3 changes: 3 additions & 0 deletions sample_project/sample_project/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from django.conf.urls import include, url
from django.contrib import admin
import watchman.views


urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^watchman/', include('watchman.urls')),
url(r'^watchman/bare/', watchman.views.bare_status, name='bare_status'),
]
49 changes: 49 additions & 0 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,55 @@ def test_returns_pong(self):
self.assertEqual(response['Content-Type'], 'text/plain')


class TestBareStatus(unittest.TestCase):
def setUp(self):
# Ensure that every test executes with separate settings
reload_settings()

def test_bare_status_success(self):
request = RequestFactory().get('/')
response = views.bare_status(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content.decode(), '')

@patch('watchman.checks._check_databases')
@override_settings(WATCHMAN_ERROR_CODE=503)
def test_bare_status_error(self, patched_check_databases):
reload_settings()
# Fake a DB error, ensure we get our error code
patched_check_databases.return_value = [{
"foo": {
"ok": False,
"error": "Fake DB Error",
"stacktrace": "Fake DB Stack Trace",
},
}]
request = RequestFactory().get('/', data={
'check': 'watchman.checks.databases',
})
response = views.bare_status(request)
self.assertEqual(response.status_code, 503)
self.assertEqual(response.content.decode(), '')

@patch('watchman.checks._check_databases')
def test_bare_status_default_error(self, patched_check_databases):
reload_settings()
# Fake a DB error, ensure we get our error code
patched_check_databases.return_value = [{
"foo": {
"ok": False,
"error": "Fake DB Error",
"stacktrace": "Fake DB Stack Trace",
},
}]
request = RequestFactory().get('/', data={
'check': 'watchman.checks.databases',
})
response = views.bare_status(request)
self.assertEqual(response.status_code, 500)
self.assertEqual(response.content.decode(), '')


class TestEmailCheck(DjangoTestCase):
def setUp(self):
# Ensure that every test executes with separate settings
Expand Down
16 changes: 8 additions & 8 deletions watchman/templates/watchman/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ <h3 class="{% if overall_status %}text-success{% else %}text-danger{% endif %}">
</tr>
</thead>
<tbody>
{% for type in checks %}
{% for type_name, type in checks.items %}
{% for status in type.statuses %}
<tr>
<td class="{% if type.ok %}success{% else %}{% if not status.ok %}danger{% else %}warning{% endif %}{# not status.ok #}{% endif %}{# type.ok #}">{{ type.type_singular|title }}</td>
<td class="{% if type.ok %}success{% else %}{% if not status.ok %}danger{% else %}warning{% endif %}{# not status.ok #}{% endif %}{# type.ok #}">{{ type_name|title }}</td>
<td class="{% if status.ok %}success{% else %}danger{% endif %}">{{ status.name }}</td>

{% if status.ok %}
Expand All @@ -64,7 +64,7 @@ <h3 class="{% if overall_status %}text-success{% else %}text-danger{% endif %}">
{% trans "ERROR!" %}

<div class="btn-group btn-group-xs pull-right">
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#{{ type.type }}{% if status.name %}-{{ status.name }}{% endif %}">{% trans "Traceback" %}</button>
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#{{ type_name }}{% if status.name %}-{{ status.name }}{% endif %}">{% trans "Traceback" %}</button>
</div>
</td>
{% endif %}{# status.ok #}
Expand All @@ -75,7 +75,7 @@ <h3 class="{% if overall_status %}text-success{% else %}text-danger{% endif %}">
<tr>
<td colspan="3">{% trans "No checks indicated." %}</td>
</tr>
{% endfor %}{# for type in checks #}
{% endfor %}{# for type_name, type in checks.items #}
</tbody>
</table>
</div><!-- .col -->
Expand All @@ -84,15 +84,15 @@ <h3 class="{% if overall_status %}text-success{% else %}text-danger{% endif %}">


{% block error_content %}
{% for type in checks %}
{% for type_name, type in checks.items %}
{% for status in type.statuses %}
{% if not status.ok %}
<div class="modal fade" id="{{ type.type }}{% if status.name %}-{{ status.name }}{% endif %}" tabindex="-1" role="dialog" aria-labelledby="{{ type.type }}{% if status.name %}-{{ status.name }}{% endif %}-title">
<div class="modal fade" id="{{ type_name }}{% if status.name %}-{{ status.name }}{% endif %}" tabindex="-1" role="dialog" aria-labelledby="{{ type_name }}{% if status.name %}-{{ status.name }}{% endif %}-title">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="{{ type.type }}{% if status.name %}-{{ status.name }}{% endif %}-title">{{ type.type|title }}{% if status.name %} - {{ status.name|title }}{% endif %}</h4>
<h4 class="modal-title" id="{{ type_name }}{% if status.name %}-{{ status.name }}{% endif %}-title">{{ type_name|title }}{% if status.name %} - {{ status.name|title }}{% endif %}</h4>
</div><!-- class="modal-header" -->
<div class="modal-body">
<h4><pre>{{ status.error }}</pre></h4>
Expand All @@ -105,7 +105,7 @@ <h4><pre>{{ status.error }}</pre></h4>
</div><!-- class="modal-dialog" -->
</div><!-- class="modal fade" -->
{% endif %}{# not status.ok #}
{% endfor %}{# for type in checks #}
{% endfor %}{# for status in type.statuses #}
{% endfor %}{# for type_name, type in checks.items #}
{% endblock %}{# error_content #}
{% endblock %}{# content #}
172 changes: 81 additions & 91 deletions watchman/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,11 @@ def _deprecation_warnings():
warnings.warn("`WATCHMAN_TOKEN` setting is deprecated, use `WATCHMAN_TOKENS` instead. It will be removed in django-watchman 1.0", DeprecationWarning)


@auth
@json_view
@non_atomic_requests
def status(request):
def run_checks(request):
_deprecation_warnings()

response = {}
http_code = 200
checks = {}
ok = True

check_list, skip_list = _get_check_params(request)

Expand All @@ -55,109 +52,102 @@ def status(request):
if type(_check[_type]) == dict:
result = _check[_type]
if not result['ok']:
http_code = settings.WATCHMAN_ERROR_CODE
ok = False
elif type(_check[_type]) == list:
for entry in _check[_type]:
for result in entry:
if not entry[result]['ok']:
http_code = settings.WATCHMAN_ERROR_CODE
response.update(_check)
ok = False
checks.update(_check)

return checks, ok


if len(response) == 0:
@auth
@json_view
@non_atomic_requests
def status(request):
checks, ok = run_checks(request)

if not checks:
raise Http404(_('No checks found'))
http_code = 200 if ok else settings.WATCHMAN_ERROR_CODE
return checks, http_code, {WATCHMAN_VERSION_HEADER: __version__}


return response, http_code, {WATCHMAN_VERSION_HEADER: __version__}
@non_atomic_requests
def bare_status(request):
checks, ok = run_checks(request)
http_code = 200 if ok else settings.WATCHMAN_ERROR_CODE
return HttpResponse(status=http_code, content_type='text/plain')


def ping(request):
_deprecation_warnings()

return HttpResponse('pong', content_type='text/plain')


@auth
@non_atomic_requests
def dashboard(request):
_deprecation_warnings()

check_types = []

check_list, skip_list = _get_check_params(request)

for check in get_checks(check_list=check_list, skip_list=skip_list):
if callable(check):
_check = check()

for _type in _check:
# For other systems (eg: email, storage) _check[_type] is a
# dictionary of status
#
# Example:
# {
# 'ok': True, # Status
# }
#
# Example:
# {
# 'ok': False, # Status
# 'error': "RuntimeError",
# 'stacktrace': "...",
# }
#
# For some systems (eg: cache, database) _check[_type] is a
# list of dictionaries of dictionaries of statuses
#
# Example:
# [
# {
# 'default': { # Cache/database name
# 'ok': True, # Status
# }
# },
# {
# 'non-default': { # Cache/database name
# 'ok': False, # Status
# 'error': "RuntimeError",
# 'stacktrace': "...",
# }
# },
# ]
#
statuses = []

if type(_check[_type]) == dict:
result = _check[_type]
statuses = [{
'name': '',
'ok': result['ok'],
'error': '' if result['ok'] else result['error'],
'stacktrace': '' if result['ok'] else result['stacktrace'],
}]

type_overall_status = _check[_type]['ok']

elif type(_check[_type]) == list:
for result in _check[_type]:
for name in result:
statuses.append({
'name': name,
'ok': result[name]['ok'],
'error': '' if result[name]['ok'] else result[name]['error'],
'stacktrace': '' if result[name]['ok'] else result[name]['stacktrace'],
})

type_overall_status = all(s['ok'] for s in statuses)

check_types.append({
'type': _type,
'type_singular': _type[:-1] if _type.endswith('s') else _type,
'ok': type_overall_status,
'statuses': statuses})

overall_status = all(type_status['ok'] for type_status in check_types)
checks, overall_status = run_checks(request)

expanded_checks = {}
for key, value in checks.items():
if isinstance(value, dict):
# For some systems (eg: email, storage) value is a
# dictionary of status
#
# Example:
# {
# 'ok': True, # Status
# }
#
# Example:
# {
# 'ok': False, # Status
# 'error': "RuntimeError",
# 'stacktrace': "...",
# }
single_status = value.copy()
single_status['name'] = ''
expanded_check = {
'ok': value['ok'],
'statuses': [single_status],
}
else:
# For other systems (eg: cache, database) value is a
# list of dictionaries of dictionaries of statuses
#
# Example:
# [
# {
# 'default': { # Cache/database name
# 'ok': True, # Status
# }
# },
# {
# 'non-default': { # Cache/database name
# 'ok': False, # Status
# 'error': "RuntimeError",
# 'stacktrace': "...",
# }
# },
# ]
statuses = []
for outer_status in value:
for name, inner_status in outer_status.items():
detail = inner_status.copy()
detail['name'] = name
statuses.append(detail)

expanded_check = {
'ok': all(detail['ok'] for detail in statuses),
'statuses': statuses,
}
expanded_checks[key] = expanded_check

response = render(request, 'watchman/dashboard.html', {
'checks': check_types,
'checks': expanded_checks,
'overall_status': overall_status
})

Expand Down

0 comments on commit 806e2e9

Please sign in to comment.