From c37eda2322e4546ade8933c45f786760635d4f17 Mon Sep 17 00:00:00 2001 From: Dina Hafez Date: Sun, 26 May 2024 10:57:08 +0100 Subject: [PATCH 01/12] Prepare code to remove GA --- static/src/javascripts/boot.js | 9 +- .../javascripts/bootstraps/enhanced/main.js | 24 --- .../javascripts/bootstraps/standard/main.js | 27 --- .../commercial/modules/hosted/gallery.js | 10 - .../commercial/modules/hosted/gallery.spec.js | 50 ----- .../common/modules/analytics/google.ts | 201 ------------------ .../modules/analytics/interaction-tracking.js | 44 +--- .../analytics/interaction-tracking.spec.js | 70 ------ static/src/javascripts/types/global.d.ts | 6 - 9 files changed, 2 insertions(+), 439 deletions(-) delete mode 100644 static/src/javascripts/projects/common/modules/analytics/google.ts diff --git a/static/src/javascripts/boot.js b/static/src/javascripts/boot.js index a39f14c5ccc..a8d25860770 100644 --- a/static/src/javascripts/boot.js +++ b/static/src/javascripts/boot.js @@ -9,7 +9,6 @@ import { captureOphanInfo } from 'lib/capture-ophan-info'; import { reportError } from 'lib/report-error'; import { cmp, getLocale, loadScript, onConsentChange } from '@guardian/libs'; import { getCookie } from 'lib/cookies'; -import { trackPerformance } from 'common/modules/analytics/google'; import { init as detectAdBlockers } from 'commercial/detect-adblock'; import ophan from 'ophan/ng'; @@ -58,13 +57,7 @@ const go = () => { if (!recordedConsentTime) { recordedConsentTime = true; - cmp.willShowPrivacyMessage().then(willShow => { - trackPerformance( - 'consent', - 'acquired', - willShow ? 'new' : 'existing' - ); - }); + cmp.willShowPrivacyMessage(); } // ------------------------------------------------------ diff --git a/static/src/javascripts/bootstraps/enhanced/main.js b/static/src/javascripts/bootstraps/enhanced/main.js index 706f849fe87..4b1e4a9f2af 100644 --- a/static/src/javascripts/bootstraps/enhanced/main.js +++ b/static/src/javascripts/bootstraps/enhanced/main.js @@ -7,7 +7,6 @@ import { markTime } from 'lib/user-timing'; import { catchErrorsWithContext } from 'lib/robust'; import { runAndTrackAbTests } from 'common/modules/experiments/ab'; import { initSport } from 'bootstraps/enhanced/sport'; -import { trackPerformance } from 'common/modules/analytics/google'; import { init as geolocationInit } from 'lib/geolocation'; import { init as initAcquisitionsLinkEnrichment } from 'common/modules/commercial/acquisitions-link-enrichment'; import { fetchAndRenderHeaderLinks } from "common/modules/support/header"; @@ -31,17 +30,6 @@ const bootEnhanced = () => { markTime('App Begin'); catchErrorsWithContext([ - [ - 'ga-user-timing-enhanced-start', - () => { - trackPerformance( - 'Javascript Load', - 'enhancedStart', - 'Enhanced start parse time' - ); - }, - ], - ['core-web-vitals', coreVitals], ['commercial-metrics', initCommercialMetrics], @@ -352,18 +340,6 @@ const bootEnhanced = () => { // Mark the end of synchronous execution. markTime('App End'); - catchErrorsWithContext([ - [ - 'ga-user-timing-enhanced-end', - () => { - trackPerformance( - 'Javascript Load', - 'enhancedEnd', - 'Enhanced end parse time' - ); - }, - ], - ]); }); }; diff --git a/static/src/javascripts/bootstraps/standard/main.js b/static/src/javascripts/bootstraps/standard/main.js index 67e8466d8ef..ed97193a405 100644 --- a/static/src/javascripts/bootstraps/standard/main.js +++ b/static/src/javascripts/bootstraps/standard/main.js @@ -26,7 +26,6 @@ import { init as initDynamicImport } from 'lib/dynamic-import-init'; import { newHeaderInit } from 'common/modules/navigation/new-header'; import { headerTopNavInit } from 'common/modules/navigation/header-top-nav'; import { fixSecondaryColumn } from 'common/modules/fix-secondary-column'; -import { trackPerformance } from 'common/modules/analytics/google'; import debounce from 'lodash/debounce'; import ophan from 'ophan/ng'; import { initEmbedResize } from "./emailEmbeds"; @@ -115,19 +114,6 @@ const bootStandard = () => { markTime('standard start'); - catchErrorsWithContext([ - [ - 'ga-user-timing-standard-start', - () => { - trackPerformance( - 'Javascript Load', - 'standardStart', - 'Standard start parse time' - ); - }, - ], - ]); - /* Add global pooled event listeners CAUTION: those are *passive*, which means calls to event.preventDefault @@ -180,19 +166,6 @@ const bootStandard = () => { showHiringMessage(); markTime('standard end'); - - catchErrorsWithContext([ - [ - 'ga-user-timing-standard-end', - () => { - trackPerformance( - 'Javascript Load', - 'standardEnd', - 'Standard end parse time' - ); - }, - ], - ]); }; export { bootStandard }; diff --git a/static/src/javascripts/projects/commercial/modules/hosted/gallery.js b/static/src/javascripts/projects/commercial/modules/hosted/gallery.js index 58e222f64b0..1c53b5f1458 100644 --- a/static/src/javascripts/projects/commercial/modules/hosted/gallery.js +++ b/static/src/javascripts/projects/commercial/modules/hosted/gallery.js @@ -12,7 +12,6 @@ import { FiniteStateMachine } from '../../../../lib/fsm'; import { loadCssPromise } from '../../../../lib/load-css-promise'; import { mediator } from '../../../../lib/mediator'; import { pushUrl } from '../../../../lib/url'; -import interactionTracking from '../../../common/modules/analytics/interaction-tracking'; class HostedGallery { constructor() { @@ -396,15 +395,6 @@ class HostedGallery { }); } - trackNavBetweenImages(data) { - if (data && data.nav) { - const trackingPrefix = config.get('page.trackingPrefix', ''); - interactionTracking.trackNonClickInteraction( - `${trackingPrefix + data.nav} - image ${this.index}`, - ); - } - } - onResize() { this.resizer = this.resizer || diff --git a/static/src/javascripts/projects/commercial/modules/hosted/gallery.spec.js b/static/src/javascripts/projects/commercial/modules/hosted/gallery.spec.js index 34f5019ea34..6986629de50 100644 --- a/static/src/javascripts/projects/commercial/modules/hosted/gallery.spec.js +++ b/static/src/javascripts/projects/commercial/modules/hosted/gallery.spec.js @@ -4,7 +4,6 @@ * This file should be considered deprecated and only exists for legacy 'hosted' pages */ -import interactionTracking from '../../../common/modules/analytics/interaction-tracking'; import { noop } from '../../../../lib/noop'; import { init } from './gallery'; import { galleryHtml } from './gallery-html'; @@ -19,9 +18,6 @@ jest.mock('../../../../lib/detect', () => ({ getBreakpoint: jest.fn(), hasTouchScreen: jest.fn(), })); -jest.mock('../../../common/modules/analytics/interaction-tracking', () => ({ - trackNonClickInteraction: jest.fn(() => Promise.resolve()), -})); jest.mock('../../../../lib/load-css-promise', () => ({ loadCssPromise: Promise.resolve(), })); @@ -99,50 +95,4 @@ describe('Hosted Gallery', () => { expect.stringContaining('minimise-oj'), ); }); - - it('should log navigation in GA when using arrow key navigation', () => { - gallery.handleKeyEvents({ keyCode: 40, preventDefault: noop }); - expect( - interactionTracking.trackNonClickInteraction, - ).toHaveBeenCalledWith('KeyPress:down - image 2'); - gallery.handleKeyEvents({ keyCode: 39, preventDefault: noop }); - expect( - interactionTracking.trackNonClickInteraction, - ).toHaveBeenCalledWith('KeyPress:right - image 3'); - gallery.handleKeyEvents({ keyCode: 38, preventDefault: noop }); - expect( - interactionTracking.trackNonClickInteraction, - ).toHaveBeenCalledWith('KeyPress:up - image 2'); - gallery.handleKeyEvents({ keyCode: 37, preventDefault: noop }); - expect( - interactionTracking.trackNonClickInteraction, - ).toHaveBeenCalledWith('KeyPress:left - image 1'); - }); - - it('should log navigation in GA when clicking through images', () => { - gallery.initScroll.call(gallery); - document.querySelector('.inline-arrow-down').click(); - expect( - interactionTracking.trackNonClickInteraction, - ).toHaveBeenCalledWith('Click - image 2'); - document.querySelector('.inline-arrow-up').click(); - expect( - interactionTracking.trackNonClickInteraction, - ).toHaveBeenCalledWith('Click - image 1'); - }); - - it('should log navigation in GA when scrolling through images', () => { - const nativeHTMLElement = window.HTMLElement; - window.HTMLElement = function () { - this.scrollTop = 20; - this.scrollHeight = 30; - }; - gallery.fadeContent({ - target: new window.HTMLElement(), - }); - expect( - interactionTracking.trackNonClickInteraction, - ).toHaveBeenCalledWith('Scroll - image 3'); - window.HTMLElement = nativeHTMLElement; - }); }); diff --git a/static/src/javascripts/projects/common/modules/analytics/google.ts b/static/src/javascripts/projects/common/modules/analytics/google.ts deleted file mode 100644 index 1b32324e528..00000000000 --- a/static/src/javascripts/projects/common/modules/analytics/google.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { getCLS, getFID, getLCP } from 'web-vitals'; -import { mediator } from 'lib/mediator'; - -const trackerName = - window.guardian.config.googleAnalytics?.trackers?.editorial ?? - 'no-ga-tracker-found'; - -const send = `${trackerName}.send`; - -const getTextContent = (el: HTMLElement): string => - (el.textContent ?? '').trim(); - -const trackNonClickInteraction = (actionName: string): void => { - window.ga(send, 'event', 'Interaction', actionName, { - nonInteraction: true, // to avoid affecting bounce rate - }); -}; - -const trackSamePageLinkClick = (target: HTMLElement, tag: string): void => { - window.ga(send, 'event', 'click', 'in page', tag, { - nonInteraction: true, // to avoid affecting bounce rate - dimension13: getTextContent(target), - }); -}; - -const trackExternalLinkClick = (target: HTMLElement, tag: string): void => { - const data: { - dimension13: string; - dimension48?: string; - } = { - dimension13: getTextContent(target), - }; - - const targetURL = target.getAttribute('href'); - - if (targetURL) { - data.dimension48 = targetURL; - } - - window.ga(send, 'event', 'click', 'external', tag, data); -}; - -const trackSponsorLogoLinkClick = (target: HTMLElement): void => { - const sponsorName = target.dataset.sponsor; - - window.ga(send, 'event', 'click', 'sponsor logo', sponsorName, { - nonInteraction: true, - }); -}; - -const trackNativeAdLinkClick = (slotName: string, tag: string): void => { - window.ga(send, 'event', 'click', 'native ad', tag, { - nonInteraction: true, - dimension25: slotName, - }); -}; - -const sendPerformanceEvent = (event: GoogleTimingEvent): void => { - const boostGaUserTimingFidelityMetrics: BoostGaUserTimingFidelityMetrics = { - standardStart: 'metric18', - standardEnd: 'metric19', - commercialStart: 'metric20', - commercialEnd: 'metric21', - enhancedStart: 'metric22', - enhancedEnd: 'metric23', - }; - - window.ga( - send, - 'timing', - event.timingCategory, - event.timingVar, - event.timeSincePageLoad, - event.timingLabel, - ); - - /* - send performance events as normal events too, - so we can avoid the 0.1% sampling that affects timing events - */ - if (window.guardian.config.switches.boostGaUserTimingFidelity) { - // these are our own metrics that map to our timing events - const metric = boostGaUserTimingFidelityMetrics[event.timingVar]; - - const fields: Record = { - nonInteraction: true, - dimension44: metric, // dimension44 is dotcomPerformance - }; - - fields[metric] = event.timeSincePageLoad; - - window.ga( - send, - 'event', - event.timingCategory, - event.timingVar, - event.timingLabel, - event.timeSincePageLoad, - fields, - ); - } -}; - -/* - Track important user timing metrics so that we can be notified and measure - over time in GA - https://developers.google.com/analytics/devguides/collection/analyticsjs/user-timings - Tracks into Behaviour > Site Speed > User Timings in GA -*/ -const trackPerformance = ( - timingCategory: string, - timingVar: keyof BoostGaUserTimingFidelityMetrics, - timingLabel: string, -): void => { - const timeSincePageLoad = Math.round(window.performance.now()); - const event: GoogleTimingEvent = { - timingCategory, - timingVar, - timeSincePageLoad, - timingLabel, - }; - - // @ts-expect-error -- no-unnecessary-condition error - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- extra safety if ga is undefined - if (window.ga ?? false) { - sendPerformanceEvent(event); - } else { - const timingEvents = - window.guardian.config.googleAnalytics?.timingEvents ?? []; - const sendDeferredEventQueue = (): void => { - timingEvents.map(sendPerformanceEvent); - mediator.off('modules:ga:ready', sendDeferredEventQueue); - }; - - mediator.on('modules:ga:ready', sendDeferredEventQueue); - timingEvents.push(event); - } -}; - -type CoreVitalsArgs = { - name: string; - delta: number; - id: string; -}; - -// This matches DCR implementation -// https://www.npmjs.com/package/web-vitals#using-analyticsjs -const sendCoreVital = ({ name, delta, id }: CoreVitalsArgs): void => { - const { ga } = window; - - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- extra safety in case it’s undefined - if (!ga) { - return; - } - - ga(send, 'event', { - eventCategory: 'Web Vitals', - eventAction: name, - // Google Analytics metrics must be integers, so the value is rounded. - // For CLS the value is first multiplied by 1000 for greater precision - // (note: increase the multiplier for greater precision if needed). - eventValue: Math.round(name === 'CLS' ? delta * 1000 : delta), - // The `id` value will be unique to the current page load. When sending - // multiple values from the same page (e.g. for CLS), Google Analytics can - // compute a total by grouping on this ID (note: requires `eventLabel` to - // be a dimension in your report). - eventLabel: id, - // Use a non-interaction event to avoid affecting bounce rate. - nonInteraction: true, - }); -}; - -// ////////////////////// -// Core Vitals Reporting -// Supported only in Chromium but npm module tested in all our supported browsers -// https://www.npmjs.com/package/web-vitals#browser-support - -// Only send for roughly 5% of users -// We want all or nothing on the corevitals so that they can be easily compared for a single pageview -// so we do this here rather than in the sendCoreVital function -const randomPerc = Math.random() * 100; -const coreVitalsSampleRate = 5; - -if (randomPerc <= coreVitalsSampleRate) { - // CLS and LCP are captured when the page lifecycle changes to 'hidden'. - // https://developers.google.com/web/updates/2018/07/page-lifecycle-api#advice-hidden - getCLS(sendCoreVital); // https://github.com/GoogleChrome/web-vitals#getcls (This is actually DCLS, as doesn't track CLS in iframes, see https://github.com/WICG/layout-instability#cumulative-scores) - getLCP(sendCoreVital); // https://github.com/GoogleChrome/web-vitals#getlcp - - // FID is captured when a user interacts with the page - getFID(sendCoreVital); // https://github.com/GoogleChrome/web-vitals#getfid -} - -export { - trackNonClickInteraction, - trackSamePageLinkClick, - trackExternalLinkClick, - trackSponsorLogoLinkClick, - trackNativeAdLinkClick, - trackPerformance, -}; diff --git a/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.js b/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.js index 77aef73192a..429f2081495 100644 --- a/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.js +++ b/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.js @@ -1,32 +1,10 @@ import { mediator } from 'lib/mediator'; import { storage } from '@guardian/libs'; -import { - trackSamePageLinkClick as trackSamePageLinkClickGoogle, - trackNonClickInteraction as trackNonClickInteractionGoogle, - trackExternalLinkClick as trackExternalLinkClickGoogle, - trackSponsorLogoLinkClick as trackSponsorLogoLinkClickGoogle, -} from 'common/modules/analytics/google'; import { catchErrorsWithContext } from 'lib/robust'; const NG_STORAGE_KEY = 'gu.analytics.referrerVars'; let loc = document.location; - -const isSponsorLogoLinkClick = (target) => - target.hasAttribute('data-sponsor'); - -// used where we don't have an element to pass as a tag, eg. keyboard interaction -const trackNonClickInteraction = (actionName) => { - trackNonClickInteractionGoogle(actionName); -}; - -const trackSamePageLinkClick = (spec) => { - // Do not perform a same-page track link when there isn't a tag. - if (spec.tag) { - trackSamePageLinkClickGoogle(spec.target, spec.tag); - } -}; - const trackInternalLinkClick = (spec) => { // Store in session storage. // GA and Omniture will both pick it up on next page load, @@ -39,36 +17,17 @@ const trackInternalLinkClick = (spec) => { storage.session.set(NG_STORAGE_KEY, storeObj); }; -const trackExternalLinkClick = (spec) => { - // Execute the GA and Omniture tracking in parallel - // and rely on Omniture to provide a 500 ms delay so they both get a chance to complete. - // TODO when Omniture goes away, implement the delay ourselves. - trackExternalLinkClickGoogle(spec.target, spec.tag); -}; - const trackClick = (spec) => { if (!spec.validTarget) { return; } - if (isSponsorLogoLinkClick(spec.target)) { - return trackSponsorLogoLinkClickGoogle(spec.target); - } - if (spec.sameHost) { - if (spec.samePage) { - trackSamePageLinkClick(spec); - } else { - trackInternalLinkClick(spec); - } - } else { - trackExternalLinkClick(spec); + trackInternalLinkClick(spec); } }; const addHandlers = () => { - mediator.on('module:clickstream:interaction', trackNonClickInteraction); - mediator.on('module:clickstream:click', spec => { // We don't want tracking errors to terminate the event emitter, as // this will mean other event listeners will not be called. @@ -93,5 +52,4 @@ const init = (options = {}) => { export default { init, trackClick, - trackNonClickInteraction, }; diff --git a/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.spec.js b/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.spec.js index 1666ca8b3b1..8c8b3f1ed42 100644 --- a/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.spec.js +++ b/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.spec.js @@ -1,8 +1,3 @@ -import { - trackSamePageLinkClick, - trackExternalLinkClick, - trackSponsorLogoLinkClick, -} from 'common/modules/analytics/google'; import { mediator } from 'lib/mediator'; import { storage } from '@guardian/libs'; @@ -10,12 +5,6 @@ import interactionTracking from './interaction-tracking'; jest.mock('lib/raven'); -jest.mock('common/modules/analytics/google', () => ({ - trackSamePageLinkClick: jest.fn(), - trackExternalLinkClick: jest.fn(), - trackSponsorLogoLinkClick: jest.fn(), -})); - describe('interaction-tracking', () => { afterEach(() => { storage.session.remove('gu.analytics.referrerVars'); @@ -23,34 +12,6 @@ describe('interaction-tracking', () => { mediator.removeEvent('module:clickstream:click'); }); - test('should log a clickstream event for an in-page link', () => { - interactionTracking.init(); - - mediator.emit('module:clickstream:click', { - target: document.documentElement, - samePage: true, - sameHost: true, - validTarget: true, - tag: true, - }); - - expect(trackSamePageLinkClick).toHaveBeenCalledTimes(1); - }); - - test('should not log clickstream events with an invalidTarget', () => { - interactionTracking.init(); - - mediator.emit('module:clickstream:click', { - target: document.documentElement, - samePage: true, - sameHost: true, - validTarget: false, - tag: true, - }); - - expect(trackSamePageLinkClick).toHaveBeenCalledTimes(1); - }); - test('should use local storage for same-host links', () => { const pathName = '/foo/bar'; const tagName = 'tag in localstorage'; @@ -70,35 +31,4 @@ describe('interaction-tracking', () => { expect(referrerVars.tag).toEqual(tagName); expect(referrerVars.path).toEqual(pathName); }); - - test('should log a clickstream event for an external link', () => { - interactionTracking.init(); - - mediator.emit('module:clickstream:click', { - target: document.createElement('a'), - samePage: false, - sameHost: false, - validTarget: true, - tag: 'tag', - }); - - expect(trackExternalLinkClick).toHaveBeenCalledTimes(1); - }); - - test('should log a clickstream event for a sponsor logo link', () => { - interactionTracking.init(); - - const el = document.createElement('a'); - el.setAttribute('data-sponsor', 'Sponsor'); - - mediator.emit('module:clickstream:click', { - target: el, - samePage: false, - sameHost: false, - validTarget: true, - tag: 'tag', - }); - - expect(trackSponsorLogoLinkClick).toHaveBeenCalledTimes(1); - }); }); diff --git a/static/src/javascripts/types/global.d.ts b/static/src/javascripts/types/global.d.ts index 06dd322f616..cabffd60b20 100644 --- a/static/src/javascripts/types/global.d.ts +++ b/static/src/javascripts/types/global.d.ts @@ -106,12 +106,6 @@ interface Config { [key: `${string}Variant`]: 'variant'; }; isDotcomRendering: boolean; - googleAnalytics?: { - trackers?: { - editorial?: string; - }; - timingEvents?: GoogleTimingEvent[]; - }; stage: Stage; } From f4b5a1d8a49b61fb9bea557fe16e85f45bad11bf Mon Sep 17 00:00:00 2001 From: Dina Hafez Date: Tue, 28 May 2024 17:01:11 +0100 Subject: [PATCH 02/12] Continue preparing code to remove GA --- admin/app/jobs/AnalyticsSanityCheckJob.scala | 14 +- admin/app/services/CloudWatchStats.scala | 3 - .../r2/interactivePageWithJQuery.html | 10 -- ...geWithScriptsRemovedAndJQueryRetained.html | 10 -- .../amp/hostedGoogleAnalytics.scala.html | 34 ---- .../guardianAmpHostedArticle.scala.html | 1 - .../guardianAmpHostedGallery.scala.html | 1 - .../hosted/guardianAmpHostedVideo.scala.html | 1 - .../inlineJS/blocking/config.scala.js | 3 - .../app/templates/javaScriptConfig.scala.js | 10 +- .../views/fragments/analytics/base.scala.html | 1 - .../fragments/analytics/google.scala.html | 166 ------------------ .../support/GoogleAnalyticsAccount.scala | 34 ---- .../13-implement-google-analytics.md | 116 ------------ identity/app/views/errors/error.scala.html | 1 - .../javascripts/bootstraps/enhanced/common.js | 16 -- .../bootstraps/enhanced/media-player.js | 1 - .../bootstraps/enhanced/video-player.js | 18 -- .../javascripts/bootstraps/videojs-embed.js | 1 - .../common/modules/analytics/discussion.js | 19 -- .../common/modules/atoms/youtube-player.ts | 28 --- .../common/modules/atoms/youtube-tracking.js | 14 -- .../common/modules/navigation/membership.js | 10 -- .../modules/tracking/google-analytics.ts | 9 - .../projects/common/modules/video/events.js | 36 ---- .../common/modules/video/ga-helper.ts | 89 ---------- 26 files changed, 2 insertions(+), 644 deletions(-) delete mode 100644 commercial/app/views/fragments/amp/hostedGoogleAnalytics.scala.html delete mode 100644 common/app/views/fragments/analytics/google.scala.html delete mode 100644 common/app/views/support/GoogleAnalyticsAccount.scala delete mode 100644 docs/03-dev-howtos/13-implement-google-analytics.md delete mode 100644 static/src/javascripts/projects/common/modules/tracking/google-analytics.ts delete mode 100644 static/src/javascripts/projects/common/modules/video/ga-helper.ts diff --git a/admin/app/jobs/AnalyticsSanityCheckJob.scala b/admin/app/jobs/AnalyticsSanityCheckJob.scala index 065e4a465b9..0e793fc4036 100644 --- a/admin/app/jobs/AnalyticsSanityCheckJob.scala +++ b/admin/app/jobs/AnalyticsSanityCheckJob.scala @@ -27,23 +27,12 @@ class AnalyticsSanityCheckJob(ophanApi: OphanApi) extends GuLogging { }, ) - val googleConversionRate = GaugeMetric( - name = "google-percent-conversion", - description = "The percentage of raw page views that contain a recorded Google Analytics page view", - metricUnit = StandardUnit.Percent, - get = () => { - googlePageViews.get.toDouble / rawPageViews.get.toDouble * 100.0d - }, - ) - def run()(implicit executionContext: ExecutionContext): Unit = { val fRawPageViews: Future[GetMetricStatisticsResult] = CloudWatchStats.rawPageViews() - val fGooglePageViews = CloudWatchStats.googleAnalyticsPageViews() val fOphanViews = ophanViews() for { rawPageViewsStats <- fRawPageViews - googlePageViewsStats <- fGooglePageViews ophanViewsCount <- fOphanViews } yield { @@ -51,10 +40,9 @@ class AnalyticsSanityCheckJob(ophanApi: OphanApi) extends GuLogging { stats.getDatapoints.asScala.headOption.map(_.getSum.longValue).getOrElse(0L) rawPageViews.set(metricLastSum(rawPageViewsStats)) - googlePageViews.set(metricLastSum(googlePageViewsStats)) ophanPageViews.set(ophanViewsCount) - CloudWatch.putMetrics("Analytics", List(ophanConversionRate, googleConversionRate), List.empty) + CloudWatch.putMetrics("Analytics", List(ophanConversionRate), List.empty) } } diff --git a/admin/app/services/CloudWatchStats.scala b/admin/app/services/CloudWatchStats.scala index 92ca85b5dc6..6f4d0bfc36f 100644 --- a/admin/app/services/CloudWatchStats.scala +++ b/admin/app/services/CloudWatchStats.scala @@ -44,7 +44,4 @@ object CloudWatchStats extends GuLogging { def rawPageViews()(implicit executionContext: ExecutionContext): Future[GetMetricStatisticsResult] = sanityData("kpis-page-views") - - def googleAnalyticsPageViews()(implicit executionContext: ExecutionContext): Future[GetMetricStatisticsResult] = - sanityData("kpis-analytics-page-views-google") } diff --git a/admin/test/resources/pagepresser/r2/interactivePageWithJQuery.html b/admin/test/resources/pagepresser/r2/interactivePageWithJQuery.html index 8aff45204f5..d65f4714991 100644 --- a/admin/test/resources/pagepresser/r2/interactivePageWithJQuery.html +++ b/admin/test/resources/pagepresser/r2/interactivePageWithJQuery.html @@ -1662,16 +1662,6 @@

Contact us

- - -@* GA pageview confidence pixel *@ - diff --git a/commercial/app/views/hosted/guardianAmpHostedArticle.scala.html b/commercial/app/views/hosted/guardianAmpHostedArticle.scala.html index 65a0b4975ad..f5abdf9ac47 100644 --- a/commercial/app/views/hosted/guardianAmpHostedArticle.scala.html +++ b/commercial/app/views/hosted/guardianAmpHostedArticle.scala.html @@ -24,7 +24,6 @@ - @fragments.amp.hostedGoogleAnalytics(page) diff --git a/commercial/app/views/hosted/guardianAmpHostedGallery.scala.html b/commercial/app/views/hosted/guardianAmpHostedGallery.scala.html index dc4e1c7dae8..9188f6a06a4 100644 --- a/commercial/app/views/hosted/guardianAmpHostedGallery.scala.html +++ b/commercial/app/views/hosted/guardianAmpHostedGallery.scala.html @@ -34,7 +34,6 @@ }/count/pv.gif"> - @fragments.amp.hostedGoogleAnalytics(page) diff --git a/commercial/app/views/hosted/guardianAmpHostedVideo.scala.html b/commercial/app/views/hosted/guardianAmpHostedVideo.scala.html index f163bcceeb5..2701d321957 100644 --- a/commercial/app/views/hosted/guardianAmpHostedVideo.scala.html +++ b/commercial/app/views/hosted/guardianAmpHostedVideo.scala.html @@ -27,7 +27,6 @@ - @fragments.amp.hostedGoogleAnalytics(page) diff --git a/common/app/templates/inlineJS/blocking/config.scala.js b/common/app/templates/inlineJS/blocking/config.scala.js index af4991555bd..1c98da8dddf 100644 --- a/common/app/templates/inlineJS/blocking/config.scala.js +++ b/common/app/templates/inlineJS/blocking/config.scala.js @@ -30,9 +30,6 @@ window.guardian = { active: undefined, onDetect: [] }, - googleAnalytics: { - initialiseGa: undefined - }, config: @JavaScript(templates.js.javaScriptConfig(page).body) }; diff --git a/common/app/templates/javaScriptConfig.scala.js b/common/app/templates/javaScriptConfig.scala.js index 353b49260ee..7a6bf5e47b5 100644 --- a/common/app/templates/javaScriptConfig.scala.js +++ b/common/app/templates/javaScriptConfig.scala.js @@ -3,7 +3,7 @@ @import conf.Static @import conf.Configuration @import play.api.libs.json.Json -@import views.support.{CamelCase, JavaScriptPage, GoogleAnalyticsAccount} +@import views.support.{CamelCase, JavaScriptPage} @import conf.Configuration.environment @import navigation.NavMenu @@ -46,14 +46,6 @@ } } }, - "googleAnalytics": { - "trackers": { - "editorialTest": "@{GoogleAnalyticsAccount.editorialTest.trackerName}", - "editorialProd": "@{GoogleAnalyticsAccount.editorialProd.trackerName}", - "editorial": "@{GoogleAnalyticsAccount.editorialTracker(context).trackerName}" - }, - "timingEvents": [] - }, "libs": { "googletag": "@{Configuration.javascript.config("googletagJsUrl")}", "cmp": { "fullVendorDataUrl": "/commercial/cmp/vendorlist.json", diff --git a/common/app/views/fragments/analytics/base.scala.html b/common/app/views/fragments/analytics/base.scala.html index eb4ed09c9e7..ca3829f902a 100644 --- a/common/app/views/fragments/analytics/base.scala.html +++ b/common/app/views/fragments/analytics/base.scala.html @@ -8,5 +8,4 @@ Core Analytics ******************************************************************************@ - @fragments.analytics.google(page) } diff --git a/common/app/views/fragments/analytics/google.scala.html b/common/app/views/fragments/analytics/google.scala.html deleted file mode 100644 index dc5396db72b..00000000000 --- a/common/app/views/fragments/analytics/google.scala.html +++ /dev/null @@ -1,166 +0,0 @@ -@(page: model.Page)(implicit request: RequestHeader, context: model.ApplicationContext) - -@import play.api.Mode.Dev -@import views.support.Commercial.listSponsorLogosOnPage -@import views.support.{Commercial, GoogleAnalyticsAccount} -@import conf.Configuration -@import common.Edition -@import navigation.NavMenu - - diff --git a/common/app/views/support/GoogleAnalyticsAccount.scala b/common/app/views/support/GoogleAnalyticsAccount.scala deleted file mode 100644 index 692e59090fc..00000000000 --- a/common/app/views/support/GoogleAnalyticsAccount.scala +++ /dev/null @@ -1,34 +0,0 @@ -package views.support - -import conf.Configuration.environment -import model.ApplicationContext - -object GoogleAnalyticsAccount { - - // NOTE that the 'samples rates' when set to 0, seem to be 100% - case class Tracker( - trackingId: String, - trackerName: String, - samplePercentage: Int = 100, - siteSpeedSamplePercentage: Double = 0.1, - ) - - // The "All editorial" property in the main GA account ("GNM Universal") - val editorialProd = Tracker("UA-78705427-1", "allEditorialPropertyTracker") - - /* - The "Guardian Test" property in the "Guardian Test" account. - I recommend using this until you're sure your GA events are working as intended, - to avoid polling the production GA property with incorrect data. - - Sample rate is set to 5% so we don't send too many events to the test tracker. - Sending too many events risks bumping us into a higher tier and costing us money. - */ - val editorialTest = Tracker("UA-33592456-1", "guardianTestPropertyTracker", 5) - - private def useProdTracker(context: ApplicationContext) = environment.isProd && !context.isPreview - - def editorialTracker(context: ApplicationContext): Tracker = - if (useProdTracker(context)) editorialProd else editorialTest - -} diff --git a/docs/03-dev-howtos/13-implement-google-analytics.md b/docs/03-dev-howtos/13-implement-google-analytics.md deleted file mode 100644 index 5a9190817d9..00000000000 --- a/docs/03-dev-howtos/13-implement-google-analytics.md +++ /dev/null @@ -1,116 +0,0 @@ -# Implement Google Analytics - -This describes how to work with Google Analytics (GA) - -## Hints - -Always discuss the reporting requirements with [data solutions](mailto:data.solutions@guardian.co.uk) and [google analytics](google.analyticscore@guardian.co.uk) teams before you implement any new dimensions. - -Talk to the mobile apps team early, things can be a slightly different once you take their needs into account (eg: web pages vs app screens) - -## Dashboards - -- [Google Analytics](https://analytics.google.com) -- [Confidence graph](https://frontend.gutools.co.uk/analytics/confidence) - -## Page View tracking - -The main entry point for google pageview tracking is in -[`analytics/base.scala.html`](https://github.com/guardian/frontend/blob/main/common/app/views/fragments/analytics/base.scala.html#L12) - -We are careful about how many tracking events we push to the GA test so only send 5% of traffic to the test account. In your local environment we are sending 100% to make development easier. - -We implement the standard [async GA tracking](https://developers.google.com/analytics/devguides/collection/analyticsjs/) - -We then send through all the custom [`dimensions`](https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets) to GA. - -All of our custom `dimensions` are [`hit scoped`](https://support.google.com/analytics/answer/2709828?hl=en#example-hit) (scopes are `user`, `session` or `hit`) -apart from `dimension15`(identityId) and `dimension2`(ophanBrowserId) which is a `user` scoped - -### Data dictionaries -We maintain documentation that describes all the custom `dimensions`, `events` and `metrics` used within GA - -- [Dimensions](https://docs.google.com/spreadsheets/d/1MmWHNeeiQE_dzekImIP9Tv4beLx_8JzWx3rOtCp4PGg) -- [Events](https://docs.google.com/spreadsheets/d/1KvBDyguXDtww9qTipD5L3D9NbH4IgkbRFWlbFTA3J2E) -- [Metrics](https://docs.google.com/spreadsheets/d/1KDZ3aImiI3CnSaxAVWOkgBxKQTZqD1QsGRoMDXlc2YQ) - -## Events - - -### Media Events - -The main entry point for Media (Video and Audio) tracking is -[`video/events.js`](https://github.com/guardian/frontend/blob/main/static/src/javascripts/projects/common/modules/video/events.js) - -This is an example of the media event using the following custom `dimensions` - -``` - var fieldsObject = { - eventCategory: category, - eventAction: action, - eventLabel: canonicalUrl, - dimension19: mediaEvent.mediaId, - dimension20: playerName - }; - // Increment the appropriate metric based on the event type - var metricId = metrics[mediaEvent.eventType]; - if (metricId) { - fieldsObject[metricId] = 1; - } -``` - -and the following custom [`metrics`](https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#metric) - -``` - var events = { - 'play': 'metric1', - 'skip': 'metric2', - 'watched25': 'metric3', - 'watched50': 'metric4', - 'watched75': 'metric5', - 'end': 'metric6' - }; -``` - - -Incrementing metrics in this way is done to give a truer account of the actions that are happening on the video. Out of the box it would give a session level interaction, not a hit level. - - -### Click Events - -The main entry point for click tracking is [analytics/interaction-tracking.js](https://github.com/guardian/frontend/blob/main/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.js) - -The click actions currently being tracked are: - -- In-page clicks (opening nav) that don't cause page load -- Internal clicks (navigating to another internal page on gu.com) -- External clicks (going to another domain) -- Clicks on sponsors' logos (regardless of destination) -- Clicks on native ads - -interaction-tracking.js is an abstraction over the top of [clickstream.js](https://github.com/guardian/frontend/blob/main/static/src/javascripts/projects/common/modules/ui/clickstream.js) that sends the events to both Omniture and Google. - -TODO: - -- When Omniture is removed: - - Re-implement delay when clicking external links which is currently handled by the omniture JS - - Re-implement the deletion of session storage which is tracked between pages - -### Discussion Events - -The comments event is a custom event defined in [analytics/discussion](https://github.com/guardian/frontend/blob/main/static/src/javascripts/projects/common/modules/analytics/discussion.js) - -Most discussion events can be tracked with click events so the only GA custom event for discussion is for 'scroll'. - -The custom category for tracking a user scrolling to the comments is *element view* with an action of *onpage item* and a label of *scroll to comments*. - -### 404 pages - -Check [chris's PR](https://github.com/guardian/frontend/pull/14114) for implementation details - -### AMP - -Main entry point for AMP analytics is -[amp/googleAnalytics.scala.html](https://github.com/guardian/frontend/blob/main/common/app/views/fragments/amp/googleAnalytics.scala.html) - - diff --git a/identity/app/views/errors/error.scala.html b/identity/app/views/errors/error.scala.html index 623edb1488d..180450dd017 100644 --- a/identity/app/views/errors/error.scala.html +++ b/identity/app/views/errors/error.scala.html @@ -84,7 +84,6 @@

