Browse Source

Add the "Last Duration" field in the "My Checks" page. Add "last_duration" attribute to the Check API resource. Fixes #257

pull/287/head
Pēteris Caune 5 years ago
parent
commit
0d924f4627
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
8 changed files with 82 additions and 12 deletions
  1. +7
    -0
      CHANGELOG.md
  2. +23
    -0
      hc/api/migrations/0063_auto_20190903_0901.py
  3. +15
    -1
      hc/api/models.py
  4. +10
    -0
      hc/api/tests/test_ping.py
  5. +10
    -7
      hc/front/views.py
  6. +5
    -1
      static/css/my_checks_desktop.css
  7. +6
    -1
      templates/front/last_ping_cell.html
  8. +6
    -2
      templates/front/my_checks_desktop.html

+ 7
- 0
CHANGELOG.md View File

@ -1,6 +1,13 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. 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 ## 1.9.0 - 2019-09-03
### Improvements ### Improvements


+ 23
- 0
hc/api/migrations/0063_auto_20190903_0901.py View File

@ -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),
),
]

+ 15
- 1
hc/api/models.py View File

@ -21,6 +21,8 @@ DEFAULT_TIMEOUT = td(days=1)
DEFAULT_GRACE = td(hours=1) DEFAULT_GRACE = td(hours=1)
NEVER = datetime(3000, 1, 1, tzinfo=pytz.UTC) NEVER = datetime(3000, 1, 1, tzinfo=pytz.UTC)
CHECK_KINDS = (("simple", "Simple"), ("cron", "Cron")) 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 = ( CHANNEL_KINDS = (
("email", "Email"), ("email", "Email"),
@ -71,6 +73,7 @@ class Check(models.Model):
n_pings = models.IntegerField(default=0) n_pings = models.IntegerField(default=0)
last_ping = models.DateTimeField(null=True, blank=True) last_ping = models.DateTimeField(null=True, blank=True)
last_start = 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) last_ping_was_fail = models.NullBooleanField(default=False)
has_confirmation_link = models.BooleanField(default=False) has_confirmation_link = models.BooleanField(default=False)
alert_after = models.DateTimeField(null=True, blank=True, editable=False) alert_after = models.DateTimeField(null=True, blank=True, editable=False)
@ -105,6 +108,10 @@ class Check(models.Model):
def email(self): def email(self):
return "%s@%s" % (self.code, settings.PING_EMAIL_DOMAIN) 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): def get_grace_start(self):
""" Return the datetime when the grace period starts. """ Return the datetime when the grace period starts.
@ -200,6 +207,9 @@ class Check(models.Model):
"next_ping": isostring(self.get_grace_start()), "next_ping": isostring(self.get_grace_start()),
} }
if self.last_duration:
result["last_duration"] = int(self.last_duration.total_seconds())
if readonly: if readonly:
code_half = self.code.hex[:16] code_half = self.code.hex[:16]
result["unique_key"] = hashlib.sha1(code_half.encode()).hexdigest() result["unique_key"] = hashlib.sha1(code_half.encode()).hexdigest()
@ -227,8 +237,12 @@ class Check(models.Model):
elif action == "ign": elif action == "ign":
pass pass
else: else:
self.last_start = None
self.last_ping = timezone.now() 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" new_status = "down" if action == "fail" else "up"
if self.status != new_status: if self.status != new_status:


+ 10
- 0
hc/api/tests/test_ping.py View File

@ -176,3 +176,13 @@ class PingTestCase(BaseTestCase):
self.check.refresh_from_db() self.check.refresh_from_db()
self.assertTrue(self.check.last_start) self.assertTrue(self.check.last_start)
self.assertEqual(self.check.status, "paused") 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)

+ 10
- 7
hc/front/views.py View File

