0
0
Fork 0
mirror of https://github.com/healthchecks/healthchecks.git synced 2025-04-08 06:30:05 +00:00

Add tooltips to tag buttons in the checks list screen

Fixes: 
This commit is contained in:
Pēteris Caune 2024-01-22 15:20:09 +02:00
parent 42f88f4fb0
commit 16450a66c7
No known key found for this signature in database
GPG key ID: E28D7679E9A9EDE2
6 changed files with 61 additions and 36 deletions

View file

@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
- Update Opsgenie instructions
- Update Spike.sh instructions
- Add system check to validate settings.SITE_ROOT (#895)
- Add tooltips to tag buttons in the checks list screen (#911)
### Bug Fixes
- Increase uWSGI buffer size to allow requests with large cookies (#925)

View file

@ -117,7 +117,9 @@ class MyChecksTestCase(BaseTestCase):
self.client.login(username="alice@example.org", password="password")
r = self.client.get(self.url)
self.assertContains(r, """<div class="btn btn-xs down ">foo</div>""")
self.assertContains(
r, """<div data-tooltip="1 of 1 down" class="btn btn-xs down ">foo</div>"""
)
def test_it_shows_grace_badge(self) -> None:
self.check.last_ping = now() - td(days=1, minutes=10)
@ -127,7 +129,9 @@ class MyChecksTestCase(BaseTestCase):
self.client.login(username="alice@example.org", password="password")
r = self.client.get(self.url)
self.assertContains(r, """<div class="btn btn-xs grace ">foo</div>""")
self.assertContains(
r, """<div data-tooltip="1 up" class="btn btn-xs grace ">foo</div>"""
)
def test_it_shows_grace_started_badge(self) -> None:
self.check.last_start = now()
@ -138,7 +142,9 @@ class MyChecksTestCase(BaseTestCase):
self.client.login(username="alice@example.org", password="password")
r = self.client.get(self.url)
self.assertContains(r, """<div class="btn btn-xs grace ">foo</div>""")
self.assertContains(
r, """<div data-tooltip="1 up" class="btn btn-xs grace ">foo</div>"""
)
def test_it_hides_actions_from_readonly_users(self) -> None:
self.bobs_membership.role = "r"

View file

@ -19,7 +19,7 @@ class StatusTestCase(BaseTestCase):
self.assertEqual(r.status_code, 200)
doc = r.json()
self.assertEqual(doc["tags"]["foo"], "up")
self.assertEqual(doc["tags"]["foo"], ["up", "1 up"])
detail = doc["details"][0]
self.assertEqual(detail["code"], str(self.check.code))

View file

@ -7,7 +7,7 @@ import os
import re
import sqlite3
import uuid
from collections import defaultdict
from collections import Counter, defaultdict
from collections.abc import Iterable
from datetime import datetime
from datetime import timedelta as td
@ -77,25 +77,32 @@ EVENTS_TMPL = get_template("front/details_events.html")
DOWNTIMES_TMPL = get_template("front/details_downtimes.html")
def _tags_statuses(checks: Iterable[Check]) -> tuple[dict[str, str], int]:
tags, down, grace, num_down = {}, {}, {}, 0
def _tags_counts(checks: Iterable[Check]) -> tuple[list[tuple[str, str, str]], int]:
num_down = 0
grace = set()
counts: Counter[str] = Counter()
down_counts: Counter[str] = Counter()
for check in checks:
status = check.get_status()
counts.update(check.tags_list())
if status == "down":
num_down += 1
for tag in check.tags_list():
down[tag] = "down"
down_counts.update(check.tags_list())
elif status == "grace":
for tag in check.tags_list():
grace[tag] = "grace"
else:
for tag in check.tags_list():
tags[tag] = "up"
grace.update(check.tags_list())
tags.update(grace)
tags.update(down)
return tags, num_down
result = []
for tag in counts:
if tag in down_counts:
status = "down"
text = f"{down_counts[tag]} of {counts[tag]} down"
else:
status = "grace" if tag in grace else "up"
text = f"{counts[tag]} up"
result.append((tag, status, text))
return result, num_down
def _get_check_for_user(
@ -222,9 +229,8 @@ def checks(request: AuthenticatedHttpRequest, code: UUID) -> HttpResponse:
checks = list(q.prefetch_related("channel_set"))
sortchecks(checks, request.profile.sort)
tags_statuses, num_down = _tags_statuses(checks)
pairs = list(tags_statuses.items())
pairs.sort(key=lambda pair: pair[0].lower())
tags_counts, num_down = _tags_counts(checks)
tags_counts.sort(key=lambda item: item[0].lower())
is_group = Case(When(kind="group", then=0), default=1)
channels = project.channel_set.annotate(is_group=is_group)
@ -269,7 +275,7 @@ def checks(request: AuthenticatedHttpRequest, code: UUID) -> HttpResponse:
"checks": checks,
"channels": channels,
"num_down": num_down,
"tags": pairs,
"tags": tags_counts,
"ping_endpoint": settings.PING_ENDPOINT,
"timezones": all_timezones,
"project": project,
@ -302,9 +308,10 @@ def status(request: AuthenticatedHttpRequest, code: UUID) -> HttpResponse:
}
)
tags_statuses, num_down = _tags_statuses(checks)
tags_counts, num_down = _tags_counts(checks)
tags = {tag: (status, tooltip) for tag, status, tooltip in tags_counts}
return JsonResponse(
{"details": details, "tags": tags_statuses, "title": num_down_title(num_down)}
{"details": details, "tags": tags, "title": num_down_title(num_down)}
)
@ -2433,14 +2440,20 @@ def metrics(request: HttpRequest, code: UUID, key: str) -> HttpResponse:
value = 1 if check.last_start is not None else 0
yield TMPL % (esc(check.name), esc(check.tags), check.unique_key, value)
tags_statuses, num_down = _tags_statuses(checks)
all_tags, down_tags, num_down = set(), set(), 0
for check in checks:
all_tags.update(check.tags_list())
if check.get_status() == "down":
num_down += 1
down_tags.update(check.tags_list())
yield "\n"
help = "Whether all checks with this tag are up (1 for yes, 0 for no)."
yield f"# HELP hc_tag_up {help}\n"
yield "# TYPE hc_tag_up gauge\n"
TMPL = """hc_tag_up{tag="%s"} %d\n"""
for tag in sorted(tags_statuses):
value = 0 if tags_statuses[tag] == "down" else 1
for tag in sorted(all_tags):
value = 0 if tag in down_tags else 1
yield TMPL % (esc(tag), value)
yield "\n"

View file

@ -67,6 +67,10 @@ $(function () {
title: 'The word "confirm" was found in request body'
});
$("#my-checks-tags .btn").tooltip({
title: function() {return this.getAttribute("data-tooltip");}
});
function applyFilters() {
// Make a list of currently checked tags:
var checked = [];
@ -226,13 +230,14 @@ $(function () {
}
}
$("#my-checks-tags div").each(function(a) {
var status = data.tags[this.innerText];
if (lastStatus[this.innerText] == status)
return;
$(this).removeClass("up grace down").addClass(status);
lastStatus[this.innerText] = status;
$("#my-checks-tags > div.btn").each(function(a) {
tag = this.innerText;
this.setAttribute("data-tooltip", data.tags[tag][1]);
var status = data.tags[tag][0];
if (lastStatus[tag] != status) {
$(this).removeClass("up grace down").addClass(status);
lastStatus[tag] = status;
}
});
if (document.title != data.title) {

View file

@ -55,8 +55,8 @@
{% if checks %}
<div class="row">
<div id="my-checks-tags" class="col-sm-9">
{% for tag, status in tags %}
<div class="btn btn-xs {{ status }} {% if tag in selected_tags %}checked{% endif%}">{{ tag }}</div>
{% for tag, status, tooltip in tags %}
<div data-tooltip="{{ tooltip }}" class="btn btn-xs {{ status }} {% if tag in selected_tags %}checked{% endif%}">{{ tag }}</div>
{% endfor %}
</div>
<div class="col-sm-3">