integrations: Update incoming webhooks overview and walkthrough.

- Updates incoming webhooks overview and walkthrough to be consistent
  with the `zerver/webhooks/` codebase.
- Tweaks the documentation for better readability.
This commit is contained in:
David Rosa 2023-12-19 16:43:13 -08:00 committed by Tim Abbott
parent 27deebe45b
commit cc472e87f9
2 changed files with 58 additions and 49 deletions

View File

@ -1,7 +1,7 @@
# Incoming webhook integrations
An incoming webhook allows a third-party service to push data to Zulip when
something happens. There's several ways to do an incoming webhook in
something happens. There are several ways to set up an incoming webhook in
Zulip:
* Use our [REST API](/api/rest) endpoint for [sending
@ -11,9 +11,9 @@ Zulip:
* Use one of our supported [integration
frameworks](/integrations/meta-integration), such as the
[Slack-compatible incoming webhook](/integrations/doc/slack_incoming),
[Zapier integration](/integrations/docs/zapier), or
[Zapier integration](/integrations/doc/zapier), or
[IFTTT integration](/integrations/doc/ifttt).
* Adding an incoming webhook integration (detailed on this page),
* Implementing an incoming webhook integration (detailed on this page),
where all the logic for formatting the Zulip messages lives in the
Zulip server. This is how most of [Zulip's official
integrations](/integrations/) work, because they enable Zulip to
@ -22,7 +22,7 @@ Zulip:
Zulip).
In an incoming webhook integration, the third-party service's
"outgoing webhook" feature sends an `HTTP POST`s to a special URL when
"outgoing webhook" feature sends an `HTTP POST` to a special URL when
it has something for you, and then the Zulip "incoming webhook"
integration handles that incoming data to format and send a message in
Zulip.
@ -40,18 +40,18 @@ process.
<https://webhook.site/>, or a similar site to capture an example
webhook payload from the third-party service. Create a
`zerver/webhooks/<mywebhook>/fixtures/` directory, and add the
captured payload as a test fixture.
captured JSON payload as a test fixture.
* Create an `Integration` object, and add it to `WEBHOOK_INTEGRATIONS` in
`zerver/lib/integrations.py`. Search for `webhook` in that file to find an
existing one to copy.
* Create an `Integration` object, and add it to the `WEBHOOK_INTEGRATIONS`
list in `zerver/lib/integrations.py`. Search for `WebhookIntegration` in that
file to find an existing one to copy.
* Write a draft webhook handler under `zerver/webhooks/`. There are a lot of
examples in that directory that you can copy. We recommend templating off
a short one, like `zendesk`.
* Write a draft webhook handler in `zerver/webhooks/<mywebhook>/view.py`. There
are a lot of examples in the `zerver/webhooks/` directory that you can copy.
We recommend templating from a short one, like `zendesk`.
* Add a test for your fixture at `zerver/webhooks/<mywebhook>/tests.py`.
Run the tests for your integration like this:
* Write a test for your fixture in `zerver/webhooks/<mywebhook>/tests.py`.
Run the test for your integration like this:
```
tools/test-backend zerver/webhooks/<mywebhook>/
@ -64,10 +64,10 @@ process.
service will make, and add tests for them; usually this part of the
process is pretty fast.
* Document the integration (required for getting it merged into Zulip). You
can template off an existing guide, like
[this one](https://raw.githubusercontent.com/zulip/zulip/main/zerver/webhooks/github/doc.md).
This should not take more than 15 minutes, even if you don't speak English
* Document the integration in `zerver/webhooks/<mywebhook>/doc.md`(required for
getting it merged into Zulip). You can use existing documentation, like
[this one](https://raw.githubusercontent.com/zulip/zulip/main/zerver/webhooks/github/doc.md),
as a template. This should not take more than 15 minutes, even if you don't speak English
as a first language (we'll clean up the text before merging).
## Hello world walkthrough
@ -84,9 +84,9 @@ below are for a webhook named `MyWebHook`.
* `zerver/webhooks/mywebhook/__init__.py`: Empty file that is an obligatory
part of every python package. Remember to `git add` it.
* `zerver/webhooks/mywebhook/view.py`: The main webhook integration function
as well as any needed helper functions.
* `zerver/webhooks/mywebhook/fixtures/messagetype.json`: Sample json payload data
* `zerver/webhooks/mywebhook/view.py`: The main webhook integration function,
called `api_mywebhook_webhook`, along with any necessary helper functions.
* `zerver/webhooks/mywebhook/fixtures/message_type.json`: Sample JSON payload data
used by tests. Add one fixture file per type of message supported by your
integration.
* `zerver/webhooks/mywebhook/tests.py`: Tests for your webhook.
@ -95,7 +95,7 @@ below are for a webhook named `MyWebHook`.
* `static/images/integrations/logos/mywebhook.svg`: A square logo for the
platform/server/product you are integrating. Used on the documentation
pages as well as the sender's avatar for messages sent by the integration.
* `static/images/integrations/mywebhook/001.svg`: A screenshot of a message
* `static/images/integrations/mywebhook/001.png`: A screenshot of a message
sent by the integration, used on the documentation page. This can be
generated by running `tools/generate-integration-docs-screenshot --integration mywebhook`.
* `static/images/integrations/bot_avatars/mywebhook.png`: A square logo for the
@ -125,8 +125,8 @@ below are for a webhook named `MyWebHook`.
## General advice
* Consider using our Zulip markup to make the output from your
integration especially attractive or useful (e.g. emoji, Markdown
emphasis or @-mentions).
integration especially attractive or useful (e.g. emoji, Markdown
emphasis, or @-mentions).
* Use topics effectively to ensure sequential messages about the same
thing are threaded together; this makes for much better consumption

View File

@ -69,7 +69,7 @@ integration uses.
## Step 1: Initialize your webhook python package
In the `zerver/webhooks/` directory, create new subdirectory that will
contain all of corresponding code. In our example it will be
contain all of the corresponding code. In our example it will be
`helloworld`. The new directory will be a python package, so you have
to create an empty `__init__.py` file in that directory via e.g.
`touch zerver/webhooks/helloworld/__init__.py`.
@ -82,25 +82,24 @@ python file, `zerver/webhooks/mywebhook/view.py`.
The Hello World integration is in `zerver/webhooks/helloworld/view.py`:
```python
from typing import Any, Dict, Sequence
from django.http import HttpRequest, HttpResponse
from zerver.decorator import webhook_view
from zerver.lib.request import REQ, has_request_variables
from zerver.lib.response import json_success
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
from zerver.lib.validator import WildValue, check_string
from zerver.lib.webhooks.common import check_send_webhook_message
from zerver.models import UserProfile
@webhook_view("HelloWorld")
@has_request_variables
@typed_endpoint
def api_helloworld_webhook(
request: HttpRequest,
user_profile: UserProfile,
payload: Dict[str, Sequence[Dict[str, Any]]] = REQ(argument_type="body"),
*,
payload: JsonBodyPayload[WildValue],
) -> HttpResponse:
# construct the body of the message
body = "Hello! I am happy to be here! :smile:"
@ -108,7 +107,10 @@ def api_helloworld_webhook(
body_template = (
"\nThe Wikipedia featured article for today is **[{featured_title}]({featured_url})**"
)
body += body_template.format(**payload)
body += body_template.format(
featured_title=payload["featured_title"].tame(check_string),
featured_url=payload["featured_url"].tame(check_string),
)
topic = "Hello World"
@ -120,9 +122,8 @@ def api_helloworld_webhook(
The above code imports the required functions and defines the main webhook
function `api_helloworld_webhook`, decorating it with `webhook_view` and
`has_request_variables`. The `has_request_variables` decorator allows you to
access request variables with `REQ()`. You can find more about `REQ` and request
variables in [Writing views](
`typed_endpoint`. The `typed_endpoint` decorator allows you to
access request variables with `JsonBodyPayload()`. You can find more about `JsonBodyPayload` and request variables in [Writing views](
https://zulip.readthedocs.io/en/latest/tutorials/writing-views.html#request-variables).
You must pass the name of your integration to the
@ -191,7 +192,7 @@ WEBHOOK_INTEGRATIONS: List[WebhookIntegration] = [
And you'll find the entry for Hello World:
```python
WebhookIntegration('helloworld', ['misc'], display_name='Hello World'),
WebhookIntegration("helloworld", ["misc"], display_name="Hello World"),
```
This tells the Zulip API to call the `api_helloworld_webhook` function in
@ -199,7 +200,7 @@ This tells the Zulip API to call the `api_helloworld_webhook` function in
`/api/v1/external/helloworld`.
This line also tells Zulip to generate an entry for Hello World on the Zulip
integrations page using `static/images/integrations/logos/helloworld.png` as its
integrations page using `static/images/integrations/logos/helloworld.svg` as its
icon. The second positional argument defines a list of categories for the
integration.
@ -323,19 +324,23 @@ class `HelloWorldHookTests`:
```python
class HelloWorldHookTests(WebhookTestCase):
STREAM_NAME = 'test'
URL_TEMPLATE = "/api/v1/external/helloworld?&api_key={api_key}"
WEBHOOK_DIR_NAME = 'helloworld'
STREAM_NAME = "test"
URL_TEMPLATE = "/api/v1/external/helloworld?&api_key={api_key}&stream={stream}"
DIRECT_MESSAGE_URL_TEMPLATE = "/api/v1/external/helloworld?&api_key={api_key}"
WEBHOOK_DIR_NAME = "helloworld"
# Note: Include a test function per each distinct message condition your integration supports
def test_hello_message(self) -> None:
expected_topic = "Hello World";
expected_message = "Hello! I am happy to be here! :smile: \nThe Wikipedia featured article for today is **[Marilyn Monroe](https://en.wikipedia.org/wiki/Marilyn_Monroe)**";
expected_topic = "Hello World"
expected_message = "Hello! I am happy to be here! :smile:\nThe Wikipedia featured article for today is **[Marilyn Monroe](https://en.wikipedia.org/wiki/Marilyn_Monroe)**"
# use fixture named helloworld_hello
self.check_webhook('hello', expected_topic, expected_message,
content_type="application/x-www-form-urlencoded")
self.check_webhook(
"hello",
expected_topic,
expected_message,
content_type="application/x-www-form-urlencoded",
)
```
In the above example, `STREAM_NAME`, `URL_TEMPLATE`, and `WEBHOOK_DIR_NAME` refer
@ -363,12 +368,16 @@ class called something like `test_goodbye_message`:
```python
def test_goodbye_message(self) -> None:
expected_topic = "Hello World";
expected_message = "Hello! I am happy to be here! :smile:\nThe Wikipedia featured article for today is **[Goodbye](https://en.wikipedia.org/wiki/Goodbye)**";
expected_topic = "Hello World"
expected_message = "Hello! I am happy to be here! :smile:\nThe Wikipedia featured article for today is **[Goodbye](https://en.wikipedia.org/wiki/Goodbye)**"
# use fixture named helloworld_goodbye
self.check_webhook('goodbye', expected_topic, expected_message,
content_type="application/x-www-form-urlencoded")
self.check_webhook(
"goodbye",
expected_topic,
expected_message,
content_type="application/x-www-form-urlencoded",
)
```
As well as a new fixture `goodbye.json` in
@ -491,7 +500,7 @@ request:
5. Submit a pull request to zulip/zulip.
If you would like feedback on your integration as you go, feel free to post a
message on the [public Zulip instance](https://chat.zulip.org/#narrow/stream/bots).
message on the [public Zulip instance](https://chat.zulip.org/#narrow/stream/integrations).
You can also create a [draft pull request](
https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests#draft-pull-requests) while you
are still working on your integration. See the