forked from mirrors/gecko-dev
Bug 1844165 - Add a script to import messaging rollouts from Nimbus. r=omc-reviewers,emcminn,negin
Differential Revision: https://phabricator.services.mozilla.com/D183916
This commit is contained in:
parent
aa8eccb5a8
commit
460a0637fd
7 changed files with 612 additions and 65 deletions
366
browser/components/newtab/bin/import-rollouts.js
Normal file
366
browser/components/newtab/bin/import-rollouts.js
Normal file
|
|
@ -0,0 +1,366 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a script to import Nimbus experiments from a given collection into
|
||||||
|
* browser/components/newtab/test/NimbusRolloutMessageProvider.sys.mjs. By
|
||||||
|
* default, it only imports messaging rollouts. This is done so that the content
|
||||||
|
* of off-train rollouts can be easily searched. That way, when we are cleaning
|
||||||
|
* up old assets (such as Fluent strings), we don't accidentally delete strings
|
||||||
|
* that live rollouts are using because it was too difficult to find whether
|
||||||
|
* they were in use.
|
||||||
|
*
|
||||||
|
* This works by fetching the message records from the Nimbus collection and
|
||||||
|
* then writing them to the file. The messages are converted from JSON to JS.
|
||||||
|
* The file is structured like this:
|
||||||
|
* export const NimbusRolloutMessageProvider = {
|
||||||
|
* getMessages() {
|
||||||
|
* return [
|
||||||
|
* { ...message1 },
|
||||||
|
* { ...message2 },
|
||||||
|
* ];
|
||||||
|
* },
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable max-depth, no-console */
|
||||||
|
const meow = require("meow");
|
||||||
|
const chalk = require("chalk");
|
||||||
|
const https = require("https");
|
||||||
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
|
const util = require("util");
|
||||||
|
const prettier = require("prettier");
|
||||||
|
const jsonschema = require("../../../../third_party/js/cfworker/json-schema.js");
|
||||||
|
|
||||||
|
const DEFAULT_COLLECTION_ID = "nimbus-desktop-experiments";
|
||||||
|
const BASE_URL =
|
||||||
|
"https://firefox.settings.services.mozilla.com/v1/buckets/main/collections/";
|
||||||
|
const EXPERIMENTER_URL = "https://experimenter.services.mozilla.com/nimbus/";
|
||||||
|
const OUTPUT_PATH = "./test/NimbusRolloutMessageProvider.sys.mjs";
|
||||||
|
const LICENSE_STRING = `/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */`;
|
||||||
|
|
||||||
|
const cli = meow(
|
||||||
|
`
|
||||||
|
Usage
|
||||||
|
$ node bin/import-rollouts.js [options]
|
||||||
|
|
||||||
|
Options
|
||||||
|
-c ID, --collection ID The Nimbus collection ID to import from
|
||||||
|
default: ${DEFAULT_COLLECTION_ID}
|
||||||
|
-e, --experiments Import all messaging experiments, not just rollouts
|
||||||
|
-s, --skip-validation Skip validation of experiments and messages
|
||||||
|
-h, --help Show this help message
|
||||||
|
|
||||||
|
Examples
|
||||||
|
$ node bin/import-rollouts.js --collection nimbus-preview
|
||||||
|
$ ./mach npm run import-rollouts --prefix=browser/components/newtab -- -e
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
description: false,
|
||||||
|
// `pkg` is a tiny optimization. It prevents meow from looking for a package
|
||||||
|
// that doesn't technically exist. meow searches for a package and changes
|
||||||
|
// the process name to the package name. It resolves to the newtab
|
||||||
|
// package.json, which would give a confusing name and be wasteful.
|
||||||
|
pkg: {
|
||||||
|
name: "import-rollouts",
|
||||||
|
version: "1.0.0",
|
||||||
|
},
|
||||||
|
flags: {
|
||||||
|
collection: {
|
||||||
|
type: "string",
|
||||||
|
alias: "c",
|
||||||
|
default: DEFAULT_COLLECTION_ID,
|
||||||
|
},
|
||||||
|
experiments: {
|
||||||
|
type: "boolean",
|
||||||
|
alias: "e",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
skipValidation: {
|
||||||
|
type: "boolean",
|
||||||
|
alias: "s",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const RECORDS_URL = `${BASE_URL}${cli.flags.collection}/records`;
|
||||||
|
|
||||||
|
function fetchJSON(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
https
|
||||||
|
.get(url, resp => {
|
||||||
|
let data = "";
|
||||||
|
resp.on("data", chunk => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
resp.on("end", () => resolve(JSON.parse(data)));
|
||||||
|
})
|
||||||
|
.on("error", reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMessageValid(validator, obj) {
|
||||||
|
if (validator) {
|
||||||
|
const result = validator.validate(obj);
|
||||||
|
return result.valid && result.errors.length === 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMessageValidators(skipValidation) {
|
||||||
|
if (skipValidation) {
|
||||||
|
return { experimentValidator: null, messageValidators: {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSchema(filePath) {
|
||||||
|
const file = await util.promisify(fs.readFile)(filePath, "utf8");
|
||||||
|
return JSON.parse(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getValidator(filePath, { common = false } = {}) {
|
||||||
|
const schema = await getSchema(filePath);
|
||||||
|
const validator = new jsonschema.Validator(schema);
|
||||||
|
|
||||||
|
if (common) {
|
||||||
|
const commonSchema = await getSchema(
|
||||||
|
"./content-src/asrouter/schemas/FxMSCommon.schema.json"
|
||||||
|
);
|
||||||
|
validator.addSchema(commonSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
return validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
const experimentValidator = await getValidator(
|
||||||
|
"./content-src/asrouter/schemas/MessagingExperiment.schema.json"
|
||||||
|
);
|
||||||
|
|
||||||
|
const messageValidators = {
|
||||||
|
cfr_doorhanger: await getValidator(
|
||||||
|
"./content-src/asrouter/templates/CFR/templates/ExtensionDoorhanger.schema.json",
|
||||||
|
{ common: true }
|
||||||
|
),
|
||||||
|
cfr_urlbar_chiclet: await getValidator(
|
||||||
|
"./content-src/asrouter/templates/CFR/templates/CFRUrlbarChiclet.schema.json",
|
||||||
|
{ common: true }
|
||||||
|
),
|
||||||
|
infobar: await getValidator(
|
||||||
|
"./content-src/asrouter/templates/CFR/templates/InfoBar.schema.json",
|
||||||
|
{ common: true }
|
||||||
|
),
|
||||||
|
pb_newtab: await getValidator(
|
||||||
|
"./content-src/asrouter/templates/PBNewtab/NewtabPromoMessage.schema.json",
|
||||||
|
{ common: true }
|
||||||
|
),
|
||||||
|
protections_panel: await getValidator(
|
||||||
|
"./content-src/asrouter/templates/OnboardingMessage/ProtectionsPanelMessage.schema.json",
|
||||||
|
{ common: true }
|
||||||
|
),
|
||||||
|
spotlight: await getValidator(
|
||||||
|
"./content-src/asrouter/templates/OnboardingMessage/Spotlight.schema.json",
|
||||||
|
{ common: true }
|
||||||
|
),
|
||||||
|
toast_notification: await getValidator(
|
||||||
|
"./content-src/asrouter/templates/ToastNotification/ToastNotification.schema.json",
|
||||||
|
{ common: true }
|
||||||
|
),
|
||||||
|
toolbar_badge: await getValidator(
|
||||||
|
"./content-src/asrouter/templates/OnboardingMessage/ToolbarBadgeMessage.schema.json",
|
||||||
|
{ common: true }
|
||||||
|
),
|
||||||
|
update_action: await getValidator(
|
||||||
|
"./content-src/asrouter/templates/OnboardingMessage/UpdateAction.schema.json",
|
||||||
|
{ common: true }
|
||||||
|
),
|
||||||
|
whatsnew_panel_message: await getValidator(
|
||||||
|
"./content-src/asrouter/templates/OnboardingMessage/WhatsNewMessage.schema.json",
|
||||||
|
{ common: true }
|
||||||
|
),
|
||||||
|
feature_callout: await getValidator(
|
||||||
|
// For now, Feature Callout and Spotlight share a common schema
|
||||||
|
"./content-src/asrouter/templates/OnboardingMessage/Spotlight.schema.json",
|
||||||
|
{ common: true }
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
messageValidators.milestone_message = messageValidators.cfr_doorhanger;
|
||||||
|
|
||||||
|
return { experimentValidator, messageValidators };
|
||||||
|
}
|
||||||
|
|
||||||
|
function annotateMessage({ message, slug, minVersion, maxVersion, url }) {
|
||||||
|
const comments = [];
|
||||||
|
if (slug) {
|
||||||
|
comments.push(`// Nimbus slug: ${slug}`);
|
||||||
|
}
|
||||||
|
let versionRange = "";
|
||||||
|
if (minVersion) {
|
||||||
|
versionRange = minVersion;
|
||||||
|
if (maxVersion) {
|
||||||
|
versionRange += `-${maxVersion}`;
|
||||||
|
} else {
|
||||||
|
versionRange += "+";
|
||||||
|
}
|
||||||
|
} else if (maxVersion) {
|
||||||
|
versionRange = `0-${maxVersion}`;
|
||||||
|
}
|
||||||
|
if (versionRange) {
|
||||||
|
comments.push(`// Version range: ${versionRange}`);
|
||||||
|
}
|
||||||
|
if (url) {
|
||||||
|
comments.push(`// Recipe: ${url}`);
|
||||||
|
}
|
||||||
|
return JSON.stringify(message, null, 2).replace(
|
||||||
|
/^{/,
|
||||||
|
`{ ${comments.join("\n")}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function format(content) {
|
||||||
|
const config = await prettier.resolveConfig("./.prettierrc.js");
|
||||||
|
return prettier.format(content, { ...config, filepath: OUTPUT_PATH });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const { MESSAGING_EXPERIMENTS_DEFAULT_FEATURES } = await import(
|
||||||
|
"../lib/MessagingExperimentConstants.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Fetching records from ${chalk.underline.yellow(RECORDS_URL)}`);
|
||||||
|
|
||||||
|
const { data: records } = await fetchJSON(RECORDS_URL);
|
||||||
|
|
||||||
|
if (!Array.isArray(records)) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Expected records to be an array, got ${typeof records}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipes = records.filter(
|
||||||
|
record =>
|
||||||
|
record.application === "firefox-desktop" &&
|
||||||
|
record.featureIds.some(id =>
|
||||||
|
MESSAGING_EXPERIMENTS_DEFAULT_FEATURES.includes(id)
|
||||||
|
) &&
|
||||||
|
(record.isRollout || cli.flags.experiments)
|
||||||
|
);
|
||||||
|
|
||||||
|
const importItems = [];
|
||||||
|
const { experimentValidator, messageValidators } = await getMessageValidators(
|
||||||
|
cli.flags.skipValidation
|
||||||
|
);
|
||||||
|
for (const recipe of recipes) {
|
||||||
|
const { slug: experimentSlug, branches, targeting } = recipe;
|
||||||
|
if (!(experimentSlug && Array.isArray(branches) && branches.length)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
`Processing ${recipe.isRollout ? "rollout" : "experiment"}: ${chalk.blue(
|
||||||
|
experimentSlug
|
||||||
|
)}${
|
||||||
|
branches.length > 1
|
||||||
|
? ` with ${chalk.underline(`${String(branches.length)} branches`)}`
|
||||||
|
: ""
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
const recipeUrl = `${EXPERIMENTER_URL}${experimentSlug}/summary`;
|
||||||
|
const [, minVersion] =
|
||||||
|
targeting?.match(/\(version\|versionCompare\(\'([0-9]+)\.!\'\) >= 0/) ||
|
||||||
|
[];
|
||||||
|
const [, maxVersion] =
|
||||||
|
targeting?.match(/\(version\|versionCompare\(\'([0-9]+)\.\*\'\) <= 0/) ||
|
||||||
|
[];
|
||||||
|
let branchIndex = branches.length > 1 ? 1 : 0;
|
||||||
|
for (const branch of branches) {
|
||||||
|
const { slug: branchSlug, features } = branch;
|
||||||
|
console.log(
|
||||||
|
` Processing branch${
|
||||||
|
branchIndex > 0 ? ` ${branchIndex} of ${branches.length}` : ""
|
||||||
|
}: ${chalk.blue(branchSlug)}`
|
||||||
|
);
|
||||||
|
branchIndex += 1;
|
||||||
|
const url = `${recipeUrl}#${branchSlug}`;
|
||||||
|
if (!Array.isArray(features)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const feature of features) {
|
||||||
|
if (
|
||||||
|
feature.enabled &&
|
||||||
|
MESSAGING_EXPERIMENTS_DEFAULT_FEATURES.includes(feature.featureId) &&
|
||||||
|
feature.value &&
|
||||||
|
typeof feature.value === "object" &&
|
||||||
|
feature.value.template
|
||||||
|
) {
|
||||||
|
if (!isMessageValid(experimentValidator, feature.value)) {
|
||||||
|
console.log(
|
||||||
|
` ${chalk.red(
|
||||||
|
"✗"
|
||||||
|
)} Skipping invalid value for branch: ${chalk.blue(branchSlug)}`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const messages = (
|
||||||
|
feature.value.template === "multi" &&
|
||||||
|
Array.isArray(feature.value.messages)
|
||||||
|
? feature.value.messages
|
||||||
|
: [feature.value]
|
||||||
|
).filter(m => m && m.id);
|
||||||
|
let msgIndex = messages.length > 1 ? 1 : 0;
|
||||||
|
for (const message of messages) {
|
||||||
|
let messageLogString = `message${
|
||||||
|
msgIndex > 0 ? ` ${msgIndex} of ${messages.length}` : ""
|
||||||
|
}: ${chalk.italic.green(message.id)}`;
|
||||||
|
if (!isMessageValid(messageValidators[message.template], message)) {
|
||||||
|
console.log(
|
||||||
|
` ${chalk.red("✗")} Skipping invalid ${messageLogString}`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
console.log(` Importing ${messageLogString}`);
|
||||||
|
let slug = `${experimentSlug}:${branchSlug}`;
|
||||||
|
if (msgIndex > 0) {
|
||||||
|
slug += ` (message ${msgIndex} of ${messages.length})`;
|
||||||
|
}
|
||||||
|
msgIndex += 1;
|
||||||
|
importItems.push({ message, slug, minVersion, maxVersion, url });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = `${LICENSE_STRING}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is generated by browser/components/newtab/bin/import-rollouts.js
|
||||||
|
* Run the following from the repository root to regenerate it:
|
||||||
|
* ./mach npm run import-rollouts --prefix=browser/components/newtab
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const NimbusRolloutMessageProvider = {
|
||||||
|
getMessages() {
|
||||||
|
return [${importItems.map(annotateMessage).join(",\n")}];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const formattedContent = await format(content);
|
||||||
|
|
||||||
|
await util.promisify(fs.writeFile)(OUTPUT_PATH, formattedContent);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`${chalk.green("✓")} Wrote ${chalk.underline.green(
|
||||||
|
`${String(importItems.length)} ${
|
||||||
|
importItems.length === 1 ? "message" : "messages"
|
||||||
|
}`
|
||||||
|
)} to ${chalk.underline.yellow(path.resolve(OUTPUT_PATH))}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
@ -52,7 +52,9 @@ XPCOMUtils.defineLazyServiceGetters(lazy, {
|
||||||
const { actionCreators: ac } = ChromeUtils.importESModule(
|
const { actionCreators: ac } = ChromeUtils.importESModule(
|
||||||
"resource://activity-stream/common/Actions.sys.mjs"
|
"resource://activity-stream/common/Actions.sys.mjs"
|
||||||
);
|
);
|
||||||
|
const { MESSAGING_EXPERIMENTS_DEFAULT_FEATURES } = ChromeUtils.importESModule(
|
||||||
|
"resource://activity-stream/lib/MessagingExperimentConstants.sys.mjs"
|
||||||
|
);
|
||||||
const { CFRMessageProvider } = ChromeUtils.importESModule(
|
const { CFRMessageProvider } = ChromeUtils.importESModule(
|
||||||
"resource://activity-stream/lib/CFRMessageProvider.sys.mjs"
|
"resource://activity-stream/lib/CFRMessageProvider.sys.mjs"
|
||||||
);
|
);
|
||||||
|
|
@ -105,26 +107,6 @@ const TOPIC_EXPERIMENT_ENROLLMENT_CHANGED = "nimbus:enrollments-updated";
|
||||||
const USE_REMOTE_L10N_PREF =
|
const USE_REMOTE_L10N_PREF =
|
||||||
"browser.newtabpage.activity-stream.asrouter.useRemoteL10n";
|
"browser.newtabpage.activity-stream.asrouter.useRemoteL10n";
|
||||||
|
|
||||||
const MESSAGING_EXPERIMENTS_DEFAULT_FEATURES = [
|
|
||||||
"cfr",
|
|
||||||
"fxms-message-1",
|
|
||||||
"fxms-message-2",
|
|
||||||
"fxms-message-3",
|
|
||||||
"fxms-message-4",
|
|
||||||
"fxms-message-5",
|
|
||||||
"fxms-message-6",
|
|
||||||
"fxms-message-7",
|
|
||||||
"fxms-message-8",
|
|
||||||
"fxms-message-9",
|
|
||||||
"fxms-message-10",
|
|
||||||
"fxms-message-11",
|
|
||||||
"infobar",
|
|
||||||
"moments-page",
|
|
||||||
"pbNewtab",
|
|
||||||
"spotlight",
|
|
||||||
"featureCallout",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Experiment groups that need to report the reach event in Messaging-Experiments.
|
// Experiment groups that need to report the reach event in Messaging-Experiments.
|
||||||
// If you're adding new groups to it, make sure they're also added in the
|
// If you're adding new groups to it, make sure they're also added in the
|
||||||
// `messaging_experiments.reach.objects` defined in "toolkit/components/telemetry/Events.yaml"
|
// `messaging_experiments.reach.objects` defined in "toolkit/components/telemetry/Events.yaml"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is used to define constants related to messaging experiments. It is
|
||||||
|
* imported by both ASRouter as well as import-rollouts.js, a node script that
|
||||||
|
* imports Nimbus rollouts into tree. It doesn't have access to any Firefox
|
||||||
|
* APIs, XPCOM, etc. and should be kept that way.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are the Nimbus feature IDs that correspond to messaging experiments.
|
||||||
|
* Other Nimbus features contain specific variables whose keys are enumerated in
|
||||||
|
* FeatureManifest.yaml. Conversely, messaging experiment features contain
|
||||||
|
* actual messages, with the usual message keys like `template` and `targeting`.
|
||||||
|
* @see FeatureManifest.yaml
|
||||||
|
*/
|
||||||
|
export const MESSAGING_EXPERIMENTS_DEFAULT_FEATURES = [
|
||||||
|
"cfr",
|
||||||
|
"fxms-message-1",
|
||||||
|
"fxms-message-2",
|
||||||
|
"fxms-message-3",
|
||||||
|
"fxms-message-4",
|
||||||
|
"fxms-message-5",
|
||||||
|
"fxms-message-6",
|
||||||
|
"fxms-message-7",
|
||||||
|
"fxms-message-8",
|
||||||
|
"fxms-message-9",
|
||||||
|
"fxms-message-10",
|
||||||
|
"fxms-message-11",
|
||||||
|
"infobar",
|
||||||
|
"moments-page",
|
||||||
|
"pbNewtab",
|
||||||
|
"spotlight",
|
||||||
|
"featureCallout",
|
||||||
|
];
|
||||||
|
|
@ -113,6 +113,7 @@
|
||||||
"fix": "npm-run-all fix:*",
|
"fix": "npm-run-all fix:*",
|
||||||
"fix:eslint": "npm run lint:eslint -- --fix",
|
"fix:eslint": "npm run lint:eslint -- --fix",
|
||||||
"fix:stylelint": "npm run lint:stylelint -- --fix",
|
"fix:stylelint": "npm run lint:stylelint -- --fix",
|
||||||
|
"import-rollouts": "node ./bin/import-rollouts.js",
|
||||||
"help": "yamscripts help",
|
"help": "yamscripts help",
|
||||||
"yamscripts": "yamscripts compile",
|
"yamscripts": "yamscripts compile",
|
||||||
"__": "# NOTE: THESE SCRIPTS ARE COMPILED!!! EDIT yamscripts.yml instead!!!"
|
"__": "# NOTE: THESE SCRIPTS ARE COMPILED!!! EDIT yamscripts.yml instead!!!"
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,234 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is generated by browser/components/newtab/bin/import-rollouts.js
|
||||||
|
* Run the following from the repository root to regenerate it:
|
||||||
|
* ./mach npm run import-rollouts --prefix=browser/components/newtab
|
||||||
|
*/
|
||||||
|
|
||||||
export const NimbusRolloutMessageProvider = {
|
export const NimbusRolloutMessageProvider = {
|
||||||
getMessages() {
|
getMessages() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
// Nimbus slug: device-migration-existing-users-sumo-switch-device-cfr-rollout:control
|
||||||
|
// Version range: 114+
|
||||||
|
// Recipe: https://experimenter.services.mozilla.com/nimbus/device-migration-existing-users-sumo-switch-device-cfr-rollout/summary#control
|
||||||
|
id: "CFR_WINDOWS_DEVICE_MIGRATION_SUMO_SWITCH_DEVICE",
|
||||||
|
groups: ["cfr"],
|
||||||
content: {
|
content: {
|
||||||
backdrop: "transparent",
|
text: {
|
||||||
id: "test-message-import-infreq-make-self-at-home:treatment-c",
|
string_id: "fxa-sync-cfr-body",
|
||||||
|
},
|
||||||
|
layout: "icon_and_message",
|
||||||
|
buttons: {
|
||||||
|
primary: {
|
||||||
|
label: {
|
||||||
|
string_id: "fxa-sync-cfr-primary",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
data: {
|
||||||
|
args: "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/switching-devices?utm_source=panel-default&utm_medium=firefox-desktop&utm_campaign=migration&utm_content=migrate-learn-more",
|
||||||
|
where: "tabshifted",
|
||||||
|
},
|
||||||
|
type: "OPEN_URL",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
secondary: [
|
||||||
|
{
|
||||||
|
label: {
|
||||||
|
string_id: "fxa-sync-cfr-secondary",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: "CANCEL",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
anchor_id: "PanelUI-menu-button",
|
||||||
|
bucket_id: "CFR_WINDOWS_DEVICE_MIGRATION_SUMO_SWITCH_DEVICE",
|
||||||
|
heading_text: {
|
||||||
|
string_id: "fxa-sync-cfr-header",
|
||||||
|
},
|
||||||
|
skip_address_bar_notifier: true,
|
||||||
|
},
|
||||||
|
trigger: {
|
||||||
|
id: "defaultBrowserCheck",
|
||||||
|
},
|
||||||
|
template: "cfr_doorhanger",
|
||||||
|
frequency: {
|
||||||
|
custom: [
|
||||||
|
{
|
||||||
|
cap: 1,
|
||||||
|
period: 604800000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
lifetime: 3,
|
||||||
|
},
|
||||||
|
targeting: "isFxASignedIn && !usesFirefoxSync && source == 'newtab'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Nimbus slug: device-migration-existing-user-messaging-tour-spotlight-rollout:control
|
||||||
|
// Version range: 114+
|
||||||
|
// Recipe: https://experimenter.services.mozilla.com/nimbus/device-migration-existing-user-messaging-tour-spotlight-rollout/summary#control
|
||||||
|
id: "WINDOWS_DEVICE_MIGRATION_SUMO_SWITCH_DEVICE",
|
||||||
|
groups: ["eco"],
|
||||||
|
content: {
|
||||||
|
id: "WINDOWS_DEVICE_MIGRATION_SUMO_SWITCH_DEVICE",
|
||||||
|
modal: "tab",
|
||||||
screens: [
|
screens: [
|
||||||
{
|
{
|
||||||
|
id: "WINDOWS_DEVICE_MIGRATION_SUMO_SWITCH_DEVICE:CONTROL_ROLLOUT",
|
||||||
|
content: {
|
||||||
|
logo: {
|
||||||
|
height: "211px",
|
||||||
|
imageURL:
|
||||||
|
"https://firefox-settings-attachments.cdn.mozilla.net/main-workspace/ms-images/f0f51715-7f5e-48de-839a-f26cc76cbb8b.svg",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: "24px",
|
||||||
|
string_id: "device-migration-fxa-spotlight-header",
|
||||||
|
paddingBlock: "20px 0",
|
||||||
|
letterSpacing: 0,
|
||||||
|
paddingInline: "82px",
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: "15px",
|
||||||
|
string_id: "device-migration-fxa-spotlight-body",
|
||||||
|
lineHeight: "1.4",
|
||||||
|
marginBlock: "8px 20px",
|
||||||
|
letterSpacing: 0,
|
||||||
|
paddingInline: "30px",
|
||||||
|
},
|
||||||
|
dismiss_button: {
|
||||||
|
action: {
|
||||||
|
data: {
|
||||||
|
id: "WINDOWS_DEVICE_MIGRATION_SUMO_SWITCH_DEVICE",
|
||||||
|
},
|
||||||
|
type: "BLOCK_MESSAGE",
|
||||||
|
navigate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
primary_button: {
|
||||||
|
label: {
|
||||||
|
string_id: "device-migration-fxa-spotlight-primary-button",
|
||||||
|
paddingBlock: "0",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
data: {
|
||||||
|
args: "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/switching-devices?utm_source=spotlight-default&utm_medium=firefox-desktop&utm_campaign=migration&utm_content=how-to-backup-data",
|
||||||
|
where: "tabshifted",
|
||||||
|
},
|
||||||
|
type: "OPEN_URL",
|
||||||
|
navigate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
secondary_button: {
|
||||||
|
label: {
|
||||||
|
string_id: "device-migration-fxa-spotlight-link",
|
||||||
|
marginBlock: "1px -20px",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
navigate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
backdrop: "transparent",
|
||||||
|
template: "multistage",
|
||||||
|
transitions: true,
|
||||||
|
},
|
||||||
|
trigger: {
|
||||||
|
id: "defaultBrowserCheck",
|
||||||
|
},
|
||||||
|
template: "spotlight",
|
||||||
|
frequency: {
|
||||||
|
custom: [
|
||||||
|
{
|
||||||
|
cap: 1,
|
||||||
|
period: 4838400000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
lifetime: 3,
|
||||||
|
},
|
||||||
|
targeting:
|
||||||
|
"!isFxASignedIn && !willShowDefaultPrompt && !isMajorUpgrade && !activeNotifications",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Nimbus slug: updated-import-infrequent-rollout-make-yourself-at-home-copy:control
|
||||||
|
// Version range: 107+
|
||||||
|
// Recipe: https://experimenter.services.mozilla.com/nimbus/updated-import-infrequent-rollout-make-yourself-at-home-copy/summary#control
|
||||||
|
id: "import-infreq-make-self-at-home:treatment-c",
|
||||||
|
groups: ["import-spotlights"],
|
||||||
|
content: {
|
||||||
|
id: "import-infreq-make-self-at-home:treatment-c",
|
||||||
|
screens: [
|
||||||
|
{
|
||||||
|
id: "IMPORT",
|
||||||
content: {
|
content: {
|
||||||
logo: {
|
logo: {
|
||||||
height: "186px",
|
height: "186px",
|
||||||
imageURL:
|
imageURL:
|
||||||
"chrome://activity-stream/content/data/content/assets/person-typing.svg",
|
"chrome://activity-stream/content/data/content/assets/person-typing.svg",
|
||||||
},
|
},
|
||||||
primary_button: {
|
title: {
|
||||||
action: {
|
fontSize: "26px",
|
||||||
navigate: true,
|
string_id: "onboarding-infrequent-import-title",
|
||||||
type: "SHOW_MIGRATION_WIZARD",
|
fontWeight: "400",
|
||||||
},
|
lineHeight: "36px",
|
||||||
label: {
|
marginBlock: "6px 0",
|
||||||
paddingBlock: "6px",
|
letterSpacing: "-.01em",
|
||||||
paddingInline: "14px",
|
|
||||||
string_id: "onboarding-infrequent-import-primary-button",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secondary_button: {
|
|
||||||
action: {
|
|
||||||
navigate: true,
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
fontSize: "13px",
|
|
||||||
lineHeight: "15px",
|
|
||||||
marginBlock: "-4px -28px",
|
|
||||||
string_id: "onboarding-not-now-button-label",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
subtitle: {
|
subtitle: {
|
||||||
fontSize: "13px",
|
fontSize: "13px",
|
||||||
letterSpacing: ".05px",
|
string_id: "onboarding-infrequent-import-subtitle",
|
||||||
lineHeight: "16px",
|
lineHeight: "16px",
|
||||||
marginBlock: "4px 12px",
|
marginBlock: "4px 12px",
|
||||||
|
letterSpacing: ".05px",
|
||||||
paddingInline: "48px",
|
paddingInline: "48px",
|
||||||
string_id: "onboarding-infrequent-import-subtitle",
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontSize: "26px",
|
|
||||||
fontWeight: "400",
|
|
||||||
letterSpacing: "-.01em",
|
|
||||||
lineHeight: "36px",
|
|
||||||
marginBlock: "6px 0",
|
|
||||||
string_id: "onboarding-infrequent-import-title",
|
|
||||||
},
|
},
|
||||||
title_style: "slim",
|
title_style: "slim",
|
||||||
|
primary_button: {
|
||||||
|
label: {
|
||||||
|
string_id: "onboarding-infrequent-import-primary-button",
|
||||||
|
paddingBlock: "6px",
|
||||||
|
paddingInline: "14px",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: "SHOW_MIGRATION_WIZARD",
|
||||||
|
navigate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
secondary_button: {
|
||||||
|
label: {
|
||||||
|
fontSize: "13px",
|
||||||
|
string_id: "onboarding-not-now-button-label",
|
||||||
|
lineHeight: "15px",
|
||||||
|
marginBlock: "-4px -28px",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
navigate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
id: "IMPORT",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
backdrop: "transparent",
|
||||||
template: "multistage",
|
template: "multistage",
|
||||||
transitions: true,
|
transitions: true,
|
||||||
},
|
},
|
||||||
frequency: {
|
|
||||||
lifetime: 1,
|
|
||||||
},
|
|
||||||
groups: ["import-spotlights"],
|
|
||||||
id: "test-message-import-infreq-make-self-at-home:treatment-c",
|
|
||||||
priority: 1,
|
|
||||||
targeting:
|
|
||||||
"!willShowDefaultPrompt && !('browser.migrate.content-modal.enabled'|preferenceValue) && source == 'startup' && !isMajorUpgrade && !activeNotifications && totalBookmarksCount == 5",
|
|
||||||
template: "spotlight",
|
|
||||||
trigger: {
|
trigger: {
|
||||||
id: "defaultBrowserCheck",
|
id: "defaultBrowserCheck",
|
||||||
},
|
},
|
||||||
|
priority: 1,
|
||||||
|
template: "spotlight",
|
||||||
|
frequency: {
|
||||||
|
lifetime: 1,
|
||||||
|
},
|
||||||
|
targeting:
|
||||||
|
"!willShowDefaultPrompt && !('browser.migrate.content-modal.enabled'|preferenceValue) && source == 'startup' && !isMajorUpgrade && !activeNotifications && totalBookmarksCount == 5",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -62,3 +62,6 @@ scripts:
|
||||||
# running fix:eslint will also reformat changed JS files using prettier.
|
# running fix:eslint will also reformat changed JS files using prettier.
|
||||||
eslint: =>lint:eslint -- --fix
|
eslint: =>lint:eslint -- --fix
|
||||||
stylelint: =>lint:stylelint -- --fix
|
stylelint: =>lint:stylelint -- --fix
|
||||||
|
|
||||||
|
# script to import Nimbus rollouts into NimbusRolloutMessageProvider.sys.mjs
|
||||||
|
import-rollouts: node ./bin/import-rollouts.js
|
||||||
|
|
|
||||||
|
|
@ -912,7 +912,7 @@ featureCallout:
|
||||||
# 1) clone an existing one here
|
# 1) clone an existing one here
|
||||||
# 2) update the YAML feature id to the next unused number
|
# 2) update the YAML feature id to the next unused number
|
||||||
# 3) update the YAML description
|
# 3) update the YAML description
|
||||||
# 4) add the new feature id to MESSAGING_EXPERIMENTS_DEFAULT_FEATURES list in ASRouter.jsm
|
# 4) add the new feature id to MESSAGING_EXPERIMENTS_DEFAULT_FEATURES list in MessagingExperimentConstants.sys.mjs
|
||||||
# 5) add the new feature id and the version it landed to the spreadsheet tab linked to above
|
# 5) add the new feature id and the version it landed to the spreadsheet tab linked to above
|
||||||
|
|
||||||
fxms-message-1:
|
fxms-message-1:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue