Merge pull request #649 from weaveworks/node-upgrade

Upgrade JS node env and dependencies
This commit is contained in:
David
2015-11-12 12:14:34 +01:00
21 changed files with 294 additions and 504 deletions
+1 -1
View File
@@ -36,8 +36,8 @@ scope.tar
scope_ui_build.tar
app/app
app/scope-app
probe/probe
probe/scope-probe
prog/probe/probe
prog/probe/scope-probe
docker/scope-app
docker/scope-probe
+3
View File
@@ -0,0 +1,3 @@
{
"presets": ["es2015", "react"]
}
-1
View File
@@ -1 +0,0 @@
app/**/__tests__/*.js
+10 -209
View File
@@ -1,216 +1,17 @@
{
"parser": "babel-eslint",
"plugins": [
"react",
"jasmine"
],
"env": {
"browser": true,
"node": true
},
"ecmaFeatures": {
"arrowFunctions": true,
"blockBindings": true,
"classes": true,
"defaultParams": true,
"destructuring": true,
"forOf": true,
"generators": false,
"modules": true,
"objectLiteralComputedProperties": true,
"objectLiteralDuplicateProperties": false,
"objectLiteralShorthandMethods": true,
"objectLiteralShorthandProperties": true,
"spread": true,
"superInFunctions": true,
"templateStrings": true,
"jsx": true
},
"extends": "airbnb",
"globals": {
__WS_URL__: false
},
"env": {
"browser": true,
"jest": true,
"node": true
},
"rules": {
/**
* Strict mode
*/
// babel inserts "use strict"; for us
// http://eslint.org/docs/rules/strict
"strict": [2, "never"],
/**
* ES6
*/
"no-var": 2, // http://eslint.org/docs/rules/no-var
/**
* Variables
*/
"no-shadow": 2, // http://eslint.org/docs/rules/no-shadow
"no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names
"no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars
"vars": "local",
"args": "after-used"
}],
"no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define
/**
* Possible errors
*/
"comma-dangle": [2, "never"], // http://eslint.org/docs/rules/comma-dangle
"no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign
"no-console": 1, // http://eslint.org/docs/rules/no-console
"no-debugger": 1, // http://eslint.org/docs/rules/no-debugger
"no-alert": 1, // http://eslint.org/docs/rules/no-alert
"no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition
"no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys
"no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case
"no-empty": 2, // http://eslint.org/docs/rules/no-empty
"no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign
"no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast
"no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi
"no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign
"no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations
"no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp
"no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace
"no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls
"no-reserved-keys": 2, // http://eslint.org/docs/rules/no-reserved-keys
"no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays
"no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable
"use-isnan": 2, // http://eslint.org/docs/rules/use-isnan
"block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var
/**
* Best practices
*/
"consistent-return": 2, // http://eslint.org/docs/rules/consistent-return
"curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly
"default-case": 2, // http://eslint.org/docs/rules/default-case
"dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation
"allowKeywords": true
}],
"eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq
"guard-for-in": 2, // http://eslint.org/docs/rules/guard-for-in
"no-caller": 2, // http://eslint.org/docs/rules/no-caller
"no-else-return": 2, // http://eslint.org/docs/rules/no-else-return
"no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null
"no-eval": 2, // http://eslint.org/docs/rules/no-eval
"no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native
"no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind
"no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough
"no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal
"no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval
"no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks
"no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func
"no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str
"no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign
"no-new": 2, // http://eslint.org/docs/rules/no-new
"no-new-func": 2, // http://eslint.org/docs/rules/no-new-func
"no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers
"no-octal": 2, // http://eslint.org/docs/rules/no-octal
"no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape
"no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign
"no-proto": 2, // http://eslint.org/docs/rules/no-proto
"no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare
"no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign
"no-script-url": 2, // http://eslint.org/docs/rules/no-script-url
"no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare
"no-sequences": 2, // http://eslint.org/docs/rules/no-sequences
"no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal
"no-with": 2, // http://eslint.org/docs/rules/no-with
"radix": 2, // http://eslint.org/docs/rules/radix
"vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top
"wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife
"yoda": 2, // http://eslint.org/docs/rules/yoda
/**
* Style
*/
"indent": [2, 2], // http://eslint.org/docs/rules/
"brace-style": [2, // http://eslint.org/docs/rules/brace-style
"1tbs", {
"allowSingleLine": true
}],
"quotes": [
2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes
],
"camelcase": [2, { // http://eslint.org/docs/rules/camelcase
"properties": "never"
}],
"comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing
"before": false,
"after": true
}],
"comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style
"eol-last": 2, // http://eslint.org/docs/rules/eol-last
"func-names": 0, // http://eslint.org/docs/rules/func-names
"key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing
"beforeColon": false,
"afterColon": true
}],
"new-cap": [2, { // http://eslint.org/docs/rules/new-cap
"newIsCap": true
}],
"no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines
"max": 2
}],
"no-nested-ternary": 2, // http://eslint.org/docs/rules/no-nested-ternary
"no-new-object": 2, // http://eslint.org/docs/rules/no-new-object
"no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func
"no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces
"no-wrap-func": 2, // http://eslint.org/docs/rules/no-wrap-func
"no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle
"one-var": [2, "never"], // http://eslint.org/docs/rules/one-var
"padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks
"semi": [2, "always"], // http://eslint.org/docs/rules/semi
"semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing
"before": false,
"after": true
}],
"space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords
"space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks
"space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren
"space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops
"space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case
"spaced-line-comment": 2, // http://eslint.org/docs/rules/spaced-line-comment
/**
* JSX style
*/
"react/display-name": 0,
"react/jsx-boolean-value": 2,
"react/jsx-quotes": [2, "double"],
"react/jsx-no-undef": 2,
"react/jsx-sort-props": 0,
"react/jsx-sort-prop-types": 0,
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/no-did-mount-set-state": [2, "allow-in-func"],
"react/no-did-update-set-state": 2,
"react/no-multi-comp": 2,
"react/no-unknown-property": 2,
"react/prop-types": 0,
"react/react-in-jsx-scope": 2,
"react/self-closing-comp": 2,
"react/wrap-multilines": 2,
"react/sort-comp": [2, {
"order": [
"displayName",
"mixins",
"statics",
"propTypes",
"getDefaultProps",
"getInitialState",
"componentWillMount",
"componentDidMount",
"componentWillReceiveProps",
"shouldComponentUpdate",
"componentWillUpdate",
"componentWillUnmount",
"/^on.+$/",
"/^get.+$/",
"/^render.+$/",
"render"
]
}]
"comma-dangle": 0,
"func-names": 0,
"react/sort-comp": 0,
"react/prop-types": 0
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
FROM node:0.10
FROM node:4.2.2
WORKDIR /home/weave
COPY package.json /home/weave/
# Dont install optional developer tools
RUN npm install --no-optional
COPY webpack.local.config.js webpack.production.config.js server.js .eslintrc .eslintignore /home/weave/
COPY webpack.local.config.js webpack.production.config.js server.js .babelrc .eslintrc /home/weave/
+1 -1
View File
@@ -85,7 +85,7 @@ module.exports = {
WebapiUtils.doControl(
probeId,
nodeId,
control,
control
);
},
@@ -3,6 +3,8 @@ jest.dontMock('../../constants/naming'); // edge naming: 'source-target'
import { fromJS, Map } from 'immutable';
const makeMap = Map;
describe('NodesLayout', () => {
const NodesLayout = require('../nodes-layout');
@@ -72,8 +74,8 @@ describe('NodesLayout', () => {
beforeEach(() => {
options = {
nodeCache: Map(),
edgeCache: Map()
nodeCache: makeMap(),
edgeCache: makeMap()
};
});
@@ -187,8 +189,8 @@ describe('NodesLayout', () => {
nodes = result.nodes.toJS();
resultCoords = getNodeCoordinates(result.nodes);
expect(resultCoords.slice(0,2)).toEqual(coords.slice(0,2));
expect(resultCoords.slice(2,6)).toEqual(coords.slice(4,8));
expect(resultCoords.slice(0, 2)).toEqual(coords.slice(0, 2));
expect(resultCoords.slice(2, 6)).toEqual(coords.slice(4, 8));
});
it('keeps nodes in rectangle after removed node reappears', () => {
@@ -227,8 +229,7 @@ describe('NodesLayout', () => {
// console.log('re-add 1 node', nodes);
resultCoords = getNodeCoordinates(result.nodes);
expect(resultCoords.slice(0,2)).toEqual(coords.slice(0,2));
expect(resultCoords.slice(2,6)).toEqual(coords.slice(4,8));
expect(resultCoords.slice(0, 2)).toEqual(coords.slice(0, 2));
expect(resultCoords.slice(2, 6)).toEqual(coords.slice(4, 8));
});
});
+7 -6
View File
@@ -1,7 +1,8 @@
const _ = require('lodash');
const d3 = require('d3');
const React = require('react');
const Spring = require('react-motion').Spring;
const Motion = require('react-motion').Motion;
const spring = require('react-motion').spring;
const AppActions = require('../actions/app-actions');
@@ -15,8 +16,8 @@ const animConfig = [80, 20]; // stiffness, bounce
const flattenPoints = function(points) {
const flattened = {};
points.forEach(function(point, i) {
flattened['x' + i] = {val: point.x, config: animConfig};
flattened['y' + i] = {val: point.y, config: animConfig};
flattened['x' + i] = spring(point.x, animConfig);
flattened['y' + i] = spring(point.y, animConfig);
});
return flattened;
};
@@ -29,7 +30,7 @@ const extractPoints = function(points) {
if (!extracted[index]) {
extracted[index] = {};
}
extracted[index][axis] = value.val;
extracted[index][axis] = value;
});
return extracted;
};
@@ -66,7 +67,7 @@ const Edge = React.createClass({
const classes = classNames.join(' ');
return (
<Spring endValue={points}>
<Motion style={points}>
{function(interpolated) {
const path = line(extractPoints(interpolated));
return (
@@ -76,7 +77,7 @@ const Edge = React.createClass({
</g>
);
}}
</Spring>
</Motion>
);
},
+14 -13
View File
@@ -1,5 +1,6 @@
const React = require('react');
const Spring = require('react-motion').Spring;
const Motion = require('react-motion').Motion;
const spring = require('react-motion').spring;
const AppActions = require('../actions/app-actions');
const NodeColorMixin = require('../mixins/node-color-mixin');
@@ -42,30 +43,30 @@ const Node = React.createClass({
const classes = classNames.join(' ');
return (
<Spring endValue={{
x: {val: this.props.dx, config: animConfig},
y: {val: this.props.dy, config: animConfig},
f: {val: scaleFactor, config: animConfig}
<Motion style={{
x: spring(this.props.dx, animConfig),
y: spring(this.props.dy, animConfig),
f: spring(scaleFactor, animConfig)
}}>
{function(interpolated) {
const transform = `translate(${interpolated.x.val},${interpolated.y.val})`;
const transform = `translate(${interpolated.x},${interpolated.y})`;
return (
<g className={classes} transform={transform} id={props.id}
onClick={onMouseClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
{props.highlighted && <circle r={scale(0.7 * interpolated.f.val)} className="highlighted"></circle>}
<circle r={scale(0.5 * interpolated.f.val)} className="border" stroke={color}></circle>
<circle r={scale(0.45 * interpolated.f.val)} className="shadow"></circle>
<circle r={Math.max(2, scale(0.125 * interpolated.f.val))} className="node"></circle>
<text className="node-label" textAnchor="middle" x="0" y={labelOffsetY + scale(0.5 * interpolated.f.val)}>
{props.highlighted && <circle r={scale(0.7 * interpolated.f)} className="highlighted"></circle>}
<circle r={scale(0.5 * interpolated.f)} className="border" stroke={color}></circle>
<circle r={scale(0.45 * interpolated.f)} className="shadow"></circle>
<circle r={Math.max(2, scale(0.125 * interpolated.f))} className="node"></circle>
<text className="node-label" textAnchor="middle" x="0" y={labelOffsetY + scale(0.5 * interpolated.f)}>
{label}
</text>
<text className="node-sublabel" textAnchor="middle" x="0" y={subLabelOffsetY + scale(0.5 * interpolated.f.val)}>
<text className="node-sublabel" textAnchor="middle" x="0" y={subLabelOffsetY + scale(0.5 * interpolated.f)}>
{subLabel}
</text>
</g>
);
}}
</Spring>
</Motion>
);
},
+9 -7
View File
@@ -4,7 +4,8 @@ const debug = require('debug')('scope:nodes-chart');
const React = require('react');
const makeMap = require('immutable').Map;
const timely = require('timely');
const Spring = require('react-motion').Spring;
const Motion = require('react-motion').Motion;
const spring = require('react-motion').spring;
const AppActions = require('../actions/app-actions');
const AppStore = require('../stores/app-store');
@@ -199,7 +200,7 @@ const NodesChart = React.createClass({
render: function() {
const nodeElements = this.renderGraphNodes(this.state.nodes, this.state.nodeScale);
const edgeElements = this.renderGraphEdges(this.state.edges, this.state.nodeScale);
let scale = this.state.scale;
const scale = this.state.scale;
// only animate shift behavior, not panning
const panTranslate = this.state.panTranslate;
@@ -213,15 +214,16 @@ const NodesChart = React.createClass({
const svgClassNames = this.state.maxNodesExceeded || nodeElements.size === 0 ? 'hide' : '';
const errorEmpty = this.renderEmptyTopologyError(AppStore.isTopologyEmpty());
const errorMaxNodesExceeded = this.renderMaxNodesError(this.state.maxNodesExceeded);
const motionConfig = [80, 20];
return (
<div className="nodes-chart">
{errorEmpty}
{errorMaxNodesExceeded}
<svg width="100%" height="100%" className={svgClassNames} onClick={this.handleMouseClick}>
<Spring endValue={{val: translate, config: [80, 20]}}>
<Motion style={{x: spring(translate[0], motionConfig), y: spring(translate[1], motionConfig)}}>
{function(interpolated) {
let interpolatedTranslate = wasShifted ? interpolated.val : panTranslate;
const interpolatedTranslate = wasShifted ? [interpolated.x, interpolated.y] : panTranslate;
const transform = 'translate(' + interpolatedTranslate + ')' +
' scale(' + scale + ')';
return (
@@ -235,7 +237,7 @@ const NodesChart = React.createClass({
</g>
);
}}
</Spring>
</Motion>
</svg>
</div>
);
@@ -291,14 +293,14 @@ const NodesChart = React.createClass({
centerSelectedNode: function(props, state) {
let stateNodes = state.nodes;
let stateEdges = state.edges;
let selectedLayoutNode = stateNodes.get(props.selectedNodeId);
const selectedLayoutNode = stateNodes.get(props.selectedNodeId);
if (!selectedLayoutNode) {
return {};
}
const adjacency = AppStore.getAdjacentNodes(props.selectedNodeId);
let adjacentLayoutNodeIds = [];
const adjacentLayoutNodeIds = [];
adjacency.forEach(function(adjacentId) {
// filter loopback
+1 -1
View File
@@ -7,7 +7,7 @@ const NodesError = React.createClass({
if (this.props.hidden) {
classNames += ' hide';
}
let iconClassName = 'fa ' + this.props.faIconClass;
const iconClassName = 'fa ' + this.props.faIconClass;
return (
<div className={classNames}>
+1 -1
View File
@@ -181,7 +181,7 @@ function hasSameEndpoints(cachedEdge, nodes) {
* @return {Object} layout clone
*/
function cloneLayout(layout, nodes, edges) {
const clone = {...layout, nodes, edges};
const clone = Object.assign({}, layout, {nodes, edges});
return clone;
}
@@ -2,8 +2,6 @@ jest.dontMock('../node-details.js');
jest.dontMock('../../mixins/node-color-mixin');
jest.dontMock('../../utils/title-utils');
__WS_URL__ = false
describe('NodeDetails', () => {
let NodeDetails;
let nodes;
@@ -12,10 +10,11 @@ describe('NodeDetails', () => {
const React = require('react');
const Immutable = require('immutable');
const TestUtils = require('react/lib/ReactTestUtils');
const makeMap = Immutable.OrderedMap;
beforeEach(() => {
NodeDetails = require('../node-details.js');
nodes = Immutable.OrderedMap();
nodes = makeMap();
nodeId = 'n1';
});
@@ -28,10 +27,10 @@ describe('NodeDetails', () => {
it('show label of node with title', () => {
nodes = nodes.set(nodeId, Immutable.fromJS({id: nodeId}));
details = {label_major: 'Node 1', tables: []};
const c = TestUtils.renderIntoDocument(<NodeDetails nodes={nodes} nodeId={nodeId} details={details} />);
const c = TestUtils.renderIntoDocument(<NodeDetails nodes={nodes}
nodeId={nodeId} details={details} />);
const title = TestUtils.findRenderedDOMComponentWithClass(c, 'node-details-header-label');
expect(title.getDOMNode().textContent).toBe('Node 1');
expect(title.textContent).toBe('Node 1');
});
});
-12
View File
@@ -1,5 +1,4 @@
const React = require('react');
const mui = require('material-ui');
const Logo = require('./logo');
const AppStore = require('../stores/app-store');
@@ -13,8 +12,6 @@ const Details = require('./details');
const Nodes = require('./nodes');
const RouterUtils = require('../utils/router-utils');
const ThemeManager = new mui.Styles.ThemeManager();
const ESC_KEY_CODE = 27;
function getStateFromStores() {
@@ -67,12 +64,6 @@ const App = React.createClass({
}
},
getChildContext: function() {
return {
muiTheme: ThemeManager.getCurrentTheme()
};
},
render: function() {
const showingDetails = this.state.selectedNodeId;
const versionString = this.state.version ? 'Version ' + this.state.version : '';
@@ -115,9 +106,6 @@ const App = React.createClass({
);
},
childContextTypes: {
muiTheme: React.PropTypes.object
}
});
module.exports = App;
+4 -4
View File
@@ -1,6 +1,4 @@
const React = require('react');
const mui = require('material-ui');
const Paper = mui.Paper;
const NodeDetails = require('./node-details');
@@ -9,9 +7,11 @@ const Details = React.createClass({
render: function() {
return (
<div id="details">
<Paper zDepth={3} style={{height: '100%', paddingBottom: 8}}>
<div style={{height: '100%', paddingBottom: 8, borderRadius: 2,
backgroundColor: '#fff',
boxShadow: '0 10px 30px rgba(0, 0, 0, 0.19), 0 6px 10px rgba(0, 0, 0, 0.23)'}}>
<NodeDetails {...this.props} />
</Paper>
</div>
</div>
);
}
+2 -1
View File
@@ -2,9 +2,10 @@ require('font-awesome-webpack');
require('../styles/main.less');
const React = require('react');
const ReactDOM = require('react-dom');
const App = require('./components/app.js');
React.render(
ReactDOM.render(
<App/>,
document.getElementById('app'));
@@ -66,11 +66,6 @@ describe('AppStore', function() {
topologyId: 'topo2'
};
const ClickGroupingAction = {
type: ActionTypes.CLICK_GROUPING,
grouping: 'grouped'
};
const CloseWebsocketAction = {
type: ActionTypes.CLOSE_WEBSOCKET
};
@@ -200,9 +195,9 @@ describe('AppStore', function() {
it('sets topology options from route', function() {
RouteAction.state = {
"topologyId":"topo1",
"selectedNodeId": null,
"topologyOptions": {'topo1':{'option1': 'on'}}};
'topologyId': 'topo1',
'selectedNodeId': null,
'topologyOptions': {'topo1': {'option1': 'on'}}};
registeredCallback(RouteAction);
expect(AppStore.getActiveTopologyOptions().get('option1')).toBe('on');
expect(AppStore.getAppState().topologyOptions.topo1.option1).toBe('on');
@@ -216,9 +211,9 @@ describe('AppStore', function() {
it('uses default topology options from route', function() {
RouteAction.state = {
"topologyId":"topo1",
"selectedNodeId": null,
"topologyOptions": null};
'topologyId': 'topo1',
'selectedNodeId': null,
'topologyOptions': null};
registeredCallback(RouteAction);
registeredCallback(ReceiveTopologiesAction);
registeredCallback(ClickTopologyAction);
@@ -255,7 +250,7 @@ describe('AppStore', function() {
expect(AppStore.getSelectedNodeId()).toBe('n1');
expect(AppStore.getNodes().toJS()).toEqual(NODE_SET);
registeredCallback(HitEscAction)
registeredCallback(HitEscAction);
expect(AppStore.getSelectedNodeId()).toBe(null);
expect(AppStore.getNodes().toJS()).toEqual(NODE_SET);
});
@@ -271,7 +266,7 @@ describe('AppStore', function() {
expect(AppStore.getAppState().selectedNodeId).toEqual('n1');
// go back in browsing
RouteAction.state = {"topologyId":"topo1","selectedNodeId": null};
RouteAction.state = {'topologyId': 'topo1', 'selectedNodeId': null};
registeredCallback(RouteAction);
expect(AppStore.getSelectedNodeId()).toBe(null);
expect(AppStore.getNodes().toJS()).toEqual(NODE_SET);
@@ -322,7 +317,7 @@ describe('AppStore', function() {
expect(AppStore.getAdjacentNodes('n1').has('n1')).toBeTruthy();
expect(AppStore.getAdjacentNodes('n1').has('n2')).toBeTruthy();
registeredCallback(HitEscAction)
registeredCallback(HitEscAction);
expect(AppStore.getAdjacentNodes().size).toEqual(0);
});
@@ -339,5 +334,4 @@ describe('AppStore', function() {
registeredCallback(ClickTopologyAction);
expect(AppStore.isTopologyEmpty()).toBeFalsy();
});
});
+167 -168
View File
@@ -1,6 +1,5 @@
const EventEmitter = require('events').EventEmitter;
const _ = require('lodash');
const assign = require('object-assign');
const debug = require('debug')('scope:app-store');
const Immutable = require('immutable');
@@ -100,7 +99,7 @@ function setDefaultTopologyOptions(topologyList) {
// Store API
const AppStore = assign({}, EventEmitter.prototype, {
const AppStore = Object.assign({}, EventEmitter.prototype, {
CHANGE_EVENT: 'change',
@@ -242,186 +241,186 @@ const AppStore = assign({}, EventEmitter.prototype, {
AppStore.registeredCallback = function(payload) {
switch (payload.type) {
case ActionTypes.CHANGE_TOPOLOGY_OPTION:
if (topologyOptions.getIn([payload.topologyId, payload.option])
!== payload.value) {
nodes = nodes.clear();
}
topologyOptions = topologyOptions.setIn(
[payload.topologyId, payload.option],
payload.value
);
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.CLEAR_CONTROL_ERROR:
controlError = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.CLICK_CLOSE_DETAILS:
selectedNodeId = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.CLICK_NODE:
if (payload.nodeId === selectedNodeId) {
// clicking same node twice unsets the selection
selectedNodeId = null;
} else {
selectedNodeId = payload.nodeId;
}
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.CLICK_TOPOLOGY:
selectedNodeId = null;
if (payload.topologyId !== currentTopologyId) {
setTopology(payload.topologyId);
nodes = nodes.clear();
}
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.CLOSE_WEBSOCKET:
websocketClosed = true;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.DO_CONTROL:
controlPending = true;
controlError = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.ENTER_EDGE:
mouseOverEdgeId = payload.edgeId;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.ENTER_NODE:
mouseOverNodeId = payload.nodeId;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.HIT_ESC_KEY:
nodeDetails = null;
selectedNodeId = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.LEAVE_EDGE:
mouseOverEdgeId = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.LEAVE_NODE:
mouseOverNodeId = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.OPEN_WEBSOCKET:
// flush nodes cache after re-connect
case ActionTypes.CHANGE_TOPOLOGY_OPTION:
if (topologyOptions.getIn([payload.topologyId, payload.option])
!== payload.value) {
nodes = nodes.clear();
websocketClosed = false;
}
topologyOptions = topologyOptions.setIn(
[payload.topologyId, payload.option],
payload.value
);
AppStore.emit(AppStore.CHANGE_EVENT);
break;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.CLEAR_CONTROL_ERROR:
controlError = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.DO_CONTROL_ERROR:
controlPending = false;
controlError = payload.error;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.CLICK_CLOSE_DETAILS:
selectedNodeId = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.DO_CONTROL_SUCCESS:
controlPending = false;
controlError = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.CLICK_NODE:
if (payload.nodeId === selectedNodeId) {
// clicking same node twice unsets the selection
selectedNodeId = null;
} else {
selectedNodeId = payload.nodeId;
}
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.RECEIVE_ERROR:
errorUrl = payload.errorUrl;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.CLICK_TOPOLOGY:
selectedNodeId = null;
if (payload.topologyId !== currentTopologyId) {
setTopology(payload.topologyId);
nodes = nodes.clear();
}
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.RECEIVE_NODE_DETAILS:
errorUrl = null;
nodeDetails = payload.details;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.CLOSE_WEBSOCKET:
websocketClosed = true;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.RECEIVE_NODES_DELTA:
const emptyMessage = !payload.delta.add && !payload.delta.remove
&& payload.delta.update;
case ActionTypes.DO_CONTROL:
controlPending = true;
controlError = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
if (!emptyMessage) {
debug('RECEIVE_NODES_DELTA',
'remove', _.size(payload.delta.remove),
'update', _.size(payload.delta.update),
'add', _.size(payload.delta.add));
case ActionTypes.ENTER_EDGE:
mouseOverEdgeId = payload.edgeId;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.ENTER_NODE:
mouseOverNodeId = payload.nodeId;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.HIT_ESC_KEY:
nodeDetails = null;
selectedNodeId = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.LEAVE_EDGE:
mouseOverEdgeId = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.LEAVE_NODE:
mouseOverNodeId = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.OPEN_WEBSOCKET:
// flush nodes cache after re-connect
nodes = nodes.clear();
websocketClosed = false;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.DO_CONTROL_ERROR:
controlPending = false;
controlError = payload.error;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.DO_CONTROL_SUCCESS:
controlPending = false;
controlError = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.RECEIVE_ERROR:
errorUrl = payload.errorUrl;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.RECEIVE_NODE_DETAILS:
errorUrl = null;
nodeDetails = payload.details;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.RECEIVE_NODES_DELTA:
const emptyMessage = !payload.delta.add && !payload.delta.remove
&& payload.delta.update;
if (!emptyMessage) {
debug('RECEIVE_NODES_DELTA',
'remove', _.size(payload.delta.remove),
'update', _.size(payload.delta.update),
'add', _.size(payload.delta.add));
}
errorUrl = null;
// nodes that no longer exist
_.each(payload.delta.remove, function(nodeId) {
// in case node disappears before mouseleave event
if (mouseOverNodeId === nodeId) {
mouseOverNodeId = null;
}
errorUrl = null;
// nodes that no longer exist
_.each(payload.delta.remove, function(nodeId) {
// in case node disappears before mouseleave event
if (mouseOverNodeId === nodeId) {
mouseOverNodeId = null;
}
if (nodes.has(nodeId) && _.contains(mouseOverEdgeId, nodeId)) {
mouseOverEdgeId = null;
}
nodes = nodes.delete(nodeId);
});
// update existing nodes
_.each(payload.delta.update, function(node) {
nodes = nodes.set(node.id, nodes.get(node.id).merge(makeNode(node)));
});
// add new nodes
_.each(payload.delta.add, function(node) {
nodes = nodes.set(node.id, Immutable.fromJS(makeNode(node)));
});
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.RECEIVE_TOPOLOGIES:
errorUrl = null;
topologies = processTopologies(payload.topologies);
setTopology(currentTopologyId);
// only set on first load, if options are not already set via route
if (!topologiesLoaded && topologyOptions.size === 0) {
setDefaultTopologyOptions(topologies);
if (nodes.has(nodeId) && _.contains(mouseOverEdgeId, nodeId)) {
mouseOverEdgeId = null;
}
topologiesLoaded = true;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
nodes = nodes.delete(nodeId);
});
case ActionTypes.RECEIVE_API_DETAILS:
errorUrl = null;
version = payload.version;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
// update existing nodes
_.each(payload.delta.update, function(node) {
nodes = nodes.set(node.id, nodes.get(node.id).merge(makeNode(node)));
});
case ActionTypes.ROUTE_TOPOLOGY:
routeSet = true;
if (currentTopologyId !== payload.state.topologyId) {
nodes = nodes.clear();
}
setTopology(payload.state.topologyId);
// add new nodes
_.each(payload.delta.add, function(node) {
nodes = nodes.set(node.id, Immutable.fromJS(makeNode(node)));
});
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.RECEIVE_TOPOLOGIES:
errorUrl = null;
topologies = processTopologies(payload.topologies);
setTopology(currentTopologyId);
// only set on first load, if options are not already set via route
if (!topologiesLoaded && topologyOptions.size === 0) {
setDefaultTopologyOptions(topologies);
selectedNodeId = payload.state.selectedNodeId;
topologyOptions = Immutable.fromJS(payload.state.topologyOptions)
|| topologyOptions;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
}
topologiesLoaded = true;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
default:
break;
case ActionTypes.RECEIVE_API_DETAILS:
errorUrl = null;
version = payload.version;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
case ActionTypes.ROUTE_TOPOLOGY:
routeSet = true;
if (currentTopologyId !== payload.state.topologyId) {
nodes = nodes.clear();
}
setTopology(payload.state.topologyId);
setDefaultTopologyOptions(topologies);
selectedNodeId = payload.state.selectedNodeId;
topologyOptions = Immutable.fromJS(payload.state.topologyOptions)
|| topologyOptions;
AppStore.emit(AppStore.CHANGE_EVENT);
break;
default:
break;
}
};
+46 -45
View File
@@ -9,56 +9,60 @@
"d3": "~3.5.5",
"dagre": "0.7.4",
"debug": "~2.2.0",
"flux": "2.0.3",
"font-awesome": "4.3.0",
"font-awesome-webpack": "0.0.3",
"flux": "2.1.1",
"font-awesome": "4.4.0",
"font-awesome-webpack": "0.0.4",
"immutable": "~3.7.4",
"keymirror": "0.1.1",
"lodash": "~3.9.3",
"material-ui": "0.11.0",
"materialize-css": "0.96.1",
"object-assign": "2.0.0",
"page": "1.6.3",
"react": "~0.13.3",
"react-motion": "0.2.7",
"react-tap-event-plugin": "0.1.7",
"reqwest": "~1.1.5",
"lodash": "~3.10.1",
"materialize-css": "0.97.2",
"page": "1.6.4",
"react": "0.14.2",
"react-addons-pure-render-mixin": "0.14.2",
"react-addons-transition-group": "0.14.2",
"react-addons-update": "0.14.2",
"react-dom": "0.14.2",
"react-motion": "0.3.1",
"reqwest": "~2.0.5",
"timely": "0.1.0"
},
"devDependencies": {
"autoprefixer-core": "5.2.0",
"babel-core": "5.4.7",
"babel-eslint": "3.1.9",
"babel-jest": "5.3.0",
"babel-loader": "5.1.3",
"css-loader": "0.14.4",
"eslint": "~0.21.2",
"eslint-loader": "0.11.2",
"eslint-plugin-jasmine": "1.0.0",
"eslint-plugin-react": "2.3.0",
"autoprefixer": "6.0.1",
"babel-core": "6.1.4",
"babel-eslint": "4.1.5",
"babel-jest": "6.0.1",
"babel-loader": "6.1.0",
"babel-preset-es2015": "6.1.4",
"babel-preset-react": "6.1.4",
"css-loader": "0.22.0",
"eslint": "1.9.0",
"eslint-config-airbnb": "1.0.0",
"eslint-loader": "1.1.1",
"eslint-plugin-jasmine": "1.6.0",
"eslint-plugin-react": "3.8.0",
"file-loader": "0.8.4",
"jest-cli": "~0.4.19",
"json-loader": "0.5.2",
"jest-cli": "~0.7.1",
"json-loader": "0.5.3",
"less": "~2.5.1",
"less-loader": "2.2.0",
"postcss-loader": "0.4.3",
"style-loader": "0.12.3",
"url": "0.10.3",
"less-loader": "2.2.1",
"postcss-loader": "0.7.0",
"style-loader": "0.13.0",
"url": "0.11.0",
"url-loader": "0.5.6",
"webpack": "~1.9.10"
"webpack": "~1.12.4"
},
"optionalDependencies": {
"express": "~4.13.3",
"express-http-proxy": "~0.6.0",
"proxy-middleware": "~0.13.1",
"react-hot-loader": "~1.2.8",
"webpack-dev-server": "~1.10.1"
"proxy-middleware": "~0.15.0",
"react-hot-loader": "~1.3.0",
"webpack-dev-server": "~1.12.1"
},
"scripts": {
"build": "webpack -p --config webpack.production.config.js",
"start": "node server.js",
"start-production": "NODE_ENV=production node server.js",
"test": "jest --coverage",
"test": "jest",
"coveralls": "cat coverage/lcov.info | coveralls",
"lint": "eslint app",
"clean": "rm build/app.js"
@@ -66,26 +70,23 @@
"jest": {
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"testFileExtensions": [
"es6",
"js"
],
"testPathDirs": [
"<rootDir>/app/scripts"
],
"globals": {
"__WS_URL__": false
},
"moduleFileExtensions": [
"js",
"json",
"es6"
"json"
],
"unmockedModulePathPatterns": [
"react",
"immutable",
"d3",
"dagre",
"keymirror",
"object-assign",
"lodash",
"debug"
"/node_modules/"
]
},
"engines": {
"node": ">=0.10.0"
"node": ">=4.2.2"
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
var webpack = require('webpack');
var autoprefixer = require('autoprefixer-core');
var autoprefixer = require('autoprefixer');
var path = require('path');
/**
@@ -75,7 +75,7 @@ module.exports = {
{
test: /\.jsx?$/,
exclude: /node_modules/,
loaders: ['react-hot', 'babel-loader?stage=0']
loaders: ['react-hot', 'babel']
}
]
},
+2 -2
View File
@@ -1,5 +1,5 @@
var webpack = require('webpack');
var autoprefixer = require('autoprefixer-core');
var autoprefixer = require('autoprefixer');
var path = require('path');
var GLOBALS = {
@@ -42,7 +42,7 @@ module.exports = {
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader'
},
{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader?stage=0' }
{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel' }
]
},