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/freshdesk/__init__.py
Normal file
0
zerver/webhooks/freshdesk/__init__.py
Normal file
140
zerver/webhooks/freshdesk/doc.md
Normal file
140
zerver/webhooks/freshdesk/doc.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Zulip Freshdesk integration
|
||||
|
||||
See customer support interactions in Zulip with our Freshdesk
|
||||
integration!
|
||||
|
||||
### Create Zulip bot for Freshdesk notifications
|
||||
|
||||
{start_tabs}
|
||||
|
||||
1. {!create-an-incoming-webhook.md!}
|
||||
|
||||
1. {!generate-webhook-url-basic.md!}
|
||||
|
||||
{end_tabs}
|
||||
|
||||
|
||||
### Add notifications for new Freshdesk tickets
|
||||
|
||||
{start_tabs}
|
||||
|
||||
1. Go to your Freshdesk **Admin** page.
|
||||
|
||||
1. Under **Helpdesk Productivity**, select **Dispatch'r**, and then
|
||||
select **New rule**.
|
||||
|
||||
1. Set **Rule Name** to a name of your choice, such as `Zulip`.
|
||||
|
||||
1. There isn't a shortcut to "always generate a notification on ticket
|
||||
creation", so we'll have to fake it by picking two complementary
|
||||
conditions: when the source **is email**, and when the source **is
|
||||
not email**. Set up the **Conditions** for the new rule, like so:
|
||||
|
||||

|
||||
|
||||
1. Under **Actions**, set the **Select Action** dropdown to **Trigger
|
||||
Webhook**.
|
||||
|
||||
1. Set **Request Type** to **POST**, and set **Callback URL** to the URL
|
||||
[generated above][create-bot].
|
||||
|
||||
1. Toggle the **Requires Authentication** checkbox.
|
||||
|
||||
1. Set **Username** to the email of the bot [created above][create-bot],
|
||||
and set **Password** to the bot's API key.
|
||||
|
||||
1. Set **Encoding** to **JSON**, and select the **Advanced** option.
|
||||
Copy and paste the following JSON into the **Content** box:
|
||||
|
||||
```
|
||||
{% raw %}
|
||||
{"freshdesk_webhook":
|
||||
{
|
||||
"triggered_event":"{{triggered_event}}",
|
||||
"ticket_id":"{{ticket.id}}",
|
||||
"ticket_url":"{{ticket.url}}",
|
||||
"ticket_type":"{{ticket.ticket_type}}",
|
||||
"ticket_subject":"{{ticket.subject}}",
|
||||
"ticket_description":"{{ticket.description}}",
|
||||
"ticket_status":"{{ticket.status}}",
|
||||
"ticket_priority":"{{ticket.priority}}",
|
||||
"requester_name":"{{ticket.requester.name}}",
|
||||
"requester_email":"{{ticket.requester.email}}"
|
||||
}
|
||||
}
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
1. Click **Save**.
|
||||
|
||||
{end_tabs}
|
||||
|
||||
### Get notifications for changes to existing Freshdesk tickets
|
||||
|
||||
{start_tabs}
|
||||
|
||||
1. Go to your Freshdesk **Admin** page.
|
||||
|
||||
1. Under **Helpdesk Productivity**, select **Observer**, and then select
|
||||
**New rule**.
|
||||
|
||||
1. Set **Rule Name** to a name of your choice, such as `Zulip`.
|
||||
|
||||
1. Under **involves any of these events**, create new events as shown below:
|
||||
|
||||

|
||||
|
||||
1. Unfortunately, there isn't a shortcut for specifying "all tickets",
|
||||
so we'll have to fake it by picking two complementary conditions:
|
||||
when the source **is email**, and when the source **is not email**.
|
||||
Under **on tickets with these properties**, create new conditions,
|
||||
like so:
|
||||
|
||||

|
||||
|
||||
1. Under **perform these actions**, set the **Select Action** dropdown
|
||||
to **Trigger Webhook**.
|
||||
|
||||
1. Set **Request Type** to **POST**, and set **Callback URL** to the URL
|
||||
[generated above][create-bot].
|
||||
|
||||
1. Toggle the **Requires Authentication** checkbox.
|
||||
|
||||
1. Set **Username** to the email of the bot [created above][create-bot],
|
||||
and set **Password** to the bot's API key.
|
||||
|
||||
1. Set **Encoding** to **JSON** and select the **Advanced** option.
|
||||
Copy and paste the following JSON into the **Content** box:
|
||||
|
||||
```
|
||||
{% raw %}
|
||||
{"freshdesk_webhook":
|
||||
{
|
||||
"triggered_event":"{{triggered_event}}",
|
||||
"ticket_id":"{{ticket.id}}",
|
||||
"ticket_url":"{{ticket.url}}",
|
||||
"ticket_type":"{{ticket.ticket_type}}",
|
||||
"ticket_subject":"{{ticket.subject}}",
|
||||
"ticket_description":"{{ticket.description}}",
|
||||
"ticket_status":"{{ticket.status}}",
|
||||
"ticket_priority":"{{ticket.priority}}",
|
||||
"requester_name":"{{ticket.requester.name}}",
|
||||
"requester_email":"{{ticket.requester.email}}"
|
||||
}
|
||||
}
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
1. Select **Save**.
|
||||
|
||||
{end_tabs}
|
||||
|
||||
{!congrats.md!}
|
||||
|
||||

