mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 14:03:30 +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>
|
<span class="integration-label">Twitter</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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">
|
<div class="integration-lozenge integration-yo-app">
|
||||||
<a class="integration-link integration-yo-app" href="#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" />
|
<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>
|
||||||
|
|
||||||
|
<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">
|
<div id="yo-app" class="integration-instructions">
|
||||||
|
|
||||||
<p>See your Yo App notifications in Zulip!</p>
|
<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
|
# type: () -> Message
|
||||||
return Message.objects.latest('id')
|
return Message.objects.latest('id')
|
||||||
|
|
||||||
|
def get_second_to_last_message(self):
|
||||||
|
return Message.objects.all().order_by('-id')[1]
|
||||||
|
|
||||||
def get_all_templates():
|
def get_all_templates():
|
||||||
# type: () -> List[str]
|
# type: () -> List[str]
|
||||||
templates = []
|
templates = []
|
||||||
|
|||||||
@@ -1244,3 +1244,39 @@ class AirbrakeHookTests(WebhookTestCase):
|
|||||||
expected_subject = u"ZulipIntegrationTest"
|
expected_subject = u"ZulipIntegrationTest"
|
||||||
expected_message = u"[ZeroDivisionError](https://zulip.airbrake.io/projects/125209/groups/1705190192091077626): \"Error message from logger\" occurred."
|
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)
|
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/teamcity$', 'webhooks.teamcity.api_teamcity_webhook'),
|
||||||
url(r'^api/v1/external/transifex$', 'webhooks.transifex.api_transifex_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/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/yo$', 'webhooks.yo.api_yo_app_webhook'),
|
||||||
url(r'^api/v1/external/zendesk$', 'webhooks.zendesk.api_zendesk_webhook'),
|
url(r'^api/v1/external/zendesk$', 'webhooks.zendesk.api_zendesk_webhook'),
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user