mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 22:13:26 +00:00
When the integration was originally rewritten, support for the deprecated webhook payloads was removed. We later noticed that some people using Zulip were still using versions of Sentry that required the older integration code. Thus this commit adds back the older integration code and whenever the Sentry webhook payload does not have a "data" field (which must be present in all modern payloads as per the documentation at https://docs.sentry.io/workflow/integrations/integration-platform/webhooks) we will use the older Sentry integration code. Signed-off-by: Hemanth V. Alluri <hdrive1999@gmail.com>
234 lines
7.0 KiB
Python
234 lines
7.0 KiB
Python
from typing import Any, Dict, List, Tuple, Optional
|
|
|
|
from django.http import HttpRequest, HttpResponse
|
|
|
|
from zerver.decorator import api_key_only_webhook_view
|
|
from zerver.lib.request import REQ, has_request_variables
|
|
from zerver.lib.response import json_success
|
|
from zerver.lib.webhooks.common import UnexpectedWebhookEventType, check_send_webhook_message
|
|
from zerver.models import UserProfile
|
|
|
|
|
|
DEPRECATED_EXCEPTION_MESSAGE_TEMPLATE = """
|
|
New [issue]({url}) (level: {level}):
|
|
|
|
``` quote
|
|
{message}
|
|
```
|
|
"""
|
|
|
|
MESSAGE_EVENT_TEMPLATE = """
|
|
**New message event:** [{title}]({web_link})
|
|
```quote
|
|
**level:** {level}
|
|
**timestamp:** {datetime}
|
|
```
|
|
"""
|
|
|
|
EXCEPTION_EVENT_TEMPLATE = """
|
|
**New exception:** [{title}]({web_link})
|
|
```quote
|
|
**level:** {level}
|
|
**timestamp:** {datetime}
|
|
**filename:** {filename}
|
|
```
|
|
"""
|
|
|
|
EXCEPTION_EVENT_TEMPLATE_WITH_TRACEBACK = EXCEPTION_EVENT_TEMPLATE + """
|
|
Traceback:
|
|
```{platform}
|
|
{pre_context}---> {context_line}{post_context}\
|
|
```
|
|
"""
|
|
# Because of the \n added at the end of each context element,
|
|
# this will actually look better in the traceback.
|
|
|
|
ISSUE_CREATED_MESSAGE_TEMPLATE = """
|
|
**New issue created:** {title}
|
|
```quote
|
|
**level:** {level}
|
|
**timestamp:** {datetime}
|
|
**assignee:** {assignee}
|
|
```
|
|
"""
|
|
|
|
ISSUE_ASSIGNED_MESSAGE_TEMPLATE = """
|
|
Issue **{title}** has now been assigned to **{assignee}** by **{actor}**.
|
|
"""
|
|
|
|
ISSUE_RESOLVED_MESSAGE_TEMPLATE = """
|
|
Issue **{title}** was marked as resolved by **{actor}**.
|
|
"""
|
|
|
|
ISSUE_IGNORED_MESSAGE_TEMPLATE = """
|
|
Issue **{title}** was ignored by **{actor}**.
|
|
"""
|
|
|
|
platforms_map = {
|
|
"go": "go",
|
|
"node": "javascript",
|
|
"python": "python3",
|
|
} # We can expand this as and when users use this integration with different platforms.
|
|
|
|
|
|
def convert_lines_to_traceback_string(lines: Optional[List[str]]) -> str:
|
|
traceback = ""
|
|
if lines is not None:
|
|
for line in lines:
|
|
if (line == ""):
|
|
traceback += "\n"
|
|
else:
|
|
traceback += " {}\n".format(line)
|
|
return traceback
|
|
|
|
|
|
def handle_event_payload(event: Dict[str, Any]) -> Tuple[str, str]:
|
|
""" Handle either an exception type event or a message type event payload."""
|
|
subject = event["title"]
|
|
|
|
# We shouldn't support the officially deprecated Raven series of SDKs.
|
|
if int(event["version"]) < 7:
|
|
raise UnexpectedWebhookEventType("Sentry", "Raven SDK")
|
|
|
|
context = {
|
|
"title": subject,
|
|
"level": event["level"],
|
|
"web_link": event["web_url"],
|
|
"datetime": event["datetime"].split(".")[0].replace("T", " ")
|
|
}
|
|
|
|
if "exception" in event:
|
|
# The event was triggered by a sentry.capture_exception() call
|
|
# (in the Python Sentry SDK) or something similar.
|
|
|
|
filename = event["metadata"]["filename"]
|
|
platform = platforms_map[event["platform"]]
|
|
|
|
stacktrace = None
|
|
for value in event["exception"]["values"]:
|
|
if "stacktrace" in value:
|
|
stacktrace = value["stacktrace"]
|
|
break
|
|
|
|
if stacktrace:
|
|
exception_frame = None
|
|
for frame in stacktrace["frames"]:
|
|
if frame["filename"] == filename:
|
|
exception_frame = frame
|
|
break
|
|
|
|
if exception_frame:
|
|
pre_context = convert_lines_to_traceback_string(exception_frame["pre_context"])
|
|
|
|
context_line = exception_frame["context_line"] + "\n"
|
|
if not context_line:
|
|
context_line = "\n" # nocoverage
|
|
|
|
post_context = convert_lines_to_traceback_string(exception_frame["post_context"])
|
|
|
|
context.update({
|
|
"platform": platform,
|
|
"filename": filename,
|
|
"pre_context": pre_context,
|
|
"context_line": context_line,
|
|
"post_context": post_context
|
|
})
|
|
|
|
body = EXCEPTION_EVENT_TEMPLATE_WITH_TRACEBACK.format(**context)
|
|
return (subject, body)
|
|
|
|
context.update({"filename": filename}) # nocoverage
|
|
body = EXCEPTION_EVENT_TEMPLATE.format(**context) # nocoverage
|
|
return (subject, body) # nocoverage
|
|
|
|
elif "logentry" in event:
|
|
# The event was triggered by a sentry.capture_message() call
|
|
# (in the Python Sentry SDK) or something similar.
|
|
body = MESSAGE_EVENT_TEMPLATE.format(**context)
|
|
|
|
else:
|
|
raise UnexpectedWebhookEventType("Sentry", "unknown-event type")
|
|
|
|
return (subject, body)
|
|
|
|
|
|
def handle_issue_payload(action: str, issue: Dict[str, Any], actor: Dict[str, Any]) -> Tuple[str, str]:
|
|
""" Handle either an issue type event. """
|
|
subject = issue["title"]
|
|
datetime = issue["lastSeen"].split(".")[0].replace("T", " ")
|
|
|
|
if issue["assignedTo"]:
|
|
if issue["assignedTo"]["type"] == "team":
|
|
assignee = "team {}".format(issue["assignedTo"]["name"])
|
|
else:
|
|
assignee = issue["assignedTo"]["name"]
|
|
else:
|
|
assignee = "No one"
|
|
|
|
if action == "created":
|
|
context = {
|
|
"title": subject,
|
|
"level": issue["level"],
|
|
"datetime": datetime,
|
|
"assignee": assignee
|
|
}
|
|
body = ISSUE_CREATED_MESSAGE_TEMPLATE.format(**context)
|
|
|
|
elif action == "resolved":
|
|
context = {
|
|
"title": subject,
|
|
"actor": actor["name"]
|
|
}
|
|
body = ISSUE_RESOLVED_MESSAGE_TEMPLATE.format(**context)
|
|
|
|
elif action == "assigned":
|
|
context = {
|
|
"title": subject,
|
|
"assignee": assignee,
|
|
"actor": actor["name"]
|
|
}
|
|
body = ISSUE_ASSIGNED_MESSAGE_TEMPLATE.format(**context)
|
|
|
|
elif action == "ignored":
|
|
context = {
|
|
"title": subject,
|
|
"actor": actor["name"]
|
|
}
|
|
body = ISSUE_IGNORED_MESSAGE_TEMPLATE.format(**context)
|
|
|
|
else:
|
|
raise UnexpectedWebhookEventType("Sentry", "unknown-issue-action type")
|
|
|
|
return (subject, body)
|
|
|
|
|
|
def handle_deprecated_payload(payload: Dict[str, Any]) -> Tuple[str, str]:
|
|
subject = "{}".format(payload.get('project_name'))
|
|
body = DEPRECATED_EXCEPTION_MESSAGE_TEMPLATE.format(
|
|
level=payload['level'].upper(),
|
|
url=payload.get('url'),
|
|
message=payload.get('message')
|
|
)
|
|
return (subject, body)
|
|
|
|
|
|
@api_key_only_webhook_view('Sentry')
|
|
@has_request_variables
|
|
def api_sentry_webhook(request: HttpRequest, user_profile: UserProfile,
|
|
payload: Dict[str, Any] = REQ(argument_type="body")) -> HttpResponse:
|
|
data = payload.get("data", None)
|
|
|
|
# We currently support two types of payloads: events and issues.
|
|
if data:
|
|
if "event" in data:
|
|
subject, body = handle_event_payload(data["event"])
|
|
elif "issue" in data:
|
|
subject, body = handle_issue_payload(payload["action"], data["issue"], payload["actor"])
|
|
else:
|
|
raise UnexpectedWebhookEventType("Sentry", str((list(data.keys()))))
|
|
else:
|
|
subject, body = handle_deprecated_payload(payload)
|
|
|
|
check_send_webhook_message(request, user_profile, subject, body)
|
|
return json_success()
|