Skip to content

Commit c794ffa

Browse files
authored
Version: new field addons (#10337)
* Version: new field `addons` This `addons` field will be used to tell El Proxito this version was built using `build.commands`. Then, El Proxito will use this field to inject the HTTP header that tells Cloudflare that it should inject the new JS client. This commit is a replacement of #10219 to avoid the time-consuming query that checks the config file of the latest build for the version. Closes #10331 * Proxito: improve query - return only the field we need ("addons") - only check for `version.addons` if the project doesn't have the feature * Tests: add test for El Proxito header
1 parent 38c1fcc commit c794ffa

File tree

8 files changed

+78
-0
lines changed

8 files changed

+78
-0
lines changed

readthedocs/api/v2/serializers.py

+2
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ class VersionAdminSerializer(VersionSerializer):
128128
project = ProjectAdminSerializer()
129129
canonical_url = serializers.SerializerMethodField()
130130
build_data = serializers.JSONField(required=False, write_only=True, allow_null=True)
131+
addons = serializers.BooleanField(required=False, write_only=True, allow_null=False)
131132

132133
def get_canonical_url(self, obj):
133134
return obj.project.get_docs_url(
@@ -138,6 +139,7 @@ def get_canonical_url(self, obj):
138139

139140
class Meta(VersionSerializer.Meta):
140141
fields = VersionSerializer.Meta.fields + [
142+
"addons",
141143
"build_data",
142144
"canonical_url",
143145
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 3.2.19 on 2023-05-23 07:07
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("builds", "0050_build_readthedocs_yaml_path"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="version",
15+
name="addons",
16+
field=models.BooleanField(
17+
blank=True,
18+
default=False,
19+
null=True,
20+
verbose_name="Inject new addons js library for this version",
21+
),
22+
),
23+
]

readthedocs/builds/models.py

+16
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,11 @@ class Version(TimeStampedModel):
145145
populate_from='verbose_name',
146146
)
147147

148+
# TODO: this field (`supported`) could be removed. It's returned only on
149+
# the footer API response but I don't think anybody is using this field at
150+
# all.
148151
supported = models.BooleanField(_('Supported'), default=True)
152+
149153
active = models.BooleanField(_('Active'), default=False)
150154
state = models.CharField(
151155
_("State"),
@@ -156,7 +160,12 @@ class Version(TimeStampedModel):
156160
help_text=_("State of the PR/MR associated to this version."),
157161
)
158162
built = models.BooleanField(_("Built"), default=False)
163+
164+
# TODO: this field (`uploaded`) could be removed. It was used to mark a
165+
# version as "Manually uploaded" by the core team, but this is not required
166+
# anymore. Users can use `build.commands` for these cases now.
159167
uploaded = models.BooleanField(_("Uploaded"), default=False)
168+
160169
privacy_level = models.CharField(
161170
_('Privacy Level'),
162171
max_length=20,
@@ -192,6 +201,13 @@ class Version(TimeStampedModel):
192201
null=True,
193202
)
194203

204+
addons = models.BooleanField(
205+
_("Inject new addons js library for this version"),
206+
null=True,
207+
blank=True,
208+
default=False,
209+
)
210+
195211
objects = VersionManager.from_queryset(VersionQuerySet)()
196212
# Only include BRANCH, TAG, UNKNOWN type Versions.
197213
internal = InternalVersionManager.from_queryset(partial(VersionQuerySet, internal_only=True))()

readthedocs/doc_builder/director.py

+7
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ def __init__(self, data):
5959
"""
6060
self.data = data
6161

62+
# Reset `addons` field. It will be set to `True` only when it's built via `build.commands`
63+
self.data.version.addons = False
64+
6265
def setup_vcs(self):
6366
"""
6467
Perform all VCS related steps.
@@ -422,6 +425,10 @@ def run_build_commands(self):
422425
# Update the `Version.documentation_type` to match the doctype defined
423426
# by the config file. When using `build.commands` it will be `GENERIC`
424427
self.data.version.documentation_type = self.data.config.doctype
428+
429+
# Mark this version to inject the new js client when serving it via El Proxito
430+
self.data.version.addons = True
431+
425432
self.store_readthedocs_build_yaml()
426433

427434
def install_build_tools(self):

readthedocs/projects/tasks/builds.py

+1
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,7 @@ def on_success(self, retval, task_id, args, kwargs):
600600
"has_epub": "epub" in valid_artifacts,
601601
"has_htmlzip": "htmlzip" in valid_artifacts,
602602
"build_data": self.data.version.build_data,
603+
"addons": self.data.version.addons,
603604
}
604605
)
605606
except HttpClientError:

readthedocs/projects/tests/test_build_tasks.py

+2
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ def test_build_updates_documentation_type(self, load_yaml_config):
299299
assert self.requests_mock.request_history[7]._request.method == "PATCH"
300300
assert self.requests_mock.request_history[7].path == "/api/v2/version/1/"
301301
assert self.requests_mock.request_history[7].json() == {
302+
"addons": False,
302303
"build_data": None,
303304
"built": True,
304305
"documentation_type": "mkdocs",
@@ -552,6 +553,7 @@ def test_successful_build(
552553
assert self.requests_mock.request_history[7]._request.method == "PATCH"
553554
assert self.requests_mock.request_history[7].path == "/api/v2/version/1/"
554555
assert self.requests_mock.request_history[7].json() == {
556+
"addons": False,
555557
"build_data": None,
556558
"built": True,
557559
"documentation_type": "sphinx",

readthedocs/proxito/middleware.py

+15
Original file line numberDiff line numberDiff line change
@@ -289,10 +289,25 @@ def process_request(self, request): # noqa
289289
return None
290290

291291
def add_hosting_integrations_headers(self, request, response):
292+
addons = False
292293
project_slug = getattr(request, "path_project_slug", "")
294+
version_slug = getattr(request, "path_version_slug", "")
295+
293296
if project_slug:
294297
project = Project.objects.get(slug=project_slug)
298+
299+
# Check for the feature flag
295300
if project.has_feature(Feature.HOSTING_INTEGRATIONS):
301+
addons = True
302+
else:
303+
# Check if the version forces injecting the addons (e.g. using `build.commands`)
304+
version = (
305+
project.versions.filter(slug=version_slug).only("addons").first()
306+
)
307+
if version and version.addons:
308+
addons = True
309+
310+
if addons:
296311
response["X-RTD-Hosting-Integrations"] = "true"
297312

298313
def _get_https_redirect(self, request):

readthedocs/proxito/tests/test_headers.py

+12
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,18 @@ def test_user_domain_headers(self):
143143
self.assertEqual(r[http_header], http_header_value)
144144
self.assertEqual(r[http_header_secure], http_header_value)
145145

146+
def test_hosting_integrations_header(self):
147+
version = self.project.versions.get(slug=LATEST)
148+
version.addons = True
149+
version.save()
150+
151+
r = self.client.get(
152+
"/en/latest/", secure=True, HTTP_HOST="project.dev.readthedocs.io"
153+
)
154+
self.assertEqual(r.status_code, 200)
155+
self.assertIsNotNone(r.get("X-RTD-Hosting-Integrations"))
156+
self.assertEqual(r["X-RTD-Hosting-Integrations"], "true")
157+
146158
@override_settings(ALLOW_PRIVATE_REPOS=False)
147159
def test_cache_headers_public_version_with_private_projects_not_allowed(self):
148160
r = self.client.get(

0 commit comments

Comments
 (0)