diff --git a/hc/api/models.py b/hc/api/models.py index 749a93bc..ebadaa5b 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -49,6 +49,7 @@ CHANNEL_KINDS = ( ("msteams", "Microsoft Teams"), ("shell", "Shell Command"), ("zulip", "Zulip"), + ("spike", "Spike"), ) PO_PRIORITIES = {-2: "lowest", -1: "low", 0: "normal", 1: "high", 2: "emergency"} @@ -456,6 +457,8 @@ class Channel(models.Model): return transports.Shell(self) elif self.kind == "zulip": return transports.Zulip(self) + elif self.kind == "spike": + return transports.Spike(self) else: raise NotImplementedError("Unknown channel kind: %s" % self.kind) diff --git a/hc/api/transports.py b/hc/api/transports.py index 302447e7..884f3ce2 100644 --- a/hc/api/transports.py +++ b/hc/api/transports.py @@ -577,3 +577,17 @@ class Zulip(HttpTransport): } return self.post(url, data=data, auth=auth) + + + +class Spike(HttpTransport): + def notify(self, check): + url = self.channel.value + headers = {"Conent-Type": "application/json"} + payload = { + "title": tmpl("spike_title.html", check=check), + "message": tmpl("spike_description.html", check=check), + "status": check.status + } + + return self.post(url, json=payload, headers=headers) diff --git a/hc/front/tests/test_add_spike.py b/hc/front/tests/test_add_spike.py new file mode 100644 index 00000000..bf7c9e72 --- /dev/null +++ b/hc/front/tests/test_add_spike.py @@ -0,0 +1,30 @@ +from hc.api.models import Channel +from hc.test import BaseTestCase + + +class AddSpikeTestCase(BaseTestCase): + url = "/integrations/add_spike/" + + def test_instructions_work(self): + self.client.login(username="alice@example.org", password="password") + r = self.client.get(self.url) + self.assertContains(r, "Spike") + + def test_it_works(self): + form = {"value": "http://example.org"} + + self.client.login(username="alice@example.org", password="password") + r = self.client.post(self.url, form) + self.assertRedirects(r, "/integrations/") + + c = Channel.objects.get() + self.assertEqual(c.kind, "spike") + self.assertEqual(c.value, "http://example.org") + self.assertEqual(c.project, self.project) + + def test_it_rejects_bad_url(self): + form = {"value": "not an URL"} + + self.client.login(username="alice@example.org", password="password") + r = self.client.post(self.url, form) + self.assertContains(r, "Enter a valid URL") \ No newline at end of file diff --git a/hc/front/urls.py b/hc/front/urls.py index 0d9f44b8..016918cb 100644 --- a/hc/front/urls.py +++ b/hc/front/urls.py @@ -77,6 +77,7 @@ project_urls = [ path("add_webhook/", views.add_webhook, name="hc-add-webhook"), path("add_whatsapp/", views.add_whatsapp, name="hc-add-whatsapp"), path("add_zulip/", views.add_zulip, name="hc-add-zulip"), + path("add_spike/", views.add_spike, name="hc-add-spike"), path("badges/", views.badges, name="hc-badges"), path("checks/", views.my_checks, name="hc-checks"), path("checks/add/", views.add_check, name="hc-add-check"), diff --git a/hc/front/views.py b/hc/front/views.py index 263742c7..b3604e56 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -1724,3 +1724,25 @@ def metrics(request, code, key): yield "hc_checks_down_total %d\n" % num_down return HttpResponse(output(checks), content_type="text/plain") + + + +@login_required +def add_spike(request, code): + project = _get_project_for_user(request, code) + + if request.method == "POST": + form = forms.AddUrlForm(request.POST) + if form.is_valid(): + channel = Channel(project=project, kind="spike") + channel.value = form.cleaned_data["value"] + channel.save() + + channel.assign_all_checks() + return redirect("hc-p-channels", project.code) + else: + form = forms.AddUrlForm() + + ctx = {"page": "channels", "project": project, "form": form} + return render(request, "integrations/add_spike.html", ctx) + diff --git a/static/img/integrations/setup_spike_1.png b/static/img/integrations/setup_spike_1.png new file mode 100644 index 00000000..435d93e2 Binary files /dev/null and b/static/img/integrations/setup_spike_1.png differ diff --git a/static/img/integrations/setup_spike_2.png b/static/img/integrations/setup_spike_2.png new file mode 100644 index 00000000..1a39ff7c Binary files /dev/null and b/static/img/integrations/setup_spike_2.png differ diff --git a/static/img/integrations/setup_spike_3.png b/static/img/integrations/setup_spike_3.png new file mode 100644 index 00000000..60face5b Binary files /dev/null and b/static/img/integrations/setup_spike_3.png differ diff --git a/static/img/integrations/spike.png b/static/img/integrations/spike.png new file mode 100644 index 00000000..ba53a13a Binary files /dev/null and b/static/img/integrations/spike.png differ diff --git a/templates/front/channels.html b/templates/front/channels.html index 753d92b8..0577805b 100644 --- a/templates/front/channels.html +++ b/templates/front/channels.html @@ -334,12 +334,21 @@ Add Integration {% endif %} + +
No BS Incident management with unlimited alerts and on-call schedules
+ + Add Integration +A messaging app with a focus on speed and security.
Add Integration diff --git a/templates/front/welcome.html b/templates/front/welcome.html index 38bdaa26..949d2eac 100644 --- a/templates/front/welcome.html +++ b/templates/front/welcome.html @@ -455,6 +455,13 @@ {% endif %} +If you are using Spike.sh, make sure to please copy the Healthchecks webhook + and paste it here. Read through the instructions.
+ ++ Create a Healthchecks integration by clicking on add + integration on Spike.sh’s dashboard. +
++ After you have created the integration, click on copy webhook. + +
++ This modal with the details for the webhook will show up. Copy that webhook and paste it below. Make + sure you have Healthchecks integration’s webhook so Spike.sh can create and resolve incidents + automatically. + +
+