Browse Source

Show the number of downtimes and total downtime minutes in "Check Details" page.

pull/272/head
Pēteris Caune 5 years ago
parent
commit
b2ebce6cf9
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
9 changed files with 63 additions and 19 deletions
  1. +5
    -4
      CHANGELOG.md
  2. +2
    -2
      hc/api/models.py
  3. +8
    -8
      hc/api/tests/test_check_model.py
  4. +3
    -0
      hc/front/views.py
  5. +15
    -0
      static/css/details.css
  6. +4
    -0
      static/js/details.js
  7. +2
    -2
      templates/emails/summary-downtimes-html.html
  8. +8
    -3
      templates/front/details.html
  9. +16
    -0
      templates/front/details_downtimes.html

+ 5
- 4
CHANGELOG.md View File

@ -4,7 +4,8 @@ All notable changes to this project will be documented in this file.
## Unreleased ## Unreleased
### Improvements ### Improvements
- Show the number of outages and total downtime in monthly reports. (#104)
- Show the number of downtimes and total downtime minutes in monthly reports (#104)
- Show the number of downtimes and total downtime minutes in "Check Details" page
## 1.8.0 - 2019-07-08 ## 1.8.0 - 2019-07-08
@ -13,8 +14,8 @@ All notable changes to this project will be documented in this file.
- Add the `prunetokenbucket` management command - Add the `prunetokenbucket` management command
- Show check counts in JSON "badges" (#251) - Show check counts in JSON "badges" (#251)
- Webhooks support HTTP PUT (#249) - Webhooks support HTTP PUT (#249)
- Webhooks can use different req. bodies and headers for "up" and "down" events. (#249)
- Show check's code instead of full URL on 992px - 1200px wide screens. (#253)
- Webhooks can use different req. bodies and headers for "up" and "down" events (#249)
- Show check's code instead of full URL on 992px - 1200px wide screens (#253)
- Add WhatsApp integration (uses Twilio same as the SMS integration) - Add WhatsApp integration (uses Twilio same as the SMS integration)
- Webhooks support the $TAGS placeholder - Webhooks support the $TAGS placeholder
- Don't include ping URLs in API responses when the read-only key is used - Don't include ping URLs in API responses when the read-only key is used
@ -59,7 +60,7 @@ All notable changes to this project will be documented in this file.
### Improvements ### Improvements
- Database schema: add uniqueness constraint to Check.code - Database schema: add uniqueness constraint to Check.code
- Database schema: add Ping.kind field. Remove "start" and "fail" fields.
- Database schema: add Ping.kind field. Remove "start" and "fail" fields
- Add "Email Settings..." dialog and "Subject Must Contain" setting - Add "Email Settings..." dialog and "Subject Must Contain" setting
- Database schema: add the Project model - Database schema: add the Project model
- Move project-specific settings to a new "Project Settings" page - Move project-specific settings to a new "Project Settings" page


+ 2
- 2
hc/api/models.py View File

@ -246,8 +246,8 @@ class Check(models.Model):
ping.body = body[:10000] ping.body = body[:10000]
ping.save() ping.save()
def outages_by_month(self, months=2):
""" Calculate the number of outages and downtime minutes per month.
def downtimes(self, months=2):
""" Calculate the number of downtimes and downtime minutes per month.
Returns a list of (datetime, downtime_in_secs, number_of_outages) tuples. Returns a list of (datetime, downtime_in_secs, number_of_outages) tuples.


+ 8
- 8
hc/api/tests/test_check_model.py View File

@ -166,24 +166,24 @@ class CheckModelTestCase(BaseTestCase):
d = check.to_dict() d = check.to_dict()
self.assertEqual(d["next_ping"], "2000-01-01T01:00:00+00:00") self.assertEqual(d["next_ping"], "2000-01-01T01:00:00+00:00")
def test_outages_by_month_handles_no_flips(self):
def test_downtimes_handles_no_flips(self):
check = Check.objects.create(project=self.project) check = Check.objects.create(project=self.project)
r = check.outages_by_month(10)
r = check.downtimes(10)
self.assertEqual(len(r), 10) self.assertEqual(len(r), 10)
for dt, downtime, outages in r: for dt, downtime, outages in r:
self.assertEqual(downtime.total_seconds(), 0) self.assertEqual(downtime.total_seconds(), 0)
self.assertEqual(outages, 0) self.assertEqual(outages, 0)
def test_outages_by_month_handles_currently_down_check(self):
def test_downtimes_handles_currently_down_check(self):
check = Check.objects.create(project=self.project, status="down") check = Check.objects.create(project=self.project, status="down")
r = check.outages_by_month(10)
r = check.downtimes(10)
self.assertEqual(len(r), 10) self.assertEqual(len(r), 10)
for dt, downtime, outages in r: for dt, downtime, outages in r:
self.assertEqual(outages, 1) self.assertEqual(outages, 1)
@patch("hc.api.models.timezone.now") @patch("hc.api.models.timezone.now")
def test_outages_by_month_handles_flip_one_day_ago(self, mock_now):
def test_downtimes_handles_flip_one_day_ago(self, mock_now):
mock_now.return_value = datetime(2019, 7, 19, tzinfo=timezone.utc) mock_now.return_value = datetime(2019, 7, 19, tzinfo=timezone.utc)
check = Check.objects.create(project=self.project, status="down") check = Check.objects.create(project=self.project, status="down")
@ -193,7 +193,7 @@ class CheckModelTestCase(BaseTestCase):
flip.new_status = "down" flip.new_status = "down"
flip.save() flip.save()
r = check.outages_by_month(10)
r = check.downtimes(10)
self.assertEqual(len(r), 10) self.assertEqual(len(r), 10)
for dt, downtime, outages in r: for dt, downtime, outages in r:
if dt.month == 7: if dt.month == 7:
@ -204,7 +204,7 @@ class CheckModelTestCase(BaseTestCase):
self.assertEqual(outages, 0) self.assertEqual(outages, 0)
@patch("hc.api.models.timezone.now") @patch("hc.api.models.timezone.now")
def test_outages_by_month_handles_flip_two_months_ago(self, mock_now):
def test_downtimes_handles_flip_two_months_ago(self, mock_now):
mock_now.return_value = datetime(2019, 7, 19, tzinfo=timezone.utc) mock_now.return_value = datetime(2019, 7, 19, tzinfo=timezone.utc)
check = Check.objects.create(project=self.project, status="down") check = Check.objects.create(project=self.project, status="down")
@ -214,7 +214,7 @@ class CheckModelTestCase(BaseTestCase):
flip.new_status = "down" flip.new_status = "down"
flip.save() flip.save()
r = check.outages_by_month(10)
r = check.downtimes(10)
self.assertEqual(len(r), 10) self.assertEqual(len(r), 10)
for dt, downtime, outages in r: for dt, downtime, outages in r:
if dt.month == 7: if dt.month == 7:


+ 3
- 0
hc/front/views.py View File

@ -58,6 +58,7 @@ VALID_SORT_VALUES = ("name", "-name", "last_ping", "-last_ping", "created")
STATUS_TEXT_TMPL = get_template("front/log_status_text.html") 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")
ONE_HOUR = td(hours=1) ONE_HOUR = td(hours=1)
TWELVE_HOURS = td(hours=12) TWELVE_HOURS = td(hours=12)
@ -474,6 +475,7 @@ def details(request, code):
"check": check, "check": check,
"channels": channels, "channels": channels,
"timezones": pytz.all_timezones, "timezones": pytz.all_timezones,
"downtimes": check.downtimes(months=3),
} }
return render(request, "front/details.html", ctx) return render(request, "front/details.html", ctx)
@ -523,6 +525,7 @@ def status_single(request, code):
if updated != request.GET.get("u"): if updated != request.GET.get("u"):
doc["events"] = EVENTS_TMPL.render({"check": check, "events": events}) doc["events"] = EVENTS_TMPL.render({"check": check, "events": events})
doc["downtimes"] = DOWNTIMES_TMPL.render({"downtimes": check.downtimes(3)})
return JsonResponse(doc) return JsonResponse(doc)


+ 15
- 0
static/css/details.css View File

@ -80,3 +80,18 @@
color: #d43f3a; color: #d43f3a;
background: #FFF; background: #FFF;
} }
#downtimes table {
width: 350px;
}
#downtimes tr:first-child td, #downtimes tr:first-child th {
border-top: 0;
}
#downtimes th {
width: 100px;
text-align: right;
font-weight: normal;
color: #888;
}

