feat(experiments): Add mutally exclusive experiment logic for CAD QR and newsletters

This commit is contained in:
Vijay Budhram 2020-05-26 11:22:22 -04:00
Родитель 3a6a83ac76
Коммит 6b2f84f5af
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: D49B640E659DCB9E
10 изменённых файлов: 95 добавлений и 11 удалений

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

@ -26,6 +26,7 @@ const MANUAL_EXPERIMENTS = {
sendSms: BaseExperiment,
newsletterSync: BaseExperiment,
qrCodeCad: BaseExperiment,
newsletterCadChooser: BaseExperiment,
};
const ALL_EXPERIMENTS = _.extend({}, STARTUP_EXPERIMENTS, MANUAL_EXPERIMENTS);

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

@ -18,6 +18,7 @@ const experimentGroupingRules = [
require('./email-mx-validation'),
require('./newsletter-sync'),
require('./qr-code-cad'),
require('./newsletter-cad-chooser'),
].map(ExperimentGroupingRule => new ExperimentGroupingRule());
class ExperimentChoiceIndex {

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

@ -0,0 +1,26 @@
/* 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/. */
/**
* To ensure that the newsletter and qr cad experiment are mutually
* exclusive, we create a new experiment rule to select only one of them.
*/
'use strict';
const BaseGroupingRule = require('./base');
const GROUPS = ['newsletterSync', 'qrCodeCad'];
module.exports = class ExperimentChooser extends BaseGroupingRule {
constructor() {
super();
this.name = 'newsletterCadChooser';
// Easier to set class properties for testability
this.groups = GROUPS;
}
choose(subject = {}) {
return this.uniformChoice(this.groups, subject.uniqueUserId);
}
};

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

@ -37,21 +37,34 @@ module.exports = class NewsletterSync extends BaseGroupingRule {
*
* @param {Object} subject data used to decide
* @param {Boolean} isSync is this a sync signup?
* @param {String} lang language of user
* @returns {Any}
*/
choose(subject = {}) {
let choice = false;
// Only enroll for sync users
if (!subject.isSync) {
if (
subject.experimentGroupingRules.choose('newsletterCadChooser') !==
this.name
) {
return false;
}
// TODO Add restriction to `EN` locales
let choice = false;
const { isSync, lang } = subject;
// Only enroll for sync users
if (!isSync || !lang) {
return false;
}
// Only en users are eligible for experiment
if (lang.slice(0, 2) !== 'en') {
return false;
}
if (this.bernoulliTrial(this.rolloutRate, subject.uniqueUserId)) {
choice = this.uniformChoice(GROUPS, subject.uniqueUserId);
}
return choice;
}
};

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

@ -17,7 +17,7 @@ const GROUPS = [
'treatment-b', // Screen displayed in non-sms markets
];
const ROLLOUT_RATE = 0.0;
const ROLLOUT_RATE = 1.0;
module.exports = class QrCodeCad extends BaseGroupingRule {
constructor() {
@ -41,6 +41,13 @@ module.exports = class QrCodeCad extends BaseGroupingRule {
return false;
}
if (
subject.experimentGroupingRules.choose('newsletterCadChooser') !==
this.name
) {
return false;
}
let telephoneInfo = CountryTelephoneInfo[subject.country];
const { featureFlags } = subject;
if (featureFlags && featureFlags.smsCountries) {
@ -58,7 +65,7 @@ module.exports = class QrCodeCad extends BaseGroupingRule {
if (this.isTestEmail(subject.account.get('email'))) {
// Test users always get the new experience
choice = 'treatment-b';
choice = 'treatment-a';
} else if (countryRollOut >= 1) {
// This experiment should only be shown to countries that are fully
// rolled out with sms

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

@ -12,6 +12,7 @@ import AuthErrors from '../lib/auth-errors';
import CachedCredentialsMixin from './mixins/cached-credentials-mixin';
import Cocktail from 'cocktail';
import CoppaMixin from './mixins/coppa-mixin';
import ExperimentMixin from './mixins/experiment-mixin';
import EmailMxValidationExperimentMixin from './mixins/email-mx-validation-experiment-mixin';
import FirefoxFamilyServicesTemplate from '../templates/partial/firefox-family-services.mustache';
import FlowBeginMixin from './mixins/flow-begin-mixin';
@ -89,6 +90,11 @@ class IndexView extends FormView {
AuthErrors.toError('SIGNUP_EMAIL_BOUNCE')
);
}
// The CAD via QR and newsletters experiment are mutally exclusive
// so a user can only be enrolled in one of them at a time. This
// select the experiment the user will belong to.
this.getAndReportExperimentGroup('newsletterCadChooser');
}
chooseEmailActionPage() {
@ -235,6 +241,7 @@ Cocktail.mixin(
CoppaMixin({}),
EmailAutocompleteDomainsMixin,
EmailMxValidationExperimentMixin,
ExperimentMixin,
FlowBeginMixin,
FormPrefillMixin,
ServiceMixin,

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

@ -18,6 +18,7 @@ export default {
isInNewsletterSyncExperimentTrailheadCopy() {
const experimentGroup = this.getAndReportExperimentGroup(EXPERIMENT_NAME, {
isSync: this.relier.isSync(),
lang: this.navigator.language,
});
return experimentGroup === 'trailhead-copy';
@ -26,6 +27,7 @@ export default {
isInNewsletterSyncExperimentNewCopy() {
const experimentGroup = this.getAndReportExperimentGroup(EXPERIMENT_NAME, {
isSync: this.relier.isSync(),
lang: this.navigator.language,
});
return experimentGroup === 'new-copy';

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

@ -9,7 +9,7 @@ import sinon from 'sinon';
describe('lib/experiments/grouping-rules/index', () => {
it('EXPERIMENT_NAMES is exported', () => {
assert.lengthOf(ExperimentGroupingRules.EXPERIMENT_NAMES, 7);
assert.lengthOf(ExperimentGroupingRules.EXPERIMENT_NAMES, 8);
});
describe('choose', () => {

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

@ -15,14 +15,35 @@ describe('lib/experiments/grouping-rules/newsletter-sync', () => {
describe('choose', () => {
it('returns false if not Sync', () => {
assert.isFalse(
experiment.choose({ isSync: false, uniqueUserId: 'user-id' })
experiment.choose({
experimentGroupingRules: { choose: () => experiment.name },
isSync: false,
uniqueUserId: 'user-id',
})
);
});
it('returns false if not `en` locale', () => {
experiment.rolloutRate = 0;
assert.isFalse(
experiment.choose({
experimentGroupingRules: { choose: () => experiment.name },
isSync: true,
lang: 'de',
uniqueUserId: 'user-id',
})
);
});
it('returns false if rollout 0%', () => {
experiment.rolloutRate = 0;
assert.isFalse(
experiment.choose({ isSync: true, uniqueUserId: 'user-id' })
experiment.choose({
experimentGroupingRules: { choose: () => experiment.name },
isSync: true,
lang: 'en',
uniqueUserId: 'user-id',
})
);
});
@ -30,7 +51,12 @@ describe('lib/experiments/grouping-rules/newsletter-sync', () => {
experiment.rolloutRate = 1;
assert.isTrue(
experiment.groups.includes(
experiment.choose({ isSync: true, uniqueUserId: 'user-id' })
experiment.choose({
experimentGroupingRules: { choose: () => experiment.name },
isSync: true,
lang: 'en',
uniqueUserId: 'user-id',
})
)
);
});

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

@ -48,6 +48,7 @@ require('./spec/lib/experiments/grouping-rules/communication-prefs');
require('./spec/lib/experiments/grouping-rules/email-mx-validation');
require('./spec/lib/experiments/grouping-rules/index');
require('./spec/lib/experiments/grouping-rules/is-sampled-user');
require('./spec/lib/experiments/grouping-rules/newsletter-sync');
require('./spec/lib/experiments/grouping-rules/send-sms-install-link');
require('./spec/lib/experiments/grouping-rules/sentry');
require('./spec/lib/fxa-client');