snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
0
zerver/webhooks/dbt/__init__.py
Normal file
0
zerver/webhooks/dbt/__init__.py
Normal file
40
zerver/webhooks/dbt/doc.md
Normal file
40
zerver/webhooks/dbt/doc.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Zulip dbt integration
|
||||
|
||||
Get notifications about dbt cloud job runs in Zulip!
|
||||
|
||||
{start_tabs}
|
||||
|
||||
1. {!create-an-incoming-webhook.md!}
|
||||
|
||||
1. {!generate-webhook-url-basic.md!}
|
||||
|
||||
If you'd like your notification messages to link to your dbt job
|
||||
runs, make sure you include the [**dbt Access URL**][dbt Access URLs]
|
||||
when generating the integration URL.
|
||||
|
||||
1. From your dbt Dashboard, navigate to **Account settings**. Select
|
||||
**Notification Settings**, go to the **Webhooks** section, and
|
||||
click **Create webhook**.
|
||||
|
||||
1. Set **Webhook name** to a name of your choice, such as `Zulip`. Select
|
||||
the **Events** and **Jobs** you want to receive notifications for,
|
||||
and set **Endpoint** to the URL generated above. Click **Save**.
|
||||
|
||||
1. Click **Test Endpoint** to send a test notification to
|
||||
your Zulip organization.
|
||||
|
||||
{end_tabs}
|
||||
|
||||
{!congrats.md!}
|
||||
|
||||

