mirror of
https://github.com/jlengrand/bugsink.git
synced 2026-03-10 08:01:17 +00:00
Issue Paginator: don't attempt to count the Issues
Counting incurs looking at all records which is too expensive if you have e.g. 1_000_000 issues. Note that we take a different approach than the one for Events (where we count-with-timeout). Reason for switching: https://sqlite.org/forum/forumpost/fa65709226 For Events we have a known count for the non-query case (denormalized/counted value), so we preserve what we had there. For Issues the trouble of keeping counts right for muted/etc. is not (currently) worth it.
This commit is contained in:
@@ -200,22 +200,22 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-6 h-6 text-slate-200"><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>
|
||||
{% endif %}
|
||||
|
||||
{% if page_obj.paginator.num_pages > 1 %}
|
||||
Issues {{ page_obj.start_index|intcomma }}–{{ page_obj.end_index|intcomma }} of {{ page_obj.paginator.count|intcomma }}
|
||||
{% elif page_obj.paginator.count > 0 %}
|
||||
{{ page_obj.paginator.count|intcomma }} Issues
|
||||
{% if page_obj.object_list|length > 0 %}
|
||||
Issues {{ page_obj.start_index|intcomma }} – {{ page_obj.end_index|intcomma }}
|
||||
{% else %}
|
||||
{% if page_obj.number > 1 %}
|
||||
Less than {{ page_obj.start_index }} Issues {# corresponds to the 1/250 case of having an exactly full page and navigating to an empty page after that #}
|
||||
{% else %}
|
||||
0 Issues
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<a href="?{% add_to_qs page=page_obj.next_page_number %}" class="inline-flex" 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>
|
||||
<a href="?{% add_to_qs page=page_obj.paginator.num_pages %}" class="inline-flex" 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 %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-6 h-6 text-slate-200"><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>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-6 h-6 text-slate-200"><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>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from django.http import Http404
|
||||
from django.core.paginator import Paginator, Page
|
||||
from django.db.utils import OperationalError
|
||||
from django.conf import settings
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from sentry.utils.safe import get_path
|
||||
from sentry_sdk_extensions import capture_or_log_exception
|
||||
@@ -87,6 +88,35 @@ class KnownCountPaginator(EagerPaginator):
|
||||
return self._count
|
||||
|
||||
|
||||
class UncountablePage(Page):
|
||||
"""The Page subclass to be used with UncountablePaginator."""
|
||||
|
||||
@cached_property
|
||||
def has_next(self):
|
||||
# hack that works 249/250 times: if the current page is full, we have a next page
|
||||
return len(self.object_list) == self.paginator.per_page
|
||||
|
||||
@cached_property
|
||||
def end_index(self):
|
||||
return (self.paginator.per_page * (self.number - 1)) + len(self.object_list)
|
||||
|
||||
|
||||
class UncountablePaginator(EagerPaginator):
|
||||
"""optimization: counting is too expensive; to be used in a template w/o .count and .last"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _get_page(self, *args, **kwargs):
|
||||
object_list = args[0]
|
||||
object_list = list(object_list)
|
||||
return UncountablePage(object_list, *(args[1:]), **kwargs)
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return 1_000_000_000 # big enough to be bigger than what you can click through or store in the DB.
|
||||
|
||||
|
||||
def _request_repr(parsed_data):
|
||||
if "request" not in parsed_data:
|
||||
return ""
|
||||
@@ -268,7 +298,7 @@ def _issue_list_pt_2(request, project, state_filter, unapplied_issue_ids):
|
||||
if request.GET.get("q"):
|
||||
issue_list = search_issues(project, issue_list, request.GET["q"])
|
||||
|
||||
paginator = EagerPaginator(issue_list, 250)
|
||||
paginator = UncountablePaginator(issue_list, 250)
|
||||
page_number = request.GET.get("page")
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user