webhooks: Enable custom topics and default PM notifications.

This commit adds a generic function called check_send_webhook_message
that does the following:
* If a stream is specified in the webhook URL, it sends a stream
  message, otherwise sends a PM to the owner of the bot.
* In the case of a stream message, if a custom topic is specified
  in the webhook URL, it uses that topic as the subject of the
  stream message.

Also, note that we need not test this anywhere except for the
helloworld webhook. Since helloworld is our default example for
webhooks, it is here to stay and it made sense that tests for a
generic function such as check_send_webhook_message be tested
with an actual generic webhook!

Fixes #8607.
This commit is contained in:
Eeshan Garg 2018-03-13 20:06:11 -02:30 committed by Tim Abbott
parent 707af5ab56
commit af56df7723
6 changed files with 84 additions and 27 deletions

View File

@ -58,23 +58,25 @@ python file, `zerver/webhooks/mywebhook/view.py`.
The Hello World integration is in `zerver/webhooks/helloworld/view.py`:
```
from django.utils.translation import ugettext as _
from zerver.lib.actions import check_send_stream_message
from zerver.lib.response import json_success, json_error
from zerver.decorator import REQ, has_request_variables, api_key_only_webhook_view
from zerver.lib.validator import check_dict, check_string
from zerver.models import Client, UserProfile
from typing import Any, Dict, Iterable, Optional, Text
from django.http import HttpRequest, HttpResponse
from typing import Dict, Any, Iterable, Optional, Text
from django.utils.translation import ugettext as _
from zerver.decorator import api_key_only_webhook_view
from zerver.lib.webhooks.common import check_send_webhook_message
from zerver.lib.request import REQ, has_request_variables
from zerver.lib.response import json_error, json_success
from zerver.lib.validator import check_dict, check_string
from zerver.models import UserProfile
@api_key_only_webhook_view('HelloWorld')
@has_request_variables
def api_helloworld_webhook(request: HttpRequest, user_profile: UserProfile,
payload: Dict[str, Iterable[Dict[str, Any]]]=REQ(argument_type='body'),
stream: Text=REQ(default='test'),
topic: Text=REQ(default='Hello World')) -> HttpResponse:
def api_helloworld_webhook(
request: HttpRequest, user_profile: UserProfile,
payload: Dict[str, Iterable[Dict[str, Any]]]=REQ(argument_type='body')
) -> HttpResponse:
# construct the body of the message
body = 'Hello! I am happy to be here! :smile:'
@ -82,11 +84,11 @@ def api_helloworld_webhook(request: HttpRequest, user_profile: UserProfile,
body_template = '\nThe Wikipedia featured article for today is **[{featured_title}]({featured_url})**'
body += body_template.format(**payload)
# send the message
check_send_stream_message(user_profile, request.client,
stream, topic, body)
topic = "Hello World"
# send the message
check_send_webhook_message(request, user_profile, topic, body)
# return json result
return json_success()
```
@ -136,8 +138,13 @@ link to the Wikipedia article of the day as provided by the json payload.
integration checks for. In such a case, any `KeyError` thrown is handled by the server
backend and will create an appropriate response.
Then we send a public (stream) message with `check_send_stream_message` which will
validate the message and then send it.
Then we send a message with `check_send_webhook_message`, which will
validate the message and do the following:
* Send a public (stream) message if the `stream` query parameter is
specified in the webhook URL.
* If the `stream` query parameter isn't specified, it will send a private
message to the owner of the webhook bot.
Finally, we return a 200 http status with a JSON format success message via
`json_success()`.

View File

