forked from GithubBackups/healthchecks
Show a red "!" in project's top navigation if any integration is not working
This commit is contained in:
parent
8e455965c4
commit
4ee2646539
@ -1,6 +1,12 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## v1.13.0-dev - Unreleased
|
||||
|
||||
### Improvements
|
||||
- Show a red "!" in project's top navigation if any integration is not working
|
||||
|
||||
|
||||
## v1.12.0 - 2020-01-02
|
||||
|
||||
### Improvements
|
||||
|
@ -282,6 +282,9 @@ class Project(models.Model):
|
||||
break
|
||||
return status
|
||||
|
||||
def have_broken_channels(self):
|
||||
return self.channel_set.exclude(last_error="").exists()
|
||||
|
||||
|
||||
class Member(models.Model):
|
||||
user = models.ForeignKey(User, models.CASCADE, related_name="memberships")
|
||||
|
@ -1,6 +1,6 @@
|
||||
from hc.test import BaseTestCase
|
||||
from hc.accounts.models import Project
|
||||
from hc.api.models import Check
|
||||
from hc.api.models import Check, Channel
|
||||
|
||||
|
||||
class ProjectModelTestCase(BaseTestCase):
|
||||
@ -13,3 +13,11 @@ class ProjectModelTestCase(BaseTestCase):
|
||||
Check.objects.create(project=p2)
|
||||
|
||||
self.assertEqual(self.project.num_checks_available(), 18)
|
||||
|
||||
def test_it_handles_zero_broken_channels(self):
|
||||
self.assertFalse(self.project.have_broken_channels())
|
||||
|
||||
def test_it_handles_one_broken_channel(self):
|
||||
Channel.objects.create(kind="webhook", last_error="x", project=self.project)
|
||||
|
||||
self.assertTrue(self.project.have_broken_channels())
|
||||
|
18
hc/api/migrations/0066_channel_last_error.py
Normal file
18
hc/api/migrations/0066_channel_last_error.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.0.1 on 2020-01-02 12:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0065_auto_20191127_1240'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='channel',
|
||||
name='last_error',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
]
|
27
hc/api/migrations/0067_last_error_values.py
Normal file
27
hc/api/migrations/0067_last_error_values.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Generated by Django 3.0.1 on 2020-01-02 14:28
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def fill_last_errors(apps, schema_editor):
|
||||
Channel = apps.get_model("api", "Channel")
|
||||
Notification = apps.get_model("api", "Notification")
|
||||
for ch in Channel.objects.all():
|
||||
error = ""
|
||||
try:
|
||||
n = Notification.objects.filter(channel=ch).latest()
|
||||
error = n.error
|
||||
except Notification.DoesNotExist:
|
||||
pass
|
||||
|
||||
ch.last_error = error
|
||||
ch.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("api", "0066_channel_last_error"),
|
||||
]
|
||||
|
||||
operations = [migrations.RunPython(fill_last_errors, migrations.RunPython.noop)]
|
@ -338,6 +338,7 @@ class Channel(models.Model):
|
||||
kind = models.CharField(max_length=20, choices=CHANNEL_KINDS)
|
||||
value = models.TextField(blank=True)
|
||||
email_verified = models.BooleanField(default=False)
|
||||
last_error = models.CharField(max_length=200, blank=True)
|
||||
checks = models.ManyToManyField(Check)
|
||||
|
||||
def __str__(self):
|
||||
@ -441,6 +442,9 @@ class Channel(models.Model):
|
||||
n.error = error
|
||||
n.save()
|
||||
|
||||
self.last_error = error
|
||||
self.save()
|
||||
|
||||
return error
|
||||
|
||||
def icon_path(self):
|
||||
|
@ -60,6 +60,7 @@ class NotifyTestCase(BaseTestCase):
|
||||
|
||||
n = Notification.objects.get()
|
||||
self.assertEqual(n.error, "Connection timed out")
|
||||
self.assertEqual(self.channel.last_error, "Connection timed out")
|
||||
|
||||
@patch("hc.api.transports.requests.request", side_effect=ConnectionError)
|
||||
def test_webhooks_handle_connection_errors(self, mock_get):
|
||||
|
@ -125,3 +125,10 @@ class ChannelsTestCase(BaseTestCase):
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get("/integrations/")
|
||||
self.assertRedirects(r, "/")
|
||||
|
||||
def test_it_shows_broken_channel_indicator(self):
|
||||
Channel.objects.create(kind="sms", project=self.project, last_error="x")
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get("/integrations/")
|
||||
self.assertContains(r, "broken-channels", status_code=200)
|
||||
|
@ -88,6 +88,9 @@ body {
|
||||
border-color: #22bc66;
|
||||
}
|
||||
|
||||
#broken-channels > a {
|
||||
color: #a94442;
|
||||
}
|
||||
|
||||
.page-checks .container-fluid, .page-details .container-fluid {
|
||||
/* Fluid below 1320px, but max width capped to 1320px ... */
|
||||
|
@ -97,9 +97,14 @@
|
||||
<a href="{% url 'hc-checks' project.code %}">Checks</a>
|
||||
</li>
|
||||
|
||||
<li {% if page == 'channels' %} class="active" {% endif %}>
|
||||
<a href="{% url 'hc-channels' %}">Integrations</a>
|
||||
{% with b=project.have_broken_channels %}
|
||||
<li {% if b %}id="broken-channels"{% endif %} {% if page == 'channels' %}class="active"{% endif %}>
|
||||
<a href="{% url 'hc-channels' %}">
|
||||
Integrations
|
||||
{% if b %}<span class="icon-grace"></span>{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endwith %}
|
||||
|
||||
<li {% if page == 'badges' %} class="active" {% endif %}>
|
||||
<a href="{% url 'hc-badges' project.code %}">Badges</a>
|
||||
|
Loading…
x
Reference in New Issue
Block a user