diff --git a/hc/api/models.py b/hc/api/models.py index 44cc2143..51c70e54 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -32,6 +32,7 @@ CHANNEL_KINDS = (("email", "Email"), ("hipchat", "HipChat"), ("slack", "Slack"), ("pd", "PagerDuty"), + ("pagertree", "PagerTree"), ("po", "Pushover"), ("pushbullet", "Pushbullet"), ("opsgenie", "OpsGenie"), @@ -260,6 +261,8 @@ class Channel(models.Model): return transports.HipChat(self) elif self.kind == "pd": return transports.PagerDuty(self) + elif self.kind == "pagertree": + return transports.PagerTree(self) elif self.kind == "victorops": return transports.VictorOps(self) elif self.kind == "pushbullet": diff --git a/hc/api/tests/test_notify.py b/hc/api/tests/test_notify.py index d65d6121..286e03a9 100644 --- a/hc/api/tests/test_notify.py +++ b/hc/api/tests/test_notify.py @@ -283,6 +283,18 @@ class NotifyTestCase(BaseTestCase): self.assertEqual(payload["event_type"], "trigger") self.assertEqual(payload["service_key"], "456") + @patch("hc.api.transports.requests.request") + def test_pagertree(self, mock_post): + self._setup_data("pagertree", "123") + mock_post.return_value.status_code = 200 + + self.channel.notify(self.check) + assert Notification.objects.count() == 1 + + args, kwargs = mock_post.call_args + payload = kwargs["json"] + self.assertEqual(payload["event_type"], "trigger") + @patch("hc.api.transports.requests.request") def test_slack(self, mock_post): self._setup_data("slack", "123") diff --git a/hc/api/transports.py b/hc/api/transports.py index faf06cc8..b94c1b40 100644 --- a/hc/api/transports.py +++ b/hc/api/transports.py @@ -230,6 +230,24 @@ class PagerDuty(HttpTransport): return self.post(self.URL, json=payload) +class PagerTree(HttpTransport): + def notify(self, check): + url = self.channel.value + headers = { + "Conent-Type": "application/json" + } + payload = { + "incident_key": str(check.code), + "event_type": "trigger" if check.status == "down" else "resolve", + "title": tmpl("pagertree_title.html", check=check), + "description": tmpl("pagertree_description.html", check=check), + "client": settings.SITE_NAME, + "client_url": settings.SITE_ROOT, + "tags": ",".join(check.tags_list()) + } + + return self.post(url, json=payload, headers=headers) + class Pushbullet(HttpTransport): def notify(self, check): diff --git a/hc/front/tests/test_add_pagertree.py b/hc/front/tests/test_add_pagertree.py new file mode 100644 index 00000000..6f06fed1 --- /dev/null +++ b/hc/front/tests/test_add_pagertree.py @@ -0,0 +1,29 @@ +from hc.api.models import Channel +from hc.test import BaseTestCase + + +class AddPagerTreeTestCase(BaseTestCase): + url = "/integrations/add_pagertree/" + + def test_instructions_work(self): + self.client.login(username="alice@example.org", password="password") + r = self.client.get(self.url) + self.assertContains(r, "PagerTree") + + 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, "pagertree") + self.assertEqual(c.value, "http://example.org") + + 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") diff --git a/hc/front/urls.py b/hc/front/urls.py index 4b209b57..5f5352df 100644 --- a/hc/front/urls.py +++ b/hc/front/urls.py @@ -17,6 +17,7 @@ channel_urls = [ url(r'^add_webhook/$', views.add_webhook, name="hc-add-webhook"), url(r'^add_pd/$', views.add_pd, name="hc-add-pd"), url(r'^add_pd/([\w]{12})/$', views.add_pd, name="hc-add-pd-state"), + url(r'^add_pagertree/$', views.add_pagertree, name="hc-add-pagertree"), url(r'^add_slack/$', views.add_slack, name="hc-add-slack"), url(r'^add_slack_btn/$', views.add_slack_btn, name="hc-add-slack-btn"), url(r'^add_hipchat/$', views.add_hipchat, name="hc-add-hipchat"), diff --git a/hc/front/views.py b/hc/front/views.py index 22853577..d7043f50 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -512,6 +512,23 @@ def add_pd(request, state=None): ctx = {"page": "channels", "connect_url": connect_url} return render(request, "integrations/add_pd.html", ctx) +@login_required +def add_pagertree(request): + if request.method == "POST": + form = AddUrlForm(request.POST) + if form.is_valid(): + channel = Channel(user=request.team.user, kind="pagertree") + channel.value = form.cleaned_data["value"] + channel.save() + + channel.assign_all_checks() + return redirect("hc-channels") + else: + form = AddUrlForm() + + ctx = {"page": "channels", "form": form} + return render(request, "integrations/add_pagertree.html", ctx) + def add_slack(request): if not settings.SLACK_CLIENT_ID and not request.user.is_authenticated: diff --git a/static/img/integrations/pagertree.png b/static/img/integrations/pagertree.png new file mode 100644 index 00000000..380f95f1 Binary files /dev/null and b/static/img/integrations/pagertree.png differ diff --git a/static/img/integrations/setup_pagertree_1.png b/static/img/integrations/setup_pagertree_1.png new file mode 100644 index 00000000..9d727d03 Binary files /dev/null and b/static/img/integrations/setup_pagertree_1.png differ diff --git a/static/img/integrations/setup_pagertree_2.png b/static/img/integrations/setup_pagertree_2.png new file mode 100644 index 00000000..601c3bd4 Binary files /dev/null and b/static/img/integrations/setup_pagertree_2.png differ diff --git a/static/img/integrations/setup_pagertree_3.png b/static/img/integrations/setup_pagertree_3.png new file mode 100644 index 00000000..def22558 Binary files /dev/null and b/static/img/integrations/setup_pagertree_3.png differ diff --git a/templates/front/channels.html b/templates/front/channels.html index f6f98b71..9e8b14f1 100644 --- a/templates/front/channels.html +++ b/templates/front/channels.html @@ -50,6 +50,9 @@ {% endif %} service key {{ ch.pd_service_key }} + {% elif ch.kind == "pagertree" %} + URL + {{ ch.value }} {% elif ch.kind == "opsgenie" %} API key {{ ch.value }} @@ -227,6 +230,15 @@ Add Integration {% endif %} +
DevOps Incident Management - On-Call Schedules, Alerts, & Notifications
+ + Add Integration +If your team uses PagerTree, + you can set up {% site_name %} to create a PagerTree incident when + a check goes down, and resolve it when a check goes back up.
+ ++ Log into your PagerTree account, select the team you wish to add this integration to. Click the Integrations tab. Then click the + Integration button. +
++ In the Create Integration Form, fill out the details with apprpriate values, but most importantly make sure the Integration Type is set to Healthchecks.io. Then click the Create button. +
++ Copy the Webhook URL and paste it below. Save the integration, and you are done! +
+