Skip to content

Commit

Permalink
Bug 1646151 - send deletion ping for each unenrolled Pioneer study r=…
Browse files Browse the repository at this point in the history
…sfoster,amiyaguchi

Differential Revision: https://phabricator.services.mozilla.com/D87523
  • Loading branch information
rhelmer committed Aug 23, 2020
1 parent 8f9966f commit a71b56e
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 27 deletions.
98 changes: 75 additions & 23 deletions browser/components/pioneer/content/pioneer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,16 @@
const { AddonManager } = ChromeUtils.import(
"resource://gre/modules/AddonManager.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");

const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);

const { RemoteSettings } = ChromeUtils.import(
"resource://services-settings/remote-settings.js"
);

XPCOMUtils.defineLazyModuleGetters(this, {
FileUtils: "resource://gre/modules/FileUtils.jsm",
});
const { TelemetryController } = ChromeUtils.import(
"resource://gre/modules/TelemetryController.jsm"
);

const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");

const PREF_PIONEER_ID = "toolkit.telemetry.pioneerId";
const PREF_PIONEER_NEW_STUDIES_AVAILABLE =
Expand Down Expand Up @@ -55,32 +52,35 @@ function showEnrollmentStatus() {
async function toggleEnrolled(studyAddonId, cachedAddons) {
let addon;
let install;
let cachedAddon;

for (cachedAddon of cachedAddons) {
if (studyAddonId == cachedAddon.addon_id) {
break;
}
}
const cachedAddon = cachedAddons.find(a => a.addon_id == studyAddonId);

if (Cu.isInAutomation) {
let testAddons = Services.prefs.getStringPref(PREF_TEST_ADDONS, "[]");
testAddons = JSON.parse(testAddons);
install = {
install: () => {
let testAddons = Services.prefs.getStringPref(PREF_TEST_ADDONS, "[]");
testAddons = JSON.parse(testAddons);

testAddons.push(studyAddonId);
Services.prefs.setStringPref(
PREF_TEST_ADDONS,
JSON.stringify(testAddons)
);
},
};

let testAddons = Services.prefs.getStringPref(PREF_TEST_ADDONS, "[]");
testAddons = JSON.parse(testAddons);

for (const testAddon of testAddons) {
if (testAddon == studyAddonId) {
addon = {};
addon.install = () => {};
addon.uninstall = () => {
Services.prefs.setStringPref(PREF_TEST_ADDONS, "[]");
Services.prefs.setStringPref(
PREF_TEST_ADDONS,
JSON.stringify(testAddons.filter(a => a.id != testAddon.id))
);
};
}
}
Expand All @@ -101,6 +101,8 @@ async function toggleEnrolled(studyAddonId, cachedAddons) {
if (addon) {
joinBtn.disabled = true;
await addon.uninstall();
await sendDeletionPing(studyAddonId);

document.l10n.setAttributes(joinBtn, "pioneer-join-study");
joinBtn.disabled = false;

Expand Down Expand Up @@ -400,17 +402,37 @@ async function setup(cachedAddons) {
document
.getElementById("leave-pioneer-accept-dialog-button")
.addEventListener("click", async event => {
const completedStudies = Services.prefs.getStringPref(
PREF_PIONEER_COMPLETED_STUDIES,
"{}"
);
const studies = JSON.parse(completedStudies);

// Send a deletion ping for all studies the user has been a part of.
for (const studyAddonId in studies) {
await sendDeletionPing(studyAddonId);
}

for (const cachedAddon of cachedAddons) {
const addon = await AddonManager.getAddonByID(cachedAddon.addon_id);
if (addon) {
// The user has ended all studies by unenrolling from Pioneer, so send deletion ping for any active studies.
await sendDeletionPing(addon.id);
await addon.uninstall();
}

const study = document.getElementById(cachedAddon.addon_id);
if (study) {
await updateStudy(cachedAddon.addon_id);
}
}

Services.prefs.clearUserPref(PREF_PIONEER_ID);
Services.prefs.clearUserPref(PREF_PIONEER_COMPLETED_STUDIES);

for (const cachedAddon of cachedAddons) {
// Record any studies that have been marked as concluded on the server.
// Record any studies that have been marked as concluded on the server, in case they re-enroll.
if ("studyEnded" in cachedAddon && cachedAddon.studyEnded === true) {
const completedStudies = Services.prefs.getStringPref(
PREF_PIONEER_COMPLETED_STUDIES,
"{}"
);
const studies = JSON.parse(completedStudies);
studies[cachedAddon.addon_id] = STUDY_LEAVE_REASONS.STUDY_ENDED;

Services.prefs.setStringPref(
Expand Down Expand Up @@ -577,3 +599,33 @@ document.addEventListener("DOMContentLoaded", async domEvent => {
await setup(cachedAddons);
await showAvailableStudies(cachedAddons);
});

async function sendDeletionPing(studyAddonId) {
const type = "pioneer-study";

const options = {
studyName: studyAddonId,
addPioneerId: true,
useEncryption: true,
// NOTE - while we're not actually sending useful data in this payload, the current Pioneer v2 Telemetry
// pipeline requires that pings are shaped this way so they are routed to the correct environment.
//
// At the moment, the public key used here isn't important but we do need to use *something*.
encryptionKeyId: "debug",
publicKey: {
crv: "P-256",
kty: "EC",
x: "XLkI3NaY3-AF2nRMspC63BT1u0Y3moXYSfss7VuQ0mk",
y: "SB0KnIW-pqk85OIEYZenoNkEyOOp5GeWQhS1KeRtEUE",
},
schemaName: "deletion-request",
schemaVersion: 1,
schemaNamespace: "pioneer-debug",
};

const payload = {
encryptedData: "",
};

await TelemetryController.submitExternalPing(type, payload, options);
}
50 changes: 46 additions & 4 deletions browser/components/pioneer/test/browser/browser_pioneer_ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ ChromeUtils.defineModuleGetter(
"resource://testing-common/ajv-4.1.1.js"
);

const { TelemetryArchive } = ChromeUtils.import(
"resource://gre/modules/TelemetryArchive.jsm"
);

const { TelemetryStorage } = ChromeUtils.import(
"resource://gre/modules/TelemetryStorage.jsm"
);

const PREF_PIONEER_ID = "toolkit.telemetry.pioneerId";
const PREF_PIONEER_NEW_STUDIES_AVAILABLE =
"toolkit.telemetry.pioneer-new-studies-available";
Expand Down Expand Up @@ -438,12 +446,29 @@ add_task(async function testAboutPage() {
const acceptUnenrollmentDialogButton = content.document.getElementById(
"leave-pioneer-accept-dialog-button"
);

acceptUnenrollmentDialogButton.click();

const pioneerUnenrolled = Services.prefs.getStringPref(
PREF_PIONEER_ID,
null
);
// Wait for deletion ping, uninstalls, and UI updates...
const pioneerUnenrolled = await new Promise((resolve, reject) => {
Services.prefs.addObserver(PREF_PIONEER_ID, function observer(
subject,
topic,
data
) {
try {
const prefValue = Services.prefs.getStringPref(
PREF_PIONEER_ID,
null
);
Services.prefs.removeObserver(PREF_PIONEER_ID, observer);
resolve(prefValue);
} catch (ex) {
Services.prefs.removeObserver(PREF_PIONEER_ID, observer);
reject(ex);
}
});
});

ok(
!pioneerUnenrolled,
Expand Down Expand Up @@ -477,6 +502,23 @@ add_task(async function testAboutPage() {
}
}
);

// Wait for any pending pings to settle.
await TelemetryStorage.testClearPendingPings();

let pings = await TelemetryArchive.promiseArchivedPingList();
ok(
// We expect two extra ping from the test studies being removed explicitly vs. leaving Pioneer and all studies being removed implicitly.
pings.length === CACHED_ADDONS.length + 1,
"The expected number of archived telemetry pings are present."
);

for (const ping of pings) {
ok(
ping.type === "pioneer-study",
"Deletion request telemetry ping was sent."
);
}
});

add_task(async function testPioneerBadge() {
Expand Down

0 comments on commit a71b56e

Please sign in to comment.