Bug 1631456 - Create a CFR message loader for ExperimentAPI r=k88hudson

Differential Revision: https://phabricator.services.mozilla.com/D71766
This commit is contained in:
Andrei Oprea 2020-04-24 15:30:28 +00:00
Родитель 8f02f0cbfa
Коммит 5cfefdf0ed
5 изменённых файлов: 264 добавлений и 0 удалений

Просмотреть файл

@ -1287,6 +1287,7 @@ pref("browser.newtabpage.activity-stream.asrouter.providers.message-groups", "{\
// this page over http opens us up to a man-in-the-middle attack that we'd rather not face. If you are a downstream
// repackager of this code using an alternate snippet url, please keep your users safe
pref("browser.newtabpage.activity-stream.asrouter.providers.snippets", "{\"id\":\"snippets\",\"enabled\":true,\"type\":\"remote\",\"url\":\"https://snippets.cdn.mozilla.net/%STARTPAGE_VERSION%/%NAME%/%VERSION%/%APPBUILDID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/\",\"updateCycleInMs\":14400000}");
pref("browser.newtabpage.activity-stream.asrouter.providers.messaging-experiments", "{\"id\":\"messaging-experiments\",\"enabled\":true,\"type\":\"remote-experiments\",\"messageGroups\":[\"cfr\",\"whats-new-panel\",\"moments-page\",\"snippets\",\"cfr-fxa\"],\"updateCycleInMs\":3600000}");
// The pref that controls if ASRouter uses the remote fluent files.
// It's enabled by default, but could be disabled to force ASRouter to use the local files.

Просмотреть файл

@ -35,6 +35,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
Downloader: "resource://services-settings/Attachments.jsm",
RemoteL10n: "resource://activity-stream/lib/RemoteL10n.jsm",
MigrationUtils: "resource:///modules/MigrationUtils.jsm",
ExperimentAPI: "resource://messaging-system/experiments/ExperimentAPI.jsm",
});
XPCOMUtils.defineLazyServiceGetters(this, {
BrowserHandler: ["@mozilla.org/browser/clh;1", "nsIBrowserHandler"],
@ -336,6 +337,31 @@ const MessageLoaderUtils = {
return RemoteSettings(bucket).get();
},
async _experimentsAPILoader(provider, options) {
try {
await ExperimentAPI.ready();
} catch (e) {
MessageLoaderUtils.reportError(e);
return [];
}
return provider.messageGroups
.map(group => {
let experimentData;
try {
experimentData = ExperimentAPI.getExperiment({ group });
} catch (e) {
MessageLoaderUtils.reportError(e);
return [];
}
if (experimentData && experimentData.branch) {
return experimentData.branch.value;
}
return [];
})
.flat();
},
_handleRemoteSettingsUndesiredEvent(event, providerId, dispatchToAS) {
if (dispatchToAS) {
dispatchToAS(
@ -363,6 +389,8 @@ const MessageLoaderUtils = {
return this._remoteSettingsLoader;
case "json":
return this._localJsonLoader;
case "remote-experiments":
return this._experimentsAPILoader;
case "local":
default:
return this._localLoader;

Просмотреть файл

@ -39,3 +39,4 @@ skip-if = (os == "linux") # Test setup only implemented for OSX and Windows
tags = remote-settings
[browser_asrouter_momentspagehub.js]
tags = remote-settings
[browser_asrouter_experimentsAPILoader.js]

Просмотреть файл

@ -0,0 +1,165 @@
const { RemoteSettings } = ChromeUtils.import(
"resource://services-settings/remote-settings.js"
);
const { ASRouter } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouter.jsm"
);
const { RemoteSettingsExperimentLoader } = ChromeUtils.import(
"resource://messaging-system/lib/RemoteSettingsExperimentLoader.jsm"
);
const { ExperimentAPI } = ChromeUtils.import(
"resource://messaging-system/experiments/ExperimentAPI.jsm"
);
const EXPERIMENT_PAYLOAD = {
enabled: true,
arguments: {
slug: "test_xman_cfr",
branches: [
{
slug: "control",
ratio: 1,
value: [
{
id: "xman_test_message",
content: {
text: "This is a test CFR",
addon: {
id: "954390",
icon:
"resource://activity-stream/data/content/assets/cfr_fb_container.png",
title: "Facebook Container",
users: 1455872,
author: "Mozilla",
rating: 4.5,
amo_url:
"https://addons.mozilla.org/firefox/addon/facebook-container/",
},
buttons: {
primary: {
label: {
string_id: "cfr-doorhanger-extension-ok-button",
},
action: {
data: {
url: null,
},
type: "INSTALL_ADDON_FROM_URL",
},
},
secondary: [
{
label: {
string_id: "cfr-doorhanger-extension-cancel-button",
},
action: {
type: "CANCEL",
},
},
{
label: {
string_id:
"cfr-doorhanger-extension-never-show-recommendation",
},
},
{
label: {
string_id:
"cfr-doorhanger-extension-manage-settings-button",
},
action: {
data: {
origin: "CFR",
category: "general-cfraddons",
},
type: "OPEN_PREFERENCES_PAGE",
},
},
],
},
category: "cfrAddons",
bucket_id: "CFR_M1",
info_icon: {
label: {
string_id: "cfr-doorhanger-extension-sumo-link",
},
sumo_path: "extensionrecommendations",
},
heading_text: "Welcome to the experiment",
notification_text: {
string_id: "cfr-doorhanger-extension-notification2",
},
},
trigger: {
id: "openURL",
params: [
"www.facebook.com",
"facebook.com",
"www.instagram.com",
"instagram.com",
"www.whatsapp.com",
"whatsapp.com",
"web.whatsapp.com",
"www.messenger.com",
"messenger.com",
],
},
template: "cfr_doorhanger",
frequency: {
lifetime: 3,
},
targeting: "true",
},
],
groups: ["cfr"],
},
],
isHighVolume: "false,",
userFacingName: "About:Welcome Pull Factor Reinforcement",
isEnrollmentPaused: false,
experimentDocumentUrl:
"https://experimenter.services.mozilla.com/experiments/aboutwelcome-pull-factor-reinforcement/",
userFacingDescription:
"This study uses 4 different variants of about:welcome with a goal of testing new experiment framework and get insights on whether reinforcing pull-factors improves retention.",
},
filter_expression: "true",
id: "test_xman_cfr",
};
add_task(async function test_loading_experimentsAPI() {
// Force the WNPanel provider cache to 0 by modifying updateCycleInMs
await SpecialPowers.pushPrefEnv({
set: [
[
"browser.newtabpage.activity-stream.asrouter.providers.messaging-experiments",
`{"id":"messaging-experiments","enabled":true,"type":"remote-experiments","messageGroups":["cfr","whats-new-panel","moments-page","snippets","cfr-fxa"],"frequency":{"custom":[{"period":"daily","cap":1}]},"updateCycleInMs":0}`,
],
],
});
const client = RemoteSettings("messaging-experiments");
await client.db.clear();
await client.db.create(
// Modify targeting to ensure the messages always show up
{ ...EXPERIMENT_PAYLOAD }
);
await client.db.saveLastModified(42); // Prevent from loading JSON dump.
// Fetch the new recipe from RS
await RemoteSettingsExperimentLoader.updateRecipes();
await BrowserTestUtils.waitForCondition(
() => ExperimentAPI.getExperiment({ group: "cfr" }),
"ExperimentAPI should return an experiment"
);
// Reload the provider
await ASRouter._updateMessageProviders();
// Wait to load the messages from the messaging-experiments provider
await ASRouter.loadMessagesFromAllProviders();
Assert.ok(ASRouter.state.messages.find(m => m.id === "xman_test_message"));
await client.db.clear();
await SpecialPowers.popPrefEnv();
// Reload the provider
await ASRouter._updateMessageProviders();
});

Просмотреть файл

@ -206,6 +206,10 @@ describe("ASRouter", () => {
return Promise.resolve("/path/to/download");
}
},
ExperimentAPI: {
getExperiment: sandbox.stub().returns({ branch: { value: [] } }),
ready: sandbox.stub().resolves(),
},
});
await createRouterAndInit();
});
@ -4161,6 +4165,71 @@ describe("ASRouter", () => {
});
});
describe("#loadMessagesForProvider", () => {
it("should fetch messages from the ExperimentAPI", async () => {
const args = {
type: "remote-experiments",
messageGroups: ["asrouter"],
};
await MessageLoaderUtils.loadMessagesForProvider(args);
assert.calledOnce(global.ExperimentAPI.getExperiment);
assert.calledWithExactly(global.ExperimentAPI.getExperiment, {
group: "asrouter",
});
});
it("should handle the case of no experiments in the ExperimentAPI", async () => {
const args = {
type: "remote-experiments",
messageGroups: ["asrouter"],
};
global.ExperimentAPI.getExperiment.throws();
const stub = sandbox.stub(MessageLoaderUtils, "reportError");
await MessageLoaderUtils.loadMessagesForProvider(args);
assert.calledOnce(stub);
});
it("should handle the case of no experiments in the ExperimentAPI", async () => {
const args = {
type: "remote-experiments",
messageGroups: ["asrouter"],
};
global.ExperimentAPI.getExperiment.returns(null);
const result = await MessageLoaderUtils.loadMessagesForProvider(args);
assert.lengthOf(result.messages, 0);
});
it("should normally load ExperimentAPI messages", async () => {
const args = {
type: "remote-experiments",
messageGroups: ["asrouter"],
};
global.ExperimentAPI.getExperiment.returns({
branch: { value: ["foo", "bar"] },
});
const result = await MessageLoaderUtils.loadMessagesForProvider(args);
assert.lengthOf(result.messages, 2);
});
it("should fetch messages from the ExperimentAPI", async () => {
global.ExperimentAPI.ready.throws();
const args = {
type: "remote-experiments",
messageGroups: ["asrouter"],
};
const stub = sandbox.stub(MessageLoaderUtils, "reportError");
await MessageLoaderUtils.loadMessagesForProvider(args);
assert.notCalled(global.ExperimentAPI.getExperiment);
assert.calledOnce(stub);
});
it("should fetch json from url", async () => {
let result = await MessageLoaderUtils.loadMessagesForProvider({
location: "http://fake.com/endpoint",