2020-08-11 03:45:55 +02:00
|
|
|
import logging
|
2020-06-11 00:54:34 +02:00
|
|
|
from typing import Any, Dict, List, Optional, Tuple
|
2017-11-16 00:43:10 +01:00
|
|
|
|
2016-09-06 21:17:18 +02:00
|
|
|
from django.http import HttpRequest, HttpResponse
|
2017-10-31 04:25:48 +01:00
|
|
|
|
|
|
|
from zerver.decorator import api_key_only_webhook_view
|
|
|
|
from zerver.lib.request import REQ, has_request_variables
|
2017-11-16 00:43:10 +01:00
|
|
|
from zerver.lib.response import json_success
|
2020-04-29 04:03:36 +02:00
|
|
|
from zerver.lib.webhooks.common import UnexpectedWebhookEventType, check_send_webhook_message
|
2017-10-31 04:25:48 +01:00
|
|
|
from zerver.models import UserProfile
|
2016-09-06 21:17:18 +02:00
|
|
|
|
2020-05-07 19:52:30 +02:00
|
|
|
DEPRECATED_EXCEPTION_MESSAGE_TEMPLATE = """
|
|
|
|
New [issue]({url}) (level: {level}):
|
|
|
|
|
|
|
|
``` quote
|
|
|
|
{message}
|
|
|
|
```
|
|
|
|
"""
|
|
|
|
|
2020-04-29 04:03:36 +02:00
|
|
|
MESSAGE_EVENT_TEMPLATE = """
|
|
|
|
**New message event:** [{title}]({web_link})
|
|
|
|
```quote
|
|
|
|
**level:** {level}
|
|
|
|
**timestamp:** {datetime}
|
2019-04-17 23:09:05 +02:00
|
|
|
```
|
2020-04-29 04:03:36 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
EXCEPTION_EVENT_TEMPLATE = """
|
|
|
|
**New exception:** [{title}]({web_link})
|
|
|
|
```quote
|
|
|
|
**level:** {level}
|
|
|
|
**timestamp:** {datetime}
|
|
|
|
**filename:** {filename}
|
|
|
|
```
|
|
|
|
"""
|
|
|
|
|
|
|
|
EXCEPTION_EVENT_TEMPLATE_WITH_TRACEBACK = EXCEPTION_EVENT_TEMPLATE + """
|
|
|
|
Traceback:
|
2020-08-11 03:44:48 +02:00
|
|
|
```{syntax_highlight_as}
|
2020-04-29 04:03:36 +02:00
|
|
|
{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}**.
|
|
|
|
"""
|
|
|
|
|
2020-08-11 03:44:48 +02:00
|
|
|
# Maps "platform" name provided by Sentry to the Pygments lexer name
|
|
|
|
syntax_highlight_as_map = {
|
2020-04-29 04:03:36 +02:00
|
|
|
"go": "go",
|
2020-08-11 03:48:43 +02:00
|
|
|
"java": "java",
|
|
|
|
"javascript": "javascript",
|
2020-04-29 04:03:36 +02:00
|
|
|
"node": "javascript",
|
|
|
|
"python": "python3",
|
2020-08-11 03:44:48 +02:00
|
|
|
}
|
2020-04-29 04:03:36 +02:00
|
|
|
|
|
|
|
|
|
|
|
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:
|
2020-06-09 00:25:09 +02:00
|
|
|
traceback += f" {line}\n"
|
2020-04-29 04:03:36 +02:00
|
|
|
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")
|
|
|
|
|
2020-06-17 01:48:38 +02:00
|
|
|
platform_name = event["platform"]
|
2020-08-11 03:45:55 +02:00
|
|
|
syntax_highlight_as = syntax_highlight_as_map.get(platform_name, "")
|
|
|
|
if syntax_highlight_as == "": # nocoverage
|
|
|
|
logging.info(f"Unknown Sentry platform: {platform_name}")
|
2020-06-17 01:48:38 +02:00
|
|
|
|
2020-04-29 04:03:36 +02:00
|
|
|
context = {
|
|
|
|
"title": subject,
|
|
|
|
"level": event["level"],
|
|
|
|
"web_link": event["web_url"],
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
"datetime": event["datetime"].split(".")[0].replace("T", " "),
|
2020-04-29 04:03:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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"]
|
|
|
|
|
|
|
|
stacktrace = None
|
|
|
|
for value in event["exception"]["values"]:
|
|
|
|
if "stacktrace" in value:
|
|
|
|
stacktrace = value["stacktrace"]
|
|
|
|
break
|
|
|
|
|
|
|
|
if stacktrace:
|
|
|
|
exception_frame = None
|
2020-08-17 22:40:14 +02:00
|
|
|
for frame in reversed(stacktrace["frames"]):
|
2020-04-29 04:03:36 +02:00
|
|
|
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({
|
2020-08-11 03:44:48 +02:00
|
|
|
"syntax_highlight_as": syntax_highlight_as,
|
2020-04-29 04:03:36 +02:00
|
|
|
"filename": filename,
|
|
|
|
"pre_context": pre_context,
|
|
|
|
"context_line": context_line,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
"post_context": post_context,
|
2020-04-29 04:03:36 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
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,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
"assignee": assignee,
|
2020-04-29 04:03:36 +02:00
|
|
|
}
|
|
|
|
body = ISSUE_CREATED_MESSAGE_TEMPLATE.format(**context)
|
|
|
|
|
|
|
|
elif action == "resolved":
|
|
|
|
context = {
|
|
|
|
"title": subject,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
"actor": actor["name"],
|
2020-04-29 04:03:36 +02:00
|
|
|
}
|
|
|
|
body = ISSUE_RESOLVED_MESSAGE_TEMPLATE.format(**context)
|
|
|
|
|
|
|
|
elif action == "assigned":
|
|
|
|
context = {
|
|
|
|
"title": subject,
|
|
|
|
"assignee": assignee,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
"actor": actor["name"],
|
2020-04-29 04:03:36 +02:00
|
|
|
}
|
|
|
|
body = ISSUE_ASSIGNED_MESSAGE_TEMPLATE.format(**context)
|
|
|
|
|
|
|
|
elif action == "ignored":
|
|
|
|
context = {
|
|
|
|
"title": subject,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
"actor": actor["name"],
|
2020-04-29 04:03:36 +02:00
|
|
|
}
|
|
|
|
body = ISSUE_IGNORED_MESSAGE_TEMPLATE.format(**context)
|
|
|
|
|
|
|
|
else:
|
|
|
|
raise UnexpectedWebhookEventType("Sentry", "unknown-issue-action type")
|
|
|
|
|
|
|
|
return (subject, body)
|
|
|
|
|
2019-04-17 23:09:05 +02:00
|
|
|
|
2020-05-07 19:52:30 +02:00
|
|
|
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'),
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
message=payload.get('message'),
|
2020-05-07 19:52:30 +02:00
|
|
|
)
|
|
|
|
return (subject, body)
|
|
|
|
|
|
|
|
|
2016-09-06 21:17:18 +02:00
|
|
|
@api_key_only_webhook_view('Sentry')
|
|
|
|
@has_request_variables
|
2017-12-19 18:53:52 +01:00
|
|
|
def api_sentry_webhook(request: HttpRequest, user_profile: UserProfile,
|
2020-04-29 04:03:36 +02:00
|
|
|
payload: Dict[str, Any] = REQ(argument_type="body")) -> HttpResponse:
|
2020-05-07 19:52:30 +02:00
|
|
|
data = payload.get("data", None)
|
2020-04-29 04:03:36 +02:00
|
|
|
|
|
|
|
# We currently support two types of payloads: events and issues.
|
2020-05-07 19:52:30 +02:00
|
|
|
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:
|
2020-06-09 00:23:10 +02:00
|
|
|
raise UnexpectedWebhookEventType("Sentry", str(list(data.keys())))
|
2020-04-29 04:03:36 +02:00
|
|
|
else:
|
2020-05-07 19:52:30 +02:00
|
|
|
subject, body = handle_deprecated_payload(payload)
|
2019-04-17 23:09:05 +02:00
|
|
|
|
2018-03-16 22:53:50 +01:00
|
|
|
check_send_webhook_message(request, user_profile, subject, body)
|
2016-09-06 21:17:18 +02:00
|
|
|
return json_success()
|