Merge pull request #6945 from mozilla/pb/6890-feature-flags
https://github.com/mozilla/fxa-content-server/pull/6945 r=shane-tomlinson,vbudhram
This commit is contained in:
Коммит
e58f443328
|
@ -6,6 +6,7 @@ jobs:
|
|||
- image: circleci/node:8
|
||||
environment:
|
||||
- DISPLAY=:99
|
||||
- image: redis
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
@ -37,6 +38,7 @@ jobs:
|
|||
working_directory: ~/fxa
|
||||
docker:
|
||||
- image: circleci/node:8-stretch-browsers
|
||||
- image: redis
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/
|
||||
|
@ -67,6 +69,7 @@ jobs:
|
|||
working_directory: ~/fxa
|
||||
docker:
|
||||
- image: circleci/node:8-stretch-browsers
|
||||
- image: redis
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/
|
||||
|
@ -97,6 +100,7 @@ jobs:
|
|||
working_directory: ~/fxa
|
||||
docker:
|
||||
- image: circleci/node:8-stretch-browsers
|
||||
- image: redis
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/
|
||||
|
@ -127,6 +131,7 @@ jobs:
|
|||
working_directory: ~/fxa
|
||||
docker:
|
||||
- image: circleci/node:8-stretch-browsers
|
||||
- image: redis
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/
|
||||
|
@ -153,6 +158,7 @@ jobs:
|
|||
working_directory: ~/fxa
|
||||
docker:
|
||||
- image: circleci/node:8-stretch-browsers
|
||||
- image: redis
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/
|
||||
|
@ -185,6 +191,7 @@ jobs:
|
|||
dockerpush:
|
||||
docker:
|
||||
- image: circleci/node:8-stretch-browsers
|
||||
- image: redis
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
|
|
@ -95,7 +95,8 @@ Start.prototype = {
|
|||
|
||||
initializeExperimentGroupingRules () {
|
||||
this._experimentGroupingRules = new ExperimentGroupingRules({
|
||||
env: this._config.env
|
||||
env: this._config.env,
|
||||
featureFlags: this._config.featureFlags
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -53,6 +53,11 @@ ConfigLoader.prototype = {
|
|||
try {
|
||||
const serializedJSONConfig = decodeURIComponent(configFromHTML);
|
||||
config = JSON.parse(serializedJSONConfig);
|
||||
|
||||
const serializedFeatureFlags = decodeURIComponent(
|
||||
$('meta[name="fxa-feature-flags"]').attr('content')
|
||||
);
|
||||
config.featureFlags = JSON.parse(serializedFeatureFlags);
|
||||
} catch (e) {
|
||||
return Promise.reject(ConfigLoader.Errors.toError('INVALID_CONFIG'));
|
||||
}
|
||||
|
|
|
@ -25,30 +25,39 @@ const AVAILABLE_LANGUAGES = [
|
|||
'zh-tw'
|
||||
];
|
||||
|
||||
const availableLocalesRegExpStr = `^(${AVAILABLE_LANGUAGES.join('|')})$`;
|
||||
const availableLocalesRegExp = new RegExp(availableLocalesRegExpStr);
|
||||
const AVAILABLE_LANGUAGES_REGEX = arrayToRegex(AVAILABLE_LANGUAGES);
|
||||
|
||||
function normalizeLanguage(lang) {
|
||||
return lang.toLowerCase().replace(/_/g, '-');
|
||||
}
|
||||
|
||||
function areCommunicationPrefsAvailable(lang) {
|
||||
function areCommunicationPrefsAvailable(lang, availableLanguages) {
|
||||
const normalizedLanguage = normalizeLanguage(lang);
|
||||
return availableLocalesRegExp.test(normalizedLanguage);
|
||||
return availableLanguages.test(normalizedLanguage);
|
||||
}
|
||||
|
||||
function arrayToRegex (array) {
|
||||
return new RegExp(`^(?:${array.join('|')})$`);
|
||||
}
|
||||
|
||||
module.exports = class CommunicationPrefsGroupingRule extends BaseGroupingRule {
|
||||
constructor () {
|
||||
super();
|
||||
this.name = 'communicationPrefsVisible';
|
||||
this.availableLanguages = AVAILABLE_LANGUAGES;
|
||||
}
|
||||
|
||||
choose (subject = {}) {
|
||||
if (! subject.lang) {
|
||||
const { featureFlags, lang } = subject;
|
||||
let availableLanguages = AVAILABLE_LANGUAGES_REGEX;
|
||||
|
||||
if (featureFlags && Array.isArray(featureFlags.communicationPrefLanguages)) {
|
||||
availableLanguages = arrayToRegex(featureFlags.communicationPrefLanguages);
|
||||
}
|
||||
|
||||
if (! lang) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return areCommunicationPrefsAvailable(subject.lang);
|
||||
return areCommunicationPrefsAvailable(lang, availableLanguages);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -24,6 +24,7 @@ class ExperimentChoiceIndex {
|
|||
constructor (options = {}) {
|
||||
this._env = options.env;
|
||||
this._experimentGroupingRules = options.experimentGroupingRules || experimentGroupingRules;
|
||||
this._featureFlags = options.featureFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,6 +52,7 @@ class ExperimentChoiceIndex {
|
|||
const subjectCopy = Object.create(subject);
|
||||
subjectCopy.env = subject.env || this._env;
|
||||
subjectCopy.experimentGroupingRules = this;
|
||||
subjectCopy.featureFlags = subject.featureFlags || this._featureFlags;
|
||||
return experiment.choose(subjectCopy);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,18 +16,24 @@ module.exports = class IsSampledUserGroupingRule extends BaseGroupingRule {
|
|||
}
|
||||
|
||||
choose (subject = {}) {
|
||||
const sampleRate = IsSampledUserGroupingRule.sampleRate(subject.env);
|
||||
const sampleRate = IsSampledUserGroupingRule.sampleRate(subject);
|
||||
return !! (subject.env && subject.uniqueUserId && this.bernoulliTrial(sampleRate, subject.uniqueUserId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sample rate for `env`
|
||||
* Return the sample rate from `featureFlags` or `env`
|
||||
*
|
||||
* @static
|
||||
* @param {String} env
|
||||
* @param {Object} options
|
||||
* @param {String} [options.env]
|
||||
* @param {Object} [options.featureFlags]
|
||||
* @returns {Number}
|
||||
*/
|
||||
static sampleRate (env) {
|
||||
static sampleRate ({ env, featureFlags }) {
|
||||
if (featureFlags && featureFlags.metricsSampleRate >= 0) {
|
||||
return featureFlags.metricsSampleRate;
|
||||
}
|
||||
|
||||
return env === 'development' ? 1.0 : 0.1;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -24,13 +24,23 @@ module.exports = class SmsGroupingRule extends BaseGroupingRule {
|
|||
}
|
||||
|
||||
choose (subject = {}) {
|
||||
if (! subject.account || ! subject.uniqueUserId || ! subject.country || ! CountryTelephoneInfo[subject.country]) {
|
||||
if (! subject.account || ! subject.uniqueUserId || ! subject.country) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let telephoneInfo = CountryTelephoneInfo[subject.country];
|
||||
const { featureFlags } = subject;
|
||||
if (featureFlags && featureFlags.smsCountries) {
|
||||
telephoneInfo = featureFlags.smsCountries[subject.country];
|
||||
}
|
||||
|
||||
if (! telephoneInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let choice = false;
|
||||
// If rolloutRate is not specified, assume 0.
|
||||
const { rolloutRate } = CountryTelephoneInfo[subject.country] || 0;
|
||||
const rolloutRate = telephoneInfo.rolloutRate || 0;
|
||||
|
||||
if (this.isTestEmail(subject.account.get('email'))) {
|
||||
choice = 'signinCodes';
|
||||
|
|
|
@ -16,7 +16,7 @@ module.exports = class SentryGroupingRule extends BaseGroupingRule {
|
|||
}
|
||||
|
||||
choose (subject = {}) {
|
||||
const sampleRate = SentryGroupingRule.sampleRate(subject.env);
|
||||
const sampleRate = SentryGroupingRule.sampleRate(subject);
|
||||
|
||||
return !! (subject.env && subject.uniqueUserId && this.bernoulliTrial(sampleRate, subject.uniqueUserId));
|
||||
}
|
||||
|
@ -28,7 +28,11 @@ module.exports = class SentryGroupingRule extends BaseGroupingRule {
|
|||
* @param {String} env
|
||||
* @returns {Number}
|
||||
*/
|
||||
static sampleRate (env) {
|
||||
static sampleRate ({ env, featureFlags }) {
|
||||
if (featureFlags && featureFlags.sentrySampleRate >= 0) {
|
||||
return featureFlags.sentrySampleRate;
|
||||
}
|
||||
|
||||
return env === 'development' ? 1.0 : 0.3;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -48,8 +48,13 @@ module.exports = class TokenCodeGroupingRule extends BaseGroupingRule {
|
|||
return false;
|
||||
}
|
||||
|
||||
const { featureFlags } = subject;
|
||||
|
||||
if (subject.clientId) {
|
||||
const client = this.ROLLOUT_CLIENTS[subject.clientId];
|
||||
let client = this.ROLLOUT_CLIENTS[subject.clientId];
|
||||
if (featureFlags && featureFlags.tokenCodeClients) {
|
||||
client = featureFlags.tokenCodeClients[subject.clientId];
|
||||
}
|
||||
|
||||
if (client) {
|
||||
const groups = client.groups || GROUPS_DEFAULT;
|
||||
|
@ -70,7 +75,12 @@ module.exports = class TokenCodeGroupingRule extends BaseGroupingRule {
|
|||
}
|
||||
|
||||
if (subject.service && subject.service === Constants.SYNC_SERVICE) {
|
||||
if (this.bernoulliTrial(this.SYNC_ROLLOUT_RATE, subject.uniqueUserId)) {
|
||||
let syncRolloutRate = this.SYNC_ROLLOUT_RATE;
|
||||
if (featureFlags && featureFlags.tokenCodeClients) {
|
||||
syncRolloutRate = featureFlags.tokenCodeClients.sync.rolloutRate;
|
||||
}
|
||||
|
||||
if (this.bernoulliTrial(syncRolloutRate, subject.uniqueUserId)) {
|
||||
return this.uniformChoice(GROUPS_DEFAULT, subject.uniqueUserId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ define(function (require, exports, module) {
|
|||
const BaseBroker = require('models/auth_brokers/base');
|
||||
const Constants = require('lib/constants');
|
||||
const ErrorUtils = require('lib/error-utils');
|
||||
const ExperimentGroupingRules = require('lib/experiments/grouping-rules');
|
||||
const FxFennecV1Broker = require('models/auth_brokers/fx-fennec-v1');
|
||||
const FxFirstrunV1Broker = require('models/auth_brokers/fx-firstrun-v1');
|
||||
const FxFirstrunV2Broker = require('models/auth_brokers/fx-firstrun-v2');
|
||||
|
@ -38,6 +39,7 @@ define(function (require, exports, module) {
|
|||
let appStart;
|
||||
let backboneHistoryMock;
|
||||
let brokerMock;
|
||||
let config;
|
||||
let notifier;
|
||||
let routerMock;
|
||||
let translator;
|
||||
|
@ -47,6 +49,12 @@ define(function (require, exports, module) {
|
|||
beforeEach(() => {
|
||||
brokerMock = new BaseBroker();
|
||||
backboneHistoryMock = new HistoryMock();
|
||||
config = {
|
||||
env: 'production',
|
||||
featureFlags: {
|
||||
foo: 'bar'
|
||||
}
|
||||
};
|
||||
notifier = new Notifier();
|
||||
routerMock = { navigate: sinon.spy() };
|
||||
translator = {
|
||||
|
@ -60,6 +68,7 @@ define(function (require, exports, module) {
|
|||
|
||||
appStart = new AppStart({
|
||||
broker: brokerMock,
|
||||
config,
|
||||
history: backboneHistoryMock,
|
||||
notifier,
|
||||
router: routerMock,
|
||||
|
@ -113,34 +122,17 @@ define(function (require, exports, module) {
|
|||
});
|
||||
});
|
||||
|
||||
it('initializeErrorMetrics skips error metrics on empty config', () => {
|
||||
appStart.initializeExperimentGroupingRules();
|
||||
const ableChoose = sinon.stub(appStart._experimentGroupingRules, 'choose').callsFake(() => {
|
||||
return true;
|
||||
});
|
||||
it('initializeExperimentGroupingRules propagates env and featureFlags', () => {
|
||||
assert.isUndefined(appStart._experimentGroupingRules);
|
||||
|
||||
appStart.initializeErrorMetrics();
|
||||
assert.isUndefined(appStart._sentryMetrics);
|
||||
ableChoose.restore();
|
||||
});
|
||||
|
||||
it('initializeErrorMetrics skips error metrics if env is not defined', () => {
|
||||
appStart.initializeExperimentGroupingRules();
|
||||
|
||||
appStart.initializeErrorMetrics();
|
||||
assert.isUndefined(appStart._sentryMetrics);
|
||||
assert.instanceOf(appStart._experimentGroupingRules, ExperimentGroupingRules);
|
||||
assert.equal(appStart._experimentGroupingRules._env, 'production');
|
||||
assert.deepEqual(appStart._experimentGroupingRules._featureFlags, { foo: 'bar' });
|
||||
});
|
||||
|
||||
it('initializeErrorMetrics creates error metrics', () => {
|
||||
const appStart = new AppStart({
|
||||
broker: brokerMock,
|
||||
config: {
|
||||
env: 'development'
|
||||
},
|
||||
history: backboneHistoryMock,
|
||||
router: routerMock,
|
||||
window: windowMock
|
||||
});
|
||||
appStart.initializeExperimentGroupingRules();
|
||||
|
||||
const ableChoose = sinon.stub(appStart._experimentGroupingRules, 'choose').callsFake(() => {
|
||||
|
@ -184,6 +176,35 @@ define(function (require, exports, module) {
|
|||
assert.instanceOf(appStart._refreshObserver, RefreshObserver);
|
||||
});
|
||||
|
||||
describe('without config', () => {
|
||||
beforeEach(() => {
|
||||
appStart = new AppStart({
|
||||
broker: brokerMock,
|
||||
history: backboneHistoryMock,
|
||||
router: routerMock,
|
||||
window: windowMock
|
||||
});
|
||||
});
|
||||
|
||||
it('initializeErrorMetrics skips error metrics on empty config', () => {
|
||||
appStart.initializeExperimentGroupingRules();
|
||||
const ableChoose = sinon.stub(appStart._experimentGroupingRules, 'choose').callsFake(() => {
|
||||
return true;
|
||||
});
|
||||
|
||||
appStart.initializeErrorMetrics();
|
||||
assert.isUndefined(appStart._sentryMetrics);
|
||||
ableChoose.restore();
|
||||
});
|
||||
|
||||
it('initializeErrorMetrics skips error metrics if env is not defined', () => {
|
||||
appStart.initializeExperimentGroupingRules();
|
||||
|
||||
appStart.initializeErrorMetrics();
|
||||
assert.isUndefined(appStart._sentryMetrics);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fatalError', () => {
|
||||
var err;
|
||||
var sandbox;
|
||||
|
|
|
@ -18,6 +18,28 @@ define(function (require, exports, module) {
|
|||
const VALID_HTML_CONFIG =
|
||||
encodeURIComponent(JSON.stringify({ env: 'dev' }));
|
||||
|
||||
const FEATURE_FLAGS = {
|
||||
communicationPrefLanguages: [ 'en', 'fr' ],
|
||||
metricsSampleRate: 0.1,
|
||||
sentrySampleRate: 1,
|
||||
smsCountries: {
|
||||
FR: {
|
||||
rolloutRate: 0.5,
|
||||
},
|
||||
GB: {
|
||||
rolloutRate: 1,
|
||||
}
|
||||
},
|
||||
tokenCodeClients: {
|
||||
deadbeefbaadf00d: {
|
||||
rolloutRate: 0
|
||||
},
|
||||
sync: {
|
||||
rolloutRate: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
const SERIALISED_FEATURE_FLAGS = encodeURIComponent(JSON.stringify(FEATURE_FLAGS));
|
||||
|
||||
describe('lib/config-loader', () => {
|
||||
let configLoader;
|
||||
|
@ -26,72 +48,66 @@ define(function (require, exports, module) {
|
|||
configLoader = new ConfigLoader();
|
||||
});
|
||||
|
||||
describe('_readConfigFromHTML', () => {
|
||||
describe('config missing', () => {
|
||||
it('returns a `MISSING_CONFIG` error', () => {
|
||||
return configLoader._readConfigFromHTML()
|
||||
.then(assert.fail, (err) => {
|
||||
assert.isTrue(ConfigLoaderErrors.is(err, 'MISSING_CONFIG'));
|
||||
});
|
||||
it('_readConfigFromHTML returns a `MISSING_CONFIG` error', () => {
|
||||
return configLoader._readConfigFromHTML()
|
||||
.then(assert.fail, (err) => {
|
||||
assert.isTrue(ConfigLoaderErrors.is(err, 'MISSING_CONFIG'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('config available', () => {
|
||||
beforeEach(() => {
|
||||
$('head').append(`<meta name="fxa-content-server/config" content="${VALID_HTML_CONFIG}" />`);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
$('meta[name="fxa-content-server/config"]').remove();
|
||||
});
|
||||
|
||||
it('returns the expected config', () => {
|
||||
return configLoader._readConfigFromHTML()
|
||||
.then((serializedHTMLConfig) => {
|
||||
assert.equal(serializedHTMLConfig, VALID_HTML_CONFIG);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_parseHTMLConfig', () => {
|
||||
describe('with an invalid URI Component', () => {
|
||||
it('throws an `INVALID_CONFIG` error', () => {
|
||||
return configLoader._parseHTMLConfig(INVALID_URI_COMPONENT_HTML_CONFIG)
|
||||
.then(assert.fail, (err) => {
|
||||
assert.isTrue(ConfigLoaderErrors.is(err, 'INVALID_CONFIG'));
|
||||
});
|
||||
it('_parseHTMLConfig rejects with invalid encoding', () => {
|
||||
return configLoader._parseHTMLConfig(INVALID_URI_COMPONENT_HTML_CONFIG)
|
||||
.then(assert.fail, (err) => {
|
||||
assert.isTrue(ConfigLoaderErrors.is(err, 'INVALID_CONFIG'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('with invalid JSON', () => {
|
||||
it('throws an `INVALID_CONFIG` error', () => {
|
||||
return configLoader._parseHTMLConfig(INVALID_JSON_HTML_CONFIG)
|
||||
.then(assert.fail, (err) => {
|
||||
assert.isTrue(ConfigLoaderErrors.is(err, 'INVALID_CONFIG'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with valid config', () => {
|
||||
it('parses the config', () => {
|
||||
return configLoader._parseHTMLConfig(VALID_HTML_CONFIG)
|
||||
.then((config) => {
|
||||
assert.equal(config.env, 'dev');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetch', () => {
|
||||
describe('with valid config', () => {
|
||||
it('_parseHTMLConfig rejects with invalid JSON', () => {
|
||||
return configLoader._parseHTMLConfig(INVALID_JSON_HTML_CONFIG)
|
||||
.then(assert.fail, (err) => {
|
||||
assert.isTrue(ConfigLoaderErrors.is(err, 'INVALID_CONFIG'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('insert config markup in to the DOM', () => {
|
||||
before(() => {
|
||||
$('head').append(`<meta name="fxa-content-server/config" content="${VALID_HTML_CONFIG}" />`);
|
||||
$('head').append(`<meta name="fxa-feature-flags" content="${SERIALISED_FEATURE_FLAGS}" />`);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
$('meta[name="fxa-content-server/config"]').remove();
|
||||
$('meta[name="fxa-feature-flags"]').remove();
|
||||
});
|
||||
|
||||
it('_readConfigFromHTML returns the expected config', () => {
|
||||
return configLoader._readConfigFromHTML()
|
||||
.then((serializedHTMLConfig) => {
|
||||
assert.equal(serializedHTMLConfig, VALID_HTML_CONFIG);
|
||||
});
|
||||
});
|
||||
|
||||
it('_parseHTMLConfig parses the config', () => {
|
||||
return configLoader._parseHTMLConfig(VALID_HTML_CONFIG)
|
||||
.then((config) => {
|
||||
assert.equal(config.env, 'dev');
|
||||
assert.deepEqual(config.featureFlags, FEATURE_FLAGS);
|
||||
});
|
||||
});
|
||||
|
||||
it('_setWebpackPublicPath sets the bundle path', () => {
|
||||
configLoader._setWebpackPublicPath('somepath');
|
||||
assert.equal(__webpack_public_path__, 'somepath'); //eslint-disable-line no-undef
|
||||
configLoader._setWebpackPublicPath();
|
||||
assert.equal(__webpack_public_path__, Constants.DEFAULT_BUNDLE_PATH); //eslint-disable-line no-undef
|
||||
});
|
||||
|
||||
describe('mock internal methods', () => {
|
||||
let $html;
|
||||
let origLang;
|
||||
let sandbox;
|
||||
|
||||
beforeEach(() => {
|
||||
$('head').append(`<meta name="fxa-content-server/config" content="${VALID_HTML_CONFIG}" />`);
|
||||
|
||||
$html = $('html');
|
||||
origLang = $html.attr('lang');
|
||||
$html.attr('lang', 'db_LB');
|
||||
|
@ -102,17 +118,17 @@ define(function (require, exports, module) {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
$('meta[name="fxa-content-server/config"]').remove();
|
||||
$html.attr('lang', origLang);
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('returns the config', () => {
|
||||
it('fetch returns the config', () => {
|
||||
return configLoader.fetch()
|
||||
.then((config) => {
|
||||
assert.equal(config.env, 'dev');
|
||||
assert.equal(config.lang, 'db_LB');
|
||||
assert.deepEqual(config.featureFlags, FEATURE_FLAGS);
|
||||
|
||||
assert.isTrue(configLoader._readConfigFromHTML.called);
|
||||
assert.isTrue(configLoader._parseHTMLConfig.called);
|
||||
|
@ -120,19 +136,5 @@ define(function (require, exports, module) {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('_setWebpackPublicPath', () => {
|
||||
it('sets the bundle path', () => {
|
||||
/*eslint-disable camelcase*/
|
||||
configLoader._setWebpackPublicPath('somepath');
|
||||
assert.equal(__webpack_public_path__, 'somepath'); //eslint-disable-line no-undef
|
||||
configLoader._setWebpackPublicPath();
|
||||
assert.equal(__webpack_public_path__, Constants.DEFAULT_BUNDLE_PATH); //eslint-disable-line no-undef
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -15,45 +15,53 @@ define(function (require, exports, module) {
|
|||
experiment = new Experiment();
|
||||
});
|
||||
|
||||
|
||||
it('has the expected number of available languages', () => {
|
||||
assert.lengthOf(experiment.availableLanguages, 12);
|
||||
it('choose returns false without subject.lang', () => {
|
||||
assert.isFalse(experiment.choose({}));
|
||||
});
|
||||
|
||||
describe('choose', () => {
|
||||
it('returns false without subject.lang', () => {
|
||||
assert.isFalse(experiment.choose({}));
|
||||
[
|
||||
'de',
|
||||
'en',
|
||||
'en-US',
|
||||
'en-GB',
|
||||
'es',
|
||||
'es-ES',
|
||||
'es-MX',
|
||||
'fr',
|
||||
'hu',
|
||||
'id',
|
||||
'pl',
|
||||
'pt-br',
|
||||
'ru',
|
||||
'zh-TW',
|
||||
].forEach((lang) => {
|
||||
it(`choose returns true for ${lang}`, () => {
|
||||
assert.isTrue(experiment.choose({ lang }));
|
||||
});
|
||||
});
|
||||
|
||||
it('returns true for available languages', () => {
|
||||
[
|
||||
'de',
|
||||
'en',
|
||||
'en-US',
|
||||
'en-GB',
|
||||
'es',
|
||||
'es-ES',
|
||||
'es-MX',
|
||||
'fr',
|
||||
'hu',
|
||||
'id',
|
||||
'pl',
|
||||
'pt-br',
|
||||
'ru',
|
||||
'zh-TW',
|
||||
].forEach((lang) => {
|
||||
assert.isTrue(experiment.choose({ lang }));
|
||||
});
|
||||
[
|
||||
'de-DE',
|
||||
'pt',
|
||||
].forEach((lang) => {
|
||||
it(`choose returns false for ${lang}`, () => {
|
||||
assert.isFalse(experiment.choose({ lang }));
|
||||
});
|
||||
});
|
||||
|
||||
it('returns false for unsupported languages', () => {
|
||||
[
|
||||
'de-DE',
|
||||
'pt',
|
||||
].forEach((lang) => {
|
||||
assert.isFalse(experiment.choose({ lang }));
|
||||
});
|
||||
});
|
||||
it('choose gives precedence to featureFlags', () => {
|
||||
assert.isFalse(experiment.choose({
|
||||
featureFlags: {
|
||||
communicationPrefLanguages: [ 'en', 'fr' ]
|
||||
},
|
||||
lang: 'de'
|
||||
}));
|
||||
assert.isTrue(experiment.choose({
|
||||
featureFlags: {
|
||||
communicationPrefLanguages: [ 'en', 'pt' ]
|
||||
},
|
||||
lang: 'pt'
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -42,7 +42,10 @@ define(function (require, exports, module) {
|
|||
rule1,
|
||||
rule2,
|
||||
rule3
|
||||
]
|
||||
],
|
||||
featureFlags: {
|
||||
foo: 'bar'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -64,11 +67,17 @@ define(function (require, exports, module) {
|
|||
|
||||
assert.isTrue(experimentGroupingRules.choose('rule1', subject));
|
||||
assert.isTrue(rule1.choose.calledOnce);
|
||||
assert.isTrue(rule1.choose.calledWith(subject));
|
||||
assert.deepEqual(rule1.choose.args[0][0], {
|
||||
experimentGroupingRules,
|
||||
featureFlags: {
|
||||
foo: 'bar'
|
||||
},
|
||||
...subject
|
||||
});
|
||||
|
||||
assert.equal(experimentGroupingRules.choose('rule2', subject), 'treatment');
|
||||
assert.isTrue(rule2.choose.calledOnce);
|
||||
assert.isTrue(rule2.choose.calledWith(subject));
|
||||
assert.deepEqual(rule2.choose.args[0][0], rule1.choose.args[0][0]);
|
||||
|
||||
// rule3 is allowed even if rule2 is forced.
|
||||
subject.forceExperiment = 'rule2';
|
||||
|
|
|
@ -18,22 +18,46 @@ define(function (require, exports, module) {
|
|||
|
||||
describe('sampleRate', () => {
|
||||
it('returns 1 for development', () => {
|
||||
assert.equal(Experiment.sampleRate('development'), 1);
|
||||
assert.equal(Experiment.sampleRate({ env: 'development' }), 1);
|
||||
});
|
||||
|
||||
it('returns 0.1 for everyone else', () => {
|
||||
assert.equal(Experiment.sampleRate('production'), 0.1);
|
||||
assert.equal(Experiment.sampleRate({ env: 'production' }), 0.1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('choose', () => {
|
||||
it('delegates to bernoulliTrial', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(experiment, 'bernoulliTrial').callsFake(() => true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
experiment.bernoulliTrial.restore();
|
||||
});
|
||||
|
||||
it('delegates to bernoulliTrial', () => {
|
||||
assert.isTrue(experiment.choose({ env: 'production', uniqueUserId: 'user-id' }));
|
||||
assert.isTrue(experiment.bernoulliTrial.calledOnce);
|
||||
assert.isTrue(experiment.bernoulliTrial.calledWith(0.1, 'user-id'));
|
||||
});
|
||||
|
||||
it('passes sampleRate as 1 if env is development', () => {
|
||||
experiment.choose({ env: 'development', uniqueUserId: 'wibble' });
|
||||
assert.equal(experiment.bernoulliTrial.callCount, 1);
|
||||
assert.equal(experiment.bernoulliTrial.args[0][0], 1);
|
||||
});
|
||||
|
||||
it('gives precedence to featureFlags', () => {
|
||||
experiment.choose({
|
||||
env: 'production',
|
||||
featureFlags: {
|
||||
metricsSampleRate: 0
|
||||
},
|
||||
uniqueUserId: 'wibble'
|
||||
});
|
||||
assert.equal(experiment.bernoulliTrial.callCount, 1);
|
||||
assert.equal(experiment.bernoulliTrial.args[0][0], 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -40,10 +40,28 @@ define(function (require, exports, module) {
|
|||
});
|
||||
|
||||
describe('country does not have a `rolloutRate`', () => {
|
||||
it('returns `false', () => {
|
||||
beforeEach(() => {
|
||||
delete CountryTelephoneInfo.GB.rolloutRate;
|
||||
});
|
||||
|
||||
it('returns `false', () => {
|
||||
assert.isFalse(experiment.choose({ account, country, uniqueUserId: 'user-id' }));
|
||||
});
|
||||
|
||||
it('featureFlags take precedence', () => {
|
||||
assert.isTrue(experiment.choose({
|
||||
account,
|
||||
country,
|
||||
featureFlags: {
|
||||
smsCountries: {
|
||||
GB: {
|
||||
rolloutRate: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
uniqueUserId: 'wibble'
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('country has a `rolloutRate`', () => {
|
||||
|
@ -79,6 +97,18 @@ define(function (require, exports, module) {
|
|||
CountryTelephoneInfo.GB.rolloutRate = 1.0;
|
||||
assert.isTrue(experiment.choose({ account, country, uniqueUserId: 'user-id' }));
|
||||
});
|
||||
|
||||
it('featureFlags take precedence', () => {
|
||||
CountryTelephoneInfo.GB.rolloutRate = 1.0;
|
||||
assert.isFalse(experiment.choose({
|
||||
account,
|
||||
country,
|
||||
featureFlags: {
|
||||
smsCountries: {}
|
||||
},
|
||||
uniqueUserId: 'wibble'
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,22 +18,46 @@ define(function (require, exports, module) {
|
|||
|
||||
describe('sampleRate', () => {
|
||||
it('returns 1 for development', () => {
|
||||
assert.equal(Experiment.sampleRate('development'), 1);
|
||||
assert.equal(Experiment.sampleRate({ env: 'development' }), 1);
|
||||
});
|
||||
|
||||
it('returns 0.3 for everyone else', () => {
|
||||
assert.equal(Experiment.sampleRate('production'), 0.3);
|
||||
assert.equal(Experiment.sampleRate({ env: 'production' }), 0.3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('choose', () => {
|
||||
it('delegates to bernoulliTrial', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(experiment, 'bernoulliTrial').callsFake(() => true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
experiment.bernoulliTrial.restore();
|
||||
});
|
||||
|
||||
it('delegates to bernoulliTrial', () => {
|
||||
assert.isTrue(experiment.choose({ env: 'production', uniqueUserId: 'user-id' }));
|
||||
assert.isTrue(experiment.bernoulliTrial.calledOnce);
|
||||
assert.isTrue(experiment.bernoulliTrial.calledWith(0.3, 'user-id'));
|
||||
});
|
||||
|
||||
it('passes sampleRate as 1 if env is development', () => {
|
||||
experiment.choose({ env: 'development', uniqueUserId: 'wibble' });
|
||||
assert.equal(experiment.bernoulliTrial.callCount, 1);
|
||||
assert.equal(experiment.bernoulliTrial.args[0][0], 1);
|
||||
});
|
||||
|
||||
it('gives precedence to featureFlags', () => {
|
||||
experiment.choose({
|
||||
env: 'production',
|
||||
featureFlags: {
|
||||
sentrySampleRate: 0
|
||||
},
|
||||
uniqueUserId: 'wibble'
|
||||
});
|
||||
assert.equal(experiment.bernoulliTrial.callCount, 1);
|
||||
assert.equal(experiment.bernoulliTrial.args[0][0], 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -76,6 +76,21 @@ describe('lib/experiments/grouping-rules/token-code', () => {
|
|||
assert.isTrue(experiment.uniformChoice.calledOnce);
|
||||
assert.isTrue(experiment.uniformChoice.calledWith(['treatment-code'], 'user-id'));
|
||||
});
|
||||
|
||||
it('featureFlags take precedence', () => {
|
||||
subject.clientId = 'invalidClientId';
|
||||
assert.equal(experiment.choose({
|
||||
...subject,
|
||||
featureFlags: {
|
||||
tokenCodeClients: {
|
||||
invalidClientId: {
|
||||
groups: [ 'treatment-code' ],
|
||||
rolloutRate: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}), 'treatment-code');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with sync', () => {
|
||||
|
@ -99,6 +114,21 @@ describe('lib/experiments/grouping-rules/token-code', () => {
|
|||
assert.isTrue(experiment.uniformChoice.calledOnce, 'called once');
|
||||
assert.isTrue(experiment.uniformChoice.calledWith(['treatment-code'], 'user-id'));
|
||||
});
|
||||
|
||||
it('featureFlags take precedence', () => {
|
||||
subject.service = 'sync';
|
||||
assert.isFalse(experiment.choose({
|
||||
...subject,
|
||||
featureFlags: {
|
||||
tokenCodeClients: {
|
||||
sync: {
|
||||
groups: [ 'treatment-code' ],
|
||||
rolloutRate: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,6 +25,7 @@ module.exports = function (grunt) {
|
|||
|
||||
var PROPAGATED_ESCAPED_TEMPLATE_FIELDS = [
|
||||
'config',
|
||||
'featureFlags',
|
||||
'flowId',
|
||||
'flowBeginTime',
|
||||
'message'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "fxa-content-server",
|
||||
"version": "1.132.0",
|
||||
"version": "1.132.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -491,9 +491,9 @@
|
|||
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
|
||||
},
|
||||
"@sinonjs/commons": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.1.tgz",
|
||||
"integrity": "sha512-rgmZk5CrBGAMATk0HlHOFvo8V44/r+On6cKS80tqid0Eljd+fFBWBOXZp9H2/EB3faxdNdzXTx6QZIKLkbJ7mA==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz",
|
||||
"integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type-detect": "4.0.8"
|
||||
|
@ -517,9 +517,9 @@
|
|||
}
|
||||
},
|
||||
"@sinonjs/samsam": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.2.0.tgz",
|
||||
"integrity": "sha512-j5F1rScewLtx6pbTK0UAjA3jJj4RYiSKOix53YWv+Jzy/AZ69qHxUpU8fwVLjyKbEEud9QrLpv6Ggs7WqTimYw==",
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.0.tgz",
|
||||
"integrity": "sha512-beHeJM/RRAaLLsMJhsCvHK31rIqZuobfPLa/80yGH5hnD8PV1hyh9xJBJNFfNmO7yWqm+zomijHsXpI6iTQJfQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1.0.2",
|
||||
|
@ -713,9 +713,9 @@
|
|||
}
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.122",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.122.tgz",
|
||||
"integrity": "sha512-9IdED8wU93ty8gP06ninox+42SBSJHp2IAamsSYMUY76mshRTeUsid/gtbl8ovnOwy8im41ib4cxTiIYMXGKew==",
|
||||
"version": "4.14.123",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.123.tgz",
|
||||
"integrity": "sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mime": {
|
||||
|
@ -731,9 +731,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "11.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.4.tgz",
|
||||
"integrity": "sha512-wa09itaLE8L705aXd8F80jnFpxz3Y1/KRHfKsYL2bPc0XF+wEWu8sR9n5bmeu8Ba1N9z2GRNzm/YdHcghLkLKg==",
|
||||
"version": "11.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.3.tgz",
|
||||
"integrity": "sha512-wp6IOGu1lxsfnrD+5mX6qwSwWuqsdkKKxTN4aQc4wByHAKZJf9/D4KXPQ1POUjEbnCP5LMggB0OEFNY9OTsMqg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/platform": {
|
||||
|
@ -1440,9 +1440,9 @@
|
|||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
|
||||
},
|
||||
"async-each": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
|
||||
"integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0="
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.2.tgz",
|
||||
"integrity": "sha512-6xrbvN0MOBKSJDdonmSSz2OwFSgxRaVtBDes26mj9KIGtDo+g9xosFRSC+i1gQh2oAN/tQ62AI/pGZGQjVOiRg=="
|
||||
},
|
||||
"async-foreach": {
|
||||
"version": "0.1.3",
|
||||
|
@ -1641,13 +1641,15 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
|
||||
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
|
||||
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-extglob": "^1.0.0"
|
||||
}
|
||||
|
@ -1689,6 +1691,7 @@
|
|||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
|
||||
"integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"remove-trailing-separator": "^1.0.1"
|
||||
}
|
||||
|
@ -2832,9 +2835,9 @@
|
|||
"integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30000941",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000941.tgz",
|
||||
"integrity": "sha512-4vzGb2MfZcO20VMPj1j6nRAixhmtlhkypM4fL4zhgzEucQIYiRzSqPcWIu1OF8i0FETD93FMIPWfUJCAcFvrqA=="
|
||||
"version": "1.0.30000947",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000947.tgz",
|
||||
"integrity": "sha512-ubgBUfufe5Oi3W1+EHyh2C3lfBIEcZ6bTuvl5wNOpIuRB978GF/Z+pQ7pGGUpeYRB0P+8C7i/3lt6xkeu2hwnA=="
|
||||
},
|
||||
"capture-stack-trace": {
|
||||
"version": "1.0.1",
|
||||
|
@ -4520,6 +4523,11 @@
|
|||
"is-obj": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"double-ended-queue": {
|
||||
"version": "2.1.0-0",
|
||||
"resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz",
|
||||
"integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw="
|
||||
},
|
||||
"duplexer": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
|
||||
|
@ -4561,9 +4569,9 @@
|
|||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.113",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz",
|
||||
"integrity": "sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g=="
|
||||
"version": "1.3.116",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.116.tgz",
|
||||
"integrity": "sha512-NKwKAXzur5vFCZYBHpdWjTMO8QptNLNP80nItkSIgUOapPAo9Uia+RvkCaZJtO7fhQaVElSvBPWEc2ku6cKsPA=="
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.4.1",
|
||||
|
@ -4661,14 +4669,14 @@
|
|||
}
|
||||
},
|
||||
"es5-ext": {
|
||||
"version": "0.10.48",
|
||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.48.tgz",
|
||||
"integrity": "sha512-CdRvPlX/24Mj5L4NVxTs4804sxiS2CjVprgCmrgoDkdmjdY4D+ySHa7K3jJf8R40dFg0tIm3z/dk326LrnuSGw==",
|
||||
"version": "0.10.49",
|
||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.49.tgz",
|
||||
"integrity": "sha512-3NMEhi57E31qdzmYp2jwRArIUsj1HI/RxbQ4bgnSB+AIKIxsAmTiK83bYMifIcpWvEc3P1X30DhUKOqEtF/kvg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es6-iterator": "~2.0.3",
|
||||
"es6-symbol": "~3.1.1",
|
||||
"next-tick": "1"
|
||||
"next-tick": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"es6-iterator": {
|
||||
|
@ -4997,9 +5005,9 @@
|
|||
}
|
||||
},
|
||||
"eslint-plugin-sorting": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-sorting/-/eslint-plugin-sorting-0.4.0.tgz",
|
||||
"integrity": "sha512-2edN1MfCdHN3XBr/oMNRkg9TkHvUkzivMjfs9glyPEyqVsQrSJuh/iDidUjg7LXVATJuDQsD8bVELldBj4bFZQ==",
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-sorting/-/eslint-plugin-sorting-0.4.1.tgz",
|
||||
"integrity": "sha512-MYMdZnRN9+YsypSP6clX37AgVh3dt1Esgo+DynoubjCMm+B8Gf2t80xdqjWt7poyDUzkwSfzu5pOVF2HvVfYug==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-scope": {
|
||||
|
@ -5861,7 +5869,8 @@
|
|||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
|
@ -5879,11 +5888,13 @@
|
|||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -5896,15 +5907,18 @@
|
|||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
|
@ -6007,7 +6021,8 @@
|
|||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
@ -6017,6 +6032,7 @@
|
|||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
|
@ -6029,17 +6045,20 @@
|
|||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.5",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
|
@ -6056,6 +6075,7 @@
|
|||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
|
@ -6128,7 +6148,8 @@
|
|||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
|
@ -6138,6 +6159,7 @@
|
|||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
@ -6213,7 +6235,8 @@
|
|||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
|
@ -6243,6 +6266,7 @@
|
|||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
|
@ -6260,6 +6284,7 @@
|
|||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
|
@ -6298,11 +6323,13 @@
|
|||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -6391,7 +6418,7 @@
|
|||
"integrity": "sha512-1Nq8FtlF0Xb5V/4oKmJuKLKD2nGnM4DuhWCdY5obZEIDtBUHgPb0CUf9FVLY54rHJGzfoItFqrVI+SNpY1GAdw==",
|
||||
"requires": {
|
||||
"es6-promise": "4.1.1",
|
||||
"sjcl": "git://github.com/bitwiseshiftleft/sjcl.git#a03ea8ef32329bc8d7bc28a438372b5acb46616b",
|
||||
"sjcl": "git://github.com/bitwiseshiftleft/sjcl.git#a03ea8e",
|
||||
"xhr2": "0.0.7"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -6421,12 +6448,16 @@
|
|||
}
|
||||
},
|
||||
"fxa-shared": {
|
||||
"version": "1.0.18",
|
||||
"resolved": "https://registry.npmjs.org/fxa-shared/-/fxa-shared-1.0.18.tgz",
|
||||
"integrity": "sha512-5un2xXGpIfjPramIx/fJtuxojw/zuJ884VYb/N4xhkW4qXKGOahAJIzcgERAUxCgAqQpcv3K7RA+Z33P+MH5ug==",
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmjs.org/fxa-shared/-/fxa-shared-1.0.20.tgz",
|
||||
"integrity": "sha512-olnR3xMo0m2+eL11yAj2A7Kf9l1kbzk1hHieYu3BmQDN8Otf6o/erPlGWOt2/G8AJdWAtxOBZ//W4SR5K+hB1w==",
|
||||
"requires": {
|
||||
"accept-language": "2.0.17",
|
||||
"moment": "2.20.1"
|
||||
"ajv": "6.10.0",
|
||||
"bluebird": "3.5.3",
|
||||
"generic-pool": "3.6.1",
|
||||
"moment": "2.20.1",
|
||||
"redis": "2.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"moment": {
|
||||
|
@ -6477,6 +6508,11 @@
|
|||
"is-property": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"generic-pool": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.6.1.tgz",
|
||||
"integrity": "sha512-iMmD/pY4q0+V+f8o4twE9JPeqfNuX+gJAaIPB3B0W1lFkBOtTxBo6B0HxHPgGhzQA8jego7EWopcYq/UDJO2KA=="
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
|
||||
|
@ -6898,13 +6934,15 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
|
||||
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
|
||||
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-extglob": "^1.0.0"
|
||||
}
|
||||
|
@ -8139,17 +8177,17 @@
|
|||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz",
|
||||
"integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.1.0.tgz",
|
||||
"integrity": "sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg==",
|
||||
"requires": {
|
||||
"ansi-regex": "^4.0.0"
|
||||
"ansi-regex": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz",
|
||||
"integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w=="
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9303,7 +9341,7 @@
|
|||
}
|
||||
},
|
||||
"legal-docs": {
|
||||
"version": "git://github.com/mozilla/legal-docs.git#6827032416da57e4b77920046dc6abbfe582fbcf",
|
||||
"version": "git://github.com/mozilla/legal-docs.git#30c5c916dc3f153df9cf2a55a263329ae403febb",
|
||||
"from": "git://github.com/mozilla/legal-docs.git#master"
|
||||
},
|
||||
"levn": {
|
||||
|
@ -9820,12 +9858,12 @@
|
|||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"mem": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz",
|
||||
"integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mem/-/mem-4.2.0.tgz",
|
||||
"integrity": "sha512-5fJxa68urlY0Ir8ijatKa3eRz5lwXnRCTvo9+TbTGAuTFJOwpGcY0X05moBd0nW45965Njt4CDI2GFQoG8DvqA==",
|
||||
"requires": {
|
||||
"map-age-cleaner": "^0.1.1",
|
||||
"mimic-fn": "^1.0.0",
|
||||
"mimic-fn": "^2.0.0",
|
||||
"p-is-promise": "^2.0.0"
|
||||
}
|
||||
},
|
||||
|
@ -9930,9 +9968,9 @@
|
|||
}
|
||||
},
|
||||
"mimic-fn": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
|
||||
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.0.0.tgz",
|
||||
"integrity": "sha512-jbex9Yd/3lmICXwYT6gA/j2mNQGU48wCh/VzRd+/Y/PjYQtlg1gLMdZqvu9s/xH7qKvngxRObl56XZR609IMbA=="
|
||||
},
|
||||
"min-document": {
|
||||
"version": "2.19.0",
|
||||
|
@ -10196,9 +10234,9 @@
|
|||
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz",
|
||||
"integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw=="
|
||||
"version": "2.13.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.13.0.tgz",
|
||||
"integrity": "sha512-5DDQvN0luhXdut8SCwzm/ZuAX2W+fwhqNzfq7CZ+OJzQ6NwpcqmIGyLD1R8MEt7BeErzcsI0JLr4pND2pNp2Cw=="
|
||||
},
|
||||
"nanomatch": {
|
||||
"version": "1.2.13",
|
||||
|
@ -10401,9 +10439,9 @@
|
|||
}
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.9.tgz",
|
||||
"integrity": "sha512-oic3GT4OtbWWKfRolz5Syw0Xus0KRFxeorLNj0s93ofX6PWyuzKjsiGxsCtWktBwwmTF6DdRRf2KreGqeOk5KA==",
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.10.tgz",
|
||||
"integrity": "sha512-KbUPCpfoBvb3oBkej9+nrU0/7xPlVhmhhUJ1PZqwIP5/1dJkRWKWD3OONjo6M2J7tSCBtDCumLwwqeI+DWWaLQ==",
|
||||
"requires": {
|
||||
"semver": "^5.3.0"
|
||||
}
|
||||
|
@ -10543,7 +10581,7 @@
|
|||
"from": "git://github.com/vladikoff/node-uap.git#9cdd16247",
|
||||
"requires": {
|
||||
"array.prototype.find": "2.0.0",
|
||||
"uap-core": "git://github.com/ua-parser/uap-core.git#add7bafbb3ba57256d1b919103add1b2cab97aa7",
|
||||
"uap-core": "git://github.com/ua-parser/uap-core.git",
|
||||
"uap-ref-impl": "0.2.0",
|
||||
"yamlparser": "0.0.2"
|
||||
}
|
||||
|
@ -10790,6 +10828,13 @@
|
|||
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
|
||||
"requires": {
|
||||
"mimic-fn": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"mimic-fn": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
|
||||
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"optimist": {
|
||||
|
@ -10992,7 +11037,8 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
|
||||
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "2.0.1",
|
||||
|
@ -11226,9 +11272,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"pngjs": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.3.3.tgz",
|
||||
"integrity": "sha512-1n3Z4p3IOxArEs1VRXnZ/RXdfEniAUS9jb68g58FIXMNkPJeZd+Qh4Uq7/e0LVxAQGos1eIUrqrt4FpjdnEd+Q==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
|
||||
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
|
||||
"dev": true
|
||||
},
|
||||
"po2json": {
|
||||
|
@ -12036,6 +12082,26 @@
|
|||
"strip-indent": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"redis": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz",
|
||||
"integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==",
|
||||
"requires": {
|
||||
"double-ended-queue": "^2.1.0-0",
|
||||
"redis-commands": "^1.2.0",
|
||||
"redis-parser": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"redis-commands": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.4.0.tgz",
|
||||
"integrity": "sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw=="
|
||||
},
|
||||
"redis-parser": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz",
|
||||
"integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs="
|
||||
},
|
||||
"referrer-policy": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.1.0.tgz",
|
||||
|
@ -13804,7 +13870,7 @@
|
|||
"from": "git://github.com/vladikoff/ua-parser-js.git#fxa-version"
|
||||
},
|
||||
"uap-core": {
|
||||
"version": "git://github.com/ua-parser/uap-core.git#add7bafbb3ba57256d1b919103add1b2cab97aa7",
|
||||
"version": "git://github.com/ua-parser/uap-core.git#b4a50d040ad03b163b675d468d7ee011e9ad5436",
|
||||
"from": "git://github.com/ua-parser/uap-core.git"
|
||||
},
|
||||
"uap-ref-impl": {
|
||||
|
@ -13991,9 +14057,9 @@
|
|||
"integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c="
|
||||
},
|
||||
"upath": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz",
|
||||
"integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw=="
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz",
|
||||
"integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q=="
|
||||
},
|
||||
"upper-case": {
|
||||
"version": "1.1.3",
|
||||
|
@ -14635,6 +14701,12 @@
|
|||
"mimic-fn": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"mimic-fn": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
|
||||
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
|
||||
"dev": true
|
||||
},
|
||||
"os-locale": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz",
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
"fxa-js-client": "1.0.8",
|
||||
"fxa-mustache-loader": "0.0.2",
|
||||
"fxa-pairing-channel": "1.0.1",
|
||||
"fxa-shared": "1.0.18",
|
||||
"fxa-shared": "1.0.20",
|
||||
"got": "6.7.1",
|
||||
"grunt": "1.0.3",
|
||||
"grunt-babel": "6.0.0",
|
||||
|
@ -133,7 +133,7 @@
|
|||
"css": "2.2.3",
|
||||
"eslint": "4.16.0",
|
||||
"eslint-plugin-fxa": "git://github.com/mozilla/eslint-plugin-fxa.git#1153ff4bbf7e2c074363253c555fb7f71bac09a1",
|
||||
"eslint-plugin-sorting": "0.4.0",
|
||||
"eslint-plugin-sorting": "0.4.1",
|
||||
"firefox-profile": "1.2.0",
|
||||
"fxa-conventional-changelog": "1.1.0",
|
||||
"grunt-ban-word": "0.1.1",
|
||||
|
|
|
@ -147,6 +147,52 @@ const conf = module.exports = convict({
|
|||
'development'
|
||||
]
|
||||
},
|
||||
featureFlags: {
|
||||
enabled: {
|
||||
default: true,
|
||||
doc: 'Enable feature flagging',
|
||||
env: 'FEATURE_FLAGS_ENABLED',
|
||||
format: Boolean
|
||||
},
|
||||
interval: {
|
||||
default: '30 seconds',
|
||||
doc: 'The refresh interval for feature-flagging',
|
||||
env: 'FEATURE_FLAGS_INTERVAL',
|
||||
format: 'duration'
|
||||
},
|
||||
redis: {
|
||||
host: {
|
||||
default: '127.0.0.1',
|
||||
doc: 'Redis host name or IP address',
|
||||
env: 'FEATURE_FLAGS_REDIS_HOST',
|
||||
format: String
|
||||
},
|
||||
maxConnections: {
|
||||
default: 1,
|
||||
doc: 'Maximum connection count for feature-flagging Redis pool',
|
||||
env: 'FEATURE_FLAGS_REDIS_MAX_CONNECTIONS',
|
||||
format: 'nat'
|
||||
},
|
||||
maxPending: {
|
||||
default: 1,
|
||||
doc: 'Maximum waiting client count for feature-flagging Redis pool',
|
||||
env: 'FEATURE_FLAGS_REDIS_MAX_PENDING',
|
||||
format: 'nat'
|
||||
},
|
||||
minConnections: {
|
||||
default: 1,
|
||||
doc: 'Minimum connection count for feature-flagging Redis pool',
|
||||
env: 'FEATURE_FLAGS_REDIS_MIN_CONNECTIONS',
|
||||
format: 'nat'
|
||||
},
|
||||
port: {
|
||||
default: 6379,
|
||||
doc: 'Redis port',
|
||||
env: 'FEATURE_FLAGS_REDIS_PORT',
|
||||
format: 'port'
|
||||
}
|
||||
}
|
||||
},
|
||||
flow_id_expiry: {
|
||||
default: '2 hours',
|
||||
doc: 'Time after which flow ids are considered stale',
|
||||
|
|
|
@ -8,6 +8,14 @@ const flowMetrics = require('../flow-metrics');
|
|||
const logger = require('../logging/log')('routes.index');
|
||||
|
||||
module.exports = function (config) {
|
||||
let featureFlags;
|
||||
const featureFlagConfig = config.get('featureFlags');
|
||||
if (featureFlagConfig.enabled) {
|
||||
featureFlags = require('fxa-shared/feature-flags')(featureFlagConfig, logger);
|
||||
} else {
|
||||
featureFlags = { get: () => ({}) };
|
||||
}
|
||||
|
||||
const AUTH_SERVER_URL = config.get('fxaccount_url');
|
||||
const CLIENT_ID = config.get('oauth_client_id');
|
||||
const COPPA_ENABLED = config.get('coppa.enabled');
|
||||
|
@ -54,17 +62,25 @@ module.exports = function (config) {
|
|||
return {
|
||||
method: 'get',
|
||||
path: '/',
|
||||
process: function (req, res) {
|
||||
process: async function (req, res) {
|
||||
const flowEventData = flowMetrics.create(FLOW_ID_KEY, req.headers['user-agent']);
|
||||
|
||||
if (NO_LONGER_SUPPORTED_CONTEXTS.has(req.query.context)) {
|
||||
return res.redirect(`/update_firefox?${req.originalUrl.split('?')[1]}`);
|
||||
}
|
||||
|
||||
let flags;
|
||||
try {
|
||||
flags = await featureFlags.get();
|
||||
} catch (err) {
|
||||
logger.error('featureFlags.error', err);
|
||||
flags = {};
|
||||
}
|
||||
res.render('index', {
|
||||
// Note that bundlePath is added to templates as a build step
|
||||
bundlePath: '/bundle',
|
||||
config: serializedConfig,
|
||||
featureFlags: encodeURIComponent(JSON.stringify(flags)),
|
||||
flowBeginTime: flowEventData.flowBeginTime,
|
||||
flowId: flowEventData.flowId,
|
||||
// Note that staticResourceUrl is added to templates as a build step
|
||||
|
@ -74,6 +90,7 @@ module.exports = function (config) {
|
|||
if (req.headers.dnt === '1') {
|
||||
logger.info('request.headers.dnt');
|
||||
}
|
||||
}
|
||||
},
|
||||
terminate: featureFlags.terminate
|
||||
};
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<meta name="referrer" content="origin">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<meta name="fxa-content-server/config" content="{{ config }}" />
|
||||
<meta name="fxa-feature-flags" content="{{ featureFlags }}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=2.0, user-scalable=yes" />
|
||||
|
||||
<!--iOS Smart Banner-->
|
||||
|
|
|
@ -20,10 +20,14 @@ registerSuite('routes/get-index', {
|
|||
instance = route(config);
|
||||
},
|
||||
|
||||
after: () => {
|
||||
instance.terminate();
|
||||
},
|
||||
|
||||
tests: {
|
||||
'instance interface is correct': function () {
|
||||
assert.isObject(instance);
|
||||
assert.lengthOf(Object.keys(instance), 3);
|
||||
assert.lengthOf(Object.keys(instance), 4);
|
||||
assert.equal(instance.method, 'get');
|
||||
assert.equal(instance.path, '/');
|
||||
assert.isFunction(instance.process);
|
||||
|
@ -37,7 +41,7 @@ registerSuite('routes/get-index', {
|
|||
query: {}
|
||||
};
|
||||
response = {render: sinon.spy()};
|
||||
instance.process(request, response);
|
||||
return instance.process(request, response);
|
||||
},
|
||||
|
||||
tests: {
|
||||
|
@ -51,10 +55,11 @@ registerSuite('routes/get-index', {
|
|||
|
||||
var renderParams = args[1];
|
||||
assert.isObject(renderParams);
|
||||
assert.lengthOf(Object.keys(renderParams), 5);
|
||||
assert.lengthOf(Object.keys(renderParams), 6);
|
||||
assert.ok(/[0-9a-f]{64}/.exec(renderParams.flowId));
|
||||
assert.isAbove(renderParams.flowBeginTime, 0);
|
||||
assert.equal(renderParams.bundlePath, '/bundle');
|
||||
assert.isObject(JSON.parse(decodeURIComponent(renderParams.featureFlags)));
|
||||
assert.equal(renderParams.staticResourceUrl, config.get('static_resource_url'));
|
||||
|
||||
assert.isString(renderParams.config);
|
||||
|
@ -88,7 +93,7 @@ registerSuite('routes/get-index', {
|
|||
},
|
||||
};
|
||||
response = {redirect: sinon.spy()};
|
||||
instance.process(request, response);
|
||||
return instance.process(request, response);
|
||||
},
|
||||
|
||||
tests: {
|
||||
|
@ -109,7 +114,7 @@ registerSuite('routes/get-index', {
|
|||
},
|
||||
};
|
||||
response = {redirect: sinon.spy()};
|
||||
instance.process(request, response);
|
||||
return instance.process(request, response);
|
||||
},
|
||||
|
||||
tests: {
|
||||
|
@ -130,7 +135,7 @@ registerSuite('routes/get-index', {
|
|||
},
|
||||
};
|
||||
response = {render: sinon.spy()};
|
||||
instance.process(request, response);
|
||||
return instance.process(request, response);
|
||||
},
|
||||
|
||||
tests: {
|
||||
|
|
Загрузка…
Ссылка в новой задаче