From 0d924f462754c99971ea55860492394c6c308573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Tue, 3 Sep 2019 13:46:41 +0300 Subject: [PATCH] Add the "Last Duration" field in the "My Checks" page. Add "last_duration" attribute to the Check API resource. Fixes #257 --- CHANGELOG.md | 7 ++++++ hc/api/migrations/0063_auto_20190903_0901.py | 23 ++++++++++++++++++++ hc/api/models.py | 16 +++++++++++++- hc/api/tests/test_ping.py | 10 +++++++++ hc/front/views.py | 17 +++++++++------ static/css/my_checks_desktop.css | 6 ++++- templates/front/last_ping_cell.html | 7 +++++- templates/front/my_checks_desktop.html | 8 +++++-- 8 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 hc/api/migrations/0063_auto_20190903_0901.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c2272a4..6bc212e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog All notable changes to this project will be documented in this file. +## Unreleased + +### Improvements + - Add the "Last Duration" field in the "My Checks" page (#257) + - Add "last_duration" attribute to the Check API resource (#257) + + ## 1.9.0 - 2019-09-03 ### Improvements diff --git a/hc/api/migrations/0063_auto_20190903_0901.py b/hc/api/migrations/0063_auto_20190903_0901.py new file mode 100644 index 00000000..240bb886 --- /dev/null +++ b/hc/api/migrations/0063_auto_20190903_0901.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.5 on 2019-09-03 09:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0062_auto_20190720_1350'), + ] + + operations = [ + migrations.AddField( + model_name='check', + name='last_duration', + field=models.DurationField(blank=True, null=True), + ), + migrations.AlterField( + model_name='channel', + name='kind', + field=models.CharField(choices=[('email', 'Email'), ('webhook', 'Webhook'), ('hipchat', 'HipChat'), ('slack', 'Slack'), ('pd', 'PagerDuty'), ('pagertree', 'PagerTree'), ('pagerteam', 'Pager Team'), ('po', 'Pushover'), ('pushbullet', 'Pushbullet'), ('opsgenie', 'OpsGenie'), ('victorops', 'VictorOps'), ('discord', 'Discord'), ('telegram', 'Telegram'), ('sms', 'SMS'), ('zendesk', 'Zendesk'), ('trello', 'Trello'), ('matrix', 'Matrix'), ('whatsapp', 'WhatsApp'), ('apprise', 'Apprise'), ('mattermost', 'Mattermost')], max_length=20), + ), + ] diff --git a/hc/api/models.py b/hc/api/models.py index 7e8114d1..e8046ee4 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -21,6 +21,8 @@ DEFAULT_TIMEOUT = td(days=1) DEFAULT_GRACE = td(hours=1) NEVER = datetime(3000, 1, 1, tzinfo=pytz.UTC) CHECK_KINDS = (("simple", "Simple"), ("cron", "Cron")) +# max time between start and ping where we will consider both events related: +MAX_DELTA = td(hours=24) CHANNEL_KINDS = ( ("email", "Email"), @@ -71,6 +73,7 @@ class Check(models.Model): n_pings = models.IntegerField(default=0) last_ping = models.DateTimeField(null=True, blank=True) last_start = models.DateTimeField(null=True, blank=True) + last_duration = models.DurationField(null=True, blank=True) last_ping_was_fail = models.NullBooleanField(default=False) has_confirmation_link = models.BooleanField(default=False) alert_after = models.DateTimeField(null=True, blank=True, editable=False) @@ -105,6 +108,10 @@ class Check(models.Model): def email(self): return "%s@%s" % (self.code, settings.PING_EMAIL_DOMAIN) + def clamped_last_duration(self): + if self.last_duration and self.last_duration < MAX_DELTA: + return self.last_duration + def get_grace_start(self): """ Return the datetime when the grace period starts. @@ -200,6 +207,9 @@ class Check(models.Model): "next_ping": isostring(self.get_grace_start()), } + if self.last_duration: + result["last_duration"] = int(self.last_duration.total_seconds()) + if readonly: code_half = self.code.hex[:16] result["unique_key"] = hashlib.sha1(code_half.encode()).hexdigest() @@ -227,8 +237,12 @@ class Check(models.Model): elif action == "ign": pass else: - self.last_start = None self.last_ping = timezone.now() + if self.last_start: + self.last_duration = self.last_ping - self.last_start + self.last_start = None + else: + self.last_duration = None new_status = "down" if action == "fail" else "up" if self.status != new_status: diff --git a/hc/api/tests/test_ping.py b/hc/api/tests/test_ping.py index ec1ee7bb..fb183bc6 100644 --- a/hc/api/tests/test_ping.py +++ b/hc/api/tests/test_ping.py @@ -176,3 +176,13 @@ class PingTestCase(BaseTestCase): self.check.refresh_from_db() self.assertTrue(self.check.last_start) self.assertEqual(self.check.status, "paused") + + def test_it_sets_last_duration(self): + self.check.last_start = now() - td(seconds=10) + self.check.save() + + r = self.client.get("/ping/%s/" % self.check.code) + self.assertEqual(r.status_code, 200) + + self.check.refresh_from_db() + self.assertTrue(self.check.last_duration.total_seconds() >= 10) diff --git a/hc/front/views.py b/hc/front/views.py index 0f446e0c..e3b62b22 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -26,6 +26,7 @@ from hc.accounts.models import Project from hc.api.models import ( DEFAULT_GRACE, DEFAULT_TIMEOUT, + MAX_DELTA, Channel, Check, Ping, @@ -60,8 +61,6 @@ STATUS_TEXT_TMPL = get_template("front/log_status_text.html") LAST_PING_TMPL = get_template("front/last_ping_cell.html") EVENTS_TMPL = get_template("front/details_events.html") DOWNTIMES_TMPL = get_template("front/details_downtimes.html") -ONE_HOUR = td(hours=1) -TWELVE_HOURS = td(hours=12) def _tags_statuses(checks): @@ -155,6 +154,13 @@ def my_checks(request, code): if search not in search_key: hidden_checks.add(check) + # Do we need to show the "Last Duration" header? + show_last_duration = False + for check in checks: + if check.clamped_last_duration(): + show_last_duration = True + break + ctx = { "page": "checks", "checks": checks, @@ -170,6 +176,7 @@ def my_checks(request, code): "selected_tags": selected_tags, "search": search, "hidden_checks": hidden_checks, + "show_last_duration": show_last_duration, } return render(request, "front/my_checks.html", ctx) @@ -421,10 +428,6 @@ def remove_check(request, code): def _get_events(check, limit): - # max time between start and ping where we will consider - # the both events related. - max_delta = min(ONE_HOUR + check.grace, TWELVE_HOURS) - pings = Ping.objects.filter(owner=check).order_by("-id")[:limit] pings = list(pings) @@ -432,7 +435,7 @@ def _get_events(check, limit): for ping in pings: if ping.kind == "start" and prev and prev.kind != "start": delta = prev.created - ping.created - if delta < max_delta: + if delta < MAX_DELTA: setattr(prev, "delta", delta) prev = ping diff --git a/static/css/my_checks_desktop.css b/static/css/my_checks_desktop.css index a586c63d..e507ef4a 100644 --- a/static/css/my_checks_desktop.css +++ b/static/css/my_checks_desktop.css @@ -67,7 +67,6 @@ } .timeout-grace .cron-expression { - display: inline-block; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; @@ -128,3 +127,8 @@ tr:hover .copy-link { line-height: 36px; color: #333; } + +.checks-subline-duration { + color: #888; + white-space: nowrap; +} diff --git a/templates/front/last_ping_cell.html b/templates/front/last_ping_cell.html index 88453940..7b24b5f6 100644 --- a/templates/front/last_ping_cell.html +++ b/templates/front/last_ping_cell.html @@ -1,9 +1,14 @@ -{% load humanize %} +{% load humanize hc_extras %} {% if check.last_ping %} {{ check.last_ping|naturaltime }} {% if check.has_confirmation_link %}
confirmation link + {% elif check.clamped_last_duration %} +
+ + {{ check.clamped_last_duration|hms }} + {% endif %} {% else %} Never diff --git a/templates/front/my_checks_desktop.html b/templates/front/my_checks_desktop.html index 14716ff7..87743c96 100644 --- a/templates/front/my_checks_desktop.html +++ b/templates/front/my_checks_desktop.html @@ -44,6 +44,10 @@ Last Ping {% endif %} + {% if show_last_duration %} +
+ Last Duration + {% endif %} @@ -102,10 +106,10 @@ class="timeout-grace"> {% if check.kind == "simple" %} {{ check.timeout|hc_duration }} +
{% elif check.kind == "cron" %} - {{ check.schedule }} +
{{ check.schedule }}
{% endif %} -
{{ check.grace|hc_duration }}