diff --git a/static/images/integrations/crashlytics/001.png b/static/images/integrations/crashlytics/001.png new file mode 100644 index 0000000000000..677a7add0e054 Binary files /dev/null and b/static/images/integrations/crashlytics/001.png differ diff --git a/static/images/integrations/logos/crashlytics.png b/static/images/integrations/logos/crashlytics.png new file mode 100644 index 0000000000000..c0d85c25c3a19 Binary files /dev/null and b/static/images/integrations/logos/crashlytics.png differ diff --git a/templates/zerver/integrations.html b/templates/zerver/integrations.html index 060e28f915ca3..b6d04529592ce 100644 --- a/templates/zerver/integrations.html +++ b/templates/zerver/integrations.html @@ -89,6 +89,12 @@ Codeship +
+ + + Crashlytics + +
@@ -635,6 +641,32 @@
+
+ +

Zulip supports integration with Crashlytics and can notify you + about Crashlytics issues.

+ +

First, create the stream you'd like to use for Crashlytics notifications, + and subscribe all interested parties to this stream. We + recommend the name crashlytics.

+ +

{{ external_api_uri }}/v1/external/crashlytics?api_key=abcdefgh&stream=crashlytics

+ +

where api_key is the API key of your Zulip bot, + and stream is the stream name you want the + notifications sent to.

+ +

Click on the app in + your Crashlytics settings panel. + Next, on the integrations subpage, click “Web Hook,” enter the URL we created above and click + Verify.

+

Congratulations! You're done!
When an issue occurs, + you'll get a notification like this:

+ + + +
+
diff --git a/zerver/fixtures/crashlytics/crashlytics_issue_message.json b/zerver/fixtures/crashlytics/crashlytics_issue_message.json new file mode 100644 index 0000000000000..caa132a6a5f40 --- /dev/null +++ b/zerver/fixtures/crashlytics/crashlytics_issue_message.json @@ -0,0 +1,13 @@ +{ + "event": "issue_impact_change", + "payload_type": "issue", + "payload": { + "display_id": 123 , + "title": "Issue Title" , + "method": "methodName of issue", + "impact_level": 2, + "crashes_count": 54, + "impacted_devices_count": 16, + "url": "http://crashlytics.com/full/url/to/issue" + } +} diff --git a/zerver/fixtures/crashlytics/crashlytics_verification.json b/zerver/fixtures/crashlytics/crashlytics_verification.json new file mode 100644 index 0000000000000..bac2386487b52 --- /dev/null +++ b/zerver/fixtures/crashlytics/crashlytics_verification.json @@ -0,0 +1,4 @@ +{ + "event": "verification", + "payload_type": "none" +} diff --git a/zerver/tests/test_hooks.py b/zerver/tests/test_hooks.py index 056c7a6c0d798..4a790810950cc 100644 --- a/zerver/tests/test_hooks.py +++ b/zerver/tests/test_hooks.py @@ -1222,3 +1222,22 @@ def build_webhook_url(self): def get_body(self, fixture_name): return {} + +class CrashlyticsHookTests(WebhookTestCase): + STREAM_NAME = 'crashlytics' + URL_TEMPLATE = "/api/v1/external/crashlytics?stream={stream}&api_key={api_key}" + FIXTURE_DIR_NAME = 'crashlytics' + + def test_crashlytics_verification_message(self): + last_message_before_request = self.get_last_message() + payload = self.get_body('verification') + url = self.build_webhook_url() + result = self.client.post(url, payload, content_type="application/json") + last_message_after_request = self.get_last_message() + self.assert_json_success(result) + self.assertEqual(last_message_after_request.pk, last_message_before_request.pk) + + def test_crashlytics_build_in_success_status(self): + expected_subject = u"123: Issue Title" + expected_message = u"[Issue](http://crashlytics.com/full/url/to/issue) impacts at least 16 device(s)." + self.send_and_test_stream_message('issue_message', expected_subject, expected_message) diff --git a/zerver/views/webhooks/crashlytics.py b/zerver/views/webhooks/crashlytics.py new file mode 100644 index 0000000000000..05c6c2ce902b1 --- /dev/null +++ b/zerver/views/webhooks/crashlytics.py @@ -0,0 +1,37 @@ +# Webhooks for external integrations. +from __future__ import absolute_import +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.decorator import REQ, has_request_variables, api_key_only_webhook_view + + +CRASHLYTICS_SUBJECT_TEMPLATE = '{display_id}: {title}' +CRASHLYTICS_MESSAGE_TEMPLATE = '[Issue]({url}) impacts at least {impacted_devices_count} device(s).' + +VERIFICATION_EVENT = 'verification' + + +@api_key_only_webhook_view('Crashlytics') +@has_request_variables +def api_crashlytics_webhook(request, user_profile, client, payload=REQ(argument_type='body'), + stream=REQ(default='crashlytics')): + try: + event = payload['event'] + if event == VERIFICATION_EVENT: + return json_success() + issue_body = payload['payload'] + subject = CRASHLYTICS_SUBJECT_TEMPLATE.format( + display_id=issue_body['display_id'], + title=issue_body['title'] + ) + body = CRASHLYTICS_MESSAGE_TEMPLATE.format( + impacted_devices_count=issue_body['impacted_devices_count'], + url=issue_body['url'] + ) + except KeyError as e: + return json_error(_("Missing key {} in JSON".format(e.message))) + + check_send_message(user_profile, client, 'stream', [stream], + subject, body) + return json_success() diff --git a/zproject/urls.py b/zproject/urls.py index 589e9d9080126..a67218e902cd7 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -150,6 +150,7 @@ url(r'^api/v1/external/bitbucket$', 'webhooks.bitbucket.api_bitbucket_webhook'), url(r'^api/v1/external/circleci', 'webhooks.circleci.api_circleci_webhook'), url(r'^api/v1/external/codeship', 'webhooks.codeship.api_codeship_webhook'), + url(r'^api/v1/external/crashlytics', 'webhooks.crashlytics.api_crashlytics_webhook'), url(r'^api/v1/external/desk$', 'webhooks.deskdotcom.api_deskdotcom_webhook'), url(r'^api/v1/external/freshdesk$', 'webhooks.freshdesk.api_freshdesk_webhook'), url(r'^api/v1/external/github$', 'webhooks.github.api_github_landing'),