Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add plausible analytics #828

Merged
merged 13 commits into from Jul 24, 2022
33 changes: 32 additions & 1 deletion docs/user_guide/analytics.rst
@@ -1,6 +1,36 @@
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 = {
"analytics": analytics_options,
tupui marked this conversation as resolved.
Show resolved Hide resolved
}

Plausible Analytics (recommended)
tupui marked this conversation as resolved.
Show resolved Hide resolved
=================================

https://plausible.io can be used to gather simple
tupui marked this conversation as resolved.
Show resolved Hide resolved
and privacy-friendly analytics for the site. The configuration consists in
a server URL and a specific domain. Plausible' javascript will be included in
all html pages to gather metrics. And the dashboard can be accessed at
``https://url/my_domain``.
tupui marked this conversation as resolved.
Show resolved Hide resolved

The Scientific-Python community can offer a self-hosted server. Contact the
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this documented somewhere? We were looking into Plausible for Jupyter a while back, but didn't do it in-part because it was pretty expensive to run at the scale of Jupyter's website. I wonder if we could piggy-back on scientific-python with this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documented as what is the process to get access? Nothing yet, this would be the first document talking about that. At some point we might have some mention on our website when we list all the initiatives we have.

@stefanv maybe you can answer this one? I don't know how hard this is for the server.
But I would guess it's ok. SciPy is getting 100k per month which is nothing. I don't know how many RPS the server can handle but I would assume >100 RPS (based on my personal experience, we should see this with e.g. locust to be sure)

team on social media following https://scientific-python.org for assistance.

.. code:: python

# To be re-used in html_theme_options["analytics"]
analytics_options = {
tupui marked this conversation as resolved.
Show resolved Hide resolved
"plausible_analytics_domain": "my-domain",
"plausible_analytics_url": "https://.../script.js",
tupui marked this conversation as resolved.
Show resolved Hide resolved
}

Google Analytics
================
tupui marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -9,6 +39,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",
tupui marked this conversation as resolved.
Show resolved Hide resolved
}
76 changes: 53 additions & 23 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,62 @@ def update_config(app, env):
)

# Add an analytics ID to the site if provided
# Currently only supports the two types of Google Analytics id.

# deprecated options for Google Analytics
tupui marked this conversation as resolved.
Show resolved Hide resolved
# 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
msg = (
"'google_analytics_id' is deprecated and will be removed in "
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this not affect the current Google Analytics section in the documentation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmm I did not update this indeed.

Copy link
Contributor Author

@tupui tupui Jul 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction, I mis-read that and I did update the doc about Google Analytics. Ah no I see what you meant. Chris did it right in the follow-up

"version 0.11, please refer to the documentation "
"and use 'analytics' instead."
)
warnings.warn(msg, DeprecationWarning, stacklevel=2)
if theme_options.get("analytics"):
theme_options["analytics"].update({"google_analytics_id": gid})
tupui marked this conversation as resolved.
Show resolved Hide resolved
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');
"""
theme_options["analytics"] = {"google_analytics_id": gid}

# Link the JS files
app.add_js_file(gid_js_path, loading_method="async")
app.add_js_file(None, body=gid_script)
analytics = theme_options.get("analytics")
tupui marked this conversation as resolved.
Show resolved Hide resolved
if analytics:
# Plausible analytics
plausible_domain = analytics.get("plausible_analytics_domain")
plausible_url = analytics.get("plausible_analytics_url")

if plausible_domain and plausible_url:
tupui marked this conversation as resolved.
Show resolved Hide resolved
plausible_script = f"""
data-domain={plausible_domain} src={plausible_url}
"""
# 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")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason we'd use defer for plausible, but async here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this but apparently this is what they have in their doc. Maybe to have correct timing?? https://plausible.io/docs/plausible-script

app.add_js_file(None, body=gid_script)


def prepare_html_config(app, pagename, templatename, context, doctree):
Expand Down
Expand Up @@ -18,6 +18,7 @@ twitter_url =
icon_links_label = Icon Links
icon_links =
google_analytics_id =
tupui marked this conversation as resolved.
Show resolved Hide resolved
analytics =
favicons =
show_prev_next = True
search_bar_text = Search the docs ...
Expand Down
45 changes: 28 additions & 17 deletions tests/test_build.py
Expand Up @@ -540,30 +540,41 @@ 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"],
),
],
)
def test_analytics(sphinx_build_factory, provider, tags):
tupui marked this conversation as resolved.
Show resolved Hide resolved
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