Skip to content

Commit

Permalink
Split product_download into product specific conversion events Fix #1…
Browse files Browse the repository at this point in the history
  • Loading branch information
stephaniehobson committed Jan 19, 2024
1 parent 29ddd50 commit 2bcd1b4
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 38 deletions.
4 changes: 2 additions & 2 deletions bedrock/mozorg/templatetags/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ def _get_adjust_link(adjust_url, app_store_url, google_play_url, redirect, local
if redirect_url:
link += "?redirect=" + quote(redirect_url, safe="") + "&" + params + "&mz_pl=" + redirect
else:
link += "?" + params + "&mz_pl=mobile"
link += "?" + params

return link

Expand All @@ -737,7 +737,7 @@ def firefox_adjust_url(ctx, redirect, adgroup, creative=None):
play_store_url = settings.GOOGLE_PLAY_FIREFOX_LINK
locale = getattr(ctx["request"], "locale", "en-US")

return _get_adjust_link(adjust_url, app_store_url, play_store_url, redirect, locale, "firefox", adgroup, creative)
return _get_adjust_link(adjust_url, app_store_url, play_store_url, redirect, locale, "firefox_mobile", adgroup, creative)


@library.global_function
Expand Down
16 changes: 8 additions & 8 deletions bedrock/mozorg/tests/test_helper_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -898,38 +898,38 @@ def test_firefox_ios_adjust_url(self):
assert (
self._render("en-US", "ios", "test-page") == "https://app.adjust.com/2uo1qc?redirect="
"https%3A%2F%2Fitunes.apple.com%2Fus%2Fapp%2Ffirefox-private-safe-browser%2Fid989804926"
"&campaign=www.mozilla.org&adgroup=test-page&mz_pr=firefox&mz_pl=ios"
"&campaign=www.mozilla.org&adgroup=test-page&mz_pr=firefox_mobile&mz_pl=ios"
)

def test_firefox_ios_adjust_url_invalid_country(self):
"""Firefox for mobile with an App Store URL redirect with an unsupported country"""
assert (
self._render("zz", "ios", "test-page") == "https://app.adjust.com/2uo1qc?redirect="
"https%3A%2F%2Fitunes.apple.com%2Fapp%2Ffirefox-private-safe-browser%2Fid989804926"
"&campaign=www.mozilla.org&adgroup=test-page&mz_pr=firefox&mz_pl=ios"
"&campaign=www.mozilla.org&adgroup=test-page&mz_pr=firefox_mobile&mz_pl=ios"
)

def test_firefox_ios_adjust_url_creative(self):
"""Firefox for mobile with an App Store URL redirect and creative param"""
assert (
self._render("de", "ios", "test-page", "experiment-name") == "https://app.adjust.com/2uo1qc?redirect="
"https%3A%2F%2Fitunes.apple.com%2Fde%2Fapp%2Ffirefox-private-safe-browser%2Fid989804926"
"&campaign=www.mozilla.org&adgroup=test-page&creative=experiment-name&mz_pr=firefox&mz_pl=ios"
"&campaign=www.mozilla.org&adgroup=test-page&creative=experiment-name&mz_pr=firefox_mobile&mz_pl=ios"
)

def test_firefox_android_adjust_url(self):
"""Firefox for mobile with a Play Store redirect"""
assert (
self._render("en-US", "android", "test-page") == "https://app.adjust.com/2uo1qc?redirect="
"https%3A%2F%2Fplay.google.com%2Fstore%2Fapps%2Fdetails%3Fid%3Dorg.mozilla.firefox"
"&campaign=www.mozilla.org&adgroup=test-page&mz_pr=firefox&mz_pl=android"
"&campaign=www.mozilla.org&adgroup=test-page&mz_pr=firefox_mobile&mz_pl=android"
)

def test_firefox_no_redirect_adjust_url(self):
"""Firefox for mobile with no redirect"""
assert (
self._render("en-US", None, "test-page") == "https://app.adjust.com/2uo1qc?"
"campaign=www.mozilla.org&adgroup=test-page&mz_pr=firefox&mz_pl=mobile"
"campaign=www.mozilla.org&adgroup=test-page&mz_pr=firefox_mobile"
)


Expand Down Expand Up @@ -981,7 +981,7 @@ def test_focus_no_redirect_adjust_url(self):
"""Firefox Focus for mobile with no redirect"""
assert (
self._render("en-US", None, "test-page") == "https://app.adjust.com/b8s7qo?"
"campaign=www.mozilla.org&adgroup=test-page&mz_pr=focus&mz_pl=mobile"
"campaign=www.mozilla.org&adgroup=test-page&mz_pr=focus"
)

def test_klar_ios_adjust_url(self):
Expand Down Expand Up @@ -1049,7 +1049,7 @@ def test_lockwise_no_redirect_adjust_url(self):
"""Firefox Lockwise for mobile with no redirect"""
assert (
self._render("en-US", None, "test-page") == "https://app.adjust.com/6tteyjo?"
"campaign=www.mozilla.org&adgroup=test-page&mz_pr=lockwise&mz_pl=mobile"
"campaign=www.mozilla.org&adgroup=test-page&mz_pr=lockwise"
)


Expand Down Expand Up @@ -1101,7 +1101,7 @@ def test_pocket_no_redirect_adjust_url(self):
"""Pocket for mobile with no redirect"""
assert (
self._render("en-US", None, "test-page") == "https://app.adjust.com/m54twk?"
"campaign=www.mozilla.org&adgroup=test-page&mz_pr=pocket&mz_pl=mobile"
"campaign=www.mozilla.org&adgroup=test-page&mz_pr=pocket"
)


Expand Down
53 changes: 39 additions & 14 deletions docs/attribution/0001-analytics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -384,30 +384,36 @@ link element.
```


Product Download
Product Downloads
~~~~~~~~~~~~~~~~

.. Important::

Only Firefox and Pocket are currently supported. VPN support has not been added.
VPN support has not been added. Firefox, Firefox Mobile, Focus, Klar, and Pocket are currently supported.

We are using a the custom event `product_download` to track product downloads and app store referrals
for Firefox, Pocket, and VPN. We are not using the default GA4 event file_download for a combination of reasons:
it does not trigger for the Firefox file types, we would like to collect more information than is included with
the default events, and we would like to treat product downloads as goals but not all file downloads are goals.
When the user signals their intent do install one of our products we log a download event named for the product.
This intent could be: clicking an app store badge, triggering a file download, or sending themselves the link
using the send to device widget. The events are in the format [product name]_download and all function the same.
So they use the same JavaScript "TrackProductDownload". For this documentation the following custom events will be
talked about as `product_download` :

.. Note::
- `firefox_download`
- `firefox_mobile_download`
- `focus_download`
- `klar_download`
- `pocket_download`

Most apps listed in *appstores.py* are supported but you may still want to check that the URL
you are tracking is identified as valid in ```isValidDownloadURL``` and will be recognized by ```getEventFromUrl``.
We are not using the default GA4 event file_download for a combination of reasons:
it does not trigger for the Firefox file types, we would like to collect more information than is included with
the default events, and we would like to treat product downloads as goals but not all file downloads are goals.

Properties for use with `product_download` (not all products will have all options):

- product (example: firefox)
- platform (example: win64)
- method (store, site, or adjust)
- release_channel (example: nightly)
- download_language (example: en-CA)
- product (one of: firefox, firefox_mobile, focus, klar, pocket, vpn)
- platform **optional** (one of: win, win-msi, win64, win64-msi, win64-aarch64, macos, linux, linux64, android, ios)
- method (one of: site, store, or adjust)
- release_channel **optional** (one of: release, esr, devedition, beta, nightly)
- download_language **optional** (example: en-CA)

There are two ways to use TrackProductDownload:

Expand All @@ -426,6 +432,25 @@ There are two ways to use TrackProductDownload:
You do NOT need to include ``datalayer-productdownload-init.es6.js`` in the page bundle, it is already included
in the site bundle.

.. Note::

Most apps listed in *appstores.py* are supported but you may still want to check that the URL
you are tracking is identified as valid in ```isValidDownloadURL``` and will be recognized by ```getEventFromUrl``.


If you would like to track something as a download that is not currently in the *appstores.py* you can
get and send the event object manually. This most often happens with adjust links generated for specific campaigns:

.. code-block:: javascript
let customEventObject = TrackProductDownload.getEventObject(
'firefox_mobile',
'', // if you are not redirecting to a specific store, leave platform empty
'adjust'
);
TrackProductDownload.sendEvent(customEventObject);
Widget Action
~~~~~~~~~~~~~

Expand Down
32 changes: 25 additions & 7 deletions media/js/base/datalayer-productdownload.es6.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ TrackProductDownload.getEventObject = (
download_language = false
) => {
const eventObject = {};
eventObject['event'] = 'product_download';
eventObject['event'] = product + '_download';
eventObject['product'] = product;
eventObject['platform'] = platform;
eventObject['method'] = method;
Expand Down Expand Up @@ -138,15 +138,15 @@ TrackProductDownload.getEventFromUrl = (downloadURL) => {

switch (idParam) {
case 'org.mozilla.firefox':
androidProduct = 'firefox';
androidProduct = 'firefox_mobile';
androidRelease = 'release';
break;
case 'org.mozilla.fenix':
androidProduct = 'firefox';
androidProduct = 'firefox_mobile';
androidRelease = 'nightly';
break;
case 'org.mozilla.firefox_beta':
androidProduct = 'firefox';
androidProduct = 'firefox_mobile';
androidRelease = 'beta';
break;
case 'org.mozilla.focus':
Expand All @@ -169,7 +169,7 @@ TrackProductDownload.getEventFromUrl = (downloadURL) => {
} else if (appStoreURL.test(downloadURL) || iTunesURL.test(downloadURL)) {
let iosProduct = 'unrecognized';
if (downloadURL.indexOf('/id989804926') !== -1) {
iosProduct = 'firefox';
iosProduct = 'firefox_mobile';
} else if (downloadURL.indexOf('/id1055677337') !== -1) {
iosProduct = 'focus';
} else if (downloadURL.indexOf('/id1073435754') !== -1) {
Expand All @@ -185,9 +185,11 @@ TrackProductDownload.getEventFromUrl = (downloadURL) => {
'release'
);
} else if (adjustURL.test(downloadURL)) {
const adjustProduct = params.mz_pr ? params.mz_pr : 'unrecognized';
const adjustPlatform = params.mz_pl ? params.mz_pl : '';
eventObject = TrackProductDownload.getEventObject(
params.mz_pr,
params.mz_pl,
adjustProduct,
adjustPlatform,
'adjust',
'release'
);
Expand Down Expand Up @@ -234,6 +236,22 @@ TrackProductDownload.sendEventFromURL = (downloadURL) => {
*/
TrackProductDownload.sendEvent = (eventObject) => {
window.dataLayer.push(eventObject);
// we also want to keep the old event name around for a few months to help with the transition
// this can be deleted as part of the UA cleanup
TrackProductDownload.sendOldEvent(eventObject);
};

/**
* Sends an version of the old product_download event to the data layer
* @param {Object} - product details formatted into a product_download event
*/
TrackProductDownload.sendOldEvent = (eventObject) => {
// deep copy of event object
const oldEventObject = JSON.parse(JSON.stringify(eventObject));
// replace event name with old event name
oldEventObject['event'] = 'product_download';
// add to dataLayer
window.dataLayer.push(oldEventObject);
};

export default TrackProductDownload;
20 changes: 13 additions & 7 deletions tests/unit/spec/base/datalayer-productdownload.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('TrackProductDownload.isValidDownloadURL', function () {
describe('TrackProductDownload.getEventObject', function () {
it('should insert parameters into the proper place in the event object', function () {
let testFullEventExpectedObject = {
event: 'product_download',
event: 'testProduct_download',
product: 'testProduct',
platform: 'testPlatform',
method: 'testMethod',
Expand All @@ -83,7 +83,7 @@ describe('TrackProductDownload.getEventObject', function () {
});
it('should create an event object even if there are no release_channel or download_language parameters', function () {
let testShortEventExpectedObject = {
event: 'product_download',
event: 'testProduct_download',
product: 'testProduct',
platform: 'testPlatform',
method: 'testMethod'
Expand All @@ -109,19 +109,19 @@ describe('TrackProductDownload.getEventFromUrl', function () {
let testEvent = TrackProductDownload.getEventFromUrl(
'https://itunes.apple.com/app/firefox-private-safe-browser/id989804926'
);
expect(testEvent['product']).toBe('firefox');
expect(testEvent['product']).toBe('firefox_mobile');
});
it('should identify product for Firefox in the Play Store', function () {
let testEvent = TrackProductDownload.getEventFromUrl(
'https://play.google.com/store/apps/details?id=org.mozilla.firefox&referrer=utm_source%3Dmozilla%26utm_medium%3DReferral%26utm_campaign%3Dmozilla-org'
);
expect(testEvent['product']).toBe('firefox');
expect(testEvent['product']).toBe('firefox_mobile');
});
it('should identify product for Firefox with Adjust', function () {
let testEvent = TrackProductDownload.getEventFromUrl(
'https://app.adjust.com/2uo1qc?mz_pr=firefox&mz_pl=mobile'
'https://app.adjust.com/2uo1qc?mz_pr=firefox_mobile&mz_pl=mobile'
);
expect(testEvent['product']).toBe('firefox');
expect(testEvent['product']).toBe('firefox_mobile');
});
it('should identify product for Pocket in the App Store', function () {
let testEvent = TrackProductDownload.getEventFromUrl(
Expand Down Expand Up @@ -244,6 +244,12 @@ describe('TrackProductDownload.getEventFromUrl', function () {
);
expect(testEvent['platform']).toBe('ios');
});
it('should not identify platform for Adjust link without redirect', function () {
let testEvent = TrackProductDownload.getEventFromUrl(
'https://app.adjust.com/b8s7qo'
);
expect(testEvent['platform']).toBe('');
});
// release channel
it('should identify release_channel for Firefox Release', function () {
let testEvent = TrackProductDownload.getEventFromUrl(
Expand Down Expand Up @@ -383,7 +389,7 @@ describe('TrackProductDownload.handleLink', function () {
expect(TrackProductDownload.isValidDownloadURL).toHaveBeenCalled();
expect(TrackProductDownload.getEventObject).toHaveBeenCalled();
expect(TrackProductDownload.sendEvent).toHaveBeenCalledWith({
event: 'product_download',
event: 'firefox_download',
product: 'firefox',
platform: 'win64',
method: 'site',
Expand Down

0 comments on commit 2bcd1b4

Please sign in to comment.