diff --git a/bedrock/mozorg/templatetags/misc.py b/bedrock/mozorg/templatetags/misc.py index 15e72c21f78..15a3f6c217f 100644 --- a/bedrock/mozorg/templatetags/misc.py +++ b/bedrock/mozorg/templatetags/misc.py @@ -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 @@ -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 diff --git a/bedrock/mozorg/tests/test_helper_misc.py b/bedrock/mozorg/tests/test_helper_misc.py index 793b639270e..3ed6242c410 100644 --- a/bedrock/mozorg/tests/test_helper_misc.py +++ b/bedrock/mozorg/tests/test_helper_misc.py @@ -898,7 +898,7 @@ 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): @@ -906,7 +906,7 @@ def test_firefox_ios_adjust_url_invalid_country(self): 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): @@ -914,7 +914,7 @@ def test_firefox_ios_adjust_url_creative(self): 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): @@ -922,14 +922,14 @@ def test_firefox_android_adjust_url(self): 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" ) @@ -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): @@ -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" ) @@ -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" ) diff --git a/docs/attribution/0001-analytics.rst b/docs/attribution/0001-analytics.rst index 3ca0aeb9b35..c504345b24f 100644 --- a/docs/attribution/0001-analytics.rst +++ b/docs/attribution/0001-analytics.rst @@ -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: @@ -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 ~~~~~~~~~~~~~ diff --git a/media/js/base/datalayer-productdownload.es6.js b/media/js/base/datalayer-productdownload.es6.js index 8b53e1ab3fe..8bd6d1647a9 100644 --- a/media/js/base/datalayer-productdownload.es6.js +++ b/media/js/base/datalayer-productdownload.es6.js @@ -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; @@ -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': @@ -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) { @@ -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' ); @@ -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; diff --git a/tests/unit/spec/base/datalayer-productdownload.js b/tests/unit/spec/base/datalayer-productdownload.js index 565fb641605..772fa36ab2c 100644 --- a/tests/unit/spec/base/datalayer-productdownload.js +++ b/tests/unit/spec/base/datalayer-productdownload.js @@ -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', @@ -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' @@ -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( @@ -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( @@ -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',