mirror of
https://github.com/zulip/zulip.git
synced 2025-11-05 22:43:42 +00:00
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:
@@ -1,7 +1,7 @@
|
|||||||
# Incoming webhook integrations
|
# Incoming webhook integrations
|
||||||
|
|
||||||
An incoming webhook allows a third-party service to push data to Zulip when
|
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:
|
Zulip:
|
||||||
|
|
||||||
* Use our [REST API](/api/rest) endpoint for [sending
|
* Use our [REST API](/api/rest) endpoint for [sending
|
||||||
@@ -11,9 +11,9 @@ Zulip:
|
|||||||
* Use one of our supported [integration
|
* Use one of our supported [integration
|
||||||
frameworks](/integrations/meta-integration), such as the
|
frameworks](/integrations/meta-integration), such as the
|
||||||
[Slack-compatible incoming webhook](/integrations/doc/slack_incoming),
|
[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).
|
[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
|
where all the logic for formatting the Zulip messages lives in the
|
||||||
Zulip server. This is how most of [Zulip's official
|
Zulip server. This is how most of [Zulip's official
|
||||||
integrations](/integrations/) work, because they enable Zulip to
|
integrations](/integrations/) work, because they enable Zulip to
|
||||||
@@ -22,7 +22,7 @@ Zulip:
|
|||||||
Zulip).
|
Zulip).
|
||||||
|
|
||||||
In an incoming webhook integration, the third-party service's
|
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"
|
it has something for you, and then the Zulip "incoming webhook"
|
||||||
integration handles that incoming data to format and send a message in
|
integration handles that incoming data to format and send a message in
|
||||||
Zulip.
|
Zulip.
|
||||||
@@ -40,18 +40,18 @@ process.
|
|||||||
<https://webhook.site/>, or a similar site to capture an example
|
<https://webhook.site/>, or a similar site to capture an example
|
||||||
webhook payload from the third-party service. Create a
|
webhook payload from the third-party service. Create a
|
||||||
`zerver/webhooks/<mywebhook>/fixtures/` directory, and add the
|
`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
|
* Create an `Integration` object, and add it to the `WEBHOOK_INTEGRATIONS`
|
||||||
`zerver/lib/integrations.py`. Search for `webhook` in that file to find an
|
list in `zerver/lib/integrations.py`. Search for `WebhookIntegration` in that
|
||||||
existing one to copy.
|
file to find an existing one to copy.
|
||||||
|
|
||||||
* Write a draft webhook handler under `zerver/webhooks/`. There are a lot of
|
* Write a draft webhook handler in `zerver/webhooks/<mywebhook>/view.py`. There
|
||||||
examples in that directory that you can copy. We recommend templating off
|
are a lot of examples in the `zerver/webhooks/` directory that you can copy.
|
||||||
a short one, like `zendesk`.
|
We recommend templating from a short one, like `zendesk`.
|
||||||
|
|
||||||
* Add a test for your fixture at `zerver/webhooks/<mywebhook>/tests.py`.
|
* Write a test for your fixture in `zerver/webhooks/<mywebhook>/tests.py`.
|
||||||
Run the tests for your integration like this:
|
Run the test for your integration like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
tools/test-backend zerver/webhooks/<mywebhook>/
|
tools/test-backend zerver/webhooks/<mywebhook>/
|
||||||
@@ -64,10 +64,10 @@ process.
|
|||||||
service will make, and add tests for them; usually this part of the
|
service will make, and add tests for them; usually this part of the
|
||||||
process is pretty fast.
|
process is pretty fast.
|
||||||
|
|
||||||
* Document the integration (required for getting it merged into Zulip). You
|
* Document the integration in `zerver/webhooks/<mywebhook>/doc.md`(required for
|
||||||
can template off an existing guide, like
|
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).
|
[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
|
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).
|
as a first language (we'll clean up the text before merging).
|
||||||
|
|
||||||
## Hello world walkthrough
|
## 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
|
* `zerver/webhooks/mywebhook/__init__.py`: Empty file that is an obligatory
|
||||||
part of every python package. Remember to `git add` it.
|
part of every python package. Remember to `git add` it.
|
||||||
* `zerver/webhooks/mywebhook/view.py`: The main webhook integration function
|
* `zerver/webhooks/mywebhook/view.py`: The main webhook integration function,
|
||||||
as well as any needed helper functions.
|
called `api_mywebhook_webhook`, along with any necessary helper functions.
|
||||||
* `zerver/webhooks/mywebhook/fixtures/messagetype.json`: Sample json payload data
|
* `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
|
used by tests. Add one fixture file per type of message supported by your
|
||||||
integration.
|
integration.
|
||||||
* `zerver/webhooks/mywebhook/tests.py`: Tests for your webhook.
|
* `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
|
* `static/images/integrations/logos/mywebhook.svg`: A square logo for the
|
||||||
platform/server/product you are integrating. Used on the documentation
|
platform/server/product you are integrating. Used on the documentation
|
||||||
pages as well as the sender's avatar for messages sent by the integration.
|
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
|
sent by the integration, used on the documentation page. This can be
|
||||||
generated by running `tools/generate-integration-docs-screenshot --integration mywebhook`.
|
generated by running `tools/generate-integration-docs-screenshot --integration mywebhook`.
|
||||||
* `static/images/integrations/bot_avatars/mywebhook.png`: A square logo for the
|
* `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
|
* Consider using our Zulip markup to make the output from your
|
||||||
integration especially attractive or useful (e.g. emoji, Markdown
|
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
|
* Use topics effectively to ensure sequential messages about the same
|
||||||
thing are threaded together; this makes for much better consumption
|
thing are threaded together; this makes for much better consumption
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ integration uses.
|
|||||||
## Step 1: Initialize your webhook python package
|
## Step 1: Initialize your webhook python package
|
||||||
|
|
||||||
In the `zerver/webhooks/` directory, create new subdirectory that will
|
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
|
`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.
|
to create an empty `__init__.py` file in that directory via e.g.
|
||||||
`touch zerver/webhooks/helloworld/__init__.py`.
|
`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`:
|
The Hello World integration is in `zerver/webhooks/helloworld/view.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from typing import Any, Dict, Sequence
|
|
||||||
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
|
||||||
from zerver.decorator import webhook_view
|
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.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.lib.webhooks.common import check_send_webhook_message
|
||||||
from zerver.models import UserProfile
|
from zerver.models import UserProfile
|
||||||
|
|
||||||
|
|
||||||
@webhook_view("HelloWorld")
|
@webhook_view("HelloWorld")
|
||||||
@has_request_variables
|
@typed_endpoint
|
||||||
def api_helloworld_webhook(
|
def api_helloworld_webhook(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
user_profile: UserProfile,
|
user_profile: UserProfile,
|
||||||
payload: Dict[str, Sequence[Dict[str, Any]]] = REQ(argument_type="body"),
|
*,
|
||||||
|
payload: JsonBodyPayload[WildValue],
|
||||||
) -> 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:"
|
||||||
|
|
||||||
@@ -108,7 +107,10 @@ def api_helloworld_webhook(
|
|||||||
body_template = (
|
body_template = (
|
||||||
"\nThe Wikipedia featured article for today is **[{featured_title}]({featured_url})**"
|
"\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"
|
topic = "Hello World"
|
||||||
|
|
||||||
@@ -120,9 +122,8 @@ def api_helloworld_webhook(
|
|||||||
|
|
||||||
The above code imports the required functions and defines the main webhook
|
The above code imports the required functions and defines the main webhook
|
||||||
function `api_helloworld_webhook`, decorating it with `webhook_view` and
|
function `api_helloworld_webhook`, decorating it with `webhook_view` and
|
||||||
`has_request_variables`. The `has_request_variables` decorator allows you to
|
`typed_endpoint`. The `typed_endpoint` decorator allows you to
|
||||||
access request variables with `REQ()`. You can find more about `REQ` and request
|
access request variables with `JsonBodyPayload()`. You can find more about `JsonBodyPayload` and request variables in [Writing views](
|
||||||
variables in [Writing views](
|
|
||||||
https://zulip.readthedocs.io/en/latest/tutorials/writing-views.html#request-variables).
|
https://zulip.readthedocs.io/en/latest/tutorials/writing-views.html#request-variables).
|
||||||
|
|
||||||
You must pass the name of your integration to the
|
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:
|
And you'll find the entry for Hello World:
|
||||||
|
|
||||||
```python
|
```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
|
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`.
|
`/api/v1/external/helloworld`.
|
||||||
|
|
||||||
This line also tells Zulip to generate an entry for Hello World on the Zulip
|
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
|
icon. The second positional argument defines a list of categories for the
|
||||||
integration.
|
integration.
|
||||||
|
|
||||||
@@ -323,19 +324,23 @@ class `HelloWorldHookTests`:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
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}"
|
||||||
WEBHOOK_DIR_NAME = 'helloworld'
|
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
|
# Note: Include a test function per each distinct message condition your integration supports
|
||||||
def test_hello_message(self) -> None:
|
def test_hello_message(self) -> None:
|
||||||
expected_topic = "Hello World";
|
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_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
|
# use fixture named helloworld_hello
|
||||||
self.check_webhook('hello', expected_topic, expected_message,
|
self.check_webhook(
|
||||||
content_type="application/x-www-form-urlencoded")
|
"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
|
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
|
```python
|
||||||
def test_goodbye_message(self) -> None:
|
def test_goodbye_message(self) -> None:
|
||||||
expected_topic = "Hello World";
|
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_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
|
# use fixture named helloworld_goodbye
|
||||||
self.check_webhook('goodbye', expected_topic, expected_message,
|
self.check_webhook(
|
||||||
content_type="application/x-www-form-urlencoded")
|
"goodbye",
|
||||||
|
expected_topic,
|
||||||
|
expected_message,
|
||||||
|
content_type="application/x-www-form-urlencoded",
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
As well as a new fixture `goodbye.json` in
|
As well as a new fixture `goodbye.json` in
|
||||||
@@ -491,7 +500,7 @@ request:
|
|||||||
5. Submit a pull request to zulip/zulip.
|
5. Submit a pull request to zulip/zulip.
|
||||||
|
|
||||||
If you would like feedback on your integration as you go, feel free to post a
|
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](
|
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
|
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
|
are still working on your integration. See the
|
||||||
|
|||||||
Reference in New Issue
Block a user