Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions plugins/tutor-contrib-google-analytics/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
Google Analytics plugin for `Tutor <https://docs.tutor.overhang.io>`__
=======================================================================

This plugin wires a Google Analytics 4 loader into every Open edX MFE that
is built through ``tutor-mfe``. It duplicates the ``GoogleAnalyticsLoader``
that used to ship with ``@openedx/frontend-platform``.

The loader reads ``GOOGLE_ANALYTICS_4_ID`` from the MFE runtime configuration
(or, for the frontend-base site, from ``commonAppConfig``). When that value
is unset, the loader is a no-op, so the plugin is safe to enable even if
Google Analytics has not yet been configured for a deployment.

Implementation
--------------

The plugin declares a single ``GoogleAnalyticsLoader`` JavaScript class and
inlines it into both build pipelines that tutor-mfe supports. The class
body is injected into ``env.config.jsx`` via the
``mfe-env-config-buildtime-definitions`` patch (for legacy MFEs) and into
``customApp.tsx`` via the ``mfe-site-custom-app-definitions`` patch (for
the frontend-base site). A single ``tutormfe.hooks.EXTERNAL_SCRIPTS``
registration targeting ``"all"`` then wires the class into both: each
legacy MFE's ``externalScripts`` array and the site's
``customApp.externalScripts``.

At runtime, each pipeline instantiates the loader with the relevant app
configuration. Setting ``GOOGLE_ANALYTICS_4_ID`` in the MFE runtime config
(for legacy MFEs) or in ``FRONTEND_SITE_CONFIG['commonAppConfig']`` (for
the frontend-base site) is enough to activate tracking. Without a value,
the loader is a safe no-op.

Installation
------------

Install directly from Github::

pip install git+https://github.com/openedx/openedx-tutor-plugins.git#subdirectory=plugins/tutor-contrib-google-analytics

Alternatively, clone the parent repository locally and install it from the
checkout::

git clone https://github.com/openedx/openedx-tutor-plugins.git
cd openedx-tutor-plugins/plugins/tutor-contrib-google-analytics
pip install -e .

Usage
-----

Enable the plugin::

tutor plugins enable google-analytics

Then rebuild the MFE images and restart the environment so the new
``env.config.jsx`` takes effect::

tutor images build mfe mfe-dev
tutor local launch

Make sure that ``GOOGLE_ANALYTICS_4_ID`` is present in the MFE runtime
configuration for the deployments where tracking should be active.

Uninstallation
--------------

To disable the plugin::

tutor plugins disable google-analytics
tutor local stop && tutor local start -d

License
-------

This software is licensed under the terms of the AGPLv3.
58 changes: 58 additions & 0 deletions plugins/tutor-contrib-google-analytics/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import io
import os
from setuptools import setup, find_packages

HERE = os.path.abspath(os.path.dirname(__file__))


def load_readme():
with io.open(os.path.join(HERE, "README.rst"), "rt", encoding="utf8") as f:
return f.read()


def load_about():
about = {}
with io.open(
os.path.join(HERE, "tutor_google_analytics", "__about__.py"),
"rt",
encoding="utf-8",
) as f:
exec(f.read(), about) # pylint: disable=exec-used
return about


ABOUT = load_about()


setup(
name="tutor-contrib-google-analytics",
version=ABOUT["__version__"],
url="https://github.com/openedx/openedx-tutor-plugins",
project_urls={
"Code": "https://github.com/openedx/openedx-tutor-plugins",
"Issue tracker": "https://github.com/openedx/openedx-tutor-plugins/issues",
},
license="AGPLv3",
author="Adolfo R. Brandes",
description="Google Analytics plugin for Tutor",
long_description=load_readme(),
packages=find_packages(exclude=["tests*"]),
include_package_data=True,
python_requires=">=3.11",
install_requires=["tutor>=21.0.0", "tutor-mfe>=21.0.0"],
extras_require={"dev": ["tutor[dev]>=21.0.0"]},
entry_points={
"tutor.plugin.v1": [
"google-analytics = tutor_google_analytics.plugin"
]
},
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Affero General Public License v3",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.1.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from tutor import hooks
from tutormfe.hooks import EXTERNAL_SCRIPTS


GOOGLE_ANALYTICS_LOADER = """
class GoogleAnalyticsLoader {
constructor({ config }) {
this.analyticsId = config.GOOGLE_ANALYTICS_4_ID;
}

loadScript() {
if (!this.analyticsId) {
return;
}

global.googleAnalytics = global.googleAnalytics || [];
const { googleAnalytics } = global;

// If the snippet was invoked do nothing.
if (googleAnalytics.invoked) {
return;
}

// Invoked flag, to make sure the snippet
// is never invoked twice.
googleAnalytics.invoked = true;

googleAnalytics.load = (key, options) => {
const scriptSrc = document.createElement('script');
scriptSrc.type = 'text/javascript';
scriptSrc.async = true;
scriptSrc.src = `https://www.googletagmanager.com/gtag/js?id=${key}`;

const scriptGtag = document.createElement('script');
scriptGtag.innerHTML = `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${key}');
`;

// Insert our scripts next to the first script element.
const first = document.getElementsByTagName('script')[0];
first.parentNode.insertBefore(scriptSrc, first);
first.parentNode.insertBefore(scriptGtag, first);
googleAnalytics._loadOptions = options; // eslint-disable-line no-underscore-dangle
};

// Load GoogleAnalytics with your key.
googleAnalytics.load(this.analyticsId);
}
}
"""


# Inline the loader into both build pipelines: env.config.jsx for legacy MFEs,
# customApp.tsx for the frontend-base site.
hooks.Filters.ENV_PATCHES.add_item(
("mfe-env-config-buildtime-definitions", GOOGLE_ANALYTICS_LOADER)
)
hooks.Filters.ENV_PATCHES.add_item(
("mfe-site-custom-app-definitions", GOOGLE_ANALYTICS_LOADER)
)

# Register the loader for both targets ("all" covers legacy MFEs and the site).
EXTERNAL_SCRIPTS.add_item(("all", "GoogleAnalyticsLoader"))