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
|
||||
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
|
||||
|
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)
|
||||
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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,9 +1,14 @@
|
||||
{% load humanize %}
|
||||
{% load humanize hc_extras %}
|
||||
|
||||
{% if check.last_ping %}
|
||||
{{ check.last_ping|naturaltime }}
|
||||
{% if check.has_confirmation_link %}
|
||||
<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 %}
|
||||
{% else %}
|
||||
Never
|
||||
|
@ -44,6 +44,10 @@
|
||||
Last Ping</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if show_last_duration %}
|
||||
<br />
|
||||
<span class="checks-subline">Last Duration</span>
|
||||
{% endif %}
|
||||
</th>
|
||||
<th class="hidden-xs"></th>
|
||||
</tr>
|
||||
@ -102,10 +106,10 @@
|
||||
class="timeout-grace">
|
||||
{% if check.kind == "simple" %}
|
||||
{{ check.timeout|hc_duration }}
|
||||
<br />
|
||||
{% elif check.kind == "cron" %}
|
||||
<span class="cron-expression">{{ check.schedule }}</span>
|
||||
<div class="cron-expression">{{ check.schedule }}</div>
|
||||
{% endif %}
|
||||
<br />
|
||||
<span class="checks-subline">
|
||||
{{ check.grace|hc_duration }}
|
||||
</span>
|
||||
|
Loading…
x
Reference in New Issue
Block a user