Expose experiments and feature flags on /__frontend_version__ (#6340)

This commit is contained in:
William Durand 2018-09-25 17:52:49 +02:00 коммит произвёл GitHub
Родитель 472a0c1a84
Коммит 2d0a256aa3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 201 добавлений и 21 удалений

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

@ -215,6 +215,7 @@
"extract-text-webpack-plugin": "3.0.2",
"fastclick": "1.0.6",
"focus-visible": "4.1.5",
"fs-extra": "7.0.0",
"full-icu": "1.2.1",
"helmet": "3.13.0",
"history": "4.7.2",

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

@ -24,6 +24,7 @@ import * as middleware from 'core/middleware';
import { loadErrorPage } from 'core/reducers/errorPage';
import { dismissSurvey } from 'core/reducers/survey';
import { addQueryParamsToHistory, convertBoolean } from 'core/utils';
import { viewFrontendVersionHandler } from 'core/utils/server';
import {
setAuthToken,
setClientApp,
@ -182,26 +183,11 @@ function baseServer(
app.use(middleware.serveAssetsLocally());
}
// Show version/commit information as JSON.
function viewVersion(req, res) {
// This is a magic file that gets written by deployment scripts.
const version = path.join(config.get('basePath'), 'version.json');
fs.stat(version, (error) => {
if (error) {
log.error(`Could not stat version file ${version}: ${error}`);
res.sendStatus(415);
} else {
res.setHeader('Content-Type', 'application/json');
fs.createReadStream(version).pipe(res);
}
});
}
// Following the ops monitoring convention, return version info at this URL.
app.get('/__version__', viewVersion);
// Following the ops monitoring Dockerflow convention, return version info at
// this URL. See: https://github.com/mozilla-services/Dockerflow
app.get('/__version__', viewFrontendVersionHandler());
// For AMO, this helps differentiate from /__version__ served by addons-server.
app.get('/__frontend_version__', viewVersion);
app.get('/__frontend_version__', viewFrontendVersionHandler());
// Return 200 for csp reports - this will need to be overridden when deployed.
app.post('/__cspreport__', (req, res) => res.status(200).end('ok'));

59
src/core/utils/server.js Normal file
Просмотреть файл

@ -0,0 +1,59 @@
/* @flow */
import path from 'path';
import fs from 'fs-extra';
import config from 'config';
import type { $Request, $Response } from 'express';
import log from 'core/logger';
const PREFIX = 'enableFeature';
type ExpressHandler = (req: $Request, res: $Response) => void;
type ViewFrontendVersionHandlerParams = {|
_config?: typeof config,
_log?: typeof log,
versionFilename?: string,
|};
export const viewFrontendVersionHandler = ({
_config = config,
_log = log,
// This is a magic file that gets written by deployment scripts.
versionFilename = 'version.json',
}: ViewFrontendVersionHandlerParams = {}): ExpressHandler => {
const version = path.join(_config.get('basePath'), versionFilename);
const featureFlags = Object.keys(_config)
.filter((key) => {
return key.startsWith(PREFIX);
})
.reduce((map, key) => {
return {
...map,
[key]: _config.get(key),
};
}, {});
const experiments = _config.get('experiments');
return (req: $Request, res: $Response) => {
fs.stat(version, async (error) => {
if (error) {
_log.error(
`Could not stat version file ${version}: ${error.toString()}`,
);
res.sendStatus(415);
} else {
const versionJson = await fs.readJson(version);
res.json({
...versionJson,
experiments,
feature_flags: featureFlags,
});
}
});
};
};

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

@ -0,0 +1,6 @@
{
"build": "https://circleci.com/gh/mozilla/addons-frontend/10080",
"commit": "afb29ad060fcdb84e300ebb16aea69cee06d4d5e",
"source": "https://github.com/mozilla/addons-frontend",
"version": ""
}

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

@ -0,0 +1,82 @@
import path from 'path';
import fs from 'fs-extra';
import MockExpressResponse from 'mock-express-response';
import { viewFrontendVersionHandler } from 'core/utils/server';
import { getFakeConfig } from 'tests/unit/helpers';
describe(__filename, () => {
describe('viewFrontendVersionHandler', () => {
const basePath = path.join(__dirname, 'fixtures');
const versionJson = fs.readJsonSync(path.join(basePath, 'version.json'));
it('exposes the version.json file', (done) => {
const _config = getFakeConfig({ basePath });
const handler = viewFrontendVersionHandler({ _config });
const res = new MockExpressResponse();
handler(null, res);
res.on('finish', () => {
expect(res.statusCode).toEqual(200);
expect(res.get('content-type')).toEqual(
'application/json; charset=utf-8',
);
expect(res._getJSON()).toMatchObject(versionJson);
done();
});
});
it('exposes the experiments and feature flags', (done) => {
const experiments = {
ab_test_1: true,
};
const featureFlags = {
enableFeatureFoo001: true,
enableFeatureBar123: false,
};
const _config = getFakeConfig(
{ basePath, experiments, ...featureFlags },
{ allowUnknownKeys: true },
);
const handler = viewFrontendVersionHandler({ _config });
const res = new MockExpressResponse();
handler(null, res);
res.on('finish', () => {
expect(res._getJSON()).toMatchObject({
...versionJson,
experiments,
feature_flags: {
...featureFlags,
},
});
done();
});
});
it('returns a 415 and logs an error when file does not exist', (done) => {
const _config = getFakeConfig({ basePath: '/some/invalid/path' });
const _log = {
error: sinon.stub(),
};
const handler = viewFrontendVersionHandler({ _config, _log });
const res = new MockExpressResponse();
handler(null, res);
res.on('finish', () => {
expect(res.statusCode).toEqual(415);
sinon.assert.calledOnce(_log.error);
done();
});
});
});
});

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

@ -442,9 +442,12 @@ export function createFakeUserAbuseReport({
// if (fakeConfig.get('isDevelopment')) {
// ...
// }
export const getFakeConfig = (params = {}) => {
export const getFakeConfig = (
params = {},
{ allowUnknownKeys = false } = {},
) => {
for (const key of Object.keys(params)) {
if (!config.has(key)) {
if (!config.has(key) && !allowUnknownKeys) {
// This will help alert us when a test accidentally relies
// on an invalid config key.
throw new Error(

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

@ -11,6 +11,7 @@ import { put, takeLatest } from 'redux-saga/effects';
/* eslint-enable import/order */
import {
getFakeConfig,
matchingSagaAction,
shallowUntilTarget,
unexpectedSuccess,
@ -274,4 +275,22 @@ describe(__filename, () => {
);
});
});
describe('getFakeConfig', () => {
it('throws an error when key is invalid', () => {
expect(() => {
getFakeConfig({ thisIsAnInvalidKey: true });
}).toThrow(/this key is invalid/);
});
it('does not throw when key is invalid and allowUnknownKeys is set to true', () => {
const value = 'some value';
const config = getFakeConfig(
{ thisIsAnInvalidKey: value },
{ allowUnknownKeys: true },
);
expect(config.get('thisIsAnInvalidKey')).toEqual(value);
});
});
});

6
version.json Normal file
Просмотреть файл

@ -0,0 +1,6 @@
{
"build": "https://circleci.com/gh/mozilla/addons-frontend/10080",
"commit": "afb29ad060fcdb84e300ebb16aea69cee06d4d5e",
"source": "https://github.com/mozilla/addons-frontend",
"version": ""
}

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

@ -4840,6 +4840,14 @@ fs-copy-file-sync@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/fs-copy-file-sync/-/fs-copy-file-sync-1.1.1.tgz#11bf32c096c10d126e5f6b36d06eece776062918"
fs-extra@7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.0.tgz#8cc3f47ce07ef7b3593a11b9fb245f7e34c041d6"
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^0.30.0:
version "0.30.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0"
@ -6909,6 +6917,12 @@ jsonfile@^2.1.0:
optionalDependencies:
graceful-fs "^4.1.6"
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
optionalDependencies:
graceful-fs "^4.1.6"
jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
@ -12099,6 +12113,10 @@ unist-util-visit@^1.1.0:
dependencies:
unist-util-is "^2.1.1"
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"