forked from GithubBackups/healthchecks
Add the "Last Duration" field in the "My Checks" page. Add "last_duration" attribute to the Check API resource. Fixes #257
This commit is contained in:
parent
a0c7cbdfeb
commit
0d924f4627
@ -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
hc/api/migrations/0063_auto_20190903_0901.py
Normal file
23
hc/api/migrations/0063_auto_20190903_0901.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 }}
|
||||||
{% elif check.kind == "cron" %}
|
|
||||||
<span class="cron-expression">{{ check.schedule }}</span>
|
|
||||||
{% endif %}
|
|
||||||
<br />
|
<br />
|
||||||
|
{% elif check.kind == "cron" %}
|
||||||
|
<div class="cron-expression">{{ check.schedule }}</div>
|
||||||
|
{% endif %}
|
||||||
<span class="checks-subline">
|
<span class="checks-subline">
|
||||||
{{ check.grace|hc_duration }}
|
{{ check.grace|hc_duration }}
|
||||||
</span>
|
</span>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user