mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 14:03:30 +00:00
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:
@@ -58,23 +58,25 @@ python file, `zerver/webhooks/mywebhook/view.py`.
|
|||||||
The Hello World integration is in `zerver/webhooks/helloworld/view.py`:
|
The Hello World integration is in `zerver/webhooks/helloworld/view.py`:
|
||||||
|
|
||||||
```
|
```
|
||||||
from django.utils.translation import ugettext as _
|
from typing import Any, Dict, Iterable, Optional, Text
|
||||||
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 django.http import HttpRequest, HttpResponse
|
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')
|
@api_key_only_webhook_view('HelloWorld')
|
||||||
@has_request_variables
|
@has_request_variables
|
||||||
def api_helloworld_webhook(request: HttpRequest, user_profile: UserProfile,
|
def api_helloworld_webhook(
|
||||||
payload: Dict[str, Iterable[Dict[str, Any]]]=REQ(argument_type='body'),
|
request: HttpRequest, user_profile: UserProfile,
|
||||||
stream: Text=REQ(default='test'),
|
payload: Dict[str, Iterable[Dict[str, Any]]]=REQ(argument_type='body')
|
||||||
topic: Text=REQ(default='Hello World')) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
|
|
||||||
# construct the body of the message
|
# construct the body of the message
|
||||||
body = 'Hello! I am happy to be here! :smile:'
|
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_template = '\nThe Wikipedia featured article for today is **[{featured_title}]({featured_url})**'
|
||||||
body += body_template.format(**payload)
|
body += body_template.format(**payload)
|
||||||
|
|
||||||
# send the message
|
topic = "Hello World"
|
||||||
check_send_stream_message(user_profile, request.client,
|
|
||||||
stream, topic, body)
|
# send the message
|
||||||
|
check_send_webhook_message(request, user_profile, topic, body)
|
||||||
|
|
||||||
# return json result
|
|
||||||
return json_success()
|
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
|
integration checks for. In such a case, any `KeyError` thrown is handled by the server
|
||||||
backend and will create an appropriate response.
|
backend and will create an appropriate response.
|
||||||
|
|
||||||
Then we send a public (stream) message with `check_send_stream_message` which will
|
Then we send a message with `check_send_webhook_message`, which will
|
||||||
validate the message and then send it.
|
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
|
Finally, we return a 200 http status with a JSON format success message via
|
||||||
`json_success()`.
|
`json_success()`.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from django.conf import settings
|
|||||||
from django.core import validators
|
from django.core import validators
|
||||||
from analytics.lib.counts import COUNT_STATS, do_increment_logging_stat, \
|
from analytics.lib.counts import COUNT_STATS, do_increment_logging_stat, \
|
||||||
RealmCount
|
RealmCount
|
||||||
|
|
||||||
from zerver.lib.bugdown import (
|
from zerver.lib.bugdown import (
|
||||||
BugdownRenderingException,
|
BugdownRenderingException,
|
||||||
version as bugdown_version,
|
version as bugdown_version,
|
||||||
|
|||||||
24
zerver/lib/webhooks/common.py
Normal file
24
zerver/lib/webhooks/common.py
Normal 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)
|
||||||
@@ -5,7 +5,8 @@ from zerver.lib.test_classes import WebhookTestCase
|
|||||||
|
|
||||||
class HelloWorldHookTests(WebhookTestCase):
|
class HelloWorldHookTests(WebhookTestCase):
|
||||||
STREAM_NAME = 'test'
|
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'
|
FIXTURE_DIR_NAME = 'hello'
|
||||||
|
|
||||||
# Note: Include a test function per each distinct message condition your integration supports
|
# 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,
|
self.send_and_test_stream_message('goodbye', expected_subject, expected_message,
|
||||||
content_type="application/x-www-form-urlencoded")
|
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:
|
def get_body(self, fixture_name: Text) -> Text:
|
||||||
return self.fixture_data("helloworld", fixture_name, file_type="json")
|
return self.fixture_data("helloworld", fixture_name, file_type="json")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from django.http import HttpRequest, HttpResponse
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from zerver.decorator import api_key_only_webhook_view
|
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.request import REQ, has_request_variables
|
||||||
from zerver.lib.response import json_error, json_success
|
from zerver.lib.response import json_error, json_success
|
||||||
from zerver.lib.validator import check_dict, check_string
|
from zerver.lib.validator import check_dict, check_string
|
||||||
@@ -13,10 +13,10 @@ from zerver.models import UserProfile
|
|||||||
|
|
||||||
@api_key_only_webhook_view('HelloWorld')
|
@api_key_only_webhook_view('HelloWorld')
|
||||||
@has_request_variables
|
@has_request_variables
|
||||||
def api_helloworld_webhook(request: HttpRequest, user_profile: UserProfile,
|
def api_helloworld_webhook(
|
||||||
payload: Dict[str, Iterable[Dict[str, Any]]]=REQ(argument_type='body'),
|
request: HttpRequest, user_profile: UserProfile,
|
||||||
stream: Text=REQ(default='test'),
|
payload: Dict[str, Iterable[Dict[str, Any]]]=REQ(argument_type='body')
|
||||||
topic: Text=REQ(default='Hello World')) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
|
|
||||||
# construct the body of the message
|
# construct the body of the message
|
||||||
body = 'Hello! I am happy to be here! :smile:'
|
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_template = '\nThe Wikipedia featured article for today is **[{featured_title}]({featured_url})**'
|
||||||
body += body_template.format(**payload)
|
body += body_template.format(**payload)
|
||||||
|
|
||||||
|
topic = "Hello World"
|
||||||
|
|
||||||
# send the message
|
# 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()
|
return json_success()
|
||||||
|
|||||||
@@ -181,15 +181,19 @@ class Command(BaseCommand):
|
|||||||
zulip_realm_bots.extend(all_realm_bots)
|
zulip_realm_bots.extend(all_realm_bots)
|
||||||
create_users(zulip_realm, zulip_realm_bots, bot_type=UserProfile.DEFAULT_BOT)
|
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_bots = [
|
||||||
("Zulip Webhook Bot", "webhook-bot@zulip.com"),
|
("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,
|
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 = [
|
zulip_outgoing_bots = [
|
||||||
("Outgoing Webhook", "outgoing-webhook@zulip.com")
|
("Outgoing Webhook", "outgoing-webhook@zulip.com")
|
||||||
]
|
]
|
||||||
aaron = get_user("AARON@zulip.com", zulip_realm)
|
|
||||||
create_users(zulip_realm, zulip_outgoing_bots,
|
create_users(zulip_realm, zulip_outgoing_bots,
|
||||||
bot_type=UserProfile.OUTGOING_WEBHOOK_BOT, bot_owner=aaron)
|
bot_type=UserProfile.OUTGOING_WEBHOOK_BOT, bot_owner=aaron)
|
||||||
# TODO: Clean up this initial bot creation code
|
# TODO: Clean up this initial bot creation code
|
||||||
|
|||||||
Reference in New Issue
Block a user