From 008c554d7fd1c9cedb818cb44311abca6486c0c8 Mon Sep 17 00:00:00 2001 From: Timur Olzhabayev Date: Wed, 9 Nov 2022 15:09:19 +0100 Subject: [PATCH] Echo: Add config option to prevent duplicate page views for GA4 (#57619) --- conf/defaults.ini | 3 ++ conf/sample.ini | 3 ++ packages/grafana-data/src/types/config.ts | 1 + packages/grafana-runtime/src/config.ts | 1 + pkg/api/dtos/index.go | 43 ++++++++++--------- pkg/api/frontendsettings.go | 1 + pkg/api/index.go | 43 ++++++++++--------- pkg/setting/setting.go | 17 +++++--- public/app/app.ts | 1 + .../echo/backends/analytics/GA4Backend.ts | 17 +++++--- public/app/core/services/echo/utils.ts | 3 +- 11 files changed, 77 insertions(+), 56 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index 305aa20b949..b071aabd65c 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -230,6 +230,9 @@ google_analytics_ua_id = # Google Analytics 4 tracking code, only enabled if you specify an id here google_analytics_4_id = +# When Google Analytics 4 Enhanced event measurement is enabled, we will try to avoid sending duplicate events and let Google Analytics 4 detect navigation changes, etc. +google_analytics_4_send_manual_page_views = false + # Google Tag Manager ID, only enabled if you specify an id here google_tag_manager_id = diff --git a/conf/sample.ini b/conf/sample.ini index 7a04486abeb..30d3e0f65b8 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -237,6 +237,9 @@ # Google Analytics 4 tracking code, only enabled if you specify an id here ;google_analytics_4_id = +# When Google Analytics 4 Enhanced event measurement is enabled, we will try to avoid sending duplicate events and let Google Analytics 4 detect navigation changes, etc. +;google_analytics_4_send_manual_page_views = false + # Google Tag Manager ID, only enabled if you specify an id here ;google_tag_manager_id = diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 06cbc24da04..0e592167184 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -211,6 +211,7 @@ export interface GrafanaConfig { secretsManagerPluginEnabled: boolean; googleAnalyticsId: string | undefined; googleAnalytics4Id: string | undefined; + googleAnalytics4SendManualPageViews: boolean; rudderstackWriteKey: string | undefined; rudderstackDataPlaneUrl: string | undefined; rudderstackSdkUrl: string | undefined; diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 73c89a41173..d92e8cab4cc 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -136,6 +136,7 @@ export class GrafanaBootConfig implements GrafanaConfig { }; googleAnalyticsId: undefined; googleAnalytics4Id: undefined; + googleAnalytics4SendManualPageViews = false; rudderstackWriteKey: undefined; rudderstackDataPlaneUrl: undefined; rudderstackSdkUrl: undefined; diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index ddb9494ed93..6d1263d0c1b 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -8,27 +8,28 @@ import ( ) type IndexViewData struct { - User *CurrentUser - Settings map[string]interface{} - AppUrl string - AppSubUrl string - GoogleAnalyticsId string - GoogleAnalytics4Id string - GoogleTagManagerId string - NavTree *navtree.NavTreeRoot - BuildVersion string - BuildCommit string - Theme string - NewGrafanaVersionExists bool - NewGrafanaVersion string - AppName string - AppNameBodyClass string - FavIcon template.URL - AppleTouchIcon template.URL - AppTitle string - Sentry *setting.Sentry - ContentDeliveryURL string - LoadingLogo template.URL + User *CurrentUser + Settings map[string]interface{} + AppUrl string + AppSubUrl string + GoogleAnalyticsId string + GoogleAnalytics4Id string + GoogleAnalytics4SendManualPageViews bool + GoogleTagManagerId string + NavTree *navtree.NavTreeRoot + BuildVersion string + BuildCommit string + Theme string + NewGrafanaVersionExists bool + NewGrafanaVersion string + AppName string + AppNameBodyClass string + FavIcon template.URL + AppleTouchIcon template.URL + AppTitle string + Sentry *setting.Sentry + ContentDeliveryURL string + LoadingLogo template.URL // Nonce is a cryptographic identifier for use with Content Security Policy. Nonce string } diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 3d82ded53ef..adb51d4cf15 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -124,6 +124,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i "queryHistoryEnabled": hs.Cfg.QueryHistoryEnabled, "googleAnalyticsId": setting.GoogleAnalyticsId, "googleAnalytics4Id": setting.GoogleAnalytics4Id, + "GoogleAnalytics4SendManualPageViews": setting.GoogleAnalytics4SendManualPageViews, "rudderstackWriteKey": setting.RudderstackWriteKey, "rudderstackDataPlaneUrl": setting.RudderstackDataPlaneUrl, "rudderstackSdkUrl": setting.RudderstackSdkUrl, diff --git a/pkg/api/index.go b/pkg/api/index.go index 65c66b67e44..c322b0d489d 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -98,27 +98,28 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat HelpFlags1: c.HelpFlags1, HasEditPermissionInFolders: hasEditPerm, }, - Settings: settings, - Theme: prefs.Theme, - AppUrl: appURL, - AppSubUrl: appSubURL, - GoogleAnalyticsId: setting.GoogleAnalyticsId, - GoogleAnalytics4Id: setting.GoogleAnalytics4Id, - GoogleTagManagerId: setting.GoogleTagManagerId, - BuildVersion: setting.BuildVersion, - BuildCommit: setting.BuildCommit, - NewGrafanaVersion: hs.grafanaUpdateChecker.LatestVersion(), - NewGrafanaVersionExists: hs.grafanaUpdateChecker.UpdateAvailable(), - AppName: setting.ApplicationName, - AppNameBodyClass: "app-grafana", - FavIcon: "public/img/fav32.png", - AppleTouchIcon: "public/img/apple-touch-icon.png", - AppTitle: "Grafana", - NavTree: navTree, - Sentry: &hs.Cfg.Sentry, - Nonce: c.RequestNonce, - ContentDeliveryURL: hs.Cfg.GetContentDeliveryURL(hs.License.ContentDeliveryPrefix()), - LoadingLogo: "public/img/grafana_icon.svg", + Settings: settings, + Theme: prefs.Theme, + AppUrl: appURL, + AppSubUrl: appSubURL, + GoogleAnalyticsId: setting.GoogleAnalyticsId, + GoogleAnalytics4Id: setting.GoogleAnalytics4Id, + GoogleAnalytics4SendManualPageViews: setting.GoogleAnalytics4SendManualPageViews, + GoogleTagManagerId: setting.GoogleTagManagerId, + BuildVersion: setting.BuildVersion, + BuildCommit: setting.BuildCommit, + NewGrafanaVersion: hs.grafanaUpdateChecker.LatestVersion(), + NewGrafanaVersionExists: hs.grafanaUpdateChecker.UpdateAvailable(), + AppName: setting.ApplicationName, + AppNameBodyClass: "app-grafana", + FavIcon: "public/img/fav32.png", + AppleTouchIcon: "public/img/apple-touch-icon.png", + AppTitle: "Grafana", + NavTree: navTree, + Sentry: &hs.Cfg.Sentry, + Nonce: c.RequestNonce, + ContentDeliveryURL: hs.Cfg.GetContentDeliveryURL(hs.License.ContentDeliveryPrefix()), + LoadingLogo: "public/img/grafana_icon.svg", } if !hs.AccessControl.IsDisabled() { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index cf48a800f44..d7ab99cb90c 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -137,13 +137,14 @@ var ( appliedEnvOverrides []string // analytics - GoogleAnalyticsId string - GoogleAnalytics4Id string - GoogleTagManagerId string - RudderstackDataPlaneUrl string - RudderstackWriteKey string - RudderstackSdkUrl string - RudderstackConfigUrl string + GoogleAnalyticsId string + GoogleAnalytics4Id string + GoogleAnalytics4SendManualPageViews bool + GoogleTagManagerId string + RudderstackDataPlaneUrl string + RudderstackWriteKey string + RudderstackSdkUrl string + RudderstackConfigUrl string // LDAP LDAPEnabled bool @@ -995,6 +996,8 @@ func (cfg *Cfg) Load(args CommandLineArgs) error { cfg.CheckForPluginUpdates = analytics.Key("check_for_plugin_updates").MustBool(true) GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String() GoogleAnalytics4Id = analytics.Key("google_analytics_4_id").String() + GoogleAnalytics4SendManualPageViews = analytics.Key("google_analytics_4_send_manual_page_views").MustBool(false) + GoogleTagManagerId = analytics.Key("google_tag_manager_id").String() RudderstackWriteKey = analytics.Key("rudderstack_write_key").String() RudderstackDataPlaneUrl = analytics.Key("rudderstack_data_plane_url").String() diff --git a/public/app/app.ts b/public/app/app.ts index 56fc8b5c7b7..466e6ba6b55 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -268,6 +268,7 @@ function initEchoSrv() { registerEchoBackend( new GA4EchoBackend({ googleAnalyticsId: config.googleAnalytics4Id, + googleAnalytics4SendManualPageViews: config.googleAnalytics4SendManualPageViews, }) ); } diff --git a/public/app/core/services/echo/backends/analytics/GA4Backend.ts b/public/app/core/services/echo/backends/analytics/GA4Backend.ts index 17b8f0f47f5..8a822f5d30d 100644 --- a/public/app/core/services/echo/backends/analytics/GA4Backend.ts +++ b/public/app/core/services/echo/backends/analytics/GA4Backend.ts @@ -11,16 +11,17 @@ declare global { export interface GA4EchoBackendOptions { googleAnalyticsId: string; + googleAnalytics4SendManualPageViews: boolean; user?: CurrentUserDTO; } export class GA4EchoBackend implements EchoBackend { supportedEvents = [EchoEventType.Pageview]; - trackedUserId: number | null = null; + googleAnalytics4SendManualPageViews = false; constructor(public options: GA4EchoBackendOptions) { const url = `https://www.googletagmanager.com/gtag/js?id=${options.googleAnalyticsId}`; - loadScript(url); + loadScript(url, true); window.dataLayer = window.dataLayer || []; window.gtag = function gtag() { @@ -28,12 +29,15 @@ export class GA4EchoBackend implements EchoBackend { const script = document.createElement('script'); script.onload = resolve; script.src = url; + script.async = async; document.head.appendChild(script); }); }