mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-04 01:18:20 +02:00
Merge pull request #92 from finn-no/improvement/reflux
Improvement/reflux
This commit is contained in:
commit
2425a91be7
@ -40,10 +40,11 @@ function getFeature(name) {
|
||||
|
||||
function getArchivedFeatures() {
|
||||
return knex
|
||||
.select(['name', 'description'])
|
||||
.select(FEATURE_COLUMNS)
|
||||
.from('features')
|
||||
.where({archived: 1})
|
||||
.orderBy('name', 'asc');
|
||||
.orderBy('name', 'asc')
|
||||
.map(rowToFeature);
|
||||
}
|
||||
|
||||
|
||||
@ -115,4 +116,3 @@ module.exports = {
|
||||
_createFeature: createFeature, // visible for testing
|
||||
_updateFeature: updateFeature // visible for testing
|
||||
};
|
||||
|
||||
|
@ -46,11 +46,13 @@
|
||||
"jsx-loader": "0.12.2",
|
||||
"jsxhint": "0.4.15",
|
||||
"knex": "^0.7.3",
|
||||
"lodash": "^3.5.0",
|
||||
"log4js": "0.6.21",
|
||||
"moment": "^2.9.0",
|
||||
"nconf": "0.6.9",
|
||||
"pg": "3.6.1",
|
||||
"react": "^0.12.0",
|
||||
"reflux": "^0.2.5",
|
||||
"reqwest": "^1.1.4",
|
||||
"webpack": "1.4.15",
|
||||
"webpack-dev-middleware": "^1.0.11"
|
||||
@ -73,7 +75,8 @@
|
||||
"jest": {
|
||||
"scriptPreprocessor": "<rootDir>/jest-preprocessor.js",
|
||||
"unmockedModulePathPatterns": [
|
||||
"<rootDir>/node_modules/react"
|
||||
"<rootDir>/node_modules/react",
|
||||
"<rootDir>/node_modules/reflux"
|
||||
],
|
||||
"moduleFileExtensions": [
|
||||
"jsx",
|
||||
|
@ -11,7 +11,7 @@
|
||||
<link rel="stylesheet" href="css/unleash.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">Loading...</div>
|
||||
<div id="content"></div>
|
||||
<script src="js/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
57
public/js/UnleashApp.jsx
Normal file
57
public/js/UnleashApp.jsx
Normal file
@ -0,0 +1,57 @@
|
||||
var React = require('react');
|
||||
var TabView = require('./components/TabView');
|
||||
var Menu = require('./components/Menu');
|
||||
var UserStore = require('./stores/UserStore');
|
||||
var ErrorMessages = require('./components/ErrorMessages');
|
||||
var initalizer = require('./stores/initalizer');
|
||||
var LogEntriesComponent = React.createFactory(require('./components/log/LogEntriesComponent'));
|
||||
var FeatureTogglesComponent = React.createFactory(require('./components/feature/FeatureTogglesComponent'));
|
||||
var StrategiesComponent = React.createFactory(require('./components/strategy/StrategiesComponent'));
|
||||
var ArchiveFeatureComponent = React.createFactory(require('./components/feature/ArchiveFeatureComponent'));
|
||||
|
||||
UserStore.init();
|
||||
|
||||
var tabPanes = [
|
||||
{
|
||||
name: 'Feature Toggles',
|
||||
slug: 'feature-toggles',
|
||||
content: new FeatureTogglesComponent({})
|
||||
},
|
||||
{
|
||||
name: 'Strategies',
|
||||
slug: 'strategies',
|
||||
content: new StrategiesComponent({})
|
||||
},
|
||||
{
|
||||
name: "Log",
|
||||
slug: 'log',
|
||||
content: new LogEntriesComponent({})
|
||||
},
|
||||
{
|
||||
name: "Archive",
|
||||
slug: 'archive',
|
||||
content: new ArchiveFeatureComponent({})
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
var UnleashApp = React.createClass({
|
||||
componentWillMount: function() {
|
||||
initalizer(30);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<Menu />
|
||||
<div className="container">
|
||||
<div className="page">
|
||||
<ErrorMessages />
|
||||
<TabView tabPanes={tabPanes} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = UnleashApp;
|
@ -1,32 +1,28 @@
|
||||
jest.dontMock("../../../components/feature/ArchiveFeatureComponent");
|
||||
jest.mock("../../../stores/FeatureToggleServerFacade");
|
||||
jest.autoMockOff();
|
||||
|
||||
var React = require("react/addons");
|
||||
var TestUtils = React.addons.TestUtils;
|
||||
var FeatureArchive = require("../../../components/feature/ArchiveFeatureComponent");
|
||||
var FeatureStore = require("../../../stores/FeatureStore");
|
||||
var FeatureArchive = require("../../../components/feature/ArchiveFeatureComponent");
|
||||
var Server = require("../../../stores/FeatureToggleServerFacade");
|
||||
var FeatureToggleStore = require("../../../stores/ArchivedToggleStore");
|
||||
|
||||
describe("FeatureForm", function () {
|
||||
var Component;
|
||||
beforeEach(function() {
|
||||
FeatureStore.getArchivedFeatures.mockImplementation(function() {
|
||||
return {
|
||||
then: function (callback) {
|
||||
return callback({
|
||||
features: [
|
||||
{ name: "featureX" },
|
||||
{ name: "featureY" }
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
FeatureStore.reviveFeature.mockImplementation(function() {
|
||||
return {
|
||||
then: function (callback) {return callback();}
|
||||
};
|
||||
var archivedToggles = [
|
||||
{ name: "featureX" },
|
||||
{ name: "featureY" }
|
||||
];
|
||||
|
||||
Server.getArchivedFeatures.mockImplementation(function(cb) {
|
||||
cb(null, archivedToggles);
|
||||
});
|
||||
|
||||
Component = TestUtils .renderIntoDocument(<FeatureArchive />);
|
||||
FeatureToggleStore.initStore(archivedToggles);
|
||||
|
||||
Component = TestUtils.renderIntoDocument(<FeatureArchive />);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@ -35,17 +31,15 @@ describe("FeatureForm", function () {
|
||||
|
||||
it("should render two archived features", function() {
|
||||
var rows = Component.getDOMNode().querySelectorAll("tbody tr");
|
||||
|
||||
expect(rows.length).toEqual(2);
|
||||
});
|
||||
|
||||
it("should revive archived feature toggle", function() {
|
||||
var button = Component.getDOMNode().querySelector("tbody button");
|
||||
TestUtils.Simulate.click(button);
|
||||
var rows = Component.getDOMNode().querySelectorAll("tbody tr");
|
||||
var button = Component.getDOMNode().querySelector("tbody button");
|
||||
TestUtils.Simulate.click(button);
|
||||
|
||||
expect(rows.length).toEqual(1);
|
||||
expect(FeatureStore.reviveFeature).toBeCalledWith({
|
||||
name: "featureX"
|
||||
});
|
||||
jest.runAllTimers();
|
||||
expect(Server.reviveFeature).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -3,31 +3,19 @@ jest.dontMock("../../../components/feature/FeatureForm");
|
||||
var React = require("react/addons");
|
||||
var TestUtils = React.addons.TestUtils;
|
||||
var FeatureForm = require("../../../components/feature/FeatureForm");
|
||||
var strategyStore = require("../../../stores/StrategyStore");
|
||||
|
||||
describe("FeatureForm", function () {
|
||||
var Component;
|
||||
beforeEach(function() {
|
||||
strategyStore.getStrategies.mockImplementation(function() {
|
||||
return {
|
||||
then: function (callback) {
|
||||
return callback({
|
||||
strategies: [
|
||||
{ name: "default"}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
var strategies = [
|
||||
{ name: "default"}
|
||||
];
|
||||
afterEach(function() {
|
||||
React.unmountComponentAtNode(document.body);
|
||||
});
|
||||
|
||||
describe("new", function () {
|
||||
it("should render empty form", function() {
|
||||
Component = TestUtils .renderIntoDocument(<FeatureForm />);
|
||||
Component = TestUtils .renderIntoDocument(<FeatureForm strategies={strategies} />);
|
||||
var name = Component.getDOMNode().querySelectorAll("input");
|
||||
expect(name[0].value).toEqual(undefined);
|
||||
});
|
||||
@ -37,11 +25,11 @@ describe("FeatureForm", function () {
|
||||
var feature = {name: "Test", strategy: "unknown"};
|
||||
|
||||
it("should show unknown strategy as deleted", function () {
|
||||
Component = TestUtils .renderIntoDocument(<FeatureForm feature={feature} />);
|
||||
Component = TestUtils .renderIntoDocument(<FeatureForm feature={feature} strategies={strategies} />);
|
||||
|
||||
var strategySelect = Component.getDOMNode().querySelector("select");
|
||||
expect(strategySelect.value).toEqual("unknown (deleted)");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -13,7 +13,10 @@ describe("FeatureList", function () {
|
||||
{ name: "featureX", strategy: "other" },
|
||||
{ name: "group.featureY", strategy: "default" }
|
||||
];
|
||||
Component = TestUtils .renderIntoDocument(<FeatureList features={features} />);
|
||||
var strategies=[
|
||||
{ name: "default"}
|
||||
];
|
||||
Component = TestUtils .renderIntoDocument(<FeatureList features={features} strategies={strategies} />);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@ -52,4 +55,4 @@ describe("FeatureList", function () {
|
||||
expect(features[0].textContent).toMatch(searchString);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
81
public/js/__tests__/stores/FeatureToggleStore-test.js
Normal file
81
public/js/__tests__/stores/FeatureToggleStore-test.js
Normal file
@ -0,0 +1,81 @@
|
||||
jest.autoMockOff()
|
||||
jest.dontMock('../../stores/FeatureToggleActions');
|
||||
jest.dontMock('../../stores/FeatureToggleStore');
|
||||
|
||||
describe('FeatureToggleStore', function() {
|
||||
|
||||
var Actions, Store, toggles;
|
||||
|
||||
beforeEach(function() {
|
||||
Actions = require('../../stores/FeatureToggleActions');
|
||||
Store = require('../../stores/FeatureToggleStore');
|
||||
toggles = [
|
||||
{name: "app.feature", enabled: true, strategy: "default"}
|
||||
];
|
||||
});
|
||||
|
||||
it('should be an empty store', function() {
|
||||
expect(Store.getFeatureToggles().length).toBe(0);
|
||||
});
|
||||
|
||||
it('should inititialize the store', function() {
|
||||
Actions.init.completed(toggles);
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(Store.getFeatureToggles().length).toBe(1);
|
||||
expect(Store.getFeatureToggles()[0].name).toEqual("app.feature");
|
||||
});
|
||||
|
||||
it('should add a another toggle', function() {
|
||||
Actions.init.completed(toggles);
|
||||
|
||||
var newToggle = {name: "app.featureB", enabled: true, strategy: "default"};
|
||||
|
||||
Actions.create.completed(newToggle);
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(Store.getFeatureToggles().length).toBe(2);
|
||||
expect(Store.getFeatureToggles()[1].name).toEqual("app.featureB");
|
||||
});
|
||||
|
||||
it('should archive toggle', function() {
|
||||
Actions.init.completed(toggles);
|
||||
|
||||
Actions.archive.completed(toggles[0]);
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(Store.getFeatureToggles().length).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('should keep toggles in sorted order', function() {
|
||||
Actions.init.completed([
|
||||
{name: "A"},
|
||||
{name: "B"},
|
||||
{name: "C"}
|
||||
]);
|
||||
|
||||
Actions.create.completed({name: "AA"});
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(Store.getFeatureToggles()[0].name).toEqual("A");
|
||||
expect(Store.getFeatureToggles()[1].name).toEqual("AA");
|
||||
expect(Store.getFeatureToggles()[3].name).toEqual("C");
|
||||
});
|
||||
|
||||
it('should update toggle', function() {
|
||||
Actions.init.completed(toggles);
|
||||
var toggle = toggles[0];
|
||||
|
||||
toggle.enabled = false;
|
||||
Actions.update.completed(toggle);
|
||||
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(Store.getFeatureToggles()[0].enabled).toEqual(false);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
@ -1,46 +1,3 @@
|
||||
var React = require('react');
|
||||
var TabView = require('./components/TabView');
|
||||
var Menu = require('./components/Menu');
|
||||
var UserStore = require('./stores/UserStore');
|
||||
var LogEntriesComponent = React.createFactory(require('./components/log/LogEntriesComponent'));
|
||||
var FeatureTogglesComponent = React.createFactory(require('./components/feature/FeatureTogglesComponent'));
|
||||
var StrategiesComponent = React.createFactory(require('./components/strategy/StrategiesComponent'));
|
||||
var ArchiveFeatureComponent = React.createFactory(require('./components/feature/ArchiveFeatureComponent'));
|
||||
|
||||
UserStore.init();
|
||||
|
||||
var tabPanes = [
|
||||
{
|
||||
name: 'Feature Toggles',
|
||||
slug: 'feature-toggles',
|
||||
content: new FeatureTogglesComponent({pollInterval: 5000})
|
||||
},
|
||||
{
|
||||
name: 'Strategies',
|
||||
slug: 'strategies',
|
||||
content: new StrategiesComponent({})
|
||||
},
|
||||
{
|
||||
name: "Log",
|
||||
slug: 'log',
|
||||
content: new LogEntriesComponent({})
|
||||
},
|
||||
{
|
||||
name: "Archive",
|
||||
slug: 'archive',
|
||||
content: new ArchiveFeatureComponent({})
|
||||
}
|
||||
];
|
||||
|
||||
React.render(
|
||||
<div>
|
||||
<Menu />
|
||||
<div className="container">
|
||||
<div className="page">
|
||||
<TabView tabPanes={tabPanes} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
,
|
||||
document.getElementById('content')
|
||||
);
|
||||
var React = require('react');
|
||||
var UnleashApp = require('./UnleashApp');
|
||||
React.render(<UnleashApp />, document.getElementById('content'));
|
||||
|
@ -1,37 +1,38 @@
|
||||
var React = require('react');
|
||||
var React = require('react');
|
||||
var Ui = require('./ErrorMessages.ui');
|
||||
var ErrorStore = require('../stores/ErrorStore');
|
||||
var ErrorActions = require('../stores/ErrorActions');
|
||||
|
||||
var ErrorMessages = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
errors: ErrorStore.getErrors()
|
||||
};
|
||||
},
|
||||
|
||||
onStoreChange: function() {
|
||||
this.setState({
|
||||
errors: ErrorStore.getErrors()
|
||||
});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.unsubscribe = ErrorStore.listen(this.onStoreChange);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this.unsubscribe();
|
||||
},
|
||||
|
||||
onClearErrors: function() {
|
||||
ErrorActions.clear();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.props.errors.length) {
|
||||
return <div/>;
|
||||
}
|
||||
|
||||
var errorNodes = this.props.errors.map(function(e, i) {
|
||||
return (<li key={e + i} className="largetext">{e}</li>);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="mod shadow mtm mrn">
|
||||
<div className="inner bg-red-lt">
|
||||
<div className="bd">
|
||||
<div className="media centerify">
|
||||
<div className="imgExt">
|
||||
<a onClick={this.props.onClearErrors}
|
||||
className="icon-kryss1 linkblock sharp">
|
||||
</a>
|
||||
</div>
|
||||
<div className="bd">
|
||||
<ul>{errorNodes}</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Ui errors={this.state.errors} onClearErrors={this.onClearErrors}></Ui>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ErrorMessages;
|
||||
|
||||
|
36
public/js/components/ErrorMessages.ui.jsx
Normal file
36
public/js/components/ErrorMessages.ui.jsx
Normal file
@ -0,0 +1,36 @@
|
||||
var React = require('react');
|
||||
|
||||
var ErrorMessages = React.createClass({
|
||||
render: function() {
|
||||
if (!this.props.errors.length) {
|
||||
return <div/>;
|
||||
}
|
||||
|
||||
var errorNodes = this.props.errors.map(function(e, i) {
|
||||
return (<li key={e + i} className="largetext">{e}</li>);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="mod shadow mtm mrn">
|
||||
<div className="inner bg-red-lt">
|
||||
<div className="bd">
|
||||
<div className="media centerify">
|
||||
<div className="imgExt">
|
||||
<a onClick={this.props.onClearErrors}
|
||||
className="icon-kryss1 linkblock sharp">
|
||||
</a>
|
||||
</div>
|
||||
<div className="bd">
|
||||
<ul>{errorNodes}</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ErrorMessages;
|
@ -1,28 +1,30 @@
|
||||
var React = require("react");
|
||||
var FeatureStore = require('../../stores/FeatureStore');
|
||||
var React = require("react");
|
||||
var FeatureActions = require('../../stores/FeatureToggleActions');
|
||||
var FeatureToggleStore = require('../../stores/ArchivedToggleStore');
|
||||
|
||||
var ArchiveFeatureComponent = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
archivedFeatures: []
|
||||
archivedFeatures: FeatureToggleStore.getArchivedToggles()
|
||||
};
|
||||
},
|
||||
|
||||
removeToggleFromState: function(item) {
|
||||
var updatedArchive = this.state.archivedFeatures.filter(function(f) {
|
||||
return f.name !== item.name;
|
||||
onStoreChange: function() {
|
||||
this.setState({
|
||||
archivedFeatures: FeatureToggleStore.getArchivedToggles()
|
||||
});
|
||||
this.setState({archivedFeatures: updatedArchive});
|
||||
},
|
||||
|
||||
onRevive: function( item) {
|
||||
FeatureStore.reviveFeature(item).then(this.removeToggleFromState.bind(this, item));
|
||||
componentDidMount: function() {
|
||||
this.unsubscribe = FeatureToggleStore.listen(this.onStoreChange);
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
FeatureStore.getArchivedFeatures().then(function(data) {
|
||||
this.setState({archivedFeatures: data.features});
|
||||
}.bind(this))
|
||||
componentWillUnmount: function() {
|
||||
this.unsubscribe();
|
||||
},
|
||||
|
||||
onRevive: function(item) {
|
||||
FeatureActions.revive.triggerPromise(item);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
@ -41,7 +43,7 @@ var ArchiveFeatureComponent = React.createClass({
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
},
|
||||
|
||||
renderArchivedItem: function(f) {
|
||||
@ -49,16 +51,15 @@ var ArchiveFeatureComponent = React.createClass({
|
||||
<tr key={f.name}>
|
||||
<td>
|
||||
{f.name}<br />
|
||||
<span className="opaque smalltext word-break">{f.description}</span>
|
||||
|
||||
</td>
|
||||
<td className="rightify" width="150">
|
||||
<button onClick={this.onRevive.bind(this, f)} title="Revive feature toggle">
|
||||
<span className="icon-svar"></span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>);
|
||||
<span className="opaque smalltext word-break">{f.description}</span>
|
||||
</td>
|
||||
<td className="rightify" width="150">
|
||||
<button onClick={this.onRevive.bind(this, f)} title="Revive feature toggle">
|
||||
<span className="icon-svar"></span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ArchiveFeatureComponent;
|
||||
module.exports = ArchiveFeatureComponent;
|
||||
|
@ -44,7 +44,11 @@ var Feature = React.createClass({
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan="4" className="pan man no-border">
|
||||
<FeatureForm feature={this.props.feature} onSubmit={this.saveFeature} onCancel={this.toggleEditMode} />
|
||||
<FeatureForm
|
||||
feature={this.props.feature}
|
||||
onSubmit={this.saveFeature}
|
||||
onCancel={this.toggleEditMode}
|
||||
strategies={this.props.strategies} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
@ -110,4 +114,4 @@ var Feature = React.createClass({
|
||||
|
||||
});
|
||||
|
||||
module.exports = Feature;
|
||||
module.exports = Feature;
|
||||
|
@ -1,22 +1,16 @@
|
||||
var React = require('react');
|
||||
var TextInput = require('../form/TextInput');
|
||||
var strategyStore = require('../../stores/StrategyStore');
|
||||
|
||||
var FeatureForm = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
strategyOptions: [],
|
||||
requiredParams: [],
|
||||
currentStrategy: this.props.feature ? this.props.feature.strategy : "default"
|
||||
};
|
||||
return {
|
||||
strategyOptions: this.props.strategies,
|
||||
requiredParams: [],
|
||||
currentStrategy: this.props.feature ? this.props.feature.strategy : "default"
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
strategyStore.getStrategies().then(this.handleStrategyResponse);
|
||||
},
|
||||
|
||||
handleStrategyResponse: function(response) {
|
||||
this.setState({strategyOptions: response.strategies});
|
||||
if(this.props.feature) {
|
||||
this.setSelectedStrategy(this.props.feature.strategy);
|
||||
}
|
||||
@ -41,9 +35,7 @@ var FeatureForm = React.createClass({
|
||||
})[0];
|
||||
|
||||
if(selectedStrategy) {
|
||||
if(selectedStrategy.parametersTemplate) {
|
||||
this.setStrategyParams(selectedStrategy);
|
||||
}
|
||||
this.setStrategyParams(selectedStrategy);
|
||||
} else {
|
||||
var updatedStrategyName = name + " (deleted)";
|
||||
this.setState({
|
||||
@ -64,9 +56,9 @@ var FeatureForm = React.createClass({
|
||||
|
||||
render: function() {
|
||||
var feature = this.props.feature || {
|
||||
name: '',
|
||||
strategy: 'default',
|
||||
enabled: false
|
||||
name: '',
|
||||
strategy: 'default',
|
||||
enabled: false
|
||||
};
|
||||
|
||||
var idPrefix = this.props.feature ? this.props.feature.name : 'new';
|
||||
@ -131,9 +123,9 @@ var FeatureForm = React.createClass({
|
||||
renderStrategyOptions: function() {
|
||||
return this.state.strategyOptions.map(function(strategy) {
|
||||
return (
|
||||
<option key={strategy.name} value={strategy.name}>
|
||||
{strategy.name}
|
||||
</option>
|
||||
<option key={strategy.name} value={strategy.name}>
|
||||
{strategy.name}
|
||||
</option>
|
||||
);
|
||||
}.bind(this));
|
||||
},
|
||||
@ -178,4 +170,4 @@ var FeatureForm = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = FeatureForm;
|
||||
module.exports = FeatureForm;
|
||||
|
@ -5,7 +5,8 @@ var noop = function() {};
|
||||
|
||||
var FeatureList = React.createClass({
|
||||
propTypes: {
|
||||
features: React.PropTypes.array.isRequired
|
||||
features: React.PropTypes.array.isRequired,
|
||||
strategies: React.PropTypes.array.isRequired
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
@ -47,7 +48,9 @@ var FeatureList = React.createClass({
|
||||
key={feature.name}
|
||||
feature={feature}
|
||||
onChange={this.props.onFeatureChanged}
|
||||
onArchive={this.props.onFeatureArchive}/>
|
||||
onArchive={this.props.onFeatureArchive}
|
||||
strategies={this.props.strategies}
|
||||
/>
|
||||
);
|
||||
}.bind(this));
|
||||
|
||||
@ -83,4 +86,4 @@ var FeatureList = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = FeatureList;
|
||||
module.exports = FeatureList;
|
||||
|
@ -1,132 +1,56 @@
|
||||
var React = require('react');
|
||||
var Timer = require('../../utils/Timer');
|
||||
var ErrorMessages = require('../ErrorMessages');
|
||||
var FeatureList = require('./FeatureList');
|
||||
var FeatureForm = require('./FeatureForm');
|
||||
var FeatureStore = require('../../stores/FeatureStore');
|
||||
var React = require('react');
|
||||
var FeatureList = require('./FeatureList');
|
||||
var FeatureForm = require('./FeatureForm');
|
||||
var FeatureActions = require('../../stores/FeatureToggleActions');
|
||||
var ErrorActions = require('../../stores/ErrorActions');
|
||||
var FeatureToggleStore = require('../../stores/FeatureToggleStore');
|
||||
var StrategyStore = require('../../stores/StrategyStore');
|
||||
|
||||
var FeatureTogglesComponent = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
features: [],
|
||||
errors: [],
|
||||
createView: false,
|
||||
featurePoller: new Timer(this.loadFeaturesFromServer, this.props.pollInterval)
|
||||
features: FeatureToggleStore.getFeatureToggles(),
|
||||
createView: false
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
this.loadFeaturesFromServer();
|
||||
this.startFeaturePoller();
|
||||
onFeatureToggleChange: function() {
|
||||
this.setState({
|
||||
features: FeatureToggleStore.getFeatureToggles()
|
||||
});
|
||||
},
|
||||
|
||||
componentWillUnmount: function () {
|
||||
this.stopFeaturePoller();
|
||||
componentDidMount: function() {
|
||||
this.unsubscribe = FeatureToggleStore.listen(this.onFeatureToggleChange);
|
||||
},
|
||||
|
||||
loadFeaturesFromServer: function () {
|
||||
FeatureStore.getFeatures().then(this.setFeatures).catch(this.handleError);
|
||||
},
|
||||
|
||||
setFeatures: function (data) {
|
||||
this.setState({features: data.features});
|
||||
},
|
||||
|
||||
handleError: function (error) {
|
||||
if (this.isClientError(error)) {
|
||||
var errors = JSON.parse(error.responseText)
|
||||
errors.forEach(function(e) { this.addError(e.msg); }.bind(this))
|
||||
} else if (error.status === 0) {
|
||||
this.addError("server unreachable");
|
||||
} else {
|
||||
this.addError(error);
|
||||
}
|
||||
|
||||
this.forceUpdate();
|
||||
componentWillUnmount: function() {
|
||||
this.unsubscribe();
|
||||
},
|
||||
|
||||
updateFeature: function (feature) {
|
||||
this.stopFeaturePoller();
|
||||
|
||||
FeatureStore
|
||||
.updateFeature(feature)
|
||||
.then(this.startFeaturePoller)
|
||||
.catch(this.handleError);
|
||||
FeatureActions.update.triggerPromise(feature);
|
||||
},
|
||||
|
||||
archiveFeature: function (feature) {
|
||||
var updatedFeatures = this.state.features.filter(function(item) {
|
||||
return item.name !== feature.name;
|
||||
});
|
||||
|
||||
FeatureStore
|
||||
.archiveFeature(feature)
|
||||
.then(function() {
|
||||
this.setState({features: updatedFeatures})
|
||||
}.bind(this))
|
||||
.catch(this.handleError);
|
||||
},
|
||||
|
||||
startFeaturePoller: function () {
|
||||
this.state.featurePoller.start();
|
||||
},
|
||||
|
||||
stopFeaturePoller: function () {
|
||||
if (this.state.featurePoller != null) {
|
||||
this.state.featurePoller.stop();
|
||||
}
|
||||
FeatureActions.archive.triggerPromise(feature);
|
||||
},
|
||||
|
||||
createFeature: function (feature) {
|
||||
this.stopFeaturePoller();
|
||||
|
||||
FeatureStore
|
||||
.createFeature(feature)
|
||||
.then(this.cancelNewFeature)
|
||||
.then(this.startFeaturePoller)
|
||||
.catch(this.handleError);
|
||||
FeatureActions.create.triggerPromise(feature)
|
||||
.then(this.cancelNewFeature);
|
||||
},
|
||||
|
||||
newFeature: function() {
|
||||
this.setState({createView: true});
|
||||
},
|
||||
|
||||
cancelNewFeature: function (feature) {
|
||||
cancelNewFeature: function () {
|
||||
this.setState({createView: false});
|
||||
},
|
||||
|
||||
clearErrors: function() {
|
||||
this.setState({errors: []});
|
||||
},
|
||||
|
||||
addError: function(msg) {
|
||||
if (this.state.errors[this.state.errors.length - 1] !== msg) {
|
||||
this.state.errors.push(msg);
|
||||
}
|
||||
},
|
||||
|
||||
isClientError: function(error) {
|
||||
try {
|
||||
return error.status >= 400 &&
|
||||
error.status < 500 &&
|
||||
JSON.parse(error.responseText);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
// fall through;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
ErrorActions.clear();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<ErrorMessages
|
||||
errors={this.state.errors}
|
||||
onClearErrors={this.clearErrors} />
|
||||
|
||||
{this.state.createView ? this.renderCreateView() : this.renderCreateButton()}
|
||||
|
||||
@ -136,18 +60,22 @@ var FeatureTogglesComponent = React.createClass({
|
||||
onFeatureArchive={this.archiveFeature}
|
||||
onFeatureSubmit={this.createFeature}
|
||||
onFeatureCancel={this.cancelNewFeature}
|
||||
onNewFeature={this.newFeature} />
|
||||
onNewFeature={this.newFeature}
|
||||
strategies={StrategyStore.getStrategies()} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderCreateView: function() {
|
||||
return <FeatureForm onCancel={this.cancelNewFeature} onSubmit={this.createFeature} />
|
||||
return <FeatureForm
|
||||
onCancel={this.cancelNewFeature}
|
||||
onSubmit={this.createFeature}
|
||||
strategies={StrategyStore.getStrategies()} />;
|
||||
},
|
||||
|
||||
renderCreateButton: function() {
|
||||
return <button className="mal" onClick={this.newFeature}>Create feature toggle</button>
|
||||
return <button className="mal" onClick={this.newFeature}>Create feature toggle</button>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = FeatureTogglesComponent;
|
||||
module.exports = FeatureTogglesComponent;
|
||||
|
@ -1,14 +1,13 @@
|
||||
var React = require('react'),
|
||||
LogEntryList = require('./LogEntryList'),
|
||||
eventStore = require('../../stores/EventStore'),
|
||||
ErrorMessages = require('../ErrorMessages');
|
||||
var React = require('react');
|
||||
var LogEntryList = require('./LogEntryList');
|
||||
var eventStore = require('../../stores/EventStore');
|
||||
var ErrorActions = require('../../stores/ErrorActions');
|
||||
|
||||
var LogEntriesComponent = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
createView: false,
|
||||
events: [],
|
||||
errors: []
|
||||
events: []
|
||||
};
|
||||
},
|
||||
|
||||
@ -19,29 +18,17 @@ var LogEntriesComponent = React.createClass({
|
||||
},
|
||||
|
||||
initError: function() {
|
||||
this.onError("Could not load events from server");
|
||||
},
|
||||
|
||||
clearErrors: function() {
|
||||
this.setState({errors: []});
|
||||
},
|
||||
|
||||
onError: function(error) {
|
||||
var errors = this.state.errors.concat([error]);
|
||||
this.setState({errors: errors});
|
||||
ErrorActions.error("Could not load events from server");
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<ErrorMessages errors={this.state.errors} onClearErrors={this.clearErrors} />
|
||||
|
||||
<hr />
|
||||
|
||||
<LogEntryList events={this.state.events} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = LogEntriesComponent;
|
||||
module.exports = LogEntriesComponent;
|
||||
|
@ -1,5 +1,5 @@
|
||||
var React = require('react'),
|
||||
LogEntry = require('./LogEntry');
|
||||
LogEntry = require('./LogEntry');
|
||||
|
||||
var LogEntryList = React.createClass({
|
||||
propTypes: {
|
||||
@ -9,7 +9,7 @@ var LogEntryList = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
showFullEvents: false
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
@ -19,9 +19,14 @@ var LogEntryList = React.createClass({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className="prs fright-ht768 smalltext">
|
||||
Show full events
|
||||
<input type="checkbox" className="mlm" value={this.state.fullEvents} onChange={this.toggleFullEvents}></input>
|
||||
<label className="prs fright-ht768 smalltext">
|
||||
Show full events
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mlm"
|
||||
value={this.state.fullEvents}
|
||||
onChange={this.toggleFullEvents}>
|
||||
</input>
|
||||
</label>
|
||||
|
||||
<table className='outerborder zebra-striped'>
|
||||
@ -30,7 +35,7 @@ var LogEntryList = React.createClass({
|
||||
<th>When</th>
|
||||
<th>Action</th>
|
||||
<th>
|
||||
Data
|
||||
Data
|
||||
</th>
|
||||
<th>Author</th>
|
||||
</tr>
|
||||
@ -40,13 +45,13 @@ var LogEntryList = React.createClass({
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
},
|
||||
|
||||
toggleFullEvents: function(e) {
|
||||
toggleFullEvents: function() {
|
||||
this.setState({showFullEvents: !this.state.showFullEvents});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = LogEntryList;
|
||||
module.exports = LogEntryList;
|
||||
|
@ -1,42 +1,27 @@
|
||||
var React = require('react'),
|
||||
StrategyList = require('./StrategyList'),
|
||||
StrategyForm = require('./StrategyForm'),
|
||||
strategyStore = require('../../stores/StrategyStore'),
|
||||
ErrorMessages = require('../ErrorMessages');
|
||||
var React = require('react');
|
||||
var StrategyList = require('./StrategyList');
|
||||
var StrategyForm = require('./StrategyForm');
|
||||
var StrategyStore = require('../../stores/StrategyStore');
|
||||
var StrategyActions = require('../../stores/StrategyActions');
|
||||
|
||||
var StrategiesComponent = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
createView: false,
|
||||
strategies: [],
|
||||
errors: []
|
||||
strategies: StrategyStore.getStrategies()
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
this.fetchStrategies();
|
||||
onStoreChange: function() {
|
||||
this.setState({
|
||||
strategies: StrategyStore.getStrategies()
|
||||
});
|
||||
},
|
||||
|
||||
fetchStrategies: function(res) {
|
||||
strategyStore.getStrategies()
|
||||
.then(function(res) {
|
||||
this.setState({strategies: res.strategies})
|
||||
}.bind(this))
|
||||
.catch(this.initError);
|
||||
|
||||
componentDidMount: function() {
|
||||
this.unsubscribe = StrategyStore.listen(this.onStoreChange);
|
||||
},
|
||||
|
||||
initError: function() {
|
||||
this.onError("Could not load inital strategies from server");
|
||||
},
|
||||
|
||||
clearErrors: function() {
|
||||
this.setState({errors: []});
|
||||
},
|
||||
|
||||
onError: function(error) {
|
||||
var errors = this.state.errors.concat([error]);
|
||||
this.setState({errors: errors});
|
||||
componentWillUnmount: function() {
|
||||
this.unsubscribe();
|
||||
},
|
||||
|
||||
onNewStrategy: function() {
|
||||
@ -48,51 +33,42 @@ var StrategiesComponent = React.createClass({
|
||||
},
|
||||
|
||||
onSave: function(strategy) {
|
||||
function handleSuccess() {
|
||||
var strategies = this.state.strategies.concat([strategy]);
|
||||
|
||||
this.setState({
|
||||
createView: false,
|
||||
strategies: strategies
|
||||
});
|
||||
|
||||
console.log("Saved strategy: ", strategy);
|
||||
}
|
||||
|
||||
strategyStore.createStrategy(strategy)
|
||||
.then(handleSuccess.bind(this))
|
||||
.catch(this.onError);
|
||||
StrategyActions.create.triggerPromise(strategy)
|
||||
.then(this.onCancelNewStrategy);
|
||||
},
|
||||
|
||||
onRemove: function(strategy) {
|
||||
strategyStore.removeStrategy(strategy)
|
||||
.then(this.fetchStrategies)
|
||||
.catch(this.onError);
|
||||
StrategyActions.remove.triggerPromise(strategy);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<ErrorMessages errors={this.state.errors} onClearErrors={this.clearErrors} />
|
||||
|
||||
{this.state.createView ? this.renderCreateView() : this.renderCreateButton()}
|
||||
|
||||
{this.state.createView ?
|
||||
this.renderCreateView() : this.renderCreateButton()}
|
||||
<hr />
|
||||
|
||||
<StrategyList strategies={this.state.strategies} onRemove={this.onRemove} />
|
||||
<StrategyList
|
||||
strategies={this.state.strategies}
|
||||
onRemove={this.onRemove} />
|
||||
</div>
|
||||
);
|
||||
);
|
||||
},
|
||||
|
||||
renderCreateView: function() {
|
||||
return (<StrategyForm onCancelNewStrategy={this.onCancelNewStrategy} onSave={this.onSave} />)
|
||||
},
|
||||
|
||||
renderCreateButton: function() {
|
||||
return (
|
||||
<button className="mal" onClick={this.onNewStrategy}>Create strategy</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
<StrategyForm
|
||||
onCancelNewStrategy={this.onCancelNewStrategy}
|
||||
onSave={this.onSave}
|
||||
/>);
|
||||
},
|
||||
|
||||
module.exports = StrategiesComponent;
|
||||
renderCreateButton: function() {
|
||||
return (
|
||||
<button className="mal" onClick={this.onNewStrategy}>
|
||||
Create strategy
|
||||
</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = StrategiesComponent;
|
||||
|
48
public/js/stores/ArchivedToggleStore.js
Normal file
48
public/js/stores/ArchivedToggleStore.js
Normal file
@ -0,0 +1,48 @@
|
||||
var Reflux = require('reflux');
|
||||
var FeatureActions = require('./FeatureToggleActions');
|
||||
var filter = require('lodash/collection/filter');
|
||||
var sortBy = require('lodash/collection/sortBy');
|
||||
|
||||
var _archivedToggles = [];
|
||||
|
||||
// Creates a DataStore
|
||||
var FeatureStore = Reflux.createStore({
|
||||
|
||||
// Initial setup
|
||||
init: function() {
|
||||
this.listenTo(FeatureActions.initArchive.completed, this.onInit);
|
||||
this.listenTo(FeatureActions.archive.completed, this.onArchive);
|
||||
this.listenTo(FeatureActions.revive.completed, this.onRevive);
|
||||
|
||||
},
|
||||
|
||||
onInit: function(toggles) {
|
||||
_archivedToggles = toggles;
|
||||
this.trigger();
|
||||
},
|
||||
|
||||
onArchive: function(feature) {
|
||||
var toggles = _archivedToggles.concat([feature]);
|
||||
_archivedToggles = sortBy(toggles, 'name');
|
||||
this.trigger();
|
||||
},
|
||||
|
||||
onRevive: function(item) {
|
||||
var newStore = filter(_archivedToggles, function(f) {
|
||||
return f.name !== item.name;
|
||||
});
|
||||
|
||||
_archivedToggles = sortBy(newStore, 'name');
|
||||
this.trigger();
|
||||
},
|
||||
|
||||
getArchivedToggles: function() {
|
||||
return _archivedToggles;
|
||||
},
|
||||
|
||||
initStore: function(archivedToggles) {
|
||||
_archivedToggles = archivedToggles;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = FeatureStore;
|
8
public/js/stores/ErrorActions.js
Normal file
8
public/js/stores/ErrorActions.js
Normal file
@ -0,0 +1,8 @@
|
||||
var Reflux = require('reflux');
|
||||
|
||||
var ErrorActions = Reflux.createActions([
|
||||
"clear",
|
||||
"error"
|
||||
]);
|
||||
|
||||
module.exports = ErrorActions;
|
66
public/js/stores/ErrorStore.js
Normal file
66
public/js/stores/ErrorStore.js
Normal file
@ -0,0 +1,66 @@
|
||||
var Reflux = require('reflux');
|
||||
var FeatureActions = require('./FeatureToggleActions');
|
||||
var ErrorActions = require('./ErrorActions');
|
||||
|
||||
// Creates a DataStore
|
||||
var FeatureStore = Reflux.createStore({
|
||||
// Initial setup
|
||||
init: function() {
|
||||
this.listenTo(FeatureActions.create.failed, this.onError);
|
||||
this.listenTo(FeatureActions.init.failed, this.onError);
|
||||
this.listenTo(FeatureActions.update.failed, this.onError);
|
||||
this.listenTo(FeatureActions.archive.failed, this.onError);
|
||||
this.listenTo(FeatureActions.revive.failed, this.onError);
|
||||
this.listenTo(ErrorActions.error, this.onError);
|
||||
this.listenTo(ErrorActions.clear, this.onClear);
|
||||
this.errors = [];
|
||||
},
|
||||
|
||||
onError: function (error) {
|
||||
if (this.isClientError(error)) {
|
||||
var errors = JSON.parse(error.responseText);
|
||||
errors.forEach(function(e) { this.addError(e.msg); }.bind(this));
|
||||
} else if (error.status === 0) {
|
||||
this.addError("server unreachable");
|
||||
} else {
|
||||
this.addError(error);
|
||||
}
|
||||
},
|
||||
|
||||
onClear: function() {
|
||||
this.errors = [];
|
||||
this.trigger([]);
|
||||
},
|
||||
|
||||
addError: function(msg) {
|
||||
var errors = this.errors;
|
||||
if (errors[errors.length - 1] !== msg) {
|
||||
errors.push(msg);
|
||||
this.errors = errors;
|
||||
this.trigger(errors);
|
||||
}
|
||||
},
|
||||
|
||||
isClientError: function(error) {
|
||||
try {
|
||||
return error.status >= 400 &&
|
||||
error.status < 500 &&
|
||||
JSON.parse(error.responseText);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
// fall through;
|
||||
console.log("Syntax error!");
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
getErrors: function() {
|
||||
return this.errors;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = FeatureStore;
|
@ -1,62 +0,0 @@
|
||||
var reqwest = require('reqwest');
|
||||
|
||||
var TYPE = 'json';
|
||||
var CONTENT_TYPE = 'application/json';
|
||||
|
||||
FeatureStore = {
|
||||
updateFeature: function (feature) {
|
||||
return reqwest({
|
||||
url: 'features/' + feature.name,
|
||||
method: 'put',
|
||||
type: TYPE,
|
||||
contentType: CONTENT_TYPE,
|
||||
data: JSON.stringify(feature)
|
||||
});
|
||||
},
|
||||
|
||||
createFeature: function (feature) {
|
||||
return reqwest({
|
||||
url: 'features',
|
||||
method: 'post',
|
||||
type: TYPE,
|
||||
contentType: CONTENT_TYPE,
|
||||
data: JSON.stringify(feature)
|
||||
});
|
||||
},
|
||||
|
||||
archiveFeature: function (feature) {
|
||||
return reqwest({
|
||||
url: 'features/' + feature.name,
|
||||
method: 'delete',
|
||||
type: TYPE
|
||||
});
|
||||
},
|
||||
|
||||
getFeatures: function () {
|
||||
return reqwest({
|
||||
url: 'features',
|
||||
method: 'get',
|
||||
type: TYPE
|
||||
});
|
||||
},
|
||||
|
||||
getArchivedFeatures: function () {
|
||||
return reqwest({
|
||||
url: 'archive/features',
|
||||
method: 'get',
|
||||
type: TYPE
|
||||
});
|
||||
},
|
||||
|
||||
reviveFeature: function (feature) {
|
||||
return reqwest({
|
||||
url: 'archive/revive',
|
||||
method: 'post',
|
||||
type: TYPE,
|
||||
contentType: CONTENT_TYPE,
|
||||
data: JSON.stringify(feature)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = FeatureStore;
|
73
public/js/stores/FeatureToggleActions.js
Normal file
73
public/js/stores/FeatureToggleActions.js
Normal file
@ -0,0 +1,73 @@
|
||||
var Reflux = require("reflux");
|
||||
var Server = require('./FeatureToggleServerFacade');
|
||||
|
||||
var FeatureToggleActions = Reflux.createActions({
|
||||
'init': { asyncResult: true },
|
||||
'initArchive':{ asyncResult: true },
|
||||
'create': { asyncResult: true },
|
||||
'update': { asyncResult: true },
|
||||
'archive': { asyncResult: true },
|
||||
'revive': { asyncResult: true }
|
||||
});
|
||||
|
||||
FeatureToggleActions.init.listen(function(){
|
||||
Server.getFeatures(function(error, features) {
|
||||
if(error) {
|
||||
this.failed(error);
|
||||
} else {
|
||||
this.completed(features);
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
FeatureToggleActions.initArchive.listen(function(){
|
||||
Server.getArchivedFeatures(function(error, archivedToggles) {
|
||||
if(error) {
|
||||
this.failed(error);
|
||||
} else {
|
||||
this.completed(archivedToggles);
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
FeatureToggleActions.create.listen(function(feature){
|
||||
Server.createFeature(feature, function(error) {
|
||||
if(error) {
|
||||
this.failed(error);
|
||||
} else {
|
||||
this.completed(feature);
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
FeatureToggleActions.update.listen(function(feature){
|
||||
Server.updateFeature(feature, function(error) {
|
||||
if(error) {
|
||||
this.failed(error);
|
||||
} else {
|
||||
this.completed(feature);
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
FeatureToggleActions.archive.listen(function(feature){
|
||||
Server.archiveFeature(feature, function(error) {
|
||||
if(error) {
|
||||
this.failed(error);
|
||||
} else {
|
||||
this.completed(feature);
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
FeatureToggleActions.revive.listen(function(feature){
|
||||
Server.reviveFeature(feature, function(error) {
|
||||
if(error) {
|
||||
this.failed(error);
|
||||
} else {
|
||||
this.completed(feature);
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
module.exports = FeatureToggleActions;
|
98
public/js/stores/FeatureToggleServerFacade.js
Normal file
98
public/js/stores/FeatureToggleServerFacade.js
Normal file
@ -0,0 +1,98 @@
|
||||
var reqwest = require('reqwest');
|
||||
|
||||
var TYPE = 'json';
|
||||
var CONTENT_TYPE = 'application/json';
|
||||
|
||||
var FeatureToggleServerFacade = {
|
||||
updateFeature: function (feature, cb) {
|
||||
reqwest({
|
||||
url: 'features/' + feature.name,
|
||||
method: 'put',
|
||||
type: TYPE,
|
||||
contentType: CONTENT_TYPE,
|
||||
data: JSON.stringify(feature),
|
||||
error: function(error) {
|
||||
cb(error);
|
||||
},
|
||||
success: function() {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
createFeature: function (feature, cb) {
|
||||
reqwest({
|
||||
url: 'features',
|
||||
method: 'post',
|
||||
type: TYPE,
|
||||
contentType: CONTENT_TYPE,
|
||||
data: JSON.stringify(feature),
|
||||
error: function(error) {
|
||||
cb(error);
|
||||
},
|
||||
success: function() {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
archiveFeature: function(feature, cb) {
|
||||
reqwest({
|
||||
url: 'features/' + feature.name,
|
||||
method: 'delete',
|
||||
type: TYPE,
|
||||
error: function(error) {
|
||||
cb(error);
|
||||
},
|
||||
success: function() {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getFeatures: function(cb) {
|
||||
reqwest({
|
||||
url: 'features',
|
||||
method: 'get',
|
||||
type: TYPE,
|
||||
error: function(error) {
|
||||
cb(error);
|
||||
},
|
||||
success: function(data) {
|
||||
cb(null, data.features);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getArchivedFeatures: function(cb) {
|
||||
reqwest({
|
||||
url: 'archive/features',
|
||||
method: 'get',
|
||||
type: TYPE,
|
||||
error: function(error) {
|
||||
cb(error);
|
||||
},
|
||||
success: function(data) {
|
||||
cb(null, data.features);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
reviveFeature: function (feature, cb) {
|
||||
reqwest({
|
||||
url: 'archive/revive',
|
||||
method: 'post',
|
||||
type: TYPE,
|
||||
contentType: CONTENT_TYPE,
|
||||
data: JSON.stringify(feature),
|
||||
error: function(error) {
|
||||
cb(error);
|
||||
},
|
||||
success: function() {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = FeatureToggleServerFacade;
|
57
public/js/stores/FeatureToggleStore.js
Normal file
57
public/js/stores/FeatureToggleStore.js
Normal file
@ -0,0 +1,57 @@
|
||||
var Reflux = require('reflux');
|
||||
var FeatureActions = require('./FeatureToggleActions');
|
||||
var filter = require('lodash/collection/filter');
|
||||
var sortBy = require('lodash/collection/sortBy');
|
||||
var findIndex = require('lodash/array/findIndex');
|
||||
|
||||
var _featureToggles = [];
|
||||
|
||||
var FeatureStore = Reflux.createStore({
|
||||
|
||||
// Initial setup
|
||||
init: function() {
|
||||
this.listenTo(FeatureActions.init.completed, this.setToggles);
|
||||
this.listenTo(FeatureActions.create.completed, this.onCreate);
|
||||
this.listenTo(FeatureActions.update.completed, this.onUpdate);
|
||||
this.listenTo(FeatureActions.archive.completed, this.onArchive);
|
||||
this.listenTo(FeatureActions.revive.completed, this.onRevive);
|
||||
},
|
||||
|
||||
onCreate: function(feature) {
|
||||
this.setToggles([feature].concat(_featureToggles));
|
||||
},
|
||||
|
||||
setToggles: function(toggles) {
|
||||
_featureToggles = sortBy(toggles, 'name');
|
||||
this.trigger();
|
||||
},
|
||||
|
||||
onUpdate: function(feature) {
|
||||
var idx = findIndex(_featureToggles, 'name', feature.name);
|
||||
_featureToggles[idx] = feature;
|
||||
this.trigger();
|
||||
},
|
||||
|
||||
onArchive: function(feature) {
|
||||
var featureToggles = filter(_featureToggles, function(item) {
|
||||
return item.name !== feature.name;
|
||||
});
|
||||
this.setToggles(featureToggles);
|
||||
this.trigger();
|
||||
},
|
||||
|
||||
onRevive: function(item) {
|
||||
this.setToggles(_featureToggles.concat([item]));
|
||||
this.trigger();
|
||||
},
|
||||
|
||||
getFeatureToggles: function() {
|
||||
return _featureToggles;
|
||||
},
|
||||
|
||||
initStore: function(toggles) {
|
||||
_featureToggles = toggles;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = FeatureStore;
|
52
public/js/stores/StrategyAPI.js
Normal file
52
public/js/stores/StrategyAPI.js
Normal file
@ -0,0 +1,52 @@
|
||||
var reqwest = require('reqwest');
|
||||
|
||||
var TYPE = 'json';
|
||||
var CONTENT_TYPE = 'application/json';
|
||||
|
||||
var StrategyAPI = {
|
||||
createStrategy: function (strategy, cb) {
|
||||
reqwest({
|
||||
url: 'strategies',
|
||||
method: 'post',
|
||||
type: TYPE,
|
||||
contentType: CONTENT_TYPE,
|
||||
data: JSON.stringify(strategy),
|
||||
error: function(error) {
|
||||
cb(error);
|
||||
},
|
||||
success: function() {
|
||||
cb(null, strategy);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
removeStrategy: function (strategy, cb) {
|
||||
reqwest({
|
||||
url: 'strategies/'+strategy.name,
|
||||
method: 'delete',
|
||||
type: TYPE,
|
||||
error: function(error) {
|
||||
cb(error);
|
||||
},
|
||||
success: function() {
|
||||
cb(null, strategy);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getStrategies: function (cb) {
|
||||
reqwest({
|
||||
url: 'strategies',
|
||||
method: 'get',
|
||||
type: TYPE,
|
||||
error: function(error) {
|
||||
cb(error);
|
||||
},
|
||||
success: function(data) {
|
||||
cb(null, data.strategies);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = StrategyAPI;
|
40
public/js/stores/StrategyActions.js
Normal file
40
public/js/stores/StrategyActions.js
Normal file
@ -0,0 +1,40 @@
|
||||
var Reflux = require("reflux");
|
||||
var StrategyAPI = require('./StrategyAPI');
|
||||
|
||||
var StrategyActions = Reflux.createActions({
|
||||
'init': { asyncResult: true },
|
||||
'create': { asyncResult: true },
|
||||
'remove': { asyncResult: true },
|
||||
});
|
||||
|
||||
StrategyActions.init.listen(function(){
|
||||
StrategyAPI.getStrategies(function(err, strategies) {
|
||||
if(err) {
|
||||
this.failed(err);
|
||||
} else {
|
||||
this.completed(strategies);
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
StrategyActions.create.listen(function(feature){
|
||||
StrategyAPI.createStrategy(feature, function(err) {
|
||||
if(err) {
|
||||
this.failed(err);
|
||||
} else {
|
||||
this.completed(feature);
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
StrategyActions.remove.listen(function(feature){
|
||||
StrategyAPI.removeStrategy(feature, function(err) {
|
||||
if(err) {
|
||||
this.failed(err);
|
||||
} else {
|
||||
this.completed(feature);
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
module.exports = StrategyActions;
|
@ -1,34 +1,42 @@
|
||||
var reqwest = require('reqwest');
|
||||
var Reflux = require('reflux');
|
||||
var StrategyActions = require('./StrategyActions');
|
||||
var filter = require('lodash/collection/filter');
|
||||
|
||||
var TYPE = 'json';
|
||||
var CONTENT_TYPE = 'application/json';
|
||||
var _strategies = [];
|
||||
|
||||
var StrategyStore = {
|
||||
createStrategy: function (strategy) {
|
||||
return reqwest({
|
||||
url: 'strategies',
|
||||
method: 'post',
|
||||
type: TYPE,
|
||||
contentType: CONTENT_TYPE,
|
||||
data: JSON.stringify(strategy)
|
||||
});
|
||||
// Creates a DataStore
|
||||
var StrategyStore = Reflux.createStore({
|
||||
|
||||
// Initial setup
|
||||
init: function() {
|
||||
this.listenTo(StrategyActions.init.completed, this.setStrategies);
|
||||
this.listenTo(StrategyActions.create.completed, this.onCreate);
|
||||
this.listenTo(StrategyActions.remove.completed, this.onRemove);
|
||||
},
|
||||
|
||||
removeStrategy: function (strategy) {
|
||||
return reqwest({
|
||||
url: 'strategies/'+strategy.name,
|
||||
method: 'delete',
|
||||
type: TYPE
|
||||
});
|
||||
onCreate: function(strategy) {
|
||||
this.setStrategies(_strategies.concat([strategy]));
|
||||
},
|
||||
|
||||
getStrategies: function () {
|
||||
return reqwest({
|
||||
url: 'strategies',
|
||||
method: 'get',
|
||||
type: TYPE
|
||||
onRemove: function(strategy) {
|
||||
var strategies = filter(_strategies, function(item) {
|
||||
return item.name !== strategy.name;
|
||||
});
|
||||
this.setStrategies(strategies);
|
||||
},
|
||||
|
||||
setStrategies: function(strategies) {
|
||||
_strategies = strategies;
|
||||
this.trigger(_strategies);
|
||||
},
|
||||
|
||||
getStrategies: function() {
|
||||
return _strategies;
|
||||
},
|
||||
|
||||
initStore: function(strategies) {
|
||||
_strategies = strategies;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = StrategyStore;
|
||||
|
17
public/js/stores/initalizer.js
Normal file
17
public/js/stores/initalizer.js
Normal file
@ -0,0 +1,17 @@
|
||||
var FeatureToggleActions = require('./FeatureToggleActions');
|
||||
var StrategyActions = require('./StrategyActions');
|
||||
var Timer = require('../utils/Timer');
|
||||
|
||||
var _timer;
|
||||
|
||||
function load() {
|
||||
FeatureToggleActions.init.triggerPromise();
|
||||
StrategyActions.init.triggerPromise();
|
||||
FeatureToggleActions.initArchive.triggerPromise();
|
||||
}
|
||||
|
||||
module.exports = function(pollInterval) {
|
||||
var intervall = pollInterval || 30;
|
||||
_timer = new Timer(load, intervall*1000);
|
||||
_timer.start();
|
||||
};
|
Loading…
Reference in New Issue
Block a user