зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1693393 - add telemetry for sponsored TopSites in Urlbar r=dao,harry
Differential Revision: https://phabricator.services.mozilla.com/D105639
This commit is contained in:
Родитель
df23c6e58c
Коммит
59c6a49f3a
|
@ -14,6 +14,8 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
AppConstants: "resource://gre/modules/AppConstants.jsm",
|
||||
BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.jsm",
|
||||
BrowserUIUtils: "resource:///modules/BrowserUIUtils.jsm",
|
||||
CONTEXTUAL_SERVICES_PING_TYPES:
|
||||
"resource:///modules/PartnerLinkAttribution.jsm",
|
||||
ExtensionSearchHandler: "resource://gre/modules/ExtensionSearchHandler.jsm",
|
||||
ObjectUtils: "resource://gre/modules/ObjectUtils.jsm",
|
||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
|
||||
|
@ -44,6 +46,9 @@ XPCOMUtils.defineLazyServiceGetter(
|
|||
const DEFAULT_FORM_HISTORY_NAME = "searchbar-history";
|
||||
const SEARCH_BUTTON_ID = "urlbar-search-button";
|
||||
|
||||
// The scalar category of TopSites click for Contextual Services
|
||||
const SCALAR_CATEGORY_TOPSITES = "contextual.services.topsites.click";
|
||||
|
||||
let getBoundsWithoutFlushing = element =>
|
||||
element.ownerGlobal.windowUtils.getBoundsWithoutFlushing(element);
|
||||
let px = number => number.toFixed(2) + "px";
|
||||
|
@ -967,6 +972,25 @@ class UrlbarInput {
|
|||
"browser.partnerlink.campaign.topsites"
|
||||
),
|
||||
});
|
||||
if (!this.isPrivate && result.providerName === "UrlbarProviderTopSites") {
|
||||
// The position is 1-based for telemetry
|
||||
const position = selIndex + 1;
|
||||
Services.telemetry.keyedScalarAdd(
|
||||
SCALAR_CATEGORY_TOPSITES,
|
||||
`urlbar_${position}`,
|
||||
1
|
||||
);
|
||||
PartnerLinkAttribution.sendContextualServicesPing(
|
||||
{
|
||||
position,
|
||||
source: "urlbar",
|
||||
tile_id: result.payload.sponsoredTileId || -1,
|
||||
reporting_url: result.payload.sponsoredClickUrl,
|
||||
advertiser: result.payload.title.toLocaleLowerCase(),
|
||||
},
|
||||
CONTEXTUAL_SERVICES_PING_TYPES.TOPSITES_SELECTION
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this._loadURL(
|
||||
|
|
|
@ -12,6 +12,9 @@ const { XPCOMUtils } = ChromeUtils.import(
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
AboutNewTab: "resource:///modules/AboutNewTab.jsm",
|
||||
CONTEXTUAL_SERVICES_PING_TYPES:
|
||||
"resource:///modules/PartnerLinkAttribution.jsm",
|
||||
PartnerLinkAttribution: "resource:///modules/PartnerLinkAttribution.jsm",
|
||||
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
|
||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
|
@ -25,6 +28,9 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
TOP_SITES_DEFAULT_ROWS: "resource://activity-stream/common/Reducers.jsm",
|
||||
});
|
||||
|
||||
// The scalar category of TopSites impression for Contextual Services
|
||||
const SCALAR_CATEGORY_TOPSITES = "contextual.services.topsites.impression";
|
||||
|
||||
/**
|
||||
* This module exports a provider returning the user's newtab Top Sites.
|
||||
*/
|
||||
|
@ -137,33 +143,70 @@ class ProviderTopSites extends UrlbarProvider {
|
|||
);
|
||||
sites = sites.slice(0, numTopSites);
|
||||
|
||||
sites = sites.map(link => ({
|
||||
type: link.searchTopSite ? "search" : "url",
|
||||
url: link.url_urlbar || link.url,
|
||||
isPinned: !!link.isPinned,
|
||||
isSponsored: !!link.sponsored_position,
|
||||
// The newtab page allows the user to set custom site titles, which
|
||||
// are stored in `label`, so prefer it. Search top sites currently
|
||||
// don't have titles but `hostname` instead.
|
||||
title: link.label || link.title || link.hostname || "",
|
||||
favicon: link.smallFavicon || link.favicon || undefined,
|
||||
sendAttributionRequest: !!link.sendAttributionRequest,
|
||||
}));
|
||||
let sponsoredSites = [];
|
||||
let index = 1;
|
||||
sites = sites.map(link => {
|
||||
let site = {
|
||||
type: link.searchTopSite ? "search" : "url",
|
||||
url: link.url_urlbar || link.url,
|
||||
isPinned: !!link.isPinned,
|
||||
isSponsored: !!link.sponsored_position,
|
||||
// The newtab page allows the user to set custom site titles, which
|
||||
// are stored in `label`, so prefer it. Search top sites currently
|
||||
// don't have titles but `hostname` instead.
|
||||
title: link.label || link.title || link.hostname || "",
|
||||
favicon: link.smallFavicon || link.favicon || undefined,
|
||||
sendAttributionRequest: !!link.sendAttributionRequest,
|
||||
};
|
||||
if (site.isSponsored) {
|
||||
let {
|
||||
sponsored_tile_id,
|
||||
sponsored_impression_url,
|
||||
sponsored_click_url,
|
||||
} = link;
|
||||
site = {
|
||||
...site,
|
||||
sponsoredTileId: sponsored_tile_id,
|
||||
sponsoredImpressionUrl: sponsored_impression_url,
|
||||
sponsoredClickUrl: sponsored_click_url,
|
||||
position: index,
|
||||
};
|
||||
sponsoredSites.push(site);
|
||||
}
|
||||
index++;
|
||||
return site;
|
||||
});
|
||||
|
||||
// Store Sponsored Top Sites so we can use it in `onEngagement`
|
||||
if (sponsoredSites.length) {
|
||||
this.sponsoredSites = sponsoredSites;
|
||||
}
|
||||
|
||||
for (let site of sites) {
|
||||
switch (site.type) {
|
||||
case "url": {
|
||||
let payload = {
|
||||
title: site.title,
|
||||
url: site.url,
|
||||
icon: site.favicon,
|
||||
isPinned: site.isPinned,
|
||||
isSponsored: site.isSponsored,
|
||||
sendAttributionRequest: site.sendAttributionRequest,
|
||||
};
|
||||
if (site.isSponsored) {
|
||||
payload = {
|
||||
...payload,
|
||||
sponsoredTileId: site.sponsoredTileId,
|
||||
sponsoredClickUrl: site.sponsoredClickUrl,
|
||||
};
|
||||
}
|
||||
let result = new UrlbarResult(
|
||||
UrlbarUtils.RESULT_TYPE.URL,
|
||||
UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
|
||||
...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
|
||||
title: site.title,
|
||||
url: site.url,
|
||||
icon: site.favicon,
|
||||
isPinned: site.isPinned,
|
||||
isSponsored: site.isSponsored,
|
||||
sendAttributionRequest: site.sendAttributionRequest,
|
||||
})
|
||||
...UrlbarResult.payloadAndSimpleHighlights(
|
||||
queryContext.tokens,
|
||||
payload
|
||||
)
|
||||
);
|
||||
|
||||
let allowTabSwitch =
|
||||
|
@ -244,6 +287,48 @@ class ProviderTopSites extends UrlbarProvider {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user starts and ends an engagement with the urlbar. We send
|
||||
* the impression ping for the sponsored TopSites, the impression scalar is
|
||||
* recorded as well.
|
||||
*
|
||||
* Note:
|
||||
* * No telemetry recording in private browsing mode
|
||||
* * The impression is only recorded for the "engagement" and "abandonment"
|
||||
* states
|
||||
*
|
||||
* @param {boolean} isPrivate True if the engagement is in a private context.
|
||||
* @param {string} state The state of the engagement, one of: start,
|
||||
* engagement, abandonment, discard.
|
||||
*/
|
||||
onEngagement(isPrivate, state) {
|
||||
if (
|
||||
!isPrivate &&
|
||||
this.sponsoredSites &&
|
||||
["engagement", "abandonment"].includes(state)
|
||||
) {
|
||||
for (let site of this.sponsoredSites) {
|
||||
Services.telemetry.keyedScalarAdd(
|
||||
SCALAR_CATEGORY_TOPSITES,
|
||||
`urlbar_${site.position}`,
|
||||
1
|
||||
);
|
||||
PartnerLinkAttribution.sendContextualServicesPing(
|
||||
{
|
||||
source: "urlbar",
|
||||
tile_id: site.sponsoredTileId || -1,
|
||||
position: site.position,
|
||||
reporting_url: site.sponsoredImpressionUrl,
|
||||
advertiser: site.title.toLocaleLowerCase(),
|
||||
},
|
||||
CONTEXTUAL_SERVICES_PING_TYPES.TOPSITES_IMPRESSION
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.sponsoredSites = null;
|
||||
}
|
||||
}
|
||||
|
||||
var UrlbarProviderTopSites = new ProviderTopSites();
|
||||
|
|
|
@ -1198,6 +1198,12 @@ UrlbarUtils.RESULT_PAYLOAD_SCHEMA = {
|
|||
sendAttributionRequest: {
|
||||
type: "boolean",
|
||||
},
|
||||
sponsoredClickUrl: {
|
||||
type: "string",
|
||||
},
|
||||
sponsoredTileId: {
|
||||
type: "number",
|
||||
},
|
||||
tags: {
|
||||
type: "array",
|
||||
items: {
|
||||
|
|
|
@ -358,6 +358,61 @@ Event Extra
|
|||
|
||||
.. _URLBar provider experiments: experiments.html#developing-address-bar-extensions
|
||||
|
||||
|
||||
Custom pings for Contextual Services
|
||||
------------------------------------
|
||||
|
||||
Contextual Services currently has two features running within the Urlbar: TopSites
|
||||
and QuickSuggest. We send various pings as the `custom pings`_ to record the impressions
|
||||
and clicks of these two features.
|
||||
|
||||
.. _custom pings: https://docs.telemetry.mozilla.org/cookbooks/new_ping.html#sending-a-custom-ping
|
||||
|
||||
TopSites Impression
|
||||
This records an impression when a sponsored TopSite is shown.
|
||||
|
||||
- ``context_id``
|
||||
A UUID representing this user. Note that it's not client_id, nor can it be used to link to a client_id.
|
||||
- ``tile_id``
|
||||
A unique identifier for the sponsored TopSite.
|
||||
- ``source``
|
||||
The browser location where the impression was displayed.
|
||||
- ``position``
|
||||
The placement of the TopSite (1-based).
|
||||
- ``advertiser``
|
||||
The Name of the advertiser.
|
||||
- ``reporting_url``
|
||||
The reporting URL of the sponsored TopSite, normally pointing to the ad partner's reporting endpoint.
|
||||
- ``version``
|
||||
Firefox version.
|
||||
- ``release_channel``
|
||||
Firefox release channel.
|
||||
- ``locale``
|
||||
User's current locale.
|
||||
|
||||
TopSites Click
|
||||
This records a click ping when a sponsored TopSite is clicked by the user.
|
||||
|
||||
- ``context_id``
|
||||
A UUID representing this user. Note that it's not client_id, nor can it be used to link to a client_id.
|
||||
- ``tile_id``
|
||||
A unique identifier for the sponsored TopSite.
|
||||
- ``source``
|
||||
The browser location where the click was tirggered.
|
||||
- ``position``
|
||||
The placement of the TopSite (1-based).
|
||||
- ``advertiser``
|
||||
The Name of the advertiser.
|
||||
- ``reporting_url``
|
||||
The reporting URL of the sponsored TopSite, normally pointing to the ad partner's reporting endpoint.
|
||||
- ``version``
|
||||
Firefox version.
|
||||
- ``release_channel``
|
||||
Firefox release channel.
|
||||
- ``locale``
|
||||
User's current locale.
|
||||
|
||||
|
||||
Search probes relevant to the Address Bar
|
||||
-----------------------------------------
|
||||
|
||||
|
@ -421,6 +476,14 @@ browser.engagement.navigation.*
|
|||
For ``urlbar`` or ``searchbar``, indicates the user confirmed a search
|
||||
suggestion.
|
||||
|
||||
contextual.services.topsites.*
|
||||
These keyed scalars instrument the impressions and clicks for sponsored TopSites
|
||||
in the urlbar.
|
||||
The key is a combination of the source and the placement of the TopSites link
|
||||
(1-based) such as 'urlbar_1'. For each key, it records the counter of the
|
||||
impression or click.
|
||||
Note that these scalars are shared with the TopSites on the newtab page.
|
||||
|
||||
Obsolete probes
|
||||
---------------
|
||||
|
||||
|
|
|
@ -270,6 +270,8 @@ tags = search-telemetry
|
|||
support-files =
|
||||
urlbarTelemetrySearchSuggestions.sjs
|
||||
urlbarTelemetrySearchSuggestions.xml
|
||||
[browser_urlbar_telemetry_sponsored_topsites.js]
|
||||
tags = search-telemetry
|
||||
[browser_urlbar_telemetry_tabtosearch.js]
|
||||
tags = search-telemetry
|
||||
[browser_urlbar_telemetry_tip.js]
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
CONTEXTUAL_SERVICES_PING_TYPES:
|
||||
"resource:///modules/PartnerLinkAttribution.jsm",
|
||||
HttpServer: "resource://testing-common/httpd.js",
|
||||
PartnerLinkAttribution: "resource:///modules/PartnerLinkAttribution.jsm",
|
||||
NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
|
||||
});
|
||||
|
||||
const EN_US_TOPSITES =
|
||||
"https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://twitter.com/";
|
||||
|
||||
// This is used for "sendAttributionRequest"
|
||||
var gHttpServer = null;
|
||||
var gRequests = [];
|
||||
|
||||
function submitHandler(request, response) {
|
||||
gRequests.push(request);
|
||||
response.setStatusLine(request.httpVersion, 200, "Ok");
|
||||
}
|
||||
|
||||
// Spy for telemetry sender
|
||||
let spy;
|
||||
|
||||
add_task(async function setup() {
|
||||
sandbox = sinon.createSandbox();
|
||||
spy = sandbox.spy(
|
||||
PartnerLinkAttribution._pingCentre,
|
||||
"sendStructuredIngestionPing"
|
||||
);
|
||||
|
||||
let topsitesAttribution = Services.prefs.getStringPref(
|
||||
"browser.partnerlink.campaign.topsites"
|
||||
);
|
||||
gHttpServer = new HttpServer();
|
||||
gHttpServer.registerPathHandler(`/cid/${topsitesAttribution}`, submitHandler);
|
||||
gHttpServer.start(-1);
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["browser.urlbar.suggest.topsites", true],
|
||||
["browser.newtabpage.activity-stream.default.sites", EN_US_TOPSITES],
|
||||
[
|
||||
"browser.partnerlink.attributionURL",
|
||||
`http://localhost:${gHttpServer.identity.primaryPort}/cid/`,
|
||||
],
|
||||
],
|
||||
});
|
||||
|
||||
await updateTopSites(
|
||||
sites => sites && sites.length == EN_US_TOPSITES.split(",").length
|
||||
);
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
sandbox.restore();
|
||||
await gHttpServer.stop();
|
||||
gHttpServer = null;
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function send_impression_and_click() {
|
||||
await BrowserTestUtils.withNewTab("about:blank", async () => {
|
||||
let link = {
|
||||
label: "test_label",
|
||||
url: "http://example.com/",
|
||||
sponsored_position: 1,
|
||||
sendAttributionRequest: true,
|
||||
sponsored_tile_id: 42,
|
||||
sponsored_impression_url: "http://impression.test.com/",
|
||||
sponsored_click_url: "http://click.test.com/",
|
||||
};
|
||||
// Pin a sponsored TopSite to set up the test fixture
|
||||
NewTabUtils.pinnedLinks.pin(link, 0);
|
||||
|
||||
await updateTopSites(sites => sites && sites[0] && sites[0].isPinned);
|
||||
|
||||
await UrlbarTestUtils.promisePopupOpen(window, () => {
|
||||
EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
|
||||
});
|
||||
|
||||
await UrlbarTestUtils.promiseSearchComplete(window);
|
||||
|
||||
// Select the first result and confirm it.
|
||||
let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
|
||||
let loadPromise = BrowserTestUtils.waitForDocLoadAndStopIt(
|
||||
result.url,
|
||||
gBrowser.selectedBrowser
|
||||
);
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
await loadPromise;
|
||||
|
||||
Assert.ok(
|
||||
spy.calledTwice,
|
||||
"Should send an impression ping and a click ping"
|
||||
);
|
||||
|
||||
// Validate the impression ping
|
||||
let [payload, endpoint] = spy.firstCall.args;
|
||||
Assert.ok(
|
||||
endpoint.includes(CONTEXTUAL_SERVICES_PING_TYPES.TOPSITES_IMPRESSION),
|
||||
"Should set the endpoint for TopSites impression"
|
||||
);
|
||||
Assert.ok(!!payload.context_id, "Should set the context_id");
|
||||
Assert.equal(payload.advertiser, "test_label", "Should set the advertiser");
|
||||
Assert.equal(
|
||||
payload.reporting_url,
|
||||
"http://impression.test.com/",
|
||||
"Should set the impression reporting URL"
|
||||
);
|
||||
Assert.equal(payload.tile_id, 42, "Should set the tile_id");
|
||||
Assert.equal(payload.position, 1, "Should set the position");
|
||||
|
||||
// Validate the click ping
|
||||
[payload, endpoint] = spy.secondCall.args;
|
||||
Assert.ok(
|
||||
endpoint.includes(CONTEXTUAL_SERVICES_PING_TYPES.TOPSITES_SELECTION),
|
||||
"Should set the endpoint for TopSites click"
|
||||
);
|
||||
Assert.ok(!!payload.context_id, "Should set the context_id");
|
||||
Assert.equal(
|
||||
payload.reporting_url,
|
||||
"http://click.test.com/",
|
||||
"Should set the click reporting URL"
|
||||
);
|
||||
Assert.equal(payload.tile_id, 42, "Should set the tile_id");
|
||||
Assert.equal(payload.position, 1, "Should set the position");
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window, () => {
|
||||
gURLBar.blur();
|
||||
});
|
||||
|
||||
NewTabUtils.pinnedLinks.unpin(link);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function zero_ping() {
|
||||
await BrowserTestUtils.withNewTab("about:blank", async () => {
|
||||
spy.resetHistory();
|
||||
|
||||
// Reload the TopSites
|
||||
await updateTopSites(
|
||||
sites => sites && sites.length == EN_US_TOPSITES.split(",").length
|
||||
);
|
||||
|
||||
await UrlbarTestUtils.promisePopupOpen(window, () => {
|
||||
EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
|
||||
});
|
||||
|
||||
await UrlbarTestUtils.promiseSearchComplete(window);
|
||||
|
||||
// Select the first result and confirm it.
|
||||
let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
|
||||
let loadPromise = BrowserTestUtils.waitForDocLoadAndStopIt(
|
||||
result.url,
|
||||
gBrowser.selectedBrowser
|
||||
);
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
await loadPromise;
|
||||
|
||||
Assert.ok(
|
||||
spy.notCalled,
|
||||
"Should not send any ping if there is no sponsored Top Site"
|
||||
);
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window, () => {
|
||||
gURLBar.blur();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,7 +6,10 @@
|
|||
|
||||
Cu.importGlobalProperties(["fetch"]);
|
||||
|
||||
var EXPORTED_SYMBOLS = ["PartnerLinkAttribution"];
|
||||
var EXPORTED_SYMBOLS = [
|
||||
"PartnerLinkAttribution",
|
||||
"CONTEXTUAL_SERVICES_PING_TYPES",
|
||||
];
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
|
@ -15,8 +18,45 @@ const { XPCOMUtils } = ChromeUtils.import(
|
|||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
Region: "resource://gre/modules/Region.jsm",
|
||||
PingCentre: "resource:///modules/PingCentre.jsm",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetters(this, {
|
||||
gUUIDGenerator: ["@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"],
|
||||
});
|
||||
|
||||
// Endpoint base URL for Structured Ingestion
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"structuredIngestionEndpointBase",
|
||||
"browser.newtabpage.activity-stream.telemetry.structuredIngestion.endpoint",
|
||||
""
|
||||
);
|
||||
const NAMESPACE_CONTEXUAL_SERVICES = "contextual-services";
|
||||
|
||||
// PingCentre client to send custom pings
|
||||
XPCOMUtils.defineLazyGetter(this, "pingcentre", () => {
|
||||
return new PingCentre({ topic: "contextual-services" });
|
||||
});
|
||||
|
||||
// `contextId` is a unique identifier used by Contextual Services
|
||||
const CONTEXT_ID_PREF = "browser.contextual-services.contextId";
|
||||
XPCOMUtils.defineLazyGetter(this, "contextId", () => {
|
||||
let _contextId = Services.prefs.getStringPref(CONTEXT_ID_PREF, null);
|
||||
if (!_contextId) {
|
||||
_contextId = String(gUUIDGenerator.generateUUID());
|
||||
Services.prefs.setStringPref(CONTEXT_ID_PREF, _contextId);
|
||||
}
|
||||
return _contextId;
|
||||
});
|
||||
|
||||
const CONTEXTUAL_SERVICES_PING_TYPES = {
|
||||
TOPSITES_IMPRESSION: "topsites-impression",
|
||||
TOPSITES_SELECTION: "topsites-click",
|
||||
QS_IMPRESSION: "quicksuggest-impression",
|
||||
QS_SELECTION: "quicksuggest-click",
|
||||
};
|
||||
|
||||
var PartnerLinkAttribution = {
|
||||
/**
|
||||
* Sends an attribution request to an anonymizing proxy.
|
||||
|
@ -116,6 +156,39 @@ var PartnerLinkAttribution = {
|
|||
|
||||
await sendRequest(attributionUrl, "searchurl", strippedTargetUrl);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a Contextual Services ping to the Mozilla data pipeline.
|
||||
*
|
||||
* Note:
|
||||
* * All Contextual Services pings are sent as custom pings
|
||||
* (https://docs.telemetry.mozilla.org/cookbooks/new_ping.html#sending-a-custom-ping)
|
||||
*
|
||||
* * The full event list can be found at https://github.com/mozilla-services/mozilla-pipeline-schemas
|
||||
* under the "contextual-services" namespace
|
||||
*
|
||||
* @param {object} payload
|
||||
* The ping payload to be sent to the Mozilla Structured Ingestion endpoint
|
||||
* @param {String} pingType
|
||||
* The ping type. Must be one of CONTEXTUAL_SERVICES_PING_TYPES
|
||||
*/
|
||||
sendContextualServicesPing(payload, pingType) {
|
||||
if (!Object.values(CONTEXTUAL_SERVICES_PING_TYPES).includes(pingType)) {
|
||||
Cu.reportError("Invalid Contextual Services ping type");
|
||||
return;
|
||||
}
|
||||
|
||||
const endpoint = makeEndpointUrl(pingType, "1");
|
||||
payload.context_id = contextId;
|
||||
pingcentre.sendStructuredIngestionPing(payload, endpoint);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the underlying PingCentre client, only used for tests.
|
||||
*/
|
||||
get _pingCentre() {
|
||||
return pingcentre;
|
||||
},
|
||||
};
|
||||
|
||||
async function sendRequest(attributionUrl, source, targetURL) {
|
||||
|
@ -131,3 +204,26 @@ function recordTelemetryEvent({ method, objectString, value }) {
|
|||
Services.telemetry.setEventRecordingEnabled("partner_link", true);
|
||||
Services.telemetry.recordEvent("partner_link", method, objectString, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a new endpoint URL for a ping submission. Note that each submission
|
||||
* to Structured Ingesttion requires a new endpoint. See more details about
|
||||
* the specs:
|
||||
*
|
||||
* https://docs.telemetry.mozilla.org/concepts/pipeline/http_edge_spec.html?highlight=docId#postput-request
|
||||
*
|
||||
* @param {String} pingType
|
||||
* The ping type. Must be one of CONTEXTUAL_SERVICES_PING_TYPES
|
||||
* @param {String} version
|
||||
* The schema version of the ping.
|
||||
*/
|
||||
function makeEndpointUrl(pingType, version) {
|
||||
// Structured Ingestion does not support the UUID generated by gUUIDGenerator.
|
||||
// Stripping off the leading and trailing braces to make it happy.
|
||||
const docID = gUUIDGenerator
|
||||
.generateUUID()
|
||||
.toString()
|
||||
.slice(1, -1);
|
||||
const extension = `${NAMESPACE_CONTEXUAL_SERVICES}/${pingType}/${version}/${docID}`;
|
||||
return `${structuredIngestionEndpointBase}/${extension}`;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
PartnerLinkAttribution,
|
||||
CONTEXTUAL_SERVICES_PING_TYPES,
|
||||
} = ChromeUtils.import("resource:///modules/PartnerLinkAttribution.jsm");
|
||||
|
||||
const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm");
|
||||
|
||||
const FAKE_PING = { tile_id: 1, position: 1 };
|
||||
|
||||
let sandbox;
|
||||
let stub;
|
||||
|
||||
add_task(function setup() {
|
||||
sandbox = sinon.createSandbox();
|
||||
stub = sandbox.stub(
|
||||
PartnerLinkAttribution._pingCentre,
|
||||
"sendStructuredIngestionPing"
|
||||
);
|
||||
stub.returns(200);
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_sendContextualService_success() {
|
||||
for (const type of Object.values(CONTEXTUAL_SERVICES_PING_TYPES)) {
|
||||
PartnerLinkAttribution.sendContextualServicesPing(FAKE_PING, type);
|
||||
|
||||
Assert.ok(stub.calledOnce, `Should send the ping for ${type}`);
|
||||
|
||||
const [payload, endpoint] = stub.firstCall.args;
|
||||
Assert.ok(!!payload.context_id, "Should add context_id to the payload");
|
||||
Assert.ok(
|
||||
endpoint.includes(type),
|
||||
"Should include the ping type in the endpoint URL"
|
||||
);
|
||||
stub.resetHistory();
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function test_rejectUnknownPingType() {
|
||||
PartnerLinkAttribution.sendContextualServicesPing(FAKE_PING, "unknown-type");
|
||||
|
||||
Assert.ok(stub.notCalled, "Should not send the ping with unknown ping type");
|
||||
});
|
|
@ -16,3 +16,4 @@ skip-if = toolkit == 'android'
|
|||
skip-if = os != 'win' # Test of a Windows-specific feature
|
||||
[test_InstallationTelemetry.js]
|
||||
skip-if = os != 'win' # Test of a Windows-specific feature
|
||||
[test_PartnerLinkAttribution.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче