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

This commit is contained in:
upstream-mirror
2026-09-15 15:05:30 +05:00
commit 64898b2c79
8999 changed files with 1757358 additions and 0 deletions

View File

View File

@@ -0,0 +1,32 @@
# Zulip Greenhouse integration
Receive Greenhouse notifications in Zulip!
{start_tabs}
1. {!create-an-incoming-webhook.md!}
1. {!generate-webhook-url-basic.md!}
1. On your Greenhouse **Dashboard**, click on the
**gear** (<i class="fa fa-cog"></i>) icon in the upper right
corner. Click on **Dev Center**, and click **Web Hooks**.
Click on **Web Hooks** one more time.
1. Set **Name this web hook** to a name of your choice, such as
`Zulip`. Set **When** to the event you'd like to be notified
about. Set **Endpoint URL** to the URL generated above.
1. Greenhouse requires you to provide a **Secret key**, but Zulip
doesn't expect any particular value. Set **Secret key** to any
random value, and click **Create Web hook**.
{end_tabs}
{!congrats.md!}
![](/static/images/integrations/greenhouse/001.png)
### Related documentation
{!webhooks-url-specification.md!}

View File

@@ -0,0 +1,112 @@
{
"action": "new_candidate_application",
"payload": {
"application": {
"id": 265293,
"rejected_at": null,
"prospect": false,
"status": "active",
"applied_at": "2013-03-22T00:00:00Z",
"last_activity_at": "2015-03-18T20:28:09Z",
"source": {
"id": 27,
"public_name": "LinkedIn"
},
"credited_to": null,
"rejection_reason": null,
"current_stage": {
"id": 678901,
"name": "Application Review",
"interviews": [
{
"id": 989099,
"name":"Application Review",
"status": "collect_feedback",
"interview_kit": {
"url": "http://app.greenhouse.io/guides/67656/people/265788",
"content": "",
"questions": []
},
"interviewers": []
}
]
},
"candidate": {
"id": 265788,
"created_at": "2013-10-04T01:24:48Z",
"first_name": "Hector",
"last_name": "Porter",
"title": null,
"company": null,
"phone_numbers": [
{
"value": "330-281-8004",
"type": "home"
}
],
"email_addresses": [
{
"value": "hector.porter.265788@example.com",
"type": "personal"
}
],
"addresses": [],
"website_addresses": [],
"social_media_addresses": [],
"recruiter": null,
"coordinator": null,
"photo_url": "https://prod-heroku.s3.amazonaws.com/...",
"attachments": [
{
"filename": "resume.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "resume"
}
],
"tags": [
"Import from Previous ATS"
],
"custom_fields": {
"favorite_color": {
"name": "Favorite Color",
"type": "short_text",
"value": "Blue"
}
}
},
"jobs": [
{
"id": 3485,
"name": "Designer",
"requisition_id": null,
"notes": "Digital and print",
"job_post_id": 54321,
"status": "open",
"created_at": "2013-10-02T22:59:29Z",
"opened_at": "2015-01-23T00:25:04Z",
"closed_at": null,
"departments": [
{
"id": 237,
"name": "Community"
}
],
"offices": [
{
"id": 54,
"name": "New York",
"location": "New York, NY"
}
],
"custom_fields": {
"employment_type": {
"name": "Employment Type",
"type": "single_select",
"value": "Full Time"
}
}
}
]
}
}
}

View File

@@ -0,0 +1,184 @@
{
"action": "hire_candidate",
"payload": {
"application": {
"id": 20,
"opening": {
"opening_id": "1234-56"
},
"credited_to": {
"id": 57,
"email": "bob_johnson1@localhost.com",
"name": "Robert Johnson"
},
"source": {
"id": 25,
"public_name": "Monster"
},
"candidate": {
"id": 19,
"first_name": "Johnny",
"last_name": "Smith",
"title": "Previous Title",
"external_id": "00000",
"phone_numbers": [{
"value": "518-555-1212",
"type": "work"
}, {
"value": "212-555-1212",
"type": "home"
}],
"email_addresses": [{
"value": "personal@example.com",
"type": "personal"
}, {
"value": "work@example.com",
"type": "work"
}],
"addresses": [{
"value": "455 Broadway New York, NY 10280",
"type": "home"
}],
"recruiter": {
"id": 55,
"email": "bob_johnson@localhost.com",
"name": "Bob Johnson"
},
"coordinator": {
"id": 56,
"email": "bob_johnson_approver1@localhost.com",
"name": "Robert J Approver"
},
"attachments": [{
"filename": "resume.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "resume"
}],
"custom_fields": {
"desired_level": {
"name": "Desired Level",
"type": "short_text",
"value": "Senior"
},
"favorite_programming_language": {
"name": "Favorite Programming Language",
"type": "short_text",
"value": "Rails"
}
}
},
"job": {
"id": 20,
"name": "Developer",
"open_date": "2014-11-20T22:49:14Z",
"close_date": "2014-11-25T22:49:14Z",
"requisition_id": null,
"departments": [{
"id": 7,
"name": "Technology"
}],
"offices": [{
"id": 13,
"name": "New York City",
"location": {
"name": "New York, NY"
}
}, {
"id": 14,
"name": "St. Louis",
"location": null
}],
"custom_fields": {
"approved": {
"name": "Approved",
"type": "boolean",
"value": true
},
"employment_type": {
"name": "Employment Type",
"type": "single_select",
"value": "Full-time"
},
"salary_range": {
"name": "SalaryRange",
"type": "currency_range",
"value": {
"unit": "USD",
"min_value": "10000.0",
"max_value": "20000.0"
}
}
}
},
"jobs": [{
"id": 20,
"name": "Developer",
"opened_at": "2014-11-20T22:49:14Z",
"closed_at": "2014-11-25T22:49:14Z",
"requisition_id": null,
"departments": [{
"id": 7,
"name": "Technology"
}],
"offices": [{
"id": 13,
"name": "New York City",
"location": {
"name": "New York, NY"
}
}, {
"id": 14,
"name": "St. Louis",
"location": null
}],
"custom_fields": {
"approved": {
"name": "Approved",
"type": "boolean",
"value": true
},
"employment_type": {
"name": "Employment Type",
"type": "single_select",
"value": "Full-time"
},
"salary_range": {
"name": "SalaryRange",
"type": "currency_range",
"value": {
"unit": "USD",
"min_value": "10000.0",
"max_value": "20000.0"
}
}
}
}],
"offer": {
"id": 13,
"version": 2,
"created_at": "2014-11-20T22:49:14Z",
"sent_at": "2014-11-10",
"resolved_at": "2014-11-20T22:49:14Z",
"starts_at": "2015-01-23",
"custom_fields": {
"salary": {
"name": "Salary",
"type": "currency",
"value": {
"amount": 80000,
"unit": "USD"
}
},
"seasons": {
"name": "Seasons",
"type": "multi_select",
"value": [
"Season 1",
"Season 2"
]
}
}
}
}
}
}

View File

