Echo: Add config option to prevent duplicate page views for GA4 (#57619)

This commit is contained in:
Timur Olzhabayev 2022-11-09 15:09:19 +01:00 committed by GitHub
parent 463f993186
commit 008c554d7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 77 additions and 56 deletions

View File

@ -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 =

View File

@ -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 =

View File

@ -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;

View File

@ -136,6 +136,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
};
googleAnalyticsId: undefined;
googleAnalytics4Id: undefined;
googleAnalytics4SendManualPageViews = false;
rudderstackWriteKey: undefined;
rudderstackDataPlaneUrl: undefined;
rudderstackSdkUrl: undefined;

View File

@ -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
}

View File

@ -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,

View File

@ -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() {

View File

@ -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()

View File

@ -268,6 +268,7 @@ function initEchoSrv() {
registerEchoBackend(
new GA4EchoBackend({
googleAnalyticsId: config.googleAnalytics4Id,
googleAnalytics4SendManualPageViews: config.googleAnalytics4SendManualPageViews,
})
);
}

View File

@ -11,16 +11,17 @@ declare global {
export interface GA4EchoBackendOptions {
googleAnalyticsId: string;
googleAnalytics4SendManualPageViews: boolean;
user?: CurrentUserDTO;
}
export class GA4EchoBackend implements EchoBackend<PageviewEchoEvent, GA4EchoBackendOptions> {
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<PageviewEchoEvent, GA4EchoBac
};
window.gtag('js', new Date());
const configOptions: Gtag.CustomParams = {};
const configOptions: Gtag.CustomParams = {
page_path: window.location.pathname,
};
if (options.user) {
configOptions.user_id = getUserIdentifier(options.user);
}
this.googleAnalytics4SendManualPageViews = options.googleAnalytics4SendManualPageViews;
window.gtag('config', options.googleAnalyticsId, configOptions);
}
@ -41,9 +45,10 @@ export class GA4EchoBackend implements EchoBackend<PageviewEchoEvent, GA4EchoBac
if (!window.gtag) {
return;
}
window.gtag('set', 'page_path', e.payload.page);
window.gtag('event', 'page_view');
// this should prevent duplicate events in case enhanced tracking is enabled
if (this.googleAnalytics4SendManualPageViews) {
window.gtag('event', 'page_view', { page_path: e.payload.page });
}
};
// Not using Echo buffering, addEvent above sends events to GA as soon as they appear

View File

@ -14,11 +14,12 @@ export function getUserIdentifier(user: CurrentUserDTO) {
return user.email;
}
export function loadScript(url: string) {
export function loadScript(url: string, async = false) {
return new Promise((resolve) => {
const script = document.createElement('script');
script.onload = resolve;
script.src = url;
script.async = async;
document.head.appendChild(script);
});
}