From 4c7c18d9a17422b64fe02803da609a76c8050ee1 Mon Sep 17 00:00:00 2001 From: JasonCent <137332364+JasonCent@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:11:07 -0400 Subject: [PATCH 1/7] Update google_codelab_analytics.js --- .../google_codelab_analytics.js | 160 +++++++++++++++++- 1 file changed, 157 insertions(+), 3 deletions(-) diff --git a/codelab-elements/google-codelab-analytics/google_codelab_analytics.js b/codelab-elements/google-codelab-analytics/google_codelab_analytics.js index 3c1df8970..5039acc4d 100644 --- a/codelab-elements/google-codelab-analytics/google_codelab_analytics.js +++ b/codelab-elements/google-codelab-analytics/google_codelab_analytics.js @@ -17,7 +17,10 @@ goog.module('googlecodelabs.CodelabAnalytics'); +const Const = goog.require('goog.string.Const'); const EventHandler = goog.require('goog.events.EventHandler'); +const TrustedResourceUrl = goog.require('goog.html.TrustedResourceUrl'); +const {safeScriptEl} = goog.require('safevalues.dom'); /** * The general codelab action event fired for trackable interactions. @@ -38,6 +41,24 @@ const PAGEVIEW_EVENT = 'google-codelab-pageview'; */ const GAID_ATTR = 'gaid'; +/** + * The Google Analytics GA4 ID. + * @const {string} + */ +const GA4ID_ATTR = 'ga4id'; + +/** @const {string} */ +const GTAG = 'gtag'; + +/** + * Namespaced data layer for use with GA4 properties. Allows for independent + * data layers so that other data layers, like that for GTM, don't receive data + * they don't need. + * + * @const {string} + */ +const CODELAB_DATA_LAYER = 'codelabDataLayer'; + /** @const {string} */ const CODELAB_ID_ATTR = 'codelab-id'; @@ -47,6 +68,12 @@ const CODELAB_ID_ATTR = 'codelab-id'; */ const CODELAB_GAID_ATTR = 'codelab-gaid'; +/** + * The GA4ID defined by the current codelab. + * @const {string} + */ +const CODELAB_GA4ID_ATTR = 'codelab-ga4id'; + /** @const {string} */ const CODELAB_ENV_ATTR = 'environment'; @@ -103,6 +130,9 @@ class CodelabAnalytics extends HTMLElement { /** @private {?string} */ this.gaid_; + /** @private {?string} */ + this.ga4Id_; + /** @private {?string} */ this.codelabId_; @@ -125,8 +155,9 @@ class CodelabAnalytics extends HTMLElement { */ connectedCallback() { this.gaid_ = this.getAttribute(GAID_ATTR) || ''; + this.ga4Id_ = this.getAttribute(GA4ID_ATTR) || ''; - if (this.hasSetup_ || !this.gaid_) { + if (this.hasSetup_ || (!this.gaid_ && !this.ga4Id_)) { return; } @@ -139,6 +170,14 @@ class CodelabAnalytics extends HTMLElement { } else { this.init_(); } + + if (this.ga4Id_) { + this.initializeGa4_(); + } + + if (this.ga4Id_ && !this.gaid_) { + this.addEventListeners_(); + } } /** @private */ @@ -153,7 +192,7 @@ class CodelabAnalytics extends HTMLElement { addEventListeners_() { this.eventHandler_.listen(document.body, ACTION_EVENT, (e) => { - const detail = /** @type {AnalyticsTrackingEvent} */ ( + const detail = /** @type {!AnalyticsTrackingEvent} */ ( e.getBrowserEvent().detail); // Add tracking... this.trackEvent_( @@ -162,7 +201,7 @@ class CodelabAnalytics extends HTMLElement { this.eventHandler_.listen(document.body, PAGEVIEW_EVENT, (e) => { - const detail = /** @type {AnalyticsPageview} */ ( + const detail = /** @type {!AnalyticsPageview} */ ( e.getBrowserEvent().detail); this.trackPageview_(detail['page'], detail['title']); }); @@ -216,6 +255,7 @@ class CodelabAnalytics extends HTMLElement { * @private */ trackEvent_(category, opt_action, opt_label) { + // UA related section. const params = { // Always event for trackEvent_ method 'hitType': 'event', @@ -227,6 +267,30 @@ class CodelabAnalytics extends HTMLElement { 'eventLabel': opt_label || '', }; this.gaSend_(params); + + // GA4 related section. + if (!this.getGa4Ids_().length) { + return; + } + + window[CODELAB_DATA_LAYER] = window[CODELAB_DATA_LAYER] || []; + window[GTAG] = window[GTAG] || function() { + window[CODELAB_DATA_LAYER].push(arguments); + }; + + for (const ga4Id of this.getGa4Ids_()) { + window[GTAG]('event', category, { + // Snakecase naming convention is followed for all built-in GA4 event + // properties. + 'send_to': ga4Id, + // Camelcase naming convention is followed for all custom dimensions + // constructed in the custom element. + 'eventAction': opt_action || '', + 'eventLabel': opt_label || '', + 'codelabEnv': this.codelabEnv_ || '', + 'codelabId': this.codelabId_ || '', + }); + } } /** @@ -235,6 +299,7 @@ class CodelabAnalytics extends HTMLElement { * @private */ trackPageview_(opt_page, opt_title) { + // UA related section. const params = { 'hitType': 'pageview', 'dimension1': this.codelabEnv_, @@ -244,6 +309,33 @@ class CodelabAnalytics extends HTMLElement { 'title': opt_title || '' }; this.gaSend_(params); + + // GA4 related section. + if (!this.getGa4Ids_().length) { + return; + } + + window[CODELAB_DATA_LAYER] = window[CODELAB_DATA_LAYER] || []; + window[GTAG] = window[GTAG] || function() { + window[CODELAB_DATA_LAYER].push(arguments); + }; + + for (const ga4Id of this.getGa4Ids_()) { + window[GTAG]('event', 'page_view', { + // Snakecase naming convention is followed for all built-in GA4 event + // properties. + 'send_to': ga4Id, + 'page_location': + `${document.location.origin}${document.location.pathname}`, + 'page_path': opt_page || '', + 'page_title': opt_title || '', + // Camelcase naming convention is followed for all custom dimensions + // constructed in the custom element. + 'codelabCategory': this.codelabCategory_ || '', + 'codelabEnv': this.codelabEnv_ || '', + 'codelabId': this.codelabId_ || '', + }); + } } /** @@ -385,6 +477,68 @@ class CodelabAnalytics extends HTMLElement { } return isCreated; } + + /** + * Gets all GA4 IDs for the current page. + * @return {!Array} + * @private + */ + getGa4Ids_() { + if (!this.ga4Id_) { + return []; + } + const ga4Ids = []; + ga4Ids.push(this.ga4Id_); + const codelabGa4Id = this.getAttribute(CODELAB_GA4ID_ATTR); + if (codelabGa4Id) { + ga4Ids.push(codelabGa4Id); + } + if (ga4Ids.length) { + return ga4Ids; + } + return []; + } + + /** + * Initialize the gtag script element and namespaced data layer based on the + * codelabs primary GA4 ID. + * @private + */ + initializeGa4_() { + if (!this.ga4Id_) { + return; + } + + // First, set the GTAG data layer before pushing anything to it. + window[CODELAB_DATA_LAYER] = window[CODELAB_DATA_LAYER] || []; + + const firstScriptElement = document.getElementsByTagName('script')[0]; + const gtagScriptElement = /** @type {!HTMLScriptElement} */ ( + document.createElement('script')); + gtagScriptElement.async = true; + // Key for the formatted params below: + // 'id': the stream id for the GA4 analytics property. The gtag script + // element must only be created once, and only the ID of the primary + // stream is appended when creating the src for that element. + // Additional streams are initialized via the function call + // `window[GTAG]('config', ga4Id...` + // 'l': the namespaced dataLayer used to separate codelabs related GA4 + // data from other data layers that may exist on a site or page. + safeScriptEl.setSrc( + gtagScriptElement, TrustedResourceUrl.formatWithParams( + Const.from('//www.googletagmanager.com/gtag/js'), + {}, {'id': this.ga4Id_, 'l': CODELAB_DATA_LAYER})); + firstScriptElement.parentNode.insertBefore( + gtagScriptElement, firstScriptElement); + + window[GTAG] = function() { + window[CODELAB_DATA_LAYER].push(arguments); + }; + window[GTAG]('js', new Date(Date.now())); + + // Set send_page_view to false. We send pageviews manually. + window[GTAG]('config', this.ga4Id_, {send_page_view: false}); + } } exports = CodelabAnalytics; From 60399ab7610831e57f559159b829408352956d70 Mon Sep 17 00:00:00 2001 From: JasonCent <137332364+JasonCent@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:40:54 -0400 Subject: [PATCH 2/7] Update parse.go --- claat/parser/md/parse.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/claat/parser/md/parse.go b/claat/parser/md/parse.go index 346537c1c..efba695b0 100644 --- a/claat/parser/md/parse.go +++ b/claat/parser/md/parse.go @@ -45,17 +45,18 @@ import ( // Metadata constants for the YAML header const ( - MetaAuthors = "authors" - MetaSummary = "summary" - MetaID = "id" - MetaCategories = "categories" - MetaEnvironments = "environments" - MetaStatus = "status" - MetaFeedbackLink = "feedback_link" - MetaAnalyticsAccount = "analytics_account" - MetaTags = "tags" - MetaSource = "source" - MetaDuration = "duration" + MetaAuthors = "authors" + MetaSummary = "summary" + MetaID = "id" + MetaCategories = "categories" + MetaEnvironments = "environments" + MetaStatus = "status" + MetaFeedbackLink = "feedback_link" + MetaAnalyticsAccount = "analytics_account" + MetaAnalyticsGa4Account = "analytics_ga4_account" + MetaTags = "tags" + MetaSource = "source" + MetaDuration = "duration" ) const ( @@ -182,7 +183,7 @@ type docState struct { survey int // last used survey ID step *types.Step // current codelab step lastNode nodes.Node // last appended node - env []string // current enviornment + env []string // current environment cur *html.Node // current HTML node stack []*stackItem // cur and flags stack } @@ -439,6 +440,9 @@ func addMetadataToCodelab(m map[string]string, c *types.Codelab, opts parser.Opt case MetaAnalyticsAccount: // Directly assign the GA id to the codelab field. c.GA = v + case MetaAnalyticsGa4Account: + // Directly assign the GA id to the codelab field. + c.GA4 = v case MetaTags: // Standardize the tags and append to the codelab field. c.Tags = append(c.Tags, util.NormalizedSplit(v)...) From 633b3901623161d837ce992c4c0030d3ba4f8be8 Mon Sep 17 00:00:00 2001 From: JasonCent <137332364+JasonCent@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:43:28 -0400 Subject: [PATCH 3/7] Update parse_test.go --- claat/parser/md/parse_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/claat/parser/md/parse_test.go b/claat/parser/md/parse_test.go index e0ef8dd93..1b39bb415 100644 --- a/claat/parser/md/parse_test.go +++ b/claat/parser/md/parse_test.go @@ -171,6 +171,7 @@ func TestParseMetadata(t *testing.T) { Tags: []string{"kiosk", "web"}, Feedback: "https://www.google.com", GA: "12345", + GA4: "54321", Extra: map[string]string{}, } @@ -181,6 +182,7 @@ summary: abcdefghij categories: not, really environments: kiosk, web analytics_account: 12345 +analytics_ga4_account: 54321 feedback_link: https://www.google.com --- @@ -204,6 +206,7 @@ func TestParseMetadataPassMetadata(t *testing.T) { Tags: []string{"kiosk", "web"}, Feedback: "https://www.google.com", GA: "12345", + GA4: "54321", Extra: map[string]string{ "extra_field_two": "bbbbb", }, @@ -216,6 +219,7 @@ summary: abcdefghij categories: not, really environments: kiosk, web analytics_account: 12345 +analytics_ga4_account: 54321 feedback_link: https://www.google.com extra_field_one: aaaaa extra_field_two: bbbbb @@ -278,6 +282,7 @@ summary: abcdefghij categories: not, really environments: kiosk, web analytics_account: 12345 +analytics_ga4_account: 54321 feedback_link: https://www.google.com extrafieldone: aaaaa extrafieldtwo: bbbbb From 61b07e94b34f0a615b67f33f1f360a9fac806a23 Mon Sep 17 00:00:00 2001 From: JasonCent <137332364+JasonCent@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:45:09 -0400 Subject: [PATCH 4/7] Update template.go --- claat/render/template.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/claat/render/template.go b/claat/render/template.go index ad73cbc78..5a50c9607 100644 --- a/claat/render/template.go +++ b/claat/render/template.go @@ -35,14 +35,15 @@ import ( // Context is a template context during execution. type Context struct { - Env string - Prefix string - GlobalGA string - Format string - Meta *types.Meta - Steps []*types.Step - Updated string - Extra map[string]string // Extra variables passed from the command line. + Env string + Prefix string + GlobalGA string + GlobalGA4 string + Format string + Meta *types.Meta + Steps []*types.Step + Updated string + Extra map[string]string // Extra variables passed from the command line. } // Execute renders a template of the fmt format into w. @@ -104,6 +105,7 @@ var funcMap = map[string]interface{}{ res += kvLine(mdParse.MetaTags, strings.Join(meta.Tags, ",")) res += kvLine(mdParse.MetaFeedbackLink, meta.Feedback) res += kvLine(mdParse.MetaAnalyticsAccount, meta.GA) + res += kvLine(mdParse.MetaAnalyticsGa4Account, meta.GA4) res += kvLine(mdParse.MetaSource, meta.Source) res += kvLine(mdParse.MetaDuration, strconv.Itoa(meta.Duration)) From 2584e9263c5c131b2f91612bf1f0caf1c442c32c Mon Sep 17 00:00:00 2001 From: JasonCent <137332364+JasonCent@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:46:12 -0400 Subject: [PATCH 5/7] Update template.html --- claat/render/template.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/claat/render/template.html b/claat/render/template.html index dd56bbf23..335454583 100644 --- a/claat/render/template.html +++ b/claat/render/template.html @@ -34,8 +34,9 @@ - + Date: Thu, 22 Jun 2023 14:47:16 -0400 Subject: [PATCH 6/7] Update codelab.go --- claat/types/codelab.go | 1 + 1 file changed, 1 insertion(+) diff --git a/claat/types/codelab.go b/claat/types/codelab.go index cfe7a85b3..fdb0ddd82 100644 --- a/claat/types/codelab.go +++ b/claat/types/codelab.go @@ -35,6 +35,7 @@ type Meta struct { Tags []string `json:"tags"` // All environments supported by the codelab Feedback string `json:"feedback,omitempty"` // Issues and bugs are sent here GA string `json:"ga,omitempty"` // Codelab-specific GA tracking ID + GA4 string `json:"ga4,omitempty"` // Codelab-specific GA4 tracking ID Extra map[string]string `json:"extra,omitempty"` // Extra metadata specified in pass_metadata URL string `json:"url"` // Legacy ID; TODO: remove From b2931352b95c6a4d8d70c47b6c0bdc57a2d7c0c9 Mon Sep 17 00:00:00 2001 From: JasonCent <137332364+JasonCent@users.noreply.github.com> Date: Thu, 22 Jun 2023 16:10:39 -0400 Subject: [PATCH 7/7] Update google_codelab_analytics.js --- .../google_codelab_analytics.js | 160 +----------------- 1 file changed, 3 insertions(+), 157 deletions(-) diff --git a/codelab-elements/google-codelab-analytics/google_codelab_analytics.js b/codelab-elements/google-codelab-analytics/google_codelab_analytics.js index 5039acc4d..3c1df8970 100644 --- a/codelab-elements/google-codelab-analytics/google_codelab_analytics.js +++ b/codelab-elements/google-codelab-analytics/google_codelab_analytics.js @@ -17,10 +17,7 @@ goog.module('googlecodelabs.CodelabAnalytics'); -const Const = goog.require('goog.string.Const'); const EventHandler = goog.require('goog.events.EventHandler'); -const TrustedResourceUrl = goog.require('goog.html.TrustedResourceUrl'); -const {safeScriptEl} = goog.require('safevalues.dom'); /** * The general codelab action event fired for trackable interactions. @@ -41,24 +38,6 @@ const PAGEVIEW_EVENT = 'google-codelab-pageview'; */ const GAID_ATTR = 'gaid'; -/** - * The Google Analytics GA4 ID. - * @const {string} - */ -const GA4ID_ATTR = 'ga4id'; - -/** @const {string} */ -const GTAG = 'gtag'; - -/** - * Namespaced data layer for use with GA4 properties. Allows for independent - * data layers so that other data layers, like that for GTM, don't receive data - * they don't need. - * - * @const {string} - */ -const CODELAB_DATA_LAYER = 'codelabDataLayer'; - /** @const {string} */ const CODELAB_ID_ATTR = 'codelab-id'; @@ -68,12 +47,6 @@ const CODELAB_ID_ATTR = 'codelab-id'; */ const CODELAB_GAID_ATTR = 'codelab-gaid'; -/** - * The GA4ID defined by the current codelab. - * @const {string} - */ -const CODELAB_GA4ID_ATTR = 'codelab-ga4id'; - /** @const {string} */ const CODELAB_ENV_ATTR = 'environment'; @@ -130,9 +103,6 @@ class CodelabAnalytics extends HTMLElement { /** @private {?string} */ this.gaid_; - /** @private {?string} */ - this.ga4Id_; - /** @private {?string} */ this.codelabId_; @@ -155,9 +125,8 @@ class CodelabAnalytics extends HTMLElement { */ connectedCallback() { this.gaid_ = this.getAttribute(GAID_ATTR) || ''; - this.ga4Id_ = this.getAttribute(GA4ID_ATTR) || ''; - if (this.hasSetup_ || (!this.gaid_ && !this.ga4Id_)) { + if (this.hasSetup_ || !this.gaid_) { return; } @@ -170,14 +139,6 @@ class CodelabAnalytics extends HTMLElement { } else { this.init_(); } - - if (this.ga4Id_) { - this.initializeGa4_(); - } - - if (this.ga4Id_ && !this.gaid_) { - this.addEventListeners_(); - } } /** @private */ @@ -192,7 +153,7 @@ class CodelabAnalytics extends HTMLElement { addEventListeners_() { this.eventHandler_.listen(document.body, ACTION_EVENT, (e) => { - const detail = /** @type {!AnalyticsTrackingEvent} */ ( + const detail = /** @type {AnalyticsTrackingEvent} */ ( e.getBrowserEvent().detail); // Add tracking... this.trackEvent_( @@ -201,7 +162,7 @@ class CodelabAnalytics extends HTMLElement { this.eventHandler_.listen(document.body, PAGEVIEW_EVENT, (e) => { - const detail = /** @type {!AnalyticsPageview} */ ( + const detail = /** @type {AnalyticsPageview} */ ( e.getBrowserEvent().detail); this.trackPageview_(detail['page'], detail['title']); }); @@ -255,7 +216,6 @@ class CodelabAnalytics extends HTMLElement { * @private */ trackEvent_(category, opt_action, opt_label) { - // UA related section. const params = { // Always event for trackEvent_ method 'hitType': 'event', @@ -267,30 +227,6 @@ class CodelabAnalytics extends HTMLElement { 'eventLabel': opt_label || '', }; this.gaSend_(params); - - // GA4 related section. - if (!this.getGa4Ids_().length) { - return; - } - - window[CODELAB_DATA_LAYER] = window[CODELAB_DATA_LAYER] || []; - window[GTAG] = window[GTAG] || function() { - window[CODELAB_DATA_LAYER].push(arguments); - }; - - for (const ga4Id of this.getGa4Ids_()) { - window[GTAG]('event', category, { - // Snakecase naming convention is followed for all built-in GA4 event - // properties. - 'send_to': ga4Id, - // Camelcase naming convention is followed for all custom dimensions - // constructed in the custom element. - 'eventAction': opt_action || '', - 'eventLabel': opt_label || '', - 'codelabEnv': this.codelabEnv_ || '', - 'codelabId': this.codelabId_ || '', - }); - } } /** @@ -299,7 +235,6 @@ class CodelabAnalytics extends HTMLElement { * @private */ trackPageview_(opt_page, opt_title) { - // UA related section. const params = { 'hitType': 'pageview', 'dimension1': this.codelabEnv_, @@ -309,33 +244,6 @@ class CodelabAnalytics extends HTMLElement { 'title': opt_title || '' }; this.gaSend_(params); - - // GA4 related section. - if (!this.getGa4Ids_().length) { - return; - } - - window[CODELAB_DATA_LAYER] = window[CODELAB_DATA_LAYER] || []; - window[GTAG] = window[GTAG] || function() { - window[CODELAB_DATA_LAYER].push(arguments); - }; - - for (const ga4Id of this.getGa4Ids_()) { - window[GTAG]('event', 'page_view', { - // Snakecase naming convention is followed for all built-in GA4 event - // properties. - 'send_to': ga4Id, - 'page_location': - `${document.location.origin}${document.location.pathname}`, - 'page_path': opt_page || '', - 'page_title': opt_title || '', - // Camelcase naming convention is followed for all custom dimensions - // constructed in the custom element. - 'codelabCategory': this.codelabCategory_ || '', - 'codelabEnv': this.codelabEnv_ || '', - 'codelabId': this.codelabId_ || '', - }); - } } /** @@ -477,68 +385,6 @@ class CodelabAnalytics extends HTMLElement { } return isCreated; } - - /** - * Gets all GA4 IDs for the current page. - * @return {!Array} - * @private - */ - getGa4Ids_() { - if (!this.ga4Id_) { - return []; - } - const ga4Ids = []; - ga4Ids.push(this.ga4Id_); - const codelabGa4Id = this.getAttribute(CODELAB_GA4ID_ATTR); - if (codelabGa4Id) { - ga4Ids.push(codelabGa4Id); - } - if (ga4Ids.length) { - return ga4Ids; - } - return []; - } - - /** - * Initialize the gtag script element and namespaced data layer based on the - * codelabs primary GA4 ID. - * @private - */ - initializeGa4_() { - if (!this.ga4Id_) { - return; - } - - // First, set the GTAG data layer before pushing anything to it. - window[CODELAB_DATA_LAYER] = window[CODELAB_DATA_LAYER] || []; - - const firstScriptElement = document.getElementsByTagName('script')[0]; - const gtagScriptElement = /** @type {!HTMLScriptElement} */ ( - document.createElement('script')); - gtagScriptElement.async = true; - // Key for the formatted params below: - // 'id': the stream id for the GA4 analytics property. The gtag script - // element must only be created once, and only the ID of the primary - // stream is appended when creating the src for that element. - // Additional streams are initialized via the function call - // `window[GTAG]('config', ga4Id...` - // 'l': the namespaced dataLayer used to separate codelabs related GA4 - // data from other data layers that may exist on a site or page. - safeScriptEl.setSrc( - gtagScriptElement, TrustedResourceUrl.formatWithParams( - Const.from('//www.googletagmanager.com/gtag/js'), - {}, {'id': this.ga4Id_, 'l': CODELAB_DATA_LAYER})); - firstScriptElement.parentNode.insertBefore( - gtagScriptElement, firstScriptElement); - - window[GTAG] = function() { - window[CODELAB_DATA_LAYER].push(arguments); - }; - window[GTAG]('js', new Date(Date.now())); - - // Set send_page_view to false. We send pageviews manually. - window[GTAG]('config', this.ga4Id_, {send_page_view: false}); - } } exports = CodelabAnalytics;