Browse Source

Show a red "!" in project's top navigation if any integration is not working

pull/320/head
Pēteris Caune 5 years ago
parent
commit
4ee2646539
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
10 changed files with 85 additions and 3 deletions
  1. +6
    -0
      CHANGELOG.md
  2. +3
    -0
      hc/accounts/models.py
  3. +9
    -1
      hc/accounts/tests/test_project_model.py
  4. +18
    -0
      hc/api/migrations/0066_channel_last_error.py
  5. +27
    -0
      hc/api/migrations/0067_last_error_values.py
  6. +4
    -0
      hc/api/models.py
  7. +1
    -0
      hc/api/tests/test_notify.py
  8. +7
    -0
      hc/front/tests/test_channels.py
  9. +3
    -0
      static/css/base.css
  10. +7
    -2
      templates/base.html

+ 6
- 0
CHANGELOG.md View File

@ -1,6 +1,12 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. 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 ## v1.12.0 - 2020-01-02
### Improvements ### Improvements


+ 3
- 0
hc/accounts/models.py View File

@ -282,6 +282,9 @@ class Project(models.Model):
break break
return status return status
def have_broken_channels(self):
return self.channel_set.exclude(last_error="").exists()
class Member(models.Model): class Member(models.Model):
user = models.ForeignKey(User, models.CASCADE, related_name="memberships") user = models.ForeignKey(User, models.CASCADE, related_name="memberships")


+ 9
- 1
hc/accounts/tests/test_project_model.py View File

@ -1,6 +1,6 @@
from hc.test import BaseTestCase from hc.test import BaseTestCase
from hc.accounts.models import Project from hc.accounts.models import Project
from hc.api.models import Check
from hc.api.models import Check, Channel
class ProjectModelTestCase(BaseTestCase): class ProjectModelTestCase(BaseTestCase):
@ -13,3 +13,11 @@ class ProjectModelTestCase(BaseTestCase):
Check.objects.create(project=p2) Check.objects.create(project=p2)
self.assertEqual(self.project.num_checks_available(), 18) 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
- 0
hc/api/migrations/0066_channel_last_error.py View 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
- 0
hc/api/migrations/0067_last_error_values.py View 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)]

+ 4
- 0
hc/api/models.py View File

@ -338,6 +338,7 @@ class Channel(models.Model):
kind = models.CharField(max_length=20, choices=CHANNEL_KINDS) kind = models.CharField(max_length=20, choices=CHANNEL_KINDS)
value = models.TextField(blank=True) value = models.TextField(blank=True)
email_verified = models.BooleanField(default=False) email_verified = models.BooleanField(default=False)
last_error = models.CharField(max_length=200, blank=True)
checks = models.ManyToManyField(Check) checks = models.ManyToManyField(Check)
def __str__(self): def __str__(self):
@ -441,6 +442,9 @@ class Channel(models.Model):
n.error = error n.error = error
n.save() n.save()
self.last_error = error
self.save()
return error return error
def icon_path(self): def icon_path(self):


+ 1
- 0
hc/api/tests/test_notify.py View File

@ -60,6 +60,7 @@ class NotifyTestCase(BaseTestCase):
n = Notification.objects.get() n = Notification.objects.get()
self.assertEqual(n.error, "Connection timed out") 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) @patch("hc.api.transports.requests.request", side_effect=ConnectionError)
def test_webhooks_handle_connection_errors(self, mock_get): def test_webhooks_handle_connection_errors(self, mock_get):


+ 7
- 0
hc/front/tests/test_channels.py View File

@ -125,3 +125,10 @@ class ChannelsTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.get("/integrations/") r = self.client.get("/integrations/")
self.assertRedirects(r, "/") 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="[email protected]", password="password")
r = self.client.get("/integrations/")
self.assertContains(r, "broken-channels", status_code=200)

+ 3
- 0
static/css/base.css View File

@ -88,6 +88,9 @@ body {
border-color: #22bc66; border-color: #22bc66;
} }
#broken-channels > a {
color: #a94442;
}
.page-checks .container-fluid, .page-details .container-fluid { .page-checks .container-fluid, .page-details .container-fluid {
/* Fluid below 1320px, but max width capped to 1320px ... */ /* Fluid below 1320px, but max width capped to 1320px ... */


+ 7
- 2
templates/base.html View File

@ -97,9 +97,14 @@
<a href="{% url 'hc-checks' project.code %}">Checks</a> <a href="{% url 'hc-checks' project.code %}">Checks</a>
</li> </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> </li>
{% endwith %}
<li {% if page == 'badges' %} class="active" {% endif %}> <li {% if page == 'badges' %} class="active" {% endif %}>
<a href="{% url 'hc-badges' project.code %}">Badges</a> <a href="{% url 'hc-badges' project.code %}">Badges</a>


Loading…
Cancel
Save