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

pull/58547/head
Timur Olzhabayev 3 years ago committed by GitHub
parent 463f993186
commit 008c554d7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      conf/defaults.ini
  2. 3
      conf/sample.ini
  3. 1
      packages/grafana-data/src/types/config.ts
  4. 1
      packages/grafana-runtime/src/config.ts
  5. 43
      pkg/api/dtos/index.go
  6. 1
      pkg/api/frontendsettings.go
  7. 43
      pkg/api/index.go
  8. 17
      pkg/setting/setting.go
  9. 1
      public/app/app.ts
  10. 17
      public/app/core/services/echo/backends/analytics/GA4Backend.ts
  11. 3
      public/app/core/services/echo/utils.ts

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save