Skip to content

Commit a1f3b05

Browse files
authored
Merge pull request #6088 from dojutsu-user/recording-query-smartly
Record search queries smartly
2 parents 887bbe4 + 294f6bd commit a1f3b05

File tree

3 files changed

+92
-10
lines changed

3 files changed

+92
-10
lines changed

readthedocs/search/api.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import itertools
22
import logging
33

4+
from django.utils import timezone
45
from rest_framework import generics, serializers
56
from rest_framework.exceptions import ValidationError
67
from rest_framework.pagination import PageNumberPagination
@@ -160,15 +161,19 @@ def list(self, request, *args, **kwargs):
160161

161162
project_slug = self.request.query_params.get('project', None)
162163
version_slug = self.request.query_params.get('version', None)
163-
query = self.request.query_params.get('q', '')
164164
total_results = response.data.get('count', 0)
165+
time = timezone.now()
166+
167+
query = self.request.query_params.get('q', '')
168+
query = query.lower().strip()
165169

166170
# record the search query with a celery task
167171
tasks.record_search_query.delay(
168172
project_slug,
169173
version_slug,
170174
query,
171175
total_results,
176+
time,
172177
)
173178

174179
return response

readthedocs/search/tasks.py

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -142,20 +142,45 @@ def delete_old_search_queries_from_db():
142142

143143

144144
@app.task(queue='web')
145-
def record_search_query(project_slug, version_slug, query, total_results):
146-
"""Record search query in database."""
147-
if not project_slug or not version_slug or not query or not total_results:
145+
def record_search_query(project_slug, version_slug, query, total_results, time):
146+
"""Record/update search query in database."""
147+
if not project_slug or not version_slug or not query:
148148
log.debug(
149149
'Not recording the search query. Passed arguments: '
150-
'project_slug: %s, version_slug: %s, query: %s, total_results: %s' % (
151-
project_slug, version_slug, query, total_results
150+
'project_slug: %s, version_slug: %s, query: %s, total_results: %s, time: %s' % (
151+
project_slug, version_slug, query, total_results, time
152152
)
153153
)
154154
return
155155

156-
project_qs = Project.objects.filter(slug=project_slug)
156+
before_10_sec = time - timezone.timedelta(seconds=10)
157+
partial_query_qs = SearchQuery.objects.filter(
158+
project__slug=project_slug,
159+
version__slug=version_slug,
160+
created__gte=before_10_sec,
161+
).order_by('-created')
162+
163+
# check if partial query exists,
164+
# if yes, then just update the object.
165+
for partial_query in partial_query_qs.iterator():
166+
if query.startswith(partial_query.query):
167+
partial_query.created = time
168+
partial_query.query = query
169+
partial_query.save()
170+
return
171+
172+
# don't record query with zero results.
173+
if not total_results:
174+
log.debug(
175+
'Not recording search query because of zero results. Passed arguments: '
176+
'project_slug: %s, version_slug: %s, query: %s, total_results: %s, time: %s' % (
177+
project_slug, version_slug, query, total_results, time
178+
)
179+
)
180+
return
157181

158-
if not project_qs.exists():
182+
project = Project.objects.filter(slug=project_slug).first()
183+
if not project:
159184
log.debug(
160185
'Not recording the search query because project does not exist. '
161186
'project_slug: %s' % (
@@ -164,15 +189,24 @@ def record_search_query(project_slug, version_slug, query, total_results):
164189
)
165190
return
166191

167-
project = project_qs.first()
168192
version_qs = Version.objects.filter(project=project, slug=version_slug)
169193

170194
if not version_qs.exists():
195+
log.debug(
196+
'Not recording the search query because version does not exist. '
197+
'project_slug: %s, version_slug: %s' % (
198+
project_slug, version_slug
199+
)
200+
)
171201
return
172202

173203
version = version_qs.first()
174-
SearchQuery.objects.create(
204+
205+
# make a new SearchQuery object.
206+
obj = SearchQuery.objects.create(
175207
project=project,
176208
version=version,
177209
query=query,
178210
)
211+
obj.created = time
212+
obj.save()

readthedocs/search/tests/test_search_tasks.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Tests for search tasks."""
22

3+
import mock
34
import pytest
45

56
from django.urls import reverse
@@ -43,6 +44,48 @@ def test_search_query_recorded_when_results_not_zero(self, api_client):
4344
SearchQuery.objects.all().count() == 1
4445
), 'there should be 1 obj since a search is made which returns one result.'
4546

47+
def test_partial_queries_are_not_recorded(self, api_client):
48+
"""Test if partial queries are not recorded."""
49+
50+
assert (
51+
SearchQuery.objects.all().count() == 0
52+
), 'no SearchQuery should be present if there is no search made.'
53+
54+
time = timezone.now()
55+
search_params = { 'q': 'stack', 'project': 'docs', 'version': 'latest' }
56+
57+
with mock.patch('django.utils.timezone.now') as test_time:
58+
test_time.return_value = time
59+
resp = api_client.get(self.url, search_params)
60+
assert resp.status_code, 200
61+
62+
assert (
63+
SearchQuery.objects.all().count() == 1
64+
), 'one SearchQuery should be present'
65+
66+
# update the time and the search query and make another search request
67+
time = time + timezone.timedelta(seconds=2)
68+
search_params['q'] = 'stack over'
69+
with mock.patch('django.utils.timezone.now') as test_time:
70+
test_time.return_value = time
71+
resp = api_client.get(self.url, search_params)
72+
assert resp.status_code, 200
73+
74+
# update the time and the search query and make another search request
75+
time = time + timezone.timedelta(seconds=2)
76+
search_params['q'] = 'stack overflow'
77+
with mock.patch('django.utils.timezone.now') as test_time:
78+
test_time.return_value = time
79+
resp = api_client.get(self.url, search_params)
80+
assert resp.status_code, 200
81+
82+
assert (
83+
SearchQuery.objects.all().count() == 1
84+
), 'one SearchQuery should be present'
85+
assert (
86+
SearchQuery.objects.all().first().query == 'stack overflow'
87+
), 'one SearchQuery should be there because partial queries gets updated'
88+
4689
def test_search_query_not_recorded_when_results_are_zero(self, api_client):
4790
"""Test that search queries are not recorded when they have zero results."""
4891

0 commit comments

Comments
 (0)