+ 4
- 0
static/js/details.js View File

@ -66,6 +66,10 @@ $(function () {
switchDateFormat(lastFormat); switchDateFormat(lastFormat);
} }
if (data.downtimes) {
$("#downtimes").html(data.downtimes);
}
if (document.title != data.title) { if (document.title != data.title) {
document.title = data.title; document.title = data.title;
} }


+ 2
- 2
templates/emails/summary-downtimes-html.html View File

@ -62,10 +62,10 @@
</table> </table>
{% endif %} {% endif %}
</td> </td>
{% for boundary, seconds, count in check.outages_by_month %}
{% for boundary, seconds, count in check.downtimes %}
{% if count %} {% if 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 }} outage{{ count|pluralize }},
{{ count }} downtime{{ count|pluralize }},
<br /> <br />
{{ seconds|hc_approx_duration }} total {{ seconds|hc_approx_duration }} total
</td> </td>


+ 8
- 3
templates/front/details.html View File

@ -87,8 +87,14 @@
<td> <td>
<span id="log-status-icon" class="status icon-{{ check.get_status }}"></span> <span id="log-status-icon" class="status icon-{{ check.get_status }}"></span>
</td> </td>
<td id="log-status-text">
{% include "front/log_status_text.html" %}
<td >
<p id="log-status-text">{% include "front/log_status_text.html" %}</p>
</td>
</tr>
<tr>
<td></td>
<td id="downtimes">
{% include "front/details_downtimes.html" %}
</td> </td>
</tr> </tr>
</table> </table>
@ -196,7 +202,6 @@
</div> </div>
<div id="events" class="col-sm-7"> <div id="events" class="col-sm-7">
<h2> <h2>
Log Log


+ 16
- 0
templates/front/details_downtimes.html View File

@ -0,0 +1,16 @@
{% load hc_extras %}
<table class="table">
{% for boundary, seconds, count in downtimes reversed %}
<tr>
<th>{{ boundary|date:"N Y"}}</th>
<td>
{% if count %}
{{ count }} downtime{{ count|pluralize }},
{{ seconds|hc_approx_duration }} total
{% else %}
All good!
{% endif %}
</td>
</tr>
{% endfor %}
</table>

Loading…
Cancel
Save