1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00
This commit is contained in:
sveisvei 2016-06-18 21:53:18 +02:00
parent aba95e30cb
commit 0f7c17654a
78 changed files with 916 additions and 894 deletions

View File

@ -1,18 +1,19 @@
var express = require('express'); 'use strict';
var favicon = require('serve-favicon'); const express = require('express');
var bodyParser = require('body-parser'); const favicon = require('serve-favicon');
var cookieParser = require('cookie-parser'); const bodyParser = require('body-parser');
var validator = require('express-validator'); const cookieParser = require('cookie-parser');
var log4js = require('log4js'); const validator = require('express-validator');
var logger = require('./lib/logger'); const log4js = require('log4js');
var routes = require('./lib/routes'); const logger = require('./lib/logger');
var path = require('path'); const routes = require('./lib/routes');
const path = require('path');
module.exports = function(config) { module.exports = function(config) {
var app = express(); const app = express();
var router = express.Router(); const router = express.Router();
var baseUriPath = config.baseUriPath || ''; const baseUriPath = config.baseUriPath || '';
var publicFolder = config.publicFolder; const publicFolder = config.publicFolder;
app.set('trust proxy'); app.set('trust proxy');
app.set('port', config.port); app.set('port', config.port);

View File

@ -1,4 +1,5 @@
var knex = require('knex'); 'use strict';
const knex = require('knex');
module.exports = function(databaseConnection) { module.exports = function(databaseConnection) {
return knex({ return knex({

View File

@ -1,4 +1,5 @@
var EVENT_COLUMNS = ['id', 'type', 'created_by', 'created_at', 'data']; 'use strict';
const EVENT_COLUMNS = ['id', 'type', 'created_by', 'created_at', 'data'];
module.exports = function(db) { module.exports = function(db) {
function storeEvent(event) { function storeEvent(event) {
@ -38,8 +39,8 @@ module.exports = function(db) {
return { return {
store: storeEvent, store: storeEvent,
getEvents: getEvents, getEvents,
getEventsFilterByName: getEventsFilterByName getEventsFilterByName
}; };
}; };

View File

@ -1,24 +1,17 @@
var eventType = require('../eventType'); 'use strict';
var logger = require('../logger'); const eventType = require('../eventType');
var NotFoundError = require('../error/NotFoundError'); const logger = require('../logger');
var FEATURE_COLUMNS = ['name', 'description', 'enabled', 'strategy_name', 'parameters']; const NotFoundError = require('../error/NotFoundError');
const FEATURE_COLUMNS = ['name', 'description', 'enabled', 'strategy_name', 'parameters'];
module.exports = function(db, eventStore) { module.exports = function(db, eventStore) {
eventStore.on(eventType.featureCreated, function (event) { eventStore.on(eventType.featureCreated, event => createFeature(event.data));
return createFeature(event.data);
});
eventStore.on(eventType.featureUpdated, function (event) { eventStore.on(eventType.featureUpdated, event => updateFeature(event.data));
return updateFeature(event.data);
});
eventStore.on(eventType.featureArchived, function (event) { eventStore.on(eventType.featureArchived, event => archiveFeature(event.data));
return archiveFeature(event.data);
});
eventStore.on(eventType.featureRevived, function (event) { eventStore.on(eventType.featureRevived, event => reviveFeature(event.data));
return reviveFeature(event.data);
});
function getFeatures() { function getFeatures() {
return db return db
@ -33,7 +26,7 @@ module.exports = function(db, eventStore) {
return db return db
.first(FEATURE_COLUMNS) .first(FEATURE_COLUMNS)
.from('features') .from('features')
.where({ name: name }) .where({ name })
.then(rowToFeature); .then(rowToFeature);
} }
@ -75,7 +68,7 @@ module.exports = function(db, eventStore) {
function createFeature(data) { function createFeature(data) {
return db('features') return db('features')
.insert(eventDataToRow(data)) .insert(eventDataToRow(data))
.catch(function (err) { .catch(err => {
logger.error('Could not insert feature, error was: ', err); logger.error('Could not insert feature, error was: ', err);
}); });
} }
@ -84,7 +77,7 @@ module.exports = function(db, eventStore) {
return db('features') return db('features')
.where({ name: data.name }) .where({ name: data.name })
.update(eventDataToRow(data)) .update(eventDataToRow(data))
.catch(function (err) { .catch(err => {
logger.error('Could not update feature, error was: ', err); logger.error('Could not update feature, error was: ', err);
}); });
} }
@ -93,7 +86,7 @@ module.exports = function(db, eventStore) {
return db('features') return db('features')
.where({ name: data.name }) .where({ name: data.name })
.update({ archived: 1 }) .update({ archived: 1 })
.catch(function (err) { .catch(err => {
logger.error('Could not archive feature, error was: ', err); logger.error('Could not archive feature, error was: ', err);
}); });
} }
@ -102,16 +95,16 @@ module.exports = function(db, eventStore) {
return db('features') return db('features')
.where({ name: data.name }) .where({ name: data.name })
.update({ archived: 0, enabled: 0 }) .update({ archived: 0, enabled: 0 })
.catch(function (err) { .catch(err => {
logger.error('Could not archive feature, error was: ', err); logger.error('Could not archive feature, error was: ', err);
}); });
} }
return { return {
getFeatures: getFeatures, getFeatures,
getFeature: getFeature, getFeature,
getArchivedFeatures: getArchivedFeatures, getArchivedFeatures,
_createFeature: createFeature, // visible for testing _createFeature: createFeature, // visible for testing
_updateFeature: updateFeature // visible for testing _updateFeature: updateFeature // visible for testing
}; };

View File

@ -1,18 +1,17 @@
var eventType = require('../eventType'); 'use strict';
var logger = require('../logger'); const eventType = require('../eventType');
var NotFoundError = require('../error/NotFoundError'); const logger = require('../logger');
var STRATEGY_COLUMNS = ['name', 'description', 'parameters_template']; const NotFoundError = require('../error/NotFoundError');
const STRATEGY_COLUMNS = ['name', 'description', 'parameters_template'];
module.exports = function(db, eventStore) { module.exports = function(db, eventStore) {
eventStore.on(eventType.strategyCreated, function (event) { eventStore.on(eventType.strategyCreated, event => createStrategy(event.data));
return createStrategy(event.data);
});
eventStore.on(eventType.strategyDeleted, function (event) { eventStore.on(eventType.strategyDeleted, event => {
db('strategies') db('strategies')
.where('name', event.data.name) .where('name', event.data.name)
.del() .del()
.catch(function (err) { .catch(err => {
logger.error('Could not delete strategy, error was: ', err); logger.error('Could not delete strategy, error was: ', err);
}); });
}); });
@ -29,7 +28,7 @@ module.exports = function(db, eventStore) {
return db return db
.first(STRATEGY_COLUMNS) .first(STRATEGY_COLUMNS)
.from('strategies') .from('strategies')
.where({ name: name }) .where({ name })
.then(rowToStrategy); .then(rowToStrategy);
} }
@ -56,14 +55,14 @@ module.exports = function(db, eventStore) {
function createStrategy(data) { function createStrategy(data) {
db('strategies') db('strategies')
.insert(eventDataToRow(data)) .insert(eventDataToRow(data))
.catch(function (err) { .catch(err => {
logger.error('Could not insert strategy, error was: ', err); logger.error('Could not insert strategy, error was: ', err);
}); });
} }
return { return {
getStrategies: getStrategies, getStrategies,
getStrategy: getStrategy, getStrategy,
_createStrategy: createStrategy // visible for testing _createStrategy: createStrategy // visible for testing
}; };
}; };

View File

@ -1,4 +1,5 @@
var util = require('util'); 'use strict';
const util = require('util');
function NameExistsError(message) { function NameExistsError(message) {
Error.call(this); Error.call(this);

View File

@ -1,4 +1,5 @@
var util = require('util'); 'use strict';
const util = require('util');
function NotFoundError(message) { function NotFoundError(message) {
Error.call(this); Error.call(this);

View File

@ -1,4 +1,5 @@
var util = require('util'); 'use strict';
const util = require('util');
function ValidationError(message) { function ValidationError(message) {
Error.call(this); Error.call(this);

View File

@ -1,8 +1,9 @@
var Promise = require("bluebird"); 'use strict';
var ValidationError = require('./ValidationError'); const Promise = require("bluebird");
const ValidationError = require('./ValidationError');
function validateRequest(req) { function validateRequest(req) {
return new Promise(function(resolve, reject) { return new Promise((resolve, reject) => {
if (req.validationErrors()) { if (req.validationErrors()) {
reject(new ValidationError("Invalid syntax")); reject(new ValidationError("Invalid syntax"));
} else { } else {

View File

@ -1,12 +1,13 @@
var eventType = require('./eventType'); 'use strict';
var diff = require('deep-diff').diff; const eventType = require('./eventType');
const diff = require('deep-diff').diff;
var strategyTypes = [ const strategyTypes = [
eventType.strategyCreated, eventType.strategyCreated,
eventType.strategyDeleted eventType.strategyDeleted
]; ];
var featureTypes = [ const featureTypes = [
eventType.featureCreated, eventType.featureCreated,
eventType.featureUpdated, eventType.featureUpdated,
eventType.featureArchived, eventType.featureArchived,
@ -19,15 +20,15 @@ function baseTypeFor(event) {
} else if (strategyTypes.indexOf(event.type) !== -1) { } else if (strategyTypes.indexOf(event.type) !== -1) {
return 'strategies'; return 'strategies';
} else { } else {
throw new Error('unknown event type: ' + JSON.stringify(event)); throw new Error(`unknown event type: ${JSON.stringify(event)}`);
} }
} }
function groupByBaseTypeAndName(events) { function groupByBaseTypeAndName(events) {
var groups = {}; const groups = {};
events.forEach(function (event) { events.forEach(event => {
var baseType = baseTypeFor(event); const baseType = baseTypeFor(event);
groups[baseType] = groups[baseType] || {}; groups[baseType] = groups[baseType] || {};
groups[baseType][event.data.name] = groups[baseType][event.data.name] || []; groups[baseType][event.data.name] = groups[baseType][event.data.name] || [];
@ -39,14 +40,17 @@ function groupByBaseTypeAndName(events) {
} }
function eachConsecutiveEvent(events, callback) { function eachConsecutiveEvent(events, callback) {
var groups = groupByBaseTypeAndName(events); const groups = groupByBaseTypeAndName(events);
Object.keys(groups).forEach(function (baseType) { Object.keys(groups).forEach(baseType => {
var group = groups[baseType]; const group = groups[baseType];
Object.keys(group).forEach(function (name) { Object.keys(group).forEach(name => {
var events = group[name]; const events = group[name];
var left, right, i, l; let left;
let right;
let i;
let l;
for (i = 0, l = events.length; i < l; i++) { for (i = 0, l = events.length; i < l; i++) {
left = events[i]; left = events[i];
right = events[i + 1]; right = events[i + 1];
@ -58,7 +62,7 @@ function eachConsecutiveEvent(events, callback) {
} }
function addDiffs(events) { function addDiffs(events) {
eachConsecutiveEvent(events, function (left, right) { eachConsecutiveEvent(events, (left, right) => {
if (right) { if (right) {
left.diffs = diff(right.data, left.data); left.diffs = diff(right.data, left.data);
left.diffs = left.diffs || []; left.diffs = left.diffs || [];
@ -70,5 +74,5 @@ function addDiffs(events) {
module.exports = { module.exports = {
addDiffs: addDiffs addDiffs
}; };

View File

@ -1,5 +1,6 @@
var util = require('util'); 'use strict';
var EventEmitter = require('events').EventEmitter; const util = require('util');
const EventEmitter = require('events').EventEmitter;
function EventStore(eventDb) { function EventStore(eventDb) {
this.eventDb = eventDb; this.eventDb = eventDb;
@ -8,8 +9,8 @@ function EventStore(eventDb) {
util.inherits(EventStore, EventEmitter); util.inherits(EventStore, EventEmitter);
EventStore.prototype.create = function (event) { EventStore.prototype.create = function (event) {
var that = this; const that = this;
return this.eventDb.store(event).then(function() { return this.eventDb.store(event).then(() => {
that.emit(event.type, event); that.emit(event.type, event);
}); });
}; };

View File

@ -1,3 +1,4 @@
'use strict';
module.exports = { module.exports = {
featureCreated: 'feature-created', featureCreated: 'feature-created',
featureUpdated: 'feature-updated', featureUpdated: 'feature-updated',

View File

@ -1,3 +1,4 @@
'use strict';
function extractUsername(req) { function extractUsername(req) {
return req.cookies.username || "unknown"; return req.cookies.username || "unknown";
} }

View File

@ -1,9 +1,10 @@
var log4js = require('log4js'); 'use strict';
const log4js = require('log4js');
log4js.clearAppenders(); log4js.clearAppenders();
log4js.addAppender(log4js.appenders.console()); log4js.addAppender(log4js.appenders.console());
var logger = log4js.getLogger('unleash'); const logger = log4js.getLogger('unleash');
// TODO: make level configurable // TODO: make level configurable
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {

View File

@ -1,17 +1,18 @@
var eventDiffer = require('../eventDiffer'); 'use strict';
const eventDiffer = require('../eventDiffer');
module.exports = function (app, config) { module.exports = function (app, config) {
var eventDb = config.eventDb; const eventDb = config.eventDb;
app.get('/events', function (req, res) { app.get('/events', (req, res) => {
eventDb.getEvents().then(function (events) { eventDb.getEvents().then(events => {
eventDiffer.addDiffs(events); eventDiffer.addDiffs(events);
res.json({ events: events }); res.json({ events });
}); });
}); });
app.get('/events/:name', function (req, res) { app.get('/events/:name', (req, res) => {
eventDb.getEventsFilterByName(req.params.name).then(function (events) { eventDb.getEventsFilterByName(req.params.name).then(events => {
if (events) { if (events) {
eventDiffer.addDiffs(events); eventDiffer.addDiffs(events);
res.json(events); res.json(events);

View File

@ -1,34 +1,33 @@
var logger = require('../logger'); 'use strict';
var eventType = require('../eventType'); const logger = require('../logger');
var ValidationError = require('../error/ValidationError'); const eventType = require('../eventType');
var validateRequest = require('../error/validateRequest'); const ValidationError = require('../error/ValidationError');
const validateRequest = require('../error/validateRequest');
module.exports = function (app, config) { module.exports = function (app, config) {
var featureDb = config.featureDb; const featureDb = config.featureDb;
var eventStore = config.eventStore; const eventStore = config.eventStore;
app.get('/archive/features', function (req, res) { app.get('/archive/features', (req, res) => {
featureDb.getArchivedFeatures().then(function (archivedFeatures) { featureDb.getArchivedFeatures().then(archivedFeatures => {
res.json({ 'features': archivedFeatures }); res.json({ features: archivedFeatures });
}); });
}); });
app.post('/archive/revive', function (req, res) { app.post('/archive/revive', (req, res) => {
req.checkBody('name', 'Name is required').notEmpty(); req.checkBody('name', 'Name is required').notEmpty();
validateRequest(req) validateRequest(req)
.then(function() { .then(() => eventStore.create({
return eventStore.create({ type: eventType.featureRevived,
type: eventType.featureRevived, createdBy: req.connection.remoteAddress,
createdBy: req.connection.remoteAddress, data: req.body
data: req.body })).then(() => {
});
}).then(function() {
res.status(200).end(); res.status(200).end();
}).catch(ValidationError, function() { }).catch(ValidationError, () => {
res.status(400).json(req.validationErrors()); res.status(400).json(req.validationErrors());
}) })
.catch(function(err) { .catch(err => {
logger.error("Could not revive feature toggle", err); logger.error("Could not revive feature toggle", err);
res.status(500).end(); res.status(500).end();
}); });

View File

@ -1,121 +1,116 @@
var Promise = require("bluebird"); 'use strict';
var logger = require('../logger'); const Promise = require("bluebird");
var eventType = require('../eventType'); const logger = require('../logger');
var NameExistsError = require('../error/NameExistsError'); const eventType = require('../eventType');
var NotFoundError = require('../error/NotFoundError'); const NameExistsError = require('../error/NameExistsError');
var ValidationError = require('../error/ValidationError'); const NotFoundError = require('../error/NotFoundError');
var validateRequest = require('../error/validateRequest'); const ValidationError = require('../error/ValidationError');
var extractUser = require('../extractUser'); const validateRequest = require('../error/validateRequest');
const extractUser = require('../extractUser');
module.exports = function (app, config) { module.exports = function (app, config) {
var featureDb = config.featureDb; const featureDb = config.featureDb;
var eventStore = config.eventStore; const eventStore = config.eventStore;
app.get('/features', function (req, res) { app.get('/features', (req, res) => {
featureDb.getFeatures().then(function (features) { featureDb.getFeatures().then(features => {
res.json({ features: features }); res.json({ features });
}); });
}); });
app.get('/features/:featureName', function (req, res) { app.get('/features/:featureName', (req, res) => {
featureDb.getFeature(req.params.featureName) featureDb.getFeature(req.params.featureName)
.then(function (feature) { .then(feature => {
res.json(feature); res.json(feature);
}) })
.catch(function () { .catch(() => {
res.status(404).json({ error: 'Could not find feature' }); res.status(404).json({ error: 'Could not find feature' });
}); });
}); });
app.post('/features', function (req, res) { app.post('/features', (req, res) => {
req.checkBody('name', 'Name is required').notEmpty(); req.checkBody('name', 'Name is required').notEmpty();
req.checkBody('name', 'Name must match format ^[a-zA-Z\\.\\-]+$').matches(/^[a-zA-Z\\.\\-]+$/i); req.checkBody('name', 'Name must match format ^[a-zA-Z\\.\\-]+$').matches(/^[a-zA-Z\\.\\-]+$/i);
validateRequest(req) validateRequest(req)
.then(validateUniqueName) .then(validateUniqueName)
.then(function() { .then(() => eventStore.create({
return eventStore.create({ type: eventType.featureCreated,
type: eventType.featureCreated, createdBy: extractUser(req),
createdBy: extractUser(req), data: req.body
data: req.body }))
}); .then(() => {
})
.then(function () {
res.status(201).end(); res.status(201).end();
}) })
.catch(NameExistsError, function() { .catch(NameExistsError, () => {
res.status(403).json([{ res.status(403).json([{
msg: "A feature named '" + req.body.name + "' already exists. It could be archived." msg: `A feature named '${req.body.name}' already exists. It could be archived.`
}]).end(); }]).end();
}) })
.catch(ValidationError, function() { .catch(ValidationError, () => {
res.status(400).json(req.validationErrors()); res.status(400).json(req.validationErrors());
}) })
.catch(function(err) { .catch(err => {
logger.error("Could not create feature toggle", err); logger.error("Could not create feature toggle", err);
res.status(500).end(); res.status(500).end();
}); });
}); });
app.put('/features/:featureName', function (req, res) { app.put('/features/:featureName', (req, res) => {
var featureName = req.params.featureName; const featureName = req.params.featureName;
var userName = extractUser(req); const userName = extractUser(req);
var updatedFeature = req.body; const updatedFeature = req.body;
updatedFeature.name = featureName; updatedFeature.name = featureName;
featureDb.getFeature(featureName) featureDb.getFeature(featureName)
.then(function () { .then(() => eventStore.create({
return eventStore.create({ type: eventType.featureUpdated,
type: eventType.featureUpdated, createdBy: userName,
createdBy: userName, data: updatedFeature
data: updatedFeature }))
}); .then(() => {
})
.then(function () {
res.status(200).end(); res.status(200).end();
}) })
.catch(NotFoundError, function () { .catch(NotFoundError, () => {
res.status(404).end(); res.status(404).end();
}) })
.catch(function (err) { .catch(err => {
logger.error("Could not update feature toggle="+featureName, err); logger.error(`Could not update feature toggle=${featureName}`, err);
res.status(500).end(); res.status(500).end();
}); });
}); });
app.delete('/features/:featureName', function (req, res) { app.delete('/features/:featureName', (req, res) => {
var featureName = req.params.featureName; const featureName = req.params.featureName;
var userName = extractUser(req); const userName = extractUser(req);
featureDb.getFeature(featureName) featureDb.getFeature(featureName)
.then(function () { .then(() => eventStore.create({
return eventStore.create({ type: eventType.featureArchived,
type: eventType.featureArchived, createdBy: userName,
createdBy: userName, data: {
data: { name: featureName
name: featureName }
} }))
}); .then(() => {
})
.then(function () {
res.status(200).end(); res.status(200).end();
}) })
.catch(NotFoundError, function () { .catch(NotFoundError, () => {
res.status(404).end(); res.status(404).end();
}) })
.catch(function (err) { .catch(err => {
logger.error("Could not archive feature="+featureName, err); logger.error(`Could not archive feature=${featureName}`, err);
res.status(500).end(); res.status(500).end();
}); });
}); });
function validateUniqueName(req) { function validateUniqueName(req) {
return new Promise(function(resolve, reject) { return new Promise((resolve, reject) => {
featureDb.getFeature(req.body.name) featureDb.getFeature(req.body.name)
.then(function() { .then(() => {
reject(new NameExistsError("Feature name already exist")); reject(new NameExistsError("Feature name already exist"));
}, function() { }, () => {
resolve(req); resolve(req);
}); });
}); });

View File

@ -1,13 +1,14 @@
var logger = require('../logger'); 'use strict';
const logger = require('../logger');
module.exports = function (app, config) { module.exports = function (app, config) {
app.get('/health', function (req, res) { app.get('/health', (req, res) => {
config.db.select(1) config.db.select(1)
.from('features') .from('features')
.then(function() { .then(() => {
res.json({ health: 'GOOD' }); res.json({ health: 'GOOD' });
}) })
.catch(function(err) { .catch(err => {
logger.error('Could not select from features, error was: ', err); logger.error('Could not select from features, error was: ', err);
res.status(500).json({ health: 'BAD' }); res.status(500).json({ health: 'BAD' });
}); });

View File

@ -2,6 +2,8 @@
* TODO: we should also inject config and * TODO: we should also inject config and
* services to the routes to ease testing. * services to the routes to ease testing.
**/ **/
'use strict';
exports.create = function (app, config) { exports.create = function (app, config) {
require('./event')(app, config); require('./event')(app, config);
require('./feature')(app, config); require('./feature')(app, config);

View File

@ -1,93 +1,90 @@
var Promise = require("bluebird"); 'use strict';
var eventType = require('../eventType'); const Promise = require("bluebird");
var logger = require('../logger'); const eventType = require('../eventType');
var NameExistsError = require('../error/NameExistsError'); const logger = require('../logger');
var ValidationError = require('../error/ValidationError'); const NameExistsError = require('../error/NameExistsError');
var NotFoundError = require('../error/NotFoundError'); const ValidationError = require('../error/ValidationError');
var validateRequest = require('../error/validateRequest'); const NotFoundError = require('../error/NotFoundError');
var extractUser = require('../extractUser'); const validateRequest = require('../error/validateRequest');
const extractUser = require('../extractUser');
module.exports = function (app, config) { module.exports = function (app, config) {
var strategyDb = config.strategyDb; const strategyDb = config.strategyDb;
var eventStore = config.eventStore; const eventStore = config.eventStore;
app.get('/strategies', function (req, res) { app.get('/strategies', (req, res) => {
strategyDb.getStrategies().then(function (strategies) { strategyDb.getStrategies().then(strategies => {
res.json({ strategies: strategies }); res.json({ strategies });
}); });
}); });
app.get('/strategies/:name', function (req, res) { app.get('/strategies/:name', (req, res) => {
strategyDb.getStrategy(req.params.name) strategyDb.getStrategy(req.params.name)
.then(function (strategy) { .then(strategy => {
res.json(strategy); res.json(strategy);
}) })
.catch(function () { .catch(() => {
res.status(404).json({ error: 'Could not find strategy' }); res.status(404).json({ error: 'Could not find strategy' });
}); });
}); });
app.delete('/strategies/:name', function (req, res) { app.delete('/strategies/:name', (req, res) => {
var strategyName = req.params.name; const strategyName = req.params.name;
strategyDb.getStrategy(strategyName) strategyDb.getStrategy(strategyName)
.then(function() { .then(() => eventStore.create({
return eventStore.create({ type: eventType.strategyDeleted,
type: eventType.strategyDeleted, createdBy: extractUser(req),
createdBy: extractUser(req), data: {
data: { name: strategyName
name: strategyName }
} }))
}); .then(() => {
})
.then(function() {
res.status(200).end(); res.status(200).end();
}) })
.catch(NotFoundError, function() { .catch(NotFoundError, () => {
res.status(404).end(); res.status(404).end();
}) })
.catch(function(err) { .catch(err => {
logger.error('Could not delete strategy='+strategyName, err); logger.error(`Could not delete strategy=${strategyName}`, err);
res.status(500).end(); res.status(500).end();
}); });
}); });
app.post('/strategies', function (req, res) { app.post('/strategies', (req, res) => {
req.checkBody('name', 'Name is required').notEmpty(); req.checkBody('name', 'Name is required').notEmpty();
req.checkBody('name', 'Name must match format ^[a-zA-Z\\.\\-]+$').matches(/^[a-zA-Z\\.\\-]+$/i); req.checkBody('name', 'Name must match format ^[a-zA-Z\\.\\-]+$').matches(/^[a-zA-Z\\.\\-]+$/i);
var newStrategy = req.body; const newStrategy = req.body;
validateRequest(req) validateRequest(req)
.then(validateStrategyName) .then(validateStrategyName)
.then(function() { .then(() => eventStore.create({
return eventStore.create({ type: eventType.strategyCreated,
type: eventType.strategyCreated, createdBy: extractUser(req),
createdBy: extractUser(req), data: newStrategy
data: newStrategy }))
}); .then(() => {
})
.then(function () {
res.status(201).end(); res.status(201).end();
}) })
.catch(NameExistsError, function() { .catch(NameExistsError, () => {
res.status(403).json([{ msg: "A strategy named '" + req.body.name + "' already exists." }]).end(); res.status(403).json([{ msg: `A strategy named '${req.body.name}' already exists.` }]).end();
}) })
.catch(ValidationError, function() { .catch(ValidationError, () => {
res.status(400).json(req.validationErrors()); res.status(400).json(req.validationErrors());
}) })
.catch(function(err) { .catch(err => {
logger.error("Could not create strategy", err); logger.error("Could not create strategy", err);
res.status(500).end(); res.status(500).end();
}); });
}); });
function validateStrategyName(req) { function validateStrategyName(req) {
return new Promise(function(resolve, reject) { return new Promise((resolve, reject) => {
strategyDb.getStrategy(req.body.name) strategyDb.getStrategy(req.body.name)
.then(function() { .then(() => {
reject(new NameExistsError("Feature name already exist")); reject(new NameExistsError("Feature name already exist"));
}, function() { }, () => {
resolve(req); resolve(req);
}); });
}); });

View File

@ -1 +1,2 @@
'use strict';
module.exports = require('../scripts/migration-runner').create('001-initial-schema'); module.exports = require('../scripts/migration-runner').create('001-initial-schema');

View File

@ -1 +1,2 @@
'use strict';
module.exports = require('../scripts/migration-runner').create('002-add-description-to-features'); module.exports = require('../scripts/migration-runner').create('002-add-description-to-features');

View File

@ -1,2 +1,3 @@
'use strict';
module.exports = require('../scripts/migration-runner').create('003-add-parameters-template-to-strategies'); module.exports = require('../scripts/migration-runner').create('003-add-parameters-template-to-strategies');

View File

@ -1 +1,2 @@
'use strict';
module.exports = require('../scripts/migration-runner').create('004-insert-default-strategy'); module.exports = require('../scripts/migration-runner').create('004-insert-default-strategy');

View File

@ -1 +1,2 @@
'use strict';
module.exports = require('../scripts/migration-runner').create('004-insert-default-strategy-event'); module.exports = require('../scripts/migration-runner').create('004-insert-default-strategy-event');

View File

@ -1 +1,2 @@
'use strict';
module.exports = require('../scripts/migration-runner').create('005-archived-flag-to-features'); module.exports = require('../scripts/migration-runner').create('005-archived-flag-to-features');

View File

@ -1 +1,2 @@
'use strict';
module.exports = require('../scripts/migration-runner').create('006-rename-eventtype'); module.exports = require('../scripts/migration-runner').create('006-rename-eventtype');

View File

@ -1,14 +1,15 @@
var fs = require('fs'); 'use strict';
var util = require('util'); const fs = require('fs');
var path = require('path'); const util = require('util');
const path = require('path');
var runMigration = function(path, db, callback) { const runMigration = function(path, db, callback) {
db.runSql(fs.readFileSync(path, { encoding: 'utf8' }), callback); db.runSql(fs.readFileSync(path, { encoding: 'utf8' }), callback);
}; };
module.exports = { module.exports = {
create: function (name) { create(name) {
var format = path.resolve(__dirname, '../migrations/sql/%s.%s.sql'); const format = path.resolve(__dirname, '../migrations/sql/%s.%s.sql');
return { return {
up: runMigration.bind(null, util.format(format, name, 'up')), up: runMigration.bind(null, util.format(format, name, 'up')),

View File

@ -1,46 +1,47 @@
var logger = require('./lib/logger'); 'use strict';
var defaultDatabaseUri = process.env.DATABASE_URL; const logger = require('./lib/logger');
const defaultDatabaseUri = process.env.DATABASE_URL;
function start(options) { function start(options) {
options = options || {}; options = options || {};
var db = require('./lib/db/dbPool')(options.databaseUri || defaultDatabaseUri); const db = require('./lib/db/dbPool')(options.databaseUri || defaultDatabaseUri);
// Database dependecies (statefull) // Database dependecies (statefull)
var eventDb = require('./lib/db/event')(db); const eventDb = require('./lib/db/event')(db);
var EventStore = require('./lib/eventStore'); const EventStore = require('./lib/eventStore');
var eventStore = new EventStore(eventDb); const eventStore = new EventStore(eventDb);
var featureDb = require('./lib/db/feature')(db, eventStore); const featureDb = require('./lib/db/feature')(db, eventStore);
var strategyDb = require('./lib/db/strategy')(db, eventStore); const strategyDb = require('./lib/db/strategy')(db, eventStore);
var config = { const config = {
baseUriPath: process.env.BASE_URI_PATH || '', baseUriPath: process.env.BASE_URI_PATH || '',
port: process.env.HTTP_PORT || process.env.PORT || 4242, port: process.env.HTTP_PORT || process.env.PORT || 4242,
db: db, db,
eventDb: eventDb, eventDb,
eventStore: eventStore, eventStore,
featureDb: featureDb, featureDb,
strategyDb: strategyDb, strategyDb,
publicFolder: options.publicFolder publicFolder: options.publicFolder
}; };
var app = require('./app')(config); const app = require('./app')(config);
var server = app.listen(app.get('port'), function() { const server = app.listen(app.get('port'), () => {
logger.info('unleash started on ' + app.get('port')); logger.info(`unleash started on ${app.get('port')}`);
}); });
return { return {
app: app, app,
server: server, server,
config: config config
}; };
} }
process.on('uncaughtException', function(err) { process.on('uncaughtException', err => {
logger.error('Uncaught Exception:', err.message); logger.error('Uncaught Exception:', err.message);
logger.error(err.stack); logger.error(err.stack);
}); });
module.exports = { module.exports = {
start: start start
}; };

View File

@ -7,5 +7,5 @@ function getDatabaseUri() {
} }
} }
module.exports = { module.exports = {
getDatabaseUri: getDatabaseUri getDatabaseUri
}; };

View File

@ -1,15 +1,15 @@
'use strict'; 'use strict';
var request = require('./specHelper').request; const request = require('./specHelper').request;
describe('The event api', function () { describe('The event api', () => {
it('returns events', function (done) { it('returns events', done => {
request request
.get('/events') .get('/events')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200, done); .expect(200, done);
}); });
it('returns events given a name', function (done) { it('returns events given a name', done => {
request request
.get('/events/myname') .get('/events/myname')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)

View File

@ -1,32 +1,32 @@
'use strict'; 'use strict';
var eventDiffer = require('../lib/eventDiffer'); const eventDiffer = require('../lib/eventDiffer');
var eventType = require('../lib/eventType'); const eventType = require('../lib/eventType');
var assert = require('assert'); const assert = require('assert');
describe('eventDiffer', function () { describe('eventDiffer', () => {
it('fails if events include an unknown event type', function () { it('fails if events include an unknown event type', () => {
var events = [ const events = [
{ type: eventType.featureCreated, data: {} }, { type: eventType.featureCreated, data: {} },
{ type: 'unknown-type', data: {} } { type: 'unknown-type', data: {} }
]; ];
assert.throws(function () { assert.throws(() => {
eventDiffer.addDiffs(events); eventDiffer.addDiffs(events);
}); });
}); });
it('diffs a feature-update event', function () { it('diffs a feature-update event', () => {
var name = 'foo'; const name = 'foo';
var desc = 'bar'; const desc = 'bar';
var events = [ const events = [
{ {
type: eventType.featureUpdated, type: eventType.featureUpdated,
data: { name: name, description: desc, strategy: 'default', enabled: true, parameters: { value: 2 } } data: { name, description: desc, strategy: 'default', enabled: true, parameters: { value: 2 } }
}, },
{ {
type: eventType.featureCreated, type: eventType.featureCreated,
data: { name: name, description: desc, strategy: 'default', enabled: false, parameters: { value: 1 } } data: { name, description: desc, strategy: 'default', enabled: false, parameters: { value: 1 } }
} }
]; ];
@ -40,8 +40,8 @@ describe('eventDiffer', function () {
assert.strictEqual(events[1].diffs, null); assert.strictEqual(events[1].diffs, null);
}); });
it('diffs only against features with the same name', function () { it('diffs only against features with the same name', () => {
var events = [ const events = [
{ {
type: eventType.featureUpdated, type: eventType.featureUpdated,
data: { name: 'bar', description: 'desc', strategy: 'default', enabled: true, parameters: {} } data: { name: 'bar', description: 'desc', strategy: 'default', enabled: true, parameters: {} }
@ -68,8 +68,8 @@ describe('eventDiffer', function () {
assert.strictEqual(events[3].diffs, null); assert.strictEqual(events[3].diffs, null);
}); });
it('sets an empty array of diffs if nothing was changed', function () { it('sets an empty array of diffs if nothing was changed', () => {
var events = [ const events = [
{ {
type: eventType.featureUpdated, type: eventType.featureUpdated,
data: { name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {} } data: { name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {} }
@ -84,8 +84,8 @@ describe('eventDiffer', function () {
assert.deepEqual(events[0].diffs, []); assert.deepEqual(events[0].diffs, []);
}); });
it('sets diffs to null if there was nothing to diff against', function () { it('sets diffs to null if there was nothing to diff against', () => {
var events = [ const events = [
{ {
type: eventType.featureUpdated, type: eventType.featureUpdated,
data: { name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {} } data: { name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {} }

View File

@ -1,38 +1,38 @@
'use strict'; 'use strict';
var logger = require('../lib/logger'); const logger = require('../lib/logger');
var assert = require('assert'); const assert = require('assert');
var specHelper = require('./specHelper'); const specHelper = require('./specHelper');
var request = specHelper.request; const request = specHelper.request;
var stringify = function (o) { const stringify = function (o) {
return JSON.stringify(o, null, ' '); return JSON.stringify(o, null, ' ');
}; };
describe('The features api', function () { describe('The features api', () => {
beforeEach(function (done) { beforeEach(done => {
specHelper.db.resetAndSetup() specHelper.db.resetAndSetup()
.then(done.bind(null, null)) .then(done.bind(null, null))
.catch(done); .catch(done);
}); });
it('returns three feature toggles', function (done) { it('returns three feature toggles', done => {
request request
.get('/features') .get('/features')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
.end(function (err, res) { .end((err, res) => {
assert(res.body.features.length === 3, "expected 3 features, got " + stringify(res.body)); assert(res.body.features.length === 3, `expected 3 features, got ${stringify(res.body)}`);
done(); done();
}); });
}); });
it('gets a feature by name', function (done) { it('gets a feature by name', done => {
request request
.get('/features/featureX') .get('/features/featureX')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200, done); .expect(200, done);
}); });
it('cant get feature that dose not exist', function (done) { it('cant get feature that dose not exist', done => {
logger.setLevel('FATAL'); logger.setLevel('FATAL');
request request
.get('/features/myfeature') .get('/features/myfeature')
@ -40,7 +40,7 @@ describe('The features api', function () {
.expect(404, done); .expect(404, done);
}); });
it('creates new feature toggle', function (done) { it('creates new feature toggle', done => {
request request
.post('/features') .post('/features')
.send({ name: 'com.test.feature', enabled: false }) .send({ name: 'com.test.feature', enabled: false })
@ -48,24 +48,24 @@ describe('The features api', function () {
.expect(201, done); .expect(201, done);
}); });
it('creates new feature toggle with createdBy', function (done) { it('creates new feature toggle with createdBy', done => {
logger.setLevel('FATAL'); logger.setLevel('FATAL');
request request
.post('/features') .post('/features')
.send({ name: 'com.test.Username', enabled: false }) .send({ name: 'com.test.Username', enabled: false })
.set('Cookie', ['username=ivaosthu']) .set('Cookie', ['username=ivaosthu'])
.set('Content-Type', 'application/json') .set('Content-Type', 'application/json')
.end(function() { .end(() => {
request request
.get('/events') .get('/events')
.end(function (err, res) { .end((err, res) => {
assert.equal(res.body.events[0].createdBy, 'ivaosthu'); assert.equal(res.body.events[0].createdBy, 'ivaosthu');
done(); done();
}); });
}); });
}); });
it('require new feature toggle to have a name', function (done) { it('require new feature toggle to have a name', done => {
logger.setLevel('FATAL'); logger.setLevel('FATAL');
request request
.post('/features') .post('/features')
@ -74,7 +74,7 @@ describe('The features api', function () {
.expect(400, done); .expect(400, done);
}); });
it('can not change status of feature toggle that does not exist', function (done) { it('can not change status of feature toggle that does not exist', done => {
logger.setLevel('FATAL'); logger.setLevel('FATAL');
request request
.put('/features/should-not-exist') .put('/features/should-not-exist')
@ -83,7 +83,7 @@ describe('The features api', function () {
.expect(404, done); .expect(404, done);
}); });
it('can change status of feature toggle that does exist', function (done) { it('can change status of feature toggle that does exist', done => {
logger.setLevel('FATAL'); logger.setLevel('FATAL');
request request
.put('/features/featureY') .put('/features/featureY')
@ -92,19 +92,19 @@ describe('The features api', function () {
.expect(200, done); .expect(200, done);
}); });
it('archives a feature by name', function (done) { it('archives a feature by name', done => {
request request
.delete('/features/featureX') .delete('/features/featureX')
.expect(200, done); .expect(200, done);
}); });
it('can not archive unknown feature', function (done) { it('can not archive unknown feature', done => {
request request
.delete('/features/featureUnknown') .delete('/features/featureUnknown')
.expect(404, done); .expect(404, done);
}); });
it('refuses to create a feature with an existing name', function (done) { it('refuses to create a feature with an existing name', done => {
request request
.post('/features') .post('/features')
.send({ name: 'featureX' }) .send({ name: 'featureX' })

View File

@ -1,30 +1,30 @@
'use strict'; 'use strict';
var assert = require('assert'); const assert = require('assert');
var specHelper = require('./specHelper'); const specHelper = require('./specHelper');
var request = specHelper.request; const request = specHelper.request;
var stringify = function (o) { const stringify = function (o) {
return JSON.stringify(o, null, ' '); return JSON.stringify(o, null, ' ');
}; };
describe('The archive features api', function () { describe('The archive features api', () => {
beforeEach(function (done) { beforeEach(done => {
specHelper.db.resetAndSetup() specHelper.db.resetAndSetup()
.then(done.bind(null, null)) .then(done.bind(null, null))
.catch(done); .catch(done);
}); });
it('returns three archived toggles', function (done) { it('returns three archived toggles', done => {
request request
.get('/archive/features') .get('/archive/features')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
.end(function (err, res) { .end((err, res) => {
assert(res.body.features.length === 3, "expected 3 features, got " + stringify(res.body)); assert(res.body.features.length === 3, `expected 3 features, got ${stringify(res.body)}`);
done(); done();
}); });
}); });
it('revives a feature by name', function (done) { it('revives a feature by name', done => {
request request
.post('/archive/revive') .post('/archive/revive')
.send({ name: 'featureArchivedX' }) .send({ name: 'featureArchivedX' })
@ -32,7 +32,7 @@ describe('The archive features api', function () {
.expect(200, done); .expect(200, done);
}); });
it('must set name when reviving toggle', function (done) { it('must set name when reviving toggle', done => {
request request
.post('/archive/revive') .post('/archive/revive')
.expect(400, done); .expect(400, done);

View File

@ -1,10 +1,10 @@
'use strict'; 'use strict';
var specHelper = require('./specHelper'); const specHelper = require('./specHelper');
var request = specHelper.request; const request = specHelper.request;
describe('The routes', function () { describe('The routes', () => {
describe('healthcheck', function () { describe('healthcheck', () => {
it('returns health good', function (done) { it('returns health good', done => {
request.get('/health') request.get('/health')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)

View File

@ -1,23 +1,23 @@
'use strict'; 'use strict';
process.env.NODE_ENV = 'test'; process.env.NODE_ENV = 'test';
var Promise = require('bluebird'); const Promise = require('bluebird');
var request = require('supertest'); let request = require('supertest');
var databaseUri = require('./databaseConfig').getDatabaseUri(); const databaseUri = require('./databaseConfig').getDatabaseUri();
var knex = require('../lib/db/dbPool')(databaseUri); const knex = require('../lib/db/dbPool')(databaseUri);
var eventDb = require('../lib/db/event')(knex); const eventDb = require('../lib/db/event')(knex);
var EventStore = require('../lib/eventStore'); const EventStore = require('../lib/eventStore');
var eventStore = new EventStore(eventDb); const eventStore = new EventStore(eventDb);
var featureDb = require('../lib/db/feature')(knex, eventStore); const featureDb = require('../lib/db/feature')(knex, eventStore);
var strategyDb = require('../lib/db/strategy')(knex, eventStore); const strategyDb = require('../lib/db/strategy')(knex, eventStore);
var app = require('../app')({ const app = require('../app')({
baseUriPath: '', baseUriPath: '',
db: knex, db: knex,
eventDb: eventDb, eventDb,
eventStore: eventStore, eventStore,
featureDb: featureDb, featureDb,
strategyDb: strategyDb strategyDb
}); });
Promise.promisifyAll(request); Promise.promisifyAll(request);
@ -37,67 +37,63 @@ function createStrategies() {
emails: "String" emails: "String"
} }
} }
], function (strategy) { ], strategy => strategyDb._createStrategy(strategy));
return strategyDb._createStrategy(strategy);
});
} }
function createFeatures() { function createFeatures() {
return Promise.map([ return Promise.map([
{ {
"name": "featureX", name: "featureX",
"description": "the #1 feature", description: "the #1 feature",
"enabled": true, enabled: true,
"strategy": "default" strategy: "default"
}, },
{ {
"name": "featureY", name: "featureY",
"description": "soon to be the #1 feature", description: "soon to be the #1 feature",
"enabled": false, enabled: false,
"strategy": "baz", strategy: "baz",
"parameters": { parameters: {
"foo": "bar" foo: "bar"
} }
}, },
{ {
"name": "featureZ", name: "featureZ",
"description": "terrible feature", description: "terrible feature",
"enabled": true, enabled: true,
"strategy": "baz", strategy: "baz",
"parameters": { parameters: {
"foo": "rab" foo: "rab"
} }
}, },
{ {
"name": "featureArchivedX", name: "featureArchivedX",
"description": "the #1 feature", description: "the #1 feature",
"enabled": true, enabled: true,
"archived": true, archived: true,
"strategy": "default" strategy: "default"
}, },
{ {
"name": "featureArchivedY", name: "featureArchivedY",
"description": "soon to be the #1 feature", description: "soon to be the #1 feature",
"enabled": false, enabled: false,
"archived": true, archived: true,
"strategy": "baz", strategy: "baz",
"parameters": { parameters: {
"foo": "bar" foo: "bar"
} }
}, },
{ {
"name": "featureArchivedZ", name: "featureArchivedZ",
"description": "terrible feature", description: "terrible feature",
"enabled": true, enabled: true,
"archived": true, archived: true,
"strategy": "baz", strategy: "baz",
"parameters": { parameters: {
"foo": "rab" foo: "rab"
} }
} }
], function (feature) { ], feature => featureDb._createFeature(feature));
return featureDb._createFeature(feature);
});
} }
function destroyStrategies() { function destroyStrategies() {
@ -117,11 +113,11 @@ function setupDatabase() {
} }
module.exports = { module.exports = {
request: request, request,
db: { db: {
reset: resetDatabase, reset: resetDatabase,
setup: setupDatabase, setup: setupDatabase,
resetAndSetup: function () { resetAndSetup() {
return resetDatabase().then(setupDatabase); return resetDatabase().then(setupDatabase);
} }
} }

View File

@ -1,36 +1,36 @@
'use strict'; 'use strict';
var specHelper = require('./specHelper'); const specHelper = require('./specHelper');
var request = specHelper.request; const request = specHelper.request;
describe('The strategy api', function () { describe('The strategy api', () => {
beforeEach(function (done) { beforeEach(done => {
specHelper.db.resetAndSetup() specHelper.db.resetAndSetup()
.then(done.bind(null, null)) .then(done.bind(null, null))
.catch(done); .catch(done);
}); });
it('gets all strategies', function (done) { it('gets all strategies', done => {
request request
.get('/strategies') .get('/strategies')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200, done); .expect(200, done);
}); });
it('gets a strategy by name', function (done) { it('gets a strategy by name', done => {
request request
.get('/strategies/default') .get('/strategies/default')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200, done); .expect(200, done);
}); });
it('cant get a strategy by name that dose not exist', function (done) { it('cant get a strategy by name that dose not exist', done => {
request request
.get('/strategies/mystrategy') .get('/strategies/mystrategy')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(404, done); .expect(404, done);
}); });
it('creates a new strategy', function (done) { it('creates a new strategy', done => {
request request
.post('/strategies') .post('/strategies')
.send({ name: 'myCustomStrategy', description: 'Best strategy ever.' }) .send({ name: 'myCustomStrategy', description: 'Best strategy ever.' })
@ -38,7 +38,7 @@ describe('The strategy api', function () {
.expect(201, done); .expect(201, done);
}); });
it('requires new strategies to have a name', function (done) { it('requires new strategies to have a name', done => {
request request
.post('/strategies') .post('/strategies')
.send({ name: '' }) .send({ name: '' })
@ -46,7 +46,7 @@ describe('The strategy api', function () {
.expect(400, done); .expect(400, done);
}); });
it('refuses to create a strategy with an existing name', function (done) { it('refuses to create a strategy with an existing name', done => {
request request
.post('/strategies') .post('/strategies')
.send({ name: 'default' }) .send({ name: 'default' })
@ -54,13 +54,13 @@ describe('The strategy api', function () {
.expect(403, done); .expect(403, done);
}); });
it('deletes a new strategy', function (done) { it('deletes a new strategy', done => {
request request
.delete('/strategies/usersWithEmail') .delete('/strategies/usersWithEmail')
.expect(200, done); .expect(200, done);
}); });
it('can\'t delete a strategy that dose not exist', function(done) { it('can\'t delete a strategy that dose not exist', done => {
request request
.delete('/strategies/unknown') .delete('/strategies/unknown')
.expect(404, done); .expect(404, done);

View File

@ -1,3 +1,3 @@
'use strict'; 'use strict';
var unleash = require('unleash-server'); const unleash = require('unleash-server');
unleash.start({}); unleash.start({});

View File

@ -1,7 +1,9 @@
// preprocessor.js // preprocessor.js
var ReactTools = require('react-tools'); 'use strict';
const ReactTools = require('react-tools');
module.exports = { module.exports = {
process: function(src) { process(src) {
return ReactTools.transform(src); return ReactTools.transform(src);
} }
}; };

View File

@ -1,20 +1,21 @@
var React = require('react'); 'use strict';
var Router = require('react-router'); const React = require('react');
var Menu = require('./components/Menu'); const Router = require('react-router');
var ErrorMessages = require('./components/ErrorMessages'); const Menu = require('./components/Menu');
var initalizer = require('./stores/initalizer'); const ErrorMessages = require('./components/ErrorMessages');
var FeatureToggleStore = require('./stores/FeatureToggleStore'); const initalizer = require('./stores/initalizer');
var StrategyStore = require('./stores/StrategyStore'); const FeatureToggleStore = require('./stores/FeatureToggleStore');
var ArchiveStore = require('./stores/ArchivedToggleStore'); const StrategyStore = require('./stores/StrategyStore');
var Link = Router.Link; const ArchiveStore = require('./stores/ArchivedToggleStore');
var RouteHandler = Router.RouteHandler; const Link = Router.Link;
const RouteHandler = Router.RouteHandler;
var UnleashApp = React.createClass({ const UnleashApp = React.createClass({
contextTypes: { contextTypes: {
router: React.PropTypes.func router: React.PropTypes.func
}, },
getInitialState: function() { getInitialState() {
return { return {
features: FeatureToggleStore.getFeatureToggles(), features: FeatureToggleStore.getFeatureToggles(),
strategies: StrategyStore.getStrategies(), strategies: StrategyStore.getStrategies(),
@ -22,40 +23,40 @@ var UnleashApp = React.createClass({
}; };
}, },
onFeatureToggleChange: function() { onFeatureToggleChange() {
this.setState({ this.setState({
features: FeatureToggleStore.getFeatureToggles() features: FeatureToggleStore.getFeatureToggles()
}); });
}, },
onStrategiesChange: function() { onStrategiesChange() {
this.setState({ this.setState({
strategies: StrategyStore.getStrategies() strategies: StrategyStore.getStrategies()
}); });
}, },
onArchiveChange: function() { onArchiveChange() {
this.setState({ this.setState({
archivedFeatures: ArchiveStore.getArchivedToggles() archivedFeatures: ArchiveStore.getArchivedToggles()
}); });
}, },
componentDidMount: function() { componentDidMount() {
this.unsubscribeFS = FeatureToggleStore.listen(this.onFeatureToggleChange); this.unsubscribeFS = FeatureToggleStore.listen(this.onFeatureToggleChange);
this.unsubscribeSS = StrategyStore.listen(this.onStrategiesChange); this.unsubscribeSS = StrategyStore.listen(this.onStrategiesChange);
this.unsubscribeAS = ArchiveStore.listen(this.onArchiveChange); this.unsubscribeAS = ArchiveStore.listen(this.onArchiveChange);
}, },
componentWillUnmount: function() { componentWillUnmount() {
this.unsubscribeFS(); this.unsubscribeFS();
this.unsubscribeSS(); this.unsubscribeSS();
this.unsubscribeAS(); this.unsubscribeAS();
}, },
componentWillMount: function() { componentWillMount() {
initalizer(30); initalizer(30);
}, },
renderLink: function(id, label) { renderLink(id, label) {
return ( return (
<Link to={id} className="nav-element centerify" activeClassName="nav-active"> <Link to={id} className="nav-element centerify" activeClassName="nav-active">
<span className="topbar-nav-svg-caption caption showbydefault no-break">{label}</span> <span className="topbar-nav-svg-caption caption showbydefault no-break">{label}</span>
@ -63,7 +64,7 @@ var UnleashApp = React.createClass({
); );
}, },
render: function () { render() {
return ( return (
<div> <div>
<Menu> <Menu>

View File

@ -1,14 +1,16 @@
/** @jsx React.DOM */ /** @jsx React.DOM */
'use strict';
jest.dontMock("../../components/Menu"); jest.dontMock("../../components/Menu");
var Menu = require("../../components/Menu"); const Menu = require("../../components/Menu");
var React = require("react/addons"); const React = require("react/addons");
var TestUtils = React.addons.TestUtils; const TestUtils = React.addons.TestUtils;
describe('Menu test', function () { describe('Menu test', () => {
it('should include unleash in menu', function () { it('should include unleash in menu', () => {
var Compononent = TestUtils .renderIntoDocument(<Menu />); const Compononent = TestUtils .renderIntoDocument(<Menu />);
expect(Compononent.getDOMNode().textContent).toMatch('unleash'); expect(Compononent.getDOMNode().textContent).toMatch('unleash');
}); });
}); });

View File

@ -1,16 +1,17 @@
'use strict';
jest.dontMock("../../../components/feature/ArchiveFeatureComponent"); jest.dontMock("../../../components/feature/ArchiveFeatureComponent");
jest.mock("../../../stores/FeatureToggleActions"); jest.mock("../../../stores/FeatureToggleActions");
jest.autoMockOff(); jest.autoMockOff();
var React = require("react/addons"); const React = require("react/addons");
var TestUtils = React.addons.TestUtils; const TestUtils = React.addons.TestUtils;
var FeatureArchive = require("../../../components/feature/ArchiveFeatureComponent"); const FeatureArchive = require("../../../components/feature/ArchiveFeatureComponent");
var FeatureActions = require("../../../stores/FeatureToggleActions"); const FeatureActions = require("../../../stores/FeatureToggleActions");
describe("FeatureForm", function () { describe("FeatureForm", () => {
var Component; let Component;
beforeEach(function() { beforeEach(() => {
var archivedToggles = [ const archivedToggles = [
{ name: "featureX" }, { name: "featureX" },
{ name: "featureY" } { name: "featureY" }
]; ];
@ -19,18 +20,18 @@ describe("FeatureForm", function () {
<FeatureArchive archivedFeatures={archivedToggles} />); <FeatureArchive archivedFeatures={archivedToggles} />);
}); });
afterEach(function() { afterEach(() => {
React.unmountComponentAtNode(document.body); React.unmountComponentAtNode(document.body);
}); });
it("should render two archived features", function() { it("should render two archived features", () => {
var rows = Component.getDOMNode().querySelectorAll("tbody tr"); const rows = Component.getDOMNode().querySelectorAll("tbody tr");
expect(rows.length).toEqual(2); expect(rows.length).toEqual(2);
}); });
it("should revive archived feature toggle", function() { it("should revive archived feature toggle", () => {
var button = Component.getDOMNode().querySelector("tbody button"); const button = Component.getDOMNode().querySelector("tbody button");
TestUtils.Simulate.click(button); TestUtils.Simulate.click(button);
jest.runAllTimers(); jest.runAllTimers();

View File

@ -1,33 +1,34 @@
'use strict';
jest.dontMock("../../../components/feature/FeatureForm"); jest.dontMock("../../../components/feature/FeatureForm");
var React = require("react/addons"); const React = require("react/addons");
var TestUtils = React.addons.TestUtils; const TestUtils = React.addons.TestUtils;
var FeatureForm = require("../../../components/feature/FeatureForm"); const FeatureForm = require("../../../components/feature/FeatureForm");
describe("FeatureForm", function () { describe("FeatureForm", () => {
var Component; let Component;
var strategies = [ const strategies = [
{ name: "default" } { name: "default" }
]; ];
afterEach(function() { afterEach(() => {
React.unmountComponentAtNode(document.body); React.unmountComponentAtNode(document.body);
}); });
describe("new", function () { describe("new", () => {
it("should render empty form", function() { it("should render empty form", () => {
Component = TestUtils .renderIntoDocument(<FeatureForm strategies={strategies} />); Component = TestUtils .renderIntoDocument(<FeatureForm strategies={strategies} />);
var name = Component.getDOMNode().querySelectorAll("input"); const name = Component.getDOMNode().querySelectorAll("input");
expect(name[0].value).toEqual(""); expect(name[0].value).toEqual("");
}); });
}); });
describe("edit", function () { describe("edit", () => {
var feature = { name: "Test", strategy: "unknown" }; const feature = { name: "Test", strategy: "unknown" };
it("should show unknown strategy as default", function () { it("should show unknown strategy as default", () => {
Component = TestUtils .renderIntoDocument(<FeatureForm feature={feature} strategies={strategies} />); Component = TestUtils .renderIntoDocument(<FeatureForm feature={feature} strategies={strategies} />);
var strategySelect = Component.getDOMNode().querySelector("select"); const strategySelect = Component.getDOMNode().querySelector("select");
expect(strategySelect.value).toEqual("default"); expect(strategySelect.value).toEqual("default");
}); });
}); });

View File

@ -1,56 +1,58 @@
'use strict';
jest.dontMock("../../../components/feature/FeatureList"); jest.dontMock("../../../components/feature/FeatureList");
jest.dontMock("../../../components/feature/Feature"); jest.dontMock("../../../components/feature/Feature");
var React = require("react/addons"); const React = require("react/addons");
var TestUtils = React.addons.TestUtils; const TestUtils = React.addons.TestUtils;
var FeatureList = require("../../../components/feature/FeatureList"); const FeatureList = require("../../../components/feature/FeatureList");
describe("FeatureList", function () { describe("FeatureList", () => {
var Component, features; let Component;
let features;
beforeEach(function() { beforeEach(() => {
features = [ features = [
{ name: "featureX", strategy: "other" }, { name: "featureX", strategy: "other" },
{ name: "group.featureY", strategy: "default" } { name: "group.featureY", strategy: "default" }
]; ];
var strategies=[ const strategies=[
{ name: "default" } { name: "default" }
]; ];
Component = TestUtils .renderIntoDocument(<FeatureList features={features} strategies={strategies} />); Component = TestUtils .renderIntoDocument(<FeatureList features={features} strategies={strategies} />);
}); });
afterEach(function() { afterEach(() => {
React.unmountComponentAtNode(document.body); React.unmountComponentAtNode(document.body);
}); });
it("should render all features", function() { it("should render all features", () => {
var features = Component.getDOMNode().querySelectorAll(".feature"); const features = Component.getDOMNode().querySelectorAll(".feature");
expect(features.length).toEqual(2); expect(features.length).toEqual(2);
}); });
it("should filter list of features", function() { it("should filter list of features", () => {
var filterNode = Component.refs.filter.getDOMNode(); const filterNode = Component.refs.filter.getDOMNode();
TestUtils.Simulate.change(filterNode, { target: { value: "group" } }); TestUtils.Simulate.change(filterNode, { target: { value: "group" } });
var features = Component.getDOMNode().querySelectorAll(".feature"); const features = Component.getDOMNode().querySelectorAll(".feature");
expect(features.length).toEqual(1); expect(features.length).toEqual(1);
}); });
it("should filter list of features ignoring case", function() { it("should filter list of features ignoring case", () => {
var filterNode = Component.refs.filter.getDOMNode(); const filterNode = Component.refs.filter.getDOMNode();
TestUtils.Simulate.change(filterNode, { target: { value: "GROUP" } }); TestUtils.Simulate.change(filterNode, { target: { value: "GROUP" } });
var features = Component.getDOMNode().querySelectorAll(".feature"); const features = Component.getDOMNode().querySelectorAll(".feature");
expect(features.length).toEqual(1); expect(features.length).toEqual(1);
expect(features[0].textContent).toMatch("group"); expect(features[0].textContent).toMatch("group");
}); });
it("should filter list of features by strategy name", function() { it("should filter list of features by strategy name", () => {
var searchString = "other"; const searchString = "other";
var filterNode = Component.refs.filter.getDOMNode(); const filterNode = Component.refs.filter.getDOMNode();
TestUtils.Simulate.change(filterNode, { target: { value: searchString } }); TestUtils.Simulate.change(filterNode, { target: { value: searchString } });
var features = Component.getDOMNode().querySelectorAll(".feature"); const features = Component.getDOMNode().querySelectorAll(".feature");
expect(features.length).toEqual(1); expect(features.length).toEqual(1);
expect(features[0].textContent).toMatch(searchString); expect(features[0].textContent).toMatch(searchString);
}); });

View File

@ -1,11 +1,14 @@
'use strict';
jest.autoMockOff(); jest.autoMockOff();
jest.dontMock('../../stores/FeatureToggleActions'); jest.dontMock('../../stores/FeatureToggleActions');
jest.dontMock('../../stores/FeatureToggleStore'); jest.dontMock('../../stores/FeatureToggleStore');
describe('FeatureToggleStore', function() { describe('FeatureToggleStore', () => {
var Actions, Store, toggles; let Actions;
let Store;
let toggles;
beforeEach(function() { beforeEach(() => {
Actions = require('../../stores/FeatureToggleActions'); Actions = require('../../stores/FeatureToggleActions');
Store = require('../../stores/FeatureToggleStore'); Store = require('../../stores/FeatureToggleStore');
toggles = [ toggles = [
@ -13,11 +16,11 @@ describe('FeatureToggleStore', function() {
]; ];
}); });
it('should be an empty store', function() { it('should be an empty store', () => {
expect(Store.getFeatureToggles().length).toBe(0); expect(Store.getFeatureToggles().length).toBe(0);
}); });
it('should inititialize the store', function() { it('should inititialize the store', () => {
Actions.init.completed(toggles); Actions.init.completed(toggles);
jest.runAllTimers(); jest.runAllTimers();
@ -25,10 +28,10 @@ describe('FeatureToggleStore', function() {
expect(Store.getFeatureToggles()[0].name).toEqual("app.feature"); expect(Store.getFeatureToggles()[0].name).toEqual("app.feature");
}); });
it('should add a another toggle', function() { it('should add a another toggle', () => {
Actions.init.completed(toggles); Actions.init.completed(toggles);
var newToggle = { name: "app.featureB", enabled: true, strategy: "default" }; const newToggle = { name: "app.featureB", enabled: true, strategy: "default" };
Actions.create.completed(newToggle); Actions.create.completed(newToggle);
@ -37,7 +40,7 @@ describe('FeatureToggleStore', function() {
expect(Store.getFeatureToggles()[1].name).toEqual("app.featureB"); expect(Store.getFeatureToggles()[1].name).toEqual("app.featureB");
}); });
it('should archive toggle', function() { it('should archive toggle', () => {
Actions.init.completed(toggles); Actions.init.completed(toggles);
Actions.archive.completed(toggles[0]); Actions.archive.completed(toggles[0]);
@ -46,7 +49,7 @@ describe('FeatureToggleStore', function() {
expect(Store.getFeatureToggles().length).toBe(0); expect(Store.getFeatureToggles().length).toBe(0);
}); });
it('should keep toggles in sorted order', function() { it('should keep toggles in sorted order', () => {
Actions.init.completed([ Actions.init.completed([
{ name: "A" }, { name: "A" },
{ name: "B" }, { name: "B" },
@ -61,9 +64,9 @@ describe('FeatureToggleStore', function() {
expect(Store.getFeatureToggles()[3].name).toEqual("C"); expect(Store.getFeatureToggles()[3].name).toEqual("C");
}); });
it('should update toggle', function() { it('should update toggle', () => {
Actions.init.completed(toggles); Actions.init.completed(toggles);
var toggle = toggles[0]; const toggle = toggles[0];
toggle.enabled = false; toggle.enabled = false;
Actions.update.completed(toggle); Actions.update.completed(toggle);

View File

@ -1,10 +1,11 @@
var React = require('react'); 'use strict';
var Router = require('react-router'); const React = require('react');
var UserStore = require('./stores/UserStore'); const Router = require('react-router');
var routes = require('./routes'); const UserStore = require('./stores/UserStore');
const routes = require('./routes');
UserStore.init(); UserStore.init();
Router.run(routes, function (Handler) { Router.run(routes, Handler => {
React.render(<Handler/>, document.getElementById('content')); React.render(<Handler/>, document.getElementById('content'));
}); });

View File

@ -1,34 +1,35 @@
var React = require('react'); 'use strict';
var Ui = require('./ErrorMessages.ui'); const React = require('react');
var ErrorStore = require('../stores/ErrorStore'); const Ui = require('./ErrorMessages.ui');
var ErrorActions = require('../stores/ErrorActions'); const ErrorStore = require('../stores/ErrorStore');
const ErrorActions = require('../stores/ErrorActions');
var ErrorMessages = React.createClass({ const ErrorMessages = React.createClass({
getInitialState: function() { getInitialState() {
return { return {
errors: ErrorStore.getErrors() errors: ErrorStore.getErrors()
}; };
}, },
onStoreChange: function() { onStoreChange() {
this.setState({ this.setState({
errors: ErrorStore.getErrors() errors: ErrorStore.getErrors()
}); });
}, },
componentDidMount: function() { componentDidMount() {
this.unsubscribe = ErrorStore.listen(this.onStoreChange); this.unsubscribe = ErrorStore.listen(this.onStoreChange);
}, },
componentWillUnmount: function() { componentWillUnmount() {
this.unsubscribe(); this.unsubscribe();
}, },
onClearErrors: function() { onClearErrors() {
ErrorActions.clear(); ErrorActions.clear();
}, },
render: function() { render() {
return ( return (
<Ui errors={this.state.errors} onClearErrors={this.onClearErrors}></Ui> <Ui errors={this.state.errors} onClearErrors={this.onClearErrors}></Ui>
); );

View File

@ -1,14 +1,13 @@
var React = require('react'); 'use strict';
const React = require('react');
var ErrorMessages = React.createClass({ const ErrorMessages = React.createClass({
render: function() { render() {
if (!this.props.errors.length) { if (!this.props.errors.length) {
return <div/>; return <div/>;
} }
var errorNodes = this.props.errors.map(function(e, i) { const errorNodes = this.props.errors.map((e, i) => <li key={e + i} className="largetext">{e}</li>);
return (<li key={e + i} className="largetext">{e}</li>);
});
return ( return (
<div className="container"> <div className="container">

View File

@ -1,8 +1,9 @@
var React = require('react'); 'use strict';
var User = require('./User'); const React = require('react');
const User = require('./User');
var Menu = React.createClass({ const Menu = React.createClass({
render: function() { return ( render() { return (
<div className="topbar mbl"> <div className="topbar mbl">
<div className="container"> <div className="container">
<div className="page"> <div className="page">

View File

@ -1,14 +1,15 @@
var React = require('react'); 'use strict';
var UserStore = require('../stores/UserStore'); const React = require('react');
const UserStore = require('../stores/UserStore');
var User = React.createClass({ const User = React.createClass({
onSave: function() { onSave() {
var value = this.refs.username.getDOMNode().value.trim(); const value = this.refs.username.getDOMNode().value.trim();
UserStore.set(value); UserStore.set(value);
}, },
render: function() { render() {
return ( return (
<div className="r-pvm"> <div className="r-pvm">
<input type="text" placeholder="username" <input type="text" placeholder="username"

View File

@ -1,13 +1,14 @@
var React = require("react"); 'use strict';
var FeatureActions = require('../../stores/FeatureToggleActions'); const React = require("react");
const FeatureActions = require('../../stores/FeatureToggleActions');
var ArchiveFeatureComponent = React.createClass({ const ArchiveFeatureComponent = React.createClass({
onRevive: function(item) { onRevive(item) {
FeatureActions.revive.triggerPromise(item); FeatureActions.revive.triggerPromise(item);
}, },
render: function () { render() {
return ( return (
<div> <div>
<h1>Archived Feature Toggles</h1> <h1>Archived Feature Toggles</h1>
@ -27,7 +28,7 @@ var ArchiveFeatureComponent = React.createClass({
); );
}, },
renderArchivedItem: function(f) { renderArchivedItem(f) {
return ( return (
<tr key={f.name}> <tr key={f.name}>
<td> <td>

View File

@ -1,10 +1,11 @@
var React = require('react'); 'use strict';
var FeatureForm = require('./FeatureForm'); const React = require('react');
var LogEntryList = require('../log/LogEntryList'); const FeatureForm = require('./FeatureForm');
var eventStore = require('../../stores/EventStore'); const LogEntryList = require('../log/LogEntryList');
const eventStore = require('../../stores/EventStore');
var Feature = React.createClass({ const Feature = React.createClass({
getInitialState: function() { getInitialState() {
return { return {
editMode: false, editMode: false,
showHistory: false, showHistory: false,
@ -12,33 +13,33 @@ var Feature = React.createClass({
}; };
}, },
handleEventsResponse: function(response) { handleEventsResponse(response) {
this.setState({events: response}); this.setState({events: response});
}, },
toggleHistory: function() { toggleHistory() {
eventStore.getEventsByName(this.props.feature.name).then(this.handleEventsResponse); eventStore.getEventsByName(this.props.feature.name).then(this.handleEventsResponse);
this.setState({showHistory: !this.state.showHistory}); this.setState({showHistory: !this.state.showHistory});
}, },
toggleEditMode: function() { toggleEditMode() {
this.setState({editMode: !this.state.editMode}); this.setState({editMode: !this.state.editMode});
}, },
saveFeature: function(feature) { saveFeature(feature) {
this.props.onChange(feature); this.props.onChange(feature);
this.toggleEditMode(); this.toggleEditMode();
}, },
archiveFeature: function() { archiveFeature() {
if (window.confirm("Are you sure you want to delete " + this.props.feature.name + "?")) { if (window.confirm(`Are you sure you want to delete ${this.props.feature.name}?`)) {
this.props.onArchive(this.props.feature); this.props.onArchive(this.props.feature);
} }
}, },
renderEditMode: function() { renderEditMode() {
return ( return (
<tr> <tr>
<td colSpan="4" className="pan man no-border"> <td colSpan="4" className="pan man no-border">
@ -53,7 +54,7 @@ var Feature = React.createClass({
}, },
render: function() { render() {
return ( return (
<tbody className="feature"> <tbody className="feature">
<tr className={this.state.editMode ? "edit bg-lilac-xlt" : ""}> <tr className={this.state.editMode ? "edit bg-lilac-xlt" : ""}>
@ -107,11 +108,11 @@ var Feature = React.createClass({
); );
}, },
renderEmptyRow: function() { renderEmptyRow() {
return (<tr />); return (<tr />);
}, },
renderHistory: function() { renderHistory() {
return (<tr> return (<tr>
<td colSpan="4" className="no-border"> <td colSpan="4" className="no-border">
<LogEntryList events={this.state.events} /> <LogEntryList events={this.state.events} />

View File

@ -1,8 +1,9 @@
var React = require('react'); 'use strict';
var TextInput = require('../form/TextInput'); const React = require('react');
const TextInput = require('../form/TextInput');
var FeatureForm = React.createClass({ const FeatureForm = React.createClass({
getInitialState: function() { getInitialState() {
return { return {
strategyOptions: this.props.strategies, strategyOptions: this.props.strategies,
requiredParams: [], requiredParams: [],
@ -10,18 +11,18 @@ var FeatureForm = React.createClass({
}; };
}, },
componentWillMount: function() { componentWillMount() {
if(this.props.feature) { if(this.props.feature) {
this.setSelectedStrategy(this.props.feature.strategy); this.setSelectedStrategy(this.props.feature.strategy);
} }
}, },
onStrategyChange: function(e) { onStrategyChange(e) {
this.setSelectedStrategy(e.target.value); this.setSelectedStrategy(e.target.value);
this.setState({currentStrategy: e.target.value}); this.setState({currentStrategy: e.target.value});
}, },
getParameterValue: function(name) { getParameterValue(name) {
if(this.props.feature && this.props.feature.parameters) { if(this.props.feature && this.props.feature.parameters) {
return this.props.feature.parameters[name]; return this.props.feature.parameters[name];
} else { } else {
@ -29,10 +30,8 @@ var FeatureForm = React.createClass({
} }
}, },
setSelectedStrategy: function(name) { setSelectedStrategy(name) {
var selectedStrategy = this.props.strategies.filter(function(strategy) { const selectedStrategy = this.props.strategies.filter(strategy => strategy.name === name)[0];
return strategy.name === name;
})[0];
if(selectedStrategy) { if(selectedStrategy) {
this.setStrategyParams(selectedStrategy); this.setStrategyParams(selectedStrategy);
@ -44,23 +43,23 @@ var FeatureForm = React.createClass({
} }
}, },
setStrategyParams: function(strategy) { setStrategyParams(strategy) {
var requiredParams = []; const requiredParams = [];
var key; let key;
for(key in strategy.parametersTemplate) { for(key in strategy.parametersTemplate) {
requiredParams.push({name: key, value: this.getParameterValue(key)}); requiredParams.push({name: key, value: this.getParameterValue(key)});
} }
this.setState({requiredParams: requiredParams}); this.setState({requiredParams});
}, },
render: function() { render() {
var feature = this.props.feature || { const feature = this.props.feature || {
name: '', name: '',
strategy: 'default', strategy: 'default',
enabled: false enabled: false
}; };
var idPrefix = this.props.feature ? this.props.feature.name : 'new'; const idPrefix = this.props.feature ? this.props.feature.name : 'new';
return ( return (
<div className="bg-lilac-xlt r-pam"> <div className="bg-lilac-xlt r-pam">
@ -70,7 +69,7 @@ var FeatureForm = React.createClass({
{this.props.feature ? "" : <legend>Create new toggle</legend>} {this.props.feature ? "" : <legend>Create new toggle</legend>}
<TextInput <TextInput
id={idPrefix + "-name"} id={`${idPrefix}-name`}
name="name" name="name"
label="Name" label="Name"
value={feature.name} value={feature.name}
@ -79,7 +78,7 @@ var FeatureForm = React.createClass({
placeholder="Toggle name" /> placeholder="Toggle name" />
<TextInput <TextInput
id={idPrefix + "-description"} id={`${idPrefix}-description`}
name="description" name="description"
label="Description" label="Description"
value={feature.description} value={feature.description}
@ -87,9 +86,9 @@ var FeatureForm = React.createClass({
placeholder="Enter description" /> placeholder="Enter description" />
<div className="formelement"> <div className="formelement">
<label htmlFor={idPrefix + "-strategy"}>Strategy</label> <label htmlFor={`${idPrefix}-strategy`}>Strategy</label>
<div className="input select"> <div className="input select">
<select id={idPrefix + "-strategy"} <select id={`${idPrefix}-strategy`}
ref="strategy" ref="strategy"
value={this.state.currentStrategy} value={this.state.currentStrategy}
onChange={this.onStrategyChange}> onChange={this.onStrategyChange}>
@ -102,11 +101,11 @@ var FeatureForm = React.createClass({
<div className="input"> <div className="input">
<ul className="inputs-list"> <ul className="inputs-list">
<li> <li>
<input id={idPrefix + "-active"} <input id={`${idPrefix}-active`}
ref="enabled" ref="enabled"
type="checkbox" type="checkbox"
defaultChecked={feature.enabled} /> defaultChecked={feature.enabled} />
<label htmlFor={idPrefix + "-active"}> <label htmlFor={`${idPrefix}-active`}>
Active Active
</label> </label>
</li> </li>
@ -127,32 +126,26 @@ var FeatureForm = React.createClass({
); );
}, },
renderStrategyOptions: function() { renderStrategyOptions() {
return this.props.strategies.map(function(strategy) { return this.props.strategies.map(strategy => <option key={strategy.name} value={strategy.name}>
return ( {strategy.name}
<option key={strategy.name} value={strategy.name}> </option>);
{strategy.name}
</option>
);
}.bind(this));
}, },
renderStrategyProperties: function() { renderStrategyProperties() {
return this.state.requiredParams.map(function(param) { return this.state.requiredParams.map(param => <TextInput
return <TextInput id={`param-${param.name}`}
id={"param-" + param.name} key={`param-${param.name}`}
key={"param-" + param.name} name={param.name}
name={param.name} label={param.name}
label={param.name} ref={param.name}
ref={param.name} value={param.value} />);
value={param.value} />;
});
}, },
saveFeature: function(e) { saveFeature(e) {
e.preventDefault(); e.preventDefault();
var feature = { const feature = {
name: this.refs.name.getValue(), name: this.refs.name.getValue(),
description: this.refs.description.getValue(), description: this.refs.description.getValue(),
strategy: this.state.currentStrategy, strategy: this.state.currentStrategy,
@ -163,16 +156,16 @@ var FeatureForm = React.createClass({
this.props.onSubmit(feature); this.props.onSubmit(feature);
}, },
cancelFeature: function(e) { cancelFeature(e) {
e.preventDefault(); e.preventDefault();
this.props.onCancel(); this.props.onCancel();
}, },
getParameters: function() { getParameters() {
var parameters = {}; const parameters = {};
this.state.requiredParams.forEach(function(param) { this.state.requiredParams.forEach(param => {
parameters[param.name] = this.refs[param.name].getValue(); parameters[param.name] = this.refs[param.name].getValue();
}.bind(this)); });
return parameters; return parameters;
} }
}); });

View File

@ -1,39 +1,38 @@
var React = require('react'); 'use strict';
var Feature = require('./Feature'); const React = require('react');
const Feature = require('./Feature');
var noop = function() {}; const noop = function() {};
var FeatureList = React.createClass({ const FeatureList = React.createClass({
propTypes: { propTypes: {
features: React.PropTypes.array.isRequired, features: React.PropTypes.array.isRequired,
strategies: React.PropTypes.array.isRequired strategies: React.PropTypes.array.isRequired
}, },
getDefaultProps: function() { getDefaultProps() {
return { return {
onFeatureChanged: noop, onFeatureChanged: noop,
onFeatureArchive: noop onFeatureArchive: noop
}; };
}, },
getInitialState: function() { getInitialState() {
return { return {
filter: undefined filter: undefined
}; };
}, },
onFilterChange: function(e) { onFilterChange(e) {
e.preventDefault(); e.preventDefault();
this.setState({filter: e.target.value.trim()}); this.setState({filter: e.target.value.trim()});
}, },
filteredFeatures: function() { filteredFeatures() {
if(this.state.filter) { if(this.state.filter) {
var regex = new RegExp(this.state.filter, "i"); const regex = new RegExp(this.state.filter, "i");
return this.props.features.filter(function(item) { return this.props.features.filter(item => regex.test(item.name) || regex.test(item.strategy));
return regex.test(item.name) || regex.test(item.strategy);
}.bind(this));
} else { } else {
return this.props.features; return this.props.features;
@ -41,18 +40,14 @@ var FeatureList = React.createClass({
}, },
render: function() { render() {
var featureNodes = this.filteredFeatures().map(function(feature) { const featureNodes = this.filteredFeatures().map(feature => <Feature
return ( key={feature.name}
<Feature feature={feature}
key={feature.name} onChange={this.props.onFeatureChanged}
feature={feature} onArchive={this.props.onFeatureArchive}
onChange={this.props.onFeatureChanged} strategies={this.props.strategies}
onArchive={this.props.onFeatureArchive} />);
strategies={this.props.strategies}
/>
);
}.bind(this));
return ( return (
<div className=''> <div className=''>

View File

@ -1,39 +1,40 @@
var React = require('react'); 'use strict';
var FeatureList = require('./FeatureList'); const React = require('react');
var FeatureForm = require('./FeatureForm'); const FeatureList = require('./FeatureList');
var FeatureActions = require('../../stores/FeatureToggleActions'); const FeatureForm = require('./FeatureForm');
var ErrorActions = require('../../stores/ErrorActions'); const FeatureActions = require('../../stores/FeatureToggleActions');
const ErrorActions = require('../../stores/ErrorActions');
var FeatureTogglesComponent = React.createClass({ const FeatureTogglesComponent = React.createClass({
getInitialState: function() { getInitialState() {
return { return {
createView: false createView: false
}; };
}, },
updateFeature: function (feature) { updateFeature(feature) {
FeatureActions.update.triggerPromise(feature); FeatureActions.update.triggerPromise(feature);
}, },
archiveFeature: function (feature) { archiveFeature(feature) {
FeatureActions.archive.triggerPromise(feature); FeatureActions.archive.triggerPromise(feature);
}, },
createFeature: function (feature) { createFeature(feature) {
FeatureActions.create.triggerPromise(feature) FeatureActions.create.triggerPromise(feature)
.then(this.cancelNewFeature); .then(this.cancelNewFeature);
}, },
newFeature: function() { newFeature() {
this.setState({createView: true}); this.setState({createView: true});
}, },
cancelNewFeature: function () { cancelNewFeature() {
this.setState({createView: false}); this.setState({createView: false});
ErrorActions.clear(); ErrorActions.clear();
}, },
render: function() { render() {
return ( return (
<div> <div>
@ -53,14 +54,14 @@ var FeatureTogglesComponent = React.createClass({
); );
}, },
renderCreateView: function() { renderCreateView() {
return <FeatureForm return <FeatureForm
onCancel={this.cancelNewFeature} onCancel={this.cancelNewFeature}
onSubmit={this.createFeature} onSubmit={this.createFeature}
strategies={this.props.strategies} />; strategies={this.props.strategies} />;
}, },
renderCreateButton: function() { renderCreateButton() {
return <button className="mal" onClick={this.newFeature}>Create feature toggle</button>; return <button className="mal" onClick={this.newFeature}>Create feature toggle</button>;
} }
}); });

View File

@ -1,6 +1,7 @@
var React = require('react'); 'use strict';
const React = require('react');
var TextInput = React.createClass({ const TextInput = React.createClass({
propTypes: { propTypes: {
name: React.PropTypes.string.isRequired, name: React.PropTypes.string.isRequired,
label: React.PropTypes.string.isRequired, label: React.PropTypes.string.isRequired,
@ -10,22 +11,22 @@ var TextInput = React.createClass({
required: React.PropTypes.bool required: React.PropTypes.bool
}, },
getDefaultProps: function() { getDefaultProps() {
return { return {
required: false required: false
}; };
}, },
getInitialState: function() { getInitialState() {
return {}; return {};
}, },
getValue: function() { getValue() {
return this.refs.input.getDOMNode().value.trim(); return this.refs.input.getDOMNode().value.trim();
}, },
render: function() { render() {
return ( return (
<div className="formelement required"> <div className="formelement required">
<label htmlFor={this.props.id} className="t4">{this.props.label}</label> <label htmlFor={this.props.id} className="t4">{this.props.label}</label>

View File

@ -1,27 +1,28 @@
var React = require('react'); 'use strict';
var LogEntryList = require('./LogEntryList'); const React = require('react');
var eventStore = require('../../stores/EventStore'); const LogEntryList = require('./LogEntryList');
var ErrorActions = require('../../stores/ErrorActions'); const eventStore = require('../../stores/EventStore');
const ErrorActions = require('../../stores/ErrorActions');
var LogEntriesComponent = React.createClass({ const LogEntriesComponent = React.createClass({
getInitialState: function() { getInitialState() {
return { return {
createView: false, createView: false,
events: [] events: []
}; };
}, },
componentDidMount: function () { componentDidMount() {
eventStore.getEvents().then(function(res) { eventStore.getEvents().then(res => {
this.setState({events: res.events}); this.setState({events: res.events});
}.bind(this), this.initError); }, this.initError);
}, },
initError: function() { initError() {
ErrorActions.error("Could not load events from server"); ErrorActions.error("Could not load events from server");
}, },
render: function() { render() {
return ( return (
<div> <div>
<h1>Log</h1> <h1>Log</h1>

View File

@ -1,27 +1,28 @@
var React = require('react'); 'use strict';
var moment = require('moment'); const React = require('react');
const moment = require('moment');
var DIFF_PREFIXES = { const DIFF_PREFIXES = {
A: ' ', A: ' ',
E: ' ', E: ' ',
D: '-', D: '-',
N: '+' N: '+'
}; };
var SPADEN_CLASS = { const SPADEN_CLASS = {
A: 'blue', // array edited A: 'blue', // array edited
E: 'blue', // edited E: 'blue', // edited
D: 'negative', // deleted D: 'negative', // deleted
N: 'positive', // added N: 'positive', // added
}; };
var LogEntry = React.createClass({ const LogEntry = React.createClass({
propTypes: { propTypes: {
event: React.PropTypes.object.isRequired event: React.PropTypes.object.isRequired
}, },
render: function() { render() {
var date = moment(this.props.event.createdAt); const date = moment(this.props.event.createdAt);
return ( return (
<tr> <tr>
@ -40,19 +41,19 @@ var LogEntry = React.createClass({
); );
}, },
renderFullEventData: function() { renderFullEventData() {
var localEventData = JSON.parse(JSON.stringify(this.props.event.data)); const localEventData = JSON.parse(JSON.stringify(this.props.event.data));
delete localEventData.description; delete localEventData.description;
delete localEventData.name; delete localEventData.name;
var prettyPrinted = JSON.stringify(localEventData, null, 2); const prettyPrinted = JSON.stringify(localEventData, null, 2);
return (<code className='JSON smalltext man'>{prettyPrinted}</code>); return (<code className='JSON smalltext man'>{prettyPrinted}</code>);
}, },
renderEventDiff: function() { renderEventDiff() {
if (!this.props.showFullEvents && this.props.event.diffs) { if (!this.props.showFullEvents && this.props.event.diffs) {
var changes = this.props.event.diffs.map(this.buildDiff); const changes = this.props.event.diffs.map(this.buildDiff);
return ( return (
<code className='smalltext man'>{changes.length === 0 ? '(no changes)' : changes}</code> <code className='smalltext man'>{changes.length === 0 ? '(no changes)' : changes}</code>
); );
@ -61,9 +62,9 @@ var LogEntry = React.createClass({
} }
}, },
buildDiff: function(diff, idx) { buildDiff(diff, idx) {
var change; let change;
var key = diff.path.join('.'); const key = diff.path.join('.');
if (diff.lhs !== undefined && diff.rhs !== undefined) { if (diff.lhs !== undefined && diff.rhs !== undefined) {
change = ( change = (
@ -73,8 +74,8 @@ var LogEntry = React.createClass({
</div> </div>
); );
} else { } else {
var spadenClass = SPADEN_CLASS[diff.kind]; const spadenClass = SPADEN_CLASS[diff.kind];
var prefix = DIFF_PREFIXES[diff.kind]; const prefix = DIFF_PREFIXES[diff.kind];
change = (<div className={spadenClass}>{prefix} {key}: {JSON.stringify(diff.rhs)}</div>); change = (<div className={spadenClass}>{prefix} {key}: {JSON.stringify(diff.rhs)}</div>);
} }

View File

@ -1,21 +1,20 @@
var React = require('react'), 'use strict';
LogEntry = require('./LogEntry'); const React = require('react');
const LogEntry = require('./LogEntry');
var LogEntryList = React.createClass({ const LogEntryList = React.createClass({
propTypes: { propTypes: {
events: React.PropTypes.array.isRequired events: React.PropTypes.array.isRequired
}, },
getInitialState: function() { getInitialState() {
return { return {
showFullEvents: false showFullEvents: false
}; };
}, },
render: function() { render() {
var logEntryNodes = this.props.events.map(function(event) { const logEntryNodes = this.props.events.map(event => <LogEntry event={event} key={event.id} showFullEvents={this.state.showFullEvents} />);
return <LogEntry event={event} key={event.id} showFullEvents={this.state.showFullEvents} />;
}.bind(this));
return ( return (
<div> <div>
@ -48,7 +47,7 @@ var LogEntryList = React.createClass({
); );
}, },
toggleFullEvents: function() { toggleFullEvents() {
this.setState({showFullEvents: !this.state.showFullEvents}); this.setState({showFullEvents: !this.state.showFullEvents});
} }

View File

@ -1,33 +1,34 @@
var React = require('react'); 'use strict';
var StrategyList = require('./StrategyList'); const React = require('react');
var StrategyForm = require('./StrategyForm'); const StrategyList = require('./StrategyList');
var StrategyActions = require('../../stores/StrategyActions'); const StrategyForm = require('./StrategyForm');
const StrategyActions = require('../../stores/StrategyActions');
var StrategiesComponent = React.createClass({ const StrategiesComponent = React.createClass({
getInitialState: function() { getInitialState() {
return { return {
createView: false createView: false
}; };
}, },
onNewStrategy: function() { onNewStrategy() {
this.setState({createView: true}); this.setState({createView: true});
}, },
onCancelNewStrategy: function() { onCancelNewStrategy() {
this.setState({createView: false}); this.setState({createView: false});
}, },
onSave: function(strategy) { onSave(strategy) {
StrategyActions.create.triggerPromise(strategy) StrategyActions.create.triggerPromise(strategy)
.then(this.onCancelNewStrategy); .then(this.onCancelNewStrategy);
}, },
onRemove: function(strategy) { onRemove(strategy) {
StrategyActions.remove.triggerPromise(strategy); StrategyActions.remove.triggerPromise(strategy);
}, },
render: function() { render() {
return ( return (
<div> <div>
<h1>Activation Strategies</h1> <h1>Activation Strategies</h1>
@ -41,7 +42,7 @@ var StrategiesComponent = React.createClass({
); );
}, },
renderCreateView: function() { renderCreateView() {
return ( return (
<StrategyForm <StrategyForm
onCancelNewStrategy={this.onCancelNewStrategy} onCancelNewStrategy={this.onCancelNewStrategy}
@ -49,7 +50,7 @@ var StrategiesComponent = React.createClass({
/>); />);
}, },
renderCreateButton: function() { renderCreateButton() {
return ( return (
<button className="mal" onClick={this.onNewStrategy}> <button className="mal" onClick={this.onNewStrategy}>
Create strategy Create strategy
@ -58,4 +59,4 @@ var StrategiesComponent = React.createClass({
} }
}); });
module.exports = StrategiesComponent; module.exports = StrategiesComponent;

View File

@ -1,19 +1,19 @@
var React = require('react'); 'use strict';
const React = require('react');
var Strategy = React.createClass({ const Strategy = React.createClass({
propTypes: { propTypes: {
strategy: React.PropTypes.object.isRequired strategy: React.PropTypes.object.isRequired
}, },
onRemove: function(event) { onRemove(event) {
event.preventDefault(); event.preventDefault();
if (window.confirm("Are you sure you want to delete strategy '"+ if (window.confirm(`Are you sure you want to delete strategy '${this.props.strategy.name}'?`)) {
this.props.strategy.name+"'?")) {
this.props.onRemove(this.props.strategy); this.props.onRemove(this.props.strategy);
} }
}, },
render: function() { render() {
return ( return (
<div className="line mal"> <div className="line mal">
<div className="unit"> <div className="unit">

View File

@ -1,32 +1,33 @@
var React = require('react'); 'use strict';
var TextInput = require('../form/TextInput'); const React = require('react');
const TextInput = require('../form/TextInput');
var StrategyForm = React.createClass({ const StrategyForm = React.createClass({
getDefaultProps: function() { getDefaultProps() {
return { return {
maxParams: 4 maxParams: 4
}; };
}, },
getInitialState: function() { getInitialState() {
return { return {
parameters: [] parameters: []
}; };
}, },
onSubmit: function(event) { onSubmit(event) {
event.preventDefault(); event.preventDefault();
var strategy = {}; const strategy = {};
strategy.name = this.refs.name.getValue(); strategy.name = this.refs.name.getValue();
strategy.description = this.refs.description.getValue(); strategy.description = this.refs.description.getValue();
strategy.parametersTemplate = {}; strategy.parametersTemplate = {};
var that = this; const that = this;
this.state.parameters.forEach(function(parameter) { this.state.parameters.forEach(parameter => {
var name = that.refs[parameter.name].getDOMNode().value.trim(); const name = that.refs[parameter.name].getDOMNode().value.trim();
if(name) { if(name) {
strategy.parametersTemplate[name] = "string"; strategy.parametersTemplate[name] = "string";
} }
@ -35,27 +36,27 @@ var StrategyForm = React.createClass({
this.props.onSave(strategy); this.props.onSave(strategy);
}, },
onCancel: function(event) { onCancel(event) {
event.preventDefault(); event.preventDefault();
this.props.onCancelNewStrategy(); this.props.onCancelNewStrategy();
}, },
onAddParam: function(event) { onAddParam(event) {
event.preventDefault(); event.preventDefault();
var id = this.state.parameters.length + 1; const id = this.state.parameters.length + 1;
var params = this.state.parameters.concat([{id:id, name: "param_" + id, label: "Parameter " +id}]); const params = this.state.parameters.concat([{id, name: `param_${id}`, label: `Parameter ${id}`}]);
this.setState({parameters: params}); this.setState({parameters: params});
}, },
onRemoveParam: function(event) { onRemoveParam(event) {
event.preventDefault(); event.preventDefault();
var params = this.state.parameters.slice(0, -1); const params = this.state.parameters.slice(0, -1);
this.setState({parameters: params}); this.setState({parameters: params});
}, },
render: function() { render() {
return ( return (
<div className="line r-pam bg-lilac-xlt"> <div className="line r-pam bg-lilac-xlt">
<div className="unit r-size1of2"> <div className="unit r-size1of2">
@ -92,41 +93,37 @@ var StrategyForm = React.createClass({
); );
}, },
renderParameters: function() { renderParameters() {
return this.state.parameters.map(function(param) { return this.state.parameters.map(param => <div className="formelement" key={param.name}>
return ( <label className="t4">{param.label}</label>
<div className="formelement" key={param.name}> <div className="input">
<label className="t4">{param.label}</label> <div className="line">
<div className="input">
<div className="line">
<div className="unit size2of3"> <div className="unit size2of3">
<input <input
type="text" type="text"
name={param.name} name={param.name}
ref={param.name} ref={param.name}
placeholder="Parameter name" placeholder="Parameter name"
/> />
</div> </div>
<div className="unit size1of3"> <div className="unit size1of3">
<select defaultValue="string"> <select defaultValue="string">
<option value="string">string</option> <option value="string">string</option>
</select> </select>
</div>
</div>
</div> </div>
</div> </div>
); </div>
}); </div>);
}, },
renderAddLink: function() { renderAddLink() {
if(this.state.parameters.length < this.props.maxParams) { if(this.state.parameters.length < this.props.maxParams) {
return <a href="#add" onClick={this.onAddParam}>+ Add required parameter</a>; return <a href="#add" onClick={this.onAddParam}>+ Add required parameter</a>;
} }
}, },
renderRemoveLink: function() { renderRemoveLink() {
if(this.state.parameters.length > 0) { if(this.state.parameters.length > 0) {
return ( return (
<div className="formelement mtn"> <div className="formelement mtn">

View File

@ -1,15 +1,14 @@
var React = require('react'), 'use strict';
Strategy = require('./Strategy'); const React = require('react');
const Strategy = require('./Strategy');
var StrategyList = React.createClass({ const StrategyList = React.createClass({
propTypes: { propTypes: {
strategies: React.PropTypes.array.isRequired strategies: React.PropTypes.array.isRequired
}, },
render: function() { render() {
var strategyNodes = this.props.strategies.map(function(strategy) { const strategyNodes = this.props.strategies.map(strategy => <Strategy strategy={strategy} key={strategy.name} onRemove={this.props.onRemove} />);
return <Strategy strategy={strategy} key={strategy.name} onRemove={this.props.onRemove} />;
}.bind(this));
return ( return (
<div>{strategyNodes}</div> <div>{strategyNodes}</div>
); );

View File

@ -1,14 +1,15 @@
var React = require('react'); 'use strict';
var Router = require('react-router'); const React = require('react');
var UnleashApp = require('./UnleashApp'); const Router = require('react-router');
var LogEntriesComponent = require('./components/log/LogEntriesComponent'); const UnleashApp = require('./UnleashApp');
var FeatureTogglesComponent = require('./components/feature/FeatureTogglesComponent'); const LogEntriesComponent = require('./components/log/LogEntriesComponent');
var StrategiesComponent = require('./components/strategy/StrategiesComponent'); const FeatureTogglesComponent = require('./components/feature/FeatureTogglesComponent');
var ArchiveFeatureComponent = require('./components/feature/ArchiveFeatureComponent'); const StrategiesComponent = require('./components/strategy/StrategiesComponent');
var DefaultRoute = Router.DefaultRoute; const ArchiveFeatureComponent = require('./components/feature/ArchiveFeatureComponent');
var Route = Router.Route; const DefaultRoute = Router.DefaultRoute;
const Route = Router.Route;
var routes = ( const routes = (
<Route name="app" path="/" handler={UnleashApp}> <Route name="app" path="/" handler={UnleashApp}>
<Route name="strategies" handler={StrategiesComponent}/> <Route name="strategies" handler={StrategiesComponent}/>
<Route name="log" handler={LogEntriesComponent}/> <Route name="log" handler={LogEntriesComponent}/>

View File

@ -1,45 +1,44 @@
var Reflux = require('reflux'); 'use strict';
var FeatureActions = require('./FeatureToggleActions'); const Reflux = require('reflux');
var filter = require('lodash/collection/filter'); const FeatureActions = require('./FeatureToggleActions');
var sortBy = require('lodash/collection/sortBy'); const filter = require('lodash/collection/filter');
const sortBy = require('lodash/collection/sortBy');
var _archivedToggles = []; let _archivedToggles = [];
// Creates a DataStore // Creates a DataStore
var FeatureStore = Reflux.createStore({ const FeatureStore = Reflux.createStore({
// Initial setup // Initial setup
init: function() { init() {
this.listenTo(FeatureActions.initArchive.completed, this.onInit); this.listenTo(FeatureActions.initArchive.completed, this.onInit);
this.listenTo(FeatureActions.archive.completed, this.onArchive); this.listenTo(FeatureActions.archive.completed, this.onArchive);
this.listenTo(FeatureActions.revive.completed, this.onRevive); this.listenTo(FeatureActions.revive.completed, this.onRevive);
}, },
onInit: function(toggles) { onInit(toggles) {
_archivedToggles = toggles; _archivedToggles = toggles;
this.trigger(); this.trigger();
}, },
onArchive: function(feature) { onArchive(feature) {
var toggles = _archivedToggles.concat([feature]); const toggles = _archivedToggles.concat([feature]);
_archivedToggles = sortBy(toggles, 'name'); _archivedToggles = sortBy(toggles, 'name');
this.trigger(); this.trigger();
}, },
onRevive: function(item) { onRevive(item) {
var newStore = filter(_archivedToggles, function(f) { const newStore = filter(_archivedToggles, f => f.name !== item.name);
return f.name !== item.name;
});
_archivedToggles = sortBy(newStore, 'name'); _archivedToggles = sortBy(newStore, 'name');
this.trigger(); this.trigger();
}, },
getArchivedToggles: function() { getArchivedToggles() {
return _archivedToggles; return _archivedToggles;
}, },
initStore: function(archivedToggles) { initStore(archivedToggles) {
_archivedToggles = archivedToggles; _archivedToggles = archivedToggles;
} }
}); });

View File

@ -1,6 +1,7 @@
var Reflux = require('reflux'); 'use strict';
const Reflux = require('reflux');
var ErrorActions = Reflux.createActions([ const ErrorActions = Reflux.createActions([
"clear", "clear",
"error" "error"
]); ]);

View File

@ -1,11 +1,12 @@
var Reflux = require('reflux'); 'use strict';
var FeatureActions = require('./FeatureToggleActions'); const Reflux = require('reflux');
var ErrorActions = require('./ErrorActions'); const FeatureActions = require('./FeatureToggleActions');
const ErrorActions = require('./ErrorActions');
// Creates a DataStore // Creates a DataStore
var FeatureStore = Reflux.createStore({ const FeatureStore = Reflux.createStore({
// Initial setup // Initial setup
init: function() { init() {
this.listenTo(FeatureActions.create.failed, this.onError); this.listenTo(FeatureActions.create.failed, this.onError);
this.listenTo(FeatureActions.init.failed, this.onError); this.listenTo(FeatureActions.init.failed, this.onError);
this.listenTo(FeatureActions.update.failed, this.onError); this.listenTo(FeatureActions.update.failed, this.onError);
@ -16,12 +17,12 @@ var FeatureStore = Reflux.createStore({
this.errors = []; this.errors = [];
}, },
onError: function (error) { onError(error) {
if (this.isClientError(error)) { if (this.isClientError(error)) {
var errors = JSON.parse(error.responseText); const errors = JSON.parse(error.responseText);
errors.forEach(function(e) { errors.forEach(e => {
this.addError(e.msg); this.addError(e.msg);
}.bind(this)); });
} else if (error.status === 0) { } else if (error.status === 0) {
this.addError("server unreachable"); this.addError("server unreachable");
} else { } else {
@ -29,13 +30,13 @@ var FeatureStore = Reflux.createStore({
} }
}, },
onClear: function() { onClear() {
this.errors = []; this.errors = [];
this.trigger([]); this.trigger([]);
}, },
addError: function(msg) { addError(msg) {
var errors = this.errors; const errors = this.errors;
if (errors[errors.length - 1] !== msg) { if (errors[errors.length - 1] !== msg) {
errors.push(msg); errors.push(msg);
this.errors = errors; this.errors = errors;
@ -43,7 +44,7 @@ var FeatureStore = Reflux.createStore({
} }
}, },
isClientError: function(error) { isClientError(error) {
try { try {
return error.status >= 400 && return error.status >= 400 &&
error.status < 500 && error.status < 500 &&
@ -60,7 +61,7 @@ var FeatureStore = Reflux.createStore({
return false; return false;
}, },
getErrors: function() { getErrors() {
return this.errors; return this.errors;
} }
}); });

View File

@ -1,9 +1,10 @@
var reqwest = require('reqwest'); 'use strict';
const reqwest = require('reqwest');
var TYPE = 'json'; const TYPE = 'json';
var EventStore = { const EventStore = {
getEvents: function () { getEvents() {
return reqwest({ return reqwest({
url: 'events', url: 'events',
method: 'get', method: 'get',
@ -11,9 +12,9 @@ var EventStore = {
}); });
}, },
getEventsByName: function (name) { getEventsByName(name) {
return reqwest({ return reqwest({
url: 'events/' + name, url: `events/${name}`,
method: 'get', method: 'get',
type: TYPE type: TYPE
}); });

View File

@ -1,73 +1,74 @@
var Reflux = require("reflux"); 'use strict';
var Server = require('./FeatureToggleServerFacade'); const Reflux = require("reflux");
const Server = require('./FeatureToggleServerFacade');
var FeatureToggleActions = Reflux.createActions({ const FeatureToggleActions = Reflux.createActions({
'init': { asyncResult: true }, init: { asyncResult: true },
'initArchive': { asyncResult: true }, initArchive: { asyncResult: true },
'create': { asyncResult: true }, create: { asyncResult: true },
'update': { asyncResult: true }, update: { asyncResult: true },
'archive': { asyncResult: true }, archive: { asyncResult: true },
'revive': { asyncResult: true } revive: { asyncResult: true }
}); });
FeatureToggleActions.init.listen(function() { FeatureToggleActions.init.listen(function() {
Server.getFeatures(function(error, features) { Server.getFeatures((error, features) => {
if (error) { if (error) {
this.failed(error); this.failed(error);
} else { } else {
this.completed(features); this.completed(features);
} }
}.bind(this)); });
}); });
FeatureToggleActions.initArchive.listen(function() { FeatureToggleActions.initArchive.listen(function() {
Server.getArchivedFeatures(function(error, archivedToggles) { Server.getArchivedFeatures((error, archivedToggles) => {
if (error) { if (error) {
this.failed(error); this.failed(error);
} else { } else {
this.completed(archivedToggles); this.completed(archivedToggles);
} }
}.bind(this)); });
}); });
FeatureToggleActions.create.listen(function(feature) { FeatureToggleActions.create.listen(function(feature) {
Server.createFeature(feature, function(error) { Server.createFeature(feature, error => {
if (error) { if (error) {
this.failed(error); this.failed(error);
} else { } else {
this.completed(feature); this.completed(feature);
} }
}.bind(this)); });
}); });
FeatureToggleActions.update.listen(function(feature) { FeatureToggleActions.update.listen(function(feature) {
Server.updateFeature(feature, function(error) { Server.updateFeature(feature, error => {
if (error) { if (error) {
this.failed(error); this.failed(error);
} else { } else {
this.completed(feature); this.completed(feature);
} }
}.bind(this)); });
}); });
FeatureToggleActions.archive.listen(function(feature) { FeatureToggleActions.archive.listen(function(feature) {
Server.archiveFeature(feature, function(error) { Server.archiveFeature(feature, error => {
if (error) { if (error) {
this.failed(error); this.failed(error);
} else { } else {
this.completed(feature); this.completed(feature);
} }
}.bind(this)); });
}); });
FeatureToggleActions.revive.listen(function(feature) { FeatureToggleActions.revive.listen(function(feature) {
Server.reviveFeature(feature, function(error) { Server.reviveFeature(feature, error => {
if (error) { if (error) {
this.failed(error); this.failed(error);
} else { } else {
this.completed(feature); this.completed(feature);
} }
}.bind(this)); });
}); });
module.exports = FeatureToggleActions; module.exports = FeatureToggleActions;

View File

@ -1,94 +1,95 @@
var reqwest = require('reqwest'); 'use strict';
const reqwest = require('reqwest');
var TYPE = 'json'; const TYPE = 'json';
var CONTENT_TYPE = 'application/json'; const CONTENT_TYPE = 'application/json';
var FeatureToggleServerFacade = { const FeatureToggleServerFacade = {
updateFeature: function (feature, cb) { updateFeature(feature, cb) {
reqwest({ reqwest({
url: 'features/' + feature.name, url: `features/${feature.name}`,
method: 'put', method: 'put',
type: TYPE, type: TYPE,
contentType: CONTENT_TYPE, contentType: CONTENT_TYPE,
data: JSON.stringify(feature), data: JSON.stringify(feature),
error: function(error) { error(error) {
cb(error); cb(error);
}, },
success: function() { success() {
cb(); cb();
} }
}); });
}, },
createFeature: function (feature, cb) { createFeature(feature, cb) {
reqwest({ reqwest({
url: 'features', url: 'features',
method: 'post', method: 'post',
type: TYPE, type: TYPE,
contentType: CONTENT_TYPE, contentType: CONTENT_TYPE,
data: JSON.stringify(feature), data: JSON.stringify(feature),
error: function(error) { error(error) {
cb(error); cb(error);
}, },
success: function() { success() {
cb(); cb();
} }
}); });
}, },
archiveFeature: function(feature, cb) { archiveFeature(feature, cb) {
reqwest({ reqwest({
url: 'features/' + feature.name, url: `features/${feature.name}`,
method: 'delete', method: 'delete',
type: TYPE, type: TYPE,
error: function(error) { error(error) {
cb(error); cb(error);
}, },
success: function() { success() {
cb(); cb();
} }
}); });
}, },
getFeatures: function(cb) { getFeatures(cb) {
reqwest({ reqwest({
url: 'features', url: 'features',
method: 'get', method: 'get',
type: TYPE, type: TYPE,
error: function(error) { error(error) {
cb(error); cb(error);
}, },
success: function(data) { success(data) {
cb(null, data.features); cb(null, data.features);
} }
}); });
}, },
getArchivedFeatures: function(cb) { getArchivedFeatures(cb) {
reqwest({ reqwest({
url: 'archive/features', url: 'archive/features',
method: 'get', method: 'get',
type: TYPE, type: TYPE,
error: function(error) { error(error) {
cb(error); cb(error);
}, },
success: function(data) { success(data) {
cb(null, data.features); cb(null, data.features);
} }
}); });
}, },
reviveFeature: function (feature, cb) { reviveFeature(feature, cb) {
reqwest({ reqwest({
url: 'archive/revive', url: 'archive/revive',
method: 'post', method: 'post',
type: TYPE, type: TYPE,
contentType: CONTENT_TYPE, contentType: CONTENT_TYPE,
data: JSON.stringify(feature), data: JSON.stringify(feature),
error: function(error) { error(error) {
cb(error); cb(error);
}, },
success: function() { success() {
cb(); cb();
} }
}); });

View File

@ -1,15 +1,16 @@
var Reflux = require('reflux'); 'use strict';
var FeatureActions = require('./FeatureToggleActions'); const Reflux = require('reflux');
var filter = require('lodash/collection/filter'); const FeatureActions = require('./FeatureToggleActions');
var sortBy = require('lodash/collection/sortBy'); const filter = require('lodash/collection/filter');
var findIndex = require('lodash/array/findIndex'); const sortBy = require('lodash/collection/sortBy');
const findIndex = require('lodash/array/findIndex');
var _featureToggles = []; let _featureToggles = [];
var FeatureStore = Reflux.createStore({ const FeatureStore = Reflux.createStore({
// Initial setup // Initial setup
init: function() { init() {
this.listenTo(FeatureActions.init.completed, this.setToggles); this.listenTo(FeatureActions.init.completed, this.setToggles);
this.listenTo(FeatureActions.create.completed, this.onCreate); this.listenTo(FeatureActions.create.completed, this.onCreate);
this.listenTo(FeatureActions.update.completed, this.onUpdate); this.listenTo(FeatureActions.update.completed, this.onUpdate);
@ -17,39 +18,37 @@ var FeatureStore = Reflux.createStore({
this.listenTo(FeatureActions.revive.completed, this.onRevive); this.listenTo(FeatureActions.revive.completed, this.onRevive);
}, },
onCreate: function(feature) { onCreate(feature) {
this.setToggles([feature].concat(_featureToggles)); this.setToggles([feature].concat(_featureToggles));
}, },
setToggles: function(toggles) { setToggles(toggles) {
_featureToggles = sortBy(toggles, 'name'); _featureToggles = sortBy(toggles, 'name');
this.trigger(); this.trigger();
}, },
onUpdate: function(feature) { onUpdate(feature) {
var idx = findIndex(_featureToggles, 'name', feature.name); const idx = findIndex(_featureToggles, 'name', feature.name);
_featureToggles[idx] = feature; _featureToggles[idx] = feature;
this.trigger(); this.trigger();
}, },
onArchive: function(feature) { onArchive(feature) {
var featureToggles = filter(_featureToggles, function(item) { const featureToggles = filter(_featureToggles, item => item.name !== feature.name);
return item.name !== feature.name;
});
this.setToggles(featureToggles); this.setToggles(featureToggles);
this.trigger(); this.trigger();
}, },
onRevive: function(item) { onRevive(item) {
this.setToggles(_featureToggles.concat([item])); this.setToggles(_featureToggles.concat([item]));
this.trigger(); this.trigger();
}, },
getFeatureToggles: function() { getFeatureToggles() {
return _featureToggles; return _featureToggles;
}, },
initStore: function(toggles) { initStore(toggles) {
_featureToggles = toggles; _featureToggles = toggles;
} }
}); });

View File

@ -1,48 +1,49 @@
var reqwest = require('reqwest'); 'use strict';
const reqwest = require('reqwest');
var TYPE = 'json'; const TYPE = 'json';
var CONTENT_TYPE = 'application/json'; const CONTENT_TYPE = 'application/json';
var StrategyAPI = { const StrategyAPI = {
createStrategy: function (strategy, cb) { createStrategy(strategy, cb) {
reqwest({ reqwest({
url: 'strategies', url: 'strategies',
method: 'post', method: 'post',
type: TYPE, type: TYPE,
contentType: CONTENT_TYPE, contentType: CONTENT_TYPE,
data: JSON.stringify(strategy), data: JSON.stringify(strategy),
error: function(error) { error(error) {
cb(error); cb(error);
}, },
success: function() { success() {
cb(null, strategy); cb(null, strategy);
} }
}); });
}, },
removeStrategy: function (strategy, cb) { removeStrategy(strategy, cb) {
reqwest({ reqwest({
url: 'strategies/'+strategy.name, url: `strategies/${strategy.name}`,
method: 'delete', method: 'delete',
type: TYPE, type: TYPE,
error: function(error) { error(error) {
cb(error); cb(error);
}, },
success: function() { success() {
cb(null, strategy); cb(null, strategy);
} }
}); });
}, },
getStrategies: function (cb) { getStrategies(cb) {
reqwest({ reqwest({
url: 'strategies', url: 'strategies',
method: 'get', method: 'get',
type: TYPE, type: TYPE,
error: function(error) { error(error) {
cb(error); cb(error);
}, },
success: function(data) { success(data) {
cb(null, data.strategies); cb(null, data.strategies);
} }
}); });

View File

@ -1,40 +1,41 @@
var Reflux = require("reflux"); 'use strict';
var StrategyAPI = require('./StrategyAPI'); const Reflux = require("reflux");
const StrategyAPI = require('./StrategyAPI');
var StrategyActions = Reflux.createActions({ const StrategyActions = Reflux.createActions({
'init': { asyncResult: true }, init: { asyncResult: true },
'create': { asyncResult: true }, create: { asyncResult: true },
'remove': { asyncResult: true }, remove: { asyncResult: true },
}); });
StrategyActions.init.listen(function() { StrategyActions.init.listen(function() {
StrategyAPI.getStrategies(function(err, strategies) { StrategyAPI.getStrategies((err, strategies) => {
if (err) { if (err) {
this.failed(err); this.failed(err);
} else { } else {
this.completed(strategies); this.completed(strategies);
} }
}.bind(this)); });
}); });
StrategyActions.create.listen(function(feature) { StrategyActions.create.listen(function(feature) {
StrategyAPI.createStrategy(feature, function(err) { StrategyAPI.createStrategy(feature, err => {
if (err) { if (err) {
this.failed(err); this.failed(err);
} else { } else {
this.completed(feature); this.completed(feature);
} }
}.bind(this)); });
}); });
StrategyActions.remove.listen(function(feature) { StrategyActions.remove.listen(function(feature) {
StrategyAPI.removeStrategy(feature, function(err) { StrategyAPI.removeStrategy(feature, err => {
if (err) { if (err) {
this.failed(err); this.failed(err);
} else { } else {
this.completed(feature); this.completed(feature);
} }
}.bind(this)); });
}); });
module.exports = StrategyActions; module.exports = StrategyActions;

View File

@ -1,40 +1,39 @@
var Reflux = require('reflux'); 'use strict';
var StrategyActions = require('./StrategyActions'); const Reflux = require('reflux');
var filter = require('lodash/collection/filter'); const StrategyActions = require('./StrategyActions');
const filter = require('lodash/collection/filter');
var _strategies = []; let _strategies = [];
// Creates a DataStore // Creates a DataStore
var StrategyStore = Reflux.createStore({ const StrategyStore = Reflux.createStore({
// Initial setup // Initial setup
init: function() { init() {
this.listenTo(StrategyActions.init.completed, this.setStrategies); this.listenTo(StrategyActions.init.completed, this.setStrategies);
this.listenTo(StrategyActions.create.completed, this.onCreate); this.listenTo(StrategyActions.create.completed, this.onCreate);
this.listenTo(StrategyActions.remove.completed, this.onRemove); this.listenTo(StrategyActions.remove.completed, this.onRemove);
}, },
onCreate: function(strategy) { onCreate(strategy) {
this.setStrategies(_strategies.concat([strategy])); this.setStrategies(_strategies.concat([strategy]));
}, },
onRemove: function(strategy) { onRemove(strategy) {
var strategies = filter(_strategies, function(item) { const strategies = filter(_strategies, item => item.name !== strategy.name);
return item.name !== strategy.name;
});
this.setStrategies(strategies); this.setStrategies(strategies);
}, },
setStrategies: function(strategies) { setStrategies(strategies) {
_strategies = strategies; _strategies = strategies;
this.trigger(_strategies); this.trigger(_strategies);
}, },
getStrategies: function() { getStrategies() {
return _strategies; return _strategies;
}, },
initStore: function(strategies) { initStore(strategies) {
_strategies = strategies; _strategies = strategies;
} }
}); });

View File

@ -1,11 +1,12 @@
var _username; 'use strict';
let _username;
// Ref: http://stackoverflow.com/questions/10730362/get-cookie-by-name // Ref: http://stackoverflow.com/questions/10730362/get-cookie-by-name
function readCookie(name) { function readCookie(name) {
var nameEQ = name + "="; const nameEQ = `${name}=`;
var ca = document.cookie.split(';'); const ca = document.cookie.split(';');
for (var i=0;i < ca.length;i++) { for (let i=0;i < ca.length;i++) {
var c = ca[i]; let c = ca[i];
while (c.charAt(0)==' ') { while (c.charAt(0)==' ') {
c = c.substring(1, c.length); c = c.substring(1, c.length);
} }
@ -16,17 +17,17 @@ function readCookie(name) {
return null; return null;
} }
var UserStore = { const UserStore = {
init: function init() { init() {
_username = readCookie("username"); _username = readCookie("username");
}, },
set: function set(username) { set(username) {
_username=username; _username=username;
document.cookie="username="+_username+"; expires=Thu, 18 Dec 2099 12:00:00 UTC"; document.cookie=`username=${_username}; expires=Thu, 18 Dec 2099 12:00:00 UTC`;
}, },
get: function get() { get() {
return _username; return _username;
} }
}; };

View File

@ -1,8 +1,9 @@
var FeatureToggleActions = require('./FeatureToggleActions'); 'use strict';
var StrategyActions = require('./StrategyActions'); const FeatureToggleActions = require('./FeatureToggleActions');
var Timer = require('../utils/Timer'); const StrategyActions = require('./StrategyActions');
const Timer = require('../utils/Timer');
var _timer; let _timer;
function load() { function load() {
FeatureToggleActions.init.triggerPromise(); FeatureToggleActions.init.triggerPromise();
@ -11,7 +12,7 @@ function load() {
} }
module.exports = function(pollInterval) { module.exports = function(pollInterval) {
var intervall = pollInterval || 30; const intervall = pollInterval || 30;
_timer = new Timer(load, intervall*1000); _timer = new Timer(load, intervall*1000);
_timer.start(); _timer.start();
}; };

View File

@ -1,4 +1,5 @@
var Timer = function(cb, interval) { 'use strict';
const Timer = function(cb, interval) {
this.cb = cb; this.cb = cb;
this.interval = interval; this.interval = interval;
this.timerId = null; this.timerId = null;

View File

@ -1,8 +1,10 @@
// docs: http://webpack.github.io/docs/configuration.html // docs: http://webpack.github.io/docs/configuration.html
var path = require('path'); 'use strict';
var root = path.normalize(path.join(__dirname, '.'));
var jsroot = path.join(path.join(root, 'public'), 'js'); const path = require('path');
const root = path.normalize(path.join(__dirname, '.'));
const jsroot = path.join(path.join(root, 'public'), 'js');
module.exports = { module.exports = {