diff --git a/hc/api/models.py b/hc/api/models.py index 7fa6199e..34ee4ed4 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -7,7 +7,7 @@ import uuid from datetime import datetime from datetime import timedelta as td from datetime import timezone -from typing import Any, TypedDict +from typing import Any, NamedTuple, TypedDict from urllib.parse import urlencode from zoneinfo import ZoneInfo @@ -130,6 +130,12 @@ class CheckDict(TypedDict, total=False): tz: str +class DowntimeRecord(NamedTuple): + boundary: datetime + duration: td + count: int | None + + class DowntimeSummary(object): def __init__(self, boundaries: list[datetime]) -> None: self.boundaries = list(sorted(boundaries, reverse=True)) @@ -143,8 +149,12 @@ class DowntimeSummary(object): self.counts[i] += 1 return - def as_tuples(self) -> zip[tuple[datetime, td, int]]: - return zip(self.boundaries, self.durations, self.counts) + def as_records(self) -> list[DowntimeRecord]: + 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): @@ -485,9 +495,7 @@ class Check(models.Model): threshold = self.n_pings - self.project.owner_profile.ping_log_limit return self.ping_set.filter(n__gt=threshold) - def downtimes_by_boundary( - self, boundaries: list[datetime] - ) -> list[tuple[datetime, td | None, int | None]]: + def downtimes_by_boundary(self, boundaries: list[datetime]) -> list[DowntimeRecord]: """Calculate downtime counts and durations for the given time intervals. Returns a list of (datetime, downtime_in_secs, number_of_outages) tuples @@ -517,24 +525,21 @@ class Check(models.Model): if prev_status != "---": status = prev_status - # Convert to a list of tuples and set counters to None - # for intervals when the check didn't exist yet + # Set count to None for intervals when the check didn't exist yet prev_boundary = None - result: list[tuple[datetime, td | None, int | None]] = [] - for triple in summary.as_tuples(): + result: list[DowntimeRecord] = [] + for record in summary.as_records(): if prev_boundary and self.created > prev_boundary: - result.append((triple[0], None, None)) + result.append(DowntimeRecord(record.boundary, record.duration, None)) continue - prev_boundary = triple[0] - result.append(triple) + prev_boundary = record.boundary + result.append(record) result.sort() return result - def downtimes( - self, months: int, tz: str - ) -> list[tuple[datetime, td | None, int | None]]: + def downtimes(self, months: int, tz: str) -> list[DowntimeRecord]: boundaries = month_boundaries(months, tz) return self.downtimes_by_boundary(boundaries) diff --git a/hc/api/tests/test_check_model.py b/hc/api/tests/test_check_model.py index 0a59bf30..170a6af7 100644 --- a/hc/api/tests/test_check_model.py +++ b/hc/api/tests/test_check_model.py @@ -301,16 +301,13 @@ class CheckModelTestCase(BaseTestCase): nov, dec, jan = check.downtimes(3, "UTC") # Nov. 2019 - self.assertIsNone(nov[1]) - self.assertIsNone(nov[2]) + self.assertIsNone(nov.count) # Dec. 2019 - self.assertIsNone(dec[1]) - self.assertIsNone(dec[2]) + self.assertIsNone(dec.count) # Jan. 2020 - self.assertEqual(jan[1], td()) - self.assertEqual(jan[2], 0) + self.assertEqual(jan.count, 0) @override_settings(S3_BUCKET=None) def test_it_prunes(self) -> None: diff --git a/templates/emails/report-summary-html.html b/templates/emails/report-summary-html.html index 87f2ecdc..52b4561d 100644 --- a/templates/emails/report-summary-html.html +++ b/templates/emails/report-summary-html.html @@ -64,16 +64,16 @@ </table> {% endif %} </td> - {% for boundary, seconds, count in check.past_downtimes %} - {% if count %} + {% for d in check.past_downtimes %} + {% if d.count %} <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 /> - {{ seconds|hc_approx_duration }} total + {{ d.duration|hc_approx_duration }} total </td> {% else %} <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 %} {% else %} All good! diff --git a/templates/front/details_downtimes.html b/templates/front/details_downtimes.html index 14a5e5aa..15a57562 100644 --- a/templates/front/details_downtimes.html +++ b/templates/front/details_downtimes.html @@ -1,14 +1,13 @@ {% load hc_extras tz %} {% timezone tz %} <table id="downtimes" class="table"> - {% for boundary, down_duration_secs, count in downtimes reversed %} + {% for d in downtimes reversed %} <tr> - <th>{{ boundary|date:"N Y"}}</th> + <th>{{ d.boundary|date:"N Y"}}</th> <td> - {% if count %} - {{ count }} downtime{{ count|pluralize }}, - {{ down_duration_secs|hc_approx_duration }} total - {% elif count is None %} + {% if d.count %} + {{ d.count }} downtime{{ d.count|pluralize }}, {{ d.duration|hc_approx_duration }} total + {% elif d.count is None %} – {% else %} All good! @@ -17,4 +16,4 @@ </tr> {% endfor %} </table> -{% endtimezone %} \ No newline at end of file +{% endtimezone %}