mirror of https://github.com/zulip/zulip.git
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:
parent
27deebe45b
commit
cc472e87f9
|
@ -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
|
||||
|
@ -126,7 +126,7 @@ below are for a webhook named `MyWebHook`.
|
|||
|
||||
* Consider using our Zulip markup to make the output from your
|
||||
integration especially attractive or useful (e.g. emoji, Markdown
|
||||
emphasis or @-mentions).
|
||||
emphasis, or @-mentions).
|
||||
|
||||
* Use topics effectively to ensure sequential messages about the same
|
||||
thing are threaded together; this makes for much better consumption
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue