mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +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