diff --git a/client/app/scripts/components/topology-option-action.js b/client/app/scripts/components/topology-option-action.js
index f8ee59770..1bb090d38 100644
--- a/client/app/scripts/components/topology-option-action.js
+++ b/client/app/scripts/components/topology-option-action.js
@@ -1,9 +1,6 @@
import React from 'react';
-import { connect } from 'react-redux';
-import { changeTopologyOption } from '../actions/app-actions';
-
-class TopologyOptionAction extends React.Component {
+export default class TopologyOptionAction extends React.Component {
constructor(props, context) {
super(props, context);
@@ -13,13 +10,14 @@ class TopologyOptionAction extends React.Component {
onClick(ev) {
ev.preventDefault();
const { optionId, topologyId, item } = this.props;
- this.props.changeTopologyOption(optionId, item.get('value'), topologyId);
+ this.props.onClick(optionId, item.get('value'), topologyId);
}
render() {
const { activeValue, item } = this.props;
- const className = activeValue === item.get('value')
- ? 'topology-option-action topology-option-action-selected' : 'topology-option-action';
+ const className = activeValue.includes(item.get('value'))
+ ? 'topology-option-action topology-option-action-selected'
+ : 'topology-option-action';
return (
{item.get('label')}
@@ -27,8 +25,3 @@ class TopologyOptionAction extends React.Component {
);
}
}
-
-export default connect(
- null,
- { changeTopologyOption }
-)(TopologyOptionAction);
diff --git a/client/app/scripts/components/topology-options.js b/client/app/scripts/components/topology-options.js
index 4d7210211..b7ee171f1 100644
--- a/client/app/scripts/components/topology-options.js
+++ b/client/app/scripts/components/topology-options.js
@@ -4,8 +4,18 @@ import { connect } from 'react-redux';
import { getCurrentTopologyOptions } from '../utils/topology-utils';
import { activeTopologyOptionsSelector } from '../selectors/topology';
import TopologyOptionAction from './topology-option-action';
+import { changeTopologyOption } from '../actions/app-actions';
class TopologyOptions extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+
+ this.handleOptionClick = this.handleOptionClick.bind(this);
+ }
+
+ handleOptionClick(optionId, value, topologyId) {
+ this.props.changeTopologyOption(optionId, value, topologyId);
+ }
renderOption(option) {
const { activeOptions, topologyId } = this.props;
@@ -19,6 +29,7 @@ class TopologyOptions extends React.Component {
{option.get('options').map(item => (
- {this.props.options && this.props.options.toIndexedSeq().map(
+ {options && options.toIndexedSeq().map(
option => this.renderOption(option))}
);
@@ -50,5 +62,6 @@ function mapStateToProps(state) {
}
export default connect(
- mapStateToProps
+ mapStateToProps,
+ { changeTopologyOption }
)(TopologyOptions);
diff --git a/client/app/scripts/reducers/__tests__/root-test.js b/client/app/scripts/reducers/__tests__/root-test.js
index ccefa0827..7d5e9e002 100644
--- a/client/app/scripts/reducers/__tests__/root-test.js
+++ b/client/app/scripts/reducers/__tests__/root-test.js
@@ -1,4 +1,6 @@
import { is, fromJS } from 'immutable';
+import expect from 'expect';
+
import { TABLE_VIEW_MODE } from '../../constants/naming';
// Root reducer test suite using Jasmine matchers
import { constructEdgeId } from '../../utils/layouter-utils';
@@ -307,7 +309,16 @@ describe('RootReducer', () => {
expect(nextState.get('topologies').size).toBe(2);
expect(nextState.get('currentTopology').get('name')).toBe('Topo1');
expect(nextState.get('currentTopology').get('url')).toBe('/topo1');
- expect(nextState.get('currentTopology').get('options').first().get('id')).toBe('option1');
+ expect(nextState.get('currentTopology').get('options').first().get('id')).toEqual(['option1']);
+ expect(nextState.getIn(['currentTopology', 'options']).toJS()).toEqual([{
+ id: 'option1',
+ defaultValue: 'off',
+ selectType: 'one',
+ options: [
+ { value: 'on'},
+ { value: 'off'}
+ ]
+ }]);
});
it('get sub-topology', () => {
@@ -318,7 +329,7 @@ describe('RootReducer', () => {
expect(nextState.get('topologies').size).toBe(2);
expect(nextState.get('currentTopology').get('name')).toBe('topo 1 grouped');
expect(nextState.get('currentTopology').get('url')).toBe('/topo1-grouped');
- expect(nextState.get('currentTopology').get('options')).toBeUndefined();
+ expect(nextState.get('currentTopology').get('options')).toNotExist();
});
// topology options
@@ -330,51 +341,49 @@ describe('RootReducer', () => {
// default options
expect(activeTopologyOptionsSelector(nextState).has('option1')).toBeTruthy();
- expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('off');
- expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('off');
+ expect(activeTopologyOptionsSelector(nextState).get('option1')).toBeA('array');
+ expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['off']);
+ expect(getUrlState(nextState).topologyOptions.topo1.option1).toEqual(['off']);
// turn on
nextState = reducer(nextState, ChangeTopologyOptionAction);
- expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('on');
- expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('on');
+ expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['on']);
+ expect(getUrlState(nextState).topologyOptions.topo1.option1).toEqual(['on']);
// turn off
nextState = reducer(nextState, ChangeTopologyOptionAction2);
- expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('off');
- expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('off');
+ expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['off']);
+ expect(getUrlState(nextState).topologyOptions.topo1.option1).toEqual(['off']);
// sub-topology should retain main topo options
nextState = reducer(nextState, ClickSubTopologyAction);
- expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('off');
- expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('off');
+ expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['off']);
+ expect(getUrlState(nextState).topologyOptions.topo1.option1).toEqual(['off']);
// other topology w/o options dont return options, but keep in app state
nextState = reducer(nextState, ClickTopology2Action);
- expect(activeTopologyOptionsSelector(nextState)).toBeUndefined();
- expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('off');
+ expect(activeTopologyOptionsSelector(nextState)).toNotExist();
+ expect(getUrlState(nextState).topologyOptions.topo1.option1).toEqual(['off']);
});
- it('changes topologyOptions for selectType "many"', () => {
- const action = {
- type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
+
+ it('adds/removes a topology option', () => {
+ const addAction = {
+ type: ActionTypes.ADD_TOPOLOGY_OPTION,
topologyId: 'services',
option: 'namespace',
- value: ['scope', 'monitoring']
+ value: 'scope'
+ };
+ const removeAction = {
+ type: ActionTypes.REMOVE_TOPOLOGY_OPTION,
+ topologyId: 'services',
+ option: 'namespace',
+ value: 'scope'
};
let nextState = initialState;
- nextState = reducer(nextState, {
- type: ActionTypes.RECEIVE_TOPOLOGIES,
- topologies
- });
- nextState = reducer(nextState, {
- type: ActionTypes.CLICK_TOPOLOGY,
- topologyId: 'services'
- });
+ nextState = reducer(nextState, { type: ActionTypes.RECEIVE_TOPOLOGIES, topologies});
+ nextState = reducer(nextState, { type: ActionTypes.CLICK_TOPOLOGY, topologyId: 'services' });
- nextState = reducer(nextState, action);
- expect(activeTopologyOptionsSelector(nextState).toJS()).toEqual({
- namespace: ['scope', 'monitoring'],
- pseudo: 'hide'
- });
+ nextState = reducer(nextState, addAction);
});
it('sets topology options from route', () => {
@@ -404,8 +413,8 @@ describe('RootReducer', () => {
nextState = reducer(nextState, RouteAction);
nextState = reducer(nextState, ReceiveTopologiesAction);
nextState = reducer(nextState, ClickTopologyAction);
- expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('off');
- expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('off');
+ expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['off']);
+ expect(getUrlState(nextState).topologyOptions.topo1.option1).toEqual(['off']);
});
// nodes delta
@@ -423,7 +432,7 @@ describe('RootReducer', () => {
it('shows nodes that were received', () => {
let nextState = initialState;
nextState = reducer(nextState, ReceiveNodesDeltaAction);
- expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
+ expect(nextState.get('nodes').toJS()).toInclude(NODE_SET);
});
it('knows a route was set', () => {
@@ -439,11 +448,11 @@ describe('RootReducer', () => {
nextState = reducer(nextState, ClickNodeAction);
expect(nextState.get('selectedNodeId')).toBe('n1');
- expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
+ expect(nextState.get('nodes').toJS()).toInclude(NODE_SET);
nextState = reducer(nextState, deSelectNode);
expect(nextState.get('selectedNodeId')).toBe(null);
- expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
+ expect(nextState.get('nodes').toJS()).toInclude(NODE_SET);
});
it('keeps showing nodes on navigating back after node click', () => {
@@ -460,7 +469,7 @@ describe('RootReducer', () => {
RouteAction.state = {topologyId: 'topo1', selectedNodeId: null};
nextState = reducer(nextState, RouteAction);
expect(nextState.get('selectedNodeId')).toBe(null);
- expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
+ expect(nextState.get('nodes').toJS()).toInclude(NODE_SET);
});
it('closes details when changing topologies', () => {
@@ -486,12 +495,12 @@ describe('RootReducer', () => {
it('resets topology on websocket reconnect', () => {
let nextState = initialState;
nextState = reducer(nextState, ReceiveNodesDeltaAction);
- expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
+ expect(nextState.get('nodes').toJS()).toInclude(NODE_SET);
nextState = reducer(nextState, CloseWebsocketAction);
expect(nextState.get('websocketClosed')).toBeTruthy();
// keep showing old nodes
- expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
+ expect(nextState.get('nodes').toJS()).toInclude(NODE_SET);
nextState = reducer(nextState, OpenWebsocketAction);
expect(nextState.get('websocketClosed')).toBeFalsy();
diff --git a/client/app/scripts/reducers/root.js b/client/app/scripts/reducers/root.js
index ebe271b95..536d5a3f2 100644
--- a/client/app/scripts/reducers/root.js
+++ b/client/app/scripts/reducers/root.js
@@ -87,15 +87,32 @@ export const initialState = makeMap({
zoomCache: makeMap(),
});
+function calcSelectType(topology) {
+ const result = {
+ ...topology,
+ options: topology.options && topology.options.map((option) => {
+ // Server doesn't return the `selectType` key unless the option is something other than `one`.
+ // Default to `one` if undefined, so the component doesn't have to handle this.
+ option.selectType = option.selectType || 'one';
+ return option;
+ })
+ };
+
+ if (topology.sub_topologies) {
+ result.sub_topologies = topology.sub_topologies.map(calcSelectType);
+ }
+ return result;
+}
+
// adds ID field to topology (based on last part of URL path) and save urls in
// map for easy lookup
function processTopologies(state, nextTopologies) {
// filter out hidden topos
const visibleTopologies = filterHiddenTopologies(nextTopologies);
-
+ // set `selectType` field for topology and sub_topologies options (recursive).
+ const topologiesWithSelectType = visibleTopologies.map(calcSelectType);
// add IDs to topology objects in-place
- const topologiesWithId = updateTopologyIds(visibleTopologies);
-
+ const topologiesWithId = updateTopologyIds(topologiesWithSelectType);
// cache URLs by ID
state = state.set('topologyUrlsById',
setTopologyUrlsById(state.get('topologyUrlsById'), topologiesWithId));
@@ -106,8 +123,7 @@ function processTopologies(state, nextTopologies) {
}
function setTopology(state, topologyId) {
- state = state.set('currentTopology', findTopologyById(
- state.get('topologies'), topologyId));
+ state = state.set('currentTopology', findTopologyById(state.get('topologies'), topologyId));
return state.set('currentTopologyId', topologyId);
}
@@ -118,7 +134,7 @@ function setDefaultTopologyOptions(state, topologyList) {
topology.get('options').forEach((option) => {
const optionId = option.get('id');
const defaultValue = option.get('defaultValue');
- defaultOptions = defaultOptions.set(optionId, defaultValue);
+ defaultOptions = defaultOptions.set(optionId, [defaultValue]);
});
}
@@ -185,7 +201,7 @@ export function rootReducer(state = initialState, action) {
}
state = state.setIn(
['topologyOptions', topologyId, action.option],
- action.value
+ [action.value]
);
}
return state;