mirror of
https://github.com/zulip/zulip.git
synced 2025-11-03 05:23:35 +00:00
Add updownio integration.
This commit is contained in:
BIN
static/images/integrations/logos/updown.png
Normal file
BIN
static/images/integrations/logos/updown.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
static/images/integrations/updown/001.png
Normal file
BIN
static/images/integrations/updown/001.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -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&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>
|
||||
|
||||
27
zerver/fixtures/updown/updown_check_down_one_event.json
Normal file
27
zerver/fixtures/updown/updown_check_down_one_event.json
Normal 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
|
||||
}
|
||||
}]
|
||||
56
zerver/fixtures/updown/updown_check_multiple_events.json
Normal file
56
zerver/fixtures/updown/updown_check_multiple_events.json
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
||||
27
zerver/fixtures/updown/updown_check_up_again_one_event.json
Normal file
27
zerver/fixtures/updown/updown_check_up_again_one_event.json
Normal 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
|
||||
}
|
||||
}]
|
||||
27
zerver/fixtures/updown/updown_check_up_first_time.json
Normal file
27
zerver/fixtures/updown/updown_check_up_first_time.json
Normal 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
|
||||
}
|
||||
}]
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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)
|
||||
|
||||
94
zerver/views/webhooks/updown.py
Normal file
94
zerver/views/webhooks/updown.py
Normal 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'])
|
||||
@@ -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'),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user