Add updownio integration.

This commit is contained in:
Tomasz Kolek
2016-05-25 20:42:28 +02:00
committed by Tim Abbott
parent 14d69348d3
commit 4e51a86ea4
11 changed files with 303 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -280,6 +280,12 @@
<span class="integration-label">Twitter</span>
</a>
</div>
<div class="integration-lozenge integration-updown">
<a class="integration-link integration-updown" href="#updown">
<img class="integration-logo" src="/static/images/integrations/logos/updown.png" alt="Updown logo" />
<span class="integration-label">Updown</span>
</a>
</div>
<div class="integration-lozenge integration-yo-app">
<a class="integration-link integration-yo-app" href="#yo-app">
<img class="integration-logo" src="/static/images/integrations/logos/yo-app.png" alt="Yo App logo" />
@@ -2155,6 +2161,32 @@ access_token_secret =</pre>
</div>
<div id="updown" class="integration-instructions">
<p>See Updown reports in Zulip! This is great to be up to date with
downtime in the services you monitor with Updown!
</p>
<p>First, create the stream you'd like to use for updown
notifications, and subscribe all interested parties to this
stream. We recommend the name <code>updown</code>.</p>
<p>Go to <a href="https://updown.io/settings/edit">your Updown
settings page</a> and in <code>WEBHOOKS</code> section, enter
the following as the URL:</p>
<p><code>{{ external_api_uri }}/v1/external/updown?api_key=abcdefgh&amp;stream=updown</code></p>
<p><b>Congratulations! You're done!</b><br />
Now you'll receive Updown notifications for your service in Zulip.
</p>
<img class="screenshot" src="/static/images/integrations/updown/001.png" />
</div>
<div id="yo-app" class="integration-instructions">
<p>See your Yo App notifications in Zulip!</p>

View File

@@ -0,0 +1,27 @@
[{
"event": "check.down",
"check": {
"token": "ngg8",
"url": "https://updown.io",
"alias": "",
"last_status": 500,
"uptime": 100.0,
"down": true,
"down_since": "2016-02-07T13:11:43Z",
"error": "500",
"period": 30,
"apdex_t": 0.25,
"string_match": "",
"enabled": true,
"published": true,
"last_check_at": "2016-02-07T13:12:13Z",
"next_check_at": "2016-02-07T13:12:43Z",
"favicon_url": "https://updown.io/favicon.png"
},
"downtime": {
"error": "500",
"started_at": "2016-02-07T13:11:43Z",
"ended_at": null,
"duration": null
}
}]

View File

@@ -0,0 +1,56 @@
[
{
"event": "check.down",
"check": {
"token": "ngg8",
"url": "https://updown.io",
"alias": "",
"last_status": 200,
"uptime": 99.954,
"down": false,
"down_since": null,
"error": null,
"period": 30,
"apdex_t": 0.25,
"string_match": "",
"enabled": true,
"published": true,
"last_check_at": "2016-02-07T13:16:07Z",
"next_check_at": "2016-02-07T13:16:37Z",
"favicon_url": "https://updown.io/favicon.png"
},
"downtime": {
"error": "500",
"started_at": "2016-02-07T13:11:43Z",
"ended_at": null,
"duration": null
}
},
{
"event": "check.up",
"check": {
"token": "ngg8",
"url": "https://updown.io",
"alias": "",
"last_status": 200,
"uptime": 99.954,
"down": false,
"down_since": null,
"error": null,
"period": 30,
"apdex_t": 0.25,
"string_match": "",
"enabled": true,
"published": true,
"last_check_at": "2016-02-07T13:16:07Z",
"next_check_at": "2016-02-07T13:16:37Z",
"favicon_url": "https://updown2.io/favicon.png"
},
"downtime": {
"error": "500",
"started_at": "2016-02-07T13:11:43Z",
"ended_at": "2016-02-07T13:11:44Z",
"duration": 1
}
}
]

View File