@@ -0,0 +1,90 @@
{
"action": "reject_candidate",
"payload": {
"application": {
"id": 265293,
"rejected_at": "2015-02-11T15:50:41Z",
"prospect": false,
"status": "rejected",
"applied_at": "2013-03-22T00:00:00Z",
"last_activity_at": "2015-02-11T15:50:41Z",
"source": {
"id": 27,
"public_name": "LinkedIn"
},
"credited_to": null,
"rejection_reason": {
"id": 14,
"name": "Too Junior",
"type": {
"id": 3,
"name": "Wrong skill set"
}
},
"candidate": {
"id": 265788,
"created_at": "2013-10-04T01:24:48Z",
"first_name": "Hector",
"last_name": "Porter",
"title": null,
"company": null,
"phone_numbers": [{
"value": "330-281-8004",
"type": "home"
}],
"email_addresses": [{
"value": "hector.porter.265788@example.com",
"type": "personal"
}],
"addresses": [],
"website_addresses": [],
"social_media_addresses": [],
"recruiter": null,
"coordinator": null,
"photo_url": "www.example.com/photo.png",
"attachments": [{
"filename": "resume.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "resume"
}],
"tags": [
"Imported"
],
"custom_fields": {
"favorite_color": {
"name": "Favorite Color",
"type": "short_text",
"value": "Blue"
}
}
},
"jobs": [{
"id": 3485,
"name": "Designer",
"requisition_id": null,
"notes": "Digital and print",
"job_post_id": 54321,
"status": "open",
"created_at": "2013-10-02T22:59:29Z",
"opened_at": "2015-01-23T00:25:04Z",
"closed_at": null,
"departments": [{
"id": 237,
"name": "Community"
}],
"offices": [{
"id": 54,
"name": "New York",
"location": "New York, NY"
}],
"custom_fields": {
"employment_type": {
"name": "Employment Type",
"type": "single_select",
"value": "Full Time"
}
}
}]
}
}
}

View File

@@ -0,0 +1,130 @@
{
"action": "candidate_stage_change",
"payload": {
"application": {
"id": 265277,
"rejected_at": null,
"prospect": false,
"status": "active",
"applied_at": "2013-03-22T00:00:00Z",
"last_activity_at": "2015-02-09T16:38:36Z",
"source": {
"id": 31,
"name": "Agency"
},
"credited_to": {
"id": 15,
"email": "ada@example.com",
"name": "Ada Lacey"
},
"rejection_reason": null,
"current_stage": {
"id": 71416,
"name": "Assessment",
"interviews": [{
"id": 113101,
"name": "Assessment",
"status": "to_be_scheduled",
"interview_kit": {
"url": "https://app.greenhouse.io/guides/113153/people/265772",
"content": "Assess their skills",
"questions": []
},
"interviewers": [{
"id": 2622,
"display_name": "Carl Buddha",
"status": "tentative"
}]
}]
},
"candidate": {
"id": 265772,
"created_at": "2013-10-04T01:24:44Z",
"first_name": "Giuseppe",
"last_name": "Hurley",
"external_id": "241b399ce4b0fd1c84e5528d",
"title": "Great Person",
"company": null,
"photo_url": "https://prod-heroku.s3.amazonaws.com/...",
"phone_numbers": [{
"value": "330-281-8004",
"type": "home"
}],
"email_addresses": [{
"value": "giuseppe.hurley@example.com",
"type": "personal"
}],
"addresses": [{
"value": "123 Fake St.",
"type": "home"
}],
"website_addresses": [{
"value": "ghurley.example.com",
"type": "personal"
}],
"social_media_addresses": [{
"value": "linkedin.example.com/ghurley"
}],
"recruiter": {
"id": 3128,
"email": "alicia.flopple.3128@example.com",
"name": "Alicia Flopple"
},
"coordinator": {
"id": 3128,
"email": "alicia.flopple.3128@example.com",
"name": "Alicia Flopple"
},
"attachments": [{
"filename": "resume.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "resume"
}, {
"filename": "cover_letter.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "cover_letter"
}, {
"filename": "portfolio.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "attachment"
}],
"tags": [
"Import from Previous ATS"
],
"custom_fields": {
"favorite_color": {
"name": "Favorite Color",
"type": "short_text",
"value": "Blue"
}
}
},
"jobs": [{
"id": 3485,
"name": "Designer",
"requisition_id": null,
"notes": "Digital and print",
"status": "open",
"created_at": "2013-10-02T22:59:29Z",
"opened_at": "2015-01-23T00:25:04Z",
"closed_at": null,
"departments": [{
"id": 237,
"name": "Community"
}],
"offices": [{
"id": 54,
"name": "New York",
"location": "New York, NY"
}],
"custom_fields": {
"employment_type": {
"name": "Employment Type",
"type": "single_select",
"value": "Full Time"
}
}
}]
}
}
}

