mirror of
https://github.com/jlengrand/bugsink.git
synced 2026-03-10 08:01:17 +00:00
Stacktrace ordering: stack-of-plates as an option
implemented in-python; doing this using flex quickly became a mess
This commit is contained in:
@@ -42,31 +42,41 @@
|
||||
</div>
|
||||
|
||||
<div class="ml-auto pr-4">{# 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 %}
|
||||
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">raise {{ exception.type }}</span>
|
||||
{% else %}
|
||||
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">raise (handled)</span>
|
||||
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">raise {{ exception.type }} (handled)</span>
|
||||
{% 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 %}
|
||||
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">→ outermost</span>
|
||||
{% 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 %}
|
||||
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">→ begin</span>
|
||||
{% else %}
|
||||
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">outermost (handled)</span>
|
||||
{% 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 %}
|
||||
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">try…</span>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="pr-4">{# chevron #}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5 js-chevron transition-all {% if forloop.parentloop.last and forloop.last %}rotate-180{% endif %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5 js-chevron transition-all {% if frame.raise_point %}rotate-180{% endif %}">
|
||||
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
</div> {# per frame header div #}
|
||||
|
||||
<div class="js-frame-details {% if frame.in_app %}js-in-app{% endif %} border-slate-400 overflow-hidden transition-all {% if forloop.parentloop.last and forloop.last %}js-raising-frame{% endif %}"
|
||||
{% if not forloop.parentloop.last or not forloop.last %}data-collapsed="true" style="height: 0px"{% endif %}> {# collapsable part #}
|
||||
<div class="js-frame-details {% if frame.in_app %}js-in-app{% endif %} border-slate-400 overflow-hidden transition-all {% if stack_of_plates and forloop.parentloop.first and forloop.first or not stack_of_plates and forloop.parentloop.last and forloop.last %}js-raising-frame{% endif %}"
|
||||
{% if not frame.raise_point %}data-collapsed="true" style="height: 0px"{% endif %}> {# collapsable part #}
|
||||
|
||||
<div class="pl-6 pr-6 {% if not forloop.last %}border-b-2 border-slate-400{% endif %}">{# 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 @@
|
||||
{# </div> #} {# per-exception div in the multi-exception case #}
|
||||
|
||||
{% if not forloop.last %}
|
||||
<div class="italic pt-4">During handling of the above exception another exception occurred or was intentionally reraised:</div>
|
||||
{# 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 %}
|
||||
<div class="italic pt-4">During handling of the above exception another exception occurred or was intentionally reraised:</div>
|
||||
{# 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 %}
|
||||
<div class="italic pt-4">The above exception was caused by (occurred or was intentially reraised during the handling of) the following exception:</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
8
theme/static/css/dist/styles.css
vendored
8
theme/static/css/dist/styles.css
vendored
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user