From 7120315fcab509a1c8e3f1086330151f53a7104c Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 4 Nov 2024 11:15:35 +0100 Subject: [PATCH] Addons: allow users to show/hide notifications on latest/non-stable (#11718) We currently allow users to disable the latest and non-stable notifications with only one checkbox. This PR split this checkbox into two, so users can decide to show a notification on latest and/or on non-stable versions individually. Note this PR also changes the addons API response: `non_latest_version_warning` and `external_version_warning` are combined into `notifications`. I think nobody is using this yet, so I'm not expecting issues with it. Let me know if you think differently here. Required by https://github.com/readthedocs/addons/issues/133 --------- Co-authored-by: Eric Holscher Co-authored-by: Eric Holscher <25510+ericholscher@users.noreply.github.com> --- docs/user/addons.rst | 4 +- docs/user/doc-notifications.rst | 26 ++++++ docs/user/index.rst | 1 + readthedocs/projects/forms.py | 13 +-- .../migrations/0128_addons_notifications.py | 80 +++++++++++++++++++ .../0129_addons_remove_old_fields.py | 31 +++++++ readthedocs/projects/models.py | 10 +-- readthedocs/proxito/tests/responses/v1.json | 10 +-- readthedocs/proxito/tests/test_hosting.py | 11 ++- readthedocs/proxito/views/hosting.py | 16 ++-- .../rtd_tests/tests/test_project_forms.py | 24 +++--- 11 files changed, 186 insertions(+), 40 deletions(-) create mode 100644 docs/user/doc-notifications.rst create mode 100644 readthedocs/projects/migrations/0128_addons_notifications.py create mode 100644 readthedocs/projects/migrations/0129_addons_remove_old_fields.py diff --git a/docs/user/addons.rst b/docs/user/addons.rst index 305cb7e5c30..edf42b94e13 100644 --- a/docs/user/addons.rst +++ b/docs/user/addons.rst @@ -8,8 +8,8 @@ and can be accessed via hotkeys or on screen UI elements. :doc:`DocDiff ` Highlight changed output from pull requests -:doc:`Pull request notification ` - Notify readers that they are reading docs from a pull request +:doc:`Documentation notification ` + Alert users to various documentation states :doc:`Flyout ` Easily switch between versions and translations diff --git a/docs/user/doc-notifications.rst b/docs/user/doc-notifications.rst new file mode 100644 index 00000000000..33e096498ed --- /dev/null +++ b/docs/user/doc-notifications.rst @@ -0,0 +1,26 @@ +Documentation notifications +=========================== + +Documentation notifications alert users to information about the documentation they are viewing. +These notifications can be enabled or disabled by the project maintainer, +and are displayed to the user in the rendered documentation. + +Overview +-------- + +The current notifications are: + +- **Pull request warning**: Show a notification on builds from pull requests, with a link back to the pull request for giving feedback. +- **Latest version warning**: Show a notification on latest version, warning users that they are reading docs from a development version. +- **Non-stable version warning**: Show a notification on non-stable versions, warning users that they are reading docs from a non-stable release. + +Manage notifications +-------------------- + +To manage notifications for your project: + +1. Go to the :term:`dashboard`. +2. Click on a project name. +3. Go to :guilabel:`Settings`. +4. In the left bar, go to :guilabel:`Addons`. +5. Configure each Addon in the :guilabel:`Notifications` section. diff --git a/docs/user/index.rst b/docs/user/index.rst index bbc08655101..f2f6fd5141d 100644 --- a/docs/user/index.rst +++ b/docs/user/index.rst @@ -47,6 +47,7 @@ Read the Docs: documentation simplified /localization /versioning-schemes /custom-domains + /doc-notifications /canonical-urls /reference/cdn /reference/sitemaps diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 1fb7b88bb2a..3a86a98197c 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -654,23 +654,26 @@ class Meta: "analytics_enabled", "doc_diff_enabled", "doc_diff_root_selector", - "external_version_warning_enabled", "flyout_enabled", "flyout_sorting", "flyout_sorting_latest_stable_at_beginning", "flyout_sorting_custom_pattern", "hotkeys_enabled", "search_enabled", - "stable_latest_version_warning_enabled", + "notifications_enabled", + "notifications_show_on_latest", + "notifications_show_on_non_stable", + "notifications_show_on_external", ) labels = { "enabled": _("Enable Addons"), - "external_version_warning_enabled": _( + "notifications_show_on_external": _( "Show a notification on builds from pull requests" ), - "stable_latest_version_warning_enabled": _( - "Show a notification on non-stable and latest versions" + "notifications_show_on_non_stable": _( + "Show a notification on non-stable versions" ), + "notifications_show_on_latest": _("Show a notification on latest version"), } widgets = { "doc_diff_root_selector": forms.TextInput( diff --git a/readthedocs/projects/migrations/0128_addons_notifications.py b/readthedocs/projects/migrations/0128_addons_notifications.py new file mode 100644 index 00000000000..e8f1a1ee88c --- /dev/null +++ b/readthedocs/projects/migrations/0128_addons_notifications.py @@ -0,0 +1,80 @@ +# Generated by Django 4.2.16 on 2024-10-29 14:05 + +from django.db import migrations, models +from django_safemigrate import Safe + + +def forward_add_fields(apps, schema_editor): + AddonsConfig = apps.get_model("projects", "AddonsConfig") + for addons in AddonsConfig.objects.filter(project__isnull=False): + addons.notifications_show_on_latest = ( + addons.stable_latest_version_warning_enabled + ) + addons.notifications_show_on_non_stable = ( + addons.stable_latest_version_warning_enabled + ) + addons.notifications_show_on_external = addons.external_version_warning_enabled + addons.save() + + +def reverse_remove_fields(apps, schema_editor): + AddonsConfig = apps.get_model("projects", "AddonsConfig") + for addons in AddonsConfig.objects.filter(project__isnull=False): + addons.stable_latest_version_warning_enabled = ( + addons.notifications_show_on_latest + or addons.notifications_show_on_non_stable + ) + addons.external_version_warning_enabled = addons.notifications_show_on_external + addons.save() + + +class Migration(migrations.Migration): + safe = Safe.before_deploy + + dependencies = [ + ("projects", "0127_default_to_semver"), + ] + + operations = [ + migrations.AddField( + model_name="addonsconfig", + name="notifications_enabled", + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name="addonsconfig", + name="notifications_show_on_external", + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name="addonsconfig", + name="notifications_show_on_latest", + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name="addonsconfig", + name="notifications_show_on_non_stable", + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name="historicaladdonsconfig", + name="notifications_enabled", + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name="historicaladdonsconfig", + name="notifications_show_on_external", + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name="historicaladdonsconfig", + name="notifications_show_on_latest", + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name="historicaladdonsconfig", + name="notifications_show_on_non_stable", + field=models.BooleanField(default=True), + ), + migrations.RunPython(forward_add_fields, reverse_remove_fields), + ] diff --git a/readthedocs/projects/migrations/0129_addons_remove_old_fields.py b/readthedocs/projects/migrations/0129_addons_remove_old_fields.py new file mode 100644 index 00000000000..502b53fa0b0 --- /dev/null +++ b/readthedocs/projects/migrations/0129_addons_remove_old_fields.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.16 on 2024-10-29 14:09 + +from django.db import migrations +from django_safemigrate import Safe + + +class Migration(migrations.Migration): + safe = Safe.after_deploy + + dependencies = [ + ("projects", "0128_addons_notifications"), + ] + + operations = [ + migrations.RemoveField( + model_name="addonsconfig", + name="external_version_warning_enabled", + ), + migrations.RemoveField( + model_name="addonsconfig", + name="stable_latest_version_warning_enabled", + ), + migrations.RemoveField( + model_name="historicaladdonsconfig", + name="external_version_warning_enabled", + ), + migrations.RemoveField( + model_name="historicaladdonsconfig", + name="stable_latest_version_warning_enabled", + ), + ] diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 7f69ce69d42..66371a96845 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -178,9 +178,6 @@ class AddonsConfig(TimeStampedModel): help_text="CSS selector for the main content of the page", ) - # External version warning - external_version_warning_enabled = models.BooleanField(default=True) - # EthicalAds ethicalads_enabled = models.BooleanField(default=True) @@ -215,8 +212,11 @@ class AddonsConfig(TimeStampedModel): search_enabled = models.BooleanField(default=True) search_default_filter = models.CharField(null=True, blank=True, max_length=128) - # Stable/Latest version warning - stable_latest_version_warning_enabled = models.BooleanField(default=True) + # Notifications + notifications_enabled = models.BooleanField(default=True) + notifications_show_on_latest = models.BooleanField(default=True) + notifications_show_on_non_stable = models.BooleanField(default=True) + notifications_show_on_external = models.BooleanField(default=True) class AddonSearchFilter(TimeStampedModel): diff --git a/readthedocs/proxito/tests/responses/v1.json b/readthedocs/proxito/tests/responses/v1.json index 27dc0c2131c..2fdeed829a5 100644 --- a/readthedocs/proxito/tests/responses/v1.json +++ b/readthedocs/proxito/tests/responses/v1.json @@ -125,9 +125,6 @@ "enabled": false, "code": null }, - "external_version_warning": { - "enabled": true - }, "hotkeys": { "enabled": true, "doc_diff": { @@ -139,8 +136,11 @@ "trigger": "Slash" } }, - "non_latest_version_warning": { - "enabled": true + "notifications": { + "enabled": true, + "show_on_external": true, + "show_on_latest": true, + "show_on_non_stable": true }, "doc_diff": { "enabled": true, diff --git a/readthedocs/proxito/tests/test_hosting.py b/readthedocs/proxito/tests/test_hosting.py index f35f2004d8d..fc1e23c12f0 100644 --- a/readthedocs/proxito/tests/test_hosting.py +++ b/readthedocs/proxito/tests/test_hosting.py @@ -149,7 +149,10 @@ def test_disabled_addons_via_addons_config(self): addons.flyout_enabled = False addons.hotkeys_enabled = False addons.search_enabled = False - addons.stable_latest_version_warning_enabled = False + addons.notifications_enabled = False + addons.notifications_show_on_latest = False + addons.notifications_show_on_non_stable = False + addons.notifications_show_on_external = False addons.save() r = self.client.get( @@ -166,8 +169,10 @@ def test_disabled_addons_via_addons_config(self): ) assert r.status_code == 200 assert r.json()["addons"]["analytics"]["enabled"] is False - assert r.json()["addons"]["external_version_warning"]["enabled"] is False - assert r.json()["addons"]["non_latest_version_warning"]["enabled"] is False + assert r.json()["addons"]["notifications"]["enabled"] is False + assert r.json()["addons"]["notifications"]["show_on_latest"] is False + assert r.json()["addons"]["notifications"]["show_on_non_stable"] is False + assert r.json()["addons"]["notifications"]["show_on_external"] is False assert r.json()["addons"]["doc_diff"]["enabled"] is False assert r.json()["addons"]["flyout"]["enabled"] is False assert r.json()["addons"]["search"]["enabled"] is False diff --git a/readthedocs/proxito/views/hosting.py b/readthedocs/proxito/views/hosting.py index 108fe7f344d..dc6a89110bb 100644 --- a/readthedocs/proxito/views/hosting.py +++ b/readthedocs/proxito/views/hosting.py @@ -462,17 +462,11 @@ def _v1(self, project, version, build, filename, url, request): # https://github.com/readthedocs/readthedocs.org/issues/9530 "code": project.analytics_code, }, - "external_version_warning": { - "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": 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]", + "notifications": { + "enabled": project.addons.notifications_enabled, + "show_on_latest": project.addons.notifications_show_on_latest, + "show_on_non_stable": project.addons.notifications_show_on_non_stable, + "show_on_external": project.addons.notifications_show_on_external, }, "flyout": { "enabled": project.addons.flyout_enabled, diff --git a/readthedocs/rtd_tests/tests/test_project_forms.py b/readthedocs/rtd_tests/tests/test_project_forms.py index c424be0ab8a..aaa518ca33e 100644 --- a/readthedocs/rtd_tests/tests/test_project_forms.py +++ b/readthedocs/rtd_tests/tests/test_project_forms.py @@ -1133,14 +1133,16 @@ def test_addonsconfig_form(self): "enabled": True, "analytics_enabled": False, "doc_diff_enabled": False, - "external_version_warning_enabled": True, "flyout_enabled": True, "flyout_sorting": ADDONS_FLYOUT_SORTING_CALVER, "flyout_sorting_latest_stable_at_beginning": True, "flyout_sorting_custom_pattern": None, "hotkeys_enabled": False, "search_enabled": False, - "stable_latest_version_warning_enabled": True, + "notifications_enabled": True, + "notifications_show_on_latest": True, + "notifications_show_on_non_stable": True, + "notifications_show_on_external": True, } form = AddonsConfigForm(data=data, project=self.project) self.assertTrue(form.is_valid()) @@ -1149,7 +1151,10 @@ def test_addonsconfig_form(self): self.assertEqual(self.project.addons.enabled, True) self.assertEqual(self.project.addons.analytics_enabled, False) self.assertEqual(self.project.addons.doc_diff_enabled, False) - self.assertEqual(self.project.addons.external_version_warning_enabled, True) + self.assertEqual(self.project.addons.notifications_enabled, True) + self.assertEqual(self.project.addons.notifications_show_on_latest, True) + self.assertEqual(self.project.addons.notifications_show_on_non_stable, True) + self.assertEqual(self.project.addons.notifications_show_on_external, True) self.assertEqual(self.project.addons.flyout_enabled, True) self.assertEqual( self.project.addons.flyout_sorting, @@ -1162,24 +1167,25 @@ def test_addonsconfig_form(self): self.assertEqual(self.project.addons.flyout_sorting_custom_pattern, None) self.assertEqual(self.project.addons.hotkeys_enabled, False) self.assertEqual(self.project.addons.search_enabled, False) - self.assertEqual( - self.project.addons.stable_latest_version_warning_enabled, - True, - ) + self.assertEqual(self.project.addons.notifications_show_on_latest, True) + self.assertEqual(self.project.addons.notifications_show_on_non_stable, True) + self.assertEqual(self.project.addons.notifications_show_on_external, True) def test_addonsconfig_form_invalid_sorting_custom_pattern(self): data = { "enabled": True, "analytics_enabled": False, "doc_diff_enabled": False, - "external_version_warning_enabled": True, "flyout_enabled": True, "flyout_sorting": ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN, "flyout_sorting_latest_stable_at_beginning": True, "flyout_sorting_custom_pattern": None, "hotkeys_enabled": False, "search_enabled": False, - "stable_latest_version_warning_enabled": True, + "notifications_enabled": True, + "notifications_show_on_latest": True, + "notifications_show_on_non_stable": True, + "notifications_show_on_external": True, } form = AddonsConfigForm(data=data, project=self.project) self.assertFalse(form.is_valid())