Skip to content

Commit 1bf51bd

Browse files
authored
Search: use alias to link to search results of subprojects (#7757)
Currently we are showing `(result from {subproject_slug})` instead of the chosen alias. With this now we can show the proper alias. Also, start tracking the doctype of the version (it changes when the version changes so we are good). With this we can have the correct urls for all projects https://github.com/readthedocs/readthedocs.org/blob/1675fd3ea55947ef1de681c87df625ff5bc43abd/readthedocs/search/serializers.py#L113
1 parent 44b2d7d commit 1bf51bd

File tree

8 files changed

+90
-50
lines changed

8 files changed

+90
-50
lines changed

docs/server-side-search.rst

+2
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ This is ``https://docs.readthedocs.io/_/api/v2/search`` for the ``docs`` project
9595
9696
:>json string type: The type of the result, currently page is the only type.
9797
:>json string project: The project slug
98+
:>json string project_alias: Alias of the project if it's a subproject.
9899
:>json string version: The version slug
99100
:>json string title: The title of the page
100101
:>json string domain: Canonical domain of the resulting page
@@ -143,6 +144,7 @@ This is ``https://docs.readthedocs.io/_/api/v2/search`` for the ``docs`` project
143144
{
144145
"type": "page",
145146
"project": "docs",
147+
"project_alias": null,
146148
"version": "latest",
147149
"title": "Server Side Search",
148150
"domain": "https://docs.readthedocs.io",

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ function attach_elastic_search_query_sphinx(data) {
7979

8080
// If the document is from a subproject, add extra information
8181
if (result.project !== project) {
82-
var text = " (from project " + result.project + ")";
82+
var text = " (from project " + result.project_alias + ")";
8383
var extra = $('<span>', {'text': text});
8484
list_item.append(extra);
8585
}
@@ -295,7 +295,7 @@ function attach_elastic_search_query_mkdocs(data) {
295295
);
296296

297297
if (result.project !== project) {
298-
var text = '(from project ' + result.project + ')';
298+
var text = '(from project ' + result.project_alias + ')';
299299
item.append($('<span>', {'text': text}));
300300
}
301301

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

+30-16
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from readthedocs.search import tasks
1616
from readthedocs.search.faceted_search import PageSearch
1717

18-
from .serializers import PageSearchSerializer, VersionData
18+
from .serializers import PageSearchSerializer, ProjectData, VersionData
1919

2020
log = logging.getLogger(__name__)
2121

@@ -183,15 +183,21 @@ def _get_all_projects_data(self):
183183
.. code::
184184
185185
{
186-
"requests": VersionData(
187-
"latest",
188-
"sphinx",
189-
"https://requests.readthedocs.io/en/latest/",
186+
"requests": ProjectData(
187+
alias='alias',
188+
version=VersionData(
189+
"latest",
190+
"sphinx",
191+
"https://requests.readthedocs.io/en/latest/",
192+
),
190193
),
191-
"requests-oauth": VersionData(
192-
"latest",
193-
"sphinx_htmldir",
194-
"https://requests-oauth.readthedocs.io/en/latest/",
194+
"requests-oauth": ProjectData(
195+
alias=None,
196+
version=VersionData(
197+
"latest",
198+
"sphinx_htmldir",
199+
"https://requests-oauth.readthedocs.io/en/latest/",
200+
),
195201
),
196202
}
197203
@@ -203,10 +209,13 @@ def _get_all_projects_data(self):
203209
main_project = self._get_project()
204210

205211
projects_data = {
206-
main_project.slug: VersionData(
207-
slug=main_version.slug,
208-
doctype=main_version.documentation_type,
209-
docs_url=main_project.get_docs_url(version_slug=main_version.slug),
212+
main_project.slug: ProjectData(
213+
alias=None,
214+
version=VersionData(
215+
slug=main_version.slug,
216+
doctype=main_version.documentation_type,
217+
docs_url=main_project.get_docs_url(version_slug=main_version.slug),
218+
),
210219
)
211220
}
212221

@@ -232,11 +241,16 @@ def _get_all_projects_data(self):
232241

233242
if version and self._has_permission(self.request.user, version):
234243
url = subproject.get_docs_url(version_slug=version.slug)
235-
projects_data[subproject.slug] = VersionData(
244+
project_alias = subproject.superprojects.values_list('alias', flat=True).first()
245+
version_data = VersionData(
236246
slug=version.slug,
237247
doctype=version.documentation_type,
238248
docs_url=url,
239249
)
250+
projects_data[subproject.slug] = ProjectData(
251+
alias=project_alias,
252+
version=version_data,
253+
)
240254

241255
return projects_data
242256

@@ -299,8 +313,8 @@ def get_queryset(self):
299313

300314
if main_project.has_feature(Feature.SEARCH_SUBPROJECTS_ON_DEFAULT_VERSION):
301315
projects = {
302-
project: version.slug
303-
for project, version in self._get_all_projects_data().items()
316+
project: project_data.version.slug
317+
for project, project_data in self._get_all_projects_data().items()
304318
}
305319
# Check to avoid searching all projects in case it's empty.
306320
if not projects:

readthedocs/search/documents.py

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class PageDocument(RTDDocTypeMixin, Document):
6969
# Metadata
7070
project = fields.KeywordField(attr='project.slug')
7171
version = fields.KeywordField(attr='version.slug')
72+
doctype = fields.KeywordField(attr='version.documentation_type')
7273
path = fields.KeywordField(attr='processed_json.path')
7374
full_path = fields.KeywordField(attr='path')
7475
rank = fields.IntegerField()

readthedocs/search/serializers.py

+43-27
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222

2323
# Structure used for storing cached data of a version mostly.
24+
ProjectData = namedtuple('ProjectData', ['version', 'alias'])
2425
VersionData = namedtuple('VersionData', ['slug', 'docs_url', 'doctype'])
2526

2627

@@ -58,13 +59,51 @@ class PageSearchSerializer(serializers.Serializer):
5859

5960
type = serializers.CharField(default='page', source=None, read_only=True)
6061
project = serializers.CharField()
62+
project_alias = serializers.SerializerMethodField()
6163
version = serializers.CharField()
6264
title = serializers.CharField()
6365
path = serializers.SerializerMethodField()
6466
domain = serializers.SerializerMethodField()
6567
highlights = PageHighlightSerializer(source='meta.highlight', default=dict)
6668
blocks = serializers.SerializerMethodField()
6769

70+
def _get_project_data(self, obj):
71+
"""
72+
Get and cache the project data.
73+
74+
Try to get the data from the ``projects_data`` context,
75+
and fallback to get it from the database.
76+
If the result is fetched from the database,
77+
it's cached into ``projects_data``.
78+
"""
79+
project_data = self.context.get('projects_data', {}).get(obj.project)
80+
if project_data:
81+
return project_data
82+
83+
project = Project.objects.filter(slug=obj.project).first()
84+
if project:
85+
docs_url = project.get_docs_url(version_slug=obj.version)
86+
project_alias = project.superprojects.values_list('alias', flat=True).first()
87+
88+
projects_data = self.context.setdefault('projects_data', {})
89+
version_data = VersionData(
90+
slug=obj.version,
91+
docs_url=docs_url,
92+
doctype=None,
93+
)
94+
projects_data[obj.project] = ProjectData(
95+
alias=project_alias,
96+
version=version_data,
97+
)
98+
return projects_data[obj.project]
99+
return None
100+
101+
def get_project_alias(self, obj):
102+
project_data = self._get_project_data(obj)
103+
if project_data:
104+
return project_data.alias
105+
return None
106+
68107
def get_domain(self, obj):
69108
full_path = self._get_full_path(obj)
70109
if full_path:
@@ -80,40 +119,17 @@ def get_path(self, obj):
80119
return None
81120

82121
def _get_full_path(self, obj):
83-
"""
84-
Get the page link.
85-
86-
Try to get the link from the ``project_data`` context,
87-
and fallback to get it from the database.
88-
If the result is fetched from the database,
89-
it's cached into ``project_data``.
90-
"""
91-
# First try to build the URL from the context.
92-
version_data = self.context.get('projects_data', {}).get(obj.project)
93-
if version_data:
94-
docs_url = version_data.docs_url
122+
project_data = self._get_project_data(obj)
123+
if project_data:
124+
docs_url = project_data.version.docs_url
95125
path = obj.full_path
96126

97127
# Generate an appropriate link for the doctypes that use htmldir,
98128
# and always end it with / so it goes directly to proxito.
99-
if version_data.doctype in {SPHINX_HTMLDIR, MKDOCS}:
129+
if project_data.version.doctype in {SPHINX_HTMLDIR, MKDOCS}:
100130
path = re.sub('(^|/)index.html$', '/', path)
101131

102132
return docs_url.rstrip('/') + '/' + path.lstrip('/')
103-
104-
# Fallback to build the URL querying the db.
105-
project = Project.objects.filter(slug=obj.project).first()
106-
if project:
107-
docs_url = project.get_docs_url(version_slug=obj.version)
108-
# cache the project URL
109-
projects_data = self.context.setdefault('projects_data', {})
110-
projects_data[obj.project] = VersionData(
111-
slug=obj.version,
112-
docs_url=docs_url,
113-
doctype=None,
114-
)
115-
return docs_url + obj.full_path
116-
117133
return None
118134

119135
def get_blocks(self, obj):

readthedocs/search/tests/test_api.py

+2
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ def test_doc_search_filter_by_version(self, api_client, project):
182182
data = resp.data['results']
183183
assert len(data) == 1
184184
assert data[0]['project'] == project.slug
185+
assert data[0]['project_alias'] is None
185186

186187
def test_doc_search_pagination(self, api_client, project):
187188
"""Test Doc search result can be paginated"""
@@ -264,6 +265,7 @@ def test_doc_search_subprojects(self, api_client, all_projects):
264265
# First result should be the subproject
265266
first_result = data[0]
266267
assert first_result['project'] == subproject.slug
268+
assert first_result['project_alias'] == subproject.slug
267269
# The result is from the same version as the main project.
268270
assert first_result['version'] == version.slug
269271
# Check the link is the subproject document link

readthedocs/search/views.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from .serializers import (
1717
PageSearchSerializer,
18+
ProjectData,
1819
ProjectSearchSerializer,
1920
VersionData,
2021
)
@@ -62,11 +63,15 @@ def _get_project_data(self, project, version_slug):
6263
.get(slug=version_slug)
6364
)
6465
docs_url = project.get_docs_url(version_slug=version_slug)
66+
version_data = VersionData(
67+
slug=version_slug,
68+
docs_url=docs_url,
69+
doctype=version_doctype,
70+
)
6571
project_data = {
66-
project.slug: VersionData(
67-
slug=version_slug,
68-
docs_url=docs_url,
69-
doctype=version_doctype,
72+
project.slug: ProjectData(
73+
alias=None,
74+
version=version_data,
7075
)
7176
}
7277
return project_data

0 commit comments

Comments
 (0)