Skip to content

Commit bd2f511

Browse files
authored
Merge pull request #7070 from readthedocs/generate-full-link
Search: generate full link from the server side
2 parents 9e7be2b + dca39bc commit bd2f511

File tree

8 files changed

+202
-32
lines changed

8 files changed

+202
-32
lines changed

readthedocs/core/static-src/core/js/doc-embed/search.js

+1-8
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,7 @@ function attach_elastic_search_query(data) {
7474
}
7575
}
7676

77-
// Creating the result from elements
78-
var suffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX;
79-
// Since sphinx 2.2.1 FILE_SUFFIX is .html for all builders,
80-
// and there is a new BUILDER option.
81-
if ('BUILDER' in DOCUMENTATION_OPTIONS && DOCUMENTATION_OPTIONS.BUILDER === 'readthedocsdirhtml') {
82-
suffix = '';
83-
}
84-
var link = doc.link + suffix + "?highlight=" + $.urlencode(query);
77+
var link = doc.link + "?highlight=" + $.urlencode(query);
8578

8679
var item = $('<a>', {'href': link});
8780

readthedocs/core/static/core/js/readthedocs-doc-embed.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

readthedocs/search/api.py

+47-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import itertools
22
import logging
3+
import re
34

45
from django.shortcuts import get_object_or_404
56
from django.utils import timezone
@@ -9,6 +10,7 @@
910

1011
from readthedocs.api.v2.permissions import IsAuthorizedToViewVersion
1112
from readthedocs.builds.models import Version
13+
from readthedocs.projects.constants import MKDOCS, SPHINX_HTMLDIR
1214
from readthedocs.projects.models import HTMLFile, Project
1315
from readthedocs.search import tasks, utils
1416
from readthedocs.search.faceted_search import PageSearch
@@ -27,15 +29,28 @@ class PageSearchSerializer(serializers.Serializer):
2729
version = serializers.CharField()
2830
title = serializers.CharField()
2931
path = serializers.CharField()
32+
full_path = serializers.CharField()
3033
link = serializers.SerializerMethodField()
3134
highlight = serializers.SerializerMethodField()
3235
inner_hits = serializers.SerializerMethodField()
3336

3437
def get_link(self, obj):
35-
projects_url = self.context.get('projects_url')
36-
if projects_url:
37-
docs_url = projects_url[obj.project]
38-
return docs_url + obj.path
38+
project_data = self.context['projects_data'].get(obj.project)
39+
if not project_data:
40+
return None
41+
42+
docs_url, doctype = project_data
43+
path = obj.full_path
44+
45+
# Generate an appropriate link for the doctypes that use htmldir,
46+
# and always end it with / so it goes directly to proxito.
47+
if doctype in {SPHINX_HTMLDIR, MKDOCS}:
48+
new_path = re.sub('(^|/)index.html$', '/', path)
49+
# docs_url already ends with /,
50+
# so path doesn't need to start with /.
51+
path = new_path.lstrip('/')
52+
53+
return docs_url + path
3954

4055
def get_highlight(self, obj):
4156
highlight = getattr(obj.meta, 'highlight', None)
@@ -160,7 +175,7 @@ def validate_query_params(self):
160175

161176
def get_serializer_context(self):
162177
context = super().get_serializer_context()
163-
context['projects_url'] = self.get_all_projects_url()
178+
context['projects_data'] = self.get_all_projects_data()
164179
return context
165180

166181
def get_all_projects(self):
@@ -188,29 +203,44 @@ def get_all_projects(self):
188203
all_projects.append(version.project)
189204
return all_projects
190205

