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 %}