@ -13,6 +13,7 @@ from django.conf import settings
from django.core import validators
from analytics.lib.counts import COUNT_STATS, do_increment_logging_stat, \
RealmCount
from zerver.lib.bugdown import (
BugdownRenderingException,
version as bugdown_version,

View File

@ -0,0 +1,24 @@
from django.http import HttpRequest
from typing import Optional, Text
from zerver.lib.actions import check_send_stream_message, \
check_send_private_message
from zerver.lib.request import REQ, has_request_variables
from zerver.models import UserProfile
@has_request_variables
def check_send_webhook_message(
request: HttpRequest, user_profile: UserProfile,
topic: Text, body: Text, stream: Optional[Text]=REQ(default=None),
user_specified_topic: Optional[Text]=REQ("topic", default=None)
) -> None:
if stream is None:
assert user_profile.bot_owner is not None
check_send_private_message(user_profile, request.client,
user_profile.bot_owner, body)
else:
if user_specified_topic is not None:
topic = user_specified_topic
check_send_stream_message(user_profile, request.client,
stream, topic, body)

View File

@ -5,7 +5,8 @@ from zerver.lib.test_classes import WebhookTestCase
class HelloWorldHookTests(WebhookTestCase):
STREAM_NAME = 'test'
URL_TEMPLATE = "/api/v1/external/helloworld?&api_key={api_key}"
URL_TEMPLATE = "/api/v1/external/helloworld?&api_key={api_key}&stream={stream}"
PM_URL_TEMPLATE = "/api/v1/external/helloworld?&api_key={api_key}"
FIXTURE_DIR_NAME = 'hello'
# Note: Include a test function per each distinct message condition your integration supports
@ -25,5 +26,23 @@ class HelloWorldHookTests(WebhookTestCase):
self.send_and_test_stream_message('goodbye', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_pm_to_bot_owner(self) -> None:
# Note that this is really just a test for check_send_webhook_message
self.URL_TEMPLATE = self.PM_URL_TEMPLATE
self.url = self.build_webhook_url()
expected_message = u"Hello! I am happy to be here! :smile:\nThe Wikipedia featured article for today is **[Goodbye](https://en.wikipedia.org/wiki/Goodbye)**"
self.send_and_test_private_message('goodbye', expected_message=expected_message,
content_type="application/x-www-form-urlencoded")
def test_custom_topic(self) -> None:
# Note that this is really just a test for check_send_webhook_message
expected_subject = u"Custom Topic"
self.url = self.build_webhook_url(topic=expected_subject)
expected_message = u"Hello! I am happy to be here! :smile:\nThe Wikipedia featured article for today is **[Goodbye](https://en.wikipedia.org/wiki/Goodbye)**"
self.send_and_test_stream_message('goodbye', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def get_body(self, fixture_name: Text) -> Text:
return self.fixture_data("helloworld", fixture_name, file_type="json")

View File

@ -5,7 +5,7 @@ from django.http import HttpRequest, HttpResponse
from django.utils.translation import ugettext as _
from zerver.decorator import api_key_only_webhook_view
from zerver.lib.actions import check_send_stream_message
from zerver.lib.webhooks.common import check_send_webhook_message
from zerver.lib.request import REQ, has_request_variables
from zerver.lib.response import json_error, json_success
from zerver.lib.validator import check_dict, check_string
@ -13,10 +13,10 @@ from zerver.models import UserProfile
@api_key_only_webhook_view('HelloWorld')
@has_request_variables
def api_helloworld_webhook(request: HttpRequest, user_profile: UserProfile,
payload: Dict[str, Iterable[Dict[str, Any]]]=REQ(argument_type='body'),
stream: Text=REQ(default='test'),
topic: Text=REQ(default='Hello World')) -> HttpResponse:
def api_helloworld_webhook(
request: HttpRequest, user_profile: UserProfile,
payload: Dict[str, Iterable[Dict[str, Any]]]=REQ(argument_type='body')
) -> HttpResponse:
# construct the body of the message
body = 'Hello! I am happy to be here! :smile:'
@ -25,7 +25,9 @@ def api_helloworld_webhook(request: HttpRequest, user_profile: UserProfile,
body_template = '\nThe Wikipedia featured article for today is **[{featured_title}]({featured_url})**'
body += body_template.format(**payload)
topic = "Hello World"
# send the message
check_send_stream_message(user_profile, request.client, stream, topic, body)
check_send_webhook_message(request, user_profile, topic, body)
return json_success()

View File

@ -181,15 +181,19 @@ class Command(BaseCommand):
zulip_realm_bots.extend(all_realm_bots)
create_users(zulip_realm, zulip_realm_bots, bot_type=UserProfile.DEFAULT_BOT)
zoe = get_user("zoe@zulip.com", zulip_realm)
zulip_webhook_bots = [
("Zulip Webhook Bot", "webhook-bot@zulip.com"),
]
# If a stream is not supplied in the webhook URL, the webhook
# will (in some cases) send the notification as a PM to the
# owner of the webhook bot, so bot_owner can't be None
create_users(zulip_realm, zulip_webhook_bots,
bot_type=UserProfile.INCOMING_WEBHOOK_BOT)
bot_type=UserProfile.INCOMING_WEBHOOK_BOT, bot_owner=zoe)
aaron = get_user("AARON@zulip.com", zulip_realm)
zulip_outgoing_bots = [
("Outgoing Webhook", "outgoing-webhook@zulip.com")
]
aaron = get_user("AARON@zulip.com", zulip_realm)
create_users(zulip_realm, zulip_outgoing_bots,
bot_type=UserProfile.OUTGOING_WEBHOOK_BOT, bot_owner=aaron)
# TODO: Clean up this initial bot creation code