mirror of
https://github.com/healthchecks/healthchecks.git
synced 2025-04-14 17:08:29 +00:00
Update Check.downtimes() to return a list of namedtuples
This way it should be easier to add extra fields like uptime to the returned data structure.
This commit is contained in:
parent
e48d361331
commit
f0085933c3
4 changed files with 35 additions and 34 deletions
hc/api
templates
|
@ -7,7 +7,7 @@ import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timedelta as td
|
from datetime import timedelta as td
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
from typing import Any, TypedDict
|
from typing import Any, NamedTuple, TypedDict
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
@ -130,6 +130,12 @@ class CheckDict(TypedDict, total=False):
|
||||||
tz: str
|
tz: str
|
||||||
|
|
||||||
|
|
||||||
|
class DowntimeRecord(NamedTuple):
|
||||||
|
boundary: datetime
|
||||||
|
duration: td
|
||||||
|
count: int | None
|
||||||
|
|
||||||
|
|
||||||
class DowntimeSummary(object):
|
class DowntimeSummary(object):
|
||||||
def __init__(self, boundaries: list[datetime]) -> None:
|
def __init__(self, boundaries: list[datetime]) -> None:
|
||||||
self.boundaries = list(sorted(boundaries, reverse=True))
|
self.boundaries = list(sorted(boundaries, reverse=True))
|
||||||
|
@ -143,8 +149,12 @@ class DowntimeSummary(object):
|
||||||
self.counts[i] += 1
|
self.counts[i] += 1
|
||||||
return
|
return
|
||||||
|
|
||||||
def as_tuples(self) -> zip[tuple[datetime, td, int]]:
|
def as_records(self) -> list[DowntimeRecord]:
|
||||||
return zip(self.boundaries, self.durations, self.counts)
|
result = []
|
||||||
|
for b, d, c in zip(self.boundaries, self.durations, self.counts):
|
||||||
|
result.append(DowntimeRecord(b, d, c))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class Check(models.Model):
|
class Check(models.Model):
|
||||||
|
@ -485,9 +495,7 @@ class Check(models.Model):
|
||||||
threshold = self.n_pings - self.project.owner_profile.ping_log_limit
|
threshold = self.n_pings - self.project.owner_profile.ping_log_limit
|
||||||
return self.ping_set.filter(n__gt=threshold)
|
return self.ping_set.filter(n__gt=threshold)
|
||||||
|
|
||||||
def downtimes_by_boundary(
|
def downtimes_by_boundary(self, boundaries: list[datetime]) -> list[DowntimeRecord]:
|
||||||
self, boundaries: list[datetime]
|
|
||||||
) -> list[tuple[datetime, td | None, int | None]]:
|
|
||||||
"""Calculate downtime counts and durations for the given time intervals.
|
"""Calculate downtime counts and durations for the given time intervals.
|
||||||
|
|
||||||
Returns a list of (datetime, downtime_in_secs, number_of_outages) tuples
|
Returns a list of (datetime, downtime_in_secs, number_of_outages) tuples
|
||||||
|
@ -517,24 +525,21 @@ class Check(models.Model):
|
||||||
if prev_status != "---":
|
if prev_status != "---":
|
||||||
status = prev_status
|
status = prev_status
|
||||||
|
|
||||||
# Convert to a list of tuples and set counters to None
|
# Set count to None for intervals when the check didn't exist yet
|
||||||
# for intervals when the check didn't exist yet
|
|
||||||
prev_boundary = None
|
prev_boundary = None
|
||||||
result: list[tuple[datetime, td | None, int | None]] = []
|
result: list[DowntimeRecord] = []
|
||||||
for triple in summary.as_tuples():
|
for record in summary.as_records():
|
||||||
if prev_boundary and self.created > prev_boundary:
|
if prev_boundary and self.created > prev_boundary:
|
||||||
result.append((triple[0], None, None))
|
result.append(DowntimeRecord(record.boundary, record.duration, None))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
prev_boundary = triple[0]
|
prev_boundary = record.boundary
|
||||||
result.append(triple)
|
result.append(record)
|
||||||
|
|
||||||
result.sort()
|
result.sort()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def downtimes(
|
def downtimes(self, months: int, tz: str) -> list[DowntimeRecord]:
|
||||||
self, months: int, tz: str
|
|
||||||
) -> list[tuple[datetime, td | None, int | None]]:
|
|
||||||
boundaries = month_boundaries(months, tz)
|
boundaries = month_boundaries(months, tz)
|
||||||
return self.downtimes_by_boundary(boundaries)
|
return self.downtimes_by_boundary(boundaries)
|
||||||
|
|
||||||
|
|
|
@ -301,16 +301,13 @@ class CheckModelTestCase(BaseTestCase):
|
||||||
nov, dec, jan = check.downtimes(3, "UTC")
|
nov, dec, jan = check.downtimes(3, "UTC")
|
||||||
|
|
||||||
# Nov. 2019
|
# Nov. 2019
|
||||||
self.assertIsNone(nov[1])
|
self.assertIsNone(nov.count)
|
||||||
self.assertIsNone(nov[2])
|
|
||||||
|
|
||||||
# Dec. 2019
|
# Dec. 2019
|
||||||
self.assertIsNone(dec[1])
|
self.assertIsNone(dec.count)
|
||||||
self.assertIsNone(dec[2])
|
|
||||||
|
|
||||||
# Jan. 2020
|
# Jan. 2020
|
||||||
self.assertEqual(jan[1], td())
|
self.assertEqual(jan.count, 0)
|
||||||
self.assertEqual(jan[2], 0)
|
|
||||||
|
|
||||||
@override_settings(S3_BUCKET=None)
|
@override_settings(S3_BUCKET=None)
|
||||||
def test_it_prunes(self) -> None:
|
def test_it_prunes(self) -> None:
|
||||||
|
|
|
@ -64,16 +64,16 @@
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% for boundary, seconds, count in check.past_downtimes %}
|
{% for d in check.past_downtimes %}
|
||||||
{% if count %}
|
{% if d.count %}
|
||||||
<td style="border-top: 1px solid #EDEFF2; padding: 16px 8px; font-family: Helvetica, Arial, sans-serif;">
|
<td style="border-top: 1px solid #EDEFF2; padding: 16px 8px; font-family: Helvetica, Arial, sans-serif;">
|
||||||
{{ count }} downtime{{ count|pluralize }},
|
{{ d.count }} downtime{{ d.count|pluralize }},
|
||||||
<br />
|
<br />
|
||||||
{{ seconds|hc_approx_duration }} total
|
{{ d.duration|hc_approx_duration }} total
|
||||||
</td>
|
</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td style="border-top: 1px solid #EDEFF2; padding: 16px 8px; font-family: Helvetica, Arial, sans-serif; color: #9BA2AB;">
|
<td style="border-top: 1px solid #EDEFF2; padding: 16px 8px; font-family: Helvetica, Arial, sans-serif; color: #9BA2AB;">
|
||||||
{% if count is None %}
|
{% if d.count is None %}
|
||||||
{% comment %} The check didn't exist yet {% endcomment %}
|
{% comment %} The check didn't exist yet {% endcomment %}
|
||||||
{% else %}
|
{% else %}
|
||||||
All good!
|
All good!
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
{% load hc_extras tz %}
|
{% load hc_extras tz %}
|
||||||
{% timezone tz %}
|
{% timezone tz %}
|
||||||
<table id="downtimes" class="table">
|
<table id="downtimes" class="table">
|
||||||
{% for boundary, down_duration_secs, count in downtimes reversed %}
|
{% for d in downtimes reversed %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ boundary|date:"N Y"}}</th>
|
<th>{{ d.boundary|date:"N Y"}}</th>
|
||||||
<td>
|
<td>
|
||||||
{% if count %}
|
{% if d.count %}
|
||||||
{{ count }} downtime{{ count|pluralize }},
|
{{ d.count }} downtime{{ d.count|pluralize }}, {{ d.duration|hc_approx_duration }} total
|
||||||
{{ down_duration_secs|hc_approx_duration }} total
|
{% elif d.count is None %}
|
||||||
{% elif count is None %}
|
|
||||||
–
|
–
|
||||||
{% else %}
|
{% else %}
|
||||||
All good!
|
All good!
|
||||||
|
@ -17,4 +16,4 @@
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% endtimezone %}
|
{% endtimezone %}
|
||||||
|
|
Loading…
Add table
Reference in a new issue