WT-964 Refactor stub attribution#1296
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1296 +/- ##
==========================================
- Coverage 79.96% 79.85% -0.12%
==========================================
Files 149 149
Lines 9655 9688 +33
==========================================
+ Hits 7721 7736 +15
- Misses 1934 1952 +18 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
8e34134 to
5047f3b
Compare
1e53908 to
e945dc7
Compare
e945dc7 to
bafc009
Compare
…analytics data We always want to include essential data for the download. We conditionally want to include analytics data (based on consent status). We also want to simplify the logic of this flow. The existing stub-attribution JS contains a lot of complex, nested conditionals which has resulted in hard-to-debug issues and makes further development difficult. Refactored attribution approach should be gated behind a switch. When that switch is active, the following changes should be applied: REMOVED - stub-attribution-consent (now handled through GTM consent mode) REPLACED - stub-attribution (now download-attribution) - stub-attribution-init (now download-attribution-init) - thanks, thanks-direct, thanks-init (now auto-download) ADDED - download-attribution - completely isolates essential and analytics data (with separate triggers) - download-attribution-init (the default that runs on all pages) - initializes an essential attribution update (the LAST essential information is what we pass to the installer, any previous essential information will be overwritten or removed) - attaches any existing download-attribution information to download links - auto-download (replaces the default init behaviour on pages where downloads should start automatically) - if essential information, adds/updates the download attribution cookie before starting download - if no essential information, starts download immediately UPDATED - Analytics initialization happens inside consent util for GTM analytics storage - Download as default JS no longer contains consent logic (this is already handled with GTM) - RTAMO uses a new template with a forced essential campaign and auto-download JS - Thanks template used auto-download JS
We can trigger initAnalytics based on GTM consent analytics storage This allows consent logic to live in one place and ensures it is consistently applied for analytics download attribution. The search params JS has moved into the site bundle to ensure it is defined at the time the GTM logic runs. Without this change, analytics initialization would always fail at download attribution functional requirements check. In future, we may create a custom GTM trigger that fires based on the analytics storage consent state.
This is a combination of thanks, thanks-init, and thanks-direct logic Copy-pasted with superficial changes: - shouldAutoDownload - getDownloadURL - beginFirefoxDownload - onSuccess - onTimeout Auto-download pages will not use the default download attribution init logic from the stub attribution block. These pages will only create/update download attribution if there is essential data to pass to the download installer. Otherwise, they will attempt to start download immediately. There is a timeout to prevent an overly long wait before download auto-starts (i.e. if the stub attribution service response is slow). This means we prioritize a timely download over passing essential information.
This applies to all except auto-download pages. On those pages, we can assume the user clicked to download from the preceding page, and the preceding page set all necessary attribution data. This change also removes fallback values in python code because we cannot reliably know if marketing data is allowed at this point.
We should only apply defaults for analytics values once we confirm there is a ga4 client id value Otherwise, consider essential only and pass nothing but the campaign
bafc009 to
b7b3b92
Compare
To recreate: - Consent required geo goes to /landing/get and accepts analytics from banner - Analytics information is added to download attribution - Consent required geo goes to /privacy/websites/cookie-settings and updates analytics preference to denied - Analytics information should be removed from download attribution This is commented as a temporary workaround because we are still conditionally loading GTM (and GTAG initialization is related to that). This means there are pages where GTAG is not defined but we might still need to remove pre-existing analytics information. Moving the removal action above the GTAG check ensures it always runs when consent is denied.
|
TODO:
|
There was a problem hiding this comment.
Pull request overview
This PR introduces a refactored “download attribution” flow (renaming/replacing parts of the legacy “stub attribution” approach) with a goal of cleanly separating essential attribution data from analytics attribution data, and wiring the new behavior behind a Waffle switch.
Changes:
- Added a new
DownloadAttributionclient module with separate essential vs analytics triggers, plus an init bundle and an auto-download bundle. - Updated templates/bundles to conditionally load new v2 JS bundles (download attribution, auto-download, download-as-default v2, firefox_all v2) when the switch is enabled.
- Added Playwright coverage for create/update/remove behaviors of the new attribution cookies and service calls.
Review completed using the repository’s custom Copilot instructions and AGENTS.md guidance.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
tests/playwright/specs/download-attribution/create.spec.js |
Adds Playwright coverage for creating analytics/essential attribution cookies and consent gating. |
tests/playwright/specs/download-attribution/update.spec.js |
Adds Playwright coverage for “preserve/override” update rules between essential and analytics attribution. |
tests/playwright/specs/download-attribution/remove.spec.js |
Adds Playwright coverage for removing analytics and essential attribution under consent/flow changes. |
springfield/privacy/templates/privacy/cookie-settings-flare26.html |
Adds a test id hook used by Playwright to target the analytics consent UI. |
springfield/firefox/views.py |
Refactors server-side stub attribution signing defaults behind a switch; updates template selection for direct downloads. |
springfield/firefox/templates/firefox/download/rtamo.html |
New RTAMO thanks template that forces an essential campaign via <html> attributes. |
springfield/firefox/templates/firefox/download/desktop/thanks.html |
Switches /thanks behavior to use auto-download JS and suppress attribution init when refactor is enabled. |
springfield/firefox/templates/firefox/download/basic/thanks.html |
Same as above for the basic thanks template. |
springfield/firefox/templates/firefox/all/base.html |
Switches /all JS bundle to a v2 initializer when refactor is enabled. |
springfield/cms/templates/cms/thanks_page.html |
Switches CMS thanks pages to auto-download and suppresses attribution init when refactor is enabled. |
springfield/cms/templates/cms/base-flare26.html |
Conditionally swaps stub-attribution → download-attribution and download_as_default → download_as_default-v2. |
media/static-bundles.json |
Registers new v2 bundles and repositions search-params into the base bundle. |
media/js/firefox/download/desktop/download-as-default-v2.es6.js |
New download-as-default integration that updates essential attribution on checkbox changes. |
media/js/firefox/download/desktop/download-as-default-init-v2.es6.js |
Initializes the new download-as-default v2 module. |
media/js/firefox/download/auto-download.es6.js |
New auto-download flow that can force an essential attribution update before starting a download. |
media/js/firefox/all/all-init-v2.es6.js |
New /all initializer that re-applies attribution to partially-fetched download links. |
media/js/base/download-attribution/download-attribution.es6.js |
New core implementation for essential+analytics attribution with separate raw cookies and re-signing. |
media/js/base/download-attribution/download-attribution-init.es6.js |
Default init that refreshes essential attribution and applies signed attribution to links. |
media/js/base/consent/utils.es6.js |
Hooks analytics consent changes to attribution analytics init/removal. |
| window.dataLayer = window.dataLayer || []; | ||
|
|
||
| /** |
| /** | ||
| * Update essential data based on checkbox state | ||
| */ | ||
| if (!checked) { | ||
| window.Mozilla.DownloadAttribution.initEssential(null, () => { | ||
| DownloadAsDefault.bindEvents(); | ||
| }); | ||
| } else { | ||
| window.Mozilla.DownloadAttribution.initEssential( | ||
| DownloadAsDefault.CAMPAIGN, | ||
| () => { | ||
| DownloadAsDefault.bindEvents(); | ||
| } |
| if ( | ||
| Mozilla.DownloadAttribution !== undefined && | ||
| document.documentElement.hasAttribute( | ||
| 'data-stub-attribution-campaign-force' | ||
| ) | ||
| ) { | ||
| // Custom success and timeout callbacks are only relevant for direct attribution | ||
| // (i.e. when we have updated the cookie on the auto-download page) | ||
| Mozilla.DownloadAttribution.successCallback = onSuccess; | ||
| Mozilla.DownloadAttribution.timeoutCallback = onTimeout; | ||
| // Don't wait too long before starting download | ||
| // (even if it means we have to leave the essential information out) | ||
| timeout = setTimeout(onTimeout, 2000); | ||
| Mozilla.DownloadAttribution.initEssential(); | ||
| } else { | ||
| // Otherwise, we can assume an existing cookie has latest data | ||
| Mozilla.DownloadAttribution.applyAttributionDataToLinks(); | ||
| beginFirefoxDownload(); |
| const existingCookies = await page.context().cookies(); | ||
| const existingEssentialCookie = existingCookies.find( | ||
| (c) => c.name === 'moz-download-attribution-essential-raw' | ||
| ); | ||
| const existingAnalyticsCookie = existingCookies.find( | ||
| (c) => c.name === 'moz-download-attribution-essential-raw' | ||
| ); | ||
| expect(existingEssentialCookie).toBeDefined(); | ||
| const existingEssentialCookieData = JSON.parse( | ||
| decodeURIComponent(existingEssentialCookie.value) | ||
| ); | ||
| const existingAnalyticsCookieData = JSON.parse( | ||
| decodeURIComponent(existingAnalyticsCookie.value) | ||
| ); |
| const acceptButton = page.getByTestId( | ||
| 'consent-banner-accept-button' | ||
| ); | ||
| acceptButton.click(); |
| const acceptButton = page.getByTestId( | ||
| 'consent-banner-accept-button' | ||
| ); | ||
| acceptButton.click(); |
| // change consent status | ||
| const analyticsCategory = await page.getByTestId( | ||
| 'cookie-consent-analytics' | ||
| ); | ||
| const disagreeOption = | ||
| await analyticsCategory.getByLabel(/I do not agree/i); | ||
| await disagreeOption.click(); | ||
| const saveButton = await page.getByRole('button', { | ||
| name: /Save changes/i | ||
| }); | ||
| await saveButton.click(); | ||
|
|
||
| // Confirm stub attribution service call uses essential campaign param only | ||
| expect(capture.params).not.toBeNull(); | ||
|
|
||
| expect(capture.params.utm_campaign).toBe( | ||
| existingEssentialCookieData.utm_campaign |
| await page.waitForFunction(() => { | ||
| return document.cookie | ||
| .split(';') | ||
| .some((c) => | ||
| c | ||
| .trim() | ||
| .startsWith( | ||
| 'moz-download-attribution-analytics-raw=' | ||
| ) | ||
| ); | ||
| }); | ||
|
|
||
| // Confirm new essential cookie added | ||
| const cookies = await page.context().cookies(); | ||
| const essentialCookie = cookies.find( | ||
| (c) => c.name === 'moz-download-attribution-essential-raw' | ||
| ); | ||
| expect(essentialCookie).toBeDefined(); | ||
|
|
||
| // Confirm stub attribution service params preserve pre-existing analytics data | ||
| // (with exception of campaign) | ||
| expect(capture.params).not.toBeNull(); | ||
|
|
||
| expect(capture.params.utm_source).toBe( | ||
| existingAnalyticsCookieData.utm_source | ||
| ); |
One-line summary
Simplify and clarify download attribution data flow
Significant changes and points to review
Refactored attribution approach should be gated behind a switch. When that switch is active, the following changes should be applied:
REMOVED
REPLACED
ADDED
UPDATED
Rough edges:
Issue / Bugzilla link
https://mozilla-hub.atlassian.net/browse/WT-964 [moz only]
Follow up work:
overridecampaign value (fallback and force cover our use cases)thanksprefix from auto-downloadTesting
Consent gating
Analytics
should not fire when
should fire
Essential
No download attribution cookie (create)
Analytics
Essential
Existing download attribution cookie (update)
Analytics
Essential
Existing download attribution cookie (remove)
Analytics
Essential
Auto-download
Regression tests
No user-facing change (unless indicated)
RTAMO auto download (DNT/GPC check no longer needed now we can cleanly remove analytics data according to consent and only apply essential data)
www.firefox.com(currently directing towww.mozilla.org)Download as default checkbox (should now work for consent required geos, only applying essential data)
Smart Window
/allafter user selections/thanksauto downloadstub-attribution-codeparam/landing/getwith marketing opt-out checkboxFallback campaign
kitcampaign if analytics allowed and no utm_campaign in URL: https://www-demo4.springfield.moz.works/en-US/kit/