191-
def get_all_projects_url(self):
206+
def get_all_projects_data(self):
192207
"""
193-
Return a dict containing the project slug and its version URL.
194-
195-
The dictionary contains the project and its subprojects . Each project's
196-
slug is used as a key and the documentation URL for that project and
197-
version as the value.
208+
Return a dict containing the project slug and its version URL and version's doctype.
198209
199-
Example:
210+
The dictionary contains the project and its subprojects. Each project's
211+
slug is used as a key and a tuple with the documentation URL and doctype
212+
from the version. Example:
200213
201214
{
202-
"requests": "https://requests.readthedocs.io/en/latest/",
203-
"requests-oauth": "https://requests-oauth.readthedocs.io/en/latest/",
215+
"requests": (
216+
"https://requests.readthedocs.io/en/latest/",
217+
"sphinx",
218+
),
219+
"requests-oauth": (
220+
"https://requests-oauth.readthedocs.io/en/latest/",
221+
"sphinx_htmldir",
222+
),
204223
}
205224
206225
:rtype: dict
207226
"""
208227
all_projects = self.get_all_projects()
209228
version_slug = self._get_version().slug
210-
projects_url = {}
229+
project_urls = {}
211230
for project in all_projects:
212-
projects_url[project.slug] = project.get_docs_url(version_slug=version_slug)
213-
return projects_url
231+
project_urls[project.slug] = project.get_docs_url(version_slug=version_slug)
232+
233+
versions_doctype = (
234+
Version.objects
235+
.filter(project__slug__in=project_urls.keys(), slug=version_slug)
236+
.values_list('project__slug', 'documentation_type')
237+
)
238+
239+
projects_data = {
240+
project_slug: (project_urls[project_slug], doctype)
241+
for project_slug, doctype in versions_doctype
242+
}
243+
return projects_data
214244

215245
def list(self, request, *args, **kwargs):
216246
"""Overriding ``list`` method to record query in database."""

readthedocs/search/tests/conftest.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import pytest
66
from django.core.management import call_command
7-
from django_dynamic_fixture import G
7+
from django_dynamic_fixture import get
88

