diff --git a/packages/unleash-frontend-next/.eslintignore b/packages/unleash-frontend-next/.eslintignore
new file mode 100644
index 0000000000..d1c1dbe227
--- /dev/null
+++ b/packages/unleash-frontend-next/.eslintignore
@@ -0,0 +1,2 @@
+node_modules
+bundle.js
diff --git a/packages/unleash-frontend-next/README.md b/packages/unleash-frontend-next/README.md
new file mode 100644
index 0000000000..9e0a26fa5b
--- /dev/null
+++ b/packages/unleash-frontend-next/README.md
@@ -0,0 +1,14 @@
+## Start developing:
+
+1. start mock-api:
+
+```bash
+npm run start:api
+```
+
+2. start webpack-dev-server with hot-reload:
+```bash
+npm run start
+```
+
+Happy coding!
diff --git a/packages/unleash-frontend-next/mock-api.json b/packages/unleash-frontend-next/mock-api.json
new file mode 100644
index 0000000000..18ac7346df
--- /dev/null
+++ b/packages/unleash-frontend-next/mock-api.json
@@ -0,0 +1,43 @@
+{
+ "features": {
+ "version": 1,
+ "features": [
+ {
+ "name": "Feature.A",
+ "description": "lorem ipsum",
+ "enabled": false,
+ "strategies": [
+ {
+ "name": "default",
+ "parameters": {}
+ }
+ ],
+ "strategy": "default",
+ "parameters": {}
+ },
+ {
+ "name": "Feature.B",
+ "description": "lorem ipsum",
+ "enabled": true,
+ "strategies": [
+ {
+ "name": "ActiveForUserWithId",
+ "parameters": {
+ "userIdList": "123,221,998"
+ }
+ },
+ {
+ "name": "GradualRolloutRandom",
+ "parameters": {
+ "percentage": "10"
+ }
+ }
+ ],
+ "strategy": "ActiveForUserWithId",
+ "parameters": {
+ "userIdList": "123,221,998"
+ }
+ }
+ ]
+ }
+}
diff --git a/packages/unleash-frontend-next/package.json b/packages/unleash-frontend-next/package.json
index 7d8c874830..5af42c2565 100644
--- a/packages/unleash-frontend-next/package.json
+++ b/packages/unleash-frontend-next/package.json
@@ -24,6 +24,8 @@
"scripts": {
"build": "webpack -p",
"start": "webpack-dev-server --config webpack.config.js --hot --progress --colors --port 3000",
+ "start:api": "json-server --watch mock-api.json -p 3001",
+ "lint": "eslint . --ext=js,jsx",
"test": "echo 'no test'",
"test:ci": "npm run test",
"prepublish": "npm run build"
@@ -38,7 +40,8 @@
"react-redux": "^4.4.5",
"react-router": "^2.8.0",
"react-toolbox": "^1.2.1",
- "redux": "^3.6.0"
+ "redux": "^3.6.0",
+ "redux-thunk": "^2.1.0"
},
"devDependencies": {
"babel-core": "^6.14.0",
@@ -49,7 +52,12 @@
"babel-preset-stage-0": "^6.5.0",
"babel-preset-stage-2": "^6.13.0",
"css-loader": "^0.25.0",
+ "eslint": "^3.4.0",
+ "eslint-config-finn": "1.0.0-alpha.11",
+ "eslint-config-finn-react": "^1.0.0-alpha.2",
+ "eslint-plugin-react": "^6.2.0",
"extract-text-webpack-plugin": "^1.0.1",
+ "json-server": "^0.8.21",
"node-sass": "~3.7.0",
"postcss-loader": "^0.13.0",
"redux-devtools": "^3.3.1",
diff --git a/packages/unleash-frontend-next/src/.eslintrc b/packages/unleash-frontend-next/src/.eslintrc
index b2d0257498..23031855c4 100644
--- a/packages/unleash-frontend-next/src/.eslintrc
+++ b/packages/unleash-frontend-next/src/.eslintrc
@@ -1,24 +1,12 @@
{
"extends": [
"finn",
- "finn-react"
+ "finn-react",
+ "finn/es-modules"
],
"env": {
"browser": true,
"commonjs": true,
"es6": true
- },
- "rules": {
- "react/sort-comp": "off"
- },
- "ecmaFeatures": {
- "sourceType": "module"
- },
- "parserOptions": {
- "sourceType": "module",
- "ecmaFeatures": {
- "jsx": true,
- "experimentalObjectRestSpread": true
- }
}
}
diff --git a/packages/unleash-frontend-next/src/action/index.js b/packages/unleash-frontend-next/src/action/index.js
deleted file mode 100644
index 69e53f0e88..0000000000
--- a/packages/unleash-frontend-next/src/action/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-let nextFeatureId = 0;
-export const addFeatureToggle = (featureName) => ({
- type: 'ADD_FEATURE_TOGGLE',
- id: nextFeatureId++,
- featureName,
-});
-
-export const toggleFeature = (id) => ({
- type: 'TOGGLE_FEATURE_TOGGLE',
- id,
-});
diff --git a/packages/unleash-frontend-next/src/component/feature/AddFeatureToggle.jsx b/packages/unleash-frontend-next/src/component/feature/AddFeatureToggle.jsx
index 1f569edf2c..2fb060e463 100644
--- a/packages/unleash-frontend-next/src/component/feature/AddFeatureToggle.jsx
+++ b/packages/unleash-frontend-next/src/component/feature/AddFeatureToggle.jsx
@@ -1,6 +1,6 @@
-import React from 'react';
+import { React, PropTypes } from 'react';
import { connect } from 'react-redux';
-import { addFeatureToggle } from '../../action';
+import { addFeatureToggle } from '../../store/actions';
let AddFeatureToggle = ({ dispatch }) => {
@@ -28,4 +28,9 @@ let AddFeatureToggle = ({ dispatch }) => {
};
AddFeatureToggle = connect()(AddFeatureToggle);
+AddFeatureToggle.propTypes = {
+ dispatch: PropTypes.func.isRequired,
+};
+
+
export default AddFeatureToggle;
diff --git a/packages/unleash-frontend-next/src/component/feature/Feature.jsx b/packages/unleash-frontend-next/src/component/feature/Feature.jsx
index 0b41e9c9f0..cb12dfd6c9 100644
--- a/packages/unleash-frontend-next/src/component/feature/Feature.jsx
+++ b/packages/unleash-frontend-next/src/component/feature/Feature.jsx
@@ -1,8 +1,10 @@
+/* eslint no-shadow: ["error", { "allow": ["name"] }]*/
+
import React, { PropTypes } from 'react';
-const Feature = ({ onClick, featureName, enabled }) => (
+const Feature = ({ onClick, name, enabled }) => (
- {featureName} is {enabled ? 'enabled ' : 'disabled '}
+ {name} is {enabled ? 'enabled ' : 'disabled '}
toggle
);
@@ -10,7 +12,7 @@ const Feature = ({ onClick, featureName, enabled }) => (
Feature.propTypes = {
onClick: PropTypes.func.isRequired,
enabled: PropTypes.bool.isRequired,
- featureName: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
};
export default Feature;
diff --git a/packages/unleash-frontend-next/src/component/feature/FeatureList.jsx b/packages/unleash-frontend-next/src/component/feature/FeatureList.jsx
index 55347eaab3..324f44ae96 100644
--- a/packages/unleash-frontend-next/src/component/feature/FeatureList.jsx
+++ b/packages/unleash-frontend-next/src/component/feature/FeatureList.jsx
@@ -1,21 +1,33 @@
import React, { PropTypes } from 'react';
import Feature from './Feature';
-const FeatureList = ({ features, onFeatureClick }) => (
-
- {features.map(featureToggle =>
- onFeatureClick(featureToggle.id)}
- />
- )}
-
-);
+export default class FeatureList extends React.Component {
+ constructor () {
+ super();
+ }
-FeatureList.propTypes = {
- onFeatureClick: PropTypes.func.isRequired,
- features: PropTypes.array.isRequired,
-};
+ static propTypes () {
+ return {
+ onFeatureClick: PropTypes.func.isRequired,
+ features: PropTypes.array.isRequired,
+ fetchFeatureToggles: PropTypes.array.isRequired,
+ };
+ }
-export default FeatureList;
+ componentDidMount () {
+ this.props.fetchFeatureToggles();
+ }
+
+ render () {
+ const onFeatureClick = this.props.onFeatureClick;
+ const features = this.props.features.map(featureToggle =>
+ onFeatureClick(featureToggle.name)}
+ />);
+
+ return (
+
+ );
+ }
+}
diff --git a/packages/unleash-frontend-next/src/component/feature/FeatureListContainer.jsx b/packages/unleash-frontend-next/src/component/feature/FeatureListContainer.jsx
index 416fa3a5fa..951a9514e0 100644
--- a/packages/unleash-frontend-next/src/component/feature/FeatureListContainer.jsx
+++ b/packages/unleash-frontend-next/src/component/feature/FeatureListContainer.jsx
@@ -1,17 +1,15 @@
import { connect } from 'react-redux';
-import { toggleFeature } from '../../action';
+import { toggleFeature, fetchFeatureToggles } from '../../store/actions';
import FeatureList from './FeatureList';
const mapStateToProps = (state) => ({
features: state.features,
});
-const mapDispatchToProps = (dispatch) => ({
- onFeatureClick: (id) => {
- dispatch(toggleFeature(id));
- },
-});
-
+const mapDispatchToProps = {
+ onFeatureClick: toggleFeature,
+ fetchFeatureToggles,
+};
const FeatureListContainer = connect(
mapStateToProps,
diff --git a/packages/unleash-frontend-next/src/index.jsx b/packages/unleash-frontend-next/src/index.jsx
index badafc5a8c..7b13bbe94d 100644
--- a/packages/unleash-frontend-next/src/index.jsx
+++ b/packages/unleash-frontend-next/src/index.jsx
@@ -2,7 +2,8 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, IndexRoute, hashHistory } from 'react-router';
import { Provider } from 'react-redux';
-import { createStore } from 'redux';
+import thunkMiddleware from 'redux-thunk';
+import { createStore, applyMiddleware } from 'redux';
import store from './store';
import App from './App';
@@ -12,7 +13,12 @@ import Strategies from './page/strategies';
import HistoryPage from './page/history';
import Archive from './page/archive';
-const unleashStore = createStore(store);
+const unleashStore = createStore(
+ store,
+ applyMiddleware(
+ thunkMiddleware
+ )
+);
ReactDOM.render(
diff --git a/packages/unleash-frontend-next/src/store/actions.js b/packages/unleash-frontend-next/src/store/actions.js
new file mode 100644
index 0000000000..e55e6538b9
--- /dev/null
+++ b/packages/unleash-frontend-next/src/store/actions.js
@@ -0,0 +1,56 @@
+export const ADD_FEATURE_TOGGLE = 'ADD_FEATURE_TOGGLE';
+export const TOGGLE_FEATURE_TOGGLE = 'TOGGLE_FEATURE_TOGGLE';
+export const REQUEST_FEATURE_TOGGLES = 'REQUEST_FEATURE_TOGGLES';
+export const RECEIVE_FEATURE_TOGGLES = 'RECEIVE_FEATURE_TOGGLES';
+export const ERROR_RECEIVE_FEATURE_TOGGLES = 'ERROR_RECEIVE_FEATURE_TOGGLES';
+
+export const addFeatureToggle = (featureName) => ({
+ type: 'ADD_FEATURE_TOGGLE',
+ name: featureName,
+});
+
+export const toggleFeature = (featureName) => ({
+ type: 'TOGGLE_FEATURE_TOGGLE',
+ name: featureName,
+});
+
+
+function requestFeatureToggles () {
+ return {
+ type: REQUEST_FEATURE_TOGGLES,
+ };
+}
+
+function receiveFeatureToggles (json) {
+ return {
+ type: RECEIVE_FEATURE_TOGGLES,
+ featureToggles: json.features.map(features => features),
+ receivedAt: Date.now(),
+ };
+}
+
+function errorReceiveFeatureToggles (statusCode) {
+ return {
+ type: ERROR_RECEIVE_FEATURE_TOGGLES,
+ statusCode,
+ receivedAt: Date.now(),
+ };
+}
+
+export function fetchFeatureToggles () {
+ return dispatch => {
+ dispatch(requestFeatureToggles());
+ return fetch('/features')
+ .then(response => {
+ if (response.ok) {
+ return response.json();
+ } else {
+ let error = new Error('failed fetching');
+ error.status = response.status;
+ throw error;
+ }
+ })
+ .then(json => dispatch(receiveFeatureToggles(json)))
+ .catch(error => dispatch(errorReceiveFeatureToggles(error)));
+ };
+}
diff --git a/packages/unleash-frontend-next/src/store/features.js b/packages/unleash-frontend-next/src/store/features.js
index e4a838ea43..04570fab96 100644
--- a/packages/unleash-frontend-next/src/store/features.js
+++ b/packages/unleash-frontend-next/src/store/features.js
@@ -1,13 +1,18 @@
+import {
+ ADD_FEATURE_TOGGLE,
+ TOGGLE_FEATURE_TOGGLE,
+ RECEIVE_FEATURE_TOGGLES,
+} from './actions';
+
const feature = (state = {}, action) => {
switch (action.type) {
- case 'ADD_FEATURE_TOGGLE':
+ case ADD_FEATURE_TOGGLE:
return {
- id: action.id,
- featureName: action.featureName,
+ name: action.name,
enabled: false,
};
- case 'TOGGLE_FEATURE_TOGGLE':
- if (state.id !== action.id) {
+ case TOGGLE_FEATURE_TOGGLE:
+ if (state.name !== action.name) {
return state;
}
@@ -20,17 +25,20 @@ const feature = (state = {}, action) => {
}
};
-const features = (state = [{ id: 1, featureName: 'test', enabled: true }], action) => {
+const features = (state = [], action) => {
switch (action.type) {
- case 'ADD_FEATURE_TOGGLE':
+ case ADD_FEATURE_TOGGLE:
return [
...state,
feature(undefined, action),
];
- case 'TOGGLE_FEATURE_TOGGLE':
+ case TOGGLE_FEATURE_TOGGLE:
return state.map(t =>
feature(t, action)
);
+ case RECEIVE_FEATURE_TOGGLES: {
+ return action.featureToggles;
+ }
default:
return state;
}
diff --git a/packages/unleash-frontend-next/webpack.config.js b/packages/unleash-frontend-next/webpack.config.js
index 47c07c188e..a159e39a86 100644
--- a/packages/unleash-frontend-next/webpack.config.js
+++ b/packages/unleash-frontend-next/webpack.config.js
@@ -56,4 +56,12 @@ module.exports = {
// stuff not in node_modules can be resolved here.
},
+ devServer: {
+ proxy: {
+ '/features': {
+ target: 'http://localhost:3001/',
+ secure: false,
+ },
+ },
+ },
};