@@ -0,0 +1,27 @@
[{
"event": "check.up",
"check": {
"token": "ngg8",
"url": "https://updown.io",
"alias": "",
"last_status": 200,
"uptime": 99.954,
"down": false,
"down_since": null,
"error": null,
"period": 30,
"apdex_t": 0.25,
"string_match": "",
"enabled": true,
"published": true,
"last_check_at": "2016-02-07T13:16:07Z",
"next_check_at": "2016-02-07T13:16:37Z",
"favicon_url": "https://updown.io/favicon.png"
},
"downtime": {
"error": "500",
"started_at": "2016-02-07T13:11:43Z",
"ended_at": "2016-02-07T13:16:07Z",
"duration": 265
}
}]

View File

@@ -0,0 +1,27 @@
[{
"event": "check.up",
"check": {
"token": "ngg8",
"url": "https://updown.io",
"alias": "",
"last_status": 200,
"uptime": 99.954,
"down": false,
"down_since": null,
"error": null,
"period": 30,
"apdex_t": 0.25,
"string_match": "",
"enabled": true,
"published": true,
"last_check_at": "2016-02-07T13:16:07Z",
"next_check_at": "2016-02-07T13:16:37Z",
"favicon_url": "https://updown.io/favicon.png"
},
"downtime": {
"error": null,
"started_at": null,
"ended_at": null,
"duration": null
}
}]

View File

@@ -416,6 +416,9 @@ class AuthedTestCase(TestCase):
# type: () -> Message
return Message.objects.latest('id')
def get_second_to_last_message(self):
return Message.objects.all().order_by('-id')[1]
def get_all_templates():
# type: () -> List[str]
templates = []

View File

@@ -1244,3 +1244,39 @@ class AirbrakeHookTests(WebhookTestCase):
expected_subject = u"ZulipIntegrationTest"
expected_message = u"[ZeroDivisionError](https://zulip.airbrake.io/projects/125209/groups/1705190192091077626): \"Error message from logger\" occurred."
self.send_and_test_stream_message('error_message', expected_subject, expected_message)
class UpdownHookTests(WebhookTestCase):
STREAM_NAME = 'updown'
URL_TEMPLATE = "/api/v1/external/updown?stream={stream}&api_key={api_key}"
FIXTURE_DIR_NAME = 'updown'
def test_updown_check_down_event(self):
expected_subject = u"https://updown.io"
expected_message = u"Service is `down`. It returned \"500\" error at 07-02-2016 13:11."
self.send_and_test_stream_message('check_down_one_event', expected_subject, expected_message)
def test_updown_check_up_again_event(self):
expected_subject = u"https://updown.io"
expected_message = u"Service is `up` again after 4 minutes 25 seconds."
self.send_and_test_stream_message('check_up_again_one_event', expected_subject, expected_message)
def test_updown_check_up_event(self):
expected_subject = u"https://updown.io"
expected_message = u"Service is `up`."
self.send_and_test_stream_message('check_up_first_time', expected_subject, expected_message)
def test_updown_check_up_multiple_events(self):
first_message_expected_subject = u"https://updown.io"
first_message_expected_message = u"Service is `up` again after 1 second."
second_message_expected_subject = u"https://updown.io"
second_message_expected_message = u"Service is `down`. It returned \"500\" error at 07-02-2016 13:11."
self.send_and_test_stream_message('check_multiple_events')
last_message = self.get_last_message()
self.do_test_subject(last_message, first_message_expected_subject)
self.do_test_message(last_message, first_message_expected_message)
second_to_last_message = self.get_second_to_last_message()
self.do_test_subject(second_to_last_message, second_message_expected_subject)
self.do_test_message(second_to_last_message, second_message_expected_message)

View File

