Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

standardize a way to configure feature toggles #76

Merged
merged 5 commits into from
Nov 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions api/v1/helpers/nouns/perspectives.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,8 @@
'use strict'; // eslint-disable-line strict

const Perspective = require('../../../../db/index').Perspective;
const config = require('../../../../config');

const featureToggles = require('feature-toggles');
const m = 'perspective';
let cacheEnabled = false;

if (config.enableCachePerspective === 'true' ||
config.enableCachePerspective === true) {
cacheEnabled = true;
}

module.exports = {
apiLinks: {
Expand All @@ -36,5 +29,5 @@ module.exports = {
},
model: Perspective,
modelName: 'Perspective',
cacheEnabled,
cacheEnabled: featureToggles.isFeatureEnabled('enableCachePerspective'),
}; // exports
7 changes: 2 additions & 5 deletions api/v1/helpers/verbs/findUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,11 @@
* api/v1/helpers/verbs/findUtils.js
*/
'use strict';

const featureToggles = require('feature-toggles');
const u = require('./utils');
const constants = require('../../constants');
const defaults = require('../../../../config').api.defaults;
const conf = require('../../../../config');

const env = conf.environment[conf.nodeEnv];
const filterSubjByTags = env.filterSubjByTags;
const filterSubjByTags = featureToggles.isFeatureEnabled('filterSubjByTags');

/**
* Escapes all percent literals so they're not treated as wildcards.
Expand Down
4 changes: 2 additions & 2 deletions clock/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
* use "npm run start-clock". To start both the web and the clock process
* locally, use "heroku local"
*/
const featureToggles = require('feature-toggles');
const conf = require('../config');
const env = conf.environment[conf.nodeEnv];

const dbSample = require('../db/index').Sample;

if (conf.enableClockDyno) {
if (featureToggles.isFeatureEnabled('enableClockDyno')) {
setInterval(() => dbSample.doTimeout(), env.checkTimeoutIntervalMillis);
}

39 changes: 1 addition & 38 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* Configuration Settings
*/
'use strict'; // eslint-disable-line strict

require('./config/toggles'); // Loads the feature toggles
const configUtil = require('./config/configUtil');
const defaultPort = 3000;
const defaultPostgresPort = 5432;
Expand All @@ -39,23 +39,12 @@ const iplist = configUtil.parseIPlist(ipWhitelist);

// Check for timed-out samples every 30 seconds if not specified in env var
const DEFAULT_CHECK_TIMEOUT_INTERVAL_MILLIS = 30000;
const enableClockDyno = pe.HEROKU_CLOCK_DYNO === 'true' ||
pe.HEROKU_CLOCK_DYNO === true || false;

// audit level values can be one of these: API, DB, ALL, NONE
const auditSubjects = pe.AUDIT_SUBJECTS || 'NONE';
const auditSamples = pe.AUDIT_SAMPLES || 'NONE';
const auditAspects = pe.AUDIT_ASPECTS || 'NONE';

const optimizeUpsert = pe.OPTIMIZE_UPSERT === 'true' ||
pe.OPTIMIZE_UPSERT === true || false;

// env variable to enable caching for /GET /v1/perspectives/{key}
const enableCachePerspective = pe.ENABLE_CACHE_PERSPECTIVE || false;

const filterSubjByTags = pe.FILTER_SUBJ_BY_TAGS === 'true' ||
pe.FILTER_SUBJ_BY_TAGS === true || false;

module.exports = {

api: {
Expand Down Expand Up @@ -114,7 +103,6 @@ module.exports = {
useAccessToken: pe.USE_ACCESS_TOKEN || false,
tokenSecret:
'7265666f637573726f636b7377697468677265656e6f776c7373616e6672616e',
filterSubjByTags,
},
development: {
checkTimeoutIntervalMillis: pe.CHECK_TIMEOUT_INTERVAL_MILLIS ||
Expand All @@ -134,7 +122,6 @@ module.exports = {
useAccessToken: pe.USE_ACCESS_TOKEN || false,
tokenSecret:
'7265666f637573726f636b7377697468677265656e6f776c7373616e6672616e',
filterSubjByTags,
},
production: {
checkTimeoutIntervalMillis: pe.CHECK_TIMEOUT_INTERVAL_MILLIS ||
Expand All @@ -153,7 +140,6 @@ module.exports = {
useAccessToken: pe.USE_ACCESS_TOKEN || false,
tokenSecret: pe.SECRET_TOKEN ||
'7265666f637573726f636b7377697468677265656e6f776c7373616e6672616e',
filterSubjByTags,
},
test: {
checkTimeoutIntervalMillis: pe.CHECK_TIMEOUT_INTERVAL_MILLIS ||
Expand All @@ -172,7 +158,6 @@ module.exports = {
useAccessToken: pe.USE_ACCESS_TOKEN || false,
tokenSecret: pe.SECRET_TOKEN ||
'7265666f637573726f636b7377697468677265656e6f776c7373616e6672616e',
filterSubjByTags,
},
testDisableHttp: {
checkTimeoutIntervalMillis: pe.CHECK_TIMEOUT_INTERVAL_MILLIS ||
Expand All @@ -186,7 +171,6 @@ module.exports = {
useAccessToken: 'true',
tokenSecret:
'7265666f637573726f636b7377697468677265656e6f776c7373616e6672616e',
filterSubjByTags,
},
testWhitelistLocalhost: {
checkTimeoutIntervalMillis: pe.CHECK_TIMEOUT_INTERVAL_MILLIS ||
Expand All @@ -200,7 +184,6 @@ module.exports = {
ipWhitelist: iplist,
tokenSecret:
'7265666f637573726f636b7377697468677265656e6f776c7373616e6672616e',
filterSubjByTags,
},
testBlockAllhosts: {
checkTimeoutIntervalMillis: pe.CHECK_TIMEOUT_INTERVAL_MILLIS ||
Expand All @@ -214,7 +197,6 @@ module.exports = {
ipWhitelist: [''],
tokenSecret:
'7265666f637573726f636b7377697468677265656e6f776c7373616e6672616e',
filterSubjByTags,
},
testTokenReq: {
checkTimeoutIntervalMillis: pe.CHECK_TIMEOUT_INTERVAL_MILLIS ||
Expand All @@ -228,7 +210,6 @@ module.exports = {
useAccessToken: 'true',
tokenSecret:
'7265666f637573726f636b7377697468677265656e6f776c7373616e6672616e',
filterSubjByTags,
},
testTokenNotReq: {
checkTimeoutIntervalMillis: pe.CHECK_TIMEOUT_INTERVAL_MILLIS ||
Expand All @@ -242,22 +223,7 @@ module.exports = {
useAccessToken: false,
tokenSecret:
'7265666f637573726f636b7377697468677265656e6f776c7373616e6672616e',
filterSubjByTags,
},
testSubjTagFilter: {
checkTimeoutIntervalMillis: pe.CHECK_TIMEOUT_INTERVAL_MILLIS ||
DEFAULT_CHECK_TIMEOUT_INTERVAL_MILLIS,
dbLogging: false, // console.log | false | ...
dbUrl: defaultDbUrl,
disableHttp,
redisUrl: '//127.0.0.1:6379',
defaultNodePort: defaultPort,
host: '127.0.0.1',
useAccessToken: false,
tokenSecret:
'7265666f637573726f636b7377697468677265656e6f776c7373616e6672616e',
filterSubjByTags: true,
}
},

nodeEnv,
Expand All @@ -269,7 +235,4 @@ module.exports = {
auditSubjects,
auditSamples,
auditAspects,
optimizeUpsert,
enableCachePerspective,
enableClockDyno,
};
61 changes: 61 additions & 0 deletions config/toggles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Copyright (c) 2016, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or
* https://opensource.org/licenses/BSD-3-Clause
*/

/**
* /config/toggles.js
*
* Configure feature toggles.
*
* Usage: wherever you need to check whether a feature is enabled, just
* add require('feature-toggles') to the top of the module then use
* featureToggles.isFeatureEnabled('MY_FEATURE_NAME') or
* featureToggles.isFeatureEnabled('MY_FEATURE_NAME', 'myarg', 'anotherarg').
* (All the args after the feature name are passed to the function if the
* feature is defined using a function.)
*/
'use strict'; // eslint-disable-line strict
const featureToggles = require('feature-toggles');
const pe = process.env; // eslint-disable-line no-process-env

/**
* Return boolean true if the named environment variable is boolean true or
* case-insensitive string 'true'.
*
* @param {String} environmentVariableName - The name of the environment var.
* @returns {Boolean} true if the named environment variable is boolean true or
* case-insensitive string 'true'.
*/
function environmentVariableTrue(processEnv, environmentVariableName) {
const x = processEnv[environmentVariableName];
return typeof x !== 'undefined' && x !== null &&
x.toString().toLowerCase() === 'true';
} // environmentVariableTrue

/*
* Add a new feature flag by adding an attribute here.
*/
const toggles = {
// Enable caching for GET /v1/perspectives/{key}?
enableCachePerspective: environmentVariableTrue(pe,
'ENABLE_CACHE_PERSPECTIVE'),

// Enable heroku clock dyno
enableClockDyno: environmentVariableTrue(pe, 'HEROKU_CLOCK_DYNO'),

// Enable GET /v1/subjects?tags=...
filterSubjByTags: environmentVariableTrue(pe, 'FILTER_SUBJ_BY_TAGS'),

// Enable bulk upsert optimization
optimizeUpsert: environmentVariableTrue(pe, 'OPTIMIZE_UPSERT'),
};

featureToggles.load(toggles);

module.exports = {
environmentVariableTrue, // exporting to make it easy to test
};
66 changes: 34 additions & 32 deletions db/model/sample.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* db/model/sample.js
*/
'use strict'; // eslint-disable-line strict
const featureToggles = require('feature-toggles');
const constants = require('../constants');
const u = require('../helpers/sampleUtils');
const common = require('../helpers/common');
Expand Down Expand Up @@ -173,7 +174,7 @@ module.exports = function sample(seq, dataTypes) {
* encountered while performing the sample upsert operation itself.
*/
upsertByName(toUpsert, isBulk) {
if (config.optimizeUpsert) {
if (featureToggles.isFeatureEnabled('optimizeUpsert')) {
let sampleExists = true;
return new seq.Promise((resolve, reject) => {
// get sample by name
Expand Down Expand Up @@ -219,39 +220,40 @@ module.exports = function sample(seq, dataTypes) {
isBulk ? resolve(err) : reject(err);
});
});
} else {
let subjasp;
return new seq.Promise((resolve, reject) => {
u.getSubjectAndAspectBySampleName(seq, toUpsert.name, isBulk)
.then((sa) => {
subjasp = sa;
toUpsert.subjectId = sa.subject.id;
toUpsert.aspectId = sa.aspect.id;
})
.then(() => Sample.findOne({
where: {
subjectId: subjasp.subject.id,
aspectId: subjasp.aspect.id,
isDeleted: NO,
},
}))
.then((o) => {
if (o === null) {
return Sample.create(toUpsert);
}
}

// set value changed to true, during updates to avoid timeouts
// Adding this to the before update hook does
// give the needed effect; so adding it here!!!.
o.changed('value', true);
return o.update(toUpsert);
})
.then((o) => resolve(o))
.catch((err) => {
isBulk ? resolve(err) : reject(err);
});
// 'optimizeUpsert' is not enabled
let subjasp;
return new seq.Promise((resolve, reject) => {
u.getSubjectAndAspectBySampleName(seq, toUpsert.name, isBulk)
.then((sa) => {
subjasp = sa;
toUpsert.subjectId = sa.subject.id;
toUpsert.aspectId = sa.aspect.id;
})
.then(() => Sample.findOne({
where: {
subjectId: subjasp.subject.id,
aspectId: subjasp.aspect.id,
isDeleted: NO,
},
}))
.then((o) => {
if (o === null) {
return Sample.create(toUpsert);
}

// set value changed to true, during updates to avoid timeouts
// Adding this to the before update hook does
// give the needed effect; so adding it here!!!.
o.changed('value', true);
return o.update(toUpsert);
})
.then((o) => resolve(o))
.catch((err) => {
isBulk ? resolve(err) : reject(err);
});
}
});
}, // upsertByName

bulkUpsertByName(toUpsert) {
Expand Down
6 changes: 4 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/**
* ./index.js
*
* Main module to start the express server(web process). To just start the
* Main module to start the express server (web process). To just start the
* web process use "node index.js". To start both the web and the clock process
* use "heroku local"
*/
Expand All @@ -20,6 +20,7 @@ const WORKERS = process.env.WEB_CONCURRENCY || 1;
* Entry point for each newly clustered process
*/
function start() { // eslint-disable-line max-statements
const featureToggles = require('feature-toggles');
const conf = require('./config');

/*
Expand Down Expand Up @@ -123,11 +124,12 @@ function start() { // eslint-disable-line max-statements
}

// if the clock dyno is not enabled run the Sample timeout query here.
if (!conf.enableClockDyno) {
if (!featureToggles.isFeatureEnabled('enableClockDyno')) {
// require the sample model only if we want to run the timeout query here
const dbSample = require('./db/index').Sample;
setInterval(() => dbSample.doTimeout(), env.checkTimeoutIntervalMillis);
}

// View engine setup
app.set('views', path.join(__dirname, 'view'));
app.set('view engine', 'pug');
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
"test-token-notreq": "NODE_ENV=testTokenNotReq mocha -R dot --recursive tests/tokenNotReq",
"test-db": "npm run resetdb && mocha -R dot --recursive tests/db",
"test-realtime": "mocha -R dot --recursive tests/realtime",
"test-subj-tag-filter": "NODE_ENV=testSubjTagFilter mocha -R dot --recursive tests/subjectTagFilter",
"test-subj-tag-filter": "FILTER_SUBJ_BY_TAGS=true mocha -R dot --recursive tests/subjectTagFilter",
"test-view": "NODE_ENV=test mocha -R dot --recursive --compilers js:babel-core/register --require ./tests/view/setup.js tests/view",
"test": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R dot --recursive tests/api tests/db && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage && npm run test-view && npm run test-realtime && npm run test-token-req && npm run test-token-notreq && npm run test-disablehttp && npm run test-subj-tag-filter",
"test": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R dot --recursive tests/api tests/db tests/config && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage && npm run test-view && npm run test-realtime && npm run test-token-req && npm run test-token-notreq && npm run test-disablehttp && npm run test-subj-tag-filter",
"postinstall": "NODE_ENV=production gulp browserifyViews && gulp movecss && gulp movesocket && gulp movelensutil && npm run checkdb",
"prestart": "npm run checkprivatedb"
},
Expand Down Expand Up @@ -72,6 +72,7 @@
"express-ipfilter": "^0.0.24",
"express-session": "^1.13.0",
"extract-text-webpack-plugin": "^0.8.2",
"feature-toggles": "^1.4.0",
"fs": "0.0.2",
"gulp": "^3.9.0",
"gulp-chmod": "^1.3.0",
Expand All @@ -87,6 +88,7 @@
"multer": "^1.0.3",
"newrelic": "^1.28.1",
"nock": "^3.6.0",
"npm": "^3.10.9",
"npm-watch": "^0.1.3",
"passport": "^0.3.2",
"passport-local": "^1.0.0",
Expand Down
Loading