We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Support more sources of Sentry exceptions
We currently support some of the Sentry backends:
https://github.com/zulip/zulip/blob/03a0a7abc65a572655d8c0e235fc8a622ce22932/zerver/webhooks/sentry/view.py#L73-L80

We should add support for more of them, like Vue and Rails.  As part of this, we should probably limit the `version` check:
https://github.com/zulip/zulip/blob/03a0a7abc65a572655d8c0e235fc8a622ce22932/zerver/webhooks/sentry/view.py#L96-L98
...to only Python, since other platforms don't seem to include it.

Sentry exception payloads are not standardized, so doing this will probably require building some toy applications in each of the platforms (the Sentry docs probably provide examples) to capture fixtures of the current payloads.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `zerver/webhooks/sentry/view.py`
Content:
```
1 import logging
2 from datetime import datetime, timezone
3 from typing import Any, Dict, List, Optional, Tuple
4 from urllib.parse import urljoin
5 
6 from django.http import HttpRequest, HttpResponse
7 
8 from zerver.decorator import webhook_view
9 from zerver.lib.exceptions import UnsupportedWebhookEventTypeError
10 from zerver.lib.request import REQ, has_request_variables
11 from zerver.lib.response import json_success
12 from zerver.lib.webhooks.common import check_send_webhook_message
13 from zerver.models import UserProfile
14 
15 DEPRECATED_EXCEPTION_MESSAGE_TEMPLATE = """
16 New [issue]({url}) (level: {level}):
17 
18 ``` quote
19 {message}
20 ```
21 """
22 
23 MESSAGE_EVENT_TEMPLATE = """
24 **New message event:** [{title}]({web_link})
25 ```quote
26 **level:** {level}
27 **timestamp:** {datetime}
28 ```
29 """
30 
31 EXCEPTION_EVENT_TEMPLATE = """
32 **New exception:** [{title}]({web_link})
33 ```quote
34 **level:** {level}
35 **timestamp:** {datetime}
36 **filename:** {filename}
37 ```
38 """
39 
40 EXCEPTION_EVENT_TEMPLATE_WITH_TRACEBACK = (
41     EXCEPTION_EVENT_TEMPLATE
42     + """
43 Traceback:
44 ```{syntax_highlight_as}
45 {pre_context}---> {context_line}{post_context}\
46 ```
47 """
48 )
49 # Because of the \n added at the end of each context element,
50 # this will actually look better in the traceback.
51 
52 ISSUE_CREATED_MESSAGE_TEMPLATE = """
53 **New issue created:** {title}
54 ```quote
55 **level:** {level}
56 **timestamp:** {datetime}
57 **assignee:** {assignee}
58 ```
59 """
60 
61 ISSUE_ASSIGNED_MESSAGE_TEMPLATE = """
62 Issue **{title}** has now been assigned to **{assignee}** by **{actor}**.
63 """
64 
65 ISSUE_RESOLVED_MESSAGE_TEMPLATE = """
66 Issue **{title}** was marked as resolved by **{actor}**.
67 """
68 
69 ISSUE_IGNORED_MESSAGE_TEMPLATE = """
70 Issue **{title}** was ignored by **{actor}**.
71 """
72 
73 # Maps "platform" name provided by Sentry to the Pygments lexer name
74 syntax_highlight_as_map = {
75     "go": "go",
76     "java": "java",
77     "javascript": "javascript",
78     "node": "javascript",
79     "python": "python3",
80 }
81 
82 
83 def convert_lines_to_traceback_string(lines: Optional[List[str]]) -> str:
84     traceback = ""
85     if lines is not None:
86         for line in lines:
87             if line == "":
88                 traceback += "\n"
89             else:
90                 traceback += f"     {line}\n"
91     return traceback
92 
93 
94 def handle_event_payload(event: Dict[str, Any]) -> Tuple[str, str]:
95     """Handle either an exception type event or a message type event payload."""
96     # We shouldn't support the officially deprecated Raven series of SDKs.
97     if int(event["version"]) < 7:
98         raise UnsupportedWebhookEventTypeError("Raven SDK")
99 
100     subject = event["title"]
101     platform_name = event["platform"]
102     syntax_highlight_as = syntax_highlight_as_map.get(platform_name, "")
103     if syntax_highlight_as == "":  # nocoverage
104         logging.info("Unknown Sentry platform: %s", platform_name)
105 
106     context = {
107         "title": subject,
108         "level": event["level"],
109         "web_link": event["web_url"],
110         "datetime": event["datetime"].split(".")[0].replace("T", " "),
111     }
112 
113     if "exception" in event:
114         # The event was triggered by a sentry.capture_exception() call
115         # (in the Python Sentry SDK) or something similar.
116 
117         filename = event["metadata"].get("filename", None)
118 
119         stacktrace = None
120         for value in reversed(event["exception"]["values"]):
121             if "stacktrace" in value:
122                 stacktrace = value["stacktrace"]
123                 break
124 
125         if stacktrace and filename:
126             exception_frame = None
127             for frame in reversed(stacktrace["frames"]):
128                 if frame.get("filename", None) == filename:
129                     exception_frame = frame
130                     break
131 
132             if (
133                 exception_frame
134                 and "context_line" in exception_frame
135                 and exception_frame["context_line"] is not None
136             ):
137                 pre_context = convert_lines_to_traceback_string(
138                     exception_frame.get("pre_context", None)
139                 )
140                 context_line = exception_frame["context_line"] + "\n"
141                 post_context = convert_lines_to_traceback_string(
142                     exception_frame.get("post_context", None)
143                 )
144 
145                 context.update(
146                     syntax_highlight_as=syntax_highlight_as,
147                     filename=filename,
148                     pre_context=pre_context,
149                     context_line=context_line,
150                     post_context=post_context,
151                 )
152 
153                 body = EXCEPTION_EVENT_TEMPLATE_WITH_TRACEBACK.format(**context)
154                 return (subject, body)
155 
156         context.update(filename=filename)  # nocoverage
157         body = EXCEPTION_EVENT_TEMPLATE.format(**context)  # nocoverage
158         return (subject, body)  # nocoverage
159 
160     elif "logentry" in event:
161         # The event was triggered by a sentry.capture_message() call
162         # (in the Python Sentry SDK) or something similar.
163         body = MESSAGE_EVENT_TEMPLATE.format(**context)
164 
165     else:
166         raise UnsupportedWebhookEventTypeError("unknown-event type")
167 
168     return (subject, body)
169 
170 
171 def handle_issue_payload(
172     action: str, issue: Dict[str, Any], actor: Dict[str, Any]
173 ) -> Tuple[str, str]:
174     """Handle either an issue type event."""
175     subject = issue["title"]
176     datetime = issue["lastSeen"].split(".")[0].replace("T", " ")
177 
178     if issue["assignedTo"]:
179         if issue["assignedTo"]["type"] == "team":
180             assignee = "team {}".format(issue["assignedTo"]["name"])
181         else:
182             assignee = issue["assignedTo"]["name"]
183     else:
184         assignee = "No one"
185 
186     if action == "created":
187         context = {
188             "title": subject,
189             "level": issue["level"],
190             "datetime": datetime,
191             "assignee": assignee,
192         }
193         body = ISSUE_CREATED_MESSAGE_TEMPLATE.format(**context)
194 
195     elif action == "resolved":
196         context = {
197             "title": subject,
198             "actor": actor["name"],
199         }
200         body = ISSUE_RESOLVED_MESSAGE_TEMPLATE.format(**context)
201 
202     elif action == "assigned":
203         context = {
204             "title": subject,
205             "assignee": assignee,
206             "actor": actor["name"],
207         }
208         body = ISSUE_ASSIGNED_MESSAGE_TEMPLATE.format(**context)
209 
210     elif action == "ignored":
211         context = {
212             "title": subject,
213             "actor": actor["name"],
214         }
215         body = ISSUE_IGNORED_MESSAGE_TEMPLATE.format(**context)
216 
217     else:
218         raise UnsupportedWebhookEventTypeError("unknown-issue-action type")
219 
220     return (subject, body)
221 
222 
223 def handle_deprecated_payload(payload: Dict[str, Any]) -> Tuple[str, str]:
224     subject = "{}".format(payload.get("project_name"))
225     body = DEPRECATED_EXCEPTION_MESSAGE_TEMPLATE.format(
226         level=payload["level"].upper(),
227         url=payload.get("url"),
228         message=payload.get("message"),
229     )
230     return (subject, body)
231 
232 
233 def transform_webhook_payload(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
234     """Attempt to use webhook payload for the notification.
235 
236     When the integration is configured as a webhook, instead of being added as
237     an internal integration, the payload is slightly different, but has all the
238     required information for sending a notification. We transform this payload to
239     look like the payload from a "properly configured" integration.
240     """
241     event = payload.get("event", {})
242     # deprecated payloads don't have event_id
243     event_id = event.get("event_id")
244     if not event_id:
245         return None
246 
247     event_path = f"events/{event_id}/"
248     event["web_url"] = urljoin(payload["url"], event_path)
249     timestamp = event.get("timestamp", event["received"])
250     event["datetime"] = datetime.fromtimestamp(timestamp, timezone.utc).isoformat(
251         timespec="microseconds"
252     )
253     return payload
254 
255 
256 @webhook_view("Sentry")
257 @has_request_variables
258 def api_sentry_webhook(
259     request: HttpRequest,
260     user_profile: UserProfile,
261     payload: Dict[str, Any] = REQ(argument_type="body"),
262 ) -> HttpResponse:
263     data = payload.get("data", None)
264 
265     if data is None:
266         data = transform_webhook_payload(payload)
267 
268     # We currently support two types of payloads: events and issues.
269     if data:
270         if "event" in data:
271             subject, body = handle_event_payload(data["event"])
272         elif "issue" in data:
273             subject, body = handle_issue_payload(payload["action"], data["issue"], payload["actor"])
274         else:
275             raise UnsupportedWebhookEventTypeError(str(list(data.keys())))
276     else:
277         subject, body = handle_deprecated_payload(payload)
278 
279     check_send_webhook_message(request, user_profile, subject, body)
280     return json_success(request)
281
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/zerver/webhooks/sentry/view.py b/zerver/webhooks/sentry/view.py
--- a/zerver/webhooks/sentry/view.py
+++ b/zerver/webhooks/sentry/view.py
@@ -77,6 +77,7 @@
     "javascript": "javascript",
     "node": "javascript",
     "python": "python3",
+    "ruby": "ruby",
 }
 
 
@@ -93,9 +94,6 @@
 
 def handle_event_payload(event: Dict[str, Any]) -> Tuple[str, str]:
     """Handle either an exception type event or a message type event payload."""
-    # We shouldn't support the officially deprecated Raven series of SDKs.
-    if int(event["version"]) < 7:
-        raise UnsupportedWebhookEventTypeError("Raven SDK")
 
     subject = event["title"]
     platform_name = event["platform"]
@@ -103,6 +101,14 @@
     if syntax_highlight_as == "":  # nocoverage
         logging.info("Unknown Sentry platform: %s", platform_name)
 
+    # We shouldn't support the officially deprecated Raven series of
+    # Python SDKs.
+    if platform_name == "python" and int(event["version"]) < 7:
+        # The sample event is still an old "version" -- accept it even
+        # though we don't accept events from the old Python SDK.
+        tags = event.get("tags", [])
+        if ["sample_event", "yes"] not in tags:
+            raise UnsupportedWebhookEventTypeError("Raven SDK")
     context = {
         "title": subject,
         "level": event["level"],

{"golden_diff": "diff --git a/zerver/webhooks/sentry/view.py b/zerver/webhooks/sentry/view.py\n--- a/zerver/webhooks/sentry/view.py\n+++ b/zerver/webhooks/sentry/view.py\n@@ -77,6 +77,7 @@\n     \"javascript\": \"javascript\",\n     \"node\": \"javascript\",\n     \"python\": \"python3\",\n+    \"ruby\": \"ruby\",\n }\n \n \n@@ -93,9 +94,6 @@\n \n def handle_event_payload(event: Dict[str, Any]) -> Tuple[str, str]:\n     \"\"\"Handle either an exception type event or a message type event payload.\"\"\"\n-    # We shouldn't support the officially deprecated Raven series of SDKs.\n-    if int(event[\"version\"]) < 7:\n-        raise UnsupportedWebhookEventTypeError(\"Raven SDK\")\n \n     subject = event[\"title\"]\n     platform_name = event[\"platform\"]\n@@ -103,6 +101,14 @@\n     if syntax_highlight_as == \"\":  # nocoverage\n         logging.info(\"Unknown Sentry platform: %s\", platform_name)\n \n+    # We shouldn't support the officially deprecated Raven series of\n+    # Python SDKs.\n+    if platform_name == \"python\" and int(event[\"version\"]) < 7:\n+        # The sample event is still an old \"version\" -- accept it even\n+        # though we don't accept events from the old Python SDK.\n+        tags = event.get(\"tags\", [])\n+        if [\"sample_event\", \"yes\"] not in tags:\n+            raise UnsupportedWebhookEventTypeError(\"Raven SDK\")\n     context = {\n         \"title\": subject,\n         \"level\": event[\"level\"],\n", "issue": "Support more sources of Sentry exceptions\nWe currently support some of the Sentry backends:\r\nhttps://github.com/zulip/zulip/blob/03a0a7abc65a572655d8c0e235fc8a622ce22932/zerver/webhooks/sentry/view.py#L73-L80\r\n\r\nWe should add support for more of them, like Vue and Rails.  As part of this, we should probably limit the `version` check:\r\nhttps://github.com/zulip/zulip/blob/03a0a7abc65a572655d8c0e235fc8a622ce22932/zerver/webhooks/sentry/view.py#L96-L98\r\n...to only Python, since other platforms don't seem to include it.\r\n\r\nSentry exception payloads are not standardized, so doing this will probably require building some toy applications in each of the platforms (the Sentry docs probably provide examples) to capture fixtures of the current payloads.\n", "before_files": [{"content": "import logging\nfrom datetime import datetime, timezone\nfrom typing import Any, Dict, List, Optional, Tuple\nfrom urllib.parse import urljoin\n\nfrom django.http import HttpRequest, HttpResponse\n\nfrom zerver.decorator import webhook_view\nfrom zerver.lib.exceptions import UnsupportedWebhookEventTypeError\nfrom zerver.lib.request import REQ, has_request_variables\nfrom zerver.lib.response import json_success\nfrom zerver.lib.webhooks.common import check_send_webhook_message\nfrom zerver.models import UserProfile\n\nDEPRECATED_EXCEPTION_MESSAGE_TEMPLATE = \"\"\"\nNew [issue]({url}) (level: {level}):\n\n``` quote\n{message}\n```\n\"\"\"\n\nMESSAGE_EVENT_TEMPLATE = \"\"\"\n**New message event:** [{title}]({web_link})\n```quote\n**level:** {level}\n**timestamp:** {datetime}\n```\n\"\"\"\n\nEXCEPTION_EVENT_TEMPLATE = \"\"\"\n**New exception:** [{title}]({web_link})\n```quote\n**level:** {level}\n**timestamp:** {datetime}\n**filename:** {filename}\n```\n\"\"\"\n\nEXCEPTION_EVENT_TEMPLATE_WITH_TRACEBACK = (\n    EXCEPTION_EVENT_TEMPLATE\n    + \"\"\"\nTraceback:\n```{syntax_highlight_as}\n{pre_context}---> {context_line}{post_context}\\\n```\n\"\"\"\n)\n# Because of the \\n added at the end of each context element,\n# this will actually look better in the traceback.\n\nISSUE_CREATED_MESSAGE_TEMPLATE = \"\"\"\n**New issue created:** {title}\n```quote\n**level:** {level}\n**timestamp:** {datetime}\n**assignee:** {assignee}\n```\n\"\"\"\n\nISSUE_ASSIGNED_MESSAGE_TEMPLATE = \"\"\"\nIssue **{title}** has now been assigned to **{assignee}** by **{actor}**.\n\"\"\"\n\nISSUE_RESOLVED_MESSAGE_TEMPLATE = \"\"\"\nIssue **{title}** was marked as resolved by **{actor}**.\n\"\"\"\n\nISSUE_IGNORED_MESSAGE_TEMPLATE = \"\"\"\nIssue **{title}** was ignored by **{actor}**.\n\"\"\"\n\n# Maps \"platform\" name provided by Sentry to the Pygments lexer name\nsyntax_highlight_as_map = {\n    \"go\": \"go\",\n    \"java\": \"java\",\n    \"javascript\": \"javascript\",\n    \"node\": \"javascript\",\n    \"python\": \"python3\",\n}\n\n\ndef convert_lines_to_traceback_string(lines: Optional[List[str]]) -> str:\n    traceback = \"\"\n    if lines is not None:\n        for line in lines:\n            if line == \"\":\n                traceback += \"\\n\"\n            else:\n                traceback += f\"     {line}\\n\"\n    return traceback\n\n\ndef handle_event_payload(event: Dict[str, Any]) -> Tuple[str, str]:\n    \"\"\"Handle either an exception type event or a message type event payload.\"\"\"\n    # We shouldn't support the officially deprecated Raven series of SDKs.\n    if int(event[\"version\"]) < 7:\n        raise UnsupportedWebhookEventTypeError(\"Raven SDK\")\n\n    subject = event[\"title\"]\n    platform_name = event[\"platform\"]\n    syntax_highlight_as = syntax_highlight_as_map.get(platform_name, \"\")\n    if syntax_highlight_as == \"\":  # nocoverage\n        logging.info(\"Unknown Sentry platform: %s\", platform_name)\n\n    context = {\n        \"title\": subject,\n        \"level\": event[\"level\"],\n        \"web_link\": event[\"web_url\"],\n        \"datetime\": event[\"datetime\"].split(\".\")[0].replace(\"T\", \" \"),\n    }\n\n    if \"exception\" in event:\n        # The event was triggered by a sentry.capture_exception() call\n        # (in the Python Sentry SDK) or something similar.\n\n        filename = event[\"metadata\"].get(\"filename\", None)\n\n        stacktrace = None\n        for value in reversed(event[\"exception\"][\"values\"]):\n            if \"stacktrace\" in value:\n                stacktrace = value[\"stacktrace\"]\n                break\n\n        if stacktrace and filename:\n            exception_frame = None\n            for frame in reversed(stacktrace[\"frames\"]):\n                if frame.get(\"filename\", None) == filename:\n                    exception_frame = frame\n                    break\n\n            if (\n                exception_frame\n                and \"context_line\" in exception_frame\n                and exception_frame[\"context_line\"] is not None\n            ):\n                pre_context = convert_lines_to_traceback_string(\n                    exception_frame.get(\"pre_context\", None)\n                )\n                context_line = exception_frame[\"context_line\"] + \"\\n\"\n                post_context = convert_lines_to_traceback_string(\n                    exception_frame.get(\"post_context\", None)\n                )\n\n                context.update(\n                    syntax_highlight_as=syntax_highlight_as,\n                    filename=filename,\n                    pre_context=pre_context,\n                    context_line=context_line,\n                    post_context=post_context,\n                )\n\n                body = EXCEPTION_EVENT_TEMPLATE_WITH_TRACEBACK.format(**context)\n                return (subject, body)\n\n        context.update(filename=filename)  # nocoverage\n        body = EXCEPTION_EVENT_TEMPLATE.format(**context)  # nocoverage\n        return (subject, body)  # nocoverage\n\n    elif \"logentry\" in event:\n        # The event was triggered by a sentry.capture_message() call\n        # (in the Python Sentry SDK) or something similar.\n        body = MESSAGE_EVENT_TEMPLATE.format(**context)\n\n    else:\n        raise UnsupportedWebhookEventTypeError(\"unknown-event type\")\n\n    return (subject, body)\n\n\ndef handle_issue_payload(\n    action: str, issue: Dict[str, Any], actor: Dict[str, Any]\n) -> Tuple[str, str]:\n    \"\"\"Handle either an issue type event.\"\"\"\n    subject = issue[\"title\"]\n    datetime = issue[\"lastSeen\"].split(\".\")[0].replace(\"T\", \" \")\n\n    if issue[\"assignedTo\"]:\n        if issue[\"assignedTo\"][\"type\"] == \"team\":\n            assignee = \"team {}\".format(issue[\"assignedTo\"][\"name\"])\n        else:\n            assignee = issue[\"assignedTo\"][\"name\"]\n    else:\n        assignee = \"No one\"\n\n    if action == \"created\":\n        context = {\n            \"title\": subject,\n            \"level\": issue[\"level\"],\n            \"datetime\": datetime,\n            \"assignee\": assignee,\n        }\n        body = ISSUE_CREATED_MESSAGE_TEMPLATE.format(**context)\n\n    elif action == \"resolved\":\n        context = {\n            \"title\": subject,\n            \"actor\": actor[\"name\"],\n        }\n        body = ISSUE_RESOLVED_MESSAGE_TEMPLATE.format(**context)\n\n    elif action == \"assigned\":\n        context = {\n            \"title\": subject,\n            \"assignee\": assignee,\n            \"actor\": actor[\"name\"],\n        }\n        body = ISSUE_ASSIGNED_MESSAGE_TEMPLATE.format(**context)\n\n    elif action == \"ignored\":\n        context = {\n            \"title\": subject,\n            \"actor\": actor[\"name\"],\n        }\n        body = ISSUE_IGNORED_MESSAGE_TEMPLATE.format(**context)\n\n    else:\n        raise UnsupportedWebhookEventTypeError(\"unknown-issue-action type\")\n\n    return (subject, body)\n\n\ndef handle_deprecated_payload(payload: Dict[str, Any]) -> Tuple[str, str]:\n    subject = \"{}\".format(payload.get(\"project_name\"))\n    body = DEPRECATED_EXCEPTION_MESSAGE_TEMPLATE.format(\n        level=payload[\"level\"].upper(),\n        url=payload.get(\"url\"),\n        message=payload.get(\"message\"),\n    )\n    return (subject, body)\n\n\ndef transform_webhook_payload(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:\n    \"\"\"Attempt to use webhook payload for the notification.\n\n    When the integration is configured as a webhook, instead of being added as\n    an internal integration, the payload is slightly different, but has all the\n    required information for sending a notification. We transform this payload to\n    look like the payload from a \"properly configured\" integration.\n    \"\"\"\n    event = payload.get(\"event\", {})\n    # deprecated payloads don't have event_id\n    event_id = event.get(\"event_id\")\n    if not event_id:\n        return None\n\n    event_path = f\"events/{event_id}/\"\n    event[\"web_url\"] = urljoin(payload[\"url\"], event_path)\n    timestamp = event.get(\"timestamp\", event[\"received\"])\n    event[\"datetime\"] = datetime.fromtimestamp(timestamp, timezone.utc).isoformat(\n        timespec=\"microseconds\"\n    )\n    return payload\n\n\n@webhook_view(\"Sentry\")\n@has_request_variables\ndef api_sentry_webhook(\n    request: HttpRequest,\n    user_profile: UserProfile,\n    payload: Dict[str, Any] = REQ(argument_type=\"body\"),\n) -> HttpResponse:\n    data = payload.get(\"data\", None)\n\n    if data is None:\n        data = transform_webhook_payload(payload)\n\n    # We currently support two types of payloads: events and issues.\n    if data:\n        if \"event\" in data:\n            subject, body = handle_event_payload(data[\"event\"])\n        elif \"issue\" in data:\n            subject, body = handle_issue_payload(payload[\"action\"], data[\"issue\"], payload[\"actor\"])\n        else:\n            raise UnsupportedWebhookEventTypeError(str(list(data.keys())))\n    else:\n        subject, body = handle_deprecated_payload(payload)\n\n    check_send_webhook_message(request, user_profile, subject, body)\n    return json_success(request)\n", "path": "zerver/webhooks/sentry/view.py"}], "after_files": [{"content": "import logging\nfrom datetime import datetime, timezone\nfrom typing import Any, Dict, List, Optional, Tuple\nfrom urllib.parse import urljoin\n\nfrom django.http import HttpRequest, HttpResponse\n\nfrom zerver.decorator import webhook_view\nfrom zerver.lib.exceptions import UnsupportedWebhookEventTypeError\nfrom zerver.lib.request import REQ, has_request_variables\nfrom zerver.lib.response import json_success\nfrom zerver.lib.webhooks.common import check_send_webhook_message\nfrom zerver.models import UserProfile\n\nDEPRECATED_EXCEPTION_MESSAGE_TEMPLATE = \"\"\"\nNew [issue]({url}) (level: {level}):\n\n``` quote\n{message}\n```\n\"\"\"\n\nMESSAGE_EVENT_TEMPLATE = \"\"\"\n**New message event:** [{title}]({web_link})\n```quote\n**level:** {level}\n**timestamp:** {datetime}\n```\n\"\"\"\n\nEXCEPTION_EVENT_TEMPLATE = \"\"\"\n**New exception:** [{title}]({web_link})\n```quote\n**level:** {level}\n**timestamp:** {datetime}\n**filename:** {filename}\n```\n\"\"\"\n\nEXCEPTION_EVENT_TEMPLATE_WITH_TRACEBACK = (\n    EXCEPTION_EVENT_TEMPLATE\n    + \"\"\"\nTraceback:\n```{syntax_highlight_as}\n{pre_context}---> {context_line}{post_context}\\\n```\n\"\"\"\n)\n# Because of the \\n added at the end of each context element,\n# this will actually look better in the traceback.\n\nISSUE_CREATED_MESSAGE_TEMPLATE = \"\"\"\n**New issue created:** {title}\n```quote\n**level:** {level}\n**timestamp:** {datetime}\n**assignee:** {assignee}\n```\n\"\"\"\n\nISSUE_ASSIGNED_MESSAGE_TEMPLATE = \"\"\"\nIssue **{title}** has now been assigned to **{assignee}** by **{actor}**.\n\"\"\"\n\nISSUE_RESOLVED_MESSAGE_TEMPLATE = \"\"\"\nIssue **{title}** was marked as resolved by **{actor}**.\n\"\"\"\n\nISSUE_IGNORED_MESSAGE_TEMPLATE = \"\"\"\nIssue **{title}** was ignored by **{actor}**.\n\"\"\"\n\n# Maps \"platform\" name provided by Sentry to the Pygments lexer name\nsyntax_highlight_as_map = {\n    \"go\": \"go\",\n    \"java\": \"java\",\n    \"javascript\": \"javascript\",\n    \"node\": \"javascript\",\n    \"python\": \"python3\",\n    \"ruby\": \"ruby\",\n}\n\n\ndef convert_lines_to_traceback_string(lines: Optional[List[str]]) -> str:\n    traceback = \"\"\n    if lines is not None:\n        for line in lines:\n            if line == \"\":\n                traceback += \"\\n\"\n            else:\n                traceback += f\"     {line}\\n\"\n    return traceback\n\n\ndef handle_event_payload(event: Dict[str, Any]) -> Tuple[str, str]:\n    \"\"\"Handle either an exception type event or a message type event payload.\"\"\"\n\n    subject = event[\"title\"]\n    platform_name = event[\"platform\"]\n    syntax_highlight_as = syntax_highlight_as_map.get(platform_name, \"\")\n    if syntax_highlight_as == \"\":  # nocoverage\n        logging.info(\"Unknown Sentry platform: %s\", platform_name)\n\n    # We shouldn't support the officially deprecated Raven series of\n    # Python SDKs.\n    if platform_name == \"python\" and int(event[\"version\"]) < 7:\n        # The sample event is still an old \"version\" -- accept it even\n        # though we don't accept events from the old Python SDK.\n        tags = event.get(\"tags\", [])\n        if [\"sample_event\", \"yes\"] not in tags:\n            raise UnsupportedWebhookEventTypeError(\"Raven SDK\")\n    context = {\n        \"title\": subject,\n        \"level\": event[\"level\"],\n        \"web_link\": event[\"web_url\"],\n        \"datetime\": event[\"datetime\"].split(\".\")[0].replace(\"T\", \" \"),\n    }\n\n    if \"exception\" in event:\n        # The event was triggered by a sentry.capture_exception() call\n        # (in the Python Sentry SDK) or something similar.\n\n        filename = event[\"metadata\"].get(\"filename\", None)\n\n        stacktrace = None\n        for value in reversed(event[\"exception\"][\"values\"]):\n            if \"stacktrace\" in value:\n                stacktrace = value[\"stacktrace\"]\n                break\n\n        if stacktrace and filename:\n            exception_frame = None\n            for frame in reversed(stacktrace[\"frames\"]):\n                if frame.get(\"filename\", None) == filename:\n                    exception_frame = frame\n                    break\n\n            if (\n                exception_frame\n                and \"context_line\" in exception_frame\n                and exception_frame[\"context_line\"] is not None\n            ):\n                pre_context = convert_lines_to_traceback_string(\n                    exception_frame.get(\"pre_context\", None)\n                )\n                context_line = exception_frame[\"context_line\"] + \"\\n\"\n                post_context = convert_lines_to_traceback_string(\n                    exception_frame.get(\"post_context\", None)\n                )\n\n                context.update(\n                    syntax_highlight_as=syntax_highlight_as,\n                    filename=filename,\n                    pre_context=pre_context,\n                    context_line=context_line,\n                    post_context=post_context,\n                )\n\n                body = EXCEPTION_EVENT_TEMPLATE_WITH_TRACEBACK.format(**context)\n                return (subject, body)\n\n        context.update(filename=filename)  # nocoverage\n        body = EXCEPTION_EVENT_TEMPLATE.format(**context)  # nocoverage\n        return (subject, body)  # nocoverage\n\n    elif \"logentry\" in event:\n        # The event was triggered by a sentry.capture_message() call\n        # (in the Python Sentry SDK) or something similar.\n        body = MESSAGE_EVENT_TEMPLATE.format(**context)\n\n    else:\n        raise UnsupportedWebhookEventTypeError(\"unknown-event type\")\n\n    return (subject, body)\n\n\ndef handle_issue_payload(\n    action: str, issue: Dict[str, Any], actor: Dict[str, Any]\n) -> Tuple[str, str]:\n    \"\"\"Handle either an issue type event.\"\"\"\n    subject = issue[\"title\"]\n    datetime = issue[\"lastSeen\"].split(\".\")[0].replace(\"T\", \" \")\n\n    if issue[\"assignedTo\"]:\n        if issue[\"assignedTo\"][\"type\"] == \"team\":\n            assignee = \"team {}\".format(issue[\"assignedTo\"][\"name\"])\n        else:\n            assignee = issue[\"assignedTo\"][\"name\"]\n    else:\n        assignee = \"No one\"\n\n    if action == \"created\":\n        context = {\n            \"title\": subject,\n            \"level\": issue[\"level\"],\n            \"datetime\": datetime,\n            \"assignee\": assignee,\n        }\n        body = ISSUE_CREATED_MESSAGE_TEMPLATE.format(**context)\n\n    elif action == \"resolved\":\n        context = {\n            \"title\": subject,\n            \"actor\": actor[\"name\"],\n        }\n        body = ISSUE_RESOLVED_MESSAGE_TEMPLATE.format(**context)\n\n    elif action == \"assigned\":\n        context = {\n            \"title\": subject,\n            \"assignee\": assignee,\n            \"actor\": actor[\"name\"],\n        }\n        body = ISSUE_ASSIGNED_MESSAGE_TEMPLATE.format(**context)\n\n    elif action == \"ignored\":\n        context = {\n            \"title\": subject,\n            \"actor\": actor[\"name\"],\n        }\n        body = ISSUE_IGNORED_MESSAGE_TEMPLATE.format(**context)\n\n    else:\n        raise UnsupportedWebhookEventTypeError(\"unknown-issue-action type\")\n\n    return (subject, body)\n\n\ndef handle_deprecated_payload(payload: Dict[str, Any]) -> Tuple[str, str]:\n    subject = \"{}\".format(payload.get(\"project_name\"))\n    body = DEPRECATED_EXCEPTION_MESSAGE_TEMPLATE.format(\n        level=payload[\"level\"].upper(),\n        url=payload.get(\"url\"),\n        message=payload.get(\"message\"),\n    )\n    return (subject, body)\n\n\ndef transform_webhook_payload(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:\n    \"\"\"Attempt to use webhook payload for the notification.\n\n    When the integration is configured as a webhook, instead of being added as\n    an internal integration, the payload is slightly different, but has all the\n    required information for sending a notification. We transform this payload to\n    look like the payload from a \"properly configured\" integration.\n    \"\"\"\n    event = payload.get(\"event\", {})\n    # deprecated payloads don't have event_id\n    event_id = event.get(\"event_id\")\n    if not event_id:\n        return None\n\n    event_path = f\"events/{event_id}/\"\n    event[\"web_url\"] = urljoin(payload[\"url\"], event_path)\n    timestamp = event.get(\"timestamp\", event[\"received\"])\n    event[\"datetime\"] = datetime.fromtimestamp(timestamp, timezone.utc).isoformat(\n        timespec=\"microseconds\"\n    )\n    return payload\n\n\n@webhook_view(\"Sentry\")\n@has_request_variables\ndef api_sentry_webhook(\n    request: HttpRequest,\n    user_profile: UserProfile,\n    payload: Dict[str, Any] = REQ(argument_type=\"body\"),\n) -> HttpResponse:\n    data = payload.get(\"data\", None)\n\n    if data is None:\n        data = transform_webhook_payload(payload)\n\n    # We currently support two types of payloads: events and issues.\n    if data:\n        if \"event\" in data:\n            subject, body = handle_event_payload(data[\"event\"])\n        elif \"issue\" in data:\n            subject, body = handle_issue_payload(payload[\"action\"], data[\"issue\"], payload[\"actor\"])\n        else:\n            raise UnsupportedWebhookEventTypeError(str(list(data.keys())))\n    else:\n        subject, body = handle_deprecated_payload(payload)\n\n    check_send_webhook_message(request, user_profile, subject, body)\n    return json_success(request)\n", "path": "zerver/webhooks/sentry/view.py"}]}