Skip to content

UTM install time injection & GTM Data Layer push#14130

Open
amit-fytq wants to merge 6 commits into
mozilla:masterfrom
amit-fytq:utm-install-time-injection
Open

UTM install time injection & GTM Data Layer push#14130
amit-fytq wants to merge 6 commits into
mozilla:masterfrom
amit-fytq:utm-install-time-injection

Conversation

@amit-fytq
Copy link
Copy Markdown
Contributor

Fixes #16178

Remove utm_ query parameters from internal AMO links while browsing to stop pollution of GA4 data*

  • Clean URL during browsing
  • Remember where the click came from - Store the reference in redux
  • Inject UTM at install time only - you click "Add to Firefox", right before Firefox's install API is called, we silently update the URL using history.replaceState(). Firefox reads this and records the attribution.
  • Clean up after install — Once the install completes (or fails/cancels), we silently remove the UTM params from the URL again.
  • External UTM params are untouched — If someone arrives from an external link like ?utm_source=blog.example.com, we never overwrite those. Firefox reads them directly.

Push page variables to GTM datalayer

On Addon Detail page, push the following variables to GTM datalayer for analytics reporting and dashboard

  • Addon Type
  • Page Locale

…to stop pollution of GA4 data

Clean URL during browsing

Remember where the click came from - Store the reference in redux

Inject UTM at install time only - you click "Add to Firefox", right before Firefox's install API is called, we silently update the URL using history.replaceState(). Firefox reads this and records the attribution.

Clean up after install — Once the install completes (or fails/cancels), we silently remove the UTM params from the URL again.

External UTM params are untouched — If someone arrives from an external link like ?utm_source=blog.example.com, we never overwrite those. Firefox reads them directly.
On Addon Detail page, push the following variables to GTM datalayer for analytics reporting and dashboard
- Addon Type
- Page Locale
Updated tests for the push page variables to GTM data layer functionality
@amit-fytq amit-fytq changed the title Utm install time injection UTM install time injection Apr 20, 2026
@amit-fytq amit-fytq changed the title UTM install time injection UTM install time injection & GTM Data Layer push Apr 20, 2026
Updated all `amo/tracking` Jest mocks across the entire test suite to include `setPageVariables`
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 20, 2026

Codecov Report

❌ Patch coverage is 97.40260% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.12%. Comparing base (c1f13a3) to head (0c0de9f).
⚠️ Report is 68 commits behind head on master.

Files with missing lines Patch % Lines
src/amo/utils/installAttribution.js 90.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #14130      +/-   ##
==========================================
- Coverage   98.13%   98.12%   -0.01%     
==========================================
  Files         265      267       +2     
  Lines       10648    10714      +66     
  Branches     3285     3304      +19     
==========================================
+ Hits        10449    10513      +64     
- Misses        186      188       +2     
  Partials       13       13              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@diox
Copy link
Copy Markdown
Member

diox commented Apr 21, 2026

In #14089 you said:

We are also investigating ways to filter out the data within Google Analytics itself. If we can do that then we won't make any code changes for this. Will update thread later today.

And then you closed the PR. Could you elaborate on why this is necessary ?

@amit-fytq
Copy link
Copy Markdown
Contributor Author

In #14089 you said:

We are also investigating ways to filter out the data within Google Analytics itself. If we can do that then we won't make any code changes for this. Will update thread later today.

And then you closed the PR. Could you elaborate on why this is necessary ?

We discussed options with our GA team and they said GA4/GTM filtering is unreliable and can cause other issues with analytics reporting. Their recommendation was to make the change at the app level so that the incorrect data never hits Google Analytics.

@diox diox requested review from diox and willdurand May 18, 2026 10:36
@diox
Copy link
Copy Markdown
Member

diox commented May 19, 2026

I'm not done testing this yet, but I noticed something: GA's dataLayer "sees" and records the replaceState() calls you're doing at install as gtm.historyChange event, just like it would for regular client-side navigation events.

Is that going to be an issue in the reporting in GA?

Copy link
Copy Markdown
Member

@diox diox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be working at least for the primary hero, but as I commented I'm not sure what the impact of replaceState dance will be in GA...

Regardless, it needs quite a few tests around the primary hero/secondary hero changes + the new install attribution stuff: You have modified tests to ensure URLs are now "clean" but I don't see tests ensuring we are keeping track of the attribution in the redux store, or tests around installation and history manipulation to inject and remove the attribution params.

Comment thread src/amo/installAddon.js Outdated
@diox
Copy link
Copy Markdown
Member

