mirror of
https://github.com/jlengrand/bugsink.git
synced 2026-03-10 08:01:17 +00:00
Event search: first version
This commit is contained in:
@@ -4,7 +4,6 @@ import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.db.utils import IntegrityError
|
||||
from django.db.models import Min, Max
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from projects.models import Project
|
||||
@@ -276,18 +275,6 @@ class Event(models.Model):
|
||||
|
||||
return None, False
|
||||
|
||||
def get_digest_order_bounds(self):
|
||||
if not hasattr(self, "_digest_order_bounds"):
|
||||
d = Event.objects.filter(issue_id=self.issue.id).aggregate(lo=Min("digest_order"), hi=Max("digest_order"))
|
||||
self._digest_order_bounds = d["lo"], d["hi"]
|
||||
return self._digest_order_bounds
|
||||
|
||||
def has_prev(self):
|
||||
return self.digest_order > self.get_digest_order_bounds()[0]
|
||||
|
||||
def has_next(self):
|
||||
return self.digest_order < self.get_digest_order_bounds()[1]
|
||||
|
||||
@cached_property
|
||||
def get_tags(self):
|
||||
return list(
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
{% if event.has_prev %} {# no need for 'is_first': if you can go to the left, you can go all the way to the left too #}
|
||||
<a href="{% url this_view issue_pk=issue.pk nav="first" %}" 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" title="First event">
|
||||
{% load add_to_qs %}
|
||||
|
||||
<form action="{% url this_view issue_pk=issue.pk nav="last" %}" method="get">{# nav="last": when doing a new search on an event-page, you want the most recent matching event to show up #}
|
||||
<input type="text" name="q" value="{{ q }}" placeholder="search..." class="focus:border-cyan-500 focus:ring-cyan-200 rounded-md mr-2"/>
|
||||
</form>
|
||||
|
||||
{% if has_prev %} {# no need for 'is_first': if you can go to the left, you can go all the way to the left too #}
|
||||
<a href="{% url this_view issue_pk=issue.pk nav="first" %}{% current_qs %}" 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" title="First event">
|
||||
<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="M3.22 7.595a.75.75 0 0 0 0 1.06l3.25 3.25a.75.75 0 0 0 1.06-1.06l-2.72-2.72 2.72-2.72a.75.75 0 0 0-1.06-1.06l-3.25 3.25Zm8.25-3.25-3.25 3.25a.75.75 0 0 0 0 1.06l3.25 3.25a.75.75 0 1 0 1.06-1.06l-2.72-2.72 2.72-2.72a.75.75 0 0 0-1.06-1.06Z" clip-rule="evenodd" /></svg>
|
||||
</a>
|
||||
{% else %}
|
||||
@@ -8,8 +14,8 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if event.has_prev %}
|
||||
<a href="{% url this_view issue_pk=issue.pk digest_order=event.digest_order nav="prev" %}" 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" title="Previous event">
|
||||
{% if has_prev %}
|
||||
<a href="{% url this_view issue_pk=issue.pk digest_order=event.digest_order nav="prev" %}{% current_qs %}" 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" title="Previous event">
|
||||
<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 %}
|
||||
@@ -18,8 +24,8 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if event.has_next %}
|
||||
<a href="{% url this_view issue_pk=issue.pk digest_order=event.digest_order nav="next" %}" 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" title="Next event">
|
||||
{% if has_next %}
|
||||
<a href="{% url this_view issue_pk=issue.pk digest_order=event.digest_order nav="next" %}{% current_qs %}" 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" title="Next event">
|
||||
<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 %}
|
||||
@@ -28,8 +34,8 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if event.has_next %}
|
||||
<a href="{% url this_view issue_pk=issue.pk nav="last" %}" 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" title="Last event">
|
||||
{% if has_next %}
|
||||
<a href="{% url this_view issue_pk=issue.pk nav="last" %}{% current_qs %}" 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" title="Last event">
|
||||
<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="M12.78 7.595a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06l2.72-2.72-2.72-2.72a.75.75 0 0 1 1.06-1.06l3.25 3.25Zm-8.25-3.25 3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06l2.72-2.72-2.72-2.72a.75.75 0 0 1 1.06-1.06Z" clip-rule="evenodd" /></svg>
|
||||
</a>
|
||||
{% else %}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
{% load issues %}
|
||||
{% load humanize %}
|
||||
{% load stricter_templates %}
|
||||
{% load add_to_qs %}
|
||||
{% block title %}{{ issue.title }} · {{ block.super }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -95,7 +96,7 @@
|
||||
<div class="overflow-hidden"><!-- top, LHS (various texts) -->
|
||||
<h1 class="text-4xl font-bold text-ellipsis whitespace-nowrap overflow-hidden pb-1 {# needed for descenders of 'g' #}">{{ issue.calculated_type }}</h1>
|
||||
<div class="text-xl text-ellipsis whitespace-nowrap overflow-hidden">{{ issue.calculated_value }}</div>
|
||||
{% if parsed_data.request %}<div class="italic mt-4">{{ parsed_data.request.method }} {{ parsed_data.request.url }}</div>{% endif %}
|
||||
{% if request_repr %}<div class="italic mt-4">{{ request_repr }}</div>{% endif %}
|
||||
<div class="text-ellipsis whitespace-nowrap overflow-hidden"><span class="font-bold">{% if issue.last_frame_module %}{{ issue.last_frame_module}}{% else %}{{ issue.last_frame_filename }}{% endif %}</span>{% if issue.last_frame_function %} in <span class="font-bold">{{ issue.last_frame_function }}</span>{% endif %}</div>
|
||||
</div> {# top, LHS (various texts) #}
|
||||
|
||||
@@ -106,13 +107,13 @@
|
||||
{# overflow-x-auto is needed at the level of the flex item such that it works at the level where we need it (the code listings)#}
|
||||
<div class="ml-4 mb-4 mr-4 border-2 overflow-x-auto flex-[2_1_96rem]"><!-- the whole of the big tabbed view--> {# 96rem is 1536px, which matches the 2xl class; this is no "must" but eyeballing revealed: good result #}
|
||||
<div class="flex bg-slate-50 border-b-2"><!-- container for the actual tab buttons -->
|
||||
<a href="/issues/issue/{{ issue.id }}/event/{{ event.id }}/"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "stacktrace" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">Stacktrace</div></a>
|
||||
<a href="/issues/issue/{{ issue.id }}/event/{{ event.id }}/details/"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "event-details" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">Event Details</div></a>
|
||||
<a href="/issues/issue/{{ issue.id }}/event/{{ event.id }}/breadcrumbs/"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "breadcrumbs" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">Breadcrumbs</div></a>
|
||||
<a href="/issues/issue/{{ issue.id }}/tags/"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "tags" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">Tags</div></a>
|
||||
<a href="/issues/issue/{{ issue.id }}/events/"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "event-list" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">Event List</div></a>
|
||||
<a href="/issues/issue/{{ issue.id }}/grouping/"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "grouping" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">Grouping</div></a>
|
||||
<a href="/issues/issue/{{ issue.id }}/history/"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "history" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">History</div></a>
|
||||
<a href="/issues/issue/{{ issue.id }}/event/{% if event %}{{ event.id }}{% else %}none{% endif %}/{% current_qs %}"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "stacktrace" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">Stacktrace</div></a>
|
||||
<a href="/issues/issue/{{ issue.id }}/event/{% if event %}{{ event.id }}{% else %}none{% endif %}/details/{% current_qs %}"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "event-details" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">Event Details</div></a>
|
||||
<a href="/issues/issue/{{ issue.id }}/event/{% if event %}{{ event.id }}{% else %}none{% endif %}/breadcrumbs/{% current_qs %}"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "breadcrumbs" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">Breadcrumbs</div></a>
|
||||
<a href="/issues/issue/{{ issue.id }}/events/{% current_qs %}"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "event-list" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">Event List</div></a>
|
||||
<a href="/issues/issue/{{ issue.id }}/tags/{% current_qs %}"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "tags" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">Tags</div></a>
|
||||
<a href="/issues/issue/{{ issue.id }}/grouping/{% current_qs %}"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "grouping" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">Grouping</div></a>
|
||||
<a href="/issues/issue/{{ issue.id }}/history/{% current_qs %}"><div class="p-4 font-bold hover:bg-slate-200 {% if tab == "history" %}text-cyan-500 border-cyan-500 border-b-4{% else %}text-slate-500 border-slate-400 hover:border-b-4{% endif %}">History</div></a>
|
||||
</div>
|
||||
|
||||
<div class="m-4"><!-- div for tab_content -->
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<div class="flex">
|
||||
<div class="overflow-hidden">
|
||||
<div class="italic">{{ event.ingested_at|date:"j M G:i T" }} (Event {{ event.digest_order|intcomma }} of {{ issue.digested_event_count|intcomma }})</div>
|
||||
<div class="italic">{{ event.ingested_at|date:"j M G:i T" }} (Event {{ event.digest_order|intcomma }} of {{ issue.digested_event_count|intcomma }} total{% if q %} — {{ event_qs.count }} found by search{% endif %})</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex-none">
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
{% extends "issues/base.html" %}
|
||||
{% load static %}
|
||||
{% load stricter_templates %}
|
||||
{% load add_to_qs %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block tab_content %}
|
||||
|
||||
<div class="flex">
|
||||
|
||||
<div class="overflow-hidden">
|
||||
<div class="italic">xxxx xx xx xx:xx (Event xxx of {{ issue.digested_event_count }})</div>
|
||||
<div class="italic">{{ issue.digested_event_count|intcomma }} events in total{% if q %} — {{ event_qs_count }} found by search{% endif %}.</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex-none">
|
||||
<div class="flex place-content-end">
|
||||
{# copy/paste of _event_nav, but not based on any event (we have none), prev/next are meaningless also #}
|
||||
{# so we have first/last enabled, and the middle ones disabled #}
|
||||
{# copy/paste of _event_nav, but not based on any event (we have none), prev/next are meaningless also; first/last only when we have an event_qs to navigate through #}
|
||||
<form action="{% url this_view issue_pk=issue.pk nav="last" %}" method="get">{# nav="last": when doing a new search on an event-page, you want the most recent matching event to show up #}
|
||||
<input type="text" name="q" value="{{ q }}" placeholder="search..." class="focus:border-cyan-500 focus:ring-cyan-200 rounded-md mr-2"/>
|
||||
</form>
|
||||
|
||||
<a href="{% url this_view issue_pk=issue.pk nav="first" %}" 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" title="First event">
|
||||
{% if event_qs_count %}
|
||||
<a href="{% url this_view issue_pk=issue.pk nav="first" %}{% current_qs %}" 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" title="First event">
|
||||
<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="M3.22 7.595a.75.75 0 0 0 0 1.06l3.25 3.25a.75.75 0 0 0 1.06-1.06l-2.72-2.72 2.72-2.72a.75.75 0 0 0-1.06-1.06l-3.25 3.25Zm8.25-3.25-3.25 3.25a.75.75 0 0 0 0 1.06l3.25 3.25a.75.75 0 1 0 1.06-1.06l-2.72-2.72 2.72-2.72a.75.75 0 0 0-1.06-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" title="First event">
|
||||
<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="M3.22 7.595a.75.75 0 0 0 0 1.06l3.25 3.25a.75.75 0 0 0 1.06-1.06l-2.72-2.72 2.72-2.72a.75.75 0 0 0-1.06-1.06l-3.25 3.25Zm8.25-3.25-3.25 3.25a.75.75 0 0 0 0 1.06l3.25 3.25a.75.75 0 1 0 1.06-1.06l-2.72-2.72 2.72-2.72a.75.75 0 0 0-1.06-1.06Z" clip-rule="evenodd" /></svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<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" title="Previous event">
|
||||
<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>
|
||||
@@ -28,19 +38,35 @@
|
||||
<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>
|
||||
|
||||
<a href="{% url this_view issue_pk=issue.pk nav="last" %}" 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" title="Last event">
|
||||
{% if event_qs_count %}
|
||||
<a href="{% url this_view issue_pk=issue.pk nav="last" %}{% current_qs %}" 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" title="Last event">
|
||||
<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="M12.78 7.595a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06l2.72-2.72-2.72-2.72a.75.75 0 0 1 1.06-1.06l3.25 3.25Zm-8.25-3.25 3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06l2.72-2.72-2.72-2.72a.75.75 0 0 1 1.06-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" title="Last event">
|
||||
<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="M12.78 7.595a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06l2.72-2.72-2.72-2.72a.75.75 0 0 1 1.06-1.06l3.25 3.25Zm-8.25-3.25 3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06l2.72-2.72-2.72-2.72a.75.75 0 0 1 1.06-1.06Z" clip-rule="evenodd" /></svg>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h1 class="text-2xl font-bold mt-4">404: Event missing from Bugsink</h1>
|
||||
<h1 class="text-2xl font-bold mt-4">{% if event_qs_count %}404: Event missing from Bugsink{% else %}No Events{% endif %}</h1>
|
||||
|
||||
<div class="mb-6">
|
||||
{# We apply the heuristic (textually) that if you have some events in your event_qs, but no current event, you're in a "404-like" ("This event not found") state #}
|
||||
{# and if there's really no events in the qs, that that fact is what you should focus on. #}
|
||||
{# This works well in practice (better than trying to match these texts to "where in _get_event() did we go wrong?" #}
|
||||
{% if event_qs_count %}
|
||||
This event cannot be found. It could have been removed manually or as part of the eviction process.
|
||||
{% elif q %}
|
||||
No events found for this search.
|
||||
{% else %}
|
||||
No events found. They could have been removed manually or as part of the eviction process.
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<div class="flex">
|
||||
<div class="overflow-hidden">
|
||||
<div class="italic">{{ event.ingested_at|date:"j M G:i T" }} (Event {{ event.digest_order|intcomma }} of {{ issue.digested_event_count|intcomma }})</div>
|
||||
<div class="italic">{{ event.ingested_at|date:"j M G:i T" }} (Event {{ event.digest_order|intcomma }} of {{ issue.digested_event_count|intcomma }} total{% if q %} — {{ event_qs.count }} found by search{% endif %})</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex-none">
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
{% extends "issues/base.html" %}
|
||||
{% load issues %}
|
||||
{% load add_to_qs %}
|
||||
{% load humanize %}
|
||||
{% load add_to_qs %}
|
||||
|
||||
{% block tab_content %}
|
||||
|
||||
<div class="flex">
|
||||
<div class="overflow-hidden">
|
||||
<div class="italic">
|
||||
Showing {{ page_obj.start_index|intcomma }} - {{ page_obj.end_index|intcomma }} of {{ page_obj.paginator.count|intcomma }}
|
||||
{% if issue.digested_event_count != issue.stored_event_count %}
|
||||
available events ({{ issue.digested_event_count|intcomma }} total observed).
|
||||
{% else %}
|
||||
total events.
|
||||
Showing {{ page_obj.start_index|intcomma }} - {{ page_obj.end_index|intcomma }} of
|
||||
{% if page_obj.paginator.count == issue.stored_event_count and issue.stored_event_count == issue.digested_event_count %} {# all equal #}
|
||||
{{ page_obj.paginator.count|intcomma }} total events.
|
||||
{% elif page_obj.paginator.count == issue.stored_event_count and issue.stored_event_count != issue.digested_event_count %} {# evictions applied #}
|
||||
{{ page_obj.paginator.count|intcomma }} available events ({{ issue.digested_event_count|intcomma }} total observed).
|
||||
{% elif page_obj.paginator.count != issue.stored_event_count and issue.stored_event_count == issue.digested_event_count %} {# search filters #}
|
||||
{{ page_obj.paginator.count|intcomma }} events found ({{ issue.digested_event_count|intcomma }} total observed).
|
||||
{% else %} {# everything unequal #}
|
||||
{{ page_obj.paginator.count|intcomma }} events found ({{ issue.digested_event_count|intcomma }} available, {{ issue.digested_event_count|intcomma }} total observed).
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,8 +26,12 @@
|
||||
{# UI / UX question: is it a good idea to reuse-with-different-meaning (pages, not events) for this? #}
|
||||
{# adapted copy/pasta from _event_nav #}
|
||||
<div class="flex place-content-end">
|
||||
<form action="." method="get">
|
||||
<input type="text" name="q" value="{{ q }}" placeholder="search..." class="focus:border-cyan-500 focus:ring-cyan-200 rounded-md mr-2"/>
|
||||
</form>
|
||||
|
||||
{% if page_obj.has_previous %} {# no need for 'is_first': if you can go to the left, you can go all the way to the left too #}
|
||||
<a href="?page=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" title="First page">
|
||||
<a href="?{% add_to_qs page=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" title="First page">
|
||||
<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="M3.22 7.595a.75.75 0 0 0 0 1.06l3.25 3.25a.75.75 0 0 0 1.06-1.06l-2.72-2.72 2.72-2.72a.75.75 0 0 0-1.06-1.06l-3.25 3.25Zm8.25-3.25-3.25 3.25a.75.75 0 0 0 0 1.06l3.25 3.25a.75.75 0 1 0 1.06-1.06l-2.72-2.72 2.72-2.72a.75.75 0 0 0-1.06-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" title="First page">
|
||||
@@ -30,7 +40,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if page_obj.has_previous %}
|
||||
<a href="?page={{ page_obj.previous_page_number }}" 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" title="Previous page">
|
||||
<a href="?{% add_to_qs page=page_obj.previous_page_number %}" 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" title="Previous page">
|
||||
<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 %}
|
||||
@@ -40,7 +50,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<a href="?page={{ page_obj.next_page_number }}" 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" title="Next page">
|
||||
<a href="?{% add_to_qs page=page_obj.next_page_number %}" 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" title="Next page">
|
||||
<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 %}
|
||||
@@ -50,7 +60,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<a href="?page={{ page_obj.paginator.num_pages }}" 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" title="Last page">
|
||||
<a href="?{% add_to_qs page=page_obj.paginator.num_pages %}" 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" title="Last page">
|
||||
<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="M12.78 7.595a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06l2.72-2.72-2.72-2.72a.75.75 0 0 1 1.06-1.06l3.25 3.25Zm-8.25-3.25 3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06l2.72-2.72-2.72-2.72a.75.75 0 0 1 1.06-1.06Z" clip-rule="evenodd" /></svg>
|
||||
</a>
|
||||
{% else %}
|
||||
@@ -109,11 +119,11 @@ TODO
|
||||
<tr class="border-slate-200 border-2 ">
|
||||
|
||||
<td class="p-4 font-bold text-slate-500 align-top">
|
||||
<a href="/issues/issue/{{ issue.id }}/event/{{ event.id }}/">{{ event.digest_order }}</a>
|
||||
<a href="/issues/issue/{{ issue.id }}/event/{{ event.id }}/{% current_qs %}">{{ event.digest_order }}</a>
|
||||
</td>
|
||||
|
||||
<td class="p-4 font-bold text-slate-500 align-top"> {# how useful is this really? #}
|
||||
<a href="/issues/issue/{{ issue.id }}/event/{{ event.id }}/">{{ event.id|truncatechars:9 }}</a>
|
||||
<a href="/issues/issue/{{ issue.id }}/event/{{ event.id }}/{% current_qs %}">{{ event.id|truncatechars:9 }}</a>
|
||||
</td>
|
||||
|
||||
<td class="p-4 font-mono whitespace-nowrap align-top">
|
||||
@@ -140,6 +150,12 @@ TODO
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="p-4 text-slate-800 italic">
|
||||
No events found{% if q %} for "{{ q }}"{% endif %}.
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{# note: no "empty" case; event-less issues are not something I expect to really support (for some definition of "really") #}
|
||||
</tbody>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{# event-nav only #}
|
||||
<div class="flex">
|
||||
<div class="overflow-hidden">
|
||||
<div class="italic">{{ event.ingested_at|date:"j M G:i T" }} (Event {{ event.digest_order|intcomma }} of {{ issue.digested_event_count|intcomma }})</div>
|
||||
<div class="italic">{{ event.ingested_at|date:"j M G:i T" }} (Event {{ event.digest_order|intcomma }} of {{ issue.digested_event_count|intcomma }} total{% if q %} — {{ event_qs.count }} found by search{% endif %})</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex-none">
|
||||
@@ -31,7 +31,7 @@
|
||||
<div class="flex">
|
||||
<div class="overflow-hidden">
|
||||
{% if forloop.counter0 == 0 %}
|
||||
<div class="italic">{{ event.ingested_at|date:"j M G:i T" }} (Event {{ event.digest_order|intcomma }} of {{ issue.digested_event_count|intcomma }})</div>
|
||||
<div class="italic">{{ event.ingested_at|date:"j M G:i T" }} (Event {{ event.digest_order|intcomma }} of {{ issue.digested_event_count|intcomma }} total{% if q %} — {{ event_qs.count }} found by search{% endif %})</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>
|
||||
|
||||
@@ -22,6 +22,7 @@ def regex_converter(passed_regex):
|
||||
|
||||
register_converter(regex_converter("(first|last)"), "first-last")
|
||||
register_converter(regex_converter("(prev|next)"), "prev-next")
|
||||
register_converter(regex_converter("(none)"), "str-none")
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
@@ -35,6 +36,11 @@ urlpatterns = [
|
||||
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/<str-none:event_pk>/', issue_event_stacktrace, name="event_stacktrace"),
|
||||
path('issue/<uuid:issue_pk>/event/<str-none:event_pk>/details/', issue_event_details, name="event_details"),
|
||||
path('issue/<uuid:issue_pk>/event/<str-none:event_pk>/breadcrumbs/',
|
||||
issue_event_breadcrumbs, name="event_breadcrumbs"),
|
||||
|
||||
path('issue/<uuid:issue_pk>/event/<int:digest_order>/', issue_event_stacktrace, name="event_stacktrace"),
|
||||
path('issue/<uuid:issue_pk>/event/<int:digest_order>/details/', issue_event_details, name="event_details"),
|
||||
path('issue/<uuid:issue_pk>/event/<int:digest_order>/breadcrumbs/', issue_event_breadcrumbs,
|
||||
|
||||
170
issues/views.py
170
issues/views.py
@@ -3,6 +3,7 @@ import json
|
||||
import sentry_sdk
|
||||
import re
|
||||
|
||||
from django.db.models import Min, Max
|
||||
from django.utils import timezone
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.db.models import Q, Subquery
|
||||
@@ -26,7 +27,7 @@ from events.ua_stuff import get_contexts_enriched_with_ua
|
||||
|
||||
from compat.timestamp import format_timestamp
|
||||
from projects.models import ProjectMembership
|
||||
from tags.models import TagValue, IssueTag
|
||||
from tags.models import TagValue, IssueTag, EventTag
|
||||
|
||||
from .models import Issue, IssueQuerysetStateManager, IssueStateManager, TurningPoint, TurningPointKind
|
||||
from .forms import CommentForm
|
||||
@@ -62,6 +63,13 @@ class KnownCountPaginator(Paginator):
|
||||
return self._count
|
||||
|
||||
|
||||
def _request_repr(parsed_data):
|
||||
if "request" not in parsed_data:
|
||||
return ""
|
||||
|
||||
return parsed_data["request"].get("method", "") + " " + parsed_data["request"].get("url", "")
|
||||
|
||||
|
||||
def _is_valid_action(action, issue):
|
||||
"""We take the 'strict' approach of complaining even when the action is simply a no-op, because you're already in
|
||||
the desired state."""
|
||||
@@ -240,6 +248,9 @@ def _and_join(q_objects):
|
||||
|
||||
|
||||
def _search(project, issue_list, q):
|
||||
if not q:
|
||||
return issue_list
|
||||
|
||||
# The simplest possible query-language that could have any value: key:value is recognized as such; the rest is "free
|
||||
# text"; no support for quoting of spaces.
|
||||
slices_to_remove = []
|
||||
@@ -274,6 +285,44 @@ def _search(project, issue_list, q):
|
||||
return issue_list
|
||||
|
||||
|
||||
def _search_events(project, event_list, q):
|
||||
if not q:
|
||||
return event_list
|
||||
|
||||
# The simplest possible query-language that could have any value: key:value is recognized as such; the rest is "free
|
||||
# text"; no support for quoting of spaces.
|
||||
slices_to_remove = []
|
||||
clauses = []
|
||||
for match in re.finditer(r"(\S+):(\S+)", q):
|
||||
slices_to_remove.append(match.span())
|
||||
key, value = match.groups()
|
||||
try:
|
||||
tag_value_obj = TagValue.objects.get(project=project, key__key=key, value=value)
|
||||
except TagValue.DoesNotExist:
|
||||
# if the tag doesn't exist, we can't have any events with it; the below short-circuit is fine, I think (I
|
||||
# mean: we _could_ say "tag x is to blame" but that's not what one does generally in search, is it?
|
||||
return event_list.none()
|
||||
|
||||
# TODO: Extensive performance testing of various choices here is necessary; in particular the choice of Subquery
|
||||
# vs. joins; and the choice of a separate query to get TagValue v.s. doing everything in a single big query will
|
||||
# have different trade-offs _in practice_.
|
||||
clauses.append(
|
||||
Q(id__in=Subquery(EventTag.objects.filter(value=tag_value_obj).values_list("event_id", flat=True))))
|
||||
|
||||
# this is really TSTTCPW (or more like a "fake it till you make it" thing); but I'd rather "have something" and then
|
||||
# have really-good-search than to have either nothing at all, or half-baked search. Note that we didn't even bother
|
||||
# to set indexes on the fields we search on (nor create a single searchable field for the whole of 'title').
|
||||
plain_text_q = remove_slices(q, slices_to_remove).strip()
|
||||
if plain_text_q:
|
||||
clauses.append(Q(Q(calculated_type__icontains=plain_text_q) | Q(calculated_value__icontains=plain_text_q)))
|
||||
|
||||
# if we reach this point, there's always either a plain_text_q or some key/value pair (this is a condition for
|
||||
# _and_join)
|
||||
event_list = event_list.filter(_and_join(clauses))
|
||||
|
||||
return event_list
|
||||
|
||||
|
||||
def _issue_list_pt_2(request, project, state_filter, unapplied_issue_ids):
|
||||
d_state_filter = {
|
||||
"open": lambda qs: qs.filter(is_resolved=False, is_muted=False),
|
||||
@@ -336,38 +385,42 @@ def _handle_post(request, issue):
|
||||
return HttpResponseRedirect(request.path_info)
|
||||
|
||||
|
||||
def _get_event(issue, event_pk, digest_order, nav):
|
||||
if nav is not None:
|
||||
if nav == "first":
|
||||
return Event.objects.filter(issue=issue).order_by("digest_order").first()
|
||||
if nav == "last":
|
||||
return Event.objects.filter(issue=issue).order_by("digest_order").last()
|
||||
def _get_event(qs, event_pk, digest_order, nav):
|
||||
"""Returns the event using the "url lookup"."""
|
||||
|
||||
if nav in ["prev", "next"]:
|
||||
if nav is not None:
|
||||
if nav not in ["first", "last", "prev", "next"]:
|
||||
raise Http404("Cannot look up with '%s'" % nav)
|
||||
|
||||
if nav == "first":
|
||||
result = qs.order_by("digest_order").first()
|
||||
elif nav == "last":
|
||||
result = qs.order_by("digest_order").last()
|
||||
elif nav in ["prev", "next"]:
|
||||
if digest_order is None:
|
||||
raise Http404("Cannot look up with '%s' without digest_order" % nav)
|
||||
|
||||
if nav == "prev":
|
||||
result = Event.objects.filter(
|
||||
issue=issue, digest_order__lt=digest_order).order_by("-digest_order").first()
|
||||
result = qs.filter(digest_order__lt=digest_order).order_by("-digest_order").first()
|
||||
elif nav == "next":
|
||||
result = Event.objects.filter(
|
||||
issue=issue, digest_order__gt=digest_order).order_by("digest_order").first()
|
||||
if result is None:
|
||||
raise Event.DoesNotExist
|
||||
return result
|
||||
result = qs.filter(digest_order__gt=digest_order).order_by("digest_order").first()
|
||||
|
||||
raise Http404("Cannot look up with '%s'" % nav)
|
||||
if result is None:
|
||||
raise Event.DoesNotExist
|
||||
return result
|
||||
|
||||
elif event_pk is not None:
|
||||
# we match on both internal and external id, trying internal first
|
||||
if event_pk == "none":
|
||||
raise Event.DoesNotExist()
|
||||
|
||||
try:
|
||||
return Event.objects.get(pk=event_pk)
|
||||
except Event.DoesNotExist:
|
||||
return Event.objects.get(issue=issue, event_id=event_pk)
|
||||
return qs.get(event_id=event_pk)
|
||||
|
||||
elif digest_order is not None:
|
||||
return Event.objects.get(issue=issue, digest_order=digest_order)
|
||||
return qs.get(digest_order=digest_order)
|
||||
else:
|
||||
raise Http404("Either event_pk, nav, or digest_order must be provided")
|
||||
|
||||
@@ -378,10 +431,14 @@ def issue_event_stacktrace(request, issue, event_pk=None, digest_order=None, nav
|
||||
if request.method == "POST":
|
||||
return _handle_post(request, issue)
|
||||
|
||||
event_qs = Event.objects.filter(issue=issue)
|
||||
if request.GET.get("q"):
|
||||
event_qs = _search_events(issue.project, event_qs, request.GET["q"])
|
||||
|
||||
try:
|
||||
event = _get_event(issue, event_pk, digest_order, nav)
|
||||
event = _get_event(event_qs, event_pk, digest_order, nav)
|
||||
except Event.DoesNotExist:
|
||||
return issue_event_404(request, issue, "stacktrace", "event_stacktrace")
|
||||
return issue_event_404(request, issue, event_qs, "stacktrace", "event_stacktrace")
|
||||
|
||||
parsed_data = event.get_parsed_data()
|
||||
|
||||
@@ -425,16 +482,20 @@ def issue_event_stacktrace(request, issue, event_pk=None, digest_order=None, nav
|
||||
"event": event,
|
||||
"is_event_page": True,
|
||||
"parsed_data": parsed_data,
|
||||
"request_repr": _request_repr(parsed_data),
|
||||
"exceptions": exceptions,
|
||||
"stack_of_plates": stack_of_plates,
|
||||
"mute_options": GLOBAL_MUTE_OPTIONS,
|
||||
"q": request.GET.get("q", ""),
|
||||
"event_qs": event_qs,
|
||||
**_has_next_prev(event, event_qs),
|
||||
})
|
||||
|
||||
|
||||
def issue_event_404(request, issue, tab, this_view):
|
||||
def issue_event_404(request, issue, event_qs, tab, this_view):
|
||||
"""If the Event is 404, but the issue is not, we can still show the issue page; we show a message for the event"""
|
||||
|
||||
last_event = issue.event_set.order_by("digest_order").last() # the template needs this for the tabs
|
||||
last_event = event_qs.last() # used for switching to an event-page (using tabs)
|
||||
return render(request, "issues/event_404.html", {
|
||||
"tab": tab,
|
||||
"this_view": this_view,
|
||||
@@ -443,6 +504,9 @@ def issue_event_404(request, issue, tab, this_view):
|
||||
"event": last_event,
|
||||
"is_event_page": False, # this variable is used to denote "we have event-related info", which we don't
|
||||
"mute_options": GLOBAL_MUTE_OPTIONS,
|
||||
"event_qs": event_qs,
|
||||
"q": request.GET.get("q", ""),
|
||||
"event_qs_count": event_qs.count(), # avoids repeating the count() query
|
||||
})
|
||||
|
||||
|
||||
@@ -452,10 +516,14 @@ def issue_event_breadcrumbs(request, issue, event_pk=None, digest_order=None, na
|
||||
if request.method == "POST":
|
||||
return _handle_post(request, issue)
|
||||
|
||||
event_qs = Event.objects.filter(issue=issue)
|
||||
if request.GET.get("q"):
|
||||
event_qs = _search_events(issue.project, event_qs, request.GET["q"])
|
||||
|
||||
try:
|
||||
event = _get_event(issue, event_pk, digest_order, nav)
|
||||
event = _get_event(event_qs, event_pk, digest_order, nav)
|
||||
except Event.DoesNotExist:
|
||||
return issue_event_404(request, issue, "breadcrumbs", "event_breadcrumbs")
|
||||
return issue_event_404(request, issue, event_qs, "breadcrumbs", "event_breadcrumbs")
|
||||
|
||||
parsed_data = event.get_parsed_data()
|
||||
|
||||
@@ -466,9 +534,12 @@ def issue_event_breadcrumbs(request, issue, event_pk=None, digest_order=None, na
|
||||
"issue": issue,
|
||||
"event": event,
|
||||
"is_event_page": True,
|
||||
"parsed_data": parsed_data,
|
||||
"request_repr": _request_repr(parsed_data),
|
||||
"breadcrumbs": get_values(parsed_data.get("breadcrumbs")),
|
||||
"mute_options": GLOBAL_MUTE_OPTIONS,
|
||||
"q": request.GET.get("q", ""),
|
||||
"event_qs": event_qs,
|
||||
**_has_next_prev(event, event_qs),
|
||||
})
|
||||
|
||||
|
||||
@@ -478,16 +549,28 @@ def _date_with_milis_html(timestamp):
|
||||
'<span class="text-xs">' + date(timestamp, "u")[:3] + '</span>')
|
||||
|
||||
|
||||
def _has_next_prev(event, event_qs):
|
||||
d = event_qs.aggregate(lo=Min("digest_order"), hi=Max("digest_order"))
|
||||
return {
|
||||
"has_prev": event.digest_order > d["lo"] if d.get("lo") is not None else False,
|
||||
"has_next": event.digest_order < d["hi"] if d.get("hi") is not None else False,
|
||||
}
|
||||
|
||||
|
||||
@atomic_for_request_method
|
||||
@issue_membership_required
|
||||
def issue_event_details(request, issue, event_pk=None, digest_order=None, nav=None):
|
||||
if request.method == "POST":
|
||||
return _handle_post(request, issue)
|
||||
|
||||
event_qs = Event.objects.filter(issue=issue)
|
||||
if request.GET.get("q"):
|
||||
event_qs = _search_events(issue.project, event_qs, request.GET["q"])
|
||||
|
||||
try:
|
||||
event = _get_event(issue, event_pk, digest_order, nav)
|
||||
event = _get_event(event_qs, event_pk, digest_order, nav)
|
||||
except Event.DoesNotExist:
|
||||
return issue_event_404(request, issue, "event-details", "event_details")
|
||||
return issue_event_404(request, issue, event_qs, "event-details", "event_details")
|
||||
parsed_data = event.get_parsed_data()
|
||||
|
||||
key_info = [
|
||||
@@ -557,11 +640,15 @@ def issue_event_details(request, issue, event_pk=None, digest_order=None, nav=No
|
||||
"event": event,
|
||||
"is_event_page": True,
|
||||
"parsed_data": parsed_data,
|
||||
"request_repr": _request_repr(parsed_data),
|
||||
"key_info": key_info,
|
||||
"logentry_info": logentry_info,
|
||||
"deployment_info": deployment_info,
|
||||
"contexts": contexts,
|
||||
"mute_options": GLOBAL_MUTE_OPTIONS,
|
||||
"q": request.GET.get("q", ""),
|
||||
"event_qs": event_qs,
|
||||
**_has_next_prev(event, event_qs),
|
||||
})
|
||||
|
||||
|
||||
@@ -571,14 +658,15 @@ def issue_history(request, issue):
|
||||
if request.method == "POST":
|
||||
return _handle_post(request, issue)
|
||||
|
||||
last_event = issue.event_set.order_by("digest_order").last() # the template needs this for the tabs
|
||||
event_qs = _search_events(issue.project, issue.event_set.order_by("digest_order"), request.GET.get("q", ""))
|
||||
last_event = event_qs.last() # used for switching to an event-page (using tabs)
|
||||
return render(request, "issues/history.html", {
|
||||
"tab": "history",
|
||||
"project": issue.project,
|
||||
"issue": issue,
|
||||
"event": last_event,
|
||||
"is_event_page": False,
|
||||
"parsed_data": last_event.get_parsed_data(),
|
||||
"request_repr": _request_repr(last_event.get_parsed_data()) if last_event is not None else "",
|
||||
"mute_options": GLOBAL_MUTE_OPTIONS,
|
||||
})
|
||||
|
||||
@@ -589,14 +677,15 @@ def issue_tags(request, issue):
|
||||
if request.method == "POST":
|
||||
return _handle_post(request, issue)
|
||||
|
||||
last_event = issue.event_set.order_by("digest_order").last() # the template needs this for the tabs
|
||||
event_qs = _search_events(issue.project, issue.event_set.order_by("digest_order"), request.GET.get("q", ""))
|
||||
last_event = event_qs.last() # used for switching to an event-page (using tabs)
|
||||
return render(request, "issues/tags.html", {
|
||||
"tab": "tags",
|
||||
"project": issue.project,
|
||||
"issue": issue,
|
||||
"event": last_event,
|
||||
"is_event_page": False,
|
||||
"parsed_data": last_event.get_parsed_data(),
|
||||
"request_repr": _request_repr(last_event.get_parsed_data()) if last_event is not None else "",
|
||||
"mute_options": GLOBAL_MUTE_OPTIONS,
|
||||
})
|
||||
|
||||
@@ -607,14 +696,15 @@ def issue_grouping(request, issue):
|
||||
if request.method == "POST":
|
||||
return _handle_post(request, issue)
|
||||
|
||||
last_event = issue.event_set.order_by("digest_order").last() # the template needs this for the tabs
|
||||
event_qs = _search_events(issue.project, issue.event_set.order_by("digest_order"), request.GET.get("q", ""))
|
||||
last_event = event_qs.last() # used for switching to an event-page (using tabs)
|
||||
return render(request, "issues/grouping.html", {
|
||||
"tab": "grouping",
|
||||
"project": issue.project,
|
||||
"issue": issue,
|
||||
"event": last_event,
|
||||
"is_event_page": False,
|
||||
"parsed_data": last_event.get_parsed_data(),
|
||||
"request_repr": _request_repr(last_event.get_parsed_data()) if last_event is not None else "",
|
||||
"mute_options": GLOBAL_MUTE_OPTIONS,
|
||||
})
|
||||
|
||||
@@ -627,13 +717,18 @@ def issue_event_list(request, issue):
|
||||
|
||||
event_list = issue.event_set.order_by("digest_order")
|
||||
|
||||
# re 250: in general "big is good" because it allows a lot "at a glance".
|
||||
paginator = KnownCountPaginator(event_list, 250, count=issue.stored_event_count)
|
||||
if "q" in request.GET:
|
||||
event_list = _search_events(issue.project, event_list, request.GET["q"])
|
||||
paginator = Paginator(event_list, 250) # might as well use Paginator; the cost of .count() is incurred anyway
|
||||
else:
|
||||
# re 250: in general "big is good" because it allows a lot "at a glance".
|
||||
paginator = KnownCountPaginator(event_list, 250, count=issue.stored_event_count)
|
||||
|
||||
page_number = request.GET.get("page")
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
last_event = issue.event_set.order_by("digest_order").last() # the template needs this for the tabs
|
||||
last_event = event_list.last() # used for switching to an event-page (using tabs)
|
||||
|
||||
return render(request, "issues/event_list.html", {
|
||||
"tab": "event-list",
|
||||
"project": issue.project,
|
||||
@@ -641,8 +736,9 @@ def issue_event_list(request, issue):
|
||||
"event": last_event,
|
||||
"event_list": event_list,
|
||||
"is_event_page": False,
|
||||
"parsed_data": last_event.get_parsed_data(),
|
||||
"request_repr": _request_repr(last_event.get_parsed_data()) if last_event is not None else "",
|
||||
"mute_options": GLOBAL_MUTE_OPTIONS,
|
||||
"q": request.GET.get("q", ""),
|
||||
"page_obj": page_obj,
|
||||
})
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ register = template.Library()
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def add_to_qs(context, **kwargs):
|
||||
"""add kwargs to query string"""
|
||||
|
||||
if 'request' not in context:
|
||||
# "should not happen", because this tag is only assumed to be used in RequestContext templates, but it's not
|
||||
# something I want to break for. Also: we have an answer that "mostly works" for that case, so let's do that.
|
||||
@@ -15,3 +17,16 @@ def add_to_qs(context, **kwargs):
|
||||
query = copy(context['request'].GET.dict())
|
||||
query.update(kwargs)
|
||||
return urlencode(query)
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def current_qs(context):
|
||||
if 'request' not in context:
|
||||
# "should not happen", because this tag is only assumed to be used in RequestContext templates, but it's not
|
||||
# something I want to break for. Also: we have an answer that "mostly works" for that case, so let's do that.
|
||||
return ""
|
||||
|
||||
query = copy(context['request'].GET.dict())
|
||||
if query:
|
||||
return '?' + urlencode(query)
|
||||
return ""
|
||||
|
||||
Reference in New Issue
Block a user