Add Slack webhook.

Adds a new webhook integration for Slack to receive messages
from one's Slack team's public channels.
Contains negative tests for broken, missing or invalid data.

Allows two different option for integration:
1. Receive notification on a single stream with different topics
for each of Slack's public channels.
2. Receive notification on different streams for each of Slack's
public channels.

Steps to choose between the two options is described in the documentation.

Fixes #3569.
This commit is contained in:
wizsid11
2017-01-31 01:48:41 +05:30
committed by Tim Abbott
parent be0a2cb20b
commit 95789eb879
16 changed files with 146 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -27,6 +27,7 @@ IGNORED_PHRASES = [
r"MiB",
r"Pivotal",
r'REMOTE_USER',
r'Slack',
r"SSO",
r'Terms of Service',
r"URL",

View File

@@ -0,0 +1 @@
token=aotJImaVOVEVWssawxmegDWt&team_id=T3W8CTX0F&team_domain=gsoc2017&service_id=133126759712&channel_id=C3W8CTZFZ&channel_name=general&timestamp=1485947263.000010&user_id=U3W8CTX2T&user_name=slack_user&text=test

View File

@@ -0,0 +1 @@
token=aotJImaVOVEVWssawxmegDWt&team_id=T3W8CTX0F&team_domain=gsoc2017&service_id=133126759712&channel_id=C3W8CTZFZ&timestamp=1485947263.000010&user_id=U3W8CTX2T&user_name=slack_user&text=test

View File

@@ -0,0 +1 @@
token=aotJImaVOVEVWssawxmegDWt&team_id=T3W8CTX0F&team_domain=gsoc2017&service_id=133126759712&channel_id=C3W8CTZFZ&channel_name=general&timestamp=1485947263.000010&user_name=slack_user&user_id=U3W8CTX2T

View File

@@ -0,0 +1 @@
token=aotJImaVOVEVWssawxmegDWt&team_id=T3W8CTX0F&team_domain=gsoc2017&service_id=133126759712&channel_id=C3W8CTZFZ&channel_name=general&timestamp=1485947263.000010&user_id=U3W8CTX2T&text=test

View File

@@ -151,6 +151,7 @@ WEBHOOK_INTEGRATIONS = [
WebhookIntegration('pivotal', display_name='Pivotal Tracker'),
WebhookIntegration('semaphore'),
WebhookIntegration('sentry'),
WebhookIntegration('slack'),
WebhookIntegration('solano', display_name='Solano Labs'),
WebhookIntegration('splunk', display_name='Splunk'),
WebhookIntegration('stash'),

View File

View File

@@ -0,0 +1,41 @@
<p>Get notifications from Slack for messages on your team's public channels</p>
<p>
There are two ways in which you may want to receive a notification:
<ul type="disc">
<li>Slack Channel to a single Zulip stream with different topics</li>
<li>Multiple Zulip streams for multiple Slack channels</li>
</ul>
</p>
<p>
If you want a single Zulip stream to receive notifications from Slack then first create your desired stream.
</p>
<p>
Go to the following URL: <code>https://api.slack.com</code><br/>
Next, under the category of App features click on <b>Legacy custom integrations</b>.<br/>
<img class="screenshot" src="/static/images/integrations/slack/001.png"/><br/><br/>
Now, click on <b>Outgoing Webhooks</b> under <b>Custom Integrations</b>.<br/>
<img class="screenshot" src="/static/images/integrations/slack/002.png"/><br/><br/>
Then click the hyperlink <b>outgoing webhook integration</b> which can be found in the page.
<img class="screenshot" src="/static/images/integrations/slack/003.png"/><br/><br/>
Next, click the <b>Add Outgoing Webhook integration</b> button.
<img class="screenshot" src="/static/images/integrations/slack/004.png"/><br/><br/>
</p><br/>
<p>
Now, under <code> Integration Settings, </code> fill in the channel you'd like to get your notifications from.<br/><br/>
Then fill in <code>{{ external_api_uri_subdomain }}/v1/external/slack?api_key=abcdefgh&amp;stream=slack&amp;channels_map_topics=1</code> as your URL.<br/><br/>
<img class="screenshot" src="/static/images/integrations/slack/005.png"/><br/><br/>
Finally, save your settings.<br/><br/>
</p>
<p>
In case you want your Slack channels mapped to multiple Zulip streams, you have to
change the parameter <code>channels_map_to_topics=1</code> to <code>channels_map_to_topics=0</code> in your URL.
The resulting URL will be
<code>{{ external_api_uri_subdomain }}/v1/external/slack?api_key=abcdefgh&amp;stream=slack&amp;channels_map_topics=0</code>
</p>
<p><b>Congratulations, you're done!</b></p>
<p><b>This integration is not created by, affiliated with, or supported by Slack Technologies, Inc.</b></p>

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
from zerver.lib.test_classes import WebhookTestCase
from six import text_type
class SlackWebhookTests(WebhookTestCase):
STREAM_NAME = 'slack'
URL_TEMPLATE = "/api/v1/external/slack?stream={stream}&api_key={api_key}"
FIXTURE_DIR_NAME = 'slack'
def test_slack_channel_to_topic(self):
# type: () -> None
expected_subject = u"channel: general"
expected_message = u"**slack_user**: `test\n`"
self.send_and_test_stream_message('message_info', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_slack_channel_to_stream(self):
# type: () -> None
self.STREAM_NAME = 'general'
self.url = "{}{}".format(self.url, "&channels_map_to_topics=0")
expected_subject = u"Message from Slack"
expected_message = u"**slack_user**: `test\n`"
self.send_and_test_stream_message('message_info', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_missing_data_user_name(self):
# type: () -> None
payload = self.get_body('message_info_missing_user_name')
url = self.build_webhook_url()
result = self.client_post(url, payload, content_type="application/x-www-form-urlencoded")
self.assert_json_error(result, "Missing 'user_name' argument")
def test_missing_data_channel_name(self):
# type: () -> None
payload = self.get_body('message_info_missing_channel_name')
url = self.build_webhook_url()
result = self.client_post(url, payload, content_type="application/x-www-form-urlencoded")
self.assert_json_error(result, "Missing 'channel_name' argument")
def test_missing_data_text(self):
# type: () -> None
payload = self.get_body('message_info_missing_text')
url = self.build_webhook_url()
result = self.client_post(url, payload, content_type="application/x-www-form-urlencoded")
self.assert_json_error(result, "Missing 'text' argument")
def test_invalid_channels_map_to_topics(self):
# type: () -> None
payload = self.get_body('message_info')
url = "{}{}".format(self.url, "&channels_map_to_topics=abc")
result = self.client_post(url, payload, content_type="application/x-www-form-urlencoded")
self.assert_json_error(result, 'Error: channels_map_to_topics parameter other than 0 or 1')
def get_body(self, fixture_name):
# type: (text_type) -> text_type
return self.fixture_data("slack", fixture_name, file_type="txt")

View File

@@ -0,0 +1,35 @@
from __future__ import absolute_import
from django.utils.translation import ugettext as _
from django.http import HttpRequest, HttpResponse
from django.utils.translation import ugettext as _
from zerver.lib.actions import check_send_message, create_stream_if_needed
from zerver.lib.response import json_success, json_error
from zerver.lib.validator import check_string, check_int
from zerver.decorator import REQ, has_request_variables, api_key_only_webhook_view
from zerver.models import UserProfile, Client
ZULIP_MESSAGE_TEMPLATE = "**{message_sender}**: `{text}`"
VALID_OPTIONS = {'SHOULD_NOT_BE_MAPPED': '0', 'SHOULD_BE_MAPPED': '1'}
@api_key_only_webhook_view('Slack')
@has_request_variables
def api_slack_webhook(request, user_profile, client,
user_name=REQ(),
text=REQ(),
channel_name=REQ(),
stream=REQ(default='slack'),
channels_map_to_topics=REQ(default='1')):
# type: (HttpRequest, UserProfile, Client, str, str, str, str, str) -> HttpResponse
if channels_map_to_topics not in list(VALID_OPTIONS.values()):
return json_error(_('Error: channels_map_to_topics parameter other than 0 or 1'))
if channels_map_to_topics == VALID_OPTIONS['SHOULD_BE_MAPPED']:
subject = "channel: {}".format(channel_name)
else:
stream = channel_name
subject = _("Message from Slack")
content = ZULIP_MESSAGE_TEMPLATE.format(message_sender=user_name, text=text)
check_send_message(user_profile, client, "stream", [stream], subject, content)
return json_success()