mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +01:00
Added support for fetching data.
- use "json-server" for serving mocked API responses. - setup redux to fetch responses when featureToggle list is mounted.
This commit is contained in:
parent
c89b91a84c
commit
ecdc916a3b
2
packages/unleash-frontend-next/.eslintignore
Normal file
2
packages/unleash-frontend-next/.eslintignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
bundle.js
|
14
packages/unleash-frontend-next/README.md
Normal file
14
packages/unleash-frontend-next/README.md
Normal file
@ -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!
|
43
packages/unleash-frontend-next/mock-api.json
Normal file
43
packages/unleash-frontend-next/mock-api.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack -p",
|
"build": "webpack -p",
|
||||||
"start": "webpack-dev-server --config webpack.config.js --hot --progress --colors --port 3000",
|
"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": "echo 'no test'",
|
||||||
"test:ci": "npm run test",
|
"test:ci": "npm run test",
|
||||||
"prepublish": "npm run build"
|
"prepublish": "npm run build"
|
||||||
@ -38,7 +40,8 @@
|
|||||||
"react-redux": "^4.4.5",
|
"react-redux": "^4.4.5",
|
||||||
"react-router": "^2.8.0",
|
"react-router": "^2.8.0",
|
||||||
"react-toolbox": "^1.2.1",
|
"react-toolbox": "^1.2.1",
|
||||||
"redux": "^3.6.0"
|
"redux": "^3.6.0",
|
||||||
|
"redux-thunk": "^2.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.14.0",
|
"babel-core": "^6.14.0",
|
||||||
@ -49,7 +52,12 @@
|
|||||||
"babel-preset-stage-0": "^6.5.0",
|
"babel-preset-stage-0": "^6.5.0",
|
||||||
"babel-preset-stage-2": "^6.13.0",
|
"babel-preset-stage-2": "^6.13.0",
|
||||||
"css-loader": "^0.25.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",
|
"extract-text-webpack-plugin": "^1.0.1",
|
||||||
|
"json-server": "^0.8.21",
|
||||||
"node-sass": "~3.7.0",
|
"node-sass": "~3.7.0",
|
||||||
"postcss-loader": "^0.13.0",
|
"postcss-loader": "^0.13.0",
|
||||||
"redux-devtools": "^3.3.1",
|
"redux-devtools": "^3.3.1",
|
||||||
|
@ -1,24 +1,12 @@
|
|||||||
{
|
{
|
||||||
"extends": [
|
"extends": [
|
||||||
"finn",
|
"finn",
|
||||||
"finn-react"
|
"finn-react",
|
||||||
|
"finn/es-modules"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"commonjs": true,
|
"commonjs": true,
|
||||||
"es6": true
|
"es6": true
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"react/sort-comp": "off"
|
|
||||||
},
|
|
||||||
"ecmaFeatures": {
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"sourceType": "module",
|
|
||||||
"ecmaFeatures": {
|
|
||||||
"jsx": true,
|
|
||||||
"experimentalObjectRestSpread": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
|
||||||
});
|
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import { React, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { addFeatureToggle } from '../../action';
|
import { addFeatureToggle } from '../../store/actions';
|
||||||
|
|
||||||
|
|
||||||
let AddFeatureToggle = ({ dispatch }) => {
|
let AddFeatureToggle = ({ dispatch }) => {
|
||||||
@ -28,4 +28,9 @@ let AddFeatureToggle = ({ dispatch }) => {
|
|||||||
};
|
};
|
||||||
AddFeatureToggle = connect()(AddFeatureToggle);
|
AddFeatureToggle = connect()(AddFeatureToggle);
|
||||||
|
|
||||||
|
AddFeatureToggle.propTypes = {
|
||||||
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export default AddFeatureToggle;
|
export default AddFeatureToggle;
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
/* eslint no-shadow: ["error", { "allow": ["name"] }]*/
|
||||||
|
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
|
||||||
const Feature = ({ onClick, featureName, enabled }) => (
|
const Feature = ({ onClick, name, enabled }) => (
|
||||||
<li>
|
<li>
|
||||||
{featureName} is {enabled ? 'enabled ' : 'disabled '}
|
{name} is {enabled ? 'enabled ' : 'disabled '}
|
||||||
<a onClick={onClick} href="#">toggle</a>
|
<a onClick={onClick} href="#">toggle</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
@ -10,7 +12,7 @@ const Feature = ({ onClick, featureName, enabled }) => (
|
|||||||
Feature.propTypes = {
|
Feature.propTypes = {
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
enabled: PropTypes.bool.isRequired,
|
enabled: PropTypes.bool.isRequired,
|
||||||
featureName: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Feature;
|
export default Feature;
|
||||||
|
@ -1,21 +1,33 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import Feature from './Feature';
|
import Feature from './Feature';
|
||||||
|
|
||||||
const FeatureList = ({ features, onFeatureClick }) => (
|
export default class FeatureList extends React.Component {
|
||||||
<ul>
|
constructor () {
|
||||||
{features.map(featureToggle =>
|
super();
|
||||||
<Feature
|
}
|
||||||
key={featureToggle.id}
|
|
||||||
{...featureToggle}
|
|
||||||
onClick={() => onFeatureClick(featureToggle.id)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
|
|
||||||
FeatureList.propTypes = {
|
static propTypes () {
|
||||||
|
return {
|
||||||
onFeatureClick: PropTypes.func.isRequired,
|
onFeatureClick: PropTypes.func.isRequired,
|
||||||
features: PropTypes.array.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 =>
|
||||||
|
<Feature key={featureToggle.name}
|
||||||
|
{...featureToggle}
|
||||||
|
onClick={() => onFeatureClick(featureToggle.name)}
|
||||||
|
/>);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul>{features}</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { toggleFeature } from '../../action';
|
import { toggleFeature, fetchFeatureToggles } from '../../store/actions';
|
||||||
import FeatureList from './FeatureList';
|
import FeatureList from './FeatureList';
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
features: state.features,
|
features: state.features,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = {
|
||||||
onFeatureClick: (id) => {
|
onFeatureClick: toggleFeature,
|
||||||
dispatch(toggleFeature(id));
|
fetchFeatureToggles,
|
||||||
},
|
};
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const FeatureListContainer = connect(
|
const FeatureListContainer = connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
|
@ -2,7 +2,8 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Router, Route, IndexRoute, hashHistory } from 'react-router';
|
import { Router, Route, IndexRoute, hashHistory } from 'react-router';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { createStore } from 'redux';
|
import thunkMiddleware from 'redux-thunk';
|
||||||
|
import { createStore, applyMiddleware } from 'redux';
|
||||||
|
|
||||||
import store from './store';
|
import store from './store';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
@ -12,7 +13,12 @@ import Strategies from './page/strategies';
|
|||||||
import HistoryPage from './page/history';
|
import HistoryPage from './page/history';
|
||||||
import Archive from './page/archive';
|
import Archive from './page/archive';
|
||||||
|
|
||||||
const unleashStore = createStore(store);
|
const unleashStore = createStore(
|
||||||
|
store,
|
||||||
|
applyMiddleware(
|
||||||
|
thunkMiddleware
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={unleashStore}>
|
<Provider store={unleashStore}>
|
||||||
|
56
packages/unleash-frontend-next/src/store/actions.js
Normal file
56
packages/unleash-frontend-next/src/store/actions.js
Normal file
@ -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)));
|
||||||
|
};
|
||||||
|
}
|
@ -1,13 +1,18 @@
|
|||||||
|
import {
|
||||||
|
ADD_FEATURE_TOGGLE,
|
||||||
|
TOGGLE_FEATURE_TOGGLE,
|
||||||
|
RECEIVE_FEATURE_TOGGLES,
|
||||||
|
} from './actions';
|
||||||
|
|
||||||
const feature = (state = {}, action) => {
|
const feature = (state = {}, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'ADD_FEATURE_TOGGLE':
|
case ADD_FEATURE_TOGGLE:
|
||||||
return {
|
return {
|
||||||
id: action.id,
|
name: action.name,
|
||||||
featureName: action.featureName,
|
|
||||||
enabled: false,
|
enabled: false,
|
||||||
};
|
};
|
||||||
case 'TOGGLE_FEATURE_TOGGLE':
|
case TOGGLE_FEATURE_TOGGLE:
|
||||||
if (state.id !== action.id) {
|
if (state.name !== action.name) {
|
||||||
return state;
|
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) {
|
switch (action.type) {
|
||||||
case 'ADD_FEATURE_TOGGLE':
|
case ADD_FEATURE_TOGGLE:
|
||||||
return [
|
return [
|
||||||
...state,
|
...state,
|
||||||
feature(undefined, action),
|
feature(undefined, action),
|
||||||
];
|
];
|
||||||
case 'TOGGLE_FEATURE_TOGGLE':
|
case TOGGLE_FEATURE_TOGGLE:
|
||||||
return state.map(t =>
|
return state.map(t =>
|
||||||
feature(t, action)
|
feature(t, action)
|
||||||
);
|
);
|
||||||
|
case RECEIVE_FEATURE_TOGGLES: {
|
||||||
|
return action.featureToggles;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -56,4 +56,12 @@ module.exports = {
|
|||||||
// stuff not in node_modules can be resolved here.
|
// stuff not in node_modules can be resolved here.
|
||||||
},
|
},
|
||||||
|
|
||||||
|
devServer: {
|
||||||
|
proxy: {
|
||||||
|
'/features': {
|
||||||
|
target: 'http://localhost:3001/',
|
||||||
|
secure: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user