From 919d10aca54650c4a9af5f597f8a0f08646d3206 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 15 Jan 2024 16:05:34 +0100 Subject: [PATCH 01/10] Addons: update form to show all the options Add all the configuration options to the `AddonsConfigForm` so the user is able to enable/disable each of the addons independently. Closes https://github.com/readthedocs/ext-theme/issues/211 --- readthedocs/projects/forms.py | 78 +++++++++++++++++++++++++++- readthedocs/proxito/views/hosting.py | 23 ++++---- 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index e5051dc7a71..b118d6a097b 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -529,7 +529,17 @@ class AddonsConfigForm(forms.ModelForm): class Meta: model = AddonsConfig - fields = ("enabled", "project") + fields = ( + "enabled", + "project", + "analytics_enabled", + "doc_diff_enabled", + "external_version_warning_enabled", + "flyout_enabled", + "hotkeys_enabled", + "search_enabled", + "stable_latest_version_warning_enabled", + ) def __init__(self, *args, **kwargs): self.project = kwargs.pop("project", None) @@ -541,6 +551,72 @@ def __init__(self, *args, **kwargs): except AddonsConfig.DoesNotExist: self.fields["enabled"].initial = False + fieldsets = [ + Fieldset( + _("Global"), + "enabled", + ), + Fieldset( + _("Analytics"), + HTML( + "

Analytics addon allows you to store traffic analytics in your project.

" + ), + *[field for field in self.fields if field.startswith("analytics_")], + ), + Fieldset( + _("DocDiff"), + HTML( + "

DocDicc addon helps you to review changes from PR by visually showing the differences.

" + ), + *[field for field in self.fields if field.startswith("doc_diff_")], + ), + Fieldset( + _("Flyout"), + HTML( + "

Flyout addon shows a small menu at bottom-right of each page with useful links to switch between versions and translations.

" + ), + *[field for field in self.fields if field.startswith("flyout_")], + ), + Fieldset( + _("Hotkeys"), + HTML( + "

Hotkeys addon enables keyboard shortcuts to access other addons quickly.

" + ), + *[field for field in self.fields if field.startswith("hotkeys_")], + ), + Fieldset( + _("Search"), + HTML( + "

Search addon improves your search experience with the search-as-you-type feature.

" + ), + *[field for field in self.fields if field.startswith("search_")], + ), + Fieldset( + _("Warning on PR previews"), + HTML("

Shows a warning notification on PR previews.

"), + *[ + field + for field in self.fields + if field.startswith("external_version_warning_") + ], + ), + Fieldset( + _("Warning on non-stable/latest versions"), + HTML( + "

Shows a warning notification stable and latest versions letting the user know they may be reading an outdated page or a non-released one.

