0
0
Fork 0
mirror of https://github.com/healthchecks/healthchecks.git synced 2025-04-10 23:40:11 +00:00

Switch back to using integer timestamps in the log page

The live-updating code still needs float timestamps, but
we only need them for the most recent event (so we know
the lower threshold for fetching new events). We now send
the float timestamp separately:

* in the `/log/` view, we put it in HTML content, in a <script> tag
* in the `/log_events/` view we put it in response header

The main benefit of this is smaller response sizes for the
`/log/` and `/log_events/` views.
This commit is contained in:
Pēteris Caune 2024-04-09 14:24:43 +03:00
parent d7948d9939
commit 27c065230a
No known key found for this signature in database
GPG key ID: E28D7679E9A9EDE2
6 changed files with 67 additions and 50 deletions
hc/front
static/js
templates/front

View file

@ -185,8 +185,8 @@ def now_isoformat() -> str:
@register.filter
def timestamp(dt: datetime) -> float:
return dt.timestamp()
def timestamp(dt: datetime) -> int:
return int(dt.timestamp())
@register.filter

View file

@ -90,44 +90,6 @@ class LogTestCase(BaseTestCase):
r = self.client.get(self.url)
self.assertEqual(r.status_code, 404)
def test_it_shows_email_notification(self) -> None:
ch = Channel(kind="email", project=self.project)
ch.value = json.dumps({"value": "alice@example.org", "up": True, "down": True})
ch.save()
Notification(owner=self.check, channel=ch, check_status="down").save()
self.client.login(username="alice@example.org", password="password")
r = self.client.get(self.url)
self.assertContains(r, "Sent email to alice@example.org", status_code=200)
def test_it_shows_pushover_notification(self) -> None:
ch = Channel.objects.create(kind="po", project=self.project)
Notification(owner=self.check, channel=ch, check_status="down").save()
self.client.login(username="alice@example.org", password="password")
r = self.client.get(self.url)
self.assertContains(r, "Sent a Pushover notification", status_code=200)
def test_it_shows_webhook_notification(self) -> None:
ch = Channel(kind="webhook", project=self.project)
ch.value = json.dumps(
{
"method_down": "GET",
"url_down": "foo/$NAME",
"body_down": "",
"headers_down": {},
}
)
ch.save()
Notification(owner=self.check, channel=ch, check_status="down").save()
self.client.login(username="alice@example.org", password="password")
r = self.client.get(self.url)
self.assertContains(r, "Called webhook foo/$NAME", status_code=200)
def test_it_shows_ignored_nonzero_exitstatus(self) -> None:
self.ping.kind = "ign"
self.ping.exitstatus = 123

View file

@ -141,3 +141,41 @@ class LogTestCase(BaseTestCase):
r = self.client.get(self.url(flip=False))
self.assertContains(r, "hello world")
self.assertNotContains(r, "new ➔ down")
def test_it_shows_email_notification(self) -> None:
ch = Channel(kind="email", project=self.project)
ch.value = json.dumps({"value": "alice@example.org", "up": True, "down": True})
ch.save()
Notification(owner=self.check, channel=ch, check_status="down").save()
self.client.login(username="alice@example.org", password="password")
r = self.client.get(self.url())
self.assertContains(r, "Sent email to alice@example.org", status_code=200)
def test_it_shows_pushover_notification(self) -> None:
ch = Channel.objects.create(kind="po", project=self.project)
Notification(owner=self.check, channel=ch, check_status="down").save()
self.client.login(username="alice@example.org", password="password")
r = self.client.get(self.url())
self.assertContains(r, "Sent a Pushover notification", status_code=200)
def test_it_shows_webhook_notification(self) -> None:
ch = Channel(kind="webhook", project=self.project)
ch.value = json.dumps(
{
"method_down": "GET",
"url_down": "foo/$NAME",
"body_down": "",
"headers_down": {},
}
)
ch.save()
Notification(owner=self.check, channel=ch, check_status="down").save()
self.client.login(username="alice@example.org", password="password")
r = self.client.get(self.url())
self.assertContains(r, "Called webhook foo/$NAME", status_code=200)

View file

@ -920,16 +920,23 @@ def log(request: AuthenticatedHttpRequest, code: UUID) -> HttpResponse:
if oldest_ping:
smin = max(smin, oldest_ping.created)
events = _get_events(check, 1000, start=smin, end=smax)
ctx = {
"page": "log",
"project": check.project,
"check": check,
"min": smin,
"max": smax,
"events": _get_events(check, 1000, start=smin, end=smax),
"events": events,
"oldest_ping": oldest_ping,
}
if events:
# A full precision timestamp of the most recent event.
# This will be used client-side for fetching live updates to specify
# "return any events after *this* point".
ctx["last_event_timestamp"] = events[0].created.timestamp()
return render(request, "front/log.html", ctx)
@ -2750,11 +2757,19 @@ def log_events(request: AuthenticatedHttpRequest, code: UUID) -> HttpResponse:
if oldest_ping:
start = max(start, oldest_ping.created)
events = _get_events(check, 1000, start=start, end=end, kinds=form.kinds())
ctx = {
"events": _get_events(check, 1000, start=start, end=end, kinds=form.kinds()),
"events": events,
"describe_body": True,
}
return render(request, "front/log_rows.html", ctx)
response = render(request, "front/log_rows.html", ctx)
if events:
# Include a full precision timestamp of the most recent event in a
# response header. This will be used client-side for fetching live updates
# to specify "return any events after *this* point".
response["X-Last-Event-Timestamp"] = str(events[0].created.timestamp())
return response
# Forks: add custom views after this line

View file

@ -87,6 +87,7 @@ $(function () {
// Once it's ready, set it to visible:
$("#log").css("visibility", "visible");
var lastUpdated = document.getElementById("last-event-timestamp").textContent;
function fetchNewEvents() {
// Do not fetch updates if the slider is not set to "now"
// or there's an AJAX request in flight
@ -97,19 +98,19 @@ $(function () {
var url = document.getElementById("log").dataset.refreshUrl;
var qs = $("#filters").serialize();
var firstRow = $("#log tr").get(0);
if (firstRow) {
qs += "&u=" + firstRow.dataset.dt;
if (lastUpdated) {
qs += "&u=" + lastUpdated;
}
activeRequest = $.ajax({
url: url + "?" + qs,
timeout: 2000,
success: function(data) {
success: function(data, textStatus, xhr) {
activeRequest = null;
if (!data)
return;
lastUpdated = xhr.getResponseHeader("X-Last-Event-Timestamp");
var tbody = document.createElement("tbody");
tbody.setAttribute("class", "new");
tbody.innerHTML = data;

View file

@ -71,9 +71,9 @@
id="end"
type="range"
name="end"
min="{{ min|timestamp|floatformat:"0" }}"
max="{{ max|timestamp|floatformat:"0" }}"
value="{{ max|timestamp|floatformat:"0" }}"
min="{{ min|timestamp }}"
max="{{ max|timestamp }}"
value="{{ max|timestamp }}"
autocomplete="off">
<p id="end-help" class="text-muted">
@ -123,6 +123,7 @@
{% endblock %}
{% block scripts %}
<script id="last-event-timestamp" type="data">{{ last_event_timestamp }}</script>
{% compress js %}
<script src="{% static 'js/moment.min.js' %}"></script>
<script src="{% static 'js/moment-timezone-with-data-10-year-range.min.js' %}"></script>