Bug 1914974: Update Review Checker telemetry to record if a recommendation was sponsored. r=shopping-reviewers,kpatenio

- Adds an extra sponsored key to pass if the recommendation was sponsored or not to `surface_ads_placement`, `surface_ads_impression` and `surface_ads_clicked`.
- Records `ads_exposure` for all recommendations.
- Parses the JSON response in exposure telemetry tests.

Differential Revision: https://phabricator.services.mozilla.com/D220302
This commit is contained in:
Fred Chasen
2024-08-27 23:23:25 +00:00
parent 63c1f671af
commit dc7dbd88e6
5 changed files with 200 additions and 10 deletions

View File

@@ -147,7 +147,7 @@ export class ShoppingSidebarChild extends RemotePageChild {
}
handleEvent(event) {
let aid;
let aid, sponsored;
switch (event.type) {
case "ContentReady":
this.updateContent();
@@ -160,13 +160,15 @@ export class ShoppingSidebarChild extends RemotePageChild {
break;
case "AdClicked":
aid = event.detail.aid;
sponsored = event.detail.sponsored;
ShoppingProduct.sendAttributionEvent("click", aid);
Glean.shopping.surfaceAdsClicked.record();
Glean.shopping.surfaceAdsClicked.record({ sponsored });
break;
case "AdImpression":
aid = event.detail.aid;
sponsored = event.detail.sponsored;
ShoppingProduct.sendAttributionEvent("impression", aid);
Glean.shopping.surfaceAdsImpression.record();
Glean.shopping.surfaceAdsImpression.record({ sponsored });
break;
case "DisableShopping":
this.sendAsyncMessage("DisableShopping");
@@ -483,11 +485,16 @@ export class ShoppingSidebarChild extends RemotePageChild {
// We tried to fetch an ad, but didn't get one.
Glean.shopping.surfaceNoAdsAvailable.record();
} else {
let sponsored = recommendationData[0].sponsored;
ShoppingProduct.sendAttributionEvent(
"placement",
recommendationData[0].aid
);
Glean.shopping.surfaceAdsPlacement.record();
Glean.shopping.surfaceAdsPlacement.record({
sponsored,
});
}
this.sendToContent("UpdateRecommendations", {

View File

@@ -72,7 +72,7 @@ class RecommendedAd extends MozLitElement {
this.dispatchEvent(
new CustomEvent("AdImpression", {
bubbles: true,
detail: { aid: this.product.aid },
detail: { aid: this.product.aid, sponsored: this.product.sponsored },
})
);
@@ -87,7 +87,7 @@ class RecommendedAd extends MozLitElement {
this.dispatchEvent(
new CustomEvent("AdClicked", {
bubbles: true,
detail: { aid: this.product.aid },
detail: { aid: this.product.aid, sponsored: this.product.sponsored },
})
);
}

View File

@@ -572,6 +572,11 @@ shopping:
- fx-desktop-shopping-eng@mozilla.com
send_in_pings:
- events
extra_keys:
sponsored:
description: >
Whether the ad was sponsored or not.
type: boolean
surface_ads_impression:
type: event
@@ -589,6 +594,11 @@ shopping:
- fx-desktop-shopping-eng@mozilla.com
send_in_pings:
- events
extra_keys:
sponsored:
description: >
Whether the ad was sponsored or not.
type: boolean
surface_ads_placement:
type: event
@@ -606,6 +616,11 @@ shopping:
- fx-desktop-shopping-eng@mozilla.com
send_in_pings:
- events
extra_keys:
sponsored:
description: >
Whether the ad was sponsored or not.
type: boolean
surface_no_ads_available:
type: event

View File

@@ -7,7 +7,7 @@ const PRODUCT_PAGE = "https://example.com/product/B09TJGHL5F";
const ADS_JSON = `[{
"name": "Test product name ftw",
"url": ${PRODUCT_PAGE},
"url": "${PRODUCT_PAGE}",
"image_url": "https://i.fakespot.io/b6vx27xf3rgwr1a597q6qd3rutp6",
"price": "249.99",
"currency": "USD",
@@ -15,7 +15,20 @@ const ADS_JSON = `[{
"adjusted_rating": 4.6,
"analysis_url": "https://www.fakespot.com/product/test-product",
"sponsored": true,
"aid": "a2VlcCBvbiByb2NraW4gdGhlIGZyZWUgd2ViIQ==",
"aid": "a2VlcCBvbiByb2NraW4gdGhlIGZyZWUgd2ViIQ=="
}]`;
const RECOMMENDATIONS_JSON = `[{
"name": "Test product name ftw",
"url": "${PRODUCT_TEST_URL_NOT_SPONSORED}",
"image_url": "https://i.fakespot.io/b6vx27xf3rgwr1a597q6qd3rutp6",
"price": "249.99",
"currency": "USD",
"grade": "A",
"adjusted_rating": 4.6,
"analysis_url": "https://www.fakespot.com/product/test-product",
"sponsored": false,
"aid": "z2VlcCBvbiByb2NraW4gdGhlIGZyZWUgd2ViIQ=="
}]`;
// Verifies that, if the ads server returns an ad, but we have disabled
@@ -54,7 +67,7 @@ add_task(async function test_ads_exposure_disabled_not_recorded() {
product,
"requestRecommendations"
);
productRequestAdsStub.resolves(adResponse);
productRequestAdsStub.resolves(JSON.parse(adResponse));
let actor = content.windowGlobalChild.getActor("ShoppingSidebar");
actor.productURI = productURI;
@@ -174,7 +187,7 @@ add_task(async function test_ads_exposure_enabled_with_ad_recorded() {
product,
"requestRecommendations"
);
productRequestAdsStub.resolves(adResponse);
productRequestAdsStub.resolves(JSON.parse(adResponse));
let actor = content.windowGlobalChild.getActor("ShoppingSidebar");
actor.productURI = productURI;
@@ -211,3 +224,72 @@ add_task(async function test_ads_exposure_enabled_with_ad_recorded() {
);
await SpecialPowers.popPrefEnv();
});
// Verifies that ads exposure will be recorded for recommendations
// that are not sponsored.
add_task(async function test_not_sponsored_exposure() {
await Services.fog.testFlushAllChildren();
Services.fog.testResetFOG();
await SpecialPowers.pushPrefEnv({
set: [
["browser.shopping.experience2023.ads.enabled", true],
["browser.shopping.experience2023.ads.exposure", true],
],
});
await BrowserTestUtils.withNewTab(
{
url: "about:shoppingsidebar",
gBrowser,
},
async browser => {
await SpecialPowers.spawn(
browser,
[PRODUCT_TEST_URL_NOT_SPONSORED, RECOMMENDATIONS_JSON],
async (prodPage, adResponse) => {
const { ShoppingProduct } = ChromeUtils.importESModule(
"chrome://global/content/shopping/ShoppingProduct.mjs"
);
const { sinon } = ChromeUtils.importESModule(
"resource://testing-common/Sinon.sys.mjs"
);
let productURI = Services.io.newURI(prodPage);
let product = new ShoppingProduct(productURI);
let productRequestAdsStub = sinon.stub(
product,
"requestRecommendations"
);
productRequestAdsStub.resolves(JSON.parse(adResponse));
let actor = content.windowGlobalChild.getActor("ShoppingSidebar");
actor.productURI = productURI;
actor.product = product;
actor.requestRecommendations(productURI);
}
);
}
);
await Services.fog.testFlushAllChildren();
const events = Glean.shopping.adsExposure.testGetValue();
Assert.equal(
events.length,
1,
"Ads exposure should have been recorded if ads exposure was enabled and ads were returned"
);
Assert.equal(
events[0].category,
"shopping",
"Glean event should have category 'shopping'"
);
Assert.equal(
events[0].name,
"ads_exposure",
"Glean event should have name 'ads_exposure'"
);
await SpecialPowers.popPrefEnv();
});

View File

@@ -82,6 +82,7 @@ add_task(async function test_ad_attribution() {
Assert.equal(adsPlacementEvents.length, 1, "should have recorded an event");
Assert.equal(adsPlacementEvents[0].category, "shopping");
Assert.equal(adsPlacementEvents[0].name, "surface_ads_placement");
Assert.equal(adsPlacementEvents[0].extra.sponsored, "true");
let impressionEvent = recommendedAdsEventListener("AdImpression", sidebar);
@@ -100,6 +101,7 @@ add_task(async function test_ad_attribution() {
);
Assert.equal(adsImpressionEvents[0].category, "shopping");
Assert.equal(adsImpressionEvents[0].name, "surface_ads_impression");
Assert.equal(adsImpressionEvents[0].extra.sponsored, "true");
//
// Test that impression event is fired after switching to a tab that was
@@ -229,8 +231,92 @@ add_task(async function test_ad_attribution() {
Assert.equal(adsClickedEvents.length, 1, "should have recorded a click");
Assert.equal(adsClickedEvents[0].category, "shopping");
Assert.equal(adsClickedEvents[0].name, "surface_ads_clicked");
Assert.equal(adsClickedEvents[0].extra.sponsored, "true");
gBrowser.removeTab(adTab);
Services.fog.testResetFOG();
});
});
add_task(async function test_non_sponsored_attribution() {
await BrowserTestUtils.withNewTab(
PRODUCT_TEST_URL_NOT_SPONSORED,
async browser => {
let sidebar = gBrowser
.getPanel(browser)
.querySelector("shopping-sidebar");
info("Waiting for sidebar to update.");
await promiseSidebarUpdated(sidebar, PRODUCT_TEST_URL_NOT_SPONSORED);
await recommendedAdVisible(sidebar);
// Test placement was recorded by telemetry
info("Verifying recommendation placement event.");
await Services.fog.testFlushAllChildren();
var adsPlacementEvents =
Glean.shopping.surfaceAdsPlacement.testGetValue();
Assert.equal(
adsPlacementEvents.length,
1,
"should have recorded an event"
);
Assert.equal(adsPlacementEvents[0].category, "shopping");
Assert.equal(adsPlacementEvents[0].name, "surface_ads_placement");
Assert.equal(adsPlacementEvents[0].extra?.sponsored, "false");
let impressionEvent = recommendedAdsEventListener(
"AdImpression",
sidebar
);
info("Waiting for ad impression event.");
await impressionEvent;
Assert.ok(true, "Got ad impression event");
// Test the impression was recorded by telemetry
await Services.fog.testFlushAllChildren();
var adsImpressionEvents =
Glean.shopping.surfaceAdsImpression.testGetValue();
Assert.equal(
adsImpressionEvents.length,
1,
"should have recorded an event"
);
Assert.equal(adsImpressionEvents[0].category, "shopping");
Assert.equal(adsImpressionEvents[0].name, "surface_ads_impression");
Assert.equal(adsImpressionEvents[0].extra.sponsored, "false");
// Test ad clicked event
let adOpenedTabPromise = BrowserTestUtils.waitForNewTab(
gBrowser,
PRODUCT_TEST_URL_NOT_SPONSORED,
true
);
let clickedEvent = recommendedAdsEventListener("AdClicked", sidebar);
await SpecialPowers.spawn(sidebar.querySelector("browser"), [], () => {
let shoppingContainer =
content.document.querySelector("shopping-container").wrappedJSObject;
let adEl = shoppingContainer.recommendedAdEl;
adEl.linkEl.click();
});
let adTab = await adOpenedTabPromise;
info("Waiting for ad clicked event.");
await clickedEvent;
Assert.ok(true, "Got ad clicked event");
// Test the click was recorded by telemetry
await Services.fog.testFlushAllChildren();
var adsClickedEvents = Glean.shopping.surfaceAdsClicked.testGetValue();
Assert.equal(adsClickedEvents.length, 1, "should have recorded a click");
Assert.equal(adsClickedEvents[0].category, "shopping");
Assert.equal(adsClickedEvents[0].name, "surface_ads_clicked");
Assert.equal(adsClickedEvents[0].extra.sponsored, "false");
gBrowser.removeTab(adTab);
Services.fog.testResetFOG();
}
);
});