diff --git a/static/src/javascripts/bootstraps/enhanced/common.js b/static/src/javascripts/bootstraps/enhanced/common.js index 2d36d8900a5..014759f31d7 100644 --- a/static/src/javascripts/bootstraps/enhanced/common.js +++ b/static/src/javascripts/bootstraps/enhanced/common.js @@ -48,7 +48,6 @@ import { } from 'common/modules/ui/cmp-ui'; import { signInGate } from 'common/modules/identity/sign-in-gate'; import { handleBraze } from 'common/modules/commercial/braze/buildBrazeMessaging'; -import { init as initGoogleAnalytics } from 'common/modules/tracking/google-analytics'; import { eitherInOktaExperimentOrElse } from 'common/modules/identity/api'; const initialiseTopNavItems = () => { @@ -99,19 +98,6 @@ const loadAnalytics = () => { interactionTracking.init(); }; -const loadGoogleAnalytics = () => { - const handleGoogleAnalytics = (gaHasConsent) => { - if (gaHasConsent && !config.get('page.gaIsInitalised')) { - window.guardian.googleAnalytics.initialiseGa() - } else { - // set window.ga back to a stub function when ga consents are removed so that we don't track events - window.ga = function() {} - config.set('page.gaIsInitalised', false) - } - } - mediator.on('ga:gaConsentChange', handleGoogleAnalytics) -} - const cleanupCookies = () => { cleanUp([ 'mmcore.pd', @@ -317,8 +303,6 @@ const init = () => { ['c-braze', handleBrazeAndReportErrors], ['c-reader-revenue-dev-utils', initReaderRevenueDevUtils], ['c-add-privacy-settings-link', addPrivacySettingsLink], - ['c-load-google-analytics', loadGoogleAnalytics], - ['c-google-analytics', initGoogleAnalytics], ]); return refreshUserFeatures().catch((err) => { diff --git a/static/src/javascripts/bootstraps/enhanced/media-player.js b/static/src/javascripts/bootstraps/enhanced/media-player.js index 83eae4bab73..95a9faa4076 100644 --- a/static/src/javascripts/bootstraps/enhanced/media-player.js +++ b/static/src/javascripts/bootstraps/enhanced/media-player.js @@ -121,7 +121,6 @@ const enhanceVideo = (el, autoplay) => { ); events.addContentEvents(player, mediaId, mediaType); - events.bindGoogleAnalyticsEvents(player, gaEventLabel); getVideoInfo(el).then(videoInfo => { if (videoInfo.expired) { diff --git a/static/src/javascripts/bootstraps/enhanced/video-player.js b/static/src/javascripts/bootstraps/enhanced/video-player.js index aa50dd492e5..6d0cc1acdbe 100644 --- a/static/src/javascripts/bootstraps/enhanced/video-player.js +++ b/static/src/javascripts/bootstraps/enhanced/video-player.js @@ -2,14 +2,9 @@ import fastdom from 'lib/fastdom-promise'; import $ from 'lib/$'; import bean from 'bean'; import raven from 'lib/raven'; -import { - buildGoogleAnalyticsEvent, - getGoogleAnalyticsEventAction, -} from 'common/modules/video/ga-helper'; import config from 'lib/config'; import ophan from 'ophan/ng'; -const gaTracker = config.get('googleAnalytics.trackers.editorial'); const isEmbed = !!window.guardian.isEmbed; const getCanonicalUrl = (dataset) => @@ -37,19 +32,6 @@ const bindTrackingEvents = (el) => { play: 'metric1', }; - window.ga( - `${gaTracker}.send`, - 'event', - buildGoogleAnalyticsEvent( - mediaEvent, - events, - canonicalUrl, - 'guardian-videojs', - getGoogleAnalyticsEventAction, - mediaId - ) - ); - // on play, fire Ophan event const record = ophanEmbed => { const eventObject = { diff --git a/static/src/javascripts/bootstraps/videojs-embed.js b/static/src/javascripts/bootstraps/videojs-embed.js index 80345c88685..b68bc05f88e 100644 --- a/static/src/javascripts/bootstraps/videojs-embed.js +++ b/static/src/javascripts/bootstraps/videojs-embed.js @@ -96,7 +96,6 @@ const initPlayer = () => { events.addContentEvents(player, mediaId, mediaType); events.bindContentEvents(player); - events.bindGoogleAnalyticsEvents(player, gaEventLabel); }); const mouseMoveIdle = debounce(() => { diff --git a/static/src/javascripts/projects/common/modules/analytics/discussion.js b/static/src/javascripts/projects/common/modules/analytics/discussion.js index 818824f792f..6ce0d0a56d6 100644 --- a/static/src/javascripts/projects/common/modules/analytics/discussion.js +++ b/static/src/javascripts/projects/common/modules/analytics/discussion.js @@ -6,24 +6,6 @@ import { mediator } from 'lib/mediator'; let seen = false; -const sendToGA = (label, customDimensions = {}) => { - const tracker = config.get('googleAnalytics.trackers.editorial'); - - window.ga( - `${tracker}.send`, - 'event', - 'element view', - 'onpage item', - label, - Object.assign( - { - nonInteraction: true, // to avoid affecting bounce rate - }, - customDimensions - ) - ); -}; - const jumpedToComments = () => { if (!seen) { seen = true; @@ -38,7 +20,6 @@ const commentPermalink = () => { const scrolledToComments = () => { if (!seen) { - sendToGA('scroll to comments'); seen = true; } }; diff --git a/static/src/javascripts/projects/common/modules/atoms/youtube-player.ts b/static/src/javascripts/projects/common/modules/atoms/youtube-player.ts index cdb384ed43c..2d908b7c44b 100644 --- a/static/src/javascripts/projects/common/modules/atoms/youtube-player.ts +++ b/static/src/javascripts/projects/common/modules/atoms/youtube-player.ts @@ -11,13 +11,11 @@ import { loadScript, log, onConsentChange } from '@guardian/libs'; import fastdom from 'fastdom'; import { getPageTargeting } from 'common/modules/commercial/build-page-targeting'; import { commercialFeatures } from 'common/modules/commercial/commercial-features'; -import { buildPfpEvent } from 'common/modules/video/ga-helper'; import config from 'lib/config'; import type { MaybeArray } from 'lib/url'; import { constructQuery } from 'lib/url'; interface WindowLocal extends Window { - ga: UniversalAnalytics.ga; onYouTubeIframeAPIReady?: () => void; YT?: typeof YT; } @@ -221,8 +219,6 @@ const setupPlayer = ( onReady: (event: YTPlayerEvent) => void, onStateChange: (event: YTPlayerEvent) => void, onError: (event: YTPlayerEvent) => void, - onAdStart: () => void, - onAdEnd: () => void, consentState: ConsentState, ): YT.Player => { // relatedChannels needs to be an array, as per YouTube's IFrame Embed Config API @@ -258,8 +254,6 @@ const setupPlayer = ( onReady, onStateChange, onError, - onAdStart, - onAdEnd, }, embedConfig: { relatedChannels, @@ -299,34 +293,12 @@ export const initYoutubePlayer = async ( console.dir(event); }; - const gaTracker = config.get( - 'googleAnalytics.trackers.editorial', - ) as string; - - const onAdStart = () => { - (window as WindowLocal).ga( - `${gaTracker}.send`, - 'event', - buildPfpEvent('adStart', videoId), - ); - }; - - const onAdEnd = () => { - (window as WindowLocal).ga( - `${gaTracker}.send`, - 'event', - buildPfpEvent('adEnd', videoId), - ); - }; - return setupPlayer( el, videoId, onPlayerReady, onPlayerStateChange, onPlayerError, - onAdStart, - onAdEnd, consentState, ); }; diff --git a/static/src/javascripts/projects/common/modules/atoms/youtube-tracking.js b/static/src/javascripts/projects/common/modules/atoms/youtube-tracking.js index 2cbbac987e3..2cf51d114ad 100644 --- a/static/src/javascripts/projects/common/modules/atoms/youtube-tracking.js +++ b/static/src/javascripts/projects/common/modules/atoms/youtube-tracking.js @@ -1,13 +1,11 @@ import { mediator } from 'lib/mediator'; import config from 'lib/config'; import ophan from 'ophan/ng'; -import { buildGoogleAnalyticsEvent } from 'common/modules/video/ga-helper'; const buildEventId = (event, videoId) => `${event}:${videoId}`; const initYoutubeEvents = (videoId) => { - const gaTracker = config.get('googleAnalytics.trackers.editorial'); const eventAction = 'video content'; const events = { metricMap: { @@ -45,18 +43,6 @@ const initYoutubeEvents = (videoId) => { isPreroll: false, }; ophanRecord(mediaEvent); - window.ga( - `${gaTracker}.send`, - 'event', - buildGoogleAnalyticsEvent( - mediaEvent, - events.metricMap, - id, - 'gu-video-youtube', - () => eventAction, - videoId - ) - ); }); }); diff --git a/static/src/javascripts/projects/common/modules/navigation/membership.js b/static/src/javascripts/projects/common/modules/navigation/membership.js index 8c7f494db64..973b23d6f76 100644 --- a/static/src/javascripts/projects/common/modules/navigation/membership.js +++ b/static/src/javascripts/projects/common/modules/navigation/membership.js @@ -44,16 +44,6 @@ const storeBannerCanBeLoadedAgainAfter = () => { ); }; -const gaTrackMMA = (category, action) => label => { - window.ga( - `${config.get('googleAnalytics.trackers.editorial')}.send`, - 'event', - category, - action, - label - ); -}; - const updateLink = accountDataUpdateWarning(); const showAccountDataUpdateWarningMessage = accountDataUpdateWarningLink => { diff --git a/static/src/javascripts/projects/common/modules/tracking/google-analytics.ts b/static/src/javascripts/projects/common/modules/tracking/google-analytics.ts deleted file mode 100644 index cd91737ccdb..00000000000 --- a/static/src/javascripts/projects/common/modules/tracking/google-analytics.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { getConsentFor, onConsentChange } from '@guardian/libs'; -import { mediator } from 'lib/mediator'; - -export const init = (): void => { - onConsentChange((state) => { - const gaHasConsent = getConsentFor('google-analytics', state); - mediator.emit('ga:gaConsentChange', gaHasConsent); - }); -}; diff --git a/static/src/javascripts/projects/common/modules/video/events.js b/static/src/javascripts/projects/common/modules/video/events.js index c71a05e146d..dc048721e18 100644 --- a/static/src/javascripts/projects/common/modules/video/events.js +++ b/static/src/javascripts/projects/common/modules/video/events.js @@ -7,10 +7,6 @@ import { isBreakpoint } from 'lib/detect'; import { isRevisit } from 'common/modules/onward/history'; import throttle from 'lodash/throttle'; import forOwn from 'lodash/forOwn'; -import { - buildGoogleAnalyticsEvent, - getGoogleAnalyticsEventAction, -} from 'common/modules/video/ga-helper'; import ophan from 'ophan/ng'; const isDesktop = isBreakpoint({ @@ -28,7 +24,6 @@ const EVENTS = [ 'content:play', 'content:end', ]; -const gaTracker = config.get('googleAnalytics.trackers.editorial'); const bindCustomMediaEvents = ( eventsMap, @@ -89,36 +84,6 @@ const addContentEvents = ( bindCustomMediaEvents(eventsMap, player, mediaId, mediaType, false); }; -const bindGoogleAnalyticsEvents = (player, canonicalUrl) => { - const events = { - play: 'metric1', - skip: 'metric2', - watched25: 'metric3', - watched50: 'metric4', - watched75: 'metric5', - end: 'metric6', - }; - - Object.keys(events) - .map(eventName => `media:${eventName}`) - .forEach(playerEvent => { - player.on(playerEvent, (_, mediaEvent) => { - window.ga( - `${gaTracker}.send`, - 'event', - buildGoogleAnalyticsEvent( - mediaEvent, - events, - canonicalUrl, - 'guardian-videojs', - getGoogleAnalyticsEventAction, - mediaEvent.mediaId - ) - ); - }); - }); -}; - const getMediaType = player => (isEmbed ? 'video' : player.guMediaType); const shouldAutoPlay = player => @@ -253,5 +218,4 @@ export default { handleInitialMediaError, bindErrorHandler, addContentEvents, - bindGoogleAnalyticsEvents, }; diff --git a/static/src/javascripts/projects/common/modules/video/ga-helper.ts b/static/src/javascripts/projects/common/modules/video/ga-helper.ts deleted file mode 100644 index 9be6129b2b1..00000000000 --- a/static/src/javascripts/projects/common/modules/video/ga-helper.ts +++ /dev/null @@ -1,89 +0,0 @@ -const eventMetrics = { - play: 'metric1', - skip: 'metric2', - '25': 'metric3', - '50': 'metric4', - '75': 'metric5', - watched25: 'metric3', - watched50: 'metric4', - watched75: 'metric5', - end: 'metric6', -} as const; - -type EventType = keyof typeof eventMetrics; -type Metric = (typeof eventMetrics)[EventType] | 'metric24' | 'metric25'; - -type EventMetrics = Partial; - -type MediaEvent = { - mediaId: string; - mediaType: string; - eventType: EventType; - isPreroll: boolean; -}; - -type InitialFieldsObject = { - eventCategory: string; - eventAction: string; - eventLabel: string; - dimension19: string; - dimension20: string; -}; -type MetricFieldsObject = Partial>; -type FinalFieldsObject = InitialFieldsObject & MetricFieldsObject; - -type EventAction = (me: MediaEvent) => string; - -const buildGoogleAnalyticsEvent = ( - mediaEvent: MediaEvent, - metrics: EventMetrics, - canonicalUrl: string, - player: string, - eventAction: EventAction, - videoId: string, -): FinalFieldsObject => { - const action = eventAction(mediaEvent); - const fieldsObject: FinalFieldsObject = { - eventCategory: 'media', - eventAction: action, - eventLabel: canonicalUrl, - dimension19: videoId, - dimension20: player, - }; - if (mediaEvent.eventType in metrics) { - const index = metrics[mediaEvent.eventType]; - if (index !== undefined) fieldsObject[index] = 1; - } - return fieldsObject; -}; - -const getGoogleAnalyticsEventAction = (mediaEvent: MediaEvent): string => { - let action = `${mediaEvent.mediaType} `; - if (mediaEvent.isPreroll) { - action += 'preroll'; - } else { - action += 'content'; - } - return action; -}; - -const buildPfpEvent = ( - pfpEventType: 'adStart' | 'adEnd', - videoId: string, -): FinalFieldsObject => { - const pfpEventMetric = pfpEventType === 'adStart' ? 24 : 25; - return { - eventCategory: 'media', - eventAction: 'video preroll', - eventLabel: videoId, - dimension19: videoId, - dimension20: 'gu-video-youtube', - [`metric${pfpEventMetric}`]: 1, - }; -}; - -export { - buildGoogleAnalyticsEvent, - getGoogleAnalyticsEventAction, - buildPfpEvent, -}; From e1a5dc4de1f4e25338242f5f4add9efb3f50596c Mon Sep 17 00:00:00 2001 From: Dina Hafez Date: Thu, 30 May 2024 09:41:14 +0100 Subject: [PATCH 03/12] Remove unused meta and remove ga from readme --- docs/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index a159ecca039..e45f191c09e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,7 +32,6 @@ - [Testing AMIs or provisioning in AWS](03-dev-howtos/10-testing-platform.md) - [Accessing HTTP access logs for your localhost](03-dev-howtos/11-access-logs-for-your-localhost.md) - [Update configuration in Systems Manager Parameter Store](03-dev-howtos/12-Update-configuration.md) -- [Implement Google Analytics](03-dev-howtos/13-implement-google-analytics.md) - [Overriding default configuration](03-dev-howtos/14-override-default-configuration.md) - [Updating the test database](03-dev-howtos/15-updating-test-database.md) - [Working with Google AMP](03-dev-howtos/16-working-with-amp.md) From 717b3ebaf8a54949d0c3a3d0c938c7dbd34268f0 Mon Sep 17 00:00:00 2001 From: Dina Hafez Date: Thu, 30 May 2024 11:51:20 +0100 Subject: [PATCH 04/12] Remove googe analytics from IdentityHtmlPage --- identity/app/pages/IdentityHtmlPage.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/identity/app/pages/IdentityHtmlPage.scala b/identity/app/pages/IdentityHtmlPage.scala index 62115a5a9e0..8b5230e6a5d 100644 --- a/identity/app/pages/IdentityHtmlPage.scala +++ b/identity/app/pages/IdentityHtmlPage.scala @@ -57,7 +57,6 @@ object IdentityHtmlPage { inlineJSNonBlocking(), views.html.layout.identitySkinnyFooter() when page.isFlow, footer() when !page.isFlow, - analytics.google(page), ), ), devTakeShot(), From f671fe939ec4c3edccb04dcfb30487079c404fcb Mon Sep 17 00:00:00 2001 From: Dina Hafez Date: Thu, 30 May 2024 16:40:33 +0100 Subject: [PATCH 05/12] Remove BoostGAUserTimingFidelity switch and unused code related to GA --- applications/app/views/videoEmbed.scala.html | 11 ----------- common/app/conf/switches/MonitoringSwitches.scala | 10 ---------- common/app/dev/DevParametersHttpRequestHandler.scala | 4 ---- common/app/model/meta.scala | 7 ------- common/app/navigation/helpers/UrlHelpers.scala | 1 - .../inlineJS/nonBlocking/getUserData.scala.js | 2 +- 6 files changed, 1 insertion(+), 34 deletions(-) diff --git a/applications/app/views/videoEmbed.scala.html b/applications/app/views/videoEmbed.scala.html index 2c09651bd8b..30a62157e18 100644 --- a/applications/app/views/videoEmbed.scala.html +++ b/applications/app/views/videoEmbed.scala.html @@ -69,17 +69,6 @@ config: @Html(templates.js.javaScriptConfig(model.SimpleContentPage(video)).body), adBlockers: { onDetect: [] } }; - @* Decide the Ophan PV ID here so we can share it with Google Analytics *@ - guardian.config.ophan = { - // This is duplicated from - // https://github.com/guardian/ophan/blob/master/tracker-js/assets/coffee/ophan/transmit.coffee - // Please do not change this without talking to the Ophan project first. - pageViewId: new Date().getTime().toString(36) + 'xxxxxxxxxxxx'.replace(/x/g, function () { - return Math.floor(Math.random() * 36).toString(36); - }) - }; - @* Find the Ophan browser ID as well, for sharing with GA *@ - @Html(templates.inlineJS.nonBlocking.js.ophanConfig().body) var docClass = document.documentElement.className; diff --git a/common/app/conf/switches/MonitoringSwitches.scala b/common/app/conf/switches/MonitoringSwitches.scala index 701a754884d..cd3fecd0ea5 100644 --- a/common/app/conf/switches/MonitoringSwitches.scala +++ b/common/app/conf/switches/MonitoringSwitches.scala @@ -55,16 +55,6 @@ trait MonitoringSwitches { exposeClientSide = false, ) - val BoostGAUserTimingFidelity = Switch( - SwitchGroup.Monitoring, - "boost-ga-user-timing-fidelity", - "CAUTION: check with Google.Analyticscore@guardian.co.uk before enabling. Extends the standard 0.1% sampling of user timing events on Google Analytics to 100%. Will send a LOT more events to GA, which costs $$$.", - owners = Seq(Owner.withName("unknown")), - safeState = Off, - sellByDate = never, - exposeClientSide = true, - ) - val LogRemovedAmpElements = Switch( SwitchGroup.Monitoring, "log-removed-amp-elements", diff --git a/common/app/dev/DevParametersHttpRequestHandler.scala b/common/app/dev/DevParametersHttpRequestHandler.scala index 626bd225b7f..4c8105ea437 100644 --- a/common/app/dev/DevParametersHttpRequestHandler.scala +++ b/common/app/dev/DevParametersHttpRequestHandler.scala @@ -77,10 +77,6 @@ class DevParametersHttpRequestHandler( "dll", // Disable lazy loading of ads "iasdebug", // IAS troubleshooting "cmpdebug", // CMP troubleshooting - "utm_source", // Google Analytics source - "utm_medium", // Google Analytics medium - "utm_campaign", // Google Analytics campaign - "utm_term", // Google Analytics term "sfdebug", // enable spacefinder visualiser. '1' = inline ads (first pass), '2' = inline ads (second pass), 'im' = inline merchandising ads, 'carrot' = carrot ads "rikerdebug", // enable debug logging for Canadian ad setup managed by the Globe and Mail "forceSendMetrics", // enable force sending of commercial metrics diff --git a/common/app/model/meta.scala b/common/app/model/meta.scala index 2979196d99a..b3c7a5c70f3 100644 --- a/common/app/model/meta.scala +++ b/common/app/model/meta.scala @@ -14,7 +14,6 @@ import conf.Configuration import conf.cricketPa.CricketTeams import model.liveblog.Blocks import model.meta.{Guardian, LinkedData, PotentialAction, WebPage} -import org.apache.commons.lang3.StringUtils import org.joda.time.DateTime import com.github.nscala_time.time.Implicits._ import play.api.libs.json._ @@ -451,12 +450,6 @@ case class MetaData( .getOrElse(Nil) def iosId(referrer: String): Option[String] = iosType.map(iosType => s"$id?contenttype=$iosType&source=$referrer") - - /** - * Content type, lowercased and with spaces removed. - * This is used for Google Analytics, to be consistent with what the mobile apps do. - */ - def normalisedContentType: String = StringUtils.remove(contentType.map(_.name.toLowerCase).getOrElse(""), ' ') } object Page { diff --git a/common/app/navigation/helpers/UrlHelpers.scala b/common/app/navigation/helpers/UrlHelpers.scala index 17b348b6997..548a912e807 100644 --- a/common/app/navigation/helpers/UrlHelpers.scala +++ b/common/app/navigation/helpers/UrlHelpers.scala @@ -103,7 +103,6 @@ object UrlHelpers { import io.lemonlabs.uri.typesafe.dsl._ - // INTCMP is passed as a separate param because people look at it in Google Analytics // It's set to the most specific thing (componentId) to maximise its usefulness val url = destination.url ? ("INTCMP" -> componentId) & ("acquisitionData" -> acquisitionData.toString) Url.parse(url.toString).toString diff --git a/common/app/templates/inlineJS/nonBlocking/getUserData.scala.js b/common/app/templates/inlineJS/nonBlocking/getUserData.scala.js index 4c77e79f4ac..4296fa9d517 100644 --- a/common/app/templates/inlineJS/nonBlocking/getUserData.scala.js +++ b/common/app/templates/inlineJS/nonBlocking/getUserData.scala.js @@ -12,7 +12,7 @@ try { return decodeURIComponent(atob(str.replace(/-/g, '+').replace(/_/g, '/').replace(/,/g, '='))); } - // Short version of cookie.get(), inspired by Google Analytics' code + // Short version of cookie.get() var cookieData = (function(a) { var d = new window.Array(), e = new window.Array(); From 700e1f6b042f980b99b56566042f6171b04705ed Mon Sep 17 00:00:00 2001 From: Dina Hafez Date: Thu, 30 May 2024 17:30:54 +0100 Subject: [PATCH 06/12] Remove all GA references in docs and the package --- common/app/model/content.scala | 1 - common/app/model/meta.scala | 4 ---- common/app/views/fragments/inlineJSNonBlocking.scala.html | 3 --- docs/02-architecture/06-client-side-architecture.md | 4 +--- package.json | 1 - yarn.lock | 8 -------- 6 files changed, 1 insertion(+), 20 deletions(-) diff --git a/common/app/model/content.scala b/common/app/model/content.scala index 5c0d429a32e..b780b41fcb0 100644 --- a/common/app/model/content.scala +++ b/common/app/model/content.scala @@ -279,7 +279,6 @@ final case class Content( def javascriptConfig: Map[String, JsValue] = Map( - ("contentId", JsString(metadata.id)), ("publication", JsString(publication)), ("hasShowcaseMainElement", JsBoolean(elements.hasShowcaseMainElement)), ("pageCode", JsString(internalPageCode)), diff --git a/common/app/model/meta.scala b/common/app/model/meta.scala index b3c7a5c70f3..3a80718e0f6 100644 --- a/common/app/model/meta.scala +++ b/common/app/model/meta.scala @@ -860,7 +860,6 @@ final case class Tags(tags: List[Tag]) { _.id } - lazy val commissioningDesks: List[String] = tracking.map(_.id).collect { case Tags.CommissioningDesk(desk) => desk } lazy val blogOrSeriesTag: Option[Tag] = { tags.find(tag => tag.showSeriesInMeta && (tag.isBlog || tag.isSeries)) } @@ -904,7 +903,6 @@ final case class Tags(tags: List[Tag]) { ), ), ("blogIds", JsString(blogs.map(_.id).mkString(","))), - ("commissioningDesks", JsString(commissioningDesks.mkString(","))), ) } @@ -959,8 +957,6 @@ object Tags { "tone/reviews", ) - val CommissioningDesk: Regex = """tracking/commissioningdesk/(.*)""".r - def make(apiContent: contentapi.Content): Tags = { Tags(apiContent.tags.toList map { Tag.make(_) diff --git a/common/app/views/fragments/inlineJSNonBlocking.scala.html b/common/app/views/fragments/inlineJSNonBlocking.scala.html index 92eb7064482..08c87b1cdab 100644 --- a/common/app/views/fragments/inlineJSNonBlocking.scala.html +++ b/common/app/views/fragments/inlineJSNonBlocking.scala.html @@ -24,9 +24,6 @@ // ************* ANALYTICS ************* -// Ophan pageview ID and browser ID are needed by Google Analytics, which runs just after this script tag -@InlineJs(ophanConfig().body, "ophanConfig.js") - @if(page.metadata.contentType.exists { c => c == DotcomContentType.Article || c == DotcomContentType.LiveBlog || diff --git a/docs/02-architecture/06-client-side-architecture.md b/docs/02-architecture/06-client-side-architecture.md index 8197893feaf..d7385255b54 100644 --- a/docs/02-architecture/06-client-side-architecture.md +++ b/docs/02-architecture/06-client-side-architecture.md @@ -153,9 +153,7 @@ Gets the [Ophan browserId](https://github.com/guardian/frontend/blob/main/common ### Analytics -The analytics for Dotcom are defined in [analytics/base.scala.html](https://github.com/guardian/frontend/blob/main/common/app/views/fragments/analytics/base.scala.html). It contains [Google Analytics](https://github.com/guardian/frontend/blob/main/common/app/views/fragments/analytics/google.scala.html), [Omniture](https://github.com/guardian/frontend/blob/main/common/app/views/fragments/analytics/omniture.scala.html) and [Comscore](https://github.com/guardian/frontend/blob/main/common/app/views/fragments/analytics/comscore.scala.html). - -[Read more about the Google Analytics implementation](https://github.com/guardian/frontend/blob/main/docs/03-dev-howtos/14-implement-google-analytics.md). +The analytics for Dotcom are defined in [analytics/base.scala.html](https://github.com/guardian/frontend/blob/main/common/app/views/fragments/analytics/base.scala.html). ### Bootstraps diff --git a/package.json b/package.json index 611ebef611f..166d007867d 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "@guardian/source-react-components": "22.1.0", "@guardian/source-react-components-development-kitchen": "19.0.0", "@guardian/support-dotcom-components": "1.1.3", - "@types/google.analytics": "^0.0.42", "@types/googletag": "^1.1.3", "@types/jest": "29.5.12", "@types/lodash-es": "^4.17.4", diff --git a/yarn.lock b/yarn.lock index 3a252239b9c..637058d956a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2805,7 +2805,6 @@ __metadata: "@guardian/source-react-components": "npm:22.1.0" "@guardian/source-react-components-development-kitchen": "npm:19.0.0" "@guardian/support-dotcom-components": "npm:1.1.3" - "@types/google.analytics": "npm:^0.0.42" "@types/googletag": "npm:^1.1.3" "@types/jest": "npm:29.5.12" "@types/lodash-es": "npm:^4.17.4" @@ -4334,13 +4333,6 @@ __metadata: languageName: node linkType: hard -"@types/google.analytics@npm:^0.0.42": - version: 0.0.42 - resolution: "@types/google.analytics@npm:0.0.42" - checksum: 10c0/7145854f12370c87547a26756e32520b019905e785d0b713ba316481e1c44bfee209fbf2f0956dc86c6531f468cb13adb71bf8117634ac401c12c5b36026c5dd - languageName: node - linkType: hard - "@types/googletag@npm:^1.1.3": version: 1.2.3 resolution: "@types/googletag@npm:1.2.3" From 7c34ed7fbd61632aef3d5038994cfcdb06120ba9 Mon Sep 17 00:00:00 2001 From: Dina Hafez Date: Thu, 30 May 2024 18:24:38 +0100 Subject: [PATCH 07/12] Remove GA event from video-player --- .../javascripts/bootstraps/enhanced/video-player.js | 11 ----------- .../common/modules/analytics/interaction-tracking.js | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/static/src/javascripts/bootstraps/enhanced/video-player.js b/static/src/javascripts/bootstraps/enhanced/video-player.js index 6d0cc1acdbe..4ff720fd1db 100644 --- a/static/src/javascripts/bootstraps/enhanced/video-player.js +++ b/static/src/javascripts/bootstraps/enhanced/video-player.js @@ -21,17 +21,6 @@ const bindTrackingEvents = (el) => { const canonicalUrl = getCanonicalUrl(dataset); const playHandler = () => { - // on play, fire GA event - const mediaEvent = { - mediaId, - mediaType, - eventType: 'play', - isPreroll: false, - }; - const events = { - play: 'metric1', - }; - // on play, fire Ophan event const record = ophanEmbed => { const eventObject = { diff --git a/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.js b/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.js index 429f2081495..9802f66c004 100644 --- a/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.js +++ b/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.js @@ -7,7 +7,7 @@ let loc = document.location; const trackInternalLinkClick = (spec) => { // Store in session storage. - // GA and Omniture will both pick it up on next page load, + // Omniture will both pick it up on next page load, // then Omniture will remove it from storage. const storeObj = { path: loc.pathname, From 77815e04c260567908be8d0d37f9e4afc750041f Mon Sep 17 00:00:00 2001 From: Dina Hafez Date: Tue, 4 Jun 2024 10:36:50 +0100 Subject: [PATCH 08/12] Put back deleted code that might be related to Ophan tracking or third-party --- common/app/model/content.scala | 1 + common/app/model/meta.scala | 4 ++++ .../app/templates/inlineJS/nonBlocking/getUserData.scala.js | 2 +- common/app/views/fragments/inlineJSNonBlocking.scala.html | 3 +++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/common/app/model/content.scala b/common/app/model/content.scala index b780b41fcb0..5c0d429a32e 100644 --- a/common/app/model/content.scala +++ b/common/app/model/content.scala @@ -279,6 +279,7 @@ final case class Content( def javascriptConfig: Map[String, JsValue] = Map( + ("contentId", JsString(metadata.id)), ("publication", JsString(publication)), ("hasShowcaseMainElement", JsBoolean(elements.hasShowcaseMainElement)), ("pageCode", JsString(internalPageCode)), diff --git a/common/app/model/meta.scala b/common/app/model/meta.scala index 3a80718e0f6..b3c7a5c70f3 100644 --- a/common/app/model/meta.scala +++ b/common/app/model/meta.scala @@ -860,6 +860,7 @@ final case class Tags(tags: List[Tag]) { _.id } + lazy val commissioningDesks: List[String] = tracking.map(_.id).collect { case Tags.CommissioningDesk(desk) => desk } lazy val blogOrSeriesTag: Option[Tag] = { tags.find(tag => tag.showSeriesInMeta && (tag.isBlog || tag.isSeries)) } @@ -903,6 +904,7 @@ final case class Tags(tags: List[Tag]) { ), ), ("blogIds", JsString(blogs.map(_.id).mkString(","))), + ("commissioningDesks", JsString(commissioningDesks.mkString(","))), ) } @@ -957,6 +959,8 @@ object Tags { "tone/reviews", ) + val CommissioningDesk: Regex = """tracking/commissioningdesk/(.*)""".r + def make(apiContent: contentapi.Content): Tags = { Tags(apiContent.tags.toList map { Tag.make(_) diff --git a/common/app/templates/inlineJS/nonBlocking/getUserData.scala.js b/common/app/templates/inlineJS/nonBlocking/getUserData.scala.js index 4296fa9d517..4c77e79f4ac 100644 --- a/common/app/templates/inlineJS/nonBlocking/getUserData.scala.js +++ b/common/app/templates/inlineJS/nonBlocking/getUserData.scala.js @@ -12,7 +12,7 @@ try { return decodeURIComponent(atob(str.replace(/-/g, '+').replace(/_/g, '/').replace(/,/g, '='))); } - // Short version of cookie.get() + // Short version of cookie.get(), inspired by Google Analytics' code var cookieData = (function(a) { var d = new window.Array(), e = new window.Array(); diff --git a/common/app/views/fragments/inlineJSNonBlocking.scala.html b/common/app/views/fragments/inlineJSNonBlocking.scala.html index 08c87b1cdab..592e188a413 100644 --- a/common/app/views/fragments/inlineJSNonBlocking.scala.html +++ b/common/app/views/fragments/inlineJSNonBlocking.scala.html @@ -24,6 +24,9 @@ // ************* ANALYTICS ************* +// Ophan pageview ID and browser ID +@InlineJs(ophanConfig().body, "ophanConfig.js") + @if(page.metadata.contentType.exists { c => c == DotcomContentType.Article || c == DotcomContentType.LiveBlog || From 1f2fa0a1ed736cadcb73a4d945b22342d574ed6b Mon Sep 17 00:00:00 2001 From: Dina Hafez Date: Wed, 5 Jun 2024 09:47:40 +0100 Subject: [PATCH 09/12] Re-add some of the Ophan code and cleanup --- applications/app/views/videoEmbed.scala.html | 11 +++++++++++ common/app/conf/switches/ABTestSwitches.scala | 4 ++-- common/app/model/meta.scala | 6 ++++++ .../common/modules/analytics/interaction-tracking.js | 2 +- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/applications/app/views/videoEmbed.scala.html b/applications/app/views/videoEmbed.scala.html index 30a62157e18..2c09651bd8b 100644 --- a/applications/app/views/videoEmbed.scala.html +++ b/applications/app/views/videoEmbed.scala.html @@ -69,6 +69,17 @@ config: @Html(templates.js.javaScriptConfig(model.SimpleContentPage(video)).body), adBlockers: { onDetect: [] } }; + @* Decide the Ophan PV ID here so we can share it with Google Analytics *@ + guardian.config.ophan = { + // This is duplicated from + // https://github.com/guardian/ophan/blob/master/tracker-js/assets/coffee/ophan/transmit.coffee + // Please do not change this without talking to the Ophan project first. + pageViewId: new Date().getTime().toString(36) + 'xxxxxxxxxxxx'.replace(/x/g, function () { + return Math.floor(Math.random() * 36).toString(36); + }) + }; + @* Find the Ophan browser ID as well, for sharing with GA *@ + @Html(templates.inlineJS.nonBlocking.js.ophanConfig().body) var docClass = document.documentElement.className; diff --git a/common/app/conf/switches/ABTestSwitches.scala b/common/app/conf/switches/ABTestSwitches.scala index e7a839b0793..12f964550be 100644 --- a/common/app/conf/switches/ABTestSwitches.scala +++ b/common/app/conf/switches/ABTestSwitches.scala @@ -41,7 +41,7 @@ trait ABTestSwitches { "Test MPU when there is no epic at the end of Article on the page.", owners = Seq(Owner.withEmail("commercial.dev@theguardian.com")), safeState = Off, - sellByDate = Some(LocalDate.of(2024, 7, 31)), + sellByDate = Some(LocalDate.of(2024, 5, 31)), exposeClientSide = true, ) @@ -51,7 +51,7 @@ trait ABTestSwitches { "Show new ad block ask component in ad slots when we detect ad blocker usage", owners = Seq(Owner.withEmail("commercial.dev@theguardian.com")), safeState = Off, - sellByDate = Some(LocalDate.of(2024, 7, 31)), + sellByDate = Some(LocalDate.of(2024, 5, 31)), exposeClientSide = true, ) diff --git a/common/app/model/meta.scala b/common/app/model/meta.scala index b3c7a5c70f3..ede392e585d 100644 --- a/common/app/model/meta.scala +++ b/common/app/model/meta.scala @@ -14,6 +14,7 @@ import conf.Configuration import conf.cricketPa.CricketTeams import model.liveblog.Blocks import model.meta.{Guardian, LinkedData, PotentialAction, WebPage} +import org.apache.commons.lang3.StringUtils import org.joda.time.DateTime import com.github.nscala_time.time.Implicits._ import play.api.libs.json._ @@ -450,6 +451,11 @@ case class MetaData( .getOrElse(Nil) def iosId(referrer: String): Option[String] = iosType.map(iosType => s"$id?contenttype=$iosType&source=$referrer") + + /** + * Content type, lowercased and with spaces removed. + */ + def normalisedContentType: String = StringUtils.remove(contentType.map(_.name.toLowerCase).getOrElse(""), ' ') } object Page { diff --git a/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.js b/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.js index 9802f66c004..29676b1143f 100644 --- a/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.js +++ b/static/src/javascripts/projects/common/modules/analytics/interaction-tracking.js @@ -7,7 +7,7 @@ let loc = document.location; const trackInternalLinkClick = (spec) => { // Store in session storage. - // Omniture will both pick it up on next page load, + // Omniture will pick it up on next page load, // then Omniture will remove it from storage. const storeObj = { path: loc.pathname, From d7b6f39867c8770234a37562ecdd9a2d5aec3efb Mon Sep 17 00:00:00 2001 From: Dina Hafez Date: Wed, 5 Jun 2024 10:01:45 +0100 Subject: [PATCH 10/12] Fix failed build by adding sellByDate as main --- common/app/conf/switches/ABTestSwitches.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/app/conf/switches/ABTestSwitches.scala b/common/app/conf/switches/ABTestSwitches.scala index 12f964550be..e7a839b0793 100644 --- a/common/app/conf/switches/ABTestSwitches.scala +++ b/common/app/conf/switches/ABTestSwitches.scala @@ -41,7 +41,7 @@ trait ABTestSwitches { "Test MPU when there is no epic at the end of Article on the page.", owners = Seq(Owner.withEmail("commercial.dev@theguardian.com")), safeState = Off, - sellByDate = Some(LocalDate.of(2024, 5, 31)), + sellByDate = Some(LocalDate.of(2024, 7, 31)), exposeClientSide = true, ) @@ -51,7 +51,7 @@ trait ABTestSwitches { "Show new ad block ask component in ad slots when we detect ad blocker usage", owners = Seq(Owner.withEmail("commercial.dev@theguardian.com")), safeState = Off, - sellByDate = Some(LocalDate.of(2024, 5, 31)), + sellByDate = Some(LocalDate.of(2024, 7, 31)), exposeClientSide = true, ) From 68ab36b8dc5d2a137c8ba7276c959cb2ec6427a4 Mon Sep 17 00:00:00 2001 From: Dina Hafez Date: Wed, 5 Jun 2024 10:21:41 +0100 Subject: [PATCH 11/12] Remove willShowPrivacyMessage based on one of the PR comments --- static/src/javascripts/boot.js | 1 - 1 file changed, 1 deletion(-) diff --git a/static/src/javascripts/boot.js b/static/src/javascripts/boot.js index a8d25860770..06769ffcc0e 100644 --- a/static/src/javascripts/boot.js +++ b/static/src/javascripts/boot.js @@ -57,7 +57,6 @@ const go = () => { if (!recordedConsentTime) { recordedConsentTime = true; - cmp.willShowPrivacyMessage(); } // ------------------------------------------------------ From 3bf743dc7c97554d63884208bde097d7635eee4b Mon Sep 17 00:00:00 2001 From: Dina Hafez Date: Mon, 10 Jun 2024 17:45:18 +0100 Subject: [PATCH 12/12] Update comment to the correct file path --- static/src/javascripts/boot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/src/javascripts/boot.js b/static/src/javascripts/boot.js index 06769ffcc0e..14ff8541c50 100644 --- a/static/src/javascripts/boot.js +++ b/static/src/javascripts/boot.js @@ -50,7 +50,7 @@ const go = () => { pageViewId, }; - // keep this in sync with CONSENT_TIMING in src/web/components/App.tsx in frontend + // keep this in sync with CONSENT_TIMING in src/client/bootCmp.ts in dotcom-rendering // mark: CONSENT_TIMING let recordedConsentTime = false; onConsentChange((consentState) => {