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 %} +
  • + PagerTree icon + +

    PagerTree

    +

    DevOps Incident Management - On-Call Schedules, Alerts, & Notifications

    + + Add Integration +
  • HipChat icon diff --git a/templates/front/log.html b/templates/front/log.html index 42422737..5f302e29 100644 --- a/templates/front/log.html +++ b/templates/front/log.html @@ -89,6 +89,8 @@ {% endif %} {% elif event.channel.kind == "pd" %} Sent alert to PagerDuty + {% elif event.channel.kind == "pagertree" %} + Sent alert to PagerTree {% elif event.channel.kind == "opsgenie" %} Sent alert to OpsGenie {% elif event.channel.kind == "hipchat" %} diff --git a/templates/front/welcome.html b/templates/front/welcome.html index 75b70e13..cfc1d85d 100644 --- a/templates/front/welcome.html +++ b/templates/front/welcome.html @@ -319,6 +319,13 @@ {% endif %} + + + PagerTree icon + + Open and resolve incidents in PagerTree. + + HipChat icon diff --git a/templates/integrations/add_pagertree.html b/templates/integrations/add_pagertree.html new file mode 100644 index 00000000..33fe3d94 --- /dev/null +++ b/templates/integrations/add_pagertree.html @@ -0,0 +1,93 @@ +{% extends "base.html" %} +{% load compress humanize staticfiles hc_extras %} + +{% block title %}Add PagerTree - {% site_name %}{% endblock %} + + +{% block content %} +
    +
    +

    PagerTree

    + +

    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.

    + +

    Setup Guide

    + +
    +
    + 1 +

    + Log into your PagerTree account, select the team you wish to add this integration to. Click the Integrations tab. Then click the + Integration button. +

    +
    +
    + Click create integration button +
    +
    + +
    +
    + 2 +

    + 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. +

    +
    +
    + Create Healthchecks.io integration with details +
    +
    + +
    +
    + 3 +

    + Copy the Webhook URL and paste it below. Save the integration, and you are done! +

    +
    +
    + Copy the Webhook URL +
    +
    + +

    Integration Settings

    + +
    + {% csrf_token %} +
    + +
    + + + {% if form.value.errors %} +
    + {{ form.value.errors|join:"" }} +
    + {% endif %} +
    +
    +
    +
    + +
    +
    +
    +
    +
    +{% endblock %} diff --git a/templates/integrations/pagertree_description.html b/templates/integrations/pagertree_description.html new file mode 100644 index 00000000..22c9e800 --- /dev/null +++ b/templates/integrations/pagertree_description.html @@ -0,0 +1,5 @@ +{% load humanize %} +{{ check.name_then_code }} is {{ check.status|upper }}. +{% if check.status == "down" %} +Last ping was {{ check.last_ping|naturaltime }}. +{% endif %} diff --git a/templates/integrations/pagertree_title.html b/templates/integrations/pagertree_title.html new file mode 100644 index 00000000..29274284 --- /dev/null +++ b/templates/integrations/pagertree_title.html @@ -0,0 +1 @@ +{{ check.name_then_code }} is {{ check.status|upper }}