|
||||
|
||||
{!event-filtering-additional-feature.md!}
|
||||
|
||||
### Related documentation
|
||||
|
||||
- [dbt's webhooks documentation](https://docs.getdbt.com/docs/deploy/webhooks)
|
||||
- [dbt Access URLs][dbt Access URLs]
|
||||
{!webhooks-url-specification.md!}
|
||||
|
||||
[dbt Access URLs]: https://docs.getdbt.com/docs/cloud/about-cloud/access-regions-ip-addresses#api-access-urls
|
||||
24
zerver/webhooks/dbt/fixtures/job_run_completed_errored.json
Normal file
24
zerver/webhooks/dbt/fixtures/job_run_completed_errored.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"accountId": 1,
|
||||
"webhookId": "wsu_12345abcde",
|
||||
"eventId": "wev_2LkSAiWzKkSA59oilyd7FDeacXD",
|
||||
"timestamp": "2025-10-05T19:16:11.818389474Z",
|
||||
"eventType": "job.run.completed",
|
||||
"webhookName": "test",
|
||||
"data": {
|
||||
"jobId": "123",
|
||||
"jobName": "Daily Job (dbt build)",
|
||||
"runId": "12345",
|
||||
"environmentId": "1234",
|
||||
"environmentName": "Production",
|
||||
"dbtVersion": "2025.9.30+21d3fd7",
|
||||
"projectName": "Example Project",
|
||||
"projectId": "167194",
|
||||
"runStatus": "Errored",
|
||||
"runStatusCode": 20,
|
||||
"runStatusMessage": "dbt command failed",
|
||||
"runReason": "Kicked off from the UI by bwilliams@example.com",
|
||||
"runStartedAt": "2025-10-05T19:15:56Z",
|
||||
"runFinishedAt": "2025-10-05T19:16:06Z"
|
||||
}
|
||||
}
|
||||
24
zerver/webhooks/dbt/fixtures/job_run_completed_success.json
Normal file
24
zerver/webhooks/dbt/fixtures/job_run_completed_success.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"accountId": 1,
|
||||
"webhookId": "wsu_12345abcde",
|
||||
"eventId": "wev_2L6ZDoilyiWzKkSA59Gmc2d7FDD",
|
||||
"timestamp": "2023-01-31T19:29:35.789265936Z",
|
||||
"eventType": "job.run.completed",
|
||||
"webhookName": "test",
|
||||
"data": {
|
||||
"jobId": "123",
|
||||
"jobName": "Daily Job (dbt build)",
|
||||
"runId": "12345",
|
||||
"environmentId": "1234",
|
||||
"environmentName": "Production",
|
||||
"dbtVersion": "1.0.0",
|
||||
"projectName": "Example Project",
|
||||
"projectId": "167194",
|
||||
"runStatus": "Success",
|
||||
"runStatusCode": 10,
|
||||
"runStatusMessage": "None",
|
||||
"runReason": "Kicked off from the UI by bwilliams@example.com",
|
||||
"runStartedAt": "2023-01-31T19:28:07Z",
|
||||
"runFinishedAt": "2023-01-31T19:29:32Z"
|
||||
}
|
||||
}
|
||||
24
zerver/webhooks/dbt/fixtures/job_run_errored.json
Normal file
24
zerver/webhooks/dbt/fixtures/job_run_errored.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"accountId": 1,
|
||||
"webhookId": "wsu_12345abcde",
|
||||
"eventId": "wev_2L6m5BggBw9uPNuSmtg4MUiW4Re",
|
||||
"timestamp": "2023-01-31T21:15:20.419714619Z",
|
||||
"eventType": "job.run.errored",
|
||||
"webhookName": "test",
|
||||
"data": {
|
||||
"jobId": "123",
|
||||
"jobName": "Daily Job (dbt build)",
|
||||
"runId": "12345",
|
||||
"environmentId": "1234",
|
||||
"environmentName": "Production",
|
||||
"dbtVersion": "1.0.0",
|
||||
"projectName": "Example Project",
|
||||
"projectId": "167194",
|
||||
"runStatus": "Errored",
|
||||
"runStatusCode": 20,
|
||||
"runStatusMessage": "None",
|
||||
"runReason": "Kicked off from the UI by bwilliams@example.com",
|
||||
"runStartedAt": "2023-01-31T21:14:41Z",
|
||||
"runErroredAt": "2023-01-31T21:15:20Z"
|
||||
}
|
||||
}
|
||||
23
zerver/webhooks/dbt/fixtures/job_run_started.json
Normal file
23
zerver/webhooks/dbt/fixtures/job_run_started.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"accountId": 1,
|
||||
"webhookId": "wsu_12345abcde",
|
||||
"eventId": "wev_2L6Z3l8uPedXKPq9D2nWbPIip7Z",
|
||||
"timestamp": "2023-01-31T19:28:15.742843678Z",
|
||||
"eventType": "job.run.started",
|
||||
"webhookName": "test",
|
||||
"data": {
|
||||
"jobId": "123",
|
||||
"jobName": "Daily Job (dbt build)",
|
||||
"runId": "12345",
|
||||
"environmentId": "1234",
|
||||
"environmentName": "Production",
|
||||
"dbtVersion": "1.0.0",
|
||||
"projectName": "Example Project",
|
||||
"projectId": "167194",
|
||||
"runStatus": "Running",
|
||||
"runStatusCode": 3,
|
||||
"runStatusMessage": "None",
|
||||
"runReason": "Kicked off from the UI by bwilliams@example.com",
|
||||
"runStartedAt": "2023-01-31T19:28:07Z"
|
||||
}
|
||||
}
|
||||
32
zerver/webhooks/dbt/tests.py
Normal file
32
zerver/webhooks/dbt/tests.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from zerver.lib.test_classes import WebhookTestCase
|
||||
|
||||
|
||||
class DbtHookTests(WebhookTestCase):
|
||||
def test_dbt_webhook_when_job_started(self) -> None:
|
||||
expected_message = """:yellow_circle: Daily Job (dbt build) deployment started in **Production**.\n
|
||||
Job #123 was kicked off from the UI by bwilliams@example.com at <time:2023-01-31T19:28:07Z>."""
|
||||
self.check_webhook("job_run_started", "Example Project", expected_message)
|
||||
|
||||
def test_dbt_webhook_when_job_completed_success(self) -> None:
|
||||
expected_message = """:green_circle: Daily Job (dbt build) deployment succeeded in **Production**.\n
|
||||
Job #123 was kicked off from the UI by bwilliams@example.com at <time:2023-01-31T19:28:07Z>."""
|
||||
self.check_webhook("job_run_completed_success", "Example Project", expected_message)
|
||||
|
||||
def test_dbt_webhook_when_job_completed_errored(self) -> None:
|
||||
expected_message = """:cross_mark: Daily Job (dbt build) deployment completed with errors in **Production**.\n
|
||||
Job #123 was kicked off from the UI by bwilliams@example.com at <time:2025-10-05T19:15:56Z>."""
|
||||
self.check_webhook("job_run_completed_errored", "Example Project", expected_message)
|
||||
|
||||
def test_dbt_webhook_when_job_errored(self) -> None:
|
||||
expected_message = """:cross_mark: Daily Job (dbt build) deployment failed in **Production**.\n
|
||||
Job #123 was kicked off from the UI by bwilliams@example.com at <time:2023-01-31T21:14:41Z>."""
|
||||
self.check_webhook("job_run_errored", "Example Project", expected_message)
|
||||
|
||||
|
||||
class DbtHookWithAccessUrlTests(WebhookTestCase):
|
||||
URL_TEMPLATE = "/api/v1/external/dbt?&api_key={api_key}&stream={stream}&access_url=https%3A%2F%2Fexample.us1.dbt.com"
|
||||
|
||||
def test_dbt_webhook_with_valid_access_url(self) -> None:
|
||||
expected_message = """:yellow_circle: Daily Job (dbt build) [deployment](https://example.us1.dbt.com/deploy/1/projects/167194/runs/12345) started in **Production**.\n
|
||||
[Job #123](https://example.us1.dbt.com/deploy/1/projects/167194/jobs/123) was kicked off from the UI by bwilliams@example.com at <time:2023-01-31T19:28:07Z>."""
|
||||
self.check_webhook("job_run_started", "Example Project", expected_message)
|
||||
96
zerver/webhooks/dbt/view.py
Normal file
96
zerver/webhooks/dbt/view.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
|
||||
from zerver.decorator import webhook_view
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
|
||||
from zerver.lib.validator import WildValue, check_int, check_string
|
||||
from zerver.lib.webhooks.common import check_send_webhook_message
|
||||
from zerver.models import UserProfile
|
||||
|
||||
NOTIFICATION_TEMPLATE = """
|
||||
{emoji} {job_name} {run_text} {status} in **{environment}**.
|
||||
|
||||
{job_text} was {run_reason} at <time:{start_time}>.
|
||||
"""
|
||||
|
||||
EVENT_TYPE_MAPPER = {
|
||||
"job.run.started": {
|
||||
"running": (":yellow_circle:", "started"),
|
||||
},
|
||||
"job.run.completed": {
|
||||
"success": (":green_circle:", "succeeded"),
|
||||
"errored": (":cross_mark:", "completed with errors"),
|
||||
},
|
||||
"job.run.errored": {
|
||||
"errored": (":cross_mark:", "failed"),
|
||||
},
|
||||
}
|
||||
|
||||
ALL_EVENT_TYPES = list(EVENT_TYPE_MAPPER.keys())
|
||||
|
||||
|
||||
def extract_data_from_payload(payload: JsonBodyPayload[WildValue]) -> dict[str, str]:
|
||||
data: dict[str, str] = {
|
||||
"account_id": str(payload["accountId"].tame(check_int)),
|
||||
"event_type": payload["eventType"].tame(check_string),
|
||||
"job_id": payload["data"]["jobId"].tame(check_string),
|
||||
"job_name": payload["data"]["jobName"].tame(check_string),
|
||||
"project_name": payload["data"]["projectName"].tame(check_string),
|
||||
"project_id": payload["data"]["projectId"].tame(check_string),
|
||||
"environment": payload["data"]["environmentName"].tame(check_string),
|
||||
"run_id": payload["data"]["runId"].tame(check_string),
|
||||
"start_time": payload["data"]["runStartedAt"].tame(check_string),
|
||||
"run_status": payload["data"]["runStatus"].tame(check_string).lower(),
|
||||
}
|
||||
# We only change the capitalization of the first letter in this
|
||||
# string for the formatting of our notification template.
|
||||
run_reason = payload["data"]["runReason"].tame(check_string)
|
||||
data["run_reason"] = run_reason[:1].lower() + run_reason[1:]
|
||||
return data
|
||||
|
||||
|
||||
def get_job_run_body(data: dict[str, str], access_url: str | None) -> str:
|
||||
emoji, status = EVENT_TYPE_MAPPER[data["event_type"]][data["run_status"]]
|
||||
|
||||
project_url = (
|
||||
urljoin(
|
||||
access_url,
|
||||
f"/deploy/{data['account_id']}/projects/{data['project_id']}",
|
||||
)
|
||||
if access_url
|
||||
else None
|
||||
)
|
||||
job_text = (
|
||||
f"[Job #{data['job_id']}]({project_url}/jobs/{data['job_id']})"
|
||||
if project_url
|
||||
else f"Job #{data['job_id']}"
|
||||
)
|
||||
run_text = f"[deployment]({project_url}/runs/{data['run_id']})" if project_url else "deployment"
|
||||
|
||||
body = NOTIFICATION_TEMPLATE.format(
|
||||
emoji=emoji,
|
||||
status=status,
|
||||
run_text=run_text,
|
||||
job_text=job_text,
|
||||
**data,
|
||||
)
|
||||
return body
|
||||
|
||||
|
||||
@webhook_view("DBT", all_event_types=ALL_EVENT_TYPES)
|
||||
@typed_endpoint
|
||||
def api_dbt_webhook(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
*,
|
||||
payload: JsonBodyPayload[WildValue],
|
||||
access_url: str | None = None,
|
||||
) -> HttpResponse:
|
||||
data = extract_data_from_payload(payload)
|
||||
body = get_job_run_body(data, access_url)
|
||||
topic_name = data["project_name"]
|
||||
event = data["event_type"]
|
||||
check_send_webhook_message(request, user_profile, topic_name, body, event)
|
||||
return json_success(request)
|
||||
Reference in New Issue
Block a user