diff --git a/docs/advertising/advertising-details.rst b/docs/advertising/advertising-details.rst index 50614ea3ac5..bd68f8c89d9 100644 --- a/docs/advertising/advertising-details.rst +++ b/docs/advertising/advertising-details.rst @@ -118,6 +118,9 @@ These steps apply both to analytics collected by Read the Docs and when * Users can opt-out of analytics by using the Do Not Track feature of their browser. * Read the Docs instructs Google to anonymize IP addresses sent to them. * The cookies set by GA expire in 30 days rather than the default 2 years. +* Project maintainers can completely disable analytics on their own projects. + Follow the steps in :ref:`guides/google-analytics:Disabling Google Analytics on your project`. + Why we use analytics ~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/api/v3.rst b/docs/api/v3.rst index ab7e9e2630d..d2a91b02d6b 100644 --- a/docs/api/v3.rst +++ b/docs/api/v3.rst @@ -355,6 +355,7 @@ Project update "default_version": "v0.27.0", "default_branch": "develop", "analytics_code": "UA000000", + "analytics_disabled": false, "single_version": false, } diff --git a/docs/guides/google-analytics.rst b/docs/guides/google-analytics.rst index 48179258fcc..a449fd55ded 100644 --- a/docs/guides/google-analytics.rst +++ b/docs/guides/google-analytics.rst @@ -20,3 +20,15 @@ and sometimes can take up to a day before it starts reporting data. For more details, see the :ref:`Do Not Track section ` of our privacy policy. + + +Disabling Google Analytics on your project +------------------------------------------ + +Google Analytics can be completely disabled on your own projects. +To disable Google Analytics: + +* Going to :guilabel:`Admin` > :guilabel:`Advanced Settings` in your project. +* Check the box **Disable Analytics**. + +Your documentation will need to be rebuilt for this change to take effect. diff --git a/media/javascript/readthedocs-analytics.js b/media/javascript/readthedocs-analytics.js index 9511b2cfdfc..ed68e3c4e4f 100644 --- a/media/javascript/readthedocs-analytics.js +++ b/media/javascript/readthedocs-analytics.js @@ -6,26 +6,24 @@ if (navigator.doNotTrack === '1') { console.log('Respecting DNT with respect to analytics...'); } else { - // RTD Analytics Code - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); + if (typeof READTHEDOCS_DATA !== 'undefined' && READTHEDOCS_DATA.global_analytics_code) { + // RTD Analytics Code + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); - if (typeof READTHEDOCS_DATA !== 'undefined') { - if (READTHEDOCS_DATA.global_analytics_code) { - ga('create', READTHEDOCS_DATA.global_analytics_code, 'auto', 'rtfd', { - 'cookieExpires': 30 * 24 * 60 * 60 - }); - ga('rtfd.set', 'dimension1', READTHEDOCS_DATA.project); - ga('rtfd.set', 'dimension2', READTHEDOCS_DATA.version); - ga('rtfd.set', 'dimension3', READTHEDOCS_DATA.language); - ga('rtfd.set', 'dimension4', READTHEDOCS_DATA.theme); - ga('rtfd.set', 'dimension5', READTHEDOCS_DATA.programming_language); - ga('rtfd.set', 'dimension6', READTHEDOCS_DATA.builder); - ga('rtfd.set', 'anonymizeIp', true); - ga('rtfd.send', 'pageview'); - } + ga('create', READTHEDOCS_DATA.global_analytics_code, 'auto', 'rtfd', { + 'cookieExpires': 30 * 24 * 60 * 60 + }); + ga('rtfd.set', 'dimension1', READTHEDOCS_DATA.project); + ga('rtfd.set', 'dimension2', READTHEDOCS_DATA.version); + ga('rtfd.set', 'dimension3', READTHEDOCS_DATA.language); + ga('rtfd.set', 'dimension4', READTHEDOCS_DATA.theme); + ga('rtfd.set', 'dimension5', READTHEDOCS_DATA.programming_language); + ga('rtfd.set', 'dimension6', READTHEDOCS_DATA.builder); + ga('rtfd.set', 'anonymizeIp', true); + ga('rtfd.send', 'pageview'); // User Analytics Code if (READTHEDOCS_DATA.user_analytics_code) { diff --git a/readthedocs/api/v2/serializers.py b/readthedocs/api/v2/serializers.py index da21980096f..f26a97640bc 100644 --- a/readthedocs/api/v2/serializers.py +++ b/readthedocs/api/v2/serializers.py @@ -58,6 +58,7 @@ class Meta(ProjectSerializer.Meta): 'enable_pdf_build', 'conf_py_file', 'analytics_code', + 'analytics_disabled', 'cdn_enabled', 'container_image', 'container_mem_limit', diff --git a/readthedocs/api/v3/serializers.py b/readthedocs/api/v3/serializers.py index 832af4daa73..d9e803c801c 100644 --- a/readthedocs/api/v3/serializers.py +++ b/readthedocs/api/v3/serializers.py @@ -452,6 +452,7 @@ class Meta: 'default_version', 'default_branch', 'analytics_code', + 'analytics_disabled', 'show_version_warning', 'single_version', diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 2b1549b76ae..1bb78683a26 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -251,7 +251,9 @@ def generate_rtd_data(self, docs_dir, mkdocs_config): 'api_host': settings.PUBLIC_API_URL, 'ad_free': not self.project.show_advertising, 'commit': self.version.project.vcs_repo(self.version.slug).commit, - 'global_analytics_code': settings.GLOBAL_ANALYTICS_CODE, + 'global_analytics_code': ( + None if self.project.analytics_disabled else settings.GLOBAL_ANALYTICS_CODE + ), 'user_analytics_code': analytics_code, 'features': { 'docsearch_disabled': ( diff --git a/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl b/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl index ff78856f63a..b6090222d6e 100644 --- a/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl +++ b/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl @@ -116,7 +116,7 @@ context = { 'ad_free': {% if project.show_advertising %}False{% else %}True{% endif %}, 'docsearch_disabled': {{ docsearch_disabled }}, 'user_analytics_code': '{{ project.analytics_code|default_if_none:'' }}', - 'global_analytics_code': '{{ settings.GLOBAL_ANALYTICS_CODE }}', + 'global_analytics_code': {% if project.analytics_disabled %}None{% else %}'{{ settings.GLOBAL_ANALYTICS_CODE }}'{% endif %}, 'commit': {% if project.repo_type == 'git' %}'{{ commit|slice:"8" }}'{% else %}'{{ commit }}'{% endif %}, } diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 74837172a6b..86d1ecfd11d 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -200,6 +200,7 @@ class Meta: 'default_version', 'default_branch', 'analytics_code', + 'analytics_disabled', 'show_version_warning', 'single_version', 'external_builds_enabled' @@ -223,6 +224,10 @@ class Meta: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + # Remove the nullable option from the form + self.fields['analytics_disabled'].widget = forms.CheckboxInput() + self.fields['analytics_disabled'].empty_value = False + self.helper = FormHelper() help_text = render_to_string( 'projects/project_advanced_settings_helptext.html' diff --git a/readthedocs/projects/migrations/0055_add_disable_analytics.py b/readthedocs/projects/migrations/0055_add_disable_analytics.py new file mode 100644 index 00000000000..877d80a6080 --- /dev/null +++ b/readthedocs/projects/migrations/0055_add_disable_analytics.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.12 on 2020-06-10 17:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0054_urlconf_blank'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='analytics_disabled', + field=models.BooleanField(default=False, help_text='Disable Google Analytics completely for this project (requires rebuilding documentation)', null=True, verbose_name='Disable Analytics'), + ), + ] diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 3ce14ea972a..dada277f9ed 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -232,6 +232,15 @@ class Project(models.Model): 'This may slow down your page loads.', ), ) + analytics_disabled = models.BooleanField( + _('Disable Analytics'), + default=False, + null=True, + help_text=_( + 'Disable Google Analytics completely for this project ' + '(requires rebuilding documentation)', + ), + ) container_image = models.CharField( _('Alternative container image'), max_length=64, diff --git a/readthedocs/rtd_tests/tests/test_api.py b/readthedocs/rtd_tests/tests/test_api.py index f5660f9ebad..dc4298ae4a5 100644 --- a/readthedocs/rtd_tests/tests/test_api.py +++ b/readthedocs/rtd_tests/tests/test_api.py @@ -2311,6 +2311,7 @@ def test_get_version_by_id(self): 'active': True, 'project': { 'analytics_code': None, + 'analytics_disabled': False, 'canonical_url': 'http://readthedocs.org/docs/pip/en/latest/', 'cdn_enabled': False, 'conf_py_file': '',