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/raygun/__init__.py
Normal file
0
zerver/webhooks/raygun/__init__.py
Normal file
27
zerver/webhooks/raygun/doc.md
Normal file
27
zerver/webhooks/raygun/doc.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Zulip Raygun integration
|
||||
|
||||
Get Zulip notifications for your Raygun events!
|
||||
|
||||
{start_tabs}
|
||||
|
||||
1. {!create-an-incoming-webhook.md!}
|
||||
|
||||
1. {!generate-webhook-url-basic.md!}
|
||||
|
||||
1. Go to your Raygun dashboard, and click on **Integrations**.
|
||||
Click on **Webhook**, and click on the **Setup** tab.
|
||||
|
||||
1. Set **Url** to the URL generated above. Check the **Enabled** checkbox,
|
||||
and click **Save Changes**.
|
||||
|
||||
{end_tabs}
|
||||
|
||||
{!congrats.md!}
|
||||
|
||||

|
||||
|
||||
{!event-filtering-additional-feature.md!}
|
||||
|
||||
### Related documentation
|
||||
|
||||
{!webhooks-url-specification.md!}
|
||||
16
zerver/webhooks/raygun/fixtures/comment_added_to_error.json
Normal file
16
zerver/webhooks/raygun/fixtures/comment_added_to_error.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"event":"error_activity",
|
||||
"eventType":"CommentAdded",
|
||||
"error":{
|
||||
"url":"https://app.raygun.com/error-url",
|
||||
"description":"Anita left a comment on error Script Error, 'Ignoring these errors'",
|
||||
"activityDate":"1970-01-28T01:49:36Z",
|
||||
"message":"Script Error",
|
||||
"comment":"Ignoring these errors",
|
||||
"user":"Anita Peacock"
|
||||
},
|
||||
"application":{
|
||||
"name":"application name",
|
||||
"url":"http://app.raygun.io/application-url"
|
||||
}
|
||||
}
|
||||
16
zerver/webhooks/raygun/fixtures/error_assigned_to_user.json
Normal file
16
zerver/webhooks/raygun/fixtures/error_assigned_to_user.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"event":"error_activity",
|
||||
"eventType":"AssignedToUser",
|
||||
"error":{
|
||||
"url":"https://app.raygun.com/error-url",
|
||||
"description":"Error Script Error was assigned to Kyle by Amy",
|
||||
"activityDate":"1970-01-28T01:49:36Z",
|
||||
"message":"Script Error",
|
||||
"assignedTo":"Kyle Kenny",
|
||||
"user":"Amy Loondon"
|
||||
},
|
||||
"application":{
|
||||
"name":"application name",
|
||||
"url":"http://app.raygun.io/application-url"
|
||||
}
|
||||
}
|
||||
16
zerver/webhooks/raygun/fixtures/error_status_changed.json
Normal file
16
zerver/webhooks/raygun/fixtures/error_status_changed.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"event":"error_activity",
|
||||
"eventType":"StatusChanged",
|
||||
"error":{
|
||||
"url":"https://app.raygun.com/error-url",
|
||||
"description":"Emma marked error Script Error as Ignored",
|
||||
"activityDate":"1970-01-28T01:49:36Z",
|
||||
"message":"Script Error",
|
||||
"status":"Ignored",
|
||||
"user":"Emma Cat"
|
||||
},
|
||||
"application":{
|
||||
"name":"Best App",
|
||||
"url":"http://app.raygun.io/application-url"
|
||||
}
|
||||
}
|
||||
16
zerver/webhooks/raygun/fixtures/hourly_followup_error.json
Normal file
16
zerver/webhooks/raygun/fixtures/hourly_followup_error.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"event":"error_notification",
|
||||
"eventType":"HourlyFollowUp",
|
||||
"error":{
|
||||
"url":"http://app.raygun.io/error-url",
|
||||
"message":"",
|
||||
"firstOccurredOn":"1970-01-28T01:49:36Z",
|
||||
"lastOccurredOn":"1970-01-28T01:49:36Z",
|
||||
"usersAffected":1,
|
||||
"totalOccurrences":1
|
||||
},
|
||||
"application":{
|
||||
"name":"application name",
|
||||
"url":"http://app.raygun.io/application-url"
|
||||
}
|
||||
}
|
||||
33
zerver/webhooks/raygun/fixtures/new_error.json
Normal file
33
zerver/webhooks/raygun/fixtures/new_error.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"event":"error_notification",
|
||||
"eventType":"NewErrorOccurred",
|
||||
"error":{
|
||||
"url":"http://app.raygun.io/error-url",
|
||||
"message":"",
|
||||
"firstOccurredOn":"1970-01-28T01:49:36Z",
|
||||
"lastOccurredOn":"1970-01-28T01:49:36Z",
|
||||
"usersAffected":1,
|
||||
"totalOccurrences":1,
|
||||
"instance":{
|
||||
"tags":[
|
||||
"test",
|
||||
"error-page",
|
||||
"v1.0.1",
|
||||
"env:staging"
|
||||
],
|
||||
"affectedUser":{
|
||||
"Identifier":"a9b7d84a-c0d3-4e7e-9ded-1ec13d033846",
|
||||
"IsAnonymous":true,
|
||||
"UUID":"a9b7d84a-c0d3-4e7e-9ded-1ec13d033846"
|
||||
},
|
||||
"customData":{
|
||||
"pageName":"Error Page",
|
||||
"userLoggedIn":true
|
||||
}
|
||||
}
|
||||
},
|
||||
"application":{
|
||||
"name":"application name",
|
||||
"url":"http://app.raygun.io/application-url"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"event":"error_notification",
|
||||
"eventType":"OneMinuteFollowUp",
|
||||
"error":{
|
||||
"url":"http://app.raygun.io/error-url",
|
||||
"message":"",
|
||||
"firstOccurredOn":"1970-01-28T01:49:36Z",
|
||||
"lastOccurredOn":"1970-01-28T01:49:36Z",
|
||||
"usersAffected":1,
|
||||
"totalOccurrences":1
|
||||
},
|
||||
"application":{
|
||||
"name":"application name",
|
||||
"url":"http://app.raygun.io/application-url"
|
||||
}
|
||||
}
|
||||
33
zerver/webhooks/raygun/fixtures/reoccurred_error.json
Normal file
33
zerver/webhooks/raygun/fixtures/reoccurred_error.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"event":"error_notification",
|
||||
"eventType":"ErrorReoccurred",
|
||||
"error":{
|
||||
"url":"http://app.raygun.io/error-url",
|
||||
"message":"",
|
||||
"firstOccurredOn":"1970-01-28T01:49:36Z",
|
||||
"lastOccurredOn":"1970-01-28T01:49:36Z",
|
||||
"usersAffected":1,
|
||||
"totalOccurrences":1,
|
||||
"instance":{
|
||||
"tags":[
|
||||
"test",
|
||||
"error-page",
|
||||
"v1.0.1",
|
||||
"env:staging"
|
||||
],
|
||||
"affectedUser":{
|
||||
"Identifier":"a9b7d84a-c0d3-4e7e-9ded-1ec13d033846",
|
||||
"IsAnonymous":true,
|
||||
"UUID":"a9b7d84a-c0d3-4e7e-9ded-1ec13d033846"
|
||||
},
|
||||
"customData":{
|
||||
"pageName":"Error Page",
|
||||
"userLoggedIn":true
|
||||
}
|
||||
}
|
||||
},
|
||||
"application":{
|
||||
"name":"application name",
|
||||
"url":"http://app.raygun.io/application-url"
|
||||
}
|
||||
}
|
||||
128
zerver/webhooks/raygun/tests.py
Normal file
128
zerver/webhooks/raygun/tests.py
Normal file
@@ -0,0 +1,128 @@
|
||||
from zerver.lib.test_classes import WebhookTestCase
|
||||
|
||||
|
||||
class RaygunHookTests(WebhookTestCase):
|
||||
def test_status_changed_message(self) -> None:
|
||||
expected_topic_name = "test"
|
||||
expected_message = """
|
||||
[Error](https://app.raygun.com/error-url) status changed to **Ignored** by Emma Cat:
|
||||
* **Timestamp**: <time:1970-01-28T01:49:36+00:00>
|
||||
* **Application details**: [Best App](http://app.raygun.io/application-url)
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"error_status_changed",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_comment_added_to_error_message(self) -> None:
|
||||
expected_topic_name = "test"
|
||||
expected_message = """
|
||||
Anita Peacock commented on [Error](https://app.raygun.com/error-url):
|
||||
|
||||
``` quote
|
||||
Ignoring these errors
|
||||
```
|
||||
* **Timestamp**: <time:1970-01-28T01:49:36+00:00>
|
||||
* **Application details**: [application name](http://app.raygun.io/application-url)
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"comment_added_to_error",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_error_assigned_to_user_message(self) -> None:
|
||||
expected_topic_name = "test"
|
||||
expected_message = """
|
||||
Amy Loondon assigned [Error](https://app.raygun.com/error-url) to Kyle Kenny:
|
||||
* **Timestamp**: <time:1970-01-28T01:49:36+00:00>
|
||||
* **Application details**: [application name](http://app.raygun.io/application-url)
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"error_assigned_to_user",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_one_minute_followup_error_message(self) -> None:
|
||||
expected_topic_name = "test"
|
||||
expected_message = """
|
||||
One minute [follow-up error](http://app.raygun.io/error-url):
|
||||
* **First occurred**: <time:1970-01-28T01:49:36+00:00>
|
||||
* **Last occurred**: <time:1970-01-28T01:49:36+00:00>
|
||||
* 1 users affected with 1 total occurrences
|
||||
* **Application details**: [application name](http://app.raygun.io/application-url)
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"one_minute_followup_error",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_hourly_followup_error_message(self) -> None:
|
||||
expected_topic_name = "test"
|
||||
expected_message = """
|
||||
Hourly [follow-up error](http://app.raygun.io/error-url):
|
||||
* **First occurred**: <time:1970-01-28T01:49:36+00:00>
|
||||
* **Last occurred**: <time:1970-01-28T01:49:36+00:00>
|
||||
* 1 users affected with 1 total occurrences
|
||||
* **Application details**: [application name](http://app.raygun.io/application-url)
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"hourly_followup_error",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_new_error_message(self) -> None:
|
||||
expected_topic_name = "test"
|
||||
expected_message = """
|
||||
New [Error](http://app.raygun.io/error-url) occurred:
|
||||
* **First occurred**: <time:1970-01-28T01:49:36+00:00>
|
||||
* **Last occurred**: <time:1970-01-28T01:49:36+00:00>
|
||||
* 1 users affected with 1 total occurrences
|
||||
* **Tags**: test, error-page, v1.0.1, env:staging
|
||||
* **Affected user**: a9b7d8...33846
|
||||
* **pageName**: Error Page
|
||||
* **userLoggedIn**: True
|
||||
* **Application details**: [application name](http://app.raygun.io/application-url)
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"new_error",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
|
||||
def test_reoccurred_error_message(self) -> None:
|
||||
expected_topic_name = "test"
|
||||
expected_message = """
|
||||
[Error](http://app.raygun.io/error-url) reoccurred:
|
||||
* **First occurred**: <time:1970-01-28T01:49:36+00:00>
|
||||
* **Last occurred**: <time:1970-01-28T01:49:36+00:00>
|
||||
* 1 users affected with 1 total occurrences
|
||||
* **Tags**: test, error-page, v1.0.1, env:staging
|
||||
* **Affected user**: a9b7d8...33846
|
||||
* **pageName**: Error Page
|
||||
* **userLoggedIn**: True
|
||||
* **Application details**: [application name](http://app.raygun.io/application-url)
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"reoccurred_error",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
)
|
||||
297
zerver/webhooks/raygun/view.py
Normal file
297
zerver/webhooks/raygun/view.py
Normal file
@@ -0,0 +1,297 @@
|
||||
from datetime import datetime
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
|
||||
from zerver.decorator import webhook_view
|
||||
from zerver.lib.exceptions import UnsupportedWebhookEventTypeError
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.timestamp import datetime_to_global_time
|
||||
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
|
||||
from zerver.lib.validator import WildValue, check_anything, check_int, check_list, check_string
|
||||
from zerver.lib.webhooks.common import check_send_webhook_message
|
||||
from zerver.models import UserProfile
|
||||
|
||||
ALL_EVENT_TYPES = ["error_notification", "error_activity"]
|
||||
|
||||
|
||||
@webhook_view("Raygun", all_event_types=ALL_EVENT_TYPES)
|
||||
@typed_endpoint
|
||||
def api_raygun_webhook(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
*,
|
||||
payload: JsonBodyPayload[WildValue],
|
||||
) -> HttpResponse:
|
||||
# The payload contains 'event' key. This 'event' key has a value of either
|
||||
# 'error_notification' or 'error_activity'. 'error_notification' happens
|
||||
# when an error is caught in an application, where as 'error_activity'
|
||||
# happens when an action is being taken for the error itself
|
||||
# (ignored/resolved/assigned/etc.).
|
||||
event = payload["event"].tame(check_string)
|
||||
|
||||
# Because we wanted to create a message for all of the payloads, it is best
|
||||
# to handle them separately. This is because some payload keys don't exist
|
||||
# in the other event.
|
||||
|
||||
if event == "error_notification":
|
||||
message = compose_notification_message(payload)
|
||||
elif event == "error_activity":
|
||||
message = compose_activity_message(payload)
|
||||
else:
|
||||
raise UnsupportedWebhookEventTypeError(event)
|
||||
|
||||
topic_name = "test"
|
||||
|
||||
check_send_webhook_message(request, user_profile, topic_name, message, event)
|
||||
|
||||
return json_success(request)
|
||||
|
||||
|
||||
def make_user_stats_chunk(error_dict: WildValue) -> str:
|
||||
"""Creates a stat chunk about total occurrences and users affected for the
|
||||
error.
|
||||
|
||||
Example: usersAffected: 2, totalOccurrences: 10
|
||||
Output: 2 users affected with 10 total occurrences
|
||||
|
||||
:param error_dict: The error dictionary containing the error keys and
|
||||
values
|
||||
:returns: A message chunk that will be added to the main message
|
||||
"""
|
||||
users_affected = error_dict["usersAffected"].tame(check_int)
|
||||
total_occurrences = error_dict["totalOccurrences"].tame(check_int)
|
||||
|
||||
# One line is subjectively better than two lines for this.
|
||||
return f"* {users_affected} users affected with {total_occurrences} total occurrences\n"
|
||||
|
||||
|
||||
def make_time_chunk(error_dict: WildValue) -> str:
|
||||
"""Creates a time message chunk.
|
||||
|
||||
Example: firstOccurredOn: "X", lastOccurredOn: "Y"
|
||||
Output:
|
||||
First occurred: X
|
||||
Last occurred: Y
|
||||
|
||||
:param error_dict: The error dictionary containing the error keys and
|
||||
values
|
||||
:returns: A message chunk that will be added to the main message
|
||||
"""
|
||||
# Make the timestamp more readable to a human.
|
||||
time_first = parse_time(error_dict["firstOccurredOn"].tame(check_string))
|
||||
time_last = parse_time(error_dict["lastOccurredOn"].tame(check_string))
|
||||
|
||||
# Provide time information about this error,
|
||||
return f"* **First occurred**: {time_first}\n* **Last occurred**: {time_last}\n"
|
||||
|
||||
|
||||
def make_message_chunk(message: str) -> str:
|
||||
"""Creates a message chunk if exists.
|
||||
|
||||
Example: message: "This is an example message" returns "Message: This is an
|
||||
example message". Whereas message: "" returns "".
|
||||
|
||||
:param message: The value of message inside of the error dictionary
|
||||
:returns: A message chunk if there exists an additional message, otherwise
|
||||
returns an empty string.
|
||||
"""
|
||||
# "Message" shouldn't be included if there is none supplied.
|
||||
return f"* **Message**: {message}\n" if message != "" else ""
|
||||
|
||||
|
||||
def make_app_info_chunk(app_dict: WildValue) -> str:
|
||||
"""Creates a message chunk that contains the application info and the link
|
||||
to the Raygun dashboard about the application.
|
||||
|
||||
:param app_dict: The application dictionary obtained from the payload
|
||||
:returns: A message chunk that will be added to the main message
|
||||
"""
|
||||
app_name = app_dict["name"].tame(check_string)
|
||||
app_url = app_dict["url"].tame(check_string)
|
||||
return f"* **Application details**: [{app_name}]({app_url})\n"
|
||||
|
||||
|
||||
def notification_message_follow_up(payload: WildValue) -> str:
|
||||
"""Creates a message for a repeating error follow up
|
||||
|
||||
:param payload: Raygun payload
|
||||
:return: Returns the message, somewhat beautifully formatted
|
||||
"""
|
||||
message = ""
|
||||
|
||||
# Link to Raygun about the follow up
|
||||
followup_link_md = "[follow-up error]({})".format(payload["error"]["url"].tame(check_string))
|
||||
|
||||
followup_type = payload["eventType"].tame(check_string)
|
||||
|
||||
if followup_type == "HourlyFollowUp":
|
||||
prefix = "Hourly"
|
||||
else:
|
||||
# Cut the "MinuteFollowUp" from the possible event types, then add "
|
||||
# minute" after that. So prefix for "OneMinuteFollowUp" is "One
|
||||
# minute", where "FiveMinuteFollowUp" is "Five minute".
|
||||
prefix = followup_type[: len(followup_type) - 14] + " minute"
|
||||
|
||||
message += f"{prefix} {followup_link_md}:\n"
|
||||
|
||||
# Get the message of the error.
|
||||
payload_msg = payload["error"]["message"].tame(check_string)
|
||||
|
||||
message += make_message_chunk(payload_msg)
|
||||
message += make_time_chunk(payload["error"])
|
||||
message += make_user_stats_chunk(payload["error"])
|
||||
message += make_app_info_chunk(payload["application"])
|
||||
|
||||
return message
|
||||
|
||||
|
||||
def notification_message_error_occurred(payload: WildValue) -> str:
|
||||
"""Creates a message for a new error or reoccurred error
|
||||
|
||||
:param payload: Raygun payload
|
||||
:return: Returns the message, somewhat beautifully formatted
|
||||
"""
|
||||
message = ""
|
||||
|
||||
# Provide a clickable link that goes to Raygun about this error.
|
||||
error_link_md = "[Error]({})".format(payload["error"]["url"].tame(check_string))
|
||||
|
||||
# Stylize the message based on the event type of the error.
|
||||
if payload["eventType"].tame(check_string) == "NewErrorOccurred":
|
||||
message += "{}:\n".format(f"New {error_link_md} occurred")
|
||||
elif payload["eventType"].tame(check_string) == "ErrorReoccurred":
|
||||
message += "{}:\n".format(f"{error_link_md} reoccurred")
|
||||
|
||||
# Get the message of the error. This value can be empty (as in "").
|
||||
payload_msg = payload["error"]["message"].tame(check_string)
|
||||
|
||||
message += make_message_chunk(payload_msg)
|
||||
message += make_time_chunk(payload["error"])
|
||||
message += make_user_stats_chunk(payload["error"])
|
||||
|
||||
# Only NewErrorOccurred and ErrorReoccurred contain an error instance.
|
||||
error_instance = payload["error"]["instance"]
|
||||
|
||||
# Extract each of the keys and values in error_instance for easier handle
|
||||
|
||||
# Contains list of tags for the error. Can be empty (null)
|
||||
tags = error_instance["tags"]
|
||||
|
||||
# Contains the identity of affected user at the moment this error
|
||||
# happened. This surprisingly can be null. Somehow.
|
||||
affected_user = error_instance["affectedUser"]
|
||||
|
||||
# Contains custom data for this particular error (if supplied). Can be
|
||||
# null.
|
||||
custom_data = error_instance["customData"]
|
||||
|
||||
if tags is not None:
|
||||
message += "* **Tags**: {}\n".format(", ".join(tags.tame(check_list(check_string))))
|
||||
|
||||
if affected_user is not None:
|
||||
user_uuid = affected_user["UUID"].tame(check_string)
|
||||
message += f"* **Affected user**: {user_uuid[:6]}...{user_uuid[-5:]}\n"
|
||||
|
||||
if custom_data is not None:
|
||||
# We don't know what the keys and values beforehand, so we are forced
|
||||
# to iterate.
|
||||
for key in sorted(custom_data.keys()):
|
||||
message += f"* **{key}**: {custom_data[key].tame(check_anything)}\n"
|
||||
|
||||
message += make_app_info_chunk(payload["application"])
|
||||
|
||||
return message
|
||||
|
||||
|
||||
def compose_notification_message(payload: WildValue) -> str:
|
||||
"""Composes a message that contains information on the error
|
||||
|
||||
:param payload: Raygun payload
|
||||
:return: Returns a response message
|
||||
"""
|
||||
|
||||
# Get the event type of the error. This can be "NewErrorOccurred",
|
||||
# "ErrorReoccurred", "OneMinuteFollowUp", "FiveMinuteFollowUp", ...,
|
||||
# "HourlyFollowUp" for notification error.
|
||||
event_type = payload["eventType"].tame(check_string)
|
||||
|
||||
# "NewErrorOccurred" and "ErrorReoccurred" contain error instance
|
||||
# information, meaning that it has payload['error']['instance']. The other
|
||||
# event type (the follow ups) doesn't have this instance.
|
||||
|
||||
# We now split this main function again into two functions. One is for
|
||||
# "NewErrorOccurred" and "ErrorReoccurred", and one is for the rest. Both
|
||||
# functions will return a text message that is formatted for the chat.
|
||||
if event_type in ("NewErrorOccurred", "ErrorReoccurred"):
|
||||
return notification_message_error_occurred(payload)
|
||||
elif "FollowUp" in event_type:
|
||||
return notification_message_follow_up(payload)
|
||||
else:
|
||||
raise UnsupportedWebhookEventTypeError(event_type)
|
||||
|
||||
|
||||
def activity_message(payload: WildValue) -> str:
|
||||
"""Creates a message from an activity that is being taken for an error
|
||||
|
||||
:param payload: Raygun payload
|
||||
:return: Returns the message, somewhat beautifully formatted
|
||||
"""
|
||||
message = ""
|
||||
|
||||
error_link_md = "[Error]({})".format(payload["error"]["url"].tame(check_string))
|
||||
|
||||
event_type = payload["eventType"].tame(check_string)
|
||||
|
||||
user = payload["error"]["user"].tame(check_string)
|
||||
if event_type == "StatusChanged":
|
||||
error_status = payload["error"]["status"].tame(check_string)
|
||||
message += f"{error_link_md} status changed to **{error_status}** by {user}:\n"
|
||||
elif event_type == "CommentAdded":
|
||||
comment = payload["error"]["comment"].tame(check_string)
|
||||
message += f"{user} commented on {error_link_md}:\n\n``` quote\n{comment}\n```\n"
|
||||
elif event_type == "AssignedToUser":
|
||||
assigned_to = payload["error"]["assignedTo"].tame(check_string)
|
||||
message += f"{user} assigned {error_link_md} to {assigned_to}:\n"
|
||||
|
||||
message += "* **Timestamp**: {}\n".format(
|
||||
parse_time(payload["error"]["activityDate"].tame(check_string))
|
||||
)
|
||||
|
||||
message += make_app_info_chunk(payload["application"])
|
||||
|
||||
return message
|
||||
|
||||
|
||||
def compose_activity_message(payload: WildValue) -> str:
|
||||
"""Composes a message that contains an activity that is being taken to
|
||||
an error, such as commenting, assigning an error to a user, ignoring the
|
||||
error, etc.
|
||||
|
||||
:param payload: Raygun payload
|
||||
:return: Returns a response message
|
||||
"""
|
||||
|
||||
event_type = payload["eventType"].tame(check_string)
|
||||
|
||||
# Activity is separated into three main categories: status changes (
|
||||
# ignores, resolved), error is assigned to user, and comment added to
|
||||
# an error,
|
||||
|
||||
# But, they all are almost identical and the only differences between them
|
||||
# are the keys at line 9 (check fixtures). So there's no need to split
|
||||
# the function like the notification one.
|
||||
if event_type in ("StatusChanged", "AssignedToUser", "CommentAdded"):
|
||||
return activity_message(payload)
|
||||
else:
|
||||
raise UnsupportedWebhookEventTypeError(event_type)
|
||||
|
||||
|
||||
def parse_time(dt_str: str) -> str:
|
||||
"""Parses and returns the timestamp provided
|
||||
|
||||
:param timestamp: The timestamp provided by the payload
|
||||
:returns: A string containing the time
|
||||
"""
|
||||
|
||||
dt = datetime.fromisoformat(dt_str)
|
||||
return datetime_to_global_time(dt)
|
||||
Reference in New Issue
Block a user