|
||||
|
||||
### Related documentation
|
||||
|
||||
{!webhooks-url-specification.md!}
|
||||
|
||||
[create-bot]: #create-zulip-bot-for-freshdesk-notifications
|
||||
14
zerver/webhooks/freshdesk/fixtures/inline_images.json
Normal file
14
zerver/webhooks/freshdesk/fixtures/inline_images.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{"freshdesk_webhook":
|
||||
{
|
||||
"triggered_event":"{ticket_action:created}",
|
||||
"ticket_id":"12",
|
||||
"ticket_url":"http://test1234zzz.freshdesk.com/helpdesk/tickets/12",
|
||||
"ticket_type":"Problem",
|
||||
"ticket_subject":"Not enough ☃ guinea pigs",
|
||||
"ticket_description":"<div>There are too many cat pictures on the internet ☃. We need more guinea pigs. Exhibit 1:</div>\n<div><br></div>\n<div></div>\n<div><img src=\"http://cdn.freshdesk.com/data/helpdesk/attachments/production/12744808/original/guinea_pig.png?1383958880\" class=\"inline-image\" data-id=\"12744808\" style=\"\"></div>\n<br>",
|
||||
"ticket_status":"Open",
|
||||
"ticket_priority":"Urgent",
|
||||
"requester_name":"Requester ☃ Bob",
|
||||
"requester_email":"requester-bob@example.com"
|
||||
}
|
||||
}
|
||||
14
zerver/webhooks/freshdesk/fixtures/priority_changed.json
Normal file
14
zerver/webhooks/freshdesk/fixtures/priority_changed.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{"freshdesk_webhook":
|
||||
{
|
||||
"triggered_event":"{priority:{from:3,to:1}}",
|
||||
"ticket_id":"11",
|
||||
"ticket_url":"http://test1234zzz.freshdesk.com/helpdesk/tickets/11",
|
||||
"ticket_type":"Incident",
|
||||
"ticket_subject":"Test ticket subject",
|
||||
"ticket_description":"<div>Test ticket description.</div>",
|
||||
"ticket_status":"Resolved",
|
||||
"ticket_priority":"Low",
|
||||
"requester_name":"Requester Bob",
|
||||
"requester_email":"requester-bob@example.com"
|
||||
}
|
||||
}
|
||||
14
zerver/webhooks/freshdesk/fixtures/private_note.json
Normal file
14
zerver/webhooks/freshdesk/fixtures/private_note.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{"freshdesk_webhook":
|
||||
{
|
||||
"triggered_event":"{note_type:private}",
|
||||
"ticket_id":"11",
|
||||
"ticket_url":"http://test1234zzz.freshdesk.com/helpdesk/tickets/11",
|
||||
"ticket_type":"Incident",
|
||||
"ticket_subject":"Test ticket subject",
|
||||
"ticket_description":"<div>Test ticket description.</div>",
|
||||
"ticket_status":"Open",
|
||||
"ticket_priority":"Medium",
|
||||
"requester_name":"Requester Bob",
|
||||
"requester_email":"requester-bob@example.com"
|
||||
}
|
||||
}
|
||||
14
zerver/webhooks/freshdesk/fixtures/public_note.json
Normal file
14
zerver/webhooks/freshdesk/fixtures/public_note.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{"freshdesk_webhook":
|
||||
{
|
||||
"triggered_event":"{note_type:public}",
|
||||
"ticket_id":"11",
|
||||
"ticket_url":"http://test1234zzz.freshdesk.com/helpdesk/tickets/11",
|
||||
"ticket_type":"Incident",
|
||||
"ticket_subject":"Test ticket subject",
|
||||
"ticket_description":"<div>Test ticket description.</div>",
|
||||
"ticket_status":"Open",
|
||||
"ticket_priority":"Medium",
|
||||
"requester_name":"Requester Bob",
|
||||
"requester_email":"requester-bob@example.com"
|
||||
}
|
||||
}
|
||||
14
zerver/webhooks/freshdesk/fixtures/status_changed.json
Normal file
14
zerver/webhooks/freshdesk/fixtures/status_changed.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{"freshdesk_webhook":
|
||||
{
|
||||
"triggered_event":"{status:{from:4,to:6}}",
|
||||
"ticket_id":"11",
|
||||
"ticket_url":"http://test1234zzz.freshdesk.com/helpdesk/tickets/11",
|
||||
"ticket_type":"Incident",
|
||||
"ticket_subject":"Test ticket subject ☃",
|
||||
"ticket_description":"<div>Test ticket description ☃.</div>",
|
||||
"ticket_status":"Waiting on Customer",
|
||||
"ticket_priority":"Low",
|
||||
"requester_name":"Requester Bob",
|
||||
"requester_email":"requester-bob@example.com"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{"freshdesk_webhook":
|
||||
{
|
||||
"ticket_id":"11",
|
||||
"ticket_url":"http://test1234zzz.freshdesk.com/helpdesk/tickets/11",
|
||||
"ticket_type":"Incident",
|
||||
"ticket_subject":"Test ticket subject ☃",
|
||||
"ticket_description":"<div>Test ticket description ☃.</div>",
|
||||
"ticket_status":"Waiting on Customer",
|
||||
"ticket_priority":"Low",
|
||||
"requester_name":"Requester Bob",
|
||||
"requester_email":"requester-bob@example.com"
|
||||
}
|
||||
}
|
||||
14
zerver/webhooks/freshdesk/fixtures/ticket_created.json
Normal file
14
zerver/webhooks/freshdesk/fixtures/ticket_created.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{"freshdesk_webhook":
|
||||
{
|
||||
"triggered_event":"{ticket_action:created}",
|
||||
"ticket_id":"11",
|
||||
"ticket_url":"http://test1234zzz.freshdesk.com/helpdesk/tickets/11",
|
||||
"ticket_type":"Incident",
|
||||
"ticket_subject":"Test ticket subject ☃",
|
||||
"ticket_description":"<div>Test ticket description ☃.</div>",
|
||||
"ticket_status":"Pending",
|
||||
"ticket_priority":"High",
|
||||
"requester_name":"Requester ☃ Bob",
|
||||
"requester_email":"requester-bob@example.com"
|
||||
}
|
||||
}
|
||||
14
zerver/webhooks/freshdesk/fixtures/unknown_payload.json
Normal file
14
zerver/webhooks/freshdesk/fixtures/unknown_payload.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{"freshdesk_webhook":
|
||||
{
|
||||
"triggered_event":"{unknown_event:{from:3,to:1}}",
|
||||
"ticket_id":"11",
|
||||
"ticket_url":"http://test1234zzz.freshdesk.com/helpdesk/tickets/11",
|
||||
"ticket_type":"Incident",
|
||||
"ticket_subject":"Test ticket subject",
|
||||
"ticket_description":"<div>Test ticket description.</div>",
|
||||
"ticket_status":"Resolved",
|
||||
"ticket_priority":"Low",
|
||||
"requester_name":"Requester Bob",
|
||||
"requester_email":"requester-bob@example.com"
|
||||
}
|
||||
}
|
||||
128
zerver/webhooks/freshdesk/tests.py
Normal file
128
zerver/webhooks/freshdesk/tests.py
Normal file
@@ -0,0 +1,128 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from zerver.lib.test_classes import WebhookTestCase
|
||||
|
||||
|
||||
class FreshdeskHookTests(WebhookTestCase):
|
||||
def test_ticket_creation(self) -> None:
|
||||
"""
|
||||
Messages are generated on ticket creation through Freshdesk's
|
||||
"Dispatch'r" service.
|
||||
"""
|
||||
expected_topic_name = "#11: Test ticket subject ☃"
|
||||
expected_message = """
|
||||
Requester ☃ Bob <requester-bob@example.com> created [ticket #11](http://test1234zzz.freshdesk.com/helpdesk/tickets/11):
|
||||
|
||||
``` quote
|
||||
Test ticket description ☃.
|
||||
```
|
||||
|
||||
* **Type**: Incident
|
||||
* **Priority**: High
|
||||
* **Status**: Pending
|
||||
""".strip()
|
||||
|
||||
self.api_channel_message(
|
||||
self.test_user,
|
||||
"ticket_created",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_status_change(self) -> None:
|
||||
"""
|
||||
Messages are generated when a ticket's status changes through
|
||||
Freshdesk's "Observer" service.
|
||||
"""
|
||||
expected_topic_name = "#11: Test ticket subject ☃"
|
||||
expected_message = """
|
||||
Requester Bob <requester-bob@example.com> updated [ticket #11](http://test1234zzz.freshdesk.com/helpdesk/tickets/11):
|
||||
|
||||
* **Status**: Resolved -> Waiting on Customer
|
||||
""".strip()
|
||||
|
||||
self.api_channel_message(
|
||||
self.test_user,
|
||||
"status_changed",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_priority_change(self) -> None:
|
||||
"""
|
||||
Messages are generated when a ticket's priority changes through
|
||||
Freshdesk's "Observer" service.
|
||||
"""
|
||||
expected_topic_name = "#11: Test ticket subject"
|
||||
expected_message = """
|
||||
Requester Bob <requester-bob@example.com> updated [ticket #11](http://test1234zzz.freshdesk.com/helpdesk/tickets/11):
|
||||
|
||||
* **Priority**: High -> Low
|
||||
""".strip()
|
||||
self.api_channel_message(
|
||||
self.test_user,
|
||||
"priority_changed",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
@patch("zerver.lib.webhooks.common.check_send_webhook_message")
|
||||
def test_unknown_event_payload_ignore(self, check_send_webhook_message_mock: MagicMock) -> None:
|
||||
"""
|
||||
Ignore unknown event payloads.
|
||||
"""
|
||||
self.url = self.build_webhook_url()
|
||||
payload = self.get_body("unknown_payload")
|
||||
result = self.client_post(
|
||||
self.url,
|
||||
payload,
|
||||
HTTP_AUTHORIZATION=self.encode_email(self.test_user.email),
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
self.assertFalse(check_send_webhook_message_mock.called)
|
||||
self.assert_json_success(result)
|
||||
|
||||
def note_change(self, fixture: str, note_type: str) -> None:
|
||||
"""
|
||||
Messages are generated when a note gets added to a ticket through
|
||||
Freshdesk's "Observer" service.
|
||||
"""
|
||||
expected_topic_name = "#11: Test ticket subject"
|
||||
expected_message = """
|
||||
Requester Bob <requester-bob@example.com> added a {} note to \
|
||||
[ticket #11](http://test1234zzz.freshdesk.com/helpdesk/tickets/11).
|
||||
""".strip().format(note_type)
|
||||
self.api_channel_message(
|
||||
self.test_user,
|
||||
fixture,
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_private_note_change(self) -> None:
|
||||
self.note_change("private_note", "private")
|
||||
|
||||
def test_public_note_change(self) -> None:
|
||||
self.note_change("public_note", "public")
|
||||
|
||||
def test_inline_image(self) -> None:
|
||||
"""
|
||||
Freshdesk sends us descriptions as HTML, so we have to make the
|
||||
descriptions Zulip Markdown-friendly while still doing our best to
|
||||
preserve links and images.
|
||||
"""
|
||||
expected_topic_name = "#12: Not enough ☃ guinea pigs"
|
||||
expected_message = """
|
||||
Requester \u2603 Bob <requester-bob@example.com> created [ticket #12](http://test1234zzz.freshdesk.com/helpdesk/tickets/12):\n\n``` quote\nThere are too many cat pictures on the internet \u2603. We need more guinea pigs. Exhibit 1:\n\n[guinea_pig.png](http://cdn.freshdesk.com/data/helpdesk/attachments/production/12744808/original/guinea_pig.png?1383958880)\n```\n\n* **Type**: Problem\n* **Priority**: Urgent\n* **Status**: Open
|
||||
""".strip()
|
||||
self.api_channel_message(
|
||||
self.test_user,
|
||||
"inline_images",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
158
zerver/webhooks/freshdesk/view.py
Normal file
158
zerver/webhooks/freshdesk/view.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""Webhooks for external integrations."""
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
|
||||
from zerver.decorator import authenticated_rest_api_view
|
||||
from zerver.lib.markdown.from_html import convert_html_to_markdown
|
||||
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
|
||||
|
||||
NOTE_TEMPLATE = "{name} <{email}> added a {note_type} note to [ticket #{ticket_id}]({ticket_url})."
|
||||
PROPERTY_CHANGE_TEMPLATE = """
|
||||
{name} <{email}> updated [ticket #{ticket_id}]({ticket_url}):
|
||||
|
||||
* **{property_name}**: {old} -> {new}
|
||||
""".strip()
|
||||
TICKET_CREATION_TEMPLATE = """
|
||||
{name} <{email}> created [ticket #{ticket_id}]({ticket_url}):
|
||||
|
||||
``` quote
|
||||
{description}
|
||||
```
|
||||
|
||||
* **Type**: {type}
|
||||
* **Priority**: {priority}
|
||||
* **Status**: {status}
|
||||
""".strip()
|
||||
|
||||
|
||||
def property_name(property: str, index: int) -> str:
|
||||
"""The Freshdesk API is currently pretty broken: statuses are customizable
|
||||
but the API will only tell you the number associated with the status, not
|
||||
the name. While we engage the Freshdesk developers about exposing this
|
||||
information through the API, since only FlightCar uses this integration,
|
||||
hardcode their statuses.
|
||||
"""
|
||||
statuses = [
|
||||
"",
|
||||
"",
|
||||
"Open",
|
||||
"Pending",
|
||||
"Resolved",
|
||||
"Closed",
|
||||
"Waiting on Customer",
|
||||
"Job Application",
|
||||
"Monthly",
|
||||
]
|
||||
priorities = ["", "Low", "Medium", "High", "Urgent"]
|
||||
|
||||
name = ""
|
||||
if property == "status":
|
||||
name = statuses[index] if index < len(statuses) else str(index)
|
||||
elif property == "priority":
|
||||
name = priorities[index] if index < len(priorities) else str(index)
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def parse_freshdesk_event(event_string: str) -> list[str]:
|
||||
"""These are always of the form "{ticket_action:created}" or
|
||||
"{status:{from:4,to:6}}". Note the lack of string quoting: this isn't
|
||||
valid JSON so we have to parse it ourselves.
|
||||
"""
|
||||
data = event_string.replace("{", "").replace("}", "").replace(",", ":").split(":")
|
||||
|
||||
if len(data) == 2:
|
||||
# This is a simple ticket action event, like
|
||||
# {ticket_action:created}.
|
||||
return data
|
||||
else:
|
||||
# This is a property change event, like {status:{from:4,to:6}}. Pull out
|
||||
# the property, from, and to states.
|
||||
property, _, from_state, _, to_state = data
|
||||
return [
|
||||
property,
|
||||
property_name(property, int(from_state)),
|
||||
property_name(property, int(to_state)),
|
||||
]
|
||||
|
||||
|
||||
def format_freshdesk_note_message(ticket: WildValue, event_info: list[str]) -> str:
|
||||
"""There are public (visible to customers) and private note types."""
|
||||
note_type = event_info[1]
|
||||
content = NOTE_TEMPLATE.format(
|
||||
name=ticket["requester_name"].tame(check_string),
|
||||
email=ticket["requester_email"].tame(check_string),
|
||||
note_type=note_type,
|
||||
ticket_id=ticket["ticket_id"].tame(check_string),
|
||||
ticket_url=ticket["ticket_url"].tame(check_string),
|
||||
)
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def format_freshdesk_property_change_message(ticket: WildValue, event_info: list[str]) -> str:
|
||||
"""Freshdesk will only tell us the first event to match our webhook
|
||||
configuration, so if we change multiple properties, we only get the before
|
||||
and after data for the first one.
|
||||
"""
|
||||
content = PROPERTY_CHANGE_TEMPLATE.format(
|
||||
name=ticket["requester_name"].tame(check_string),
|
||||
email=ticket["requester_email"].tame(check_string),
|
||||
ticket_id=ticket["ticket_id"].tame(check_string),
|
||||
ticket_url=ticket["ticket_url"].tame(check_string),
|
||||
property_name=event_info[0].capitalize(),
|
||||
old=event_info[1],
|
||||
new=event_info[2],
|
||||
)
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def format_freshdesk_ticket_creation_message(ticket: WildValue) -> str:
|
||||
"""They send us the description as HTML."""
|
||||
cleaned_description = convert_html_to_markdown(ticket["ticket_description"].tame(check_string))
|
||||
content = TICKET_CREATION_TEMPLATE.format(
|
||||
name=ticket["requester_name"].tame(check_string),
|
||||
email=ticket["requester_email"].tame(check_string),
|
||||
ticket_id=ticket["ticket_id"].tame(check_string),
|
||||
ticket_url=ticket["ticket_url"].tame(check_string),
|
||||
description=cleaned_description,
|
||||
type=ticket["ticket_type"].tame(check_string),
|
||||
priority=ticket["ticket_priority"].tame(check_string),
|
||||
status=ticket["ticket_status"].tame(check_string),
|
||||
)
|
||||
|
||||
return content
|
||||
|
||||
|
||||
@authenticated_rest_api_view(webhook_client_name="Freshdesk")
|
||||
@typed_endpoint
|
||||
def api_freshdesk_webhook(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
*,
|
||||
payload: JsonBodyPayload[WildValue],
|
||||
) -> HttpResponse:
|
||||
ticket = payload["freshdesk_webhook"]
|
||||
|
||||
topic_name = (
|
||||
f"#{ticket['ticket_id'].tame(check_string)}: {ticket['ticket_subject'].tame(check_string)}"
|
||||
)
|
||||
event_info = parse_freshdesk_event(ticket["triggered_event"].tame(check_string))
|
||||
|
||||
if event_info[1] == "created":
|
||||
content = format_freshdesk_ticket_creation_message(ticket)
|
||||
elif event_info[0] == "note_type":
|
||||
content = format_freshdesk_note_message(ticket, event_info)
|
||||
elif event_info[0] in ("status", "priority"):
|
||||
content = format_freshdesk_property_change_message(ticket, event_info)
|
||||
else:
|
||||
# Not an event we know handle; do nothing.
|
||||
return json_success(request)
|
||||
|
||||
check_send_webhook_message(request, user_profile, topic_name, content)
|
||||
return json_success(request)
|
||||
Reference in New Issue
Block a user