99
from readthedocs.projects.constants import PUBLIC
1010
from readthedocs.projects.models import HTMLFile, Project
@@ -28,7 +28,7 @@ def all_projects(es_index, mock_processed_json, db, settings):
2828
settings.ELASTICSEARCH_DSL_AUTOSYNC = True
2929
projects_list = []
3030
for project_slug in ALL_PROJECTS:
31-
project = G(
31+
project = get(
3232
Project,
3333
slug=project_slug,
3434
name=project_slug,
@@ -41,7 +41,13 @@ def all_projects(es_index, mock_processed_json, db, settings):
4141
# file_basename in config are without extension so add html extension
4242
file_name = file_basename + '.html'
4343
version = project.versions.all()[0]
44-
html_file = G(HTMLFile, project=project, version=version, name=file_name)
44+
html_file = get(
45+
HTMLFile,
46+
project=project,
47+
version=version,
48+
name=file_name,
49+
path=file_name,
50+
)
4551

4652
# creating sphinx domain test objects
4753
file_path = get_json_file_path(project.slug, file_basename)
@@ -54,7 +60,7 @@ def all_projects(es_index, mock_processed_json, db, settings):
5460
domain_role_name = domain_data.pop('role_name')
5561
domain, type_ = domain_role_name.split(':')
5662

57-
G(
63+
get(
5864
SphinxDomain,
5965
project=project,
6066
version=version,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"path": "guides/index",
3+
"title": "Guides",
4+
"sections": [
5+
{
6+
"id": "guides",
7+
"title": "Guides",
8+
"content": "Content from guides/index"
9+
}
10+
],
11+
"domains": [],
12+
"domain_data": {}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"path": "index",
3+
"title": "Index",
4+
"sections": [
5+
{
6+
"id": "title",
7+
"title": "Title",
8+
"content": "Some content from index"
9+
}
10+
],
11+
"domains": [],
12+
"domain_data": {}
13+
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PROJECT_DATA_FILES = {
22
'pipeline': ['installation', 'signals'],
33
'kuma': ['documentation', 'docker'],
4-
'docs': ['support', 'wiping'],
4+
'docs': ['support', 'wiping', 'index', 'guides/index'],
55
}
66

77
ALL_PROJECTS = PROJECT_DATA_FILES.keys()

readthedocs/search/tests/test_api.py

+116-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66
from django_dynamic_fixture import G
77

88
from readthedocs.builds.models import Version
9-
from readthedocs.projects.constants import PUBLIC
9+
from readthedocs.projects.constants import (
10+
MKDOCS,
11+
MKDOCS_HTML,
12+
PUBLIC,
13+
SPHINX,
14+
SPHINX_HTMLDIR,
15+
SPHINX_SINGLEHTML,
16+
)
1017
from readthedocs.projects.models import HTMLFile, Project
1118
from readthedocs.search.api import PageSearchAPIView
1219
from readthedocs.search.documents import PageDocument
@@ -324,6 +331,114 @@ def test_doc_search_hidden_versions(self, api_client, all_projects):
324331
first_result = data[0]
325332
assert first_result['project'] == subproject.slug
326333

334+
@pytest.mark.parametrize('doctype', [SPHINX, SPHINX_SINGLEHTML, MKDOCS_HTML])
335+
def test_search_correct_link_for_normal_page_html_projects(self, api_client, doctype):
336+
project = Project.objects.get(slug='docs')
337+
project.versions.update(documentation_type=doctype)
338+
version = project.versions.all().first()
339+
340+
search_params = {
341+
'project': project.slug,
342+
'version': version.slug,
343+
'q': 'Support',
344+
}
345+
resp = self.get_search(api_client, search_params)
346+
assert resp.status_code == 200
347+
348+
result = resp.data['results'][0]
349+
assert result['project'] == project.slug
350+
assert result['link'].endswith('en/latest/support.html')
351+
352+
@pytest.mark.parametrize('doctype', [SPHINX, SPHINX_SINGLEHTML, MKDOCS_HTML])
353+
def test_search_correct_link_for_index_page_html_projects(self, api_client, doctype):
354+
project = Project.objects.get(slug='docs')
355+
project.versions.update(documentation_type=doctype)
356+
version = project.versions.all().first()
357+
358+
search_params = {
359+
'project': project.slug,
360+
'version': version.slug,
361+
'q': 'Some content from index',
362+
}
363+
resp = self.get_search(api_client, search_params)
364+
assert resp.status_code == 200
365+
366+
result = resp.data['results'][0]
367+
assert result['project'] == project.slug
368+
assert result['link'].endswith('en/latest/index.html')
369+
370+
@pytest.mark.parametrize('doctype', [SPHINX, SPHINX_SINGLEHTML, MKDOCS_HTML])
371+
def test_search_correct_link_for_index_page_subdirectory_html_projects(self, api_client, doctype):
372+
project = Project.objects.get(slug='docs')
373+
project.versions.update(documentation_type=doctype)
374+
version = project.versions.all().first()
375+
376+
search_params = {
377+
'project': project.slug,
378+
'version': version.slug,
379+
'q': 'Some content from guides/index',
380+
}
381+
resp = self.get_search(api_client, search_params)
382+
assert resp.status_code == 200
383+
384+
result = resp.data['results'][0]
385+
assert result['project'] == project.slug
386+
assert result['link'].endswith('en/latest/guides/index.html')
387+
388+
@pytest.mark.parametrize('doctype', [SPHINX_HTMLDIR, MKDOCS])
389+
def test_search_correct_link_for_normal_page_htmldir_projects(self, api_client, doctype):
390+
project = Project.objects.get(slug='docs')
391+
project.versions.update(documentation_type=doctype)
392+
version = project.versions.all().first()
393+
394+
search_params = {
395+
'project': project.slug,
396+
'version': version.slug,
397+
'q': 'Support',
398+
}
399+
resp = self.get_search(api_client, search_params)
400+
assert resp.status_code == 200
401+
402+
result = resp.data['results'][0]
403+
assert result['project'] == project.slug
404+
assert result['link'].endswith('en/latest/support.html')
405+
406+
@pytest.mark.parametrize('doctype', [SPHINX_HTMLDIR, MKDOCS])
407+
def test_search_correct_link_for_index_page_htmldir_projects(self, api_client, doctype):
408+
project = Project.objects.get(slug='docs')
409+
project.versions.update(documentation_type=doctype)
410+
version = project.versions.all().first()
411+
412+
search_params = {
413+
'project': project.slug,
414+
'version': version.slug,
415+
'q': 'Some content from index',
416+
}
417+
resp = self.get_search(api_client, search_params)
418+
assert resp.status_code == 200
419+
420+
result = resp.data['results'][0]
421+
assert result['project'] == project.slug
422+
assert result['link'].endswith('en/latest/')
423+
424+
@pytest.mark.parametrize('doctype', [SPHINX_HTMLDIR, MKDOCS])
425+
def test_search_correct_link_for_index_page_subdirectory_htmldir_projects(self, api_client, doctype):
426+
project = Project.objects.get(slug='docs')
427+
project.versions.update(documentation_type=doctype)
428+
version = project.versions.all().first()
429+
430+
search_params = {
431+
'project': project.slug,
432+
'version': version.slug,
433+
'q': 'Some content from guides/index',
434+
}
435+
resp = self.get_search(api_client, search_params)
436+
assert resp.status_code == 200
437+
438+
result = resp.data['results'][0]
439+
assert result['project'] == project.slug
440+
assert result['link'].endswith('en/latest/guides/')
441+
327442

328443
class TestDocumentSearch(BaseTestDocumentSearch):
329444

0 commit comments

Comments
 (0)