From 22d4d5534073b7b2812ccc5612a8c5f9d7b38ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Thu, 5 Dec 2019 12:27:37 +0200 Subject: [PATCH] Added support for Shields.io badges. cc: #304, #305 --- CHANGELOG.md | 1 + hc/api/tests/test_badge.py | 4 ++++ hc/api/urls.py | 16 ++-------------- hc/api/views.py | 18 +++++++++++++++--- hc/front/views.py | 3 ++- hc/lib/badges.py | 8 +++----- static/css/settings.css | 6 +++++- static/js/badges.js | 11 +++++++++-- templates/front/badges.html | 35 +++++++++++++++++++++++++++++++++-- 9 files changed, 74 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b880e08..be737b34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ### Improvements - "Filtering Rules" dialog, an option to require HTTP POST (#297) - Show Healthchecks version in Django admin header (#306) +- Added JSON endpoint for Shields.io (#304) ## v1.11.0 - 2019-11-22 diff --git a/hc/api/tests/test_badge.py b/hc/api/tests/test_badge.py index c79c9675..de33b3a9 100644 --- a/hc/api/tests/test_badge.py +++ b/hc/api/tests/test_badge.py @@ -27,6 +27,10 @@ class BadgeTestCase(BaseTestCase): self.assertEqual(r["Access-Control-Allow-Origin"], "*") self.assertContains(r, "#4c1") + def test_it_rejects_bad_format(self): + r = self.client.get(self.json_url + "foo") + self.assertEqual(r.status_code, 404) + def test_it_handles_options(self): r = self.client.options(self.svg_url) self.assertEqual(r.status_code, 204) diff --git a/hc/api/urls.py b/hc/api/urls.py index ddb3c154..5ff1b5f3 100644 --- a/hc/api/urls.py +++ b/hc/api/urls.py @@ -27,27 +27,15 @@ urlpatterns = [ path("api/v1/notifications//bounce", views.bounce, name="hc-api-bounce"), path("api/v1/channels/", views.channels), path( - "badge///.svg", + "badge///.", views.badge, name="hc-badge", ), path( - "badge//.svg", + "badge//.", views.badge, {"tag": "*"}, name="hc-badge-all", ), - path( - "badge///.json", - views.badge, - {"format": "json"}, - name="hc-badge-json", - ), - path( - "badge//.json", - views.badge, - {"format": "json", "tag": "*"}, - name="hc-badge-json-all", - ), path("api/v1/status/", views.status), ] diff --git a/hc/api/views.py b/hc/api/views.py index 00496930..b6f07a98 100644 --- a/hc/api/views.py +++ b/hc/api/views.py @@ -203,10 +203,13 @@ def pause(request, code): @never_cache @cors("GET") -def badge(request, badge_key, signature, tag, format="svg"): +def badge(request, badge_key, signature, tag, fmt="svg"): if not check_signature(badge_key, tag, signature): return HttpResponseNotFound() + if fmt not in ("svg", "json", "shields"): + return HttpResponseNotFound() + q = Check.objects.filter(project__badge_key=badge_key) if tag != "*": q = q.filter(tags__contains=tag) @@ -225,7 +228,7 @@ def badge(request, badge_key, signature, tag, format="svg"): if check_status == "down": down += 1 status = "down" - if format == "svg": + if fmt == "svg": # For SVG badges, we can leave the loop as soon as we # find the first "down" break @@ -234,7 +237,16 @@ def badge(request, badge_key, signature, tag, format="svg"): if status == "up": status = "late" - if format == "json": + if fmt == "shields": + color = "success" + if status == "down": + color = "critical" + elif status == "late": + color = "important" + + return JsonResponse({"label": label, "message": status, "color": color}) + + if fmt == "json": return JsonResponse( {"status": status, "total": total, "grace": grace, "down": down} ) diff --git a/hc/front/views.py b/hc/front/views.py index 7c9f050b..7aa3ea95 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -590,7 +590,8 @@ def badges(request, code): { "tag": tag, "svg": get_badge_url(project.badge_key, tag), - "json": get_badge_url(project.badge_key, tag, format="json"), + "json": get_badge_url(project.badge_key, tag, fmt="json"), + "shields": get_badge_url(project.badge_key, tag, fmt="shields"), } ) diff --git a/hc/lib/badges.py b/hc/lib/badges.py index 8847e882..c503df77 100644 --- a/hc/lib/badges.py +++ b/hc/lib/badges.py @@ -103,14 +103,12 @@ def check_signature(username, tag, sig): return ours == sig -def get_badge_url(username, tag, format="svg"): +def get_badge_url(username, tag, fmt="svg"): sig = base64_hmac(str(username), tag, settings.SECRET_KEY) if tag == "*": - view = "hc-badge-json-all" if format == "json" else "hc-badge-all" - url = reverse(view, args=[username, sig[:8]]) + url = reverse("hc-badge-all", args=[username, sig[:8], fmt]) else: - view = "hc-badge-json" if format == "json" else "hc-badge" - url = reverse(view, args=[username, sig[:8], tag]) + url = reverse("hc-badge", args=[username, sig[:8], tag, fmt]) return settings.SITE_ROOT + url diff --git a/static/css/settings.css b/static/css/settings.css index 7172b16c..e0bd28f5 100644 --- a/static/css/settings.css +++ b/static/css/settings.css @@ -37,10 +37,14 @@ padding-top: 32px; } -#badges-json { +#badges-json, #badges-shields { display: none; } +#badges-shields label:first-child { + margin: 20px 0 10px 0; +} + .json-response code { display: inline-block; background: #eee; diff --git a/static/js/badges.js b/static/js/badges.js index 2701334c..1b83d549 100644 --- a/static/js/badges.js +++ b/static/js/badges.js @@ -7,13 +7,20 @@ $(function() { }); $("#show-svg").click(function() { - $("#badges-json").hide(); $("#badges-svg").show(); + $("#badges-json").hide(); + $("#badges-shields").hide(); }) $("#show-json").click(function() { $("#badges-svg").hide(); $("#badges-json").show(); + $("#badges-shields").hide(); }) -}); \ No newline at end of file + $("#show-shields").click(function() { + $("#badges-svg").hide(); + $("#badges-json").hide(); + $("#badges-shields").show(); + }) +}); diff --git a/templates/front/badges.html b/templates/front/badges.html index 9270ca90..6562208d 100644 --- a/templates/front/badges.html +++ b/templates/front/badges.html @@ -24,6 +24,9 @@ + @@ -44,7 +47,7 @@ - @@ -67,12 +70,40 @@ - {% endfor %}
+ {{ urldict.svg }}
+ {{ urldict.json }}
+ +
+ + {% if have_tags %} + + + + + {% endif %} + + {% for urldict in badges %} + {% if urldict.tag == "*" %} + + + + {% endif %} + + + + + + {% endfor %} +
Shields.io badgeJSON endpoint for Shields.io (how to use)
Overall Status
+ + + {{ urldict.shields }} +
+
{% endblock %}