From d7948d9939633b806ef1585a7295a9abc579844b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= <cuu508@gmail.com> Date: Tue, 9 Apr 2024 12:39:42 +0300 Subject: [PATCH] Show status changes (flips) in check's log page Fixes: #447 --- CHANGELOG.md | 5 +++++ hc/front/forms.py | 3 ++- hc/front/tests/test_log_events.py | 21 +++++++++++++++++---- hc/front/views.py | 13 ++++++++++--- static/css/log.css | 11 ++++++++--- static/css/variables.css | 6 ++---- templates/front/log.html | 10 +++++++--- templates/front/log_row.html | 12 +++++++++--- 8 files changed, 60 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f22069d..9cac572d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog All notable changes to this project will be documented in this file. +## v3.4-dev - Unreleased + +### Improvements +- Show status changes (flips) in check's log page (#447) + ## v3.3 - 2024-04-03 ### Improvements diff --git a/hc/front/forms.py b/hc/front/forms.py index 827d5ffc..48435b18 100644 --- a/hc/front/forms.py +++ b/hc/front/forms.py @@ -391,6 +391,7 @@ class LogFiltersForm(forms.Form): log = forms.BooleanField(required=False) ign = forms.BooleanField(required=False) notification = forms.BooleanField(required=False) + flip = forms.BooleanField(required=False) def clean_u(self) -> datetime | None: if self.cleaned_data["u"]: @@ -403,7 +404,7 @@ class LogFiltersForm(forms.Form): return None def kinds(self) -> tuple[str, ...]: - kind_keys = ("success", "fail", "start", "log", "ign", "notification") + kind_keys = ("success", "fail", "start", "log", "ign", "notification", "flip") return tuple(key for key in kind_keys if self.cleaned_data[key]) diff --git a/hc/front/tests/test_log_events.py b/hc/front/tests/test_log_events.py index 23ecd8b4..354a25f4 100644 --- a/hc/front/tests/test_log_events.py +++ b/hc/front/tests/test_log_events.py @@ -6,7 +6,7 @@ from urllib.parse import urlencode from django.utils.timezone import now -from hc.api.models import Channel, Check, Notification, Ping +from hc.api.models import Channel, Check, Flip, Notification, Ping from hc.test import BaseTestCase @@ -21,6 +21,12 @@ class LogTestCase(BaseTestCase): ch.value = json.dumps({"value": "alice@example.org", "up": True, "down": True}) ch.save() + f = Flip(owner=self.check) + f.created = now() - td(hours=1) + f.old_status = "new" + f.new_status = "down" + f.save() + n = Notification(owner=self.check) n.created = now() - td(hours=1) n.channel = ch @@ -36,7 +42,7 @@ class LogTestCase(BaseTestCase): def url(self, **kwargs): params = {} - for key in ("success", "fail", "start", "log", "ign", "notification"): + for key in ("success", "fail", "start", "log", "ign", "notification", "flip"): if kwargs.get(key, True): params[key] = "on" for key in ("u", "end"): @@ -48,8 +54,9 @@ class LogTestCase(BaseTestCase): def test_it_works(self) -> None: self.client.login(username="alice@example.org", password="password") r = self.client.get(self.url()) - self.assertContains(r, "hello world") - self.assertContains(r, "Sent email to alice@example.org", status_code=200) + self.assertContains(r, "hello world", status_code=200) + self.assertContains(r, "Sent email to alice@example.org") + self.assertContains(r, "new ➔ down") def test_team_access_works(self) -> None: # Logging in as bob, not alice. Bob has team access so this @@ -128,3 +135,9 @@ class LogTestCase(BaseTestCase): r = self.client.get(self.url(notification=False)) self.assertContains(r, "hello world") self.assertNotContains(r, "Sent email to alice@example.org", status_code=200) + + def test_it_filters_flip(self) -> None: + self.client.login(username="alice@example.org", password="password") + r = self.client.get(self.url(flip=False)) + self.assertContains(r, "hello world") + self.assertNotContains(r, "new ➔ down") diff --git a/hc/front/views.py b/hc/front/views.py index dc830a3e..0483300a 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -51,6 +51,7 @@ from hc.api.models import ( MAX_DURATION, Channel, Check, + Flip, Notification, Ping, TokenBucket, @@ -847,7 +848,7 @@ def _get_events( start: datetime, end: datetime, kinds: tuple[str, ...] | None = None, -) -> list[Notification | Ping]: +) -> list[Notification | Ping | Flip]: # Sorting by "n" instead of "id" is important here. Both give the same # query results, but sorting by "id" can cause postgres to pick # api_ping.id index (slow if the api_ping table is big). Sorting by @@ -892,13 +893,19 @@ def _get_events( ping.duration = None alerts: list[Notification] = [] - if kinds is None or "notification" in kinds: + if kinds and "notification" in kinds: aq = check.notification_set.order_by("-created") aq = aq.filter(created__gte=start, created__lte=end, check_status="down") aq = aq.select_related("channel") alerts = list(aq[:page_limit]) - events = pings + alerts + flips: list[Flip] = [] + if kinds is None or "flip" in kinds: + fq = check.flip_set.order_by("-created") + fq = fq.filter(created__gte=start, created__lte=end) + flips = list(fq[:page_limit]) + + events = pings + alerts + flips events.sort(key=lambda el: el.created, reverse=True) return events[:page_limit] diff --git a/static/css/log.css b/static/css/log.css index 0be108d8..3ed5fe7f 100644 --- a/static/css/log.css +++ b/static/css/log.css @@ -82,9 +82,11 @@ } #log tr.missing td { - color: #d9534f; - background: var(--log-missing-bg); - border-bottom: 1px solid var(--log-missing-border); + color: #a94442; +} + +#log tr.flip { + background: var(--log-flip-bg); } #log tr.missing td:nth-child(4) { @@ -105,3 +107,6 @@ line-height: 1; } +#filters hr { + margin: 10px; +} \ No newline at end of file diff --git a/static/css/variables.css b/static/css/variables.css index 95c2c409..1218a06f 100644 --- a/static/css/variables.css +++ b/static/css/variables.css @@ -40,8 +40,7 @@ --label-start-color: #117a3f; --link-color: #0091EA; --link-hover-color: #00629e; - --log-missing-bg: #fff3f2; - --log-missing-border: #fbe2e0; + --log-flip-bg: #eee; --modal-content-bg: #fff; --nav-link-hover-bg: #eee; --panel-bg: #fff; @@ -123,8 +122,7 @@ body.dark { --label-start-color: #e0e0e2; --link-color: hsl(202.8, 100%, 55%); --link-hover-color: hsl(202.8, 100%, 65%); - --log-missing-bg: #2d1e21; - --log-missing-border: #403033; + --log-flip-bg: #383840; --modal-content-bg: #1f1f22; --nav-link-hover-bg: #383840; --panel-bg: hsl(240, 6%, 16%); diff --git a/templates/front/log.html b/templates/front/log.html index 41523704..47cc5bee 100644 --- a/templates/front/log.html +++ b/templates/front/log.html @@ -84,7 +84,7 @@ <br> <label>Event types</label> <div class="checkbox"> - <label><input type="checkbox" name="success" autocomplete="off" checked> OK</label> + <label><input type="checkbox" name="success" autocomplete="off" checked> Success</label> </div> <div class="checkbox"> <label><input type="checkbox" name="fail" autocomplete="off" checked> Failure</label> @@ -96,10 +96,14 @@ <label><input type="checkbox" name="log" autocomplete="off" checked> Log</label> </div> <div class="checkbox"> - <label><input type="checkbox" name="ign" autocomplete="off" checked> Ignored</label> + <label><input type="checkbox" name="ign" autocomplete="off" checked> Ignored ping</label> + </div> + <hr> + <div class="checkbox"> + <label><input type="checkbox" name="flip" autocomplete="off" checked> Status change</label> </div> <div class="checkbox"> - <label><input type="checkbox" name="notification" autocomplete="off" checked> Notification</label> + <label><input type="checkbox" name="notification" autocomplete="off"> Downtime alert</label> </div> </form> </div> diff --git a/templates/front/log_row.html b/templates/front/log_row.html index 8f737eb3..cfc2a572 100644 --- a/templates/front/log_row.html +++ b/templates/front/log_row.html @@ -24,8 +24,14 @@ {% elif event.check_status %} <tr class="missing" data-dt="{{ event.created|timestamp }}"> <td><span class="ic-missing"></span></td> - <td></td> - <td></td> + <td></td><td></td> <td colspan="2">{% include "front/event_summary.html" %}</td> </tr> -{% endif %} \ No newline at end of file +{% elif event.new_status %} +<tr class="flip" data-dt="{{ event.created|timestamp }}"> + <td></td><td></td><td></td> + <td colspan="2"> + Status: <strong>{{ event.old_status }} ➔ {{ event.new_status }}</strong>. + </td> +</tr> +{% endif %}