From f6dd01b04298794ed913dbc4878c5cf14417667c Mon Sep 17 00:00:00 2001 From: "Adolfo R. Brandes" Date: Wed, 15 Apr 2026 18:19:08 -0300 Subject: [PATCH] demo: add Google Analytics app to site configs Defines a local GoogleAnalyticsLoader implementing the ExternalScriptLoader interface from frontend-base, and wires it into both site.config.dev.tsx and site.config.build.tsx via a local googleAnalyticsApp. Demonstrates how operators can attach their own external script loaders to an app. Co-Authored-By: Claude --- site.config.build.tsx | 8 ++++ site.config.dev.tsx | 8 ++++ src/googleAnalytics/GoogleAnalyticsLoader.ts | 50 ++++++++++++++++++++ src/googleAnalytics/app.ts | 9 ++++ src/googleAnalytics/index.ts | 2 + 5 files changed, 77 insertions(+) create mode 100644 src/googleAnalytics/GoogleAnalyticsLoader.ts create mode 100644 src/googleAnalytics/app.ts create mode 100644 src/googleAnalytics/index.ts diff --git a/site.config.build.tsx b/site.config.build.tsx index f909cf6..21ef60d 100644 --- a/site.config.build.tsx +++ b/site.config.build.tsx @@ -3,6 +3,8 @@ import { authnApp } from '@openedx/frontend-app-authn'; import { instructorDashboardApp } from '@openedx/frontend-app-instructor-dashboard'; import { learnerDashboardApp } from '@openedx/frontend-app-learner-dashboard'; +import { googleAnalyticsApp } from './src/googleAnalytics'; + import './src/site.scss'; const siteConfig: SiteConfig = { @@ -21,6 +23,12 @@ const siteConfig: SiteConfig = { authnApp, learnerDashboardApp, instructorDashboardApp, + { + ...googleAnalyticsApp, + config: { + GOOGLE_ANALYTICS_4_ID: 'G-TEST123', + }, + }, ], externalRoutes: [ { diff --git a/site.config.dev.tsx b/site.config.dev.tsx index 6138b62..2af027d 100644 --- a/site.config.dev.tsx +++ b/site.config.dev.tsx @@ -3,6 +3,8 @@ import { authnApp } from '@openedx/frontend-app-authn'; import { instructorDashboardApp } from '@openedx/frontend-app-instructor-dashboard'; import { learnerDashboardApp } from '@openedx/frontend-app-learner-dashboard'; +import { googleAnalyticsApp } from './src/googleAnalytics'; + import './src/site.scss'; const siteConfig: SiteConfig = { @@ -21,6 +23,12 @@ const siteConfig: SiteConfig = { authnApp, learnerDashboardApp, instructorDashboardApp, + { + ...googleAnalyticsApp, + config: { + GOOGLE_ANALYTICS_4_ID: 'G-TEST123', + }, + }, ], externalRoutes: [ { diff --git a/src/googleAnalytics/GoogleAnalyticsLoader.ts b/src/googleAnalytics/GoogleAnalyticsLoader.ts new file mode 100644 index 0000000..af3b93b --- /dev/null +++ b/src/googleAnalytics/GoogleAnalyticsLoader.ts @@ -0,0 +1,50 @@ +import { AppConfig, ExternalScriptLoader } from '@openedx/frontend-base'; + +export default class GoogleAnalyticsLoader implements ExternalScriptLoader { + analyticsId: string; + + constructor({ config }: { config: AppConfig }) { + this.analyticsId = config.GOOGLE_ANALYTICS_4_ID as string; + } + + loadScript() { + if (!this.analyticsId) { + return; + } + + global.googleAnalytics = global.googleAnalytics || []; + // @ts-expect-error We just added googleAnalytics to global, it's there. + const { googleAnalytics } = global; + + if (googleAnalytics.invoked) { + return; + } + + 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}'); + `; + + const first = document.getElementsByTagName('script')[0]; + if (first?.parentNode === null) { + throw new Error('No script to insert Google analytics script before.'); + } + first.parentNode.insertBefore(scriptSrc, first); + first.parentNode.insertBefore(scriptGtag, first); + googleAnalytics._loadOptions = options; + }; + + googleAnalytics.load(this.analyticsId); + } +} diff --git a/src/googleAnalytics/app.ts b/src/googleAnalytics/app.ts new file mode 100644 index 0000000..8d6effd --- /dev/null +++ b/src/googleAnalytics/app.ts @@ -0,0 +1,9 @@ +import { App } from '@openedx/frontend-base'; +import GoogleAnalyticsLoader from './GoogleAnalyticsLoader'; + +const app: App = { + appId: 'org.openedx.frontend.app.googleAnalytics', + externalScripts: [GoogleAnalyticsLoader], +}; + +export default app; diff --git a/src/googleAnalytics/index.ts b/src/googleAnalytics/index.ts new file mode 100644 index 0000000..5270cf5 --- /dev/null +++ b/src/googleAnalytics/index.ts @@ -0,0 +1,2 @@ +export { default as GoogleAnalyticsLoader } from './GoogleAnalyticsLoader'; +export { default as googleAnalyticsApp } from './app';