Skip to content

Commit

Permalink
Add admin panel view for text search cache
Browse files Browse the repository at this point in the history
  • Loading branch information
matti-lamppu committed Sep 2, 2024
1 parent 6320482 commit 871e885
Show file tree
Hide file tree
Showing 11 changed files with 507 additions and 365 deletions.
2 changes: 2 additions & 0 deletions api/graphql/types/allocated_time_slot/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from applications.models import AllocatedTimeSlot
from applications.querysets.allocated_time_slot import AllocatedTimeSlotQuerySet
from common.db import text_search
from common.utils import log_text_search

__all__ = [
"AllocatedTimeSlotFilterSet",
Expand Down Expand Up @@ -65,6 +66,7 @@ def filter_text_search(self, qs: AllocatedTimeSlotQuerySet, name: str, value: st
"applicant",
)
qs = qs.alias(applicant=L("reservation_unit_option__application_section__application__applicant"))
log_text_search(where="allocated_time_slots", text=value)
return text_search(qs=qs, fields=fields, text=value)

@staticmethod
Expand Down
2 changes: 2 additions & 0 deletions api/graphql/types/application/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from applications.models import Application
from applications.querysets.application import ApplicationQuerySet
from common.db import text_search
from common.utils import log_text_search

__all__ = [
"ApplicationFilterSet",
Expand Down Expand Up @@ -40,6 +41,7 @@ class Meta:
def filter_by_text_search(qs: ApplicationQuerySet, name: str, value: str) -> models.QuerySet:
fields = ("id", "application_sections__id", "application_sections__name", "applicant")
qs = qs.alias(applicant=L("applicant"))
log_text_search(where="applications", text=value)
return text_search(qs=qs, fields=fields, text=value)

@staticmethod
Expand Down
2 changes: 2 additions & 0 deletions api/graphql/types/application_section/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from applications.models import ApplicationSection
from applications.querysets.application_section import ApplicationSectionQuerySet
from common.db import text_search
from common.utils import log_text_search

__all__ = [
"ApplicationSectionFilterSet",
Expand Down Expand Up @@ -88,6 +89,7 @@ def filter_include_preferred_order(qs: ApplicationSectionQuerySet, name: str, va
def filter_text_search(qs: ApplicationSectionQuerySet, name: str, value: str) -> QuerySet:
fields = ("application__id", "id", "name", "applicant")
qs = qs.alias(applicant=L("application__applicant"))
log_text_search(where="application_section", text=value)
return text_search(qs=qs, fields=fields, text=value)

def filter_has_allocations(self, queryset: ApplicationSectionQuerySet, name: str, value: bool) -> QuerySet:
Expand Down
2 changes: 2 additions & 0 deletions api/graphql/types/rejected_occurrence/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from lookup_property import L

from common.db import text_search
from common.utils import log_text_search
from reservations.models import RejectedOccurrence
from reservations.querysets import RejectedOccurrenceQuerySet

Expand Down Expand Up @@ -93,6 +94,7 @@ def filter_text_search(qs: RejectedOccurrenceQuerySet, name: str, value: str) ->
"__applicant"
)
qs = qs.alias(applicant=L(applicant_ref))
log_text_search(where="rejected_occurrences", text=value)
return text_search(qs=qs, fields=fields, text=value)

@staticmethod
Expand Down
3 changes: 3 additions & 0 deletions api/graphql/types/reservation/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from api.graphql.extensions.filters import TimezoneAwareDateFilter
from common.db import text_search
from common.utils import log_text_search
from merchants.enums import OrderStatusWithFree
from permissions.helpers import has_general_permission
from permissions.models import GeneralPermissionChoices, UnitPermissionChoices
Expand Down Expand Up @@ -152,6 +153,8 @@ def filter_by_text_search(self, qs: QuerySet, name: str, value: str) -> QuerySet
if not value:
return qs

log_text_search(where="reservations", text=value)

# Shortcut for searching only emails
if EMAIL_DOMAIN_PATTERN.match(value):
return qs.filter(Q(user__email__icontains=value) | Q(reservee_email__icontains=value))
Expand Down
3 changes: 3 additions & 0 deletions api/graphql/types/reservation_unit/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from graphene_django_extensions.filters import EnumMultipleChoiceFilter, IntMultipleChoiceFilter

from common.date_utils import local_datetime
from common.utils import log_text_search
from elastic_django.reservation_units.query_builder import build_elastic_query_str
from permissions.helpers import has_any_general_permission
from permissions.models import GeneralPermissionChoices
Expand Down Expand Up @@ -119,6 +120,8 @@ def get_text_search(qs: ReservationUnitQuerySet, name: str, value: str) -> Query
query_str = build_elastic_query_str(search_words=value)
if not query_str:
return qs

log_text_search(where="reservation_units", text=value)
sq = SearchQuery.do_search("reservation_units", {"query_string": {"query": query_str}})
return qs.from_search_results(sq)

Expand Down
31 changes: 31 additions & 0 deletions common/admin/text_search_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from datetime import datetime

from admin_data_views.typing import TableContext
from admin_data_views.utils import render_with_table_view
from django.conf import settings
from django.core.cache import cache
from django.http import HttpRequest


@render_with_table_view
def text_search_list_view(request: HttpRequest, **kwargs) -> TableContext:
"""Show all text searches in a table view."""
data: dict[str, str] = cache.get_many(cache.keys("text_search:*"))

timestamps: list[str] = []
locations: list[str] = []
searches: list[str] = []
for key, value in data.items():
_, location, timestamp = key.split(":", maxsplit=2)
timestamps.append(datetime.fromisoformat(timestamp).isoformat(timespec="seconds"))
locations.append(location.capitalize().replace("_", " "))
searches.append(value)

return TableContext(
title=f"Text searches in the last {settings.TEXT_SEARCH_CACHE_TIME_DAYS} days",
table={
"Location": locations,
"Search": searches,
"Timestamp": timestamps,
},
)
16 changes: 16 additions & 0 deletions common/utils.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import datetime
import operator
from collections.abc import Generator, Iterable, Sequence
from typing import Any, Generic, Literal, TypeVar

from django.conf import settings
from django.core.cache import cache
from django.db import models
from django.http import HttpRequest
from django.utils import translation
from django.utils.functional import Promise
from django.utils.translation import get_language_from_request
from modeltranslation.manager import get_translatable_fields_for_model

from common.date_utils import local_datetime
from users.models import User

__all__ = [
Expand Down Expand Up @@ -170,3 +173,16 @@ def safe_getattr(obj: object, dotted_path: str, default: Any = None) -> Any:
return operator.attrgetter(dotted_path)(obj)
except AttributeError:
return default


def log_text_search(where: str, text: str) -> None:
"""
Log text search to the cache for the next 4 weeks.
This is used for analysis of the text search performance.
:param where: Where the text search was performed.
:param text: Text search query.
"""
key = f"text_search:{where}:{local_datetime().isoformat()}"
cache_time_seconds = int(datetime.timedelta(days=settings.TEXT_SEARCH_CACHE_TIME_DAYS).total_seconds())
cache.set(key, text, timeout=cache_time_seconds)
Loading

0 comments on commit 871e885

Please sign in to comment.