@ -26,6 +26,7 @@ from hc.accounts.models import Project
from hc.api.models import ( from hc.api.models import (
DEFAULT_GRACE, DEFAULT_GRACE,
DEFAULT_TIMEOUT, DEFAULT_TIMEOUT,
MAX_DELTA,
Channel, Channel,
Check, Check,
Ping, 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") LAST_PING_TMPL = get_template("front/last_ping_cell.html")
EVENTS_TMPL = get_template("front/details_events.html") EVENTS_TMPL = get_template("front/details_events.html")
DOWNTIMES_TMPL = get_template("front/details_downtimes.html") DOWNTIMES_TMPL = get_template("front/details_downtimes.html")
ONE_HOUR = td(hours=1)
TWELVE_HOURS = td(hours=12)
def _tags_statuses(checks): def _tags_statuses(checks):
@ -155,6 +154,13 @@ def my_checks(request, code):
if search not in search_key: if search not in search_key:
hidden_checks.add(check) 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 = { ctx = {
"page": "checks", "page": "checks",
"checks": checks, "checks": checks,
@ -170,6 +176,7 @@ def my_checks(request, code):
"selected_tags": selected_tags, "selected_tags": selected_tags,
"search": search, "search": search,
"hidden_checks": hidden_checks, "hidden_checks": hidden_checks,
"show_last_duration": show_last_duration,
} }
return render(request, "front/my_checks.html", ctx) return render(request, "front/my_checks.html", ctx)
@ -421,10 +428,6 @@ def remove_check(request, code):
def _get_events(check, limit): 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 = Ping.objects.filter(owner=check).order_by("-id")[:limit]
pings = list(pings) pings = list(pings)
@ -432,7 +435,7 @@ def _get_events(check, limit):
for ping in pings: for ping in pings:
if ping.kind == "start" and prev and prev.kind != "start": if ping.kind == "start" and prev and prev.kind != "start":
delta = prev.created - ping.created delta = prev.created - ping.created
if delta < max_delta:
if delta < MAX_DELTA:
setattr(prev, "delta", delta) setattr(prev, "delta", delta)
prev = ping prev = ping


+ 5
- 1
static/css/my_checks_desktop.css View File

@ -67,7 +67,6 @@
} }
.timeout-grace .cron-expression { .timeout-grace .cron-expression {
display: inline-block;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@ -128,3 +127,8 @@ tr:hover .copy-link {
line-height: 36px; line-height: 36px;
color: #333; color: #333;
} }
.checks-subline-duration {
color: #888;
white-space: nowrap;
}

+ 6
- 1
templates/front/last_ping_cell.html View File

@ -1,9 +1,14 @@
{% load humanize %}
{% load humanize hc_extras %}
{% if check.last_ping %} {% if check.last_ping %}
{{ check.last_ping|naturaltime }} {{ check.last_ping|naturaltime }}
{% if check.has_confirmation_link %} {% if check.has_confirmation_link %}
<br /><span class="label label-confirmation">confirmation link</span> <br /><span class="label label-confirmation">confirmation link</span>
{% elif check.clamped_last_duration %}
<br />
<span class="checks-subline-duration">
<span class="icon-timer"></span> {{ check.clamped_last_duration|hms }}
</span>
{% endif %} {% endif %}
{% else %} {% else %}
Never Never

+ 6
- 2
templates/front/my_checks_desktop.html View File

@ -44,6 +44,10 @@
Last Ping</span> Last Ping</span>
</a> </a>
{% endif %} {% endif %}
{% if show_last_duration %}
<br />
<span class="checks-subline">Last Duration</span>
{% endif %}
</th> </th>
<th class="hidden-xs"></th> <th class="hidden-xs"></th>
</tr> </tr>
@ -102,10 +106,10 @@
class="timeout-grace"> class="timeout-grace">
{% if check.kind == "simple" %} {% if check.kind == "simple" %}
{{ check.timeout|hc_duration }} {{ check.timeout|hc_duration }}
<br />
{% elif check.kind == "cron" %} {% elif check.kind == "cron" %}
<span class="cron-expression">{{ check.schedule }}</span>
<div class="cron-expression">{{ check.schedule }}</div>
{% endif %} {% endif %}
<br />
<span class="checks-subline"> <span class="checks-subline">
{{ check.grace|hc_duration }} {{ check.grace|hc_duration }}
</span> </span>


Loading…
Cancel
Save