Skip to content

Commit

Permalink
ENH: Add plausible analytics (#828)
Browse files Browse the repository at this point in the history
* Add plausible analytics

* Add documentation for plausible analytics

* Fix linter

* Fix test

* Rephrase doc and lint

* Fix tests

* Refactor analytics in a single parameter and deprecate old way

* Apply suggestions from code review

Co-authored-by: Chris Holdgraf <choldgraf@gmail.com>

* Scientific Python admonition and plausible

* Simplify analytics logic and add more tests

* Apply suggestions from code review

Co-authored-by: Chris Holdgraf <choldgraf@gmail.com>

* Update docs/user_guide/analytics.rst

Co-authored-by: Chris Holdgraf <choldgraf@gmail.com>
  • Loading branch information
tupui and choldgraf committed Jul 24, 2022
1 parent e233d04 commit 75c7754
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 43 deletions.
54 changes: 53 additions & 1 deletion docs/user_guide/analytics.rst
@@ -1,6 +1,57 @@
Analytics and usage services
============================

The theme supports several web analytics services via the ``analytics`` option. It is configured
by passing a dictionary with options. See the sections bellow for relevant
options depending on the analytics provider that you want to use.

.. code:: python
html_theme_options = {
# See below for options for each service
"analytics": analytics_options,
}
Generally speaking we recommend using Plausible over Google Analytics because
it has a better story around user security and privacy. In addition, it is more
open-source and transparent. In fact,
`you can self-host a Plausible server <https://www.elvisduru.com/blog/how-to-self-host-plausible-analytics>`__.

.. admonition:: Get a self-hosted Plausible server at ``scientific-python.org``
:class: tip

If your documentation is for a package that is part of the SciPy / PyData
ecosystem, they might be able to host a Plausible server for you at
``<your-package>.scientific-python.org``.
To ask about this, contact them on the social media platform of your choice
and learn more at `scientific-python.org <https://scientific-python.org>`__.

Plausible Analytics
===================

`plausible.io <https://plausible.io>`__ can be used to gather simple
and privacy-friendly analytics for the site. To configure, you will need to provide two things:

- A URL pointing to the JavaScript analytics script that is served by your Plausible server
- A domain that reflects where your documentation lives

Plausible' javascript will be included in all html pages to gather metrics.
The dashboard with analytics results will be accessible at ``https://<plausible-url>/<my-domain>``.

.. code:: python
# To be re-used in html_theme_options["analytics"]
analytics_options = {
# The domain you'd like to use for this analytics instance
"plausible_analytics_domain": "my-domain",
# The analytics script that is served by Plausible
"plausible_analytics_url": "https://.../script.js",
}
.. seealso::

See the `Plausible Documentation <https://plausible.io/docs/plausible-script>`__ for more information about this script.

Google Analytics
================

Expand All @@ -9,6 +60,7 @@ Google Analytics' javascript is included in the html pages.

.. code:: python
html_theme_options = {
# To be re-used in html_theme_options["analytics"]
analytics_options = {
"google_analytics_id": "G-XXXXXXXXXX",
}
75 changes: 51 additions & 24 deletions src/pydata_sphinx_theme/__init__.py
Expand Up @@ -2,6 +2,7 @@
Bootstrap-based sphinx theme from the PyData community
"""
import os
import warnings
from pathlib import Path

import jinja2
Expand Down Expand Up @@ -49,33 +50,59 @@ def update_config(app, env):
)

# Add an analytics ID to the site if provided
# Currently only supports the two types of Google Analytics id.
analytics = theme_options.get("analytics", {})
# deprecated options for Google Analytics
# TODO: deprecate >= v0.12
gid = theme_options.get("google_analytics_id")
if gid:
# In this case it is "new-style" google analytics
if "G-" in gid:
gid_js_path = f"https://www.googletagmanager.com/gtag/js?id={gid}"
gid_script = f"""
window.dataLayer = window.dataLayer || [];
function gtag(){{ dataLayer.push(arguments); }}
gtag('js', new Date());
gtag('config', '{gid}');
"""
# In this case it is "old-style" google analytics
else:
gid_js_path = "https://www.google-analytics.com/analytics.js"
gid_script = f"""
window.ga = window.ga || function () {{
(ga.q = ga.q || []).push(arguments) }};
ga.l = +new Date;
ga('create', '{gid}', 'auto');
ga('set', 'anonymizeIp', true);
ga('send', 'pageview');
msg = (
"'google_analytics_id' is deprecated and will be removed in "
"version 0.11, please refer to the documentation "
"and use 'analytics' instead."
)
warnings.warn(msg, DeprecationWarning, stacklevel=2)
analytics.update({"google_analytics_id": gid})

if analytics:
# Plausible analytics
plausible_domain = analytics.get("plausible_analytics_domain")
plausible_url = analytics.get("plausible_analytics_url")

# Ref: https://plausible.io/docs/plausible-script
if plausible_domain and plausible_url:
plausible_script = f"""
data-domain={plausible_domain} src={plausible_url}
"""

# Link the JS files
app.add_js_file(gid_js_path, loading_method="async")
app.add_js_file(None, body=gid_script)
# Link the JS file
app.add_js_file(None, body=plausible_script, loading_method="defer")

# Two types of Google Analytics id.
gid = analytics.get("google_analytics_id")
if gid:
# In this case it is "new-style" google analytics
if "G-" in gid:
gid_js_path = f"https://www.googletagmanager.com/gtag/js?id={gid}"
gid_script = f"""
window.dataLayer = window.dataLayer || [];
function gtag(){{ dataLayer.push(arguments); }}
gtag('js', new Date());
gtag('config', '{gid}');
"""
# In this case it is "old-style" google analytics
else:
gid_js_path = "https://www.google-analytics.com/analytics.js"
gid_script = f"""
window.ga = window.ga || function () {{
(ga.q = ga.q || []).push(arguments) }};
ga.l = +new Date;
ga('create', '{gid}', 'auto');
ga('set', 'anonymizeIp', true);
ga('send', 'pageview');
"""

# Link the JS files
app.add_js_file(gid_js_path, loading_method="async")
app.add_js_file(None, body=gid_script)


def prepare_html_config(app, pagename, templatename, context, doctree):
Expand Down
5 changes: 4 additions & 1 deletion src/pydata_sphinx_theme/theme/pydata_sphinx_theme/theme.conf
Expand Up @@ -17,7 +17,7 @@ gitlab_url =
twitter_url =
icon_links_label = Icon Links
icon_links =
google_analytics_id =
analytics =
favicons =
show_prev_next = True
search_bar_text = Search the docs ...
Expand All @@ -43,3 +43,6 @@ announcement =

# DEPRECATE after 0.11
logo_text =

# DEPRECATE after 0.12
google_analytics_id =
67 changes: 50 additions & 17 deletions tests/test_build.py
Expand Up @@ -540,30 +540,63 @@ def test_edit_page_url(sphinx_build_factory, html_context, edit_url):
assert edit_link[0].attrs["href"] == edit_url, f"edit link didn't match {edit_link}"


def test_new_google_analytics_id(sphinx_build_factory):
confoverrides = {"html_theme_options.google_analytics_id": "G-XXXXX"}
sphinx_build = sphinx_build_factory("base", confoverrides=confoverrides)
sphinx_build.build()
index_html = sphinx_build.html_tree("index.html")

# Search all the scripts and make sure one of them has the Google tag in there
tags_found = False
for script in index_html.select("script"):
if script.string and "gtag" in script.string and "G-XXXXX" in script.string:
tags_found = True
assert tags_found is True


def test_old_google_analytics_id(sphinx_build_factory):
confoverrides = {"html_theme_options.google_analytics_id": "UA-XXXXX"}
@pytest.mark.parametrize(
"provider,tags",
[
# TODO: Deprecate old-style analytics config >= 0.12
# new_google_analytics_id
({"html_theme_options.google_analytics_id": "G-XXXXX"}, ["gtag", "G-XXXXX"]),
# old_google_analytics_id
({"html_theme_options.google_analytics_id": "UA-XXXXX"}, ["ga", "UA-XXXXX"]),
# google analytics
(
{"html_theme_options.analytics": {"google_analytics_id": "G-XXXXX"}},
["gtag", "G-XXXXX"],
),
# plausible
(
{
"html_theme_options.analytics": {
"plausible_analytics_domain": "toto",
"plausible_analytics_url": "http://.../script.js",
}
},
["data-domain", "toto"],
),
# google and plausible
(
{
"html_theme_options.analytics": {
"google_analytics_id": "G-XXXXX",
"plausible_analytics_domain": "toto",
"plausible_analytics_url": "http://.../script.js",
}
},
["gtag", "G-XXXXX"],
),
# TODO: Deprecate old-style analytics config >= 0.12
(
{
"html_theme_options.analytics": {
"plausible_analytics_domain": "toto",
"plausible_analytics_url": "http://.../script.js",
},
"html_theme_options.google_analytics_id": "G-XXXXX",
},
["gtag", "G-XXXXX"],
),
],
)
def test_analytics(sphinx_build_factory, provider, tags):
confoverrides = provider
sphinx_build = sphinx_build_factory("base", confoverrides=confoverrides)
sphinx_build.build()
index_html = sphinx_build.html_tree("index.html")

# Search all the scripts and make sure one of them has the Google tag in there
tags_found = False
for script in index_html.select("script"):
if script.string and "ga" in script.string and "UA-XXXXX" in script.string:
if script.string and tags[0] in script.string and tags[1] in script.string:
tags_found = True
assert tags_found is True

Expand Down

0 comments on commit 75c7754

Please sign in to comment.