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

feat: use feature flags #798

Draft
wants to merge 3 commits into
base: stable
Choose a base branch
from
Draft
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
6 changes: 5 additions & 1 deletion config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ const config = {
'bugsnag_key',
'cookie'
],
bugsnag_key: process.env.BUGSNAG_KEY_CORE || 'CHANGEME'
bugsnag_key: process.env.BUGSNAG_KEY || 'CHANGEME',
featureflag_url: process.env.FEATUREFLAG_URL || 'CHANGEME',
featureflag_token: process.env.FEATUREFLAG_TOKEN || 'CHANGEME',
flagsmith_token: process.env.FLAGSMITH_TOKEN || 'CHANGEME',
devcycle_token: process.env.DEVCYCLE_TOKEN || 'CHANGEME',
},
development: {

Expand Down
8 changes: 8 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ services:
HOST: "${SUBDOMAIN_FRONTEND}${BASE_URL}"
CORE_LOGIN: "${CORE_LOGIN}"
CORE_PASSWORD: "${CORE_PASSWORD}"
LOGLEVEL: "warn"
# it's gonna be one of those three offerings in the end
# NB: all are selfhosted except there's no trace of devcycle docker.
# maybe they removed the offering
FEATUREFLAG_URL: "${FEATUREFLAG_URL}"
FEATUREFLAG_TOKEN: "${FEATUREFLAG_TOKEN}"
FLAGSMITH_TOKEN: "${FLAGSMITH_TOKEN}"
DEVCYCLE_TOKEN: "${DEVCYCLE_TOKEN}"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8084/healthcheck"]
interval: 30s
Expand Down
81 changes: 81 additions & 0 deletions lib/featureflags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const Flagsmith = require('flagsmith-nodejs'); // nice but less strategies, weirder to selfhost
const { Unleash } = require('unleash-client'); // cumbersome with the proxy thing but seems ok, good to selfhost
const DVC = require('@devcycle/nodejs-server-sdk'); // what the actual fuck to get the flag
const config = require('../config');
const logger = require('./logger');

// NOTE: for all three providers, the choice of environment is
// via the API key (i.e. there's an API key for each env)

/** Flagsmith */

let flagsmith;
if (!flagsmith) {
flagsmith = new Flagsmith({
environmentKey: config.flagsmith_token,
});
logger.info('Feature Flag: provider flagsmith initialised');
}

/* usage:
var showButton = flags.isFeatureEnabled('secret_button');
var buttonData = flags.getFeatureValue('secret_button');
*/

/** unleash */

let unleash;
if (!unleash) {
unleash = new Unleash({
url: config.featureflag_url, // NOT proxy but the :4242/api
appName: 'Default', // can't change the stupid name in the unleash UI...
customHeaders: { Authorization: config.featureflag_token },
});
logger.info('Feature Flag: provider unleash initialised');
}

/** DevCycle */

let devcycleClient;

async function initializeDevCycle() {
try {
devcycleClient = await DVC.initializeDevCycle(config.devcycle_token).onClientInitialized();
logger.info('Feature Flag: provider DVC initialised');
} catch (ex) {
return logger.error(`Error initializing DevCycle: ${ex}`);
}
}

if (!devcycleClient) initializeDevCycle();

function returnDVCVariable(feature, user = null) {
// The user data must be passed into every method, only the user_id is required
const userDescription = user || {
user_id: 'user1@devcycle.com', // whatever name or ID works
name: 'user 1 name',
customData: {
customKey: 'customValue'
}
};

// Fetch variable values using the identifier key coupled with a default value
// The default value can be of type string, boolean, number, or object
const dvcVariableValue = devcycleClient.variableValue(userDescription, feature, false);
return dvcVariableValue;
}

async function isFFEnabled(feature, engine = 'flagsmith') {
switch (engine) {
case 'dvc':
return returnDVCVariable(feature);
case 'unleash':
return unleash.isEnabled(feature);
default:
// eslint-disable-next-line no-case-declarations
const flags = await flagsmith.getEnvironmentFlags();
return flags.isFeatureEnabled(feature);
}
}

module.exports = isFFEnabled;
65 changes: 59 additions & 6 deletions lib/mailer.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
const request = require('request-promise-native');

const amqp = require('amqplib/callback_api');
const logger = require('./logger');
const isFFEnabled = require('./featureflags');
const config = require('../config');

/**
* @param {Object} options
* @param {string|string[]} options.to
* @param {string} options.template
* @param {string} options.subject
* @param {object} options.reply_to
* @param {string} options.subject
* @param {string} options.template
* @param {object} options.parameters
*/
module.exports.sendMail = async (options) => {
const sendMail = async (options) => {
logger.error('into old mailer');
const mailerBody = await request({
url: config.mailer.url + ':' + config.mailer.port + '/',
method: 'POST',
simple: false,
json: true,
body: {
to: options.to,
reply_to: options.reply_to,
subject: options.subject,
template: options.template,
parameters: options.parameters,
reply_to: options.reply_to
parameters: options.parameters
}
});

Expand All @@ -35,3 +38,53 @@ module.exports.sendMail = async (options) => {

return mailerBody;
};

const queue = 'email';
const enqueuer = async (options) => {
amqp.connect('amqp://rabbit', (error0, connection) => {
if (error0) {
throw error0;
}
connection.createChannel((error1, channel) => {
if (error1) {
throw error1;
}

const msg = {
from: 'mailer@aegee.eu', // it was auto-set in mailer, here we must set it
to: options.to,
reply_to: options.reply_to || 'noreply@aegee.eu', // FIXME
subject: options.subject,
template: options.template,
parameters: options.parameters,
};

// channel.assertQueue(queue, {
// durable: true,
// 'x-dead-letter-exchange': 'dead_letter_exchange'
// });

channel.sendToQueue(queue, Buffer.from(JSON.stringify(msg)));
logger.info(' [x] Sent %s', msg);
});
setTimeout(() => {
connection.close();
}, 500);
return { response: { success: true } }; // TODO: handle rabbit issues
});
};

// This function is in order to bypass the fact that one can't
// change the export at runtime
const sender = async (options) => {
if (await isFFEnabled('new_mailer')) {
logger.info('Experimental: new mailer');
options.template = options.template.replace('.html', ''); // the dispatcher adds the extension
const thevalue = await enqueuer(options);
return thevalue;
}
const thevalue = await sendMail(options);
return thevalue;
};

module.exports.sendMail = sender;
Loading