" + ), + *[ + field + for field in self.fields + if field.startswith("stable_latest_version_warning_") + ], + ), + ] + + self.helper = FormHelper() + self.helper.layout = Layout(*fieldsets) + self.helper.add_input(Submit("save", _("Save"))) + def clean_project(self): return self.project diff --git a/readthedocs/proxito/views/hosting.py b/readthedocs/proxito/views/hosting.py index 71f7d89ee50..5bd01a45cd4 100644 --- a/readthedocs/proxito/views/hosting.py +++ b/readthedocs/proxito/views/hosting.py @@ -23,7 +23,7 @@ from readthedocs.core.resolver import Resolver from readthedocs.core.unresolver import UnresolverError, unresolver from readthedocs.core.utils.extend import SettingsOverrideObject -from readthedocs.projects.models import Feature, Project +from readthedocs.projects.models import Project log = structlog.get_logger(__name__) # noqa @@ -320,8 +320,7 @@ def _v0(self, project, version, build, filename, url, user): # serializer than the keys ``project``, ``version`` and ``build`` from the top level. "addons": { "analytics": { - "enabled": Feature.ADDONS_ANALYTICS_DISABLED - not in project_features, + "enabled": project.addons.analytics_enabled, # TODO: consider adding this field into the ProjectSerializer itself. # NOTE: it seems we are removing this feature, # so we may not need the ``code`` attribute here @@ -329,15 +328,13 @@ def _v0(self, project, version, build, filename, url, user): "code": project.analytics_code, }, "external_version_warning": { - "enabled": Feature.ADDONS_EXTERNAL_VERSION_WARNING_DISABLED - not in project_features, + "enabled": project.addons.external_version_warning_enabled, # NOTE: I think we are moving away from these selectors # since we are doing floating noticications now. # "query_selector": "[role=main]", }, "non_latest_version_warning": { - "enabled": Feature.ADDONS_NON_LATEST_VERSION_WARNING_DISABLED - not in project_features, + "enabled": project.addons.stable_latest_version_warning_enabled, # NOTE: I think we are moving away from these selectors # since we are doing floating noticications now. # "query_selector": "[role=main]", @@ -346,7 +343,7 @@ def _v0(self, project, version, build, filename, url, user): ), }, "flyout": { - "enabled": Feature.ADDONS_FLYOUT_DISABLED not in project_features, + "enabled": project.addons.flyout_enabled, "translations": [ { # TODO: name this field "display_name" @@ -398,7 +395,7 @@ def _v0(self, project, version, build, filename, url, user): # }, }, "search": { - "enabled": Feature.ADDONS_SEARCH_DISABLED not in project_features, + "enabled": project.addons.search_enabled, "project": project.slug, "version": version.slug if version else None, "api_endpoint": "/_/api/v3/search/", @@ -416,7 +413,7 @@ def _v0(self, project, version, build, filename, url, user): else None, }, "hotkeys": { - "enabled": Feature.ADDONS_HOTKEYS_DISABLED not in project_features, + "enabled": project.addons.hotkeys_enabled, "doc_diff": { "enabled": True, "trigger": "KeyD", # Could be something like "Ctrl + D" @@ -437,8 +434,7 @@ def _v0(self, project, version, build, filename, url, user): data["addons"].update( { "doc_diff": { - "enabled": Feature.ADDONS_DOC_DIFF_DISABLED - not in project_features, + "enabled": project.addons.doc_diff_enabled, # "http://test-builds-local.devthedocs.org/en/latest/index.html" "base_url": resolver.resolve( project=project, @@ -478,8 +474,7 @@ def _v0(self, project, version, build, filename, url, user): data["addons"].update( { "ethicalads": { - "enabled": Feature.ADDONS_ETHICALADS_DISABLED - not in project_features, + "enabled": project.addons.ethicalads_enabled, # NOTE: this endpoint is not authenticated, the user checks are done over an annonymous user for now # # NOTE: it requires ``settings.USE_PROMOS=True`` to return ``ad_free=false`` here From b6f51e8b8af9d5b1176c53b4357efb770e58a1b5 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 16 Jan 2024 11:44:32 +0100 Subject: [PATCH 02/10] Addons: create an `AddonsConfig` for projects automatically --- readthedocs/proxito/views/hosting.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/readthedocs/proxito/views/hosting.py b/readthedocs/proxito/views/hosting.py index 5bd01a45cd4..f3366f83f47 100644 --- a/readthedocs/proxito/views/hosting.py +++ b/readthedocs/proxito/views/hosting.py @@ -23,7 +23,7 @@ from readthedocs.core.resolver import Resolver from readthedocs.core.unresolver import UnresolverError, unresolver from readthedocs.core.utils.extend import SettingsOverrideObject -from readthedocs.projects.models import Project +from readthedocs.projects.models import AddonsConfig, Project log = structlog.get_logger(__name__) # noqa @@ -280,15 +280,9 @@ def _v0(self, project, version, build, filename, url, user): # en (original), es, ru project_translations = itertools.chain([main_project], project_translations) - # Make one DB query here and then check on Python code - # TODO: make usage of ``Project.addons._enabled`` to decide if enabled - # - # NOTE: using ``feature_id__startswith="addons_"`` to make the query faster. - # It went down from 20ms to 1ms since it does not have to check the - # `Project.pub_date` against all the features. - project_features = project.features.filter( - feature_id__startswith="addons_" - ).values_list("feature_id", flat=True) + # Automatically create an AddonsConfig with the default values for + # projects that don't have one already + AddonsConfig.objects.get_or_create(project=project) data = { "api_version": "0", From 7dfe3a34f3a65bdde5133fba979eb85a3e9c1757 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 16 Jan 2024 11:46:20 +0100 Subject: [PATCH 03/10] Addons: remove features related to them --- readthedocs/projects/models.py | 58 ---------------------------------- 1 file changed, 58 deletions(-) diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 5dea927ed40..cc003a80f26 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -1977,25 +1977,6 @@ def add_features(sender, **kwargs): # Build related features SCALE_IN_PROTECTION = "scale_in_prtection" - # Addons related features - HOSTING_INTEGRATIONS = "hosting_integrations" - # NOTE: this is mainly temporal while we are rolling these features out. - # The idea here is to have more control over particular projects and do some testing. - # All these features will be enabled by default to all projects, - # and we can disable them if we want to - ADDONS_ANALYTICS_DISABLED = "addons_analytics_disabled" - ADDONS_DOC_DIFF_DISABLED = "addons_doc_diff_disabled" - ADDONS_ETHICALADS_DISABLED = "addons_ethicalads_disabled" - ADDONS_EXTERNAL_VERSION_WARNING_DISABLED = ( - "addons_external_version_warning_disabled" - ) - ADDONS_FLYOUT_DISABLED = "addons_flyout_disabled" - ADDONS_NON_LATEST_VERSION_WARNING_DISABLED = ( - "addons_non_latest_version_warning_disabled" - ) - ADDONS_SEARCH_DISABLED = "addons_search_disabled" - ADDONS_HOTKEYS_DISABLED = "addons_hotkeys_disabled" - FEATURES = ( ( MKDOCS_THEME_RTD, @@ -2093,45 +2074,6 @@ def add_features(sender, **kwargs): SCALE_IN_PROTECTION, _("Build: Set scale-in protection before/after building."), ), - # Addons related features. - ( - HOSTING_INTEGRATIONS, - _( - "Proxito: Inject 'readthedocs-addons.js' as