0
0
Fork 0
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:
Pēteris Caune 2023-09-27 10:18:52 +03:00
parent e48d361331
commit f0085933c3
No known key found for this signature in database
GPG key ID: E28D7679E9A9EDE2
4 changed files with 35 additions and 34 deletions

View file

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

View file

@ -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:

View file

@ -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 }}&nbsp;downtime{{ count|pluralize }}, {{ d.count }}&nbsp;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!

View file

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