diox commented May 19, 2026

FWIW, inspecting dataLayer after installing from various sources:

  • Primary hero ✅ (I can see the replaceState calls being made)
  • Secondary hero ❓ (hard to test manually)
  • "External" links ✅ (nothing should have changed here)
  • Other home shelves ❌ (no attribution recorded)
  • Search results ❌ (no attribution recorded)

I haven't tested yet if that actually translates to correct attribution in Firefox itself when it works.

- Added additional test coverage for the UTM params injection/removal functionality
- Added UTM params removal for event.target.error === ERROR_CORRUPT_FILE case
@amit-fytq
Copy link
Copy Markdown
Contributor Author

Added the following test coverage

Reducer (test_addoninstallSource.js)
  • Confirm initial state is null
  • Verify dispatching setAddonInstallSource updates the store with the correct source string
  • Verify dispatching clearAddonInstallSource resets the state to null when navigating away
  • Verify state is unaffected by unrelated actions
Core logic - injection and cleanup (test_installAttribution.js)
  • Verify that calling injectUTMParams(installSource) correctly appends utm_source=addons.mozilla.org, utm_medium=referral, and utm_content=[installSource] to the URL query string.
  • Verify that if a user landed on the page with existing external tracking parameters (e.g. from an email campaign or external site), the logic returns early without overwriting the original attribution.
  • Verify that removeUTMParams() strips away only the injected parameters (matching DEFAULT_UTM_SOURCE), returning the browser URL to its original, pristine path.
  • Verify that removeUTMParams() will never touch or remove external UTM query parameters.
Install lifecycle (TestInstallButtonWrapper.js)
  • Verify that if installSource is present in the Redux store, the wrapper executes _injectUTMParams with the stored source exactly when the user clicks "Add to Firefox".
  • Verify that _removeUTMParams is always invoked upon completion of the installation.
  • Verify that if the user triggers an installation directly without clicking an internal link (so installSource is null), the injection helper is skipped, but the cleanup is still safely run to guarantee idempotence.

@amit-fytq
Copy link
Copy Markdown
Contributor Author

Also discussed the other concerns with GA team. They advised that gtm.historyChange event will not impact the reporting or dashboard views

@amit-fytq
Copy link
Copy Markdown
Contributor Author

@diox - The test coverage has been expanded and is back to repo baseline

@amit-fytq amit-fytq requested a review from diox May 21, 2026 00:38
@diox
Copy link
Copy Markdown
Member

diox commented May 22, 2026

@amit-fytq I'm still having the issues I mentioned above. could you add functional tests ensuring install attribution is done correctly when clicking the primary hero, secondary hero, other shelves and search results ?

@amit-fytq
Copy link
Copy Markdown
Contributor Author

@amit-fytq I'm still having the issues I mentioned above. could you add functional tests ensuring install attribution is done correctly when clicking the primary hero, secondary hero, other shelves and search results ?

@diox - I am not sure what you mean by "install attribution" in the context of this UTM params removal PR? Do you mean Firefox telemetry attribution?

@diox
Copy link
Copy Markdown
Member

diox commented May 22, 2026

No, I mean the injection of the attribution source at install time

@amit-fytq
Copy link
Copy Markdown
Contributor Author

No, I mean the injection of the attribution source at install time

That is covered by the new tests which were added in the latest commit

#14130 (comment)

@diox
Copy link
Copy Markdown
Member

diox commented May 22, 2026

Those test cover individual component behavior. I'm interested in having functional tests that start from displaying the primary/secondary hero or shelves on the homepage, or search results, and end up with an install. There is no coverage for that currently (onInternalLinkClick() in SecondaryHero is not covered, neither is the dispatch(setAddonInstallSource(addonInstallSource)) call in SearchResult).

As I said above I could only get the end to end attribution to work properly with the primary hero, so something looks broken somewhere - having tests for that flow would help prove that it's working properly in all those cases.

@amit-fytq
Copy link
Copy Markdown
Contributor Author

amit-fytq commented May 22, 2026

@diox It seems the SearchResult component is invoking e.stopPropagation() inside its onClickAddon handler. This is preventing the click event from bubbling up to the wrapper <li>, which houses the onClickResult handler which dispatches setAddonInstallSource to Redux.

I'll fix this

- Fixes to SearchResult component to properly dispatch setAddonInstallSource to Redux
- Added functional tests
@amit-fytq
Copy link
Copy Markdown
Contributor Author

@diox please review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Task]: Merge UTM fixes to AMO

2 participants