mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
Merge pull request #65 from weaveworks/fix-topology-click
Fix grouping bar for topologies that don't support grouping
This commit is contained in:
7
Makefile
7
Makefile
@@ -35,7 +35,12 @@ client/dist/scripts/bundle.js: client/app/scripts/*
|
||||
mkdir -p client/dist
|
||||
docker run -ti -v $(shell pwd)/client/app:/home/weave/app \
|
||||
-v $(shell pwd)/client/dist:/home/weave/dist \
|
||||
$(SCOPE_UI_BUILD_IMAGE)
|
||||
$(SCOPE_UI_BUILD_IMAGE) gulp build
|
||||
|
||||
client-test: client/test/*
|
||||
docker run -ti -v $(shell pwd)/client/app:/home/weave/app \
|
||||
-v $(shell pwd)/client/test:/home/weave/test \
|
||||
$(SCOPE_UI_BUILD_IMAGE) npm test
|
||||
|
||||
$(SCOPE_UI_BUILD_EXPORT): client/Dockerfile client/gulpfile.js client/package.json
|
||||
docker build -t $(SCOPE_UI_BUILD_IMAGE) client
|
||||
|
||||
@@ -37,6 +37,7 @@ test:
|
||||
- cd $SRCDIR; make static
|
||||
- cd $SRCDIR; make
|
||||
- cd $SRCDIR; ./bin/test
|
||||
- cd $SRCDIR; make client-test
|
||||
post:
|
||||
- goveralls -repotoken $COVERALLS_REPO_TOKEN -coverprofile=$SRCDIR/profile.cov -service=circleci || true
|
||||
- cd $SRCDIR; cp coverage.html $CIRCLE_ARTIFACTS
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
FROM mhart/alpine-node
|
||||
|
||||
FROM debian:latest
|
||||
WORKDIR /home/weave
|
||||
|
||||
RUN apt-get update && apt-get install -y curl bzip2 libfreetype6 libfontconfig1
|
||||
|
||||
# Install nodejs
|
||||
RUN curl -sL https://deb.nodesource.com/setup | bash -
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
# build tool
|
||||
RUN npm install -g gulp
|
||||
|
||||
@@ -11,9 +16,4 @@ RUN npm install
|
||||
|
||||
ADD gulpfile.js /home/weave/
|
||||
|
||||
# run container via
|
||||
#
|
||||
# `docker run -v $GOPATH/src/github.com/weaveworks/scope/client:/app weaveworks/scope-build`
|
||||
#
|
||||
# after the container is run, bundled app should be in ./dist/ dir
|
||||
CMD gulp build
|
||||
# For instructions on running this container, consult the toplevel Makefile
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = {
|
||||
grouping: grouping
|
||||
});
|
||||
RouterUtils.updateRoute();
|
||||
WebapiUtils.getNodesDelta(AppStore.getUrlForTopology(AppStore.getCurrentTopology()));
|
||||
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl());
|
||||
},
|
||||
|
||||
clickNode: function(nodeId) {
|
||||
@@ -24,7 +24,7 @@ module.exports = {
|
||||
nodeId: nodeId
|
||||
});
|
||||
RouterUtils.updateRoute();
|
||||
WebapiUtils.getNodeDetails(AppStore.getUrlForTopology(AppStore.getCurrentTopology()), AppStore.getSelectedNodeId());
|
||||
WebapiUtils.getNodeDetails(AppStore.getCurrentTopologyUrl(), AppStore.getSelectedNodeId());
|
||||
},
|
||||
|
||||
clickTopology: function(topologyId) {
|
||||
@@ -33,7 +33,14 @@ module.exports = {
|
||||
topologyId: topologyId
|
||||
});
|
||||
RouterUtils.updateRoute();
|
||||
WebapiUtils.getNodesDelta(AppStore.getUrlForTopology(AppStore.getCurrentTopology()));
|
||||
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl());
|
||||
},
|
||||
|
||||
enterNode: function(nodeId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.ENTER_NODE,
|
||||
nodeId: nodeId
|
||||
});
|
||||
},
|
||||
|
||||
hitEsc: function() {
|
||||
@@ -43,6 +50,13 @@ module.exports = {
|
||||
RouterUtils.updateRoute();
|
||||
},
|
||||
|
||||
leaveNode: function(nodeId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.LEAVE_NODE,
|
||||
nodeId: nodeId
|
||||
});
|
||||
},
|
||||
|
||||
receiveNodeDetails: function(details) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.RECEIVE_NODE_DETAILS,
|
||||
@@ -50,13 +64,20 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
receiveNodesDelta: function(delta) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.RECEIVE_NODES_DELTA,
|
||||
delta: delta
|
||||
});
|
||||
},
|
||||
|
||||
receiveTopologies: function(topologies) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.RECEIVE_TOPOLOGIES,
|
||||
topologies: topologies
|
||||
});
|
||||
WebapiUtils.getNodesDelta(AppStore.getUrlForTopology(AppStore.getCurrentTopology()));
|
||||
WebapiUtils.getNodeDetails(AppStore.getUrlForTopology(AppStore.getCurrentTopology()), AppStore.getSelectedNodeId());
|
||||
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl());
|
||||
WebapiUtils.getNodeDetails(AppStore.getCurrentTopologyUrl(), AppStore.getSelectedNodeId());
|
||||
},
|
||||
|
||||
route: function(state) {
|
||||
@@ -64,8 +85,8 @@ module.exports = {
|
||||
state: state,
|
||||
type: ActionTypes.ROUTE_TOPOLOGY
|
||||
});
|
||||
WebapiUtils.getNodesDelta(AppStore.getUrlForTopology(AppStore.getCurrentTopology()));
|
||||
WebapiUtils.getNodeDetails(AppStore.getUrlForTopology(AppStore.getCurrentTopology()), AppStore.getSelectedNodeId());
|
||||
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl());
|
||||
WebapiUtils.getNodeDetails(AppStore.getCurrentTopologyUrl(), AppStore.getSelectedNodeId());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
var AppDispatcher = require('../dispatcher/app-dispatcher');
|
||||
var ActionTypes = require('../constants/action-types');
|
||||
|
||||
module.exports = {
|
||||
enterNode: function(nodeId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.ENTER_NODE,
|
||||
nodeId: nodeId
|
||||
});
|
||||
},
|
||||
|
||||
leaveNode: function(nodeId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.LEAVE_NODE,
|
||||
nodeId: nodeId
|
||||
});
|
||||
},
|
||||
|
||||
receiveNodesDelta: function(delta) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.RECEIVE_NODES_DELTA,
|
||||
delta: delta
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -8,7 +8,6 @@ var AppStore = require('../stores/app-store');
|
||||
var Groupings = require('./groupings.js');
|
||||
var Status = require('./status.js');
|
||||
var Topologies = require('./topologies.js');
|
||||
var TopologyStore = require('../stores/topology-store');
|
||||
var WebapiUtils = require('../utils/web-api-utils');
|
||||
var AppActions = require('../actions/app-actions');
|
||||
var Details = require('./details');
|
||||
@@ -20,12 +19,12 @@ var ESC_KEY_CODE = 27;
|
||||
|
||||
function getStateFromStores() {
|
||||
return {
|
||||
activeTopology: AppStore.getCurrentTopology(),
|
||||
currentTopology: AppStore.getCurrentTopology(),
|
||||
connectionState: AppStore.getConnectionState(),
|
||||
currentGrouping: AppStore.getCurrentGrouping(),
|
||||
selectedNodeId: AppStore.getSelectedNodeId(),
|
||||
nodeDetails: AppStore.getNodeDetails(),
|
||||
nodes: TopologyStore.getNodes(),
|
||||
nodes: AppStore.getNodes(),
|
||||
topologies: AppStore.getTopologies()
|
||||
}
|
||||
}
|
||||
@@ -38,7 +37,6 @@ var App = React.createClass({
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
TopologyStore.on(TopologyStore.CHANGE_EVENT, this.onChange);
|
||||
AppStore.on(AppStore.CHANGE_EVENT, this.onChange);
|
||||
window.addEventListener('keyup', this.onKeyPress);
|
||||
|
||||
@@ -63,13 +61,12 @@ var App = React.createClass({
|
||||
<div>
|
||||
{showingDetails && <Details nodes={this.state.nodes}
|
||||
nodeId={this.state.selectedNodeId}
|
||||
details={this.state.nodeDetails}
|
||||
topology={this.state.activeTopology} /> }
|
||||
details={this.state.nodeDetails} /> }
|
||||
|
||||
<div className="header">
|
||||
<Logo />
|
||||
<Topologies topologies={this.state.topologies} active={this.state.activeTopology} />
|
||||
<Groupings active={this.state.currentGrouping} />
|
||||
<Topologies topologies={this.state.topologies} currentTopology={this.state.currentTopology} />
|
||||
<Groupings active={this.state.currentGrouping} currentTopology={this.state.currentTopology} />
|
||||
<Status connectionState={this.state.connectionState} />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ var IconButton = mui.IconButton;
|
||||
|
||||
var AppActions = require('../actions/app-actions');
|
||||
var NodeDetails = require('./node-details');
|
||||
var WebapiUtils = require('../utils/web-api-utils');
|
||||
|
||||
var Details = React.createClass({
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@ var AppStore = require('../stores/app-store');
|
||||
|
||||
var GROUPINGS = [{
|
||||
id: 'none',
|
||||
iconClass: 'fa fa-th'
|
||||
iconClass: 'fa fa-th',
|
||||
needsTopology: false
|
||||
}, {
|
||||
id: 'grouped',
|
||||
iconClass: 'fa fa-th-large'
|
||||
iconClass: 'fa fa-th-large',
|
||||
needsTopology: 'grouped_url'
|
||||
}];
|
||||
|
||||
var Groupings = React.createClass({
|
||||
@@ -21,22 +23,40 @@ var Groupings = React.createClass({
|
||||
AppActions.clickGrouping(ev.currentTarget.getAttribute('rel'));
|
||||
},
|
||||
|
||||
renderGrouping: function(grouping, active) {
|
||||
var className = grouping.id === active ? "groupings-item groupings-item-active" : "groupings-item";
|
||||
isGroupingSupportedByTopology: function(topology, grouping) {
|
||||
return !grouping.needsTopology || topology && topology[grouping.needsTopology];
|
||||
},
|
||||
|
||||
getGroupingsSupportedByTopology: function(topology) {
|
||||
return _.filter(GROUPINGS, _.partial(this.isGroupingSupportedByTopology, topology));
|
||||
},
|
||||
|
||||
renderGrouping: function(grouping, activeGroupingId) {
|
||||
var className = "groupings-item",
|
||||
isSupportedByTopology = this.isGroupingSupportedByTopology(this.props.currentTopology, grouping);
|
||||
|
||||
if (grouping.id === activeGroupingId) {
|
||||
className += " groupings-item-active";
|
||||
} else if (!isSupportedByTopology) {
|
||||
className += " groupings-item-disabled";
|
||||
} else {
|
||||
className += " groupings-item-default";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className} key={grouping.id} rel={grouping.id} onClick={this.onGroupingClick}>
|
||||
<div className={className} key={grouping.id} rel={grouping.id} onClick={isSupportedByTopology && this.onGroupingClick}>
|
||||
<span className={grouping.iconClass} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var activeGrouping = this.props.active;
|
||||
var activeGrouping = this.props.active,
|
||||
isGroupingSupported = _.size(this.getGroupingsSupportedByTopology(this.props.currentTopology)) > 1;
|
||||
|
||||
return (
|
||||
<div className="groupings">
|
||||
{GROUPINGS.map(function(grouping) {
|
||||
{isGroupingSupported && GROUPINGS.map(function(grouping) {
|
||||
return this.renderGrouping(grouping, activeGrouping);
|
||||
}, this)}
|
||||
</div>
|
||||
|
||||
@@ -14,8 +14,9 @@ var Topologies = React.createClass({
|
||||
},
|
||||
|
||||
renderTopology: function(topology, active) {
|
||||
var className = AppStore.isUrlForTopology(topology.url, active) ? "topologies-item topologies-item-active" : "topologies-item",
|
||||
topologyId = AppStore.getTopologyForUrl(topology.url),
|
||||
var isActive = topology.name === this.props.currentTopology.name,
|
||||
className = isActive ? "topologies-item topologies-item-active" : "topologies-item",
|
||||
topologyId = AppStore.getTopologyIdForUrl(topology.url),
|
||||
title = ['Topology: ' + topology.name,
|
||||
'Nodes: ' + topology.stats.node_count,
|
||||
'Connections: ' + topology.stats.node_count].join('\n');
|
||||
@@ -32,15 +33,14 @@ var Topologies = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var activeTopologyId = this.props.active,
|
||||
topologies = _.sortBy(this.props.topologies, function(topology) {
|
||||
var topologies = _.sortBy(this.props.topologies, function(topology) {
|
||||
return topology.name;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="topologies">
|
||||
{topologies.map(function(topology) {
|
||||
return this.renderTopology(topology, activeTopologyId);
|
||||
{this.props.currentTopology && topologies.map(function(topology) {
|
||||
return this.renderTopology(topology);
|
||||
}, this)}
|
||||
</div>
|
||||
);
|
||||
|
||||
61
client/app/scripts/stores/__tests__/app-store-test.js
Normal file
61
client/app/scripts/stores/__tests__/app-store-test.js
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
describe('AppStore', function() {
|
||||
|
||||
var ActionTypes = require('../../constants/action-types');
|
||||
var AppStore, registeredCallback;
|
||||
|
||||
// actions
|
||||
|
||||
var ClickTopologyAction = {
|
||||
type: ActionTypes.CLICK_TOPOLOGY,
|
||||
topologyId: 'topo1'
|
||||
};
|
||||
|
||||
var ClickGroupingAction = {
|
||||
type: ActionTypes.CLICK_GROUPING,
|
||||
grouping: 'grouped'
|
||||
};
|
||||
|
||||
var ReceiveTopologiesAction = {
|
||||
type: ActionTypes.RECEIVE_TOPOLOGIES,
|
||||
topologies: [{
|
||||
url: '/topo1',
|
||||
grouped_url: '/topo1grouped',
|
||||
name: 'Topo1'
|
||||
}]
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
AppStore = require('../app-store');
|
||||
registeredCallback = AppStore.registeredCallback;
|
||||
});
|
||||
|
||||
// topology tests
|
||||
|
||||
it('init with no topologies', function() {
|
||||
var topos = AppStore.getTopologies();
|
||||
expect(topos.length).toBe(0);
|
||||
expect(AppStore.getCurrentTopology()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('get current topology', function() {
|
||||
registeredCallback(ClickTopologyAction);
|
||||
registeredCallback(ReceiveTopologiesAction);
|
||||
|
||||
expect(AppStore.getTopologies().length).toBe(1);
|
||||
expect(AppStore.getCurrentTopology().name).toBe('Topo1');
|
||||
expect(AppStore.getCurrentTopologyUrl()).toBe('/topo1');
|
||||
});
|
||||
|
||||
it('get grouped topology', function() {
|
||||
registeredCallback(ClickTopologyAction);
|
||||
registeredCallback(ReceiveTopologiesAction);
|
||||
registeredCallback(ClickGroupingAction);
|
||||
|
||||
expect(AppStore.getTopologies().length).toBe(1);
|
||||
expect(AppStore.getCurrentTopology().name).toBe('Topo1');
|
||||
expect(AppStore.getCurrentTopologyUrl()).toBe('/topo1grouped');
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -5,15 +5,15 @@ var assign = require('object-assign');
|
||||
|
||||
var AppDispatcher = require('../dispatcher/app-dispatcher');
|
||||
var ActionTypes = require('../constants/action-types');
|
||||
var TopologyStore = require('./topology-store');
|
||||
// var topologies = require('../constants/topologies');
|
||||
|
||||
|
||||
// Initial values
|
||||
|
||||
var connectionState = 'disconnected';
|
||||
var currentGrouping = 'none';
|
||||
var currentTopology = 'applications';
|
||||
var currentTopologyId = 'applications';
|
||||
var mouseOverNode = null;
|
||||
var nodes = {};
|
||||
var nodeDetails = null;
|
||||
var selectedNodeId = null;
|
||||
var topologies = [];
|
||||
@@ -26,8 +26,8 @@ var AppStore = assign({}, EventEmitter.prototype, {
|
||||
|
||||
getAppState: function() {
|
||||
return {
|
||||
currentTopology: this.getCurrentTopology(),
|
||||
currentGrouping: this.getCurrentGrouping(),
|
||||
topologyId: currentTopologyId,
|
||||
grouping: this.getCurrentGrouping(),
|
||||
selectedNodeId: this.getSelectedNodeId()
|
||||
};
|
||||
},
|
||||
@@ -37,7 +37,17 @@ var AppStore = assign({}, EventEmitter.prototype, {
|
||||
},
|
||||
|
||||
getCurrentTopology: function() {
|
||||
return currentTopology;
|
||||
return _.find(topologies, function(topology) {
|
||||
return isUrlForTopologyId(topology.url, currentTopologyId);
|
||||
});
|
||||
},
|
||||
|
||||
getCurrentTopologyUrl: function() {
|
||||
var topology = this.getCurrentTopology();
|
||||
|
||||
if (topology) {
|
||||
return topology.grouped_url && currentGrouping == 'grouped' ? topology.grouped_url : topology.url;
|
||||
}
|
||||
},
|
||||
|
||||
getCurrentGrouping: function() {
|
||||
@@ -48,6 +58,10 @@ var AppStore = assign({}, EventEmitter.prototype, {
|
||||
return nodeDetails;
|
||||
},
|
||||
|
||||
getNodes: function() {
|
||||
return nodes;
|
||||
},
|
||||
|
||||
getSelectedNodeId: function() {
|
||||
return selectedNodeId;
|
||||
},
|
||||
@@ -56,40 +70,35 @@ var AppStore = assign({}, EventEmitter.prototype, {
|
||||
return topologies;
|
||||
},
|
||||
|
||||
getTopologyForUrl: function(url) {
|
||||
getTopologyIdForUrl: function(url) {
|
||||
return url.split('/').pop();
|
||||
},
|
||||
|
||||
getUrlForTopology: function(topologyId) {
|
||||
var topology = _.find(topologies, function(topology) {
|
||||
return this.isUrlForTopology(topology.url, topologyId);
|
||||
}, this);
|
||||
|
||||
if (topology) {
|
||||
return topology.grouped_url && currentGrouping == 'grouped' ? topology.grouped_url : topology.url;
|
||||
}
|
||||
},
|
||||
|
||||
isUrlForTopology: function(url, topologyId) {
|
||||
return _.endsWith(url, topologyId);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Helpers
|
||||
|
||||
function isUrlForTopologyId(url, topologyId) {
|
||||
return _.endsWith(url, topologyId);
|
||||
}
|
||||
|
||||
|
||||
// Store Dispatch Hooks
|
||||
|
||||
AppStore.dispatchToken = AppDispatcher.register(function(payload) {
|
||||
AppStore.registeredCallback = function(payload) {
|
||||
switch (payload.type) {
|
||||
|
||||
case ActionTypes.CLICK_CLOSE_DETAILS:
|
||||
selectedNodeId = null;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_GROUPING:
|
||||
currentGrouping = payload.grouping;
|
||||
AppDispatcher.waitFor([TopologyStore.dispatchToken]);
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
if (payload.grouping !== currentGrouping) {
|
||||
currentGrouping = payload.grouping;
|
||||
nodes = {};
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
}
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_NODE:
|
||||
@@ -98,8 +107,15 @@ AppStore.dispatchToken = AppDispatcher.register(function(payload) {
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_TOPOLOGY:
|
||||
currentTopology = payload.topologyId;
|
||||
AppDispatcher.waitFor([TopologyStore.dispatchToken]);
|
||||
if (payload.topologyId !== currentTopologyId) {
|
||||
currentTopologyId = payload.topologyId;
|
||||
nodes = {};
|
||||
}
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.ENTER_NODE:
|
||||
mouseOverNode = payload.nodeId;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
@@ -109,14 +125,43 @@ AppStore.dispatchToken = AppDispatcher.register(function(payload) {
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.LEAVE_NODE:
|
||||
mouseOverNode = null;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_NODE_DETAILS:
|
||||
nodeDetails = payload.details;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_NODES_DELTA:
|
||||
console.log('RECEIVE_NODES_DELTA',
|
||||
'remove', _.size(payload.delta.remove),
|
||||
'update', _.size(payload.delta.update),
|
||||
'add', _.size(payload.delta.add));
|
||||
|
||||
connectionState = "connected";
|
||||
AppDispatcher.waitFor([TopologyStore.dispatchToken]);
|
||||
|
||||
// nodes that no longer exist
|
||||
_.each(payload.delta.remove, function(nodeId) {
|
||||
// in case node disappears before mouseleave event
|
||||
if (mouseOverNode === nodeId) {
|
||||
mouseOverNode = null;
|
||||
}
|
||||
delete nodes[nodeId];
|
||||
});
|
||||
|
||||
// update existing nodes
|
||||
_.each(payload.delta.update, function(node) {
|
||||
nodes[node.id] = node;
|
||||
});
|
||||
|
||||
// add new nodes
|
||||
_.each(payload.delta.add, function(node) {
|
||||
nodes[node.id] = node;
|
||||
});
|
||||
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
@@ -126,10 +171,10 @@ AppStore.dispatchToken = AppDispatcher.register(function(payload) {
|
||||
break;
|
||||
|
||||
case ActionTypes.ROUTE_TOPOLOGY:
|
||||
currentTopology = payload.state.currentTopology;
|
||||
currentGrouping = payload.state.currentGrouping;
|
||||
nodes = {};
|
||||
currentTopologyId = payload.state.topologyId;
|
||||
currentGrouping = payload.state.grouping;
|
||||
selectedNodeId = payload.state.selectedNodeId;
|
||||
AppDispatcher.waitFor([TopologyStore.dispatchToken]);
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
@@ -137,6 +182,8 @@ AppStore.dispatchToken = AppDispatcher.register(function(payload) {
|
||||
break;
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
AppStore.dispatchToken = AppDispatcher.register(AppStore.registeredCallback);
|
||||
|
||||
module.exports = AppStore;
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var _ = require('lodash');
|
||||
var assign = require('object-assign');
|
||||
|
||||
var AppDispatcher = require('../dispatcher/app-dispatcher');
|
||||
var ActionTypes = require('../constants/action-types');
|
||||
|
||||
|
||||
|
||||
// Initial values
|
||||
|
||||
var nodes = {};
|
||||
var mouseOverNode = null;
|
||||
|
||||
// Store API
|
||||
|
||||
var TopologyStore = assign({}, EventEmitter.prototype, {
|
||||
|
||||
CHANGE_EVENT: 'change',
|
||||
|
||||
getNodes: function() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Store Dispatch Hooks
|
||||
|
||||
TopologyStore.dispatchToken = AppDispatcher.register(function(payload) {
|
||||
switch (payload.type) {
|
||||
case ActionTypes.CLICK_GROUPING:
|
||||
nodes = {};
|
||||
TopologyStore.emit(TopologyStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_TOPOLOGY:
|
||||
nodes = {};
|
||||
TopologyStore.emit(TopologyStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.ENTER_NODE:
|
||||
mouseOverNode = payload.nodeId;
|
||||
TopologyStore.emit(TopologyStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.LEAVE_NODE:
|
||||
mouseOverNode = null;
|
||||
TopologyStore.emit(TopologyStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_NODES_DELTA:
|
||||
// nodes that no longer exist
|
||||
_.each(payload.delta.remove, function(nodeId) {
|
||||
// in case node disappears before mouseleave event
|
||||
if (mouseOverNode === nodeId) {
|
||||
mouseOverNode = null;
|
||||
}
|
||||
delete nodes[nodeId];
|
||||
});
|
||||
|
||||
// update existing nodes
|
||||
_.each(payload.delta.update, function(node) {
|
||||
nodes[node.id] = node;
|
||||
});
|
||||
|
||||
// add new nodes
|
||||
_.each(payload.delta.add, function(node) {
|
||||
nodes[node.id] = node;
|
||||
});
|
||||
|
||||
TopologyStore.emit(TopologyStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.ROUTE_TOPOLOGY:
|
||||
nodes = {};
|
||||
TopologyStore.emit(TopologyStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = TopologyStore;
|
||||
@@ -1,8 +1,6 @@
|
||||
var reqwest = require('reqwest');
|
||||
|
||||
var TopologyActions = require('../actions/topology-actions');
|
||||
var AppActions = require('../actions/app-actions');
|
||||
var AppStore = require('../stores/app-store');
|
||||
|
||||
var WS_URL = window.WS_URL || 'ws://' + location.host;
|
||||
|
||||
@@ -33,7 +31,7 @@ function createWebsocket(topologyUrl) {
|
||||
socket.onmessage = function(event) {
|
||||
var msg = JSON.parse(event.data);
|
||||
if (msg.add || msg.remove || msg.update) {
|
||||
TopologyActions.receiveNodesDelta(msg);
|
||||
AppActions.receiveNodesDelta(msg);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,9 +46,9 @@ function getTopologies() {
|
||||
});
|
||||
}
|
||||
|
||||
function getNodeDetails(topology, nodeId) {
|
||||
if (nodeId) {
|
||||
var url = [AppStore.getUrlForTopology(topology), nodeId].join('/');
|
||||
function getNodeDetails(topologyUrl, nodeId) {
|
||||
if (topologyUrl && nodeId) {
|
||||
var url = [topologyUrl, nodeId].join('/');
|
||||
reqwest(url, function(res) {
|
||||
AppActions.receiveNodeDetails(res.node);
|
||||
});
|
||||
|
||||
@@ -101,7 +101,13 @@ body {
|
||||
display: inline-block;
|
||||
color: @text-tertiary-color;
|
||||
|
||||
&-active, &:hover {
|
||||
&-disabled {
|
||||
color: @text-tertiary-color;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&-default:hover,
|
||||
&-active {
|
||||
color: @text-color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,15 +38,25 @@
|
||||
"gulp-size": "^1.2.1",
|
||||
"gulp-sourcemaps": "^1.5.2",
|
||||
"gulp-uglify": "^1.2.0",
|
||||
"gulp-useref": "^1.1.1",
|
||||
"gulp-util": "^3.0.4",
|
||||
"jasmine-core": "^2.3.4",
|
||||
"jshint-stylish": "^1.0.2",
|
||||
"karma": "^0.12.32",
|
||||
"karma-browserify": "^4.2.1",
|
||||
"karma-cli": "0.0.4",
|
||||
"karma-jasmine": "^0.3.5",
|
||||
"karma-phantomjs-launcher": "^0.1.4",
|
||||
"opn": "^1.0.1",
|
||||
"proxy-middleware": "^0.11.1",
|
||||
"react-tools": "^0.13.3",
|
||||
"reactify": "^1.1.1",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "gulp"
|
||||
"start": "gulp",
|
||||
"test": "karma start test/karma.conf.js --single-run"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
|
||||
9
client/test/README.md
Normal file
9
client/test/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Testing
|
||||
|
||||
Scope unit testing is done unsing Karma/Jasmine. (Jest was too big and slow.)
|
||||
|
||||
To run tests, do `npm test` in the toplevel directory.
|
||||
|
||||
The tests are placed in `__tests__` directories, relative to what they are testing.
|
||||
|
||||
For more info see [Testing Flux Apps with Karma](http://kentor.me/posts/testing-react-and-flux-applications-with-karma-and-webpack/)
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "gulp-webapp",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"chai": "~1.8.0",
|
||||
"mocha": "~1.14.0"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Mocha Spec Runner</title>
|
||||
<link rel="stylesheet" href="bower_components/mocha/mocha.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<script src="bower_components/mocha/mocha.js"></script>
|
||||
<script>mocha.setup('bdd')</script>
|
||||
<script src="bower_components/chai/chai.js"></script>
|
||||
<script>
|
||||
var assert = chai.assert;
|
||||
var expect = chai.expect;
|
||||
var should = chai.should();
|
||||
</script>
|
||||
|
||||
<!-- include source files here... -->
|
||||
|
||||
<!-- include spec files here... -->
|
||||
<script src="spec/test.js"></script>
|
||||
|
||||
<script>mocha.run()</script>
|
||||
</body>
|
||||
</html>
|
||||
23
client/test/karma.conf.js
Normal file
23
client/test/karma.conf.js
Normal file
@@ -0,0 +1,23 @@
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
browsers: [
|
||||
'PhantomJS'
|
||||
],
|
||||
files: [
|
||||
'../app/**/__tests__/*.js'
|
||||
],
|
||||
frameworks: [
|
||||
'jasmine', 'browserify'
|
||||
],
|
||||
preprocessors: {
|
||||
'../app/**/__tests__/*.js': ['browserify']
|
||||
},
|
||||
browserify: {
|
||||
debug: true,
|
||||
transform: ['reactify']
|
||||
},
|
||||
reporters: [
|
||||
'dots'
|
||||
]
|
||||
});
|
||||
};
|
||||
7
client/test/preprocessor.js
Normal file
7
client/test/preprocessor.js
Normal file
@@ -0,0 +1,7 @@
|
||||
var ReactTools = require('react-tools');
|
||||
|
||||
module.exports = {
|
||||
process: function(src) {
|
||||
return ReactTools.transform(src);
|
||||
}
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
/* global describe, it */
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
describe('Give it some context', function () {
|
||||
describe('maybe a bit more context here', function () {
|
||||
it('should run here few assertions', function () {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user