@@ -0,0 +1,94 @@
# Webhooks for external integrations.
from __future__ import absolute_import
import re
from datetime import datetime
from typing import Any, Dict
from django.http import HttpRequest, HttpResponse
from django.utils.translation import ugettext as _
from zerver.lib.actions import check_send_message
from zerver.lib.response import json_success, json_error
from zerver.lib.request import JsonableError
from zerver.decorator import REQ, has_request_variables, api_key_only_webhook_view
from zerver.models import UserProfile, Client
SUBJECT_TEMPLATE = "{service_url}"
class UnsupportedUpdownEventType(JsonableError):
pass
def send_message_for_event(event, user_profile, client, stream):
# type: (Dict[str, Any], UserProfile, Client, str) -> None
try:
event_type = get_event_type(event)
subject = SUBJECT_TEMPLATE.format(service_url=event['check']['url'])
body = EVENT_TYPE_BODY_MAPPER[event_type](event)
except KeyError as e:
return json_error(_("Missing key {} in JSON").format(e.message))
check_send_message(user_profile, client, 'stream', [stream], subject, body)
def get_body_for_up_event(event):
# type: (Dict[str, Any]) -> str
body = "Service is `up`"
event_downtime = event['downtime']
if event_downtime['started_at']:
body = "{} again".format(body)
string_date = get_time_string_based_on_duration(event_downtime['duration'])
if string_date:
body = "{} after {}".format(body, string_date)
return "{}.".format(body)
def get_time_string_based_on_duration(duration):
# type: (int) -> str
days, reminder = divmod(duration, 86400)
hours, reminder = divmod(reminder, 3600)
minutes, seconds = divmod(reminder, 60)
string_date = ''
string_date += add_time_part_to_string_date_if_needed(days, 'day')
string_date += add_time_part_to_string_date_if_needed(hours, 'hour')
string_date += add_time_part_to_string_date_if_needed(minutes, 'minute')
string_date += add_time_part_to_string_date_if_needed(seconds, 'second')
return string_date.rstrip()
def add_time_part_to_string_date_if_needed(value, text_name):
# type: (int, str) -> str
if value == 1:
return "1 {} ".format(text_name)
if value > 1:
return "{} {}s ".format(value, text_name)
return ''
def get_body_for_down_event(event):
# type: (Dict[str, Any]) -> str
event_downtime = event['downtime']
started_at = datetime.strptime(event_downtime['started_at'], "%Y-%m-%dT%H:%M:%SZ")
return "Service is `down`. It returned \"{}\" error at {}.".format(
event_downtime['error'],
started_at.strftime("%d-%m-%Y %H:%M")
)
@api_key_only_webhook_view('Updown')
@has_request_variables
def api_updown_webhook(request, user_profile, client,
payload=REQ(argument_type='body'),
stream=REQ(default='updown')):
# type: (HttpRequest, UserProfile, Client, List[Dict[str, Any]], str) -> HttpResponse
for event in payload:
send_message_for_event(event, user_profile, client, stream)
return json_success()
EVENT_TYPE_BODY_MAPPER = {
'up': get_body_for_up_event,
'down': get_body_for_down_event
}
def get_event_type(event):
# type: (Dict[str, Any]) -> str
event_type_match = re.match('check.(.*)', event['event'])
if event_type_match:
event_type = event_type_match.group(1)
if event_type in EVENT_TYPE_BODY_MAPPER:
return event_type
raise UnsupportedUpdownEventType(event['event'])

View File

@@ -163,6 +163,7 @@ urlpatterns += patterns('zerver.views',
url(r'^api/v1/external/teamcity$', 'webhooks.teamcity.api_teamcity_webhook'),
url(r'^api/v1/external/transifex$', 'webhooks.transifex.api_transifex_webhook'),
url(r'^api/v1/external/travis$', 'webhooks.travis.api_travis_webhook'),
url(r'^api/v1/external/updown$', 'webhooks.updown.api_updown_webhook'),
url(r'^api/v1/external/yo$', 'webhooks.yo.api_yo_app_webhook'),
url(r'^api/v1/external/zendesk$', 'webhooks.zendesk.api_zendesk_webhook'),