View File

@@ -0,0 +1,13 @@
{
"payload":{
"active":true,
"created_at":"2018-10-19T16:25:57Z",
"web_hook_id":9590,
"name":"Zulip",
"endpoint_url":"https:\/\/zulip.example.com\/",
"content-type":"application\/json",
"updated_at":"2018-10-19T16:49:16Z",
"event":"Candidate has been hired"
},
"action":"ping"
}

View File

@@ -0,0 +1,104 @@
{
"action": "new_prospect_application",
"payload": {
"application": {
"id": 979554,
"rejected_at": null,
"prospect": true,
"status": "active",
"applied_at": "2014-12-02T23:10:16Z",
"last_activity_at": "2014-12-02T23:10:16Z",
"current_stage": null,
"source": {
"id": 13,
"public_name": "Referral"
},
"credited_to": {
"id": 2622,
"email": "carl.buddha.2622@example.com",
"name": "Carl Buddha"
},
"rejection_reason": null,
"candidate": {
"id": 968190,
"created_at": "2014-12-02T23:10:16Z",
"first_name": "Trisha",
"last_name": "Troy",
"title": null,
"company": null,
"phone_numbers": [
{
"value": "123456",
"type": "other"
}
],
"email_addresses": [
{
"value": "t.troy@example.com",
"type": "personal"
}
],
"addresses": [],
"website_addresses": [],
"social_media_addresses": [],
"recruiter": {
"id": 3128,
"email": "alicia.flopple.3128@example.com",
"name": "Alicia Flopple"
},
"coordinator": null,
"photo_url": "https://prod-heroku.s3.amazonaws.com/...",
"attachments": [
{
"filename": "resume.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "resume"
}
],
"tags": [
"Import from Previous ATS"
],
"custom_fields": {
"favorite_color": {
"name": "Favorite Color",
"type": "short_text",
"value": "Blue"
}
}
},
"jobs": [
{
"id": 3485,
"name": "Designer",
"requisition_id": null,
"notes": "Digital and print",
"job_post_id": 54321,
"status": "open",
"created_at": "2013-10-02T22:59:29Z",
"opened_at": "2015-01-23T00:25:04Z",
"closed_at": null,
"departments": [
{
"id": 237,
"name": "Community"
}
],
"offices": [
{
"id": 54,
"name": "New York",
"location": "New York, NY"
}
],
"custom_fields": {
"employment_type": {
"name": "Employment Type",
"type": "single_select",
"value": "Full Time"
}
}
}
]
}
}
}

View File

@@ -0,0 +1,76 @@
from unittest.mock import MagicMock, patch
from zerver.lib.test_classes import WebhookTestCase
class GreenhouseHookTests(WebhookTestCase):
CONTENT_TYPE = "application/x-www-form-urlencoded"
def test_message_candidate_hired(self) -> None:
expected_topic_name = "Hire Candidate - 19"
expected_message = """
Hire Candidate Johnny Smith (ID: 19), applying for:
* **Role**: Developer
* **Emails**: personal@example.com (Personal), work@example.com (Work)
* **Attachments**: [Resume](https://prod-heroku.s3.amazonaws.com/...)
""".strip()
self.check_webhook(
"candidate_hired", expected_topic_name, expected_message, content_type=self.CONTENT_TYPE
)
def test_message_candidate_rejected(self) -> None:
expected_topic_name = "Reject Candidate - 265788"
expected_message = """
Reject Candidate Hector Porter (ID: 265788), applying for:
* **Role**: Designer
* **Emails**: hector.porter.265788@example.com (Personal)
* **Attachments**: [Resume](https://prod-heroku.s3.amazonaws.com/...)
""".strip()
self.check_webhook(
"candidate_rejected",
expected_topic_name,
expected_message,
content_type=self.CONTENT_TYPE,
)
def test_message_candidate_stage_change(self) -> None:
expected_topic_name = "Candidate Stage Change - 265772"
expected_message = """
Candidate Stage Change Giuseppe Hurley (ID: 265772), applying for:
* **Role**: Designer
* **Emails**: giuseppe.hurley@example.com (Personal)
* **Attachments**: [Resume](https://prod-heroku.s3.amazonaws.com/...), [Cover_Letter](https://prod-heroku.s3.amazonaws.com/...), [Attachment](https://prod-heroku.s3.amazonaws.com/...)
""".strip()
self.check_webhook(
"candidate_stage_change",
expected_topic_name,
expected_message,
content_type=self.CONTENT_TYPE,
)
def test_message_prospect_created(self) -> None:
expected_topic_name = "New Prospect Application - 968190"
expected_message = """
New Prospect Application Trisha Troy (ID: 968190), applying for:
* **Role**: Designer
* **Emails**: t.troy@example.com (Personal)
* **Attachments**: [Resume](https://prod-heroku.s3.amazonaws.com/...)
""".strip()
self.check_webhook(
"prospect_created",
expected_topic_name,
expected_message,
content_type=self.CONTENT_TYPE,
)
@patch("zerver.webhooks.greenhouse.view.check_send_webhook_message")
def test_ping_message_ignore(self, check_send_webhook_message_mock: MagicMock) -> None:
self.url = self.build_webhook_url()
payload = self.get_body("ping_event")
result = self.client_post(self.url, payload, content_type=self.CONTENT_TYPE)
self.assertFalse(check_send_webhook_message_mock.called)
self.assert_json_success(result)

View File

@@ -0,0 +1,64 @@
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_none_or, check_string, check_url
from zerver.lib.webhooks.common import check_send_webhook_message
from zerver.models import UserProfile
MESSAGE_TEMPLATE = """
{action} {first_name} {last_name} (ID: {candidate_id}), applying for:
* **Role**: {role}
* **Emails**: {emails}
* **Attachments**: {attachments}
""".strip()
def dict_list_to_string(some_list: WildValue) -> str:
response_chunks = []
for item in some_list:
item_type = item.get("type", "").tame(check_string).title()
item_value = item.get("value").tame(check_none_or(check_string))
item_url = item.get("url").tame(check_none_or(check_url))
if item_type and item_value:
response_chunks.append(f"{item_value} ({item_type})")
elif item_type and item_url:
response_chunks.append(f"[{item_type}]({item_url})")
return ", ".join(response_chunks)
@webhook_view("Greenhouse")
@typed_endpoint
def api_greenhouse_webhook(
request: HttpRequest,
user_profile: UserProfile,
*,
payload: JsonBodyPayload[WildValue],
) -> HttpResponse:
action = payload["action"].tame(check_string)
if action == "ping":
return json_success(request)
if action == "update_candidate":
candidate = payload["payload"]["candidate"]
else:
candidate = payload["payload"]["application"]["candidate"]
action = action.replace("_", " ").title()
application = payload["payload"]["application"]
body = MESSAGE_TEMPLATE.format(
action=action,
first_name=candidate["first_name"].tame(check_string),
last_name=candidate["last_name"].tame(check_string),
candidate_id=str(candidate["id"].tame(check_int)),
role=application["jobs"][0]["name"].tame(check_string),
emails=dict_list_to_string(application["candidate"]["email_addresses"]),
attachments=dict_list_to_string(application["candidate"]["attachments"]),
)
topic_name = "{} - {}".format(action, str(candidate["id"].tame(check_int)))
check_send_webhook_message(request, user_profile, topic_name, body)
return json_success(request)