From 09a716a8d7a838233c4a484a504bbd3ed762095e Mon Sep 17 00:00:00 2001 From: Klaas van Schelven Date: Thu, 4 Apr 2024 10:40:32 +0200 Subject: [PATCH] Stacktrace ordering: stack-of-plates as an option implemented in-python; doing this using flex quickly became a mess --- issues/templates/issues/issue_stacktrace.html | 40 +++++++++++++------ issues/tests.py | 3 ++ issues/views.py | 23 ++++++++++- theme/static/css/dist/styles.css | 8 ++++ 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/issues/templates/issues/issue_stacktrace.html b/issues/templates/issues/issue_stacktrace.html index e224b7c..098fe84 100644 --- a/issues/templates/issues/issue_stacktrace.html +++ b/issues/templates/issues/issue_stacktrace.html @@ -42,31 +42,41 @@
{# indicator for frame's position in stacktrace #} - {% if forloop.last %} - {% if forloop.parentloop.last %} + {% if stack_of_plates and forloop.first or not stack_of_plates and forloop.last %} + {% if stack_of_plates and forloop.parentloop.first or not stack_of_plates and forloop.parentloop.last %} raise {{ exception.type }} {% else %} - raise (handled) + raise {{ exception.type }} (handled) {% endif %} - {% elif forloop.first %} {# strictly speaking, not actually "else", but to avoid clutter we hide 'outermost' info when this is also the raise-point #} - {% if forloop.parentloop.last %} - → outermost + {% elif stack_of_plates and forloop.last or not stack_of_plates and forloop.first %} {# strictly speaking, not actually "else", but to avoid clutter we hide 'outermost' info when this is also the raise-point #} + {% if stack_of_plates and forloop.parentloop.first or not stack_of_plates and forloop.parentloop.last %} + → begin {% else %} - outermost (handled) + {% comment %}I find it (quite too) hard to come up with a good name for this type of frame that is both short and clear. Thoughts so fare were: + * try... + * start try + * start failing try (handled) + * "begin handled" () + * "begin handled" {{ exception.type }} + * "outermost handled" + * "divergence w/ main exception" + * first unique frame + {% endcomment %} + try… {% endif %} {% endif %}
{# chevron #} - +
{# per frame header div #} -
{# collapsable part #} +
{# collapsable part #}
{# convience div for padding & border; the border is basically the top-border of the next header #} {% if "context_line" in frame and frame.context_line is not None %} @@ -114,9 +124,13 @@ {#
#} {# per-exception div in the multi-exception case #} {% if not forloop.last %} -
During handling of the above exception another exception occurred or was intentionally reraised:
- {# note: the above is specific to Python. We cannot distinguish between Python's 2 types of chained exceptions because the info is not sent by the client #} - {# we could try to infer this from the stacktrace, but parsing potentially arbitrarily formatted partial code is brittle #} + {% if not stack_of_plates %} +
During handling of the above exception another exception occurred or was intentionally reraised:
+ {# note: the above is specific to Python. We cannot distinguish between Python's 2 types of chained exceptions because the info is not sent by the client #} + {# we could try to infer this from the stacktrace, but parsing potentially arbitrarily formatted partial code is brittle #} + {% else %} +
The above exception was caused by (occurred or was intentially reraised during the handling of) the following exception:
+ {% endif %} {% endif %} {% endfor %} diff --git a/issues/tests.py b/issues/tests.py index 2fa1d7c..3f74298 100644 --- a/issues/tests.py +++ b/issues/tests.py @@ -1,3 +1,4 @@ +import uuid import json import time from io import StringIO @@ -485,6 +486,8 @@ class IntegrationTest(DjangoTestCase): with open(filename) as f: data = json.loads(f.read()) + data["event_id"] = uuid.uuid4().hex + if "timestamp" not in data: # as per send_json command ("weirdly enough a large numer of sentry test data don't actually...") data["timestamp"] = time.time() diff --git a/issues/views.py b/issues/views.py index d490809..bfe9c78 100644 --- a/issues/views.py +++ b/issues/views.py @@ -177,6 +177,25 @@ def issue_event_stacktrace(request, issue, event_pk): # the list of exceptions, but we don't aim for endless backwards compat (yet) so we don't. exceptions = parsed_data["exception"]["values"] if "exception" in parsed_data else None + # NOTE: I considered making this a clickable button of some sort, but decided against it in the end. Getting the UI + # right is quite hard (https://ux.stackexchange.com/questions/1318) but more generally I would assume that having + # your whole screen turned upside down is not something you do willy-nilly. Better to just have good defaults and + # (possibly later) have this as something that is configurable at the user level. + stack_of_plates = event.platform != "python" # Python is the only platform that has chronological stacktraces + + if exceptions is not None and len(exceptions) > 0: + if exceptions[-1].get('stacktrace') and exceptions[-1]['stacktrace'].get('frames'): + exceptions[-1]['stacktrace']['frames'][-1]['raise_point'] = True + + if stack_of_plates: + # NOTE manipulation of parsed_data going on here, this could be a trap if other parts depend on it + # (e.g. grouper) + exceptions = [e for e in reversed(exceptions)] + for exception in exceptions: + if not exception.get('stacktrace'): + continue + exception['stacktrace']['frames'] = [f for f in reversed(exception['stacktrace']['frames'])] + if "logentry" in parsed_data: logentry = parsed_data["logentry"] if "formatted" not in logentry: @@ -195,7 +214,7 @@ def issue_event_stacktrace(request, issue, event_pk): "is_event_page": True, "parsed_data": parsed_data, "exceptions": exceptions, - "issue_grouper": get_issue_grouper_for_data(parsed_data), + "stack_of_plates": stack_of_plates, "mute_options": GLOBAL_MUTE_OPTIONS, }) @@ -262,6 +281,7 @@ def issue_grouping(request, issue): return _handle_post(request, issue) last_event = issue.event_set.order_by("timestamp").last() # the template needs this for the tabs, we pick the last + parsed_data = json.loads(last_event.data) # should this not just be saved on the Issue?? return render(request, "issues/issue_grouping.html", { "tab": "grouping", "project": issue.project, @@ -269,6 +289,7 @@ def issue_grouping(request, issue): "event": last_event, "is_event_page": False, "parsed_data": json.loads(last_event.data), + "issue_grouper": get_issue_grouper_for_data(parsed_data), "mute_options": GLOBAL_MUTE_OPTIONS, }) diff --git a/theme/static/css/dist/styles.css b/theme/static/css/dist/styles.css index 043d6dc..59133bd 100644 --- a/theme/static/css/dist/styles.css +++ b/theme/static/css/dist/styles.css @@ -1028,10 +1028,18 @@ select { appearance: none; } +.flex-row-reverse { + flex-direction: row-reverse; +} + .flex-col { flex-direction: column; } +.flex-col-reverse { + flex-direction: column-reverse; +} + .place-content-end { place-content: end; }