ingest_order: first setup

This commit is contained in:
Klaas van Schelven
2024-04-08 22:13:52 +02:00
parent 6d4b1beae4
commit 1b37298a95
13 changed files with 171 additions and 21 deletions

View File

@@ -22,6 +22,7 @@ def create_event(project, issue, timestamp=None, event_data=None):
has_exception=True,
has_logentry=True,
data=json.dumps(event_data),
ingest_order=0,
)

View File

@@ -0,0 +1,19 @@
# Generated by Django 4.2.11 on 2024-04-08 19:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('events', '0012_events_calculated_data'),
]
operations = [
migrations.AddField(
model_name='event',
name='ingest_order',
field=models.IntegerField(default=666),
preserve_default=False,
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 4.2.11 on 2024-04-08 19:22
from django.db import migrations
def fill_ingest_order(apps, schema_editor):
Issue = apps.get_model('issues', 'Issue')
Event = apps.get_model('events', 'Event')
for issue in Issue.objects.all():
for i, event in enumerate(Event.objects.filter(issue=issue).order_by('server_side_timestamp', 'timestamp')):
event.ingest_order = i + 1
event.save()
class Migration(migrations.Migration):
dependencies = [
('events', '0013_event_ingest_order'),
]
operations = [
migrations.RunPython(fill_ingest_order),
]

View File

@@ -138,6 +138,10 @@ class Event(models.Model):
calculated_type = models.CharField(max_length=255, blank=True, null=False, default="")
calculated_value = models.CharField(max_length=255, blank=True, null=False, default="")
# 1-based, because this is for human consumption only, and using 0-based internally when we don't actually do
# anything with this value other than showing it to humans is super-confusing. Sorry Dijkstra!
ingest_order = models.IntegerField(blank=False, null=False)
class Meta:
unique_together = (("project", "event_id"),)
# index_together = (("group_id", "datetime"),) TODO seriously think about indexes
@@ -154,7 +158,7 @@ class Event(models.Model):
return "/events/event/%s/download/" % self.id
@classmethod
def from_ingested(cls, ingested_event, issue, parsed_data, calculated_type, calculated_value):
def from_ingested(cls, ingested_event, ingest_order, issue, parsed_data, calculated_type, calculated_value):
# 'from_ingested' may be a bit of a misnomer... the full 'from_ingested' is done in 'digest_event' in the views.
# below at least puts the parsed_data in the right place, and does some of the basic object set up (FKs to other
# objects etc).
@@ -191,6 +195,7 @@ class Event(models.Model):
'calculated_type': calculated_type,
'calculated_value': calculated_value,
'ingest_order': ingest_order,
}
)
return event, created

View File

@@ -134,7 +134,15 @@ class BaseIngestAPIView(APIView):
# NOTE: an event always has a single (automatically calculated) Grouping associated with it. Since we have that
# information available here, we could add it to the Event model.
event, event_created = Event.from_ingested(ingested_event, issue, event_data, calculated_type, calculated_value)
event, event_created = Event.from_ingested(
ingested_event,
# the assymetry with + 1 is because the event_count is only incremented below for the not issue_created case
issue.event_count if issue_created else issue.event_count + 1,
issue,
event_data,
calculated_type,
calculated_value,
)
if not event_created:
# note: previously we created the event before the issue, which allowed for one less query. I don't see
# straight away how we can reproduce that now that we create issue-before-event (since creating the issue

View File

@@ -0,0 +1,19 @@
{% if event.ingest_order > 1 %}
<a href="{% url this_view issue_pk=issue.pk ingest_order=event.ingest_order|add:"-1" %}" class="font-bold text-slate-500 border-slate-300 pl-4 pr-4 pb-1 pt-1 mr-2 border-2 rounded-md hover:bg-slate-200 active:ring inline-flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-6 h-6"><path fill-rule="evenodd" d="M9.78 4.22a.75.75 0 0 1 0 1.06L7.06 8l2.72 2.72a.75.75 0 1 1-1.06 1.06L5.47 8.53a.75.75 0 0 1 0-1.06l3.25-3.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" /></svg>
</a>
{% else %}
<div class="font-bold text-slate-300 border-slate-300 pl-4 pr-4 pb-1 pt-1 mr-2 border-2 rounded-md inline-flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-6 h-6"><path fill-rule="evenodd" d="M9.78 4.22a.75.75 0 0 1 0 1.06L7.06 8l2.72 2.72a.75.75 0 1 1-1.06 1.06L5.47 8.53a.75.75 0 0 1 0-1.06l3.25-3.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" /></svg>
</div>
{% endif %}
{% if event.ingest_order < issue.event_count %}
<a href="{% url this_view issue_pk=issue.pk ingest_order=event.ingest_order|add:"1" %}"" class="font-bold text-slate-500 border-slate-300 pl-4 pr-4 pb-1 pt-1 mr-2 border-2 rounded-md hover:bg-slate-200 active:ring inline-flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-6 h-6"><path fill-rule="evenodd" d="M6.22 4.22a.75.75 0 0 1 1.06 0l3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06L8.94 8 6.22 5.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /></svg>
</a>
{% else %}
<div class="font-bold text-slate-300 border-slate-300 pl-4 pr-4 pb-1 pt-1 mr-2 border-2 rounded-md inline-flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-6 h-6"><path fill-rule="evenodd" d="M6.22 4.22a.75.75 0 0 1 1.06 0l3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06L8.94 8 6.22 5.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /></svg>
</div>
{% endif %}

View File

@@ -117,7 +117,7 @@
</div>
<div class="flex p-4 bg-slate-200 border-b-2"><!-- bottom nav bar -->
{% if is_event_page %}<div>Event ... {# TODO #} out of {{ issue.event_count}} which occured at <span class="font-bold">{{ event.server_side_timestamp|date:"j M G:i" }}</span></div>{% endif %}
{% if is_event_page %}<div>Event {{ event.ingest_order }} of {{ issue.event_count}} which occured at <span class="font-bold">{{ event.server_side_timestamp|date:"j M G:i" }}</span></div>{% endif %}
<div class="ml-auto pr-4 font-bold text-slate-500">
<a href="/admin/issues/issue/{{ issue.id }}/change/">Issue Admin</a>
{% if is_event_page %}

View File

@@ -5,6 +5,18 @@
{% block tab_content %}
<div class="flex">
<div class="overflow-hidden">
<div class="italic">{{ event.server_side_timestamp|date:"j M G:i" }} (Event {{ event.ingest_order }} of {{ issue.event_count }})</div>
</div>
<div class="ml-auto flex-none">
<div class="flex place-content-end">
{% include "issues/_event_nav.html" %}
</div>
</div>
</div>
{% if not parsed_data.breadcrumbs or not parsed_data.breadcrumbs.values %}
<div class="mt-6 mb-6 italic">
@@ -13,6 +25,7 @@
{% else %}
<div class="pt-4">
<table class="w-full">
{# <thead> </thead> #}
<tbody>
@@ -43,6 +56,7 @@
{% endfor %}
</tbody>
</table>
</div>
{% endif %}

View File

@@ -4,6 +4,19 @@
{% block tab_content %}
<div class="flex">
<div class="overflow-hidden">
<div class="italic">{{ event.server_side_timestamp|date:"j M G:i" }} (Event {{ event.ingest_order }} of {{ issue.event_count }})</div>
</div>
<div class="ml-auto flex-none">
<div class="flex place-content-end">
{% include "issues/_event_nav.html" %}
</div>
</div>
</div>
{# NOTE if we store event.grouper on the event, we could also show that here #}
{% if not parsed_data.top_levels and not parsed_data.tags and not parsed_data.user and not parsed_data.request and not parsed_data.contexts.runtime and not parsed_data.modules and not parsed_data.sdk and not parsed_data.extra %}{# and not parsed_data.contexts.trace #}

View File

@@ -6,6 +6,19 @@
{% block tab_content %}
{% if not exceptions %}
{# event-nav only #}
<div class="flex">
<div class="overflow-hidden">
<div class="italic">{{ event.server_side_timestamp|date:"j M G:i" }} (Event {{ event.ingest_order }} of {{ issue.event_count }})</div>
</div>
<div class="ml-auto flex-none">
<div class="flex place-content-end">
{% include "issues/_event_nav.html" %}
</div>
</div>
</div>
<div class="mt-6 mb-6 italic">
No stacktrace available for this event.
</div>
@@ -16,6 +29,9 @@
<div class="flex">
<div class="overflow-hidden">
{% if forloop.counter0 == 0 %}
<div class="italic">{{ event.server_side_timestamp|date:"j M G:i" }} (Event {{ event.ingest_order }} of {{ issue.event_count }})</div>
{% endif %}
<h1 class="text-2xl font-bold {% if forloop.counter0 > 0 %}mt-4{% endif %} text-ellipsis whitespace-nowrap overflow-hidden">{{ exception.type }}</h1>
<div class="text-lg mb-4 text-ellipsis whitespace-nowrap overflow-hidden">{{ exception.value }}</div>
@@ -27,6 +43,8 @@
<button class="font-bold text-slate-500 border-slate-300 pl-4 pr-4 pb-1 pt-1 mr-2 border-2 rounded-md hover:bg-slate-200 active:ring" onclick="showInAppFrames()">Show in-app</button>
<button class="font-bold text-slate-500 border-slate-300 pl-4 pr-4 pb-1 pt-1 mr-2 border-2 rounded-md hover:bg-slate-200 active:ring" onclick="showRaisingFrame()">Show raise</button>
<button class="font-bold text-slate-500 border-slate-300 pl-4 pr-4 pb-1 pt-1 mr-2 border-2 rounded-md hover:bg-slate-200 active:ring" onclick="hideAllFrames()">Collapse all</button>
{% include "issues/_event_nav.html" %}
</div>
</div>
{% endif %}

View File

@@ -11,9 +11,15 @@ urlpatterns = [
path('<int:project_pk>/muted/', issue_list, {"state_filter": "muted"}, name="issue_list_muted"),
path('<int:project_pk>/all/', issue_list, {"state_filter": "all"}, name="issue_list_all"),
path('issue/<uuid:issue_pk>/event/<uuid:event_pk>/', issue_event_stacktrace),
path('issue/<uuid:issue_pk>/event/<uuid:event_pk>/details/', issue_event_details),
path('issue/<uuid:issue_pk>/event/<uuid:event_pk>/breadcrumbs/', issue_event_breadcrumbs),
path('issue/<uuid:issue_pk>/event/<uuid:event_pk>/', issue_event_stacktrace, name="event_stacktrace"),
path('issue/<uuid:issue_pk>/event/<uuid:event_pk>/details/', issue_event_details, name="event_details"),
path('issue/<uuid:issue_pk>/event/<uuid:event_pk>/breadcrumbs/', issue_event_breadcrumbs, name="event_breadcrumbs"),
path('issue/<uuid:issue_pk>/event/<int:ingest_order>/', issue_event_stacktrace, name="event_stacktrace"),
path('issue/<uuid:issue_pk>/event/<int:ingest_order>/details/', issue_event_details, name="event_details"),
path('issue/<uuid:issue_pk>/event/<int:ingest_order>/breadcrumbs/', issue_event_breadcrumbs,
name="event_breadcrumbs"),
path('issue/<uuid:issue_pk>/history/', issue_history),
path('issue/<uuid:issue_pk>/grouping/', issue_grouping),
path('issue/<uuid:issue_pk>/event/last/', issue_last_event),

View File

@@ -163,12 +163,21 @@ def _handle_post(request, issue):
return HttpResponseRedirect(request.path_info)
def _get_event(issue, event_pk, ingest_order):
if event_pk is not None:
return get_object_or_404(Event, pk=event_pk)
elif ingest_order is not None:
return get_object_or_404(Event, issue=issue, ingest_order=ingest_order)
else:
raise ValueError("either event_pk or ingest_order must be provided")
@issue_membership_required
def issue_event_stacktrace(request, issue, event_pk):
def issue_event_stacktrace(request, issue, event_pk=None, ingest_order=None):
if request.method == "POST":
return _handle_post(request, issue)
event = get_object_or_404(Event, pk=event_pk)
event = _get_event(issue, event_pk, ingest_order)
parsed_data = json.loads(event.data)
@@ -207,6 +216,7 @@ def issue_event_stacktrace(request, issue, event_pk):
return render(request, "issues/issue_stacktrace.html", {
"tab": "stacktrace",
"this_view": "event_stacktrace",
"project": issue.project,
"issue": issue,
"event": event,
@@ -219,16 +229,17 @@ def issue_event_stacktrace(request, issue, event_pk):
@issue_membership_required
def issue_event_breadcrumbs(request, issue, event_pk):
def issue_event_breadcrumbs(request, issue, event_pk=None, ingest_order=None):
if request.method == "POST":
return _handle_post(request, issue)
event = get_object_or_404(Event, pk=event_pk)
event = _get_event(issue, event_pk, ingest_order)
parsed_data = json.loads(event.data)
return render(request, "issues/issue_breadcrumbs.html", {
"tab": "breadcrumbs",
"this_view": "event_breadcrumbs",
"project": issue.project,
"issue": issue,
"event": event,
@@ -239,11 +250,11 @@ def issue_event_breadcrumbs(request, issue, event_pk):
@issue_membership_required
def issue_event_details(request, issue, event_pk):
def issue_event_details(request, issue, event_pk=None, ingest_order=None):
if request.method == "POST":
return _handle_post(request, issue)
event = get_object_or_404(Event, pk=event_pk)
event = _get_event(issue, event_pk, ingest_order)
parsed_data = json.loads(event.data)
parsed_data["top_levels"] = \
@@ -253,6 +264,7 @@ def issue_event_details(request, issue, event_pk):
return render(request, "issues/issue_event_details.html", {
"tab": "event-details",
"this_view": "event_details",
"project": issue.project,
"issue": issue,
"event": event,

View File

@@ -898,6 +898,10 @@ select {
display: flex;
}
.inline-flex {
display: inline-flex;
}
.table {
display: table;
}
@@ -918,6 +922,10 @@ select {
height: 3rem;
}
.h-4 {
height: 1rem;
}
.h-5 {
height: 1.25rem;
}
@@ -950,6 +958,10 @@ select {
width: 75%;
}
.w-4 {
width: 1rem;
}
.w-5 {
width: 1.25rem;
}
@@ -1048,6 +1060,10 @@ select {
align-items: center;
}
.items-stretch {
align-items: stretch;
}
.justify-center {
justify-content: center;
}
@@ -1118,10 +1134,6 @@ select {
border-left-width: 2px;
}
.border-l-4 {
border-left-width: 4px;
}
.border-r-2 {
border-right-width: 2px;
}
@@ -1331,11 +1343,6 @@ select {
line-height: 2rem;
}
.text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
}
.text-4xl {
font-size: 2.25rem;
line-height: 2.5rem;
@@ -1369,6 +1376,10 @@ select {
font-style: italic;
}
.leading-4 {
line-height: 1rem;
}
.leading-normal